backend: new option --skipInitialGeneration to skip the full static files regeneratio...
[fic/server.git] / backend / main.go
index a0cb785b7c049776a1c9781c2c3953c0ec57df07..96c8b60a3009fa67efe3cedfaff6dd2dbbd5fed8 100644 (file)
@@ -7,6 +7,7 @@ import (
        "math/rand"
        "os"
        "path"
+       "strconv"
        "strings"
        "time"
 
@@ -30,7 +31,7 @@ func watchsubdir(watcher *fsnotify.Watcher, pathname string) error {
        } else {
                for _, d := range ds {
                        p := path.Join(pathname, d.Name())
-                       if d.IsDir() {
+                       if d.IsDir() && d.Name() != ".tmp" && d.Mode() & os.ModeSymlink == 0 {
                                if err := watchsubdir(watcher, p); err != nil {
                                        return err
                                }
@@ -43,22 +44,27 @@ func watchsubdir(watcher *fsnotify.Watcher, pathname string) error {
 }
 
 var lastRegeneration time.Time
+var skipInitialGeneration = false
 
 func reloadSettings(config settings.FICSettings) {
        if lastRegeneration != config.Generation || fic.PartialValidation != config.PartialValidation || fic.UnlockedChallenges != !config.EnableExerciceDepend || fic.FirstBlood != config.FirstBlood || fic.SubmissionCostBase != config.SubmissionCostBase {
-               lastRegeneration = config.Generation
-
                fic.PartialValidation = config.PartialValidation
                fic.UnlockedChallenges = !config.EnableExerciceDepend
 
                fic.FirstBlood = config.FirstBlood
                fic.SubmissionCostBase = config.SubmissionCostBase
 
-               log.Println("Generating files...")
-               go func() {
-                       genAll()
-                       log.Println("Full generation done")
-               }()
+               if !skipInitialGeneration {
+                       log.Println("Generating files...")
+                       go func() {
+                               genAll()
+                               log.Println("Full generation done")
+                       }()
+               } else {
+                       skipInitialGeneration = false
+                       log.Println("Regeneration skipped by option.")
+               }
+               lastRegeneration = config.Generation
        } else {
                log.Println("No change found. Skipping regeneration.")
        }
@@ -66,13 +72,17 @@ func reloadSettings(config settings.FICSettings) {
 
 func main() {
        var dsn = flag.String("dsn", fic.DSNGenerator(), "DSN to connect to the MySQL server")
+       flag.StringVar(&settings.SettingsDir, "settings", settings.SettingsDir, "Base directory where load and save settings")
        flag.StringVar(&SubmissionDir, "submission", "./submissions", "Base directory where save submissions")
        flag.StringVar(&TeamsDir, "teams", "./TEAMS", "Base directory where save teams JSON files")
        flag.StringVar(&fic.FilesDir, "files", "/files", "Request path prefix to reach files")
+       var debugINotify = flag.Bool("debuginotify", false, "Show skipped inotofy events")
+       flag.BoolVar(&skipInitialGeneration, "skipfullgeneration", skipInitialGeneration, "Skip the initial regeneration")
        flag.Parse()
 
        log.SetPrefix("[backend] ")
 
+       settings.SettingsDir = path.Clean(settings.SettingsDir)
        SubmissionDir = path.Clean(SubmissionDir)
        TeamsDir = path.Clean(TeamsDir)
 
@@ -92,7 +102,7 @@ func main() {
        defer fic.DBClose()
 
        // Load configuration
-       settings.LoadAndWatchSettings(path.Join(TeamsDir, settings.SettingsFile), reloadSettings)
+       settings.LoadAndWatchSettings(path.Join(settings.SettingsDir, settings.SettingsFile), reloadSettings)
 
        log.Println("Registering directory events...")
        watcher, err := fsnotify.NewWatcher()
@@ -105,16 +115,26 @@ func main() {
                log.Fatal(err)
        }
 
+       watchedNotify := fsnotify.Create
+
        for {
                select {
                case ev := <-watcher.Events:
-                       if d, err := os.Stat(ev.Name); err == nil && d.IsDir() && ev.Op & fsnotify.Create == fsnotify.Create {
+                       if d, err := os.Lstat(ev.Name); err == nil && ev.Op & fsnotify.Create == fsnotify.Create && d.Mode().IsDir() && d.Mode() & os.ModeSymlink == 0 && d.Name() != ".tmp" {
                                // Register new subdirectory
                                if err := watchsubdir(watcher, ev.Name); err != nil {
                                        log.Println(err)
                                }
-                       } else if ev.Op & fsnotify.Write == fsnotify.Write {
+                       } else if ev.Op & watchedNotify == watchedNotify && d.Mode().IsRegular() {
+                               if *debugINotify {
+                                       log.Println("Treating event:", ev, "for", ev.Name)
+                               }
                                go treat(ev.Name)
+                       } else if ev.Op & fsnotify.Write == fsnotify.Write {
+                               log.Println("FSNOTIFY WRITE SEEN. Prefer looking at them, as it appears files are not atomically moved.")
+                               watchedNotify = fsnotify.Write
+                       } else if *debugINotify {
+                               log.Println("Skipped event:", ev, "for", ev.Name)
                        }
                case err := <-watcher.Errors:
                        log.Println("error:", err)
@@ -128,14 +148,36 @@ func treat(raw_path string) {
 
        if len(spath) == 3 {
                if spath[1] == "_registration" {
-                       treatRegistration(raw_path)
-               } else if team, err := fic.GetTeamByInitialName(spath[1]); err != nil {
+                       treatRegistration(raw_path, spath[2])
+                       return
+               }
+
+               var team fic.Team
+
+               if strings.HasPrefix(spath[1], "_AUTH_ID_") {
+                       if serial, err := strconv.ParseInt(strings.TrimPrefix(spath[1], "_AUTH_ID_"), 16, 64); err != nil {
+                               log.Println("[ERR]", err)
+                               return
+                       } else if team, err = fic.GetTeamBySerial(serial); err != nil {
+                               log.Println("[ERR]", err)
+                               return
+                       }
+               } else if teamid, err := strconv.ParseInt(spath[1], 10, 64); err != nil {
                        log.Println("[ERR]", err)
-               } else if spath[2] == "name" {
+                       return
+               } else if team, err = fic.GetTeam(teamid); err != nil {
+                       log.Println("[ERR]", err)
+                       return
+               }
+
+               switch spath[2] {
+               case "name":
                        treatRename(raw_path, team)
-               } else if spath[2] == "hint" {
+               case "hint":
                        treatOpeningHint(raw_path, team)
-               } else {
+               case "choices":
+                       treatWantChoices(raw_path, team)
+               default:
                        treatSubmission(raw_path, team, spath[2])
                }
        } else {