182 lines
6.0 KiB
Go
182 lines
6.0 KiB
Go
package sync
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net/http"
|
|
"path"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"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 {
|
|
// Enforce file format
|
|
if path.Ext(fname) == "rar" || path.Ext(fname) == "7z" {
|
|
errs = append(errs, fmt.Sprintf("%q: WARNING %q use a forbidden archive type.", path.Base(exercice.Path), fname))
|
|
}
|
|
|
|
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(c *gin.Context) {
|
|
theme, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
|
|
if theme != nil {
|
|
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.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,
|
|
})
|
|
}
|
|
c.JSON(http.StatusOK, ret)
|
|
} else {
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
|
return
|
|
}
|
|
} else {
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
|
return
|
|
}
|
|
} else {
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Errorf("%q", errs)})
|
|
return
|
|
}
|
|
}
|