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/static/views/team-stats.html b/admin/static/views/team-stats.html
index 7c264afb..4bb75eb1 100644
--- a/admin/static/views/team-stats.html
+++ b/admin/static/views/team-stats.html
@@ -37,7 +37,7 @@
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/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/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
}
}
diff --git a/admin/sync/full.go b/admin/sync/full.go
index 275a1e83..d112fd2e 100644
--- a/admin/sync/full.go
+++ b/admin/sync/full.go
@@ -72,13 +72,9 @@ func SpeedySyncDeep(i Importer) (errs SyncReport) {
errs.ThemesSync = append(errs.ThemesSync, sterr.Error())
}
- if themes, err := fic.GetThemes(); err == nil {
+ if themes, err := fic.GetThemesExtended(); err == nil {
DeepSyncProgress = 2
- if i.Exists(fic.StandaloneExercicesDirectory) {
- themes = append(themes, &fic.StandaloneExercicesTheme)
- }
-
var themeStep uint8 = uint8(250) / uint8(len(themes))
for tid, theme := range themes {
@@ -143,14 +139,9 @@ func SyncDeep(i Importer) (errs SyncReport) {
}
// Synchronize themes
- if themes, err := fic.GetThemes(); err == nil {
+ if themes, err := fic.GetThemesExtended(); err == nil {
DeepSyncProgress = 2
- // Also synchronize standalone exercices
- if i.Exists(fic.StandaloneExercicesDirectory) {
- themes = append(themes, &fic.StandaloneExercicesTheme)
- }
-
var themeStep uint8 = uint8(250) / uint8(len(themes))
for tid, theme := range themes {
@@ -246,7 +237,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/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
diff --git a/admin/sync/themes.go b/admin/sync/themes.go
index 20b3c114..09a4440b 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"
@@ -39,16 +40,34 @@ func GetThemes(i Importer) (themes []string, err error) {
return themes, nil
}
+// GetThemesExtended returns all theme directories, including standalone exercices.
+func GetThemesExtended(i Importer) (themes []string, err error) {
+ themes, err = GetThemes(i)
+ if err != nil {
+ return
+ }
+
+ if i.Exists(fic.StandaloneExercicesDirectory) {
+ themes = append(themes, fic.StandaloneExercicesDirectory)
+ }
+
+ return
+}
+
// 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 +80,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 +90,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
}
@@ -273,6 +292,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(i, 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 +343,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
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..d4fc1ceb
--- /dev/null
+++ b/fileexporter/archive.go
@@ -0,0 +1,58 @@
+package main
+
+import (
+ "archive/zip"
+ "errors"
+ "io"
+ "log"
+ "os"
+ "path"
+)
+
+type archiveFileCreator interface {
+ Create(name string) (io.Writer, error)
+ Close() error
+}
+
+type filesCloser []io.Closer
+
+func (fds filesCloser) Close() error {
+ log.Println("Closing fd..")
+ for _, fd := range fds {
+ err := fd.Close()
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func init() {
+ OutputFormats["archive"] = func(args ...string) (func(string) (io.WriteCloser, error), io.Closer, error) {
+ if len(args) != 1 {
+ return nil, nil, errors.New("archive has 1 required argument: [destination-file]")
+ }
+
+ fd, err := os.Create(args[0])
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var w archiveFileCreator
+ if path.Ext(args[0]) == ".zip" {
+ w = zip.NewWriter(fd)
+ } else {
+ return nil, 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
+ }, filesCloser{w, fd}, nil
+ }
+}
diff --git a/fileexporter/copy.go b/fileexporter/copy.go
new file mode 100644
index 00000000..d4c7b96a
--- /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), io.Closer, error) {
+ if len(args) > 1 {
+ return nil, nil, errors.New("copy can only take 1 argument: [destination-folder]")
+ }
+
+ if len(args) == 1 {
+ fic.FilesDir = args[0]
+ }
+
+ return nil, nil, nil
+ }
+}
diff --git a/fileexporter/main.go b/fileexporter/main.go
new file mode 100644
index 00000000..5fea7f6d
--- /dev/null
+++ b/fileexporter/main.go
@@ -0,0 +1,184 @@
+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), io.Closer, 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+1, len(exercices), edir)
+ 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())
+
+ hasError := doExport()
+ if hasError {
+ os.Exit(1)
+ }
+}
+
+func doExport() bool {
+ // 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, closer, err := outputFormat(flag.Args()[1:]...)
+ if closer != nil {
+ defer closer.Close()
+ }
+ if err != nil {
+ log.Fatal(err)
+ } else if fw != nil {
+ sync.SetWriteFileFunc(fw)
+ }
+ }
+
+ themes, err := sync.GetThemesExtended(sync.GlobalImporter)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ hasError := false
+ for i, tdir := range themes {
+ log.Printf("Doing theme %d/%d: %s", i+1, len(themes), tdir)
+ err = exportThemeFiles(tdir)
+ if err != nil {
+ hasError = true
+ log.Println(err)
+ }
+ }
+
+ return hasError
+}
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
+}