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"
2019-09-06 23:25:42 +00:00
"errors"
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-07-05 22:40:31 +00:00
func buildExerciceHints ( i Importer , exercice fic . Exercice ) ( hints [ ] fic . EHint , 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 )
}
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
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 ) )
}
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 ... )
2018-01-07 22:40:11 +00:00
2019-07-05 22:40:31 +00:00
for n , hint := range hints {
2018-01-10 03:42:50 +00:00
// Import hint
2018-08-17 19:13:13 +00:00
if _ , err := exercice . AddHint ( hint . Title , hint . Content , hint . Cost ) ; err != nil {
2019-07-05 22:40:31 +00:00
errs = append ( errs , fmt . Sprintf ( "%q: hint #%d %s: %s" , path . Base ( exercice . Path ) , n + 1 , hint . Title , err ) )
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 {
exercice , _ , _ , _ , errs := BuildExercice ( GlobalImporter , * theme , path . Join ( theme . Path , ps . ByName ( "exid" ) ) , nil )
if exercice != nil {
hints , errs := CheckExerciceHints ( GlobalImporter , * exercice )
if hints != nil {
return hints , nil
} else {
return hints , errors . New ( fmt . Sprintf ( "%q" , errs ) )
}
} else {
return exercice , errors . New ( fmt . Sprintf ( "%q" , errs ) )
}
} else {
return nil , errors . New ( fmt . Sprintf ( "%q" , errs ) )
}
}