163 lines
3.9 KiB
Go
163 lines
3.9 KiB
Go
package run
|
|
|
|
import (
|
|
"archive/tar"
|
|
"cloudsave/pkg/game"
|
|
"compress/gzip"
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/google/subcommands"
|
|
"github.com/schollz/progressbar/v3"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
pg := progressbar.New(len(datastore))
|
|
defer pg.Close()
|
|
|
|
for _, metadata := range datastore {
|
|
pg.Describe("Scanning " + metadata.Name + "...")
|
|
metadataPath := filepath.Join(game.DatastorePath(), metadata.ID)
|
|
//todo transaction
|
|
err := archiveIfChanged(metadata.ID, 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
|
|
}
|
|
if err := game.SetVersion(metadata.ID, metadata.Version+1); err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: cannot process the data of %s: %s\n", metadata.ID, err)
|
|
return subcommands.ExitFailure
|
|
}
|
|
if err := game.SetDate(metadata.ID, time.Now()); err != nil {
|
|
fmt.Fprintf(os.Stderr, "error: cannot process the data of %s: %s\n", metadata.ID, err)
|
|
return subcommands.ExitFailure
|
|
}
|
|
pg.Add(1)
|
|
}
|
|
|
|
pg.Finish()
|
|
|
|
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(id, 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 {
|
|
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)
|
|
}
|
|
|
|
return nil
|
|
}
|