Pierre-Olivier Mercier
651d428223
All checks were successful
continuous-integration/drone/push Build is passing
194 lines
6.1 KiB
Go
194 lines
6.1 KiB
Go
package sync
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/multierr"
|
|
_ "golang.org/x/crypto/blake2b"
|
|
|
|
"srs.epita.fr/fic-server/libfic"
|
|
)
|
|
|
|
type importHint struct {
|
|
Line int
|
|
Hint *fic.EHint
|
|
FlagsDeps []int64
|
|
}
|
|
|
|
func buildExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (hints []importHint, errs error) {
|
|
params, _, err := parseExerciceParams(i, exercice.Path)
|
|
if err != nil {
|
|
errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, 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 == nil {
|
|
h.Cost = exercice.Gain / 4
|
|
} else {
|
|
h.Cost = *hint.Cost
|
|
}
|
|
|
|
if hint.Filename != "" {
|
|
if hint.Content != "" {
|
|
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("content and filename can't be filled at the same time")))
|
|
continue
|
|
} else if !i.Exists(path.Join(exercice.Path, "files", hint.Filename)) {
|
|
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: File not found", hint.Filename)))
|
|
continue
|
|
} else {
|
|
// Handle files as downloadable content
|
|
if res, err := i.importFile(path.Join(exercice.Path, "files", 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 = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: unable to import hint file: %w", hint.Filename, err)))
|
|
continue
|
|
} else if s, ok := res.(string); !ok {
|
|
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: unable to import hint file: invalid string returned as filename", hint.Filename)))
|
|
continue
|
|
} else {
|
|
h.Content = s
|
|
}
|
|
}
|
|
} else if hint.Content == "" {
|
|
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("content and filename can't be empty at the same time")))
|
|
continue
|
|
} else {
|
|
// Call checks hooks
|
|
for _, hk := range hooks.mdTextHooks {
|
|
for _, err := range multierr.Errors(hk(h.Content, exercice.Language, exceptions)) {
|
|
errs = multierr.Append(errs, NewHintError(exercice, h, n, err))
|
|
}
|
|
}
|
|
|
|
if h.Content, err = ProcessMarkdown(i, fixnbsp(hint.Content), exercice.Path); err != nil {
|
|
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("error during markdown formating: %w", err)))
|
|
}
|
|
}
|
|
|
|
// Call checks hooks
|
|
for _, hook := range hooks.hintHooks {
|
|
for _, e := range multierr.Errors(hook(h, exercice, exceptions)) {
|
|
errs = multierr.Append(errs, NewHintError(exercice, h, n, e))
|
|
}
|
|
}
|
|
|
|
newHint := importHint{
|
|
Line: n + 1,
|
|
Hint: h,
|
|
}
|
|
|
|
// Read dependency to flag
|
|
for _, nf := range hint.NeedFlag {
|
|
newHint.FlagsDeps = append(newHint.FlagsDeps, nf.Id)
|
|
}
|
|
|
|
hints = append(hints, newHint)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// CheckExerciceHints checks if all hints are corrects..
|
|
func CheckExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) ([]importHint, error) {
|
|
exceptions = exceptions.GetFileExceptions("challenge.toml", "challenge.txt")
|
|
|
|
hints, errs := buildExerciceHints(i, exercice, exceptions)
|
|
|
|
for _, hint := range hints {
|
|
if hint.Hint.Cost >= exercice.Gain {
|
|
errs = multierr.Append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("hint's cost is higher than exercice gain")))
|
|
}
|
|
}
|
|
|
|
return hints, errs
|
|
}
|
|
|
|
// SyncExerciceHints reads the content of files/ directories and import it as EHint for the given challenge.
|
|
func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int64]fic.Flag, exceptions *CheckExceptions) (hintsBindings map[int]*fic.EHint, errs error) {
|
|
if _, err := exercice.WipeHints(); err != nil {
|
|
errs = multierr.Append(errs, err)
|
|
} else {
|
|
exceptions = exceptions.GetFileExceptions("challenge.toml", "challenge.txt")
|
|
|
|
hints, berrs := buildExerciceHints(i, exercice, exceptions)
|
|
errs = multierr.Append(errs, berrs)
|
|
|
|
hintsBindings = map[int]*fic.EHint{}
|
|
|
|
for _, hint := range hints {
|
|
// Import hint
|
|
if h, err := exercice.AddHint(hint.Hint.Title, hint.Hint.Content, hint.Hint.Cost); err != nil {
|
|
errs = multierr.Append(errs, NewHintError(exercice, hint.Hint, hint.Line, err))
|
|
} else {
|
|
hintsBindings[hint.Line] = h
|
|
|
|
// Handle hints dependencies on flags
|
|
for _, nf := range hint.FlagsDeps {
|
|
if f, ok := flagsBindings[nf]; ok {
|
|
if herr := h.AddDepend(f); herr != nil {
|
|
errs = multierr.Append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("error hint dependency to flag #%d: %w", nf, herr)))
|
|
}
|
|
} else {
|
|
errs = multierr.Append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("error hint dependency to flag #%d: Unexistant flag", nf)))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// ApiListRemoteExerciceHints is an accessor letting foreign packages to access remote exercice hints.
|
|
func ApiGetRemoteExerciceHints(c *gin.Context) {
|
|
theme, exceptions, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
|
|
if theme != nil {
|
|
exercice, _, _, eexceptions, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil, exceptions)
|
|
if exercice != nil {
|
|
hints, errs := CheckExerciceHints(GlobalImporter, exercice, eexceptions)
|
|
if hints != nil {
|
|
c.JSON(http.StatusOK, hints)
|
|
return
|
|
}
|
|
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
|
return
|
|
}
|
|
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
|
return
|
|
}
|
|
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
|
|
}
|