From 57fc77755e79102598e2ac59713e580b50805f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lie=20DELHAIE?= Date: Fri, 12 Sep 2025 01:39:47 +0200 Subject: [PATCH] wip --- cmd/cli/commands/sync/sync.go | 1 + cmd/gui/window/credential/credential.go | 10 +- cmd/gui/window/mainwindow/mainwindow.go | 136 ++++++++++++++++-------- pkg/sync/sync.go | 4 +- pkg/tools/iterator/iterator.go | 40 +++++++ 5 files changed, 142 insertions(+), 49 deletions(-) create mode 100644 pkg/tools/iterator/iterator.go diff --git a/cmd/cli/commands/sync/sync.go b/cmd/cli/commands/sync/sync.go index 1d6b671..f30b599 100644 --- a/cmd/cli/commands/sync/sync.go +++ b/cmd/cli/commands/sync/sync.go @@ -55,6 +55,7 @@ func (p *SyncCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) fmt.Println() pg := progressbar.New(-1) + pg.Describe("Warming up...") destroyPg := func() { pg.Finish() pg.Clear() diff --git a/cmd/gui/window/credential/credential.go b/cmd/gui/window/credential/credential.go index f4d3588..9b61a79 100644 --- a/cmd/gui/window/credential/credential.go +++ b/cmd/gui/window/credential/credential.go @@ -17,7 +17,7 @@ type ( } ) -func Make(remoteHostname string, callback func(v bool), w fyne.Window) *CredentialDialog { +func Make(remoteHostname string, callback func(ok bool, username, password string), w fyne.Window) *CredentialDialog { label := canvas.NewText("Connexion to "+remoteHostname, color.Black) inputUsername := widget.NewEntry() @@ -43,13 +43,11 @@ func Make(remoteHostname string, callback func(v bool), w fyne.Window) *Credenti } d := &CredentialDialog{ - FormDialog: dialog.NewForm("Syncing", "Connexion", "Cancel", formItems, callback, w), inputUsername: inputUsername, inputPassword: inputPassword, } + d.FormDialog = dialog.NewForm("Syncing", "Connexion", "Cancel", formItems, func(b bool) { + callback(b, d.inputUsername.Text, d.inputPassword.Text) + }, w) return d } - -func (c *CredentialDialog) Credentials() (string, string) { - return c.inputUsername.Text, c.inputPassword.Text -} diff --git a/cmd/gui/window/mainwindow/mainwindow.go b/cmd/gui/window/mainwindow/mainwindow.go index 7d7cd70..8358c74 100644 --- a/cmd/gui/window/mainwindow/mainwindow.go +++ b/cmd/gui/window/mainwindow/mainwindow.go @@ -4,10 +4,14 @@ import ( "cloudsave/cmd/gui/window/about" "cloudsave/cmd/gui/window/credential" "cloudsave/cmd/gui/window/loading" + + syncdialog "cloudsave/cmd/gui/window/sync" "cloudsave/pkg/data" "cloudsave/pkg/remote" "cloudsave/pkg/remote/client" + "cloudsave/pkg/repository" "cloudsave/pkg/sync" + "cloudsave/pkg/tools/iterator" "fmt" "os" "path/filepath" @@ -19,6 +23,17 @@ import ( "fyne.io/fyne/v2/widget" ) +type ( + ExtraLabel struct { + *widget.Label + id string + } +) + +func (e *ExtraLabel) SetID(id string) { + e.id = id +} + func Make(a fyne.App, d *data.Service) fyne.Window { w := a.NewWindow("CloudSave") w.Resize(fyne.NewSize(1000, 700)) @@ -38,12 +53,17 @@ func Make(a fyne.App, d *data.Service) fyne.Window { return len(games) }, func() fyne.CanvasObject { - return widget.NewLabel("template") + return &ExtraLabel{Label: widget.NewLabel("")} }, func(i widget.ListItemID, o fyne.CanvasObject) { - o.(*widget.Label).SetText(games[i].Name) + o.(*ExtraLabel).SetText(games[i].Name) + o.(*ExtraLabel).SetID(games[i].ID) }) + list.OnSelected = func(id widget.ListItemID) { + fmt.Println(id) + } + toolbar := widget.NewToolbar( widget.NewToolbarAction(theme.FolderNewIcon(), func() { folderSelection := dialog.NewFolderOpen(func(lu fyne.ListableURI, err error) { @@ -121,53 +141,85 @@ func Make(a fyne.App, d *data.Service) fyne.Window { return w } -func doSync(d *data.Service, w fyne.Window) error { - remotes, err := remote.All() +func doSync(d *data.Service, w fyne.Window) { + rs, err := remote.All() if err != nil { - return err + d := dialog.NewError(fmt.Errorf("failed to load datastore: %w", err), w) + d.Show() + return } - i := 0 - nextCh := make(chan struct{}) - doneCh := make(chan struct{}) + it := iterator.New(rs) + if it.IsEmpty() { + dialog.NewInformation("Sync", "no remote configured", w).Show() + return + } + dialog := credential.Make(it.Value().URL, syncing(it, d, w), w) + dialog.Show() +} - var cd *credential.CredentialDialog +func syncing(it *iterator.Iterator[remote.Remote], d *data.Service, w fyne.Window) func(bool, string, string) { + return func(b bool, username, password string) { + if b { + r := it.Value() - go func() { - nextCh <- struct{}{} - }() + cli := client.New(r.URL, username, password) - for { - select { - case <-doneCh: - return nil - case <-nextCh: - { - cd = credential.Make(remotes[i].URL, func(v bool) { - if !v { - return - } - - username, password := cd.Credentials() - cli := client.New(remotes[i].URL, username, password) - - sync.NewSyncer(cli, d) - - fmt.Println(i) - - if i < len(remotes) { - go func() { - nextCh <- struct{}{} - }() - } else { - go func() { - doneCh <- struct{}{} - }() - } - - }, w) - cd.Show() + if err := cli.Ping(); err != nil { + dialog.NewError(fmt.Errorf("failed to connect to the server: %w", err), w).Show() + return } + + syncDialog := syncdialog.Make(0, w) + syncDialog.Show() + + s := sync.NewSyncer(cli, d) + + var errg error + s.SetStateCallback(func(s sync.State, g repository.Metadata) { + switch s { + case sync.FetchingMetdata: + fyne.Do(func() { + syncDialog.UpdateLabel(fmt.Sprintf("%s: fetching metadata from repository", g.Name)) + }) + case sync.Pushing: + fyne.Do(func() { + syncDialog.UpdateLabel(fmt.Sprintf("%s: pushing data to the server", g.Name)) + }) + case sync.Pulling: + fyne.Do(func() { + syncDialog.UpdateLabel(fmt.Sprintf("%s: pull data from the server", g.Name)) + }) + case sync.UpToDate: + fyne.Do(func() { + syncDialog.UpdateLabel(fmt.Sprintf("%s: already up-to-date", g.Name)) + }) + case sync.Pushed: + fyne.Do(func() { + syncDialog.UpdateLabel(fmt.Sprintf("%s: pushed", g.Name)) + }) + case sync.Pulled: + fyne.Do(func() { + syncDialog.UpdateLabel(fmt.Sprintf("%s: pulled", g.Name)) + }) + case sync.Done: + fyne.Do(func() { + syncDialog.Dismiss() + if errg == nil { + dialog.NewInformation("Sync", "The sync ended sucessfully!", w).Show() + } + }) + } + }) + + s.SetErrorCallback(func(err error, g repository.Metadata) { + errg = err + fyne.Do(func() { + dialog.NewError(err, w).Show() + }) + }) + + go s.Sync() } } } diff --git a/pkg/sync/sync.go b/pkg/sync/sync.go index a5a6cd5..9bdcbbc 100644 --- a/pkg/sync/sync.go +++ b/pkg/sync/sync.go @@ -43,6 +43,7 @@ const ( Pushed Pulled UpToDate + Done ) var ( @@ -81,7 +82,7 @@ func (s *Syncer) Sync() { for _, g := range games { r, err := remote.One(g.ID) if err != nil { - s.errorCallback(fmt.Errorf("%w: %s", ErrDatastore, err), g) + continue } if r.URL != s.cli.BaseURL() { continue @@ -90,6 +91,7 @@ func (s *Syncer) Sync() { s.errorCallback(err, g) } } + s.stateCallback(Done, repository.Metadata{}) } func (s *Syncer) sync(g repository.Metadata) error { diff --git a/pkg/tools/iterator/iterator.go b/pkg/tools/iterator/iterator.go new file mode 100644 index 0000000..6d82b2a --- /dev/null +++ b/pkg/tools/iterator/iterator.go @@ -0,0 +1,40 @@ +package iterator + +type ( + Iterator[T any] struct { + index int + values []T + } +) + +func New[T any](values []T) *Iterator[T] { + return &Iterator[T]{ + values: values, + } +} + +func (it *Iterator[T]) Next() bool { + if len(it.values) == it.index { + return false + } + it.index += 1 + return true +} + +func (it *Iterator[T]) Value() T { + if len(it.values) == 0 { + var zero T + return zero + } + + if len(it.values) == it.index { + var zero T + return zero + } + + return it.values[it.index] +} + +func (it *Iterator[T]) IsEmpty() bool { + return len(it.values) == 0 +}