2017-12-09 00:21:58 +00:00
package sync
import (
2019-07-05 17:07:28 +00:00
"bufio"
2017-12-09 00:21:58 +00:00
"encoding/hex"
2020-01-18 23:54:00 +00:00
"errors"
2017-12-09 00:21:58 +00:00
"fmt"
"path"
"strings"
"unicode"
2020-01-18 23:54:00 +00:00
"github.com/julienschmidt/httprouter"
2017-12-09 00:21:58 +00:00
"srs.epita.fr/fic-server/libfic"
)
2020-01-16 17:53:27 +00:00
func BuildFilesListInto ( i Importer , exercice fic . Exercice , into string ) ( files [ ] string , digests map [ string ] [ ] byte , errs [ ] string ) {
2018-01-06 15:50:47 +00:00
// If no files directory, don't display error
2019-07-05 17:07:28 +00:00
if ! i . exists ( path . Join ( exercice . Path , into ) ) {
2018-01-06 15:50:47 +00:00
return
}
2017-12-09 00:21:58 +00:00
2019-07-05 17:07:28 +00:00
// Parse DIGESTS.txt
if digs , err := getFileContent ( i , path . Join ( exercice . Path , into , "DIGESTS.txt" ) ) ; err != nil {
2017-12-09 00:21:58 +00:00
errs = append ( errs , fmt . Sprintf ( "%q: unable to read DIGESTS.txt: %s" , path . Base ( exercice . Path ) , err ) )
} else {
2019-07-05 17:07:28 +00:00
digests = map [ string ] [ ] byte { }
2017-12-09 00:21:58 +00:00
for nline , d := range strings . Split ( digs , "\n" ) {
2018-12-07 23:53:13 +00:00
if dsplt := strings . SplitN ( d , " " , 2 ) ; len ( dsplt ) < 2 {
2019-07-05 17:07:28 +00:00
errs = append ( errs , fmt . Sprintf ( "%q: unable to parse DIGESTS.txt line %d: invalid format" , path . Base ( exercice . Path ) , nline + 1 ) )
2017-12-09 00:21:58 +00:00
continue
} else if hash , err := hex . DecodeString ( dsplt [ 0 ] ) ; err != nil {
2019-07-05 17:07:28 +00:00
errs = append ( errs , fmt . Sprintf ( "%q: unable to parse DIGESTS.txt line %d: %s" , path . Base ( exercice . Path ) , nline + 1 , err ) )
2017-12-09 00:21:58 +00:00
continue
} else {
2018-12-07 23:53:13 +00:00
digests [ strings . TrimFunc ( dsplt [ 1 ] , unicode . IsSpace ) ] = hash
2017-12-09 00:21:58 +00:00
}
}
2019-07-05 17:07:28 +00:00
}
// Read file list
if flist , err := i . listDir ( path . Join ( exercice . Path , into ) ) ; err != nil {
errs = append ( errs , err . Error ( ) )
} else {
for _ , fname := range flist {
if fname == "DIGESTS.txt" {
continue
}
2017-12-09 00:21:58 +00:00
2020-01-19 00:06:03 +00:00
if matched , _ := path . Match ( "*.[0-9][0-9]" , fname ) ; matched {
2017-12-12 06:11:01 +00:00
fname = fname [ : len ( fname ) - 3 ]
2020-01-19 00:06:03 +00:00
} else if matched , _ := path . Match ( "*[0-9][0-9]" , fname ) ; matched {
2017-12-12 06:11:01 +00:00
fname = fname [ : len ( fname ) - 2 ]
} else if matched , _ := path . Match ( "*_MERGED" , fname ) ; matched {
continue
}
2019-07-05 17:07:28 +00:00
fileFound := false
for _ , f := range files {
if fname == f {
fileFound = true
break
2017-12-12 06:11:01 +00:00
}
}
2019-07-05 17:07:28 +00:00
if ! fileFound {
files = append ( files , fname )
2017-12-12 06:11:01 +00:00
}
2019-07-05 17:07:28 +00:00
}
}
2017-12-12 06:11:01 +00:00
2019-07-05 17:07:28 +00:00
return
}
2020-01-16 17:53:27 +00:00
// CheckExerciceFilesPresence limits remote checks to presence, don't get it to check digest.
func CheckExerciceFilesPresence ( i Importer , exercice fic . Exercice ) ( files [ ] string , errs [ ] string ) {
flist , digests , berrs := BuildFilesListInto ( i , exercice , "files" )
errs = append ( errs , berrs ... )
for _ , fname := range flist {
if ! i . exists ( path . Join ( exercice . Path , "files" , fname ) ) {
errs = append ( errs , fmt . Sprintf ( "%q: unable to read file %q: No such file or directory" , path . Base ( exercice . Path ) , fname ) )
} else if _ , ok := digests [ fname ] ; ! ok {
errs = append ( errs , fmt . Sprintf ( "%q: unable to import file %q: No digest given" , path . Base ( exercice . Path ) , fname ) )
} else {
files = append ( files , fname )
}
}
for fname := range digests {
if ! i . exists ( path . Join ( exercice . Path , "files" , fname ) ) {
errs = append ( errs , fmt . Sprintf ( "%q: unable to read file %q: No such file or directory. Check your DIGESTS.txt for legacy entries." , path . Base ( exercice . Path ) , fname ) )
}
}
return
}
2019-07-05 17:07:28 +00:00
// CheckExerciceFiles checks that remote files have the right digest.
2020-01-16 17:53:27 +00:00
func CheckExerciceFiles ( i Importer , exercice fic . Exercice ) ( files [ ] string , errs [ ] string ) {
flist , digests , berrs := BuildFilesListInto ( i , exercice , "files" )
2019-07-05 17:07:28 +00:00
errs = append ( errs , berrs ... )
for _ , fname := range flist {
w , hash160 , hash512 := fic . CreateHashBuffers ( )
if err := i . getFile ( path . Join ( exercice . Path , "files" , fname ) , bufio . NewWriter ( w ) ) ; err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: unable to read file %q: %s" , path . Base ( exercice . Path ) , fname , err ) )
continue
} else if _ , err := fic . CheckBufferHash ( hash160 , hash512 , digests [ fname ] ) ; err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: %s: %s" , path . Base ( exercice . Path ) , fname , err ) )
}
2020-01-16 17:53:27 +00:00
files = append ( files , fname )
2019-07-05 17:07:28 +00:00
}
return
}
// SyncExerciceFiles reads the content of files/ directory and import it as EFile for the given challenge.
// It takes care of DIGESTS.txt and ensure imported files match.
func SyncExerciceFiles ( i Importer , exercice fic . Exercice ) ( errs [ ] string ) {
if _ , err := exercice . WipeFiles ( ) ; err != nil {
errs = append ( errs , err . Error ( ) )
}
2020-01-16 17:53:27 +00:00
files , digests , berrs := BuildFilesListInto ( i , exercice , "files" )
2019-07-05 17:07:28 +00:00
errs = append ( errs , berrs ... )
// Import standard files
for _ , fname := range files {
if f , err := i . importFile ( path . Join ( exercice . Path , "files" , fname ) ,
func ( filePath string , origin string ) ( interface { } , error ) {
return exercice . ImportFile ( filePath , origin , digests [ fname ] )
} ) ; err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: unable to import file %q: %s" , path . Base ( exercice . Path ) , fname , err ) )
continue
} else if f . ( fic . EFile ) . Size == 0 {
errs = append ( errs , fmt . Sprintf ( "%q: WARNING imported file %q is empty!" , path . Base ( exercice . Path ) , fname ) )
2017-12-09 00:21:58 +00:00
}
}
2018-01-06 15:50:47 +00:00
return
2017-12-09 00:21:58 +00:00
}
2020-01-18 23:54:00 +00:00
// ApiGetRemoteExerciceFiles is an accessor to remote exercice files list.
func ApiGetRemoteExerciceFiles ( ps httprouter . Params , _ [ ] byte ) ( interface { } , error ) {
theme , errs := BuildTheme ( GlobalImporter , ps . ByName ( "thid" ) )
if theme != nil {
exercice , _ , _ , _ , errs := BuildExercice ( GlobalImporter , * theme , path . Join ( theme . Path , ps . ByName ( "exid" ) ) , nil )
if exercice != nil {
files , digests , errs := BuildFilesListInto ( GlobalImporter , * exercice , "files" )
if files != nil {
var ret [ ] fic . EFile
for _ , fname := range files {
fPath := path . Join ( exercice . Path , "files" , fname )
fSize , _ := getFileSize ( GlobalImporter , fPath )
ret = append ( ret , fic . EFile {
Path : fPath ,
Name : fname ,
Checksum : digests [ fname ] ,
Size : fSize ,
} )
}
return ret , nil
} else {
return nil , errors . New ( fmt . Sprintf ( "%q" , errs ) )
}
} else {
return nil , errors . New ( fmt . Sprintf ( "%q" , errs ) )
}
} else {
return nil , errors . New ( fmt . Sprintf ( "%q" , errs ) )
}
}