package sync import ( "bufio" "crypto" "encoding/hex" "fmt" "io" "os" "path" "strings" "srs.epita.fr/fic-server/libfic" _ "golang.org/x/crypto/blake2b" ) func buildExerciceHints(i Importer, exercice fic.Exercice) (hints []fic.EHint, errs []string) { params, _, err := parseExerciceParams(i, exercice.Path) if err != nil { errs = append(errs, fmt.Sprintf("%q: challenge.txt: %s", path.Base(exercice.Path), err)) return } for n, hint := range params.Hints { h := fic.EHint{} if hint.Title == "" { h.Title = fmt.Sprintf("Astuce #%d", n+1) } else { h.Title = fixnbsp(hint.Title) } if hint.Cost <= 0 { h.Cost = exercice.Gain / 4 } else { h.Cost = hint.Cost } if hint.Filename != "" { if hint.Content != "" { errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): content and filename can't be filled at the same time", path.Base(exercice.Path), hint.Title, n+1)) continue } else if !i.exists(path.Join(exercice.Path, "hints", hint.Filename)) { errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): %s: File not found", path.Base(exercice.Path), hint.Title, n+1, hint.Filename)) continue } else { // Handle files as downloadable content if res, err := i.importFile(path.Join(exercice.Path, "hints", hint.Filename), func(filePath string, origin string) (interface{}, error) { // Calculate hash hash512 := crypto.BLAKE2b_512.New() if fd, err := os.Open(filePath); err != nil { return nil, err } else { defer fd.Close() reader := bufio.NewReader(fd) if _, err := io.Copy(hash512, reader); err != nil { return nil, err } } result512 := hash512.Sum(nil) // Special format for downloadable hints: $FILES + hexhash + path from FILES/ return "$FILES" + hex.EncodeToString(result512) + strings.TrimPrefix(filePath, fic.FilesDir), nil }); err != nil { errs = append(errs, fmt.Sprintf("%q: unable to import hint file %q: %s", path.Base(exercice.Path), hint.Filename, err)) continue } else if s, ok := res.(string); !ok { errs = append(errs, fmt.Sprintf("%q: unable to import hint file %q: invalid string returned as filename", path.Base(exercice.Path), hint.Filename)) continue } else { h.Content = s } } } else if hint.Content == "" { errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): content and filename can't be empty at the same time", path.Base(exercice.Path), hint.Title, n+1)) continue } else if h.Content, err = ProcessMarkdown(i, fixnbsp(hint.Content), exercice.Path); err != nil { errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): error during markdown formating: %s", path.Base(exercice.Path), hint.Title, n+1, err)) } hints = append(hints, h) } return } // CheckExerciceHints checks if all hints are corrects.. func CheckExerciceHints(i Importer, exercice fic.Exercice) ([]fic.EHint, []string) { return buildExerciceHints(i, exercice) } // SyncExerciceHints reads the content of hints/ directories and import it as EHint for the given challenge. func SyncExerciceHints(i Importer, exercice fic.Exercice) (errs []string) { if _, err := exercice.WipeHints(); err != nil { errs = append(errs, err.Error()) } else { hints, berrs := buildExerciceHints(i, exercice) errs = append(errs, berrs...) for n, hint := range hints { // Import hint if _, err := exercice.AddHint(hint.Title, hint.Content, hint.Cost); err != nil { errs = append(errs, fmt.Sprintf("%q: hint #%d %s: %s", path.Base(exercice.Path), n+1, hint.Title, err)) } } } return }