2017-11-11 04:06:16 +00:00
package common
2017-02-10 13:39:13 +00:00
2017-06-25 09:56:39 +00:00
import (
"bytes"
2018-09-26 08:05:25 +00:00
"compress/gzip"
2019-03-11 08:47:45 +00:00
"crypto/sha256"
"encoding/hex"
2018-05-14 08:56:56 +00:00
"errors"
2018-06-24 13:49:29 +00:00
"fmt"
2017-06-25 09:56:39 +00:00
"io/ioutil"
2018-09-26 08:05:25 +00:00
"mime"
2017-06-25 09:56:39 +00:00
"net/http"
2017-09-03 04:50:31 +00:00
"os"
"path/filepath"
2018-09-26 08:05:25 +00:00
"strconv"
"strings"
"sync"
2018-05-14 08:56:56 +00:00
2018-10-27 03:21:02 +00:00
"github.com/Azareal/Gosora/tmpl_client"
2017-06-25 09:56:39 +00:00
)
2016-12-05 07:21:17 +00:00
2017-11-11 04:06:16 +00:00
type SFileList map [ string ] SFile
var StaticFiles SFileList = make ( map [ string ] SFile )
2017-12-01 02:04:29 +00:00
var staticFileMutex sync . RWMutex
2017-11-11 04:06:16 +00:00
2017-09-03 04:50:31 +00:00
type SFile struct {
Data [ ] byte
GzipData [ ] byte
2019-03-21 22:59:41 +00:00
Sha256 string
2019-04-19 10:39:17 +00:00
OName string
2017-09-03 04:50:31 +00:00
Pos int64
Length int64
GzipLength int64
2019-07-23 10:34:44 +00:00
StrGzipLength string
2017-09-03 04:50:31 +00:00
Mimetype string
Info os . FileInfo
2016-12-05 07:21:17 +00:00
FormattedModTime string
}
2017-09-18 17:03:52 +00:00
type CSSData struct {
2018-03-11 09:33:49 +00:00
Phrases map [ string ] string
2016-12-05 07:21:17 +00:00
}
2018-05-14 08:56:56 +00:00
func ( list SFileList ) JSTmplInit ( ) error {
2018-05-15 05:59:52 +00:00
DebugLog ( "Initialising the client side templates" )
2018-05-14 10:21:18 +00:00
return filepath . Walk ( "./tmpl_client" , func ( path string , f os . FileInfo , err error ) error {
2019-02-10 05:52:26 +00:00
if f . IsDir ( ) || strings . HasSuffix ( path , "template_list.go" ) || strings . HasSuffix ( path , "stub.go" ) {
2018-05-14 08:56:56 +00:00
return nil
}
path = strings . Replace ( path , "\\" , "/" , - 1 )
DebugLog ( "Processing client template " + path )
data , err := ioutil . ReadFile ( path )
if err != nil {
return err
}
2018-06-24 13:49:29 +00:00
path = strings . TrimPrefix ( path , "tmpl_client/" )
2019-04-28 10:08:05 +00:00
tmplName := strings . TrimSuffix ( path , ".jgo" )
2018-06-24 13:49:29 +00:00
shortName := strings . TrimPrefix ( tmplName , "template_" )
2019-10-26 23:11:09 +00:00
replace := func ( data [ ] byte , replaceThis string , withThis string ) [ ] byte {
2018-05-14 08:56:56 +00:00
return bytes . Replace ( data , [ ] byte ( replaceThis ) , [ ] byte ( withThis ) , - 1 )
}
2019-11-07 03:53:29 +00:00
startIndex , hasFunc := skipAllUntilCharsExist ( data , 0 , [ ] byte ( "if(tmplInits===undefined)" ) )
2018-05-14 08:56:56 +00:00
if ! hasFunc {
2019-11-07 03:53:29 +00:00
return errors . New ( "no init map found" )
2018-05-14 08:56:56 +00:00
}
2019-11-07 03:53:29 +00:00
data = data [ startIndex - len ( [ ] byte ( "if(tmplInits===undefined)" ) ) : ]
data = replace ( data , "// nolint" , "" )
2018-05-14 08:56:56 +00:00
data = replace ( data , "func " , "function " )
data = replace ( data , " error {\n" , " {\nlet out = \"\"\n" )
2018-06-24 13:49:29 +00:00
funcIndex , hasFunc := skipAllUntilCharsExist ( data , 0 , [ ] byte ( "function Template_" ) )
if ! hasFunc {
return errors . New ( "no template function found" )
}
spaceIndex , hasSpace := skipUntilIfExists ( data , funcIndex , ' ' )
2018-05-14 08:56:56 +00:00
if ! hasSpace {
return errors . New ( "no spaces found after the template function name" )
}
endBrace , hasBrace := skipUntilIfExists ( data , spaceIndex , ')' )
if ! hasBrace {
return errors . New ( "no right brace found after the template function name" )
}
2018-06-24 13:49:29 +00:00
fmt . Println ( "spaceIndex: " , spaceIndex )
fmt . Println ( "endBrace: " , endBrace )
fmt . Println ( "string(data[spaceIndex:endBrace]): " , string ( data [ spaceIndex : endBrace ] ) )
2018-05-14 08:56:56 +00:00
preLen := len ( data )
data = replace ( data , string ( data [ spaceIndex : endBrace ] ) , "" )
2018-12-27 05:42:41 +00:00
data = replace ( data , "))\n" , " \n" )
2018-05-14 08:56:56 +00:00
endBrace -= preLen - len ( data ) // Offset it as we've deleted portions
2018-06-24 13:49:29 +00:00
fmt . Println ( "new endBrace: " , endBrace )
fmt . Println ( "data: " , string ( data ) )
2018-05-14 08:56:56 +00:00
2019-10-26 23:11:09 +00:00
/ * showPos := func ( data [ ] byte , index int ) ( out string ) {
2018-05-14 08:56:56 +00:00
out = "["
for j , char := range data {
if index == j {
out += "[" + string ( char ) + "] "
} else {
out += string ( char ) + " "
}
}
return out + "]"
2018-05-15 05:59:52 +00:00
} * /
2018-05-14 08:56:56 +00:00
// ? Can we just use a regex? I'm thinking of going more efficient, or just outright rolling wasm, this is a temp hack in a place where performance doesn't particularly matter
2019-10-26 23:11:09 +00:00
each := func ( phrase string , handle func ( index int ) ) {
2018-05-15 05:59:52 +00:00
//fmt.Println("find each '" + phrase + "'")
2019-10-26 23:11:09 +00:00
index := endBrace
2018-06-24 13:49:29 +00:00
if index < 0 {
panic ( "index under zero: " + strconv . Itoa ( index ) )
}
2018-05-14 08:56:56 +00:00
var foundIt bool
for {
2018-05-15 05:59:52 +00:00
//fmt.Println("in index: ", index)
//fmt.Println("pos: ", showPos(data, index))
2018-05-14 08:56:56 +00:00
index , foundIt = skipAllUntilCharsExist ( data , index , [ ] byte ( phrase ) )
if ! foundIt {
break
}
handle ( index )
}
}
each ( "strconv.Itoa(" , func ( index int ) {
2018-12-27 05:42:41 +00:00
braceAt , hasEndBrace := skipUntilIfExistsOrLine ( data , index , ')' )
2018-05-14 08:56:56 +00:00
if hasEndBrace {
data [ braceAt ] = ' ' // Blank it
}
} )
2018-12-27 05:42:41 +00:00
each ( "[]byte(" , func ( index int ) {
braceAt , hasEndBrace := skipUntilIfExistsOrLine ( data , index , ')' )
2018-05-14 08:56:56 +00:00
if hasEndBrace {
data [ braceAt ] = ' ' // Blank it
}
} )
2018-12-27 05:42:41 +00:00
each ( "StringToBytes(" , func ( index int ) {
braceAt , hasEndBrace := skipUntilIfExistsOrLine ( data , index , ')' )
2018-11-22 07:21:43 +00:00
if hasEndBrace {
data [ braceAt ] = ' ' // Blank it
}
} )
2018-12-27 05:42:41 +00:00
each ( "w.Write(" , func ( index int ) {
braceAt , hasEndBrace := skipUntilIfExistsOrLine ( data , index , ')' )
2018-12-15 04:39:50 +00:00
if hasEndBrace {
data [ braceAt ] = ' ' // Blank it
}
} )
2018-12-27 05:42:41 +00:00
each ( "RelativeTime(" , func ( index int ) {
braceAt , _ := skipUntilIfExistsOrLine ( data , index , 10 )
if data [ braceAt - 1 ] == ' ' {
2019-02-10 05:52:26 +00:00
data [ braceAt - 1 ] = ' ' // Blank it
2018-05-14 08:56:56 +00:00
}
} )
each ( "if " , func ( index int ) {
2018-05-15 05:59:52 +00:00
//fmt.Println("if index: ", index)
2018-12-27 05:42:41 +00:00
braceAt , hasBrace := skipUntilIfExistsOrLine ( data , index , '{' )
2018-05-14 08:56:56 +00:00
if hasBrace {
if data [ braceAt - 1 ] != ' ' {
panic ( "couldn't find space before brace, found ' " + string ( data [ braceAt - 1 ] ) + "' instead" )
}
data [ braceAt - 1 ] = ')' // Drop a brace here to satisfy JS
}
} )
2018-06-24 13:49:29 +00:00
each ( "for _, item := range " , func ( index int ) {
//fmt.Println("for index: ", index)
braceAt , hasBrace := skipUntilIfExists ( data , index , '{' )
if hasBrace {
if data [ braceAt - 1 ] != ' ' {
panic ( "couldn't find space before brace, found ' " + string ( data [ braceAt - 1 ] ) + "' instead" )
}
data [ braceAt - 1 ] = ')' // Drop a brace here to satisfy JS
}
} )
data = replace ( data , "for _, item := range " , "for(item of " )
2018-05-14 08:56:56 +00:00
data = replace ( data , "w.Write([]byte(" , "out += " )
2018-12-15 04:39:50 +00:00
data = replace ( data , "w.Write(StringToBytes(" , "out += " )
2018-05-14 08:56:56 +00:00
data = replace ( data , "w.Write(" , "out += " )
data = replace ( data , "strconv.Itoa(" , "" )
2018-09-26 08:05:25 +00:00
data = replace ( data , "strconv.FormatInt(" , "" )
2019-11-07 03:53:29 +00:00
data = replace ( data , " c." , "" )
2018-11-01 06:51:04 +00:00
data = replace ( data , "phrases." , "" )
2018-09-26 08:05:25 +00:00
data = replace ( data , ", 10;" , "" )
2019-11-07 03:53:29 +00:00
data = replace ( data , "var plist = GetTmplPhrasesBytes(" + shortName + "_tmpl_phrase_id)" , "const plist = tmplPhrases[\"" + tmplName + "\"];" )
2018-11-22 07:21:43 +00:00
data = replace ( data , "var cached_var_" , "let cached_var_" )
2019-02-10 05:52:26 +00:00
data = replace ( data , ` tmpl_ ` + shortName + ` _vars, ok := tmpl_ ` + shortName + ` _i. ` , ` /* ` )
2018-12-27 05:42:41 +00:00
data = replace ( data , "[]byte(" , "" )
data = replace ( data , "StringToBytes(" , "" )
2019-02-10 05:52:26 +00:00
data = replace ( data , "RelativeTime(tmpl_" + shortName + "_vars." , "tmpl_" + shortName + "_vars.Relative" )
2018-12-27 05:42:41 +00:00
// TODO: Format dates properly on the client side
data = replace ( data , ".Format(\"2006-01-02 15:04:05\"" , "" )
data = replace ( data , ", 10" , "" )
2018-05-14 08:56:56 +00:00
data = replace ( data , "if " , "if(" )
data = replace ( data , "return nil" , "return out" )
data = replace ( data , " )" , ")" )
data = replace ( data , " \n" , "\n" )
data = replace ( data , "\n" , ";\n" )
data = replace ( data , "{;" , "{" )
data = replace ( data , "};" , "}" )
2018-06-24 13:49:29 +00:00
data = replace ( data , "[;" , "[" )
2018-05-14 08:56:56 +00:00
data = replace ( data , ";;" , ";" )
2018-06-24 13:49:29 +00:00
data = replace ( data , ",;" , "," )
data = replace ( data , "=;" , "=" )
data = replace ( data , ` ,
} ) ;
} ` , "\n\t];" )
data = replace ( data , ` =
} ` , "= []" )
2018-05-14 08:56:56 +00:00
2019-02-28 07:28:17 +00:00
fragset := tmpl . GetFrag ( shortName )
if fragset != nil {
2019-09-29 04:56:39 +00:00
sfrags := [ ] byte ( "let " + shortName + "_frags = [];\n" )
2019-02-28 07:28:17 +00:00
for _ , frags := range fragset {
sfrags = append ( sfrags , [ ] byte ( shortName + "_frags.push(`" + string ( frags ) + "`);\n" ) ... )
}
data = append ( sfrags , data ... )
2018-05-14 08:56:56 +00:00
}
2019-02-28 07:28:17 +00:00
data = replace ( data , "\n;" , "\n" )
2018-05-14 08:56:56 +00:00
2019-02-28 07:28:17 +00:00
for name , _ := range Themes {
if strings . HasSuffix ( shortName , "_" + name ) {
2019-03-16 11:31:10 +00:00
data = append ( data , "\nvar Template_" + strings . TrimSuffix ( shortName , "_" + name ) + " = Template_" + shortName + ";" ... )
2019-02-28 07:28:17 +00:00
break
}
2018-05-14 08:56:56 +00:00
}
path = tmplName + ".js"
DebugLog ( "js path: " , path )
2019-09-29 04:56:39 +00:00
ext := filepath . Ext ( "/tmpl_client/" + path )
2019-04-27 10:22:39 +00:00
gzipData , err := CompressBytesGzip ( data )
2018-08-21 08:00:35 +00:00
if err != nil {
return err
}
2018-05-14 08:56:56 +00:00
2019-03-11 08:47:45 +00:00
// Get a checksum for CSPs and cache busting
hasher := sha256 . New ( )
hasher . Write ( data )
2019-03-21 22:59:41 +00:00
checksum := hex . EncodeToString ( hasher . Sum ( nil ) )
2019-03-11 08:47:45 +00:00
2019-08-14 10:39:04 +00:00
list . Set ( "/s/" + path , SFile { data , gzipData , checksum , path + "?h=" + checksum , 0 , int64 ( len ( data ) ) , int64 ( len ( gzipData ) ) , strconv . Itoa ( len ( gzipData ) ) , mime . TypeByExtension ( ext ) , f , f . ModTime ( ) . UTC ( ) . Format ( http . TimeFormat ) } )
2018-05-14 08:56:56 +00:00
DebugLogf ( "Added the '%s' static file." , path )
return nil
} )
}
2017-11-11 04:06:16 +00:00
func ( list SFileList ) Init ( ) error {
2017-09-03 04:50:31 +00:00
return filepath . Walk ( "./public" , func ( path string , f os . FileInfo , err error ) error {
2017-06-25 09:56:39 +00:00
if f . IsDir ( ) {
return nil
}
2017-09-03 04:50:31 +00:00
path = strings . Replace ( path , "\\" , "/" , - 1 )
2017-06-25 09:56:39 +00:00
data , err := ioutil . ReadFile ( path )
if err != nil {
return err
}
2017-09-03 04:50:31 +00:00
path = strings . TrimPrefix ( path , "public/" )
var ext = filepath . Ext ( "/public/" + path )
2018-08-22 01:32:07 +00:00
mimetype := mime . TypeByExtension ( ext )
2019-03-11 08:47:45 +00:00
// Get a checksum for CSPs and cache busting
hasher := sha256 . New ( )
hasher . Write ( data )
2019-03-21 22:59:41 +00:00
checksum := hex . EncodeToString ( hasher . Sum ( nil ) )
2019-03-11 08:47:45 +00:00
2018-08-22 01:32:07 +00:00
// Avoid double-compressing images
var gzipData [ ] byte
if mimetype != "image/jpeg" && mimetype != "image/png" && mimetype != "image/gif" {
2019-04-27 10:22:39 +00:00
gzipData , err = CompressBytesGzip ( data )
2018-08-22 01:32:07 +00:00
if err != nil {
return err
}
// Don't use Gzip if we get meagre gains from it as it takes longer to process the responses
if len ( gzipData ) >= ( len ( data ) + 100 ) {
gzipData = nil
} else {
diff := len ( data ) - len ( gzipData )
if diff <= len ( data ) / 100 {
gzipData = nil
}
}
2018-08-21 08:00:35 +00:00
}
2017-06-25 09:56:39 +00:00
2019-08-14 10:39:04 +00:00
list . Set ( "/s/" + path , SFile { data , gzipData , checksum , path + "?h=" + checksum , 0 , int64 ( len ( data ) ) , int64 ( len ( gzipData ) ) , strconv . Itoa ( len ( gzipData ) ) , mimetype , f , f . ModTime ( ) . UTC ( ) . Format ( http . TimeFormat ) } )
2017-06-25 09:56:39 +00:00
2018-02-19 04:26:01 +00:00
DebugLogf ( "Added the '%s' static file." , path )
2017-06-25 09:56:39 +00:00
return nil
} )
}
2017-01-07 06:31:04 +00:00
2017-11-11 04:06:16 +00:00
func ( list SFileList ) Add ( path string , prefix string ) error {
2017-01-07 06:31:04 +00:00
data , err := ioutil . ReadFile ( path )
if err != nil {
return err
}
fi , err := os . Open ( path )
if err != nil {
return err
}
f , err := fi . Stat ( )
if err != nil {
return err
}
2017-06-25 09:56:39 +00:00
2019-09-29 04:56:39 +00:00
ext := filepath . Ext ( path )
2017-01-07 06:31:04 +00:00
path = strings . TrimPrefix ( path , prefix )
2019-04-27 10:22:39 +00:00
gzipData , err := CompressBytesGzip ( data )
2018-08-21 08:00:35 +00:00
if err != nil {
return err
}
2017-06-25 09:56:39 +00:00
2019-03-11 08:47:45 +00:00
// Get a checksum for CSPs and cache busting
hasher := sha256 . New ( )
hasher . Write ( data )
2019-03-21 22:59:41 +00:00
checksum := hex . EncodeToString ( hasher . Sum ( nil ) )
2019-03-11 08:47:45 +00:00
2019-08-14 10:39:04 +00:00
list . Set ( "/s" + path , SFile { data , gzipData , checksum , path + "?h=" + checksum , 0 , int64 ( len ( data ) ) , int64 ( len ( gzipData ) ) , strconv . Itoa ( len ( gzipData ) ) , mime . TypeByExtension ( ext ) , f , f . ModTime ( ) . UTC ( ) . Format ( http . TimeFormat ) } )
2017-06-25 09:56:39 +00:00
2018-02-19 04:26:01 +00:00
DebugLogf ( "Added the '%s' static file" , path )
2017-01-07 06:31:04 +00:00
return nil
2017-02-10 13:39:13 +00:00
}
2017-12-01 02:04:29 +00:00
func ( list SFileList ) Get ( name string ) ( file SFile , exists bool ) {
staticFileMutex . RLock ( )
defer staticFileMutex . RUnlock ( )
file , exists = list [ name ]
return file , exists
}
func ( list SFileList ) Set ( name string , data SFile ) {
staticFileMutex . Lock ( )
defer staticFileMutex . Unlock ( )
list [ name ] = data
}
2019-04-27 10:22:39 +00:00
func CompressBytesGzip ( in [ ] byte ) ( [ ] byte , error ) {
2017-02-10 13:39:13 +00:00
var buff bytes . Buffer
2018-08-21 08:00:35 +00:00
gz , err := gzip . NewWriterLevel ( & buff , gzip . BestCompression )
if err != nil {
return nil , err
}
_ , err = gz . Write ( in )
if err != nil {
return nil , err
}
err = gz . Close ( )
if err != nil {
return nil , err
}
return buff . Bytes ( ) , nil
2017-02-10 13:39:13 +00:00
}