From c5d0616896ddd78c6ae68119521a4fbc503bc8ee Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 7 Apr 2025 10:14:13 +0200 Subject: [PATCH 1/7] sync: Split SyncFiles function into import and files sync --- admin/api/sync.go | 2 +- admin/sync/exercice_files.go | 140 +++++++++++++++++++++-------------- admin/sync/full.go | 2 +- admin/sync/themes.go | 56 ++++++++------ 4 files changed, 118 insertions(+), 82 deletions(-) diff --git a/admin/api/sync.go b/admin/api/sync.go index 225b6e96..582c55e0 100644 --- a/admin/api/sync.go +++ b/admin/api/sync.go @@ -241,7 +241,7 @@ func declareSyncExercicesRoutes(router *gin.RouterGroup) { exceptions := sync.LoadExerciceException(sync.GlobalImporter, theme, exercice, nil) - c.JSON(http.StatusOK, flatifySyncErrors(sync.SyncExerciceFiles(sync.GlobalImporter, exercice, exceptions))) + c.JSON(http.StatusOK, flatifySyncErrors(sync.ImportExerciceFiles(sync.GlobalImporter, exercice, exceptions))) }) apiSyncExercicesRoutes.POST("/fixurlid", func(c *gin.Context) { exercice := c.MustGet("exercice").(*fic.Exercice) diff --git a/admin/sync/exercice_files.go b/admin/sync/exercice_files.go index 11afe1ff..3ce45c80 100644 --- a/admin/sync/exercice_files.go +++ b/admin/sync/exercice_files.go @@ -315,9 +315,57 @@ func DownloadExerciceFile(pf ExerciceFile, dest string, exercice *fic.Exercice, return } -// SyncExerciceFiles reads the content of files/ directory and import it as EFile for the given challenge. +type importedFile struct { + file interface{} + Name string +} + +func SyncExerciceFiles(i Importer, exercice *fic.Exercice, paramsFiles map[string]ExerciceFile, actionAfterImport func(fname string, digests map[string][]byte, filePath, origin string) (interface{}, error)) (ret []*importedFile, errs error) { + files, digests, berrs := BuildFilesListInto(i, exercice, "files") + errs = multierr.Append(errs, berrs) + + // Import standard files + for _, fname := range files { + var f interface{} + var err error + + if pf, exists := paramsFiles[fname]; exists && pf.URL != "" && !i.Exists(path.Join(exercice.Path, "files", fname)) { + dest := GetDestinationFilePath(pf.URL, &pf.Filename) + + if _, err := os.Stat(dest); !os.IsNotExist(err) { + if d, err := actionAfterImport(fname, digests, dest, pf.URL); err == nil { + f = d + } + } + + if f == nil { + errs = multierr.Append(errs, DownloadExerciceFile(paramsFiles[fname], dest, exercice, false)) + + f, err = actionAfterImport(fname, digests, dest, pf.URL) + } + } else { + f, err = i.importFile(path.Join(exercice.Path, "files", fname), func(filePath, origin string) (interface{}, error) { + return actionAfterImport(fname, digests, filePath, origin) + }) + } + + if err != nil { + errs = multierr.Append(errs, NewFileError(exercice, fname, err)) + continue + } + + ret = append(ret, &importedFile{ + f, + fname, + }) + } + + return +} + +// ImportExerciceFiles reads the content of files/ directory and import it as EFile for the given challenge. // It takes care of DIGESTS.txt and ensure imported files match. -func SyncExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (errs error) { +func ImportExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (errs error) { if _, err := exercice.WipeFiles(); err != nil { errs = multierr.Append(errs, err) } @@ -328,63 +376,41 @@ func SyncExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExce return } - files, digests, berrs := BuildFilesListInto(i, exercice, "files") + actionAfterImport := func(fname string, digests map[string][]byte, filePath, origin string) (interface{}, error) { + var digest_shown []byte + if strings.HasSuffix(fname, ".gz") { + if d, exists := digests[strings.TrimSuffix(fname, ".gz")]; exists { + digest_shown = d + } + } + + published := true + disclaimer := "" + if f, exists := paramsFiles[fname]; exists { + published = !f.Hidden + + // Call checks hooks + for _, hk := range hooks.mdTextHooks { + for _, err := range multierr.Errors(hk(f.Disclaimer, exercice.Language, exceptions)) { + errs = multierr.Append(errs, NewFileError(exercice, fname, err)) + } + } + + if disclaimer, err = ProcessMarkdown(i, fixnbsp(f.Disclaimer), exercice.Path); err != nil { + errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("error during markdown formating of disclaimer: %w", err))) + } + } + + return exercice.ImportFile(filePath, origin, digests[fname], digest_shown, disclaimer, published) + } + + files, berrs := SyncExerciceFiles(i, exercice, paramsFiles, actionAfterImport) errs = multierr.Append(errs, berrs) - // Import standard files - for _, fname := range files { - actionAfterImport := func(filePath string, origin string) (interface{}, error) { - var digest_shown []byte - if strings.HasSuffix(fname, ".gz") { - if d, exists := digests[strings.TrimSuffix(fname, ".gz")]; exists { - digest_shown = d - } - } - - published := true - disclaimer := "" - if f, exists := paramsFiles[fname]; exists { - published = !f.Hidden - - // Call checks hooks - for _, hk := range hooks.mdTextHooks { - for _, err := range multierr.Errors(hk(f.Disclaimer, exercice.Language, exceptions)) { - errs = multierr.Append(errs, NewFileError(exercice, fname, err)) - } - } - - if disclaimer, err = ProcessMarkdown(i, fixnbsp(f.Disclaimer), exercice.Path); err != nil { - errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("error during markdown formating of disclaimer: %w", err))) - } - } - - return exercice.ImportFile(filePath, origin, digests[fname], digest_shown, disclaimer, published) - } - - var f interface{} - - if pf, exists := paramsFiles[fname]; exists && pf.URL != "" && !i.Exists(path.Join(exercice.Path, "files", fname)) { - dest := GetDestinationFilePath(pf.URL, &pf.Filename) - - if _, err := os.Stat(dest); !os.IsNotExist(err) { - if d, err := actionAfterImport(dest, pf.URL); err == nil { - f = d - } - } - - if f == nil { - errs = multierr.Append(errs, DownloadExerciceFile(paramsFiles[fname], dest, exercice, false)) - - f, err = actionAfterImport(dest, pf.URL) - } - } else { - f, err = i.importFile(path.Join(exercice.Path, "files", fname), actionAfterImport) - } - - if err != nil { - errs = multierr.Append(errs, NewFileError(exercice, fname, err)) - continue - } + // Import files in db + for _, file := range files { + fname := file.Name + f := file.file if f.(*fic.EFile).Size == 0 { errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("imported file is empty!"))) diff --git a/admin/sync/full.go b/admin/sync/full.go index 275a1e83..1a64158c 100644 --- a/admin/sync/full.go +++ b/admin/sync/full.go @@ -246,7 +246,7 @@ func SyncThemeDeep(i Importer, theme *fic.Theme, tid int, themeStep uint8, excep log.Printf("Deep synchronization in progress: %d/255 - doing Theme %q, Exercice %q: %q\n", DeepSyncProgress, theme.Name, exercice.Title, exercice.Path) DeepSyncProgress = 3 + uint8(tid)*themeStep + uint8(eid)*exerciceStep - errs = multierr.Append(errs, SyncExerciceFiles(i, exercice, ex_exceptions[eid])) + errs = multierr.Append(errs, ImportExerciceFiles(i, exercice, ex_exceptions[eid])) DeepSyncProgress += exerciceStep / 3 flagsBindings, ferrs := SyncExerciceFlags(i, exercice, ex_exceptions[eid]) diff --git a/admin/sync/themes.go b/admin/sync/themes.go index 20b3c114..3079544e 100644 --- a/admin/sync/themes.go +++ b/admin/sync/themes.go @@ -273,6 +273,36 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept return } +// SyncThemeFiles import all theme's related files +func SyncThemeFiles(i Importer, btheme *fic.Theme) (errs error) { + if len(btheme.Image) > 0 { + if _, err := i.importFile(btheme.Image, + func(filePath string, origin string) (interface{}, error) { + if err := resizePicture(origin, filePath, image.Rect(0, 0, 500, 300)); err != nil { + return nil, err + } + + btheme.Image = strings.TrimPrefix(filePath, fic.FilesDir) + btheme.BackgroundColor, _ = getBackgroundColor(filePath) + return nil, nil + }); err != nil { + errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("unable to import heading image: %w", err))) + } + } + + if len(btheme.PartnerImage) > 0 { + if _, err := i.importFile(btheme.PartnerImage, + func(filePath string, origin string) (interface{}, error) { + btheme.PartnerImage = strings.TrimPrefix(filePath, fic.FilesDir) + return nil, nil + }); err != nil { + errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("unable to import partner image: %w", err))) + } + } + + return +} + // SyncThemes imports new or updates existing themes. func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, errs error) { if themes, err := GetThemes(i); err != nil { @@ -294,29 +324,9 @@ func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, errs error) exceptions[tdir] = excepts - if len(btheme.Image) > 0 { - if _, err := i.importFile(btheme.Image, - func(filePath string, origin string) (interface{}, error) { - if err := resizePicture(filePath, image.Rect(0, 0, 500, 300)); err != nil { - return nil, err - } - - btheme.Image = strings.TrimPrefix(filePath, fic.FilesDir) - btheme.BackgroundColor, _ = getBackgroundColor(filePath) - return nil, nil - }); err != nil { - errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("unable to import heading image: %w", err))) - } - } - - if len(btheme.PartnerImage) > 0 { - if _, err := i.importFile(btheme.PartnerImage, - func(filePath string, origin string) (interface{}, error) { - btheme.PartnerImage = strings.TrimPrefix(filePath, fic.FilesDir) - return nil, nil - }); err != nil { - errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("unable to import partner image: %w", err))) - } + err = SyncThemeFiles(i, btheme) + if err != nil { + errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("unable to import heading image: %w", err))) } var theme *fic.Theme From c7d1d7ce4c1a39101938e913c362c8c1a150a4b8 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 7 Apr 2025 10:15:26 +0200 Subject: [PATCH 2/7] sync: Refactor importFile to use a parametrable writer --- admin/sync/file.go | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/admin/sync/file.go b/admin/sync/file.go index 39dc11e2..7efd47fa 100644 --- a/admin/sync/file.go +++ b/admin/sync/file.go @@ -188,26 +188,35 @@ func GetDestinationFilePath(URI string, filename *string) string { return path.Join(fic.FilesDir, strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(hash[:])), *filename) } -func importFile(i Importer, URI string, dest string) error { +var fileWriter = fileWriterToFS + +func SetWriteFileFunc(writerFunc func(dest string) (io.WriteCloser, error)) { + fileWriter = writerFunc +} + +func fileWriterToFS(dest string) (io.WriteCloser, error) { if err := os.MkdirAll(path.Dir(dest), 0751); err != nil { - return err + return nil, err } - // Write file - if fdto, err := os.Create(dest); err != nil { + return os.Create(dest) +} + +func importFile(i Importer, URI string, dest string) error { + if fdfrom, closer, err := GetFile(i, URI); err != nil { + os.Remove(dest) return err } else { - defer fdto.Close() + defer closer() - if fdfrom, closer, err := GetFile(i, URI); err != nil { - os.Remove(dest) - return err - } else { - defer closer() - - _, err = io.Copy(fdto, fdfrom) + fdto, err := fileWriter(dest) + if err != nil { return err } + defer fdto.Close() + + _, err = io.Copy(fdto, fdfrom) + return err } } From b55151623cd03b5a24177f5ff21395aad5728fc5 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 7 Apr 2025 10:16:03 +0200 Subject: [PATCH 3/7] sync: resizePicture uses image from importer instead of local file --- admin/sync/exercices.go | 2 +- admin/sync/themes.go | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/admin/sync/exercices.go b/admin/sync/exercices.go index 49e1eef9..b452d4b6 100644 --- a/admin/sync/exercices.go +++ b/admin/sync/exercices.go @@ -398,7 +398,7 @@ func SyncExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*f 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 { + if err := resizePicture(i, origin, filePath, image.Rect(0, 0, 500, 300)); err != nil { return nil, err } diff --git a/admin/sync/themes.go b/admin/sync/themes.go index 3079544e..8ac666d4 100644 --- a/admin/sync/themes.go +++ b/admin/sync/themes.go @@ -5,6 +5,7 @@ import ( "fmt" "image" "image/jpeg" + "io" "math/rand" "net/http" "os" @@ -40,15 +41,19 @@ func GetThemes(i Importer) (themes []string, err error) { } // resizePicture makes the given image just fill the given rectangle. -func resizePicture(importedPath string, rect image.Rectangle) error { - if fl, err := os.Open(importedPath); err != nil { +func resizePicture(i Importer, imgPath string, importedPath string, rect image.Rectangle) error { + if fl, err := i.GetFile(imgPath); err != nil { return err } else { if src, _, err := image.Decode(fl); err != nil { - fl.Close() + if flc, ok := fl.(io.ReadCloser); ok { + flc.Close() + } return err } else if src.Bounds().Max.X > rect.Max.X && src.Bounds().Max.Y > rect.Max.Y { - fl.Close() + if flc, ok := fl.(io.ReadCloser); ok { + flc.Close() + } mWidth := rect.Max.Y * src.Bounds().Max.X / src.Bounds().Max.Y mHeight := rect.Max.X * src.Bounds().Max.Y / src.Bounds().Max.X @@ -61,7 +66,7 @@ func resizePicture(importedPath string, rect image.Rectangle) error { dst := image.NewRGBA(rect) draw.CatmullRom.Scale(dst, rect, src, src.Bounds(), draw.Over, nil) - dstFile, err := os.Create(strings.TrimSuffix(importedPath, ".jpg") + ".thumb.jpg") + dstFile, err := fileWriter(strings.TrimSuffix(importedPath, ".jpg") + ".thumb.jpg") if err != nil { return err } @@ -71,7 +76,7 @@ func resizePicture(importedPath string, rect image.Rectangle) error { return err } } else { - dstFile, err := os.Create(strings.TrimSuffix(importedPath, ".jpg") + ".thumb.jpg") + dstFile, err := fileWriter(strings.TrimSuffix(importedPath, ".jpg") + ".thumb.jpg") if err != nil { return err } @@ -278,7 +283,7 @@ func SyncThemeFiles(i Importer, btheme *fic.Theme) (errs error) { if len(btheme.Image) > 0 { if _, err := i.importFile(btheme.Image, func(filePath string, origin string) (interface{}, error) { - if err := resizePicture(origin, filePath, image.Rect(0, 0, 500, 300)); err != nil { + if err := resizePicture(i, origin, filePath, image.Rect(0, 0, 500, 300)); err != nil { return nil, err } From 8723f500cc5526785fb51adeb1af2f0d5fa21101 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 7 Apr 2025 10:16:30 +0200 Subject: [PATCH 4/7] sync: Markdown imports files using generic functions --- admin/sync/markdown.go | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/admin/sync/markdown.go b/admin/sync/markdown.go index fe2eb12a..5175b212 100644 --- a/admin/sync/markdown.go +++ b/admin/sync/markdown.go @@ -3,9 +3,7 @@ package sync import ( "bytes" "encoding/base32" - "io" "net/url" - "os" "path" "strings" @@ -89,27 +87,10 @@ func (t *imageImporterTransformer) Transform(doc *ast.Document, reader text.Read dPath := path.Join(fic.FilesDir, strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(t.hash[:])), iPath) child.Destination = []byte(path.Join(t.absPath, string(child.Destination))) - if err := os.MkdirAll(path.Dir(dPath), 0755); err != nil { + err := importFile(t.importer, path.Join(t.rootDir, iPath), dPath) + if err != nil { return ast.WalkStop, err } - - if fdto, err := os.Create(dPath); err != nil { - return ast.WalkStop, err - } else { - defer fdto.Close() - - if fd, closer, err := GetFile(t.importer, path.Join(t.rootDir, iPath)); err != nil { - os.Remove(dPath) - return ast.WalkStop, err - } else { - defer closer() - - _, err = io.Copy(fdto, fd) - if err != nil { - return ast.WalkStop, err - } - } - } } return ast.WalkContinue, nil From c2996b9f0aa06bd27dfe1a7e592d1ba107b6bdfd Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 7 Apr 2025 10:29:06 +0200 Subject: [PATCH 5/7] repochecker: Use SetWriteFileFunc to avoid writing any file on disk --- repochecker/main.go | 12 +++--------- repochecker/null.go | 11 +++++++++++ 2 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 repochecker/null.go diff --git a/repochecker/main.go b/repochecker/main.go index ff49aacb..de6e1c57 100644 --- a/repochecker/main.go +++ b/repochecker/main.go @@ -6,7 +6,7 @@ import ( "errors" "flag" "fmt" - "io/ioutil" + "io" "log" "os" "os/exec" @@ -194,14 +194,8 @@ func main() { regenImporter = true } - var err error - - // Create temporary directory for storing FILES/ content - fic.FilesDir, err = ioutil.TempDir("", "fic-repochecker.") - if err != nil { - - } - defer os.RemoveAll(fic.FilesDir) + // Don't write any files + sync.SetWriteFileFunc(func(dest string) (io.WriteCloser, error) { return &nullFileWriter{}, nil }) if sync.GlobalImporter != nil { log.Println("Using", sync.GlobalImporter.Kind()) diff --git a/repochecker/null.go b/repochecker/null.go new file mode 100644 index 00000000..e618c541 --- /dev/null +++ b/repochecker/null.go @@ -0,0 +1,11 @@ +package main + +type nullFileWriter struct{} + +func (fw *nullFileWriter) Write(p []byte) (int, error) { + return len(p), nil +} + +func (fw *nullFileWriter) Close() error { + return nil +} From 7f38911bbb57e439deb0a6bdc055446d7a730a62 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 7 Apr 2025 11:07:14 +0200 Subject: [PATCH 6/7] Introducing fileexporter to create archive from git or other importer --- fileexporter/.gitignore | 1 + fileexporter/archive.go | 42 ++++++++++ fileexporter/copy.go | 22 +++++ fileexporter/main.go | 176 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 fileexporter/.gitignore create mode 100644 fileexporter/archive.go create mode 100644 fileexporter/copy.go create mode 100644 fileexporter/main.go diff --git a/fileexporter/.gitignore b/fileexporter/.gitignore new file mode 100644 index 00000000..cbf49db1 --- /dev/null +++ b/fileexporter/.gitignore @@ -0,0 +1 @@ +fileexporter diff --git a/fileexporter/archive.go b/fileexporter/archive.go new file mode 100644 index 00000000..19544724 --- /dev/null +++ b/fileexporter/archive.go @@ -0,0 +1,42 @@ +package main + +import ( + "archive/zip" + "errors" + "io" + "os" + "path" +) + +type archiveFileCreator interface { + Create(name string) (io.Writer, error) +} + +func init() { + OutputFormats["archive"] = func(args ...string) (func(string) (io.WriteCloser, error), error) { + if len(args) != 1 { + return nil, errors.New("archive has 1 required argument: [destination-file]") + } + + fd, err := os.Create(args[0]) + if err != nil { + return nil, err + } + + var w archiveFileCreator + if path.Ext(args[0]) == ".zip" { + w = zip.NewWriter(fd) + } else { + return nil, errors.New("destination file has to have .zip extension") + } + + return func(dest string) (io.WriteCloser, error) { + fw, err := w.Create(dest) + if err != nil { + return nil, err + } + + return NopCloser(fw), nil + }, nil + } +} diff --git a/fileexporter/copy.go b/fileexporter/copy.go new file mode 100644 index 00000000..6610b2c1 --- /dev/null +++ b/fileexporter/copy.go @@ -0,0 +1,22 @@ +package main + +import ( + "errors" + "io" + + "srs.epita.fr/fic-server/libfic" +) + +func init() { + OutputFormats["copy"] = func(args ...string) (func(string) (io.WriteCloser, error), error) { + if len(args) > 1 { + return nil, errors.New("copy can only take 1 argument: [destination-folder]") + } + + if len(args) == 1 { + fic.FilesDir = args[0] + } + + return nil, nil + } +} diff --git a/fileexporter/main.go b/fileexporter/main.go new file mode 100644 index 00000000..7113c09d --- /dev/null +++ b/fileexporter/main.go @@ -0,0 +1,176 @@ +package main + +import ( + "bytes" + "errors" + "flag" + "io" + "log" + "os" + "path" + "strings" + + "srs.epita.fr/fic-server/admin/sync" + "srs.epita.fr/fic-server/libfic" +) + +var OutputFormats = map[string]func(...string) (func(string) (io.WriteCloser, error), error){} + +func exportThemeFiles(tdir string) (errs error) { + theme, exceptions, err := sync.BuildTheme(sync.GlobalImporter, tdir) + errs = errors.Join(errs, err) + + err = sync.SyncThemeFiles(sync.GlobalImporter, theme) + if err != nil { + errs = errors.Join(errs, err) + } + + exercices, err := sync.GetExercices(sync.GlobalImporter, theme) + if err != nil { + log.Fatalf("Unable to list exercices for theme %q: %s", theme.Name, err) + } + + dmap := map[int64]*fic.Exercice{} + + for i, edir := range exercices { + log.Printf("In theme %s, doing exercice %d/%d: %s", tdir, i, len(exercices), tdir) + err = exportExerciceFiles(theme, edir, &dmap, exceptions) + errs = errors.Join(errs, err) + } + + return +} + +func exportExerciceFiles(theme *fic.Theme, edir string, dmap *map[int64]*fic.Exercice, exceptions *sync.CheckExceptions) (errs error) { + exercice, _, eid, exceptions, _, berrs := sync.BuildExercice(sync.GlobalImporter, theme, path.Join(theme.Path, edir), dmap, nil) + errs = errors.Join(errs, berrs) + + if exercice != nil { + paramsFiles, err := sync.GetExerciceFilesParams(sync.GlobalImporter, exercice) + if err != nil { + errs = errors.Join(errs, sync.NewChallengeTxtError(exercice, 0, err)) + return + } + + _, err = sync.SyncExerciceFiles(sync.GlobalImporter, exercice, paramsFiles, func(fname string, digests map[string][]byte, filePath, origin string) (interface{}, error) { + return nil, nil + }) + errs = errors.Join(errs, err) + + if dmap != nil { + (*dmap)[int64(eid)] = exercice + } + } + return +} + +type nopCloser struct { + w io.Writer +} + +func (nc *nopCloser) Close() error { + return nil +} + +func (nc *nopCloser) Write(p []byte) (int, error) { + return nc.w.Write(p) +} + +func NopCloser(w io.Writer) *nopCloser { + return &nopCloser{w} +} + +func writeFileToTar(dest string) (io.WriteCloser, error) { + log.Println("import2Tar", dest) + return NopCloser(bytes.NewBuffer([]byte{})), nil +} + +func main() { + cloudDAVBase := "" + cloudUsername := "fic" + cloudPassword := "" + localImporterDirectory := "" + + // Read paremeters from environment + if v, exists := os.LookupEnv("FICCLOUD_URL"); exists { + cloudDAVBase = v + } + if v, exists := os.LookupEnv("FICCLOUD_USER"); exists { + cloudUsername = v + } + if v, exists := os.LookupEnv("FICCLOUD_PASS"); exists { + cloudPassword = v + } + + // Read parameters from command line + flag.StringVar(&localImporterDirectory, "localimport", localImporterDirectory, + "Base directory where to find challenges files to import, local part") + flag.StringVar(&cloudDAVBase, "clouddav", cloudDAVBase, + "Base directory where to find challenges files to import, cloud part") + flag.StringVar(&cloudUsername, "clouduser", cloudUsername, "Username used to sync") + flag.StringVar(&cloudPassword, "cloudpass", cloudPassword, "Password used to sync") + flag.BoolVar(&fic.OptionalDigest, "optionaldigest", fic.OptionalDigest, "Is the digest required when importing files?") + flag.BoolVar(&fic.StrongDigest, "strongdigest", fic.StrongDigest, "Are BLAKE2b digests required or is SHA-1 good enough?") + flag.Parse() + + // Do not display timestamp + log.SetFlags(0) + + // Instantiate importer + if localImporterDirectory != "" { + sync.GlobalImporter = sync.LocalImporter{Base: localImporterDirectory, Symlink: false} + } else if cloudDAVBase != "" { + sync.GlobalImporter, _ = sync.NewCloudImporter(cloudDAVBase, cloudUsername, cloudPassword) + } + + if sync.GlobalImporter == nil { + log.Fatal("No importer configured!") + } + + log.Println("Using", sync.GlobalImporter.Kind()) + + // Configure destination + if flag.NArg() < 1 { + var formats []string + + for k := range OutputFormats { + formats = append(formats, k) + } + + log.Fatal("Please define wanted output format between [" + strings.Join(formats, " ") + "]") + } else if outputFormat, ok := OutputFormats[flag.Arg(0)]; !ok { + var formats []string + + for k := range OutputFormats { + formats = append(formats, k) + } + + log.Fatal("Please define wanted output format between [" + strings.Join(formats, " ") + "]") + } else { + fw, err := outputFormat(flag.Args()[1:]...) + if err != nil { + log.Fatal(err) + } else if fw != nil { + sync.SetWriteFileFunc(fw) + } + } + + themes, err := sync.GetThemes(sync.GlobalImporter) + if err != nil { + log.Fatal(err) + } + + hasError := false + for i, tdir := range themes { + log.Printf("Doing theme %d/%d: %s", i, len(themes), tdir) + err = exportThemeFiles(tdir) + if err != nil { + hasError = true + log.Println(err) + } + } + + if hasError { + os.Exit(1) + } +} From af97e1317f706e9353d6666a7a3da6a12a9bd4fa Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 7 Apr 2025 10:19:56 +0000 Subject: [PATCH 7/7] chore(deps): update dependency @sveltejs/kit to v2.20.4 --- frontend/fic/package-lock.json | 6 +++--- qa/ui/package-lock.json | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/fic/package-lock.json b/frontend/fic/package-lock.json index d08b942c..08b40758 100644 --- a/frontend/fic/package-lock.json +++ b/frontend/fic/package-lock.json @@ -1308,9 +1308,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.20.2", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.2.tgz", - "integrity": "sha512-Dv8TOAZC9vyfcAB9TMsvUEJsRbklRTeNfcYBPaeH6KnABJ99i3CvCB2eNx8fiiliIqe+9GIchBg4RodRH5p1BQ==", + "version": "2.20.4", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.4.tgz", + "integrity": "sha512-B3Y1mb1Qjt57zXLVch5tfqsK/ebHe6uYTcFSnGFNwRpId3+fplLgQK6Z2zhDVBezSsPuhDq6Pry+9PA88ocN6Q==", "dev": true, "license": "MIT", "dependencies": { diff --git a/qa/ui/package-lock.json b/qa/ui/package-lock.json index 775181b2..c6925737 100644 --- a/qa/ui/package-lock.json +++ b/qa/ui/package-lock.json @@ -989,9 +989,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.20.2", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.2.tgz", - "integrity": "sha512-Dv8TOAZC9vyfcAB9TMsvUEJsRbklRTeNfcYBPaeH6KnABJ99i3CvCB2eNx8fiiliIqe+9GIchBg4RodRH5p1BQ==", + "version": "2.20.4", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.4.tgz", + "integrity": "sha512-B3Y1mb1Qjt57zXLVch5tfqsK/ebHe6uYTcFSnGFNwRpId3+fplLgQK6Z2zhDVBezSsPuhDq6Pry+9PA88ocN6Q==", "dev": true, "license": "MIT", "dependencies": {