2017-12-09 00:21:58 +00:00
package sync
import (
2022-05-19 10:45:32 +00:00
"bytes"
2017-12-09 00:21:58 +00:00
"fmt"
2024-03-18 09:34:42 +00:00
"hash/adler32"
2023-06-14 15:34:18 +00:00
"image"
2022-05-16 09:38:46 +00:00
"net/http"
2022-05-24 15:36:33 +00:00
"net/url"
2017-12-09 00:21:58 +00:00
"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"
2022-05-16 09:38:46 +00:00
"github.com/gin-gonic/gin"
2022-05-19 10:45:32 +00:00
"github.com/yuin/goldmark"
2023-11-22 11:16:53 +00:00
"go.uber.org/multierr"
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
)
2022-11-05 14:26:57 +00:00
// Set AllowWIPExercice if WIP exercices are accepted.
var AllowWIPExercice bool = false
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.
2021-11-22 14:35:07 +00:00
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
2023-11-25 16:13:31 +00:00
} 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 {
2023-11-25 16:13:31 +00:00
if _ , err := i . ListDir ( path . Join ( theme . Path , dir ) ) ; err == nil {
2021-05-13 23:25:08 +00:00
if dir [ 0 ] != '.' && strings . Contains ( dir , "-" ) {
2019-07-05 23:39:35 +00:00
exercices = append ( exercices , dir )
}
2017-12-09 00:21:58 +00:00
}
}
}
return exercices , nil
}
2021-11-22 14:35:07 +00:00
func buildDependancyMap ( i Importer , theme * fic . Theme ) ( dmap map [ int64 ] * fic . Exercice , err error ) {
2018-12-05 04:02:27 +00:00
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 {
2021-11-22 14:35:07 +00:00
dmap = map [ int64 ] * fic . Exercice { }
2018-12-05 04:02:27 +00:00
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
}
2021-11-22 14:35:07 +00:00
var e * fic . Exercice
2018-12-05 04:02:27 +00:00
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 {
2020-04-15 05:39:38 +00:00
err = fmt . Errorf ( "%q is not a valid exercice directory: missing id prefix" , edir )
2018-12-05 04:02:27 +00:00
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 {
2020-04-15 05:39:38 +00:00
err = fmt . Errorf ( "%q: invalid exercice identifier: %s" , edir , err )
2018-12-05 04:02:27 +00:00
return
}
2024-03-18 09:34:42 +00:00
// ID 0: peak a deterministic-random-ordered ID instead
if eid == 0 {
eid = int ( adler32 . Checksum ( [ ] byte ( edir_splt [ 1 ] ) ) )
}
2018-12-05 04:02:27 +00:00
ename = edir_splt [ 1 ]
return
}
2019-07-04 18:12:59 +00:00
// BuildExercice creates an Exercice from a given importer.
2023-11-22 11:16:53 +00:00
func BuildExercice ( i Importer , theme * fic . Theme , epath string , dmap * map [ int64 ] * fic . Exercice , exceptions_in * CheckExceptions ) ( e * fic . Exercice , p ExerciceParams , eid int , exceptions * CheckExceptions , edir string , errs error ) {
2019-07-04 18:12:59 +00:00
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 {
2023-07-09 08:31:54 +00:00
// Ignore eid if we are certain this is an exercice directory, eid will be 0
if ! i . Exists ( path . Join ( epath , "title.txt" ) ) {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "unable to parse exercice directory: %w" , err ) , theme ) )
2023-07-09 17:05:58 +00:00
return nil , p , eid , exceptions_in , edir , errs
2023-07-09 08:31:54 +00:00
}
2018-12-05 04:02:27 +00:00
}
2023-06-14 15:24:15 +00:00
// Get exceptions
2023-07-09 17:05:58 +00:00
exceptions = LoadExerciceException ( i , theme , e , exceptions_in )
2022-10-29 15:03:57 +00:00
//log.Printf("Kept repochecker exceptions for this exercice: %v", exceptions)
2024-03-15 16:46:50 +00:00
if theme != nil {
e . Language = theme . Language
}
2023-01-17 17:26:04 +00:00
// Overwrite language if language.txt exists
if language , err := GetFileContent ( i , path . Join ( epath , "language.txt" ) ) ; err == nil {
language = strings . TrimSpace ( language )
if strings . Contains ( language , "\n" ) {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "language.txt: Language can't contain new lines" ) , theme ) )
2023-01-17 17:26:04 +00:00
} else {
e . Language = language
}
}
2022-01-20 15:16:52 +00:00
// Overwrite title if title.txt exists
2022-05-24 19:52:58 +00:00
if myTitle , err := GetFileContent ( i , path . Join ( epath , "title.txt" ) ) ; err == nil {
2022-01-20 15:16:52 +00:00
myTitle = strings . TrimSpace ( myTitle )
if strings . Contains ( myTitle , "\n" ) {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "title.txt: Title can't contain new lines" ) , theme ) )
2022-01-20 15:16:52 +00:00
} else {
e . Title = myTitle
}
}
2022-11-05 14:26:57 +00:00
// Character reserved for WIP exercices
if len ( e . Title ) > 0 && e . Title [ 0 ] == '%' {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "title can't contain start by '%%'" ) , theme ) )
2022-11-05 14:26:57 +00:00
}
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
2023-06-14 19:04:56 +00:00
if i . Exists ( path . Join ( epath , "AUTHORS.txt" ) ) {
if authors , err := getAuthors ( i , epath ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "unable to get AUTHORS.txt: %w" , err ) ) )
2023-06-14 19:04:56 +00:00
} else {
// Format authors
e . Authors = strings . Join ( authors , ", " )
}
}
2023-10-13 14:04:42 +00:00
// Process headline
if i . Exists ( path . Join ( epath , "headline.txt" ) ) {
e . Headline , err = GetFileContent ( i , path . Join ( epath , "headline.txt" ) )
} else if i . Exists ( path . Join ( epath , "headline.md" ) ) {
e . Headline , err = GetFileContent ( i , path . Join ( epath , "headline.md" ) )
}
if err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "unable to get exercice's headline: %w" , err ) ) )
2023-10-13 14:04:42 +00:00
}
if e . Headline != "" {
// Call checks hooks
for _ , h := range hooks . mdTextHooks {
2023-11-22 11:16:53 +00:00
for _ , err := range multierr . Errors ( h ( e . Headline , e . Language , exceptions . GetFileExceptions ( "headline.md" , "headline.txt" ) ) ) {
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "headline.md: %w" , err ) ) )
2023-10-13 14:04:42 +00:00
}
}
}
2018-12-05 04:02:27 +00:00
// Texts to format using Markdown
2023-05-03 08:54:02 +00:00
if i . Exists ( path . Join ( epath , "overview.txt" ) ) {
2022-05-24 19:52:58 +00:00
e . Overview , err = GetFileContent ( i , path . Join ( epath , "overview.txt" ) )
2023-05-03 08:54:02 +00:00
} else if i . Exists ( path . Join ( epath , "overview.md" ) ) {
2022-05-24 19:52:58 +00:00
e . Overview , err = GetFileContent ( i , path . Join ( epath , "overview.md" ) )
2022-05-19 14:39:24 +00:00
} else {
err = fmt . Errorf ( "Unable to find overview.txt nor overview.md" )
}
2018-12-05 04:02:27 +00:00
if err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "overview.txt: %s" , err ) , theme ) )
2018-12-05 04:02:27 +00:00
} else {
2018-12-09 22:59:20 +00:00
e . Overview = fixnbsp ( e . Overview )
2022-05-19 10:45:32 +00:00
2022-10-29 15:03:57 +00:00
// Call checks hooks
for _ , h := range hooks . mdTextHooks {
2023-11-22 11:16:53 +00:00
for _ , err := range multierr . Errors ( h ( e . Overview , e . Language , exceptions . GetFileExceptions ( "overview.md" , "overview.txt" ) ) ) {
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "overview.md: %w" , err ) ) )
2022-10-29 15:03:57 +00:00
}
}
2022-05-19 10:45:32 +00:00
var buf bytes . Buffer
2023-10-13 14:04:42 +00:00
if e . Headline == "" {
err := goldmark . Convert ( [ ] byte ( strings . Split ( e . Overview , "\n" ) [ 0 ] ) , & buf )
if err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "overview.md: an error occurs during markdown formating of the headline: %w" , err ) , theme ) )
2023-10-13 14:04:42 +00:00
} else {
e . Headline = string ( buf . Bytes ( ) )
}
2022-05-19 10:45:32 +00:00
}
2022-05-19 14:39:24 +00:00
if e . Overview , err = ProcessMarkdown ( i , e . Overview , epath ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "overview.md: an error occurs during markdown formating: %w" , err ) , theme ) )
2022-05-19 14:39:24 +00:00
}
2018-12-05 04:02:27 +00:00
}
2023-05-03 08:54:02 +00:00
if i . Exists ( path . Join ( epath , "statement.txt" ) ) {
2022-05-24 19:52:58 +00:00
e . Statement , err = GetFileContent ( i , path . Join ( epath , "statement.txt" ) )
2023-05-03 08:54:02 +00:00
} else if i . Exists ( path . Join ( epath , "statement.md" ) ) {
2022-05-24 19:52:58 +00:00
e . Statement , err = GetFileContent ( i , path . Join ( epath , "statement.md" ) )
2022-05-19 14:39:24 +00:00
} else {
err = fmt . Errorf ( "Unable to find statement.txt nor statement.md" )
}
2018-12-05 04:02:27 +00:00
if err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "statement.md: %w" , err ) , theme ) )
2018-12-05 04:02:27 +00:00
} else {
2022-10-29 15:03:57 +00:00
// Call checks hooks
for _ , h := range hooks . mdTextHooks {
2023-11-22 11:16:53 +00:00
for _ , err := range multierr . Errors ( h ( e . Statement , e . Language , exceptions . GetFileExceptions ( "statement.md" , "statement.txt" ) ) ) {
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "statement.md: %w" , err ) ) )
2022-10-29 15:03:57 +00:00
}
}
2018-12-09 22:59:20 +00:00
if e . Statement , err = ProcessMarkdown ( i , fixnbsp ( e . Statement ) , epath ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "statement.md: an error occurs during markdown formating: %w" , err ) , theme ) )
2018-12-05 04:02:27 +00:00
}
}
2023-05-03 08:54:02 +00:00
if i . Exists ( path . Join ( epath , "finished.txt" ) ) {
2022-05-24 19:52:58 +00:00
e . Finished , err = GetFileContent ( i , path . Join ( epath , "finished.txt" ) )
2023-05-03 08:54:02 +00:00
} else if i . Exists ( path . Join ( epath , "finished.md" ) ) {
2022-12-09 15:07:06 +00:00
e . Finished , err = GetFileContent ( i , path . Join ( epath , "finished.md" ) )
}
if err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "finished.md: %w" , err ) , theme ) )
2022-12-09 15:07:06 +00:00
} else if len ( e . Finished ) > 0 {
// Call checks hooks
for _ , h := range hooks . mdTextHooks {
2023-11-22 11:16:53 +00:00
for _ , err := range multierr . Errors ( h ( e . Finished , e . Language , exceptions . GetFileExceptions ( "finished.md" , "finished.txt" ) ) ) {
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "finished.md: %w" , err ) ) )
2022-10-29 15:03:57 +00:00
}
2022-12-09 15:07:06 +00:00
}
2022-10-29 15:03:57 +00:00
2022-12-09 15:07:06 +00:00
if e . Finished , err = ProcessMarkdown ( i , e . Finished , epath ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "finished.md: an error occurs during markdown formating: %w" , err ) , theme ) )
2018-12-05 04:02:27 +00:00
}
}
2017-12-09 00:21:58 +00:00
2023-06-14 15:34:18 +00:00
if i . Exists ( path . Join ( epath , "heading.jpg" ) ) {
e . Image = path . Join ( epath , "heading.jpg" )
} else if i . Exists ( path . Join ( epath , "heading.png" ) ) {
e . Image = path . Join ( epath , "heading.png" )
2024-03-15 16:46:50 +00:00
} else if theme == nil || theme . Image == "" {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "heading.jpg: No such file" ) ) )
2023-06-14 15:34:18 +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 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewChallengeTxtError ( e , 0 , err , theme ) )
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 ( ) {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewChallengeTxtError ( e , 0 , fmt . Errorf ( "unknown key %q found, check https://fic.srs.epita.fr/doc/files/challenge/" , k ) , theme ) )
2019-07-21 20:31:43 +00:00
}
}
2022-11-05 14:26:57 +00:00
e . WIP = p . WIP
if p . WIP && ! AllowWIPExercice {
2024-05-16 09:10:30 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "exercice declared Work In Progress in challenge.toml" ) , theme ) )
2022-11-05 14:26:57 +00:00
}
2018-12-05 04:02:27 +00:00
if p . Gain == 0 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewChallengeTxtError ( e , 0 , fmt . Errorf ( "Undefined gain for challenge" ) , theme ) )
2018-12-05 04:02:27 +00:00
} else {
e . Gain = p . Gain
}
// Handle dependency
if len ( p . Dependencies ) > 0 {
2024-03-15 16:46:50 +00:00
if len ( p . Dependencies [ 0 ] . Theme ) > 0 && ( theme == nil || p . Dependencies [ 0 ] . Theme != theme . Name ) {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "unable to treat dependency to another theme (%q): not implemented." , p . Dependencies [ 0 ] . Theme ) , theme ) )
2018-12-05 04:02:27 +00:00
} else {
if dmap == nil {
if dmap2 , err := buildDependancyMap ( i , theme ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "unable to build dependency map: %w" , err ) , theme ) )
2018-12-05 04:02:27 +00:00
} 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
}
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "Unable to find required exercice dependancy %d (available at time of processing: %s)" , p . Dependencies [ 0 ] . Id , strings . Join ( dmap_keys , "," ) ) , theme ) )
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
2021-12-10 17:55:47 +00:00
// Handle resolutions
resolutionFound := false
2018-12-05 04:02:27 +00:00
e . VideoURI = path . Join ( epath , "resolution.mp4" )
2023-05-03 08:54:02 +00:00
if ! i . Exists ( e . VideoURI ) {
2018-12-05 04:02:27 +00:00
e . VideoURI = ""
2022-11-21 17:55:38 +00:00
} else if size , err := GetFileSize ( i , e . VideoURI ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "resolution.mp4: %w" , err ) , theme ) )
2019-01-19 00:13:17 +00:00
e . VideoURI = ""
} else if size == 0 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "resolution.mp4: The file is empty!" ) , theme ) )
2019-01-19 00:13:17 +00:00
e . VideoURI = ""
2021-12-10 17:55:47 +00:00
} else {
2022-06-12 10:15:39 +00:00
e . VideoURI = strings . Replace ( url . PathEscape ( path . Join ( "$RFILES$" , e . VideoURI ) ) , "%2F" , "/" , - 1 )
2021-12-10 17:55:47 +00:00
resolutionFound = true
}
writeup := path . Join ( epath , "resolution.md" )
2023-05-03 08:54:02 +00:00
if ! i . Exists ( writeup ) {
2021-12-10 17:55:47 +00:00
writeup = path . Join ( epath , "resolution.txt" )
}
2023-05-03 08:54:02 +00:00
if i . Exists ( writeup ) {
2022-11-21 17:55:38 +00:00
if size , err := GetFileSize ( i , writeup ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "resolution.md: %w" , err ) , theme ) )
2021-12-10 17:55:47 +00:00
} else if size == 0 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "resolution.md: The file is empty!" ) , theme ) )
2022-05-24 19:52:58 +00:00
} else if e . Resolution , err = GetFileContent ( i , writeup ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "resolution.md: %w" , err ) , theme ) )
2021-12-10 17:55:47 +00:00
} else {
2022-11-24 12:10:19 +00:00
// Call checks hooks
for _ , h := range hooks . mdTextHooks {
2023-11-22 11:16:53 +00:00
for _ , err := range multierr . Errors ( h ( e . Resolution , e . Language , exceptions . GetFileExceptions ( "resolution.md" ) , p . GetRawFlags ( ) ... ) ) {
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "resolution.md: %w" , err ) ) )
2022-11-24 12:10:19 +00:00
}
}
if e . Resolution , err = ProcessMarkdown ( i , e . Resolution , epath ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "resolution.md: error during markdown processing: %w" , err ) , theme ) )
2022-11-24 12:10:19 +00:00
} else {
resolutionFound = true
}
2021-12-10 17:55:47 +00:00
}
}
if ! resolutionFound {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , ErrResolutionNotFound , theme ) )
2018-12-05 04:02:27 +00:00
}
2017-12-09 00:21:58 +00:00
2022-10-29 15:03:57 +00:00
// Call checks hooks
for _ , h := range hooks . exerciceHooks {
2023-11-22 11:16:53 +00:00
for _ , err := range multierr . Errors ( h ( e , exceptions ) ) {
errs = multierr . Append ( errs , NewExerciceError ( e , err ) )
2022-10-29 15:03:57 +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.
2023-11-22 11:16:53 +00:00
func SyncExercice ( i Importer , theme * fic . Theme , epath string , dmap * map [ int64 ] * fic . Exercice , exceptions_in * CheckExceptions ) ( e * fic . Exercice , eid int , exceptions * CheckExceptions , errs error ) {
2019-07-04 18:12:59 +00:00
var err error
var p ExerciceParams
2023-11-22 11:16:53 +00:00
var berrors error
2019-07-04 18:12:59 +00:00
2023-07-09 17:05:58 +00:00
e , p , eid , exceptions , _ , berrors = BuildExercice ( i , theme , epath , dmap , exceptions_in )
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , berrors )
2019-07-04 18:12:59 +00:00
if e != nil {
2023-06-14 15:34:18 +00:00
if len ( e . Image ) > 0 {
if _ , err := i . importFile ( e . Image ,
func ( filePath string , origin string ) ( interface { } , error ) {
if err := resizePicture ( filePath , image . Rect ( 0 , 0 , 500 , 300 ) ) ; err != nil {
return nil , err
}
e . Image = strings . TrimPrefix ( filePath , fic . FilesDir )
2024-03-18 10:26:01 +00:00
e . BackgroundColor , _ = getBackgroundColor ( filePath )
2023-06-14 15:34:18 +00:00
// If the theme has no image yet, use the first exercice's image found
2024-03-24 19:17:25 +00:00
if theme != nil && theme . Image == "" {
theme . Image = e . Image
_ , err := theme . Update ( )
if err != nil {
return nil , err
}
}
2023-06-14 15:34:18 +00:00
2024-03-24 19:17:25 +00:00
return nil , nil
2023-06-14 15:34:18 +00:00
} ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "unable to import heading image: %w" , err ) ) )
2023-06-14 15:34:18 +00:00
}
}
2019-07-04 18:12:59 +00:00
// Create or update the exercice
err = theme . SaveNamedExercice ( e )
if err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "error on exercice save: %w" , err ) , theme ) )
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 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "unable to wipe tags: %w" , err ) , theme ) )
2019-07-04 18:12:59 +00:00
}
for _ , tag := range p . Tags {
if _ , err := e . AddTag ( tag ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewExerciceError ( e , fmt . Errorf ( "unable to add tag: %w" , err ) , theme ) )
2019-07-04 18:12:59 +00:00
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.
2023-11-22 11:16:53 +00:00
func SyncExercices ( i Importer , theme * fic . Theme , exceptions * CheckExceptions ) ( exceptions_out map [ int ] * CheckExceptions , errs error ) {
2019-07-04 18:12:59 +00:00
if exercices , err := GetExercices ( i , theme ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , err )
2018-12-05 04:02:27 +00:00
} else {
2023-07-24 21:37:44 +00:00
exceptions_out = make ( map [ int ] * CheckExceptions )
2018-12-05 04:02:27 +00:00
emap := map [ string ] int { }
dmap , _ := buildDependancyMap ( i , theme )
for _ , edir := range exercices {
2023-07-09 17:05:58 +00:00
e , eid , ex_exceptions , cur_errs := SyncExercice ( i , theme , path . Join ( theme . Path , edir ) , & dmap , exceptions )
2019-07-04 18:12:59 +00:00
if e != nil {
emap [ e . Title ] = eid
2021-11-22 14:35:07 +00:00
dmap [ int64 ( eid ) ] = e
2023-07-09 17:05:58 +00:00
exceptions_out [ eid ] = ex_exceptions
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , cur_errs )
2019-07-04 18:12:59 +00:00
}
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.
2022-05-16 09:38:46 +00:00
func ApiListRemoteExercices ( c * gin . Context ) {
2024-03-15 16:46:50 +00:00
if c . Params . ByName ( "thid" ) == "_" {
exercices , err := GetExercices ( GlobalImporter , & fic . Theme { Path : StandaloneExercicesDirectory } )
if err != nil {
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : err . Error ( ) } )
return
}
c . JSON ( http . StatusOK , exercices )
return
}
2022-10-29 15:03:57 +00:00
theme , _ , errs := BuildTheme ( GlobalImporter , c . Params . ByName ( "thid" ) )
2019-09-06 23:25:42 +00:00
if theme != nil {
2022-05-16 09:38:46 +00:00
exercices , err := GetExercices ( GlobalImporter , theme )
if err != nil {
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : err . Error ( ) } )
return
}
c . JSON ( http . StatusOK , exercices )
2019-09-06 23:25:42 +00:00
} else {
2022-05-16 09:38:46 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , fmt . Errorf ( "%q" , errs ) )
return
2019-09-06 23:25:42 +00:00
}
}
// ApiListRemoteExercice is an accessor letting foreign packages to access remote exercice attributes.
2022-05-16 09:38:46 +00:00
func ApiGetRemoteExercice ( c * gin . Context ) {
2024-03-15 16:46:50 +00:00
if c . Params . ByName ( "thid" ) == "_" {
exercice , _ , _ , _ , _ , errs := BuildExercice ( GlobalImporter , nil , path . Join ( StandaloneExercicesDirectory , c . Params . ByName ( "exid" ) ) , nil , nil )
if exercice != nil {
c . JSON ( http . StatusOK , exercice )
return
} else {
c . JSON ( http . StatusInternalServerError , gin . H { "errmsg" : fmt . Errorf ( "%q" , errs ) } )
return
}
}
2022-10-29 15:03:57 +00:00
theme , exceptions , errs := BuildTheme ( GlobalImporter , c . Params . ByName ( "thid" ) )
2019-09-06 23:25:42 +00:00
if theme != nil {
2023-07-09 17:05:58 +00:00
exercice , _ , _ , _ , _ , errs := BuildExercice ( GlobalImporter , theme , path . Join ( theme . Path , c . Params . ByName ( "exid" ) ) , nil , exceptions )
2019-09-06 23:25:42 +00:00
if exercice != nil {
2022-05-16 09:38:46 +00:00
c . JSON ( http . StatusOK , exercice )
return
2019-09-06 23:25:42 +00:00
} else {
2022-05-16 09:38:46 +00:00
c . JSON ( http . StatusInternalServerError , gin . H { "errmsg" : fmt . Errorf ( "%q" , errs ) } )
return
2019-09-06 23:25:42 +00:00
}
} else {
2022-05-16 09:38:46 +00:00
c . JSON ( http . StatusInternalServerError , gin . H { "errmsg" : fmt . Errorf ( "%q" , errs ) } )
return
2019-09-06 23:25:42 +00:00
}
2017-12-09 00:21:58 +00:00
}