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"
"strings"
"strconv"
"srs.epita.fr/fic-server/libfic"
2018-08-17 19:18:10 +00:00
"gopkg.in/russross/blackfriday.v2"
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 )
}
2018-05-11 23:08:37 +00:00
// getExercices returns all exercice directories existing in a given theme, considering the given Importer.
2017-12-09 00:21:58 +00:00
func getExercices ( i Importer , theme fic . Theme ) ( [ ] string , error ) {
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 {
2017-12-09 00:21:58 +00:00
exercices = append ( exercices , dir )
}
}
}
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
if exercices , err = getExercices ( i , theme ) ; err != nil {
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 {
return
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
}
// 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
e . Path = epath
edir := path . Base ( epath )
eid , e . Title , err = parseExerciceDirname ( edir )
if err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: unable to parse exercice directory: %s" , edir , err ) )
return
}
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
p , err := parseExerciceParams ( i , epath )
if err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: challenge.txt: %s" , edir , err ) )
return
}
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
}
2018-12-05 04:02:27 +00:00
errs = append ( errs , fmt . Sprintf ( "%q: Unable to find required 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 = ""
}
2017-12-09 00:21:58 +00:00
2018-12-05 04:02:27 +00:00
// 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 ) )
return
}
2018-08-17 19:18:10 +00:00
2018-12-05 04:02:27 +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
}
}
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 ) {
if exercices , err := getExercices ( i , theme ) ; err != nil {
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 )
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.
2017-12-09 00:21:58 +00:00
func ApiListRemoteExercices ( theme fic . Theme , _ [ ] byte ) ( interface { } , error ) {
return getExercices ( GlobalImporter , theme )
}