package sync import ( "bufio" "encoding/hex" "fmt" "path" "strings" "unicode" "github.com/julienschmidt/httprouter" "srs.epita.fr/fic-server/libfic" ) func BuildFilesListInto(i Importer, exercice fic.Exercice, into string) (files []string, digests map[string][]byte, errs []string) { // If no files directory, don't display error if !i.exists(path.Join(exercice.Path, into)) { return } // Parse DIGESTS.txt if digs, err := getFileContent(i, path.Join(exercice.Path, into, "DIGESTS.txt")); err != nil { errs = append(errs, fmt.Sprintf("%q: unable to read DIGESTS.txt: %s", path.Base(exercice.Path), err)) } else { digests = map[string][]byte{} for nline, d := range strings.Split(digs, "\n") { if dsplt := strings.SplitN(d, " ", 2); len(dsplt) < 2 { errs = append(errs, fmt.Sprintf("%q: unable to parse DIGESTS.txt line %d: invalid format", path.Base(exercice.Path), nline+1)) continue } else if hash, err := hex.DecodeString(dsplt[0]); err != nil { errs = append(errs, fmt.Sprintf("%q: unable to parse DIGESTS.txt line %d: %s", path.Base(exercice.Path), nline+1, err)) continue } else { digests[strings.TrimFunc(dsplt[1], unicode.IsSpace)] = hash } } } // 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" || fname == ".gitattributes" { continue } if matched, _ := path.Match("*.[0-9][0-9]", fname); matched { fname = fname[:len(fname)-3] } else if matched, _ := path.Match("*[0-9][0-9]", fname); matched { fname = fname[:len(fname)-2] } else if matched, _ := path.Match("*_MERGED", fname); matched { continue } fileFound := false for _, f := range files { if fname == f { fileFound = true break } } if !fileFound { files = append(files, fname) } } } return } // 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 } // CheckExerciceFiles checks that remote files have the right digest. func CheckExerciceFiles(i Importer, exercice fic.Exercice) (files []string, errs []string) { flist, digests, berrs := BuildFilesListInto(i, exercice, "files") errs = append(errs, berrs...) for _, fname := range flist { w, hash160, hash512 := fic.CreateHashBuffers() if err := getFile(i, 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)) } files = append(files, fname) } 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()) } files, digests, berrs := BuildFilesListInto(i, exercice, "files") 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)) } } return } // 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, fmt.Errorf("%q", errs) } } else { return nil, fmt.Errorf("%q", errs) } } else { return nil, fmt.Errorf("%q", errs) } }