Introducing fileexporter to create archive from git or other importer

This commit is contained in:
nemunaire 2025-04-07 11:07:14 +02:00
parent c2996b9f0a
commit 7f38911bbb
4 changed files with 241 additions and 0 deletions

1
fileexporter/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
fileexporter

42
fileexporter/archive.go Normal file
View file

@ -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
}
}

22
fileexporter/copy.go Normal file
View file

@ -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
}
}

176
fileexporter/main.go Normal file
View file

@ -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)
}
}