2017-12-09 00:21:58 +00:00
package sync
import (
2018-01-07 22:40:11 +00:00
"bufio"
2018-02-03 01:03:08 +00:00
"crypto"
2018-01-07 22:40:11 +00:00
"encoding/hex"
2017-12-09 00:21:58 +00:00
"fmt"
2018-01-07 22:40:11 +00:00
"io"
"os"
2017-12-09 00:21:58 +00:00
"path"
2018-01-07 22:40:11 +00:00
"strings"
2017-12-09 00:21:58 +00:00
2019-09-06 23:25:42 +00:00
"github.com/julienschmidt/httprouter"
2018-02-03 01:03:08 +00:00
_ "golang.org/x/crypto/blake2b"
2019-09-06 23:25:42 +00:00
"srs.epita.fr/fic-server/libfic"
2018-01-07 22:40:11 +00:00
)
2017-12-09 00:21:58 +00:00
2019-11-25 15:18:59 +00:00
type importHint struct {
Line int
Hint fic . EHint
FlagsDeps [ ] int64
}
2021-11-22 14:35:07 +00:00
func buildExerciceHints ( i Importer , exercice * fic . Exercice ) ( hints [ ] importHint , errs [ ] string ) {
2019-07-21 20:31:43 +00:00
params , _ , err := parseExerciceParams ( i , exercice . Path )
2018-01-10 03:42:50 +00:00
if err != nil {
2018-05-12 00:01:49 +00:00
errs = append ( errs , fmt . Sprintf ( "%q: challenge.txt: %s" , path . Base ( exercice . Path ) , err ) )
2019-07-05 22:40:31 +00:00
return
2018-01-10 03:42:50 +00:00
}
2019-07-05 22:40:31 +00:00
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 )
}
2021-11-14 15:15:19 +00:00
if hint . Cost == nil {
2019-07-05 22:40:31 +00:00
h . Cost = exercice . Gain / 4
} else {
2021-11-14 15:15:19 +00:00
h . Cost = * hint . Cost
2019-07-05 22:40:31 +00:00
}
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
2018-12-09 22:59:20 +00:00
} else {
2019-07-05 22:40:31 +00:00
// 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 ( )
2018-01-10 03:42:50 +00:00
2019-07-05 22:40:31 +00:00
reader := bufio . NewReader ( fd )
if _ , err := io . Copy ( hash512 , reader ) ; err != nil {
2018-08-17 19:13:13 +00:00
return nil , err
}
}
2019-07-05 22:40:31 +00:00
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
2018-01-07 22:40:11 +00:00
}
}
2019-07-05 22:40:31 +00:00
} 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 ) )
}
2019-11-25 15:18:59 +00:00
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 )
2019-07-05 22:40:31 +00:00
}
return
}
// CheckExerciceHints checks if all hints are corrects..
2021-11-22 14:35:07 +00:00
func CheckExerciceHints ( i Importer , exercice * fic . Exercice ) ( [ ] importHint , [ ] string ) {
2019-07-05 22:40:31 +00:00
return buildExerciceHints ( i , exercice )
}
// SyncExerciceHints reads the content of hints/ directories and import it as EHint for the given challenge.
2021-11-22 14:35:07 +00:00
func SyncExerciceHints ( i Importer , exercice * fic . Exercice , flagsBindings map [ int64 ] fic . Flag ) ( hintsBindings map [ int ] * fic . EHint , errs [ ] string ) {
2019-07-05 22:40:31 +00:00
if _ , err := exercice . WipeHints ( ) ; err != nil {
errs = append ( errs , err . Error ( ) )
} else {
hints , berrs := buildExerciceHints ( i , exercice )
errs = append ( errs , berrs ... )
2018-01-07 22:40:11 +00:00
2021-11-22 14:35:07 +00:00
hintsBindings = map [ int ] * fic . EHint { }
2019-11-25 15:18:59 +00:00
for _ , hint := range hints {
2018-01-10 03:42:50 +00:00
// Import hint
2019-11-25 15:18:59 +00:00
if h , err := exercice . AddHint ( hint . Hint . Title , hint . Hint . Content , hint . Hint . Cost ) ; err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: hint #%d %s: %s" , path . Base ( exercice . Path ) , hint . Line , hint . Hint . Title , err ) )
2019-11-25 13:57:21 +00:00
} else {
2019-11-25 15:18:59 +00:00
hintsBindings [ hint . Line ] = h
// Handle hints dependencies on flags
for _ , nf := range hint . FlagsDeps {
if f , ok := flagsBindings [ nf ] ; ok {
2020-01-23 13:23:45 +00:00
if herr := h . AddDepend ( f ) ; herr != nil {
errs = append ( errs , fmt . Sprintf ( "%q: error hint #%d dependency to flag #%d: %s" , path . Base ( exercice . Path ) , hint . Line , nf , herr ) )
}
2019-11-25 15:18:59 +00:00
} else {
errs = append ( errs , fmt . Sprintf ( "%q: error hint #%d dependency to flag #%d: Unexistant flag" , path . Base ( exercice . Path ) , hint . Line , nf ) )
}
}
2017-12-09 00:21:58 +00:00
}
}
}
2018-01-07 22:40:11 +00:00
return
2017-12-09 00:21:58 +00:00
}
2019-09-06 23:25:42 +00:00
// ApiListRemoteExerciceHints is an accessor letting foreign packages to access remote exercice hints.
func ApiGetRemoteExerciceHints ( ps httprouter . Params , _ [ ] byte ) ( interface { } , error ) {
theme , errs := BuildTheme ( GlobalImporter , ps . ByName ( "thid" ) )
if theme != nil {
2021-11-22 14:35:07 +00:00
exercice , _ , _ , _ , errs := BuildExercice ( GlobalImporter , theme , path . Join ( theme . Path , ps . ByName ( "exid" ) ) , nil )
2019-09-06 23:25:42 +00:00
if exercice != nil {
2021-11-22 14:35:07 +00:00
hints , errs := CheckExerciceHints ( GlobalImporter , exercice )
2019-09-06 23:25:42 +00:00
if hints != nil {
return hints , nil
} else {
2020-04-15 05:39:38 +00:00
return hints , fmt . Errorf ( "%q" , errs )
2019-09-06 23:25:42 +00:00
}
} else {
2020-04-15 05:39:38 +00:00
return exercice , fmt . Errorf ( "%q" , errs )
2019-09-06 23:25:42 +00:00
}
} else {
2020-04-15 05:39:38 +00:00
return nil , fmt . Errorf ( "%q" , errs )
2019-09-06 23:25:42 +00:00
}
}