2022-06-06 10:55:39 +00:00
package main
import (
"flag"
2022-06-07 15:04:07 +00:00
"io/ioutil"
2022-06-06 10:55:39 +00:00
"log"
"os"
2022-06-07 15:13:30 +00:00
"os/signal"
2022-06-06 10:55:39 +00:00
"path"
2022-06-07 14:06:36 +00:00
"path/filepath"
2022-06-06 10:55:39 +00:00
"strconv"
2022-06-07 15:13:30 +00:00
"syscall"
2022-06-07 22:09:49 +00:00
"time"
2022-06-07 15:04:07 +00:00
"gopkg.in/fsnotify.v1"
2022-06-06 10:55:39 +00:00
)
var (
TeamsDir string
skipInitialSync bool
2023-04-06 01:48:52 +00:00
dryRun bool
2022-06-06 10:55:39 +00:00
)
func main ( ) {
flag . StringVar ( & TeamsDir , "teams" , "./TEAMS" , "Base directory where save teams JSON files" )
2022-06-07 15:04:07 +00:00
var debugINotify = flag . Bool ( "debuginotify" , false , "Show skipped inotofy events" )
2023-04-06 01:48:52 +00:00
flag . BoolVar ( & dryRun , "dry-run" , dryRun , "Don't perform any write action, just display" )
2022-06-06 10:55:39 +00:00
flag . BoolVar ( & skipInitialSync , "skipinitialsync" , skipInitialSync , "Skip the initial synchronization" )
2023-04-03 10:04:48 +00:00
flag . BoolVar ( & noValidateChallenge , "no-validate-challenge" , noValidateChallenge , "Consider challenge validation as a standard award (if each exercice hasn't been imported on their side)" )
2022-06-07 15:04:07 +00:00
daemon := flag . Bool ( "watch" , false , "Enable daemon mode by watching the directory" )
2022-06-07 14:06:36 +00:00
tspath := flag . String ( "timestamp-file" , "./REMOTE/timestamp" , "Path to the file storing the last timestamp" )
exercicespath := flag . String ( "exercices-file" , "./REMOTE/exercices-bindings.json" , "Path to the file containing the ID bindings" )
coeff := flag . Float64 ( "global-coeff" , 10.0 , "Coefficient to use to multiply all scores before passing them to the other platform" )
2022-06-06 10:55:39 +00:00
flag . Parse ( )
api := AirbusAPI {
2022-06-07 22:09:49 +00:00
BaseURL : "https://portal.european-cybercup.lan/api" ,
2022-06-06 10:55:39 +00:00
}
if v , exists := os . LookupEnv ( "AIRBUS_BASEURL" ) ; exists {
api . BaseURL = v
}
if v , exists := os . LookupEnv ( "AIRBUS_TOKEN" ) ; exists {
api . Token = v
}
2023-04-06 01:48:52 +00:00
2022-06-06 10:55:39 +00:00
if v , exists := os . LookupEnv ( "AIRBUS_SESSIONID" ) ; exists {
var err error
2022-06-07 14:06:36 +00:00
api . SessionID , err = strconv . ParseInt ( v , 10 , 64 )
2022-06-06 10:55:39 +00:00
if err != nil {
log . Fatal ( "AIRBUS_SESSIONID is invalid: " , err . Error ( ) )
}
2023-04-06 01:48:52 +00:00
} else if v , exists := os . LookupEnv ( "AIRBUS_SESSION_NAME" ) ; exists {
sessions , err := api . GetSessions ( )
if err != nil {
log . Fatal ( "Unable to retrieve session: " , err )
}
for _ , session := range sessions {
if session . Name == v {
api . SessionID = session . ID
break
}
}
2023-04-06 13:37:05 +00:00
if api . SessionID == 0 {
log . Fatal ( "Session ID not found" )
} else {
log . Println ( "Session ID discovered: " , api . SessionID )
}
2022-06-06 10:55:39 +00:00
}
2023-04-06 01:48:52 +00:00
2022-06-07 22:09:49 +00:00
if v , exists := os . LookupEnv ( "AIRBUS_SESSIONUUID" ) ; exists {
api . SessionUUID = v
}
2022-06-06 10:55:39 +00:00
log . SetPrefix ( "[challenge-sync-airbus] " )
TeamsDir = path . Clean ( TeamsDir )
2022-06-07 14:06:36 +00:00
// Load the timestamp
ts , err := loadTS ( * tspath )
2022-06-06 10:55:39 +00:00
if err != nil {
2022-06-07 14:06:36 +00:00
log . Fatal ( "Unable to open timestamp file: " , err . Error ( ) )
2022-06-06 10:55:39 +00:00
}
2022-06-07 14:06:36 +00:00
// Load teams.json
teamsbindings , err := getTeams ( filepath . Join ( TeamsDir , "teams.json" ) )
if err != nil {
log . Fatal ( "Unable to open teams bindings file: " , err . Error ( ) )
}
2023-04-06 13:37:05 +00:00
log . Println ( "Team bindings loaded: " , len ( teamsbindings ) )
2022-06-07 14:06:36 +00:00
w := Walker {
2023-04-06 01:48:52 +00:00
LastSync : ts ,
Teams : teamsbindings ,
API : api ,
Coeff : * coeff ,
}
if err = w . fetchTeams ( ) ; err != nil {
log . Fatal ( "Unable to fetch Airbus teams: " , err . Error ( ) )
}
if ! noValidateChallenge {
// Load exercices bindings
w . Exercices , err = ReadExercicesBindings ( * exercicespath )
if err != nil {
log . Fatal ( "Unable to open exercices bindings file: " , err . Error ( ) )
}
2022-06-06 10:55:39 +00:00
}
2022-06-07 15:04:07 +00:00
if ! skipInitialSync {
2023-04-06 13:37:05 +00:00
err = w . BalanceScores ( )
2022-06-07 15:04:07 +00:00
if err != nil {
2023-04-06 13:37:05 +00:00
log . Println ( "Something goes wrong during score balance: " , err . Error ( ) )
2022-06-07 15:04:07 +00:00
}
// save current timestamp for teams
err = saveTS ( * tspath , w . LastSync )
if err != nil {
log . Fatal ( "Unable to save timestamp file: " , err . Error ( ) )
}
2022-06-07 14:06:36 +00:00
}
2022-06-06 10:55:39 +00:00
2023-04-06 13:37:05 +00:00
log . Println ( "initial sync done" )
2022-06-07 15:04:07 +00:00
if daemon != nil && * daemon {
// Watch teams.json and scores.json
log . Println ( "Registering directory events..." )
watcher , err := fsnotify . NewWatcher ( )
if err != nil {
log . Fatal ( err )
}
defer watcher . Close ( )
2022-06-06 10:55:39 +00:00
2022-06-07 15:04:07 +00:00
if err := watchsubdir ( watcher , TeamsDir ) ; err != nil {
log . Fatal ( err )
}
2022-06-07 15:13:30 +00:00
// Register SIGUSR1, SIGUSR2
interrupt1 := make ( chan os . Signal , 1 )
signal . Notify ( interrupt1 , syscall . SIGHUP )
interrupt2 := make ( chan os . Signal , 1 )
signal . Notify ( interrupt2 , syscall . SIGUSR1 )
interrupt3 := make ( chan os . Signal , 1 )
signal . Notify ( interrupt3 , syscall . SIGUSR2 )
2022-06-07 22:09:49 +00:00
ticker := time . NewTicker ( 5 * time . Second )
2022-06-07 15:13:30 +00:00
2022-06-07 15:04:07 +00:00
watchedNotify := fsnotify . Create
for {
select {
2022-06-07 15:13:30 +00:00
case <- interrupt1 :
log . Println ( "SIGHUP received, reloading files..." )
teamsbindings , err := getTeams ( filepath . Join ( TeamsDir , "teams.json" ) )
if err != nil {
log . Println ( "Unable to open teams bindings file: " , err . Error ( ) )
return
}
w . Teams = teamsbindings
2023-04-06 01:48:52 +00:00
if err = w . fetchTeams ( ) ; err != nil {
log . Fatal ( "Unable to fetch teams: " , err . Error ( ) )
}
2022-06-07 15:13:30 +00:00
// save current timestamp for teams
err = saveTS ( * tspath , w . LastSync )
if err != nil {
log . Fatal ( "Unable to save timestamp file: " , err . Error ( ) )
}
log . Println ( "SIGHUP treated." )
case <- interrupt2 :
log . Println ( "SIGUSR1 received, resynching all teams" )
// Iterate over teams scores
2022-06-07 22:09:49 +00:00
err = filepath . WalkDir ( TeamsDir , w . WalkScoreSync )
2022-06-07 15:13:30 +00:00
if err != nil {
log . Printf ( "Something goes wrong during walking" )
}
// save current timestamp for teams
err = saveTS ( * tspath , w . LastSync )
if err != nil {
log . Fatal ( "Unable to save timestamp file: " , err . Error ( ) )
}
log . Println ( "SIGUSR1 treated." )
case <- interrupt3 :
log . Println ( "SIGUSR2 received, resynching all teams from teams.json" )
teamsbindings , err := getTeams ( filepath . Join ( TeamsDir , "teams.json" ) )
if err != nil {
log . Println ( "Unable to open teams bindings file: " , err . Error ( ) )
return
}
w . Teams = teamsbindings
2023-04-06 01:48:52 +00:00
if err = w . fetchTeams ( ) ; err != nil {
log . Fatal ( "Unable to fetch teams: " , err . Error ( ) )
}
2022-06-07 15:13:30 +00:00
// FIXME
log . Println ( "SIGUSR2 treated." )
2022-06-07 15:04:07 +00:00
case ev := <- watcher . Events :
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 )
}
2023-04-06 13:37:05 +00:00
} else if err == nil && ev . Op & watchedNotify == watchedNotify && d . Mode ( ) . IsRegular ( ) {
2022-06-07 15:04:07 +00:00
if * debugINotify {
log . Println ( "Treating event:" , ev , "for" , ev . Name )
}
if filepath . Base ( ev . Name ) == "scores.json" {
go w . treat ( ev . Name )
} else if filepath . Base ( ev . Name ) == "teams.json" {
teamsbindings , err := getTeams ( filepath . Join ( TeamsDir , "teams.json" ) )
if err != nil {
log . Println ( "Unable to open teams bindings file: " , err . Error ( ) )
return
}
w . Teams = teamsbindings
2023-04-06 01:48:52 +00:00
if err = w . fetchTeams ( ) ; err != nil {
log . Fatal ( "Unable to fetch teams: " , err . Error ( ) )
}
2022-06-07 15:04:07 +00:00
}
2023-04-06 13:37:05 +00:00
} else if err == nil && ev . Op & fsnotify . Write == fsnotify . Write {
2022-06-07 15:04:07 +00:00
log . Println ( "FSNOTIFY WRITE SEEN. Prefer looking at them, as it appears files are not atomically moved." )
watchedNotify = fsnotify . Write
2023-04-06 13:37:05 +00:00
} else if err == nil && * debugINotify {
2022-06-07 15:04:07 +00:00
log . Println ( "Skipped event:" , ev , "for" , ev . Name )
}
case err := <- watcher . Errors :
log . Println ( "error:" , err )
2022-06-07 15:13:30 +00:00
case <- ticker . C :
// save current timestamp for teams
err = saveTS ( * tspath , w . LastSync )
if err != nil {
log . Fatal ( "Unable to save timestamp file: " , err . Error ( ) )
}
2022-06-07 15:04:07 +00:00
}
}
}
2022-06-07 15:13:30 +00:00
// save current timestamp for teams
err = saveTS ( * tspath , w . LastSync )
if err != nil {
log . Fatal ( "Unable to save timestamp file: " , err . Error ( ) )
}
2022-06-07 15:04:07 +00:00
}
func watchsubdir ( watcher * fsnotify . Watcher , pathname string ) error {
log . Println ( "Watch new directory:" , pathname )
if err := watcher . Add ( pathname ) ; err != nil {
return err
}
if ds , err := ioutil . ReadDir ( pathname ) ; err != nil {
return err
} else {
for _ , d := range ds {
p := path . Join ( pathname , d . Name ( ) )
if d . IsDir ( ) && d . Name ( ) != ".tmp" && d . Mode ( ) & os . ModeSymlink == 0 {
if err := watchsubdir ( watcher , p ) ; err != nil {
return err
}
}
}
return nil
2022-06-06 10:55:39 +00:00
}
}