2017-12-09 00:21:58 +00:00
package sync
import (
2018-12-05 04:02:27 +00:00
"errors"
2017-12-09 00:21:58 +00:00
"fmt"
"path"
"strconv"
2019-07-04 18:12:59 +00:00
"strings"
2017-12-09 00:21:58 +00:00
2019-07-21 20:31:43 +00:00
"github.com/BurntSushi/toml"
2019-09-06 23:25:42 +00:00
"github.com/julienschmidt/httprouter"
2018-08-17 19:18:10 +00:00
"gopkg.in/russross/blackfriday.v2"
2019-07-21 20:31:43 +00:00
2019-07-04 18:12:59 +00:00
"srs.epita.fr/fic-server/libfic"
2017-12-09 00:21:58 +00:00
)
2018-12-09 22:59:20 +00:00
func fixnbsp ( s string ) string {
return strings . Replace ( strings . Replace ( strings . Replace ( s , " ?" , " ?" , - 1 ) , " !" , " !" , - 1 ) , " :" , " :" , - 1 )
}
2019-07-04 18:12:59 +00:00
// GetExercices returns all exercice directories existing in a given theme, considering the given Importer.
func GetExercices ( i Importer , theme fic . Theme ) ( [ ] string , error ) {
2017-12-09 00:21:58 +00:00
var exercices [ ] string
2018-07-13 05:25:21 +00:00
if len ( theme . Path ) == 0 {
return [ ] string { } , nil
} else if dirs , err := i . listDir ( theme . Path ) ; err != nil {
2017-12-09 00:21:58 +00:00
return [ ] string { } , err
} else {
for _ , dir := range dirs {
2018-07-13 05:25:21 +00:00
if _ , err := i . listDir ( path . Join ( theme . Path , dir ) ) ; err == nil {
2019-07-05 23:39:35 +00:00
if dir [ 0 ] != '.' {
exercices = append ( exercices , dir )
}
2017-12-09 00:21:58 +00:00
}
}
}
return exercices , nil
}
2018-12-05 04:02:27 +00:00
func buildDependancyMap ( i Importer , theme fic . Theme ) ( dmap map [ int64 ] fic . Exercice , err error ) {
var exercices [ ] string
2019-07-04 18:12:59 +00:00
if exercices , err = GetExercices ( i , theme ) ; err != nil {
2018-12-05 04:02:27 +00:00
return
2017-12-09 00:21:58 +00:00
} else {
2018-12-05 04:02:27 +00:00
dmap = map [ int64 ] fic . Exercice { }
2017-12-09 00:21:58 +00:00
for _ , edir := range exercices {
2018-12-05 04:02:27 +00:00
var eid int
var ename string
eid , ename , err = parseExerciceDirname ( edir )
if err != nil {
2018-12-09 23:30:02 +00:00
err = nil
continue
2017-12-09 00:21:58 +00:00
}
2018-12-05 04:02:27 +00:00
var e fic . Exercice
e , err = theme . GetExerciceByTitle ( ename )
2017-12-09 00:21:58 +00:00
if err != nil {
2018-12-05 04:02:27 +00:00
return
2017-12-09 00:21:58 +00:00
}
2018-12-05 04:02:27 +00:00
dmap [ int64 ( eid ) ] = e
}
return
}
}
2017-12-09 00:21:58 +00:00
2018-12-05 04:02:27 +00:00
func parseExerciceDirname ( edir string ) ( eid int , ename string , err error ) {
edir_splt := strings . SplitN ( edir , "-" , 2 )
if len ( edir_splt ) != 2 {
err = errors . New ( fmt . Sprintf ( "%q is not a valid exercice directory: missing id prefix" , edir ) )
return
}
2017-12-17 13:30:48 +00:00
2018-12-05 04:02:27 +00:00
eid , err = strconv . Atoi ( edir_splt [ 0 ] )
if err != nil {
err = errors . New ( fmt . Sprintf ( "%q: invalid exercice identifier: %s" , edir , err ) )
return
}
ename = edir_splt [ 1 ]
return
}
2019-07-04 18:12:59 +00:00
// BuildExercice creates an Exercice from a given importer.
func BuildExercice ( i Importer , theme fic . Theme , epath string , dmap * map [ int64 ] fic . Exercice ) ( e * fic . Exercice , p ExerciceParams , eid int , edir string , errs [ ] string ) {
e = & fic . Exercice { }
2018-12-05 04:02:27 +00:00
e . Path = epath
2019-07-04 18:12:59 +00:00
edir = path . Base ( epath )
2018-12-05 04:02:27 +00:00
2019-07-04 18:12:59 +00:00
var err error
2018-12-05 04:02:27 +00:00
eid , e . Title , err = parseExerciceDirname ( edir )
if err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: unable to parse exercice directory: %s" , edir , err ) )
2019-07-04 18:12:59 +00:00
return nil , p , eid , edir , errs
2018-12-05 04:02:27 +00:00
}
e . URLId = fic . ToURLid ( e . Title )
2018-12-09 22:59:20 +00:00
e . Title = fixnbsp ( e . Title )
2018-12-05 04:02:27 +00:00
// Texts to format using Markdown
e . Overview , err = getFileContent ( i , path . Join ( epath , "overview.txt" ) )
if err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: overview.txt: %s" , edir , err ) )
} else {
2018-12-09 22:59:20 +00:00
e . Overview = fixnbsp ( e . Overview )
2018-12-05 04:02:27 +00:00
e . Headline = string ( blackfriday . Run ( [ ] byte ( strings . Split ( e . Overview , "\n" ) [ 0 ] ) ) )
if e . Overview , err = ProcessMarkdown ( i , e . Overview , epath ) ; err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: overview.txt: an error occurs during markdown formating: %s" , edir , err ) )
}
}
e . Statement , err = getFileContent ( i , path . Join ( epath , "statement.txt" ) )
if err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: statement.txt: %s" , edir , err ) )
} else {
2018-12-09 22:59:20 +00:00
if e . Statement , err = ProcessMarkdown ( i , fixnbsp ( e . Statement ) , epath ) ; err != nil {
2018-12-05 04:02:27 +00:00
errs = append ( errs , fmt . Sprintf ( "%q: statement.txt: an error occurs during markdown formating: %s" , edir , err ) )
}
}
if i . exists ( path . Join ( epath , "finished.txt" ) ) {
e . Finished , err = getFileContent ( i , path . Join ( epath , "finished.txt" ) )
if err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: finished.txt: %s" , edir , err ) )
} else {
if e . Finished , err = ProcessMarkdown ( i , e . Finished , epath ) ; err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: finished.txt: an error occurs during markdown formating: %s" , edir , err ) )
2017-12-09 00:21:58 +00:00
}
2018-12-05 04:02:27 +00:00
}
}
2017-12-09 00:21:58 +00:00
2018-12-05 04:02:27 +00:00
// Parse challenge.txt
2019-07-21 20:31:43 +00:00
var md toml . MetaData
p , md , err = parseExerciceParams ( i , epath )
2018-12-05 04:02:27 +00:00
if err != nil {
2019-07-21 20:31:43 +00:00
errs = append ( errs , fmt . Sprintf ( "%q: %s" , edir , err ) )
2018-12-05 04:02:27 +00:00
return
}
2019-07-21 20:31:43 +00:00
// Alert about unknown keys in challenge.txt
if len ( md . Undecoded ( ) ) > 0 {
for _ , k := range md . Undecoded ( ) {
2019-09-06 18:41:15 +00:00
errs = append ( errs , fmt . Sprintf ( "%q: challenge.txt: unknown key %q found, check https://srs.nemunai.re/fic/files/challenge/" , edir , k ) )
2019-07-21 20:31:43 +00:00
}
}
2018-12-05 04:02:27 +00:00
if p . Gain == 0 {
errs = append ( errs , fmt . Sprintf ( "%q: challenge.txt: Undefined gain for challenge" , edir ) )
} else {
e . Gain = p . Gain
}
// Handle dependency
if len ( p . Dependencies ) > 0 {
if len ( p . Dependencies [ 0 ] . Theme ) > 0 && p . Dependencies [ 0 ] . Theme != theme . Name {
errs = append ( errs , fmt . Sprintf ( "%q: unable to treat dependency to another theme (%q): not implemented." , edir , p . Dependencies [ 0 ] . Theme ) )
} else {
if dmap == nil {
if dmap2 , err := buildDependancyMap ( i , theme ) ; err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: unable to build dependency map: %s" , edir , err ) )
} else {
dmap = & dmap2
2018-12-02 16:53:26 +00:00
}
}
2018-12-05 04:02:27 +00:00
if dmap != nil {
for edk , ed := range * dmap {
if edk == p . Dependencies [ 0 ] . Id {
e . Depend = & ed . Id
break
}
}
if e . Depend == nil {
dmap_keys := [ ] string { }
for k , _ := range * dmap {
dmap_keys = append ( dmap_keys , fmt . Sprintf ( "%d" , k ) )
2018-09-09 20:50:06 +00:00
}
2019-07-04 18:12:59 +00:00
errs = append ( errs , fmt . Sprintf ( "%q: Unable to find required exercice dependancy %d (available at time of processing: %s)" , edir , p . Dependencies [ 0 ] . Id , strings . Join ( dmap_keys , "," ) ) )
2018-09-09 20:50:06 +00:00
}
2018-01-10 02:45:56 +00:00
}
2018-12-05 04:02:27 +00:00
}
}
2017-12-09 00:21:58 +00:00
2018-12-05 04:02:27 +00:00
// Handle video
e . VideoURI = path . Join ( epath , "resolution.mp4" )
if ! i . exists ( e . VideoURI ) {
errs = append ( errs , fmt . Sprintf ( "%q: resolution.mp4: no video file found at %s" , edir , e . VideoURI ) )
e . VideoURI = ""
2019-01-19 00:13:17 +00:00
} else if size , err := getFileSize ( i , e . VideoURI ) ; err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: resolution.mp4: " , edir , err ) )
e . VideoURI = ""
} else if size == 0 {
errs = append ( errs , fmt . Sprintf ( "%q: resolution.mp4: The file is empty!" , edir ) )
e . VideoURI = ""
2018-12-05 04:02:27 +00:00
}
2017-12-09 00:21:58 +00:00
2019-07-04 18:12:59 +00:00
return
}
2018-08-17 19:18:10 +00:00
2019-07-04 18:12:59 +00:00
// SyncExercice imports new or updates existing given exercice.
func SyncExercice ( i Importer , theme fic . Theme , epath string , dmap * map [ int64 ] fic . Exercice ) ( e * fic . Exercice , eid int , errs [ ] string ) {
var err error
var edir string
var p ExerciceParams
e , p , eid , edir , errs = BuildExercice ( i , theme , epath , dmap )
if e != nil {
// Create or update the exercice
err = theme . SaveNamedExercice ( e )
if err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: error on exercice save: %s" , edir , err ) )
2018-12-05 04:02:27 +00:00
return
}
2019-07-04 18:12:59 +00:00
// Import eercice tags
if _ , err := e . WipeTags ( ) ; err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: Unable to wipe tags: %s" , edir , err ) )
}
for _ , tag := range p . Tags {
if _ , err := e . AddTag ( tag ) ; err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: Unable to add tag: %s" , edir , err ) )
return
}
}
2018-12-05 04:02:27 +00:00
}
2019-07-04 18:12:59 +00:00
2018-12-05 04:02:27 +00:00
return
}
2018-11-18 21:44:23 +00:00
2018-12-05 04:02:27 +00:00
// SyncExercices imports new or updates existing exercices, in a given theme.
func SyncExercices ( i Importer , theme fic . Theme ) ( errs [ ] string ) {
2019-07-04 18:12:59 +00:00
if exercices , err := GetExercices ( i , theme ) ; err != nil {
2018-12-05 04:02:27 +00:00
errs = append ( errs , err . Error ( ) )
} else {
emap := map [ string ] int { }
dmap , _ := buildDependancyMap ( i , theme )
for _ , edir := range exercices {
e , eid , cur_errs := SyncExercice ( i , theme , path . Join ( theme . Path , edir ) , & dmap )
2019-07-04 18:12:59 +00:00
if e != nil {
emap [ e . Title ] = eid
dmap [ int64 ( eid ) ] = * e
errs = append ( errs , cur_errs ... )
}
2017-12-09 00:21:58 +00:00
}
2017-12-12 15:54:32 +00:00
// Remove old exercices
if exercices , err := theme . GetExercices ( ) ; err == nil {
for _ , ex := range exercices {
if _ , ok := emap [ ex . Title ] ; ! ok {
2018-12-04 22:11:45 +00:00
ex . DeleteCascade ( )
2017-12-17 15:00:32 +00:00
}
}
}
2017-12-09 00:21:58 +00:00
}
2018-12-05 04:02:27 +00:00
return
2017-12-09 00:21:58 +00:00
}
2018-05-11 23:08:37 +00:00
// ApiListRemoteExercices is an accessor letting foreign packages to access remote exercices list.
2019-09-06 23:25:42 +00:00
func ApiListRemoteExercices ( ps httprouter . Params , _ [ ] byte ) ( interface { } , error ) {
theme , errs := BuildTheme ( GlobalImporter , ps . ByName ( "thid" ) )
if theme != nil {
return GetExercices ( GlobalImporter , * theme )
} else {
return nil , errors . New ( fmt . Sprintf ( "%q" , errs ) )
}
}
// ApiListRemoteExercice is an accessor letting foreign packages to access remote exercice attributes.
func ApiGetRemoteExercice ( 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 {
return exercice , nil
} else {
return exercice , errors . New ( fmt . Sprintf ( "%q" , errs ) )
}
} else {
return nil , errors . New ( fmt . Sprintf ( "%q" , errs ) )
}
2017-12-09 00:21:58 +00:00
}