first commit

This commit is contained in:
2025-05-10 17:42:25 +02:00
commit 7827b9c0cc
8 changed files with 337 additions and 0 deletions

147
cmd/cli/commands/run/run.go Normal file
View File

@@ -0,0 +1,147 @@
package run
import (
"archive/tar"
"cloudsave/pkg/game"
"compress/gzip"
"context"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"time"
"github.com/google/subcommands"
)
type (
RunCmd struct {
}
)
func (RunCmd) Name() string { return "run" }
func (RunCmd) Synopsis() string { return "Check and process all the folder" }
func (RunCmd) Usage() string {
return `run:
Check and process all the folder
`
}
func (p RunCmd) SetFlags(f *flag.FlagSet) {}
func (p RunCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
datastore, err := game.All()
if err != nil {
fmt.Fprintln(os.Stderr, "error: failed to load datastore:", err)
return subcommands.ExitFailure
}
for _, metadata := range datastore {
metadataPath := filepath.Join(game.DatastorePath(), metadata.ID)
err := archiveIfChanged(metadata.Path, filepath.Join(metadataPath, "data.tar.gz"), filepath.Join(metadataPath, ".last_run"))
if err != nil {
fmt.Fprintf(os.Stderr, "error: cannot process the data of %s: %s\n", metadata.ID, err)
return subcommands.ExitFailure
}
}
return subcommands.ExitSuccess
}
// archiveIfChanged will archive srcDir into destTarGz only if any file
// in srcDir has a modification time > the last run time stored in stateFile.
// After archiving, it updates stateFile to the current time.
func archiveIfChanged(srcDir, destTarGz, stateFile string) error {
// 1) Load last run time
var lastRun time.Time
data, err := os.ReadFile(stateFile)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("reading state file: %w", err)
}
if err == nil {
lastRun, err = time.Parse(time.RFC3339, string(data))
if err != nil {
return fmt.Errorf("parsing state file timestamp: %w", err)
}
}
// 2) Check for changes
changed := false
err = filepath.Walk(srcDir, func(path string, info os.FileInfo, walkErr error) error {
if walkErr != nil {
return walkErr
}
if info.ModTime().After(lastRun) {
changed = true
return io.EOF // early exit
}
return nil
})
if err != nil && err != io.EOF {
return fmt.Errorf("scanning source directory: %w", err)
}
if !changed {
fmt.Println("No changes detected; skipping archive.")
return nil
}
// 3) Create tar.gz
f, err := os.Create(destTarGz)
if err != nil {
return fmt.Errorf("creating archive file: %w", err)
}
defer f.Close()
gw := gzip.NewWriter(f)
defer gw.Close()
tw := tar.NewWriter(gw)
defer tw.Close()
// Walk again to add files
err = filepath.Walk(srcDir, func(path string, info os.FileInfo, walkErr error) error {
if walkErr != nil {
return walkErr
}
// Create tar header
header, err := tar.FileInfoHeader(info, path)
if err != nil {
return err
}
// Preserve directory structure relative to srcDir
relPath, err := filepath.Rel(filepath.Dir(srcDir), path)
if err != nil {
return err
}
header.Name = relPath
if err := tw.WriteHeader(header); err != nil {
return err
}
if info.Mode().IsRegular() {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
if _, err := io.Copy(tw, file); err != nil {
return err
}
}
return nil
})
if err != nil {
return fmt.Errorf("writing tar entries: %w", err)
}
// 4) Update state file
now := time.Now().UTC().Format(time.RFC3339)
if err := os.WriteFile(stateFile, []byte(now), 0644); err != nil {
return fmt.Errorf("updating state file: %w", err)
}
fmt.Printf("Archived %q to %q and updated state file.\n", srcDir, destTarGz)
return nil
}