package sync import ( "cloudsave/cmd/cli/tools/prompt" "cloudsave/cmd/cli/tools/prompt/credentials" "cloudsave/pkg/data" "cloudsave/pkg/remote" "cloudsave/pkg/remote/client" "cloudsave/pkg/repository" "cloudsave/pkg/sync" "context" "flag" "fmt" "os" "time" "github.com/google/subcommands" "github.com/schollz/progressbar/v3" ) type ( SyncCmd struct { Service *data.Service } ) func (*SyncCmd) Name() string { return "sync" } func (*SyncCmd) Synopsis() string { return "list all game registered" } func (*SyncCmd) Usage() string { return `Usage: cloudsave sync Synchronize the archives with the server defined for each game. ` } func (p *SyncCmd) SetFlags(f *flag.FlagSet) { } func (p *SyncCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { remoteCred := make(map[string]map[string]string) rs, err := remote.All() if err != nil { fmt.Fprintln(os.Stderr, "error: failed to connect to the remote:", err) return subcommands.ExitFailure } done := make(map[string]struct{}) for _, r := range rs { if _, ok := done[r.URL]; ok { continue } cli, err := connect(remoteCred, r) if err != nil { fmt.Fprintln(os.Stderr, "error: failed to connect to the remote:", err) return subcommands.ExitFailure } fmt.Println() done[r.URL] = struct{}{} syncer := sync.NewSyncer(cli, p.Service) var pg *progressbar.ProgressBar destroyPg := func() { pg.Finish() pg.Clear() pg.Close() } syncer.SetStateCallback(func(s sync.State, g repository.Metadata) { switch s { case sync.FetchingMetdata: pg = progressbar.New(-1) pg.Describe(fmt.Sprintf("%s: fetching metadata from repository", g.Name)) case sync.Pushing: pg.Describe(fmt.Sprintf("%s: pushing data to the server", g.Name)) case sync.Pulling: pg.Describe(fmt.Sprintf("%s: pull data from the server", g.Name)) case sync.UpToDate: destroyPg() fmt.Println("🆗", g.Name+": already up-to-date") case sync.Pushed: destroyPg() fmt.Println("⬆️", g.Name+": pushed") case sync.Pulled: destroyPg() fmt.Println("⬇️", g.Name+": pulled") } }) syncer.SetErrorCallback(func(err error, g repository.Metadata) { destroyPg() fmt.Println("❌", g.Name+": "+err.Error()) }) syncer.SetConflictCallback(func(a, b repository.Metadata) sync.ConflictResolution { fmt.Println() fmt.Println("--- ⚠️ CONFLICT ---") fmt.Println(a.Name, "(", a.Path, ")") fmt.Println("----") fmt.Println("Your version:", a.Date.Format(time.RFC1123)) fmt.Println("Their version:", b.Date.Format(time.RFC1123)) fmt.Println() res := prompt.Conflict() switch res { case prompt.Their: return sync.Their case prompt.My: return sync.Mine } return sync.None }) syncer.Sync() } fmt.Println("done.") return subcommands.ExitSuccess } func connect(remoteCred map[string]map[string]string, r remote.Remote) (*client.Client, error) { var cli *client.Client if v, ok := remoteCred[r.URL]; ok { cli = client.New(r.URL, v["username"], v["password"]) return cli, nil } fmt.Println() fmt.Println("Connexion to", r.URL) fmt.Println("============") username, password, err := credentials.Read(r.URL) if err != nil { return nil, fmt.Errorf("failed to read std output: %w", err) } cli = client.New(r.URL, username, password) if err := cli.Ping(); err != nil { return nil, fmt.Errorf("failed to connect to the remote: %w", err) } c := make(map[string]string) c["username"] = username c["password"] = password remoteCred[r.URL] = c return cli, nil }