diff --git a/internal/backends/backends_test.go b/internal/backends/backends_test.go index e115636..b6a6968 100644 --- a/internal/backends/backends_test.go +++ b/internal/backends/backends_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023-2025 Philipp Wolfer +Copyright © 2023 Philipp Wolfer This file is part of Scotty. @@ -18,6 +18,7 @@ Scotty. If not, see . package backends_test import ( + "reflect" "testing" "github.com/spf13/viper" @@ -32,7 +33,6 @@ import ( "go.uploadedlobster.com/scotty/internal/backends/maloja" "go.uploadedlobster.com/scotty/internal/backends/scrobblerlog" "go.uploadedlobster.com/scotty/internal/backends/spotify" - "go.uploadedlobster.com/scotty/internal/backends/spotifyhistory" "go.uploadedlobster.com/scotty/internal/backends/subsonic" "go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/i18n" @@ -93,9 +93,9 @@ func TestImplementsInterfaces(t *testing.T) { expectInterface[models.LovesExport](t, &funkwhale.FunkwhaleApiBackend{}) // expectInterface[models.LovesImport](t, &funkwhale.FunkwhaleApiBackend{}) - expectInterface[models.ListensExport](t, &jspf.JSPFBackend{}) + // expectInterface[models.ListensExport](t, &jspf.JSPFBackend{}) expectInterface[models.ListensImport](t, &jspf.JSPFBackend{}) - expectInterface[models.LovesExport](t, &jspf.JSPFBackend{}) + // expectInterface[models.LovesExport](t, &jspf.JSPFBackend{}) expectInterface[models.LovesImport](t, &jspf.JSPFBackend{}) // expectInterface[models.ListensExport](t, &lastfm.LastfmApiBackend{}) @@ -115,8 +115,6 @@ func TestImplementsInterfaces(t *testing.T) { expectInterface[models.LovesExport](t, &spotify.SpotifyApiBackend{}) // expectInterface[models.LovesImport](t, &spotify.SpotifyApiBackend{}) - expectInterface[models.ListensExport](t, &spotifyhistory.SpotifyHistoryBackend{}) - expectInterface[models.ListensExport](t, &scrobblerlog.ScrobblerLogBackend{}) expectInterface[models.ListensImport](t, &scrobblerlog.ScrobblerLogBackend{}) @@ -127,6 +125,6 @@ func TestImplementsInterfaces(t *testing.T) { func expectInterface[T interface{}](t *testing.T, backend models.Backend) { ok, name := backends.ImplementsInterface[T](&backend) if !ok { - t.Errorf("%v expected to implement %v", backend.Name(), name) + t.Errorf("%v expected to implement %v", reflect.TypeOf(backend).Name(), name) } } diff --git a/internal/backends/deezer/deezer.go b/internal/backends/deezer/deezer.go index 2209769..4ba367d 100644 --- a/internal/backends/deezer/deezer.go +++ b/internal/backends/deezer/deezer.go @@ -1,5 +1,5 @@ /* -Copyright © 2023-2025 Philipp Wolfer +Copyright © 2023 Philipp Wolfer Scotty is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -77,7 +77,7 @@ func (b *DeezerApiBackend) OAuth2Setup(token oauth2.TokenSource) error { return nil } -func (b *DeezerApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { +func (b *DeezerApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) { // Choose a high offset, we attempt to search the loves backwards starting // at the oldest one. offset := math.MaxInt32 @@ -88,18 +88,13 @@ func (b *DeezerApiBackend) ExportListens(oldestTimestamp time.Time, results chan totalDuration := startTime.Sub(oldestTimestamp) - p := models.TransferProgress{ - Export: &models.Progress{ - Total: int64(totalDuration.Seconds()), - }, - } + p := models.Progress{Total: int64(totalDuration.Seconds())} out: for { result, err := b.client.UserHistory(offset, perPage) if err != nil { - p.Export.Abort() - progress <- p + progress <- p.Abort() results <- models.ListensResult{Error: err} return } @@ -107,6 +102,7 @@ out: // The offset was higher then the actual number of tracks. Adjust the offset // and continue. if offset >= result.Total { + p.Total = int64(result.Total) offset = max(result.Total-perPage, 0) continue } @@ -132,8 +128,7 @@ out: } remainingTime := startTime.Sub(minTime) - p.Export.TotalItems += len(listens) - p.Export.Elapsed = int64(totalDuration.Seconds() - remainingTime.Seconds()) + p.Elapsed = int64(totalDuration.Seconds() - remainingTime.Seconds()) progress <- p results <- models.ListensResult{Items: listens, OldestTimestamp: minTime} @@ -149,29 +144,23 @@ out: } results <- models.ListensResult{OldestTimestamp: minTime} - p.Export.Complete() - progress <- p + progress <- p.Complete() } -func (b *DeezerApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.TransferProgress) { +func (b *DeezerApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) { // Choose a high offset, we attempt to search the loves backwards starting // at the oldest one. offset := math.MaxInt32 perPage := MaxItemsPerGet - p := models.TransferProgress{ - Export: &models.Progress{ - Total: int64(perPage), - }, - } + p := models.Progress{Total: int64(perPage)} var totalCount int out: for { result, err := b.client.UserTracks(offset, perPage) if err != nil { - p.Export.Abort() - progress <- p + progress <- p.Abort() results <- models.LovesResult{Error: err} return } @@ -179,8 +168,8 @@ out: // The offset was higher then the actual number of tracks. Adjust the offset // and continue. if offset >= result.Total { + p.Total = int64(result.Total) totalCount = result.Total - p.Export.Total = int64(totalCount) offset = max(result.Total-perPage, 0) continue } @@ -197,14 +186,13 @@ out: loves = append(loves, love) } else { totalCount -= 1 + break } } sort.Sort(loves) results <- models.LovesResult{Items: loves, Total: totalCount} - p.Export.TotalItems = totalCount - p.Export.Total = int64(totalCount) - p.Export.Elapsed += int64(count) + p.Elapsed += int64(count) progress <- p if offset <= 0 { @@ -218,8 +206,7 @@ out: } } - p.Export.Complete() - progress <- p + progress <- p.Complete() } func (t Listen) AsListen() models.Listen { diff --git a/internal/backends/dump/dump.go b/internal/backends/dump/dump.go index add8711..70be12d 100644 --- a/internal/backends/dump/dump.go +++ b/internal/backends/dump/dump.go @@ -1,5 +1,5 @@ /* -Copyright © 2023-2025 Philipp Wolfer +Copyright © 2023 Philipp Wolfer This file is part of Scotty. @@ -36,27 +36,27 @@ func (b *DumpBackend) InitConfig(config *config.ServiceConfig) error { func (b *DumpBackend) StartImport() error { return nil } func (b *DumpBackend) FinishImport() error { return nil } -func (b *DumpBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { +func (b *DumpBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { for _, listen := range export.Items { importResult.UpdateTimestamp(listen.ListenedAt) importResult.ImportCount += 1 msg := fmt.Sprintf("🎶 %v: \"%v\" by %v (%v)", listen.ListenedAt, listen.TrackName, listen.ArtistName(), listen.RecordingMBID) importResult.Log(models.Info, msg) - progress <- models.TransferProgress{}.FromImportResult(importResult, false) + progress <- models.Progress{}.FromImportResult(importResult) } return importResult, nil } -func (b *DumpBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { +func (b *DumpBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { for _, love := range export.Items { importResult.UpdateTimestamp(love.Created) importResult.ImportCount += 1 msg := fmt.Sprintf("❤️ %v: \"%v\" by %v (%v)", love.Created, love.TrackName, love.ArtistName(), love.RecordingMBID) importResult.Log(models.Info, msg) - progress <- models.TransferProgress{}.FromImportResult(importResult, false) + progress <- models.Progress{}.FromImportResult(importResult) } return importResult, nil diff --git a/internal/backends/export.go b/internal/backends/export.go index 54daafb..0346af2 100644 --- a/internal/backends/export.go +++ b/internal/backends/export.go @@ -16,7 +16,6 @@ Scotty. If not, see . package backends import ( - "sync" "time" "go.uploadedlobster.com/scotty/internal/models" @@ -24,7 +23,7 @@ import ( type ExportProcessor[T models.ListensResult | models.LovesResult] interface { ExportBackend() models.Backend - Process(wg *sync.WaitGroup, oldestTimestamp time.Time, results chan T, progress chan models.TransferProgress) + Process(oldestTimestamp time.Time, results chan T, progress chan models.Progress) } type ListensExportProcessor struct { @@ -35,10 +34,9 @@ func (p ListensExportProcessor) ExportBackend() models.Backend { return p.Backend } -func (p ListensExportProcessor) Process(wg *sync.WaitGroup, oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { - wg.Add(1) - defer wg.Done() +func (p ListensExportProcessor) Process(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) { defer close(results) + defer close(progress) p.Backend.ExportListens(oldestTimestamp, results, progress) } @@ -50,9 +48,8 @@ func (p LovesExportProcessor) ExportBackend() models.Backend { return p.Backend } -func (p LovesExportProcessor) Process(wg *sync.WaitGroup, oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.TransferProgress) { - wg.Add(1) - defer wg.Done() +func (p LovesExportProcessor) Process(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) { defer close(results) + defer close(progress) p.Backend.ExportLoves(oldestTimestamp, results, progress) } diff --git a/internal/backends/funkwhale/funkwhale.go b/internal/backends/funkwhale/funkwhale.go index cd2f28e..e32a952 100644 --- a/internal/backends/funkwhale/funkwhale.go +++ b/internal/backends/funkwhale/funkwhale.go @@ -1,5 +1,5 @@ /* -Copyright © 2023-2025 Philipp Wolfer +Copyright © 2023 Philipp Wolfer This file is part of Scotty. @@ -60,26 +60,19 @@ func (b *FunkwhaleApiBackend) InitConfig(config *config.ServiceConfig) error { return nil } -func (b *FunkwhaleApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { +func (b *FunkwhaleApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) { page := 1 perPage := MaxItemsPerGet // We need to gather the full list of listens in order to sort them listens := make(models.ListensList, 0, 2*perPage) - p := models.TransferProgress{ - Export: &models.Progress{ - Total: int64(perPage), - }, - } + p := models.Progress{Total: int64(perPage)} out: for { result, err := b.client.GetHistoryListenings(b.username, page, perPage) if err != nil { - p.Export.Abort() - progress <- p results <- models.ListensResult{Error: err} - return } count := len(result.Results) @@ -90,7 +83,7 @@ out: for _, fwListen := range result.Results { listen := fwListen.AsListen() if listen.ListenedAt.After(oldestTimestamp) { - p.Export.Elapsed += 1 + p.Elapsed += 1 listens = append(listens, listen) } else { break out @@ -99,42 +92,34 @@ out: if result.Next == "" { // No further results - p.Export.Total = p.Export.Elapsed - p.Export.Total -= int64(perPage - count) + p.Total = p.Elapsed + p.Total -= int64(perPage - count) break out } - p.Export.TotalItems = len(listens) - p.Export.Total += int64(perPage) + p.Total += int64(perPage) progress <- p page += 1 } sort.Sort(listens) - p.Export.TotalItems = len(listens) - p.Export.Complete() - progress <- p + progress <- p.Complete() results <- models.ListensResult{Items: listens} } -func (b *FunkwhaleApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.TransferProgress) { +func (b *FunkwhaleApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) { page := 1 perPage := MaxItemsPerGet // We need to gather the full list of listens in order to sort them loves := make(models.LovesList, 0, 2*perPage) - p := models.TransferProgress{ - Export: &models.Progress{ - Total: int64(perPage), - }, - } + p := models.Progress{Total: int64(perPage)} out: for { result, err := b.client.GetFavoriteTracks(page, perPage) if err != nil { - p.Export.Abort() - progress <- p + progress <- p.Abort() results <- models.LovesResult{Error: err} return } @@ -147,7 +132,7 @@ out: for _, favorite := range result.Results { love := favorite.AsLove() if love.Created.After(oldestTimestamp) { - p.Export.Elapsed += 1 + p.Elapsed += 1 loves = append(loves, love) } else { break out @@ -159,16 +144,13 @@ out: break out } - p.Export.TotalItems = len(loves) - p.Export.Total += int64(perPage) + p.Total += int64(perPage) progress <- p page += 1 } sort.Sort(loves) - p.Export.TotalItems = len(loves) - p.Export.Complete() - progress <- p + progress <- p.Complete() results <- models.LovesResult{Items: loves} } diff --git a/internal/backends/import.go b/internal/backends/import.go index 0a2e341..407082c 100644 --- a/internal/backends/import.go +++ b/internal/backends/import.go @@ -18,15 +18,13 @@ Scotty. If not, see . package backends import ( - "sync" - "go.uploadedlobster.com/scotty/internal/models" ) type ImportProcessor[T models.ListensResult | models.LovesResult] interface { ImportBackend() models.ImportBackend - Process(wg *sync.WaitGroup, results chan T, out chan models.ImportResult, progress chan models.TransferProgress) - Import(export T, result models.ImportResult, out chan models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) + Process(results chan T, out chan models.ImportResult, progress chan models.Progress) + Import(export T, result models.ImportResult, out chan models.ImportResult, progress chan models.Progress) (models.ImportResult, error) } type ListensImportProcessor struct { @@ -37,11 +35,11 @@ func (p ListensImportProcessor) ImportBackend() models.ImportBackend { return p.Backend } -func (p ListensImportProcessor) Process(wg *sync.WaitGroup, results chan models.ListensResult, out chan models.ImportResult, progress chan models.TransferProgress) { - process(wg, p, results, out, progress) +func (p ListensImportProcessor) Process(results chan models.ListensResult, out chan models.ImportResult, progress chan models.Progress) { + process(p, results, out, progress) } -func (p ListensImportProcessor) Import(export models.ListensResult, result models.ImportResult, out chan models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { +func (p ListensImportProcessor) Import(export models.ListensResult, result models.ImportResult, out chan models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { if export.Error != nil { return result, export.Error } @@ -66,11 +64,11 @@ func (p LovesImportProcessor) ImportBackend() models.ImportBackend { return p.Backend } -func (p LovesImportProcessor) Process(wg *sync.WaitGroup, results chan models.LovesResult, out chan models.ImportResult, progress chan models.TransferProgress) { - process(wg, p, results, out, progress) +func (p LovesImportProcessor) Process(results chan models.LovesResult, out chan models.ImportResult, progress chan models.Progress) { + process(p, results, out, progress) } -func (p LovesImportProcessor) Import(export models.LovesResult, result models.ImportResult, out chan models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { +func (p LovesImportProcessor) Import(export models.LovesResult, result models.ImportResult, out chan models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { if export.Error != nil { return result, export.Error } @@ -87,12 +85,11 @@ func (p LovesImportProcessor) Import(export models.LovesResult, result models.Im return importResult, nil } -func process[R models.LovesResult | models.ListensResult, P ImportProcessor[R]](wg *sync.WaitGroup, processor P, results chan R, out chan models.ImportResult, progress chan models.TransferProgress) { - wg.Add(1) - defer wg.Done() +func process[R models.LovesResult | models.ListensResult, P ImportProcessor[R]](processor P, results chan R, out chan models.ImportResult, progress chan models.Progress) { defer close(out) + defer close(progress) result := models.ImportResult{} - p := models.TransferProgress{} + p := models.Progress{} if err := processor.ImportBackend().StartImport(); err != nil { out <- handleError(result, err, progress) @@ -107,7 +104,7 @@ func process[R models.LovesResult | models.ListensResult, P ImportProcessor[R]]( out <- handleError(result, err, progress) return } - progress <- p.FromImportResult(result, false) + progress <- p.FromImportResult(result) } if err := processor.ImportBackend().FinishImport(); err != nil { @@ -115,14 +112,12 @@ func process[R models.LovesResult | models.ListensResult, P ImportProcessor[R]]( return } - progress <- p.FromImportResult(result, true) + progress <- p.FromImportResult(result).Complete() out <- result } -func handleError(result models.ImportResult, err error, progress chan models.TransferProgress) models.ImportResult { +func handleError(result models.ImportResult, err error, progress chan models.Progress) models.ImportResult { result.Error = err - p := models.TransferProgress{}.FromImportResult(result, false) - p.Import.Abort() - progress <- p + progress <- models.Progress{}.FromImportResult(result).Abort() return result } diff --git a/internal/backends/jspf/jspf.go b/internal/backends/jspf/jspf.go index 0e200f2..e981741 100644 --- a/internal/backends/jspf/jspf.go +++ b/internal/backends/jspf/jspf.go @@ -93,15 +93,10 @@ func (b *JSPFBackend) FinishImport() error { return b.writeJSPF() } -func (b *JSPFBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { +func (b *JSPFBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) { err := b.readJSPF() - p := models.TransferProgress{ - Export: &models.Progress{}, - } - if err != nil { - p.Export.Abort() - progress <- p + progress <- models.Progress{}.Abort() results <- models.ListensResult{Error: err} return } @@ -114,13 +109,11 @@ func (b *JSPFBackend) ExportListens(oldestTimestamp time.Time, results chan mode } } sort.Sort(listens) - p.Export.Total = int64(len(listens)) - p.Export.Complete() - progress <- p + progress <- models.Progress{Total: int64(len(listens))}.Complete() results <- models.ListensResult{Items: listens} } -func (b *JSPFBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { +func (b *JSPFBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { for _, listen := range export.Items { track := listenAsTrack(listen) b.playlist.Tracks = append(b.playlist.Tracks, track) @@ -128,19 +121,14 @@ func (b *JSPFBackend) ImportListens(export models.ListensResult, importResult mo importResult.UpdateTimestamp(listen.ListenedAt) } - progress <- models.TransferProgress{}.FromImportResult(importResult, false) + progress <- models.Progress{}.FromImportResult(importResult) return importResult, nil } -func (b *JSPFBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.TransferProgress) { +func (b *JSPFBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) { err := b.readJSPF() - p := models.TransferProgress{ - Export: &models.Progress{}, - } - if err != nil { - p.Export.Abort() - progress <- p + progress <- models.Progress{}.Abort() results <- models.LovesResult{Error: err} return } @@ -153,13 +141,11 @@ func (b *JSPFBackend) ExportLoves(oldestTimestamp time.Time, results chan models } } sort.Sort(loves) - p.Export.Total = int64(len(loves)) - p.Export.Complete() - progress <- p + progress <- models.Progress{Total: int64(len(loves))}.Complete() results <- models.LovesResult{Items: loves} } -func (b *JSPFBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { +func (b *JSPFBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { for _, love := range export.Items { track := loveAsTrack(love) b.playlist.Tracks = append(b.playlist.Tracks, track) @@ -167,7 +153,7 @@ func (b *JSPFBackend) ImportLoves(export models.LovesResult, importResult models importResult.UpdateTimestamp(love.Created) } - progress <- models.TransferProgress{}.FromImportResult(importResult, false) + progress <- models.Progress{}.FromImportResult(importResult) return importResult, nil } diff --git a/internal/backends/lastfm/lastfm.go b/internal/backends/lastfm/lastfm.go index d262ada..d45f793 100644 --- a/internal/backends/lastfm/lastfm.go +++ b/internal/backends/lastfm/lastfm.go @@ -1,5 +1,5 @@ /* -Copyright © 2023-2025 Philipp Wolfer +Copyright © 2023 Philipp Wolfer Scotty is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -88,17 +88,13 @@ func (b *LastfmApiBackend) OAuth2Setup(token oauth2.TokenSource) error { return nil } -func (b *LastfmApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { +func (b *LastfmApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) { page := MaxPage minTime := oldestTimestamp perPage := MaxItemsPerGet // We need to gather the full list of listens in order to sort them - p := models.TransferProgress{ - Export: &models.Progress{ - Total: int64(page), - }, - } + p := models.Progress{Total: int64(page)} out: for page > 0 { @@ -112,8 +108,7 @@ out: result, err := b.client.User.GetRecentTracks(args) if err != nil { results <- models.ListensResult{Error: err} - p.Export.Abort() - progress <- p + progress <- p.Abort() return } @@ -132,12 +127,11 @@ out: timestamp, err := strconv.ParseInt(scrobble.Date.Uts, 10, 64) if err != nil { results <- models.ListensResult{Error: err} - p.Export.Abort() - progress <- p + progress <- p.Abort() break out } if timestamp > oldestTimestamp.Unix() { - p.Export.Elapsed += 1 + p.Elapsed += 1 listen := models.Listen{ ListenedAt: time.Unix(timestamp, 0), UserName: b.username, @@ -171,18 +165,16 @@ out: Total: result.Total, OldestTimestamp: minTime, } - p.Export.Total = int64(result.TotalPages) - p.Export.Elapsed = int64(result.TotalPages - page) - p.Export.TotalItems += len(listens) + p.Total = int64(result.TotalPages) + p.Elapsed = int64(result.TotalPages - page) progress <- p } results <- models.ListensResult{OldestTimestamp: minTime} - p.Export.Complete() - progress <- p + progress <- p.Complete() } -func (b *LastfmApiBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { +func (b *LastfmApiBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { total := len(export.Items) for i := 0; i < total; i += MaxListensPerSubmission { listens := export.Items[i:min(i+MaxListensPerSubmission, total)] @@ -252,24 +244,20 @@ func (b *LastfmApiBackend) ImportListens(export models.ListensResult, importResu importResult.UpdateTimestamp(listens[count-1].ListenedAt) importResult.ImportCount += accepted - progress <- models.TransferProgress{}.FromImportResult(importResult, false) + progress <- models.Progress{}.FromImportResult(importResult) } return importResult, nil } -func (b *LastfmApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.TransferProgress) { +func (b *LastfmApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) { // Choose a high offset, we attempt to search the loves backwards starting // at the oldest one. page := 1 perPage := MaxItemsPerGet loves := make(models.LovesList, 0, 2*MaxItemsPerGet) - p := models.TransferProgress{ - Export: &models.Progress{ - Total: int64(perPage), - }, - } + p := models.Progress{Total: int64(perPage)} var totalCount int out: @@ -280,12 +268,12 @@ out: "page": page, }) if err != nil { - p.Export.Abort() - progress <- p + progress <- p.Abort() results <- models.LovesResult{Error: err} return } + p.Total = int64(result.Total) count := len(result.Tracks) if count == 0 { break out @@ -294,8 +282,7 @@ out: for _, track := range result.Tracks { timestamp, err := strconv.ParseInt(track.Date.Uts, 10, 64) if err != nil { - p.Export.Abort() - progress <- p + progress <- p.Abort() results <- models.LovesResult{Error: err} return } @@ -321,21 +308,18 @@ out: } } - p.Export.Total += int64(perPage) - p.Export.TotalItems = totalCount - p.Export.Elapsed += int64(count) + p.Elapsed += int64(count) progress <- p page += 1 } sort.Sort(loves) - p.Export.Complete() - progress <- p results <- models.LovesResult{Items: loves, Total: totalCount} + progress <- p.Complete() } -func (b *LastfmApiBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { +func (b *LastfmApiBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { for _, love := range export.Items { err := b.client.Track.Love(lastfm.P{ "track": love.TrackName, @@ -351,7 +335,7 @@ func (b *LastfmApiBackend) ImportLoves(export models.LovesResult, importResult m importResult.Log(models.Error, msg) } - progress <- models.TransferProgress{}.FromImportResult(importResult, false) + progress <- models.Progress{}.FromImportResult(importResult) } return importResult, nil diff --git a/internal/backends/listenbrainz/listenbrainz.go b/internal/backends/listenbrainz/listenbrainz.go index 61597d1..fffe0f0 100644 --- a/internal/backends/listenbrainz/listenbrainz.go +++ b/internal/backends/listenbrainz/listenbrainz.go @@ -1,5 +1,5 @@ /* -Copyright © 2023-2025 Philipp Wolfer +Copyright © 2023 Philipp Wolfer This file is part of Scotty. @@ -72,25 +72,21 @@ func (b *ListenBrainzApiBackend) InitConfig(config *config.ServiceConfig) error func (b *ListenBrainzApiBackend) StartImport() error { return nil } func (b *ListenBrainzApiBackend) FinishImport() error { return nil } -func (b *ListenBrainzApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { +func (b *ListenBrainzApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) { startTime := time.Now() minTime := oldestTimestamp if minTime.Unix() < 1 { minTime = time.Unix(1, 0) } - totalDuration := startTime.Sub(oldestTimestamp) - p := models.TransferProgress{ - Export: &models.Progress{ - Total: int64(totalDuration.Seconds()), - }, - } + totalDuration := startTime.Sub(minTime) + + p := models.Progress{Total: int64(totalDuration.Seconds())} for { result, err := b.client.GetListens(b.username, time.Now(), minTime) if err != nil { - p.Export.Abort() - progress <- p + progress <- p.Abort() results <- models.ListensResult{Error: err} return } @@ -100,7 +96,7 @@ func (b *ListenBrainzApiBackend) ExportListens(oldestTimestamp time.Time, result if minTime.Unix() < result.Payload.OldestListenTimestamp { minTime = time.Unix(result.Payload.OldestListenTimestamp, 0) totalDuration = startTime.Sub(minTime) - p.Export.Total = int64(totalDuration.Seconds()) + p.Total = int64(totalDuration.Seconds()) continue } else { break @@ -123,20 +119,18 @@ func (b *ListenBrainzApiBackend) ExportListens(oldestTimestamp time.Time, result } sort.Sort(listens) - p.Export.TotalItems += len(listens) - p.Export.Elapsed = int64(totalDuration.Seconds() - remainingTime.Seconds()) + p.Elapsed = int64(totalDuration.Seconds() - remainingTime.Seconds()) progress <- p results <- models.ListensResult{Items: listens, OldestTimestamp: minTime} } results <- models.ListensResult{OldestTimestamp: minTime} - p.Export.Complete() - progress <- p + progress <- p.Complete() } -func (b *ListenBrainzApiBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { +func (b *ListenBrainzApiBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { total := len(export.Items) - p := models.TransferProgress{}.FromImportResult(importResult, false) + p := models.Progress{}.FromImportResult(importResult) for i := 0; i < total; i += MaxListensPerRequest { listens := export.Items[i:min(i+MaxListensPerRequest, total)] count := len(listens) @@ -152,7 +146,7 @@ func (b *ListenBrainzApiBackend) ImportListens(export models.ListensResult, impo for _, l := range listens { if b.checkDuplicates { isDupe, err := b.checkDuplicateListen(l) - p.Import.Elapsed += 1 + p.Elapsed += 1 progress <- p if err != nil { return importResult, err @@ -192,36 +186,31 @@ func (b *ListenBrainzApiBackend) ImportListens(export models.ListensResult, impo importResult.UpdateTimestamp(listens[count-1].ListenedAt) } importResult.ImportCount += count - progress <- p.FromImportResult(importResult, false) + progress <- p.FromImportResult(importResult) } return importResult, nil } -func (b *ListenBrainzApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.TransferProgress) { +func (b *ListenBrainzApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) { exportChan := make(chan models.LovesResult) - p := models.TransferProgress{ - Export: &models.Progress{}, - } + p := models.Progress{} go b.exportLoves(oldestTimestamp, exportChan) for existingLoves := range exportChan { if existingLoves.Error != nil { - p.Export.Abort() - progress <- p + progress <- p.Abort() results <- models.LovesResult{Error: existingLoves.Error} return } - p.Export.TotalItems = existingLoves.Total - p.Export.Total = int64(existingLoves.Total) - p.Export.Elapsed += int64(len(existingLoves.Items)) + p.Total = int64(existingLoves.Total) + p.Elapsed += int64(existingLoves.Items.Len()) progress <- p results <- existingLoves } - p.Export.Complete() - progress <- p + progress <- p.Complete() } func (b *ListenBrainzApiBackend) exportLoves(oldestTimestamp time.Time, results chan models.LovesResult) { @@ -271,7 +260,7 @@ out: } } -func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { +func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { if len(b.existingMBIDs) == 0 { existingLovesChan := make(chan models.LovesResult) go b.exportLoves(time.Unix(0, 0), existingLovesChan) @@ -341,7 +330,7 @@ func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importRe importResult.Log(models.Error, msg) } - progress <- models.TransferProgress{}.FromImportResult(importResult, false) + progress <- models.Progress{}.FromImportResult(importResult) } return importResult, nil diff --git a/internal/backends/maloja/maloja.go b/internal/backends/maloja/maloja.go index 8968942..a22393b 100644 --- a/internal/backends/maloja/maloja.go +++ b/internal/backends/maloja/maloja.go @@ -1,5 +1,5 @@ /* -Copyright © 2023-2025 Philipp Wolfer +Copyright © 2023 Philipp Wolfer This file is part of Scotty. @@ -63,24 +63,19 @@ func (b *MalojaApiBackend) InitConfig(config *config.ServiceConfig) error { func (b *MalojaApiBackend) StartImport() error { return nil } func (b *MalojaApiBackend) FinishImport() error { return nil } -func (b *MalojaApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { +func (b *MalojaApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) { page := 0 perPage := MaxItemsPerGet // We need to gather the full list of listens in order to sort them listens := make(models.ListensList, 0, 2*perPage) - p := models.TransferProgress{ - Export: &models.Progress{ - Total: int64(perPage), - }, - } + p := models.Progress{Total: int64(perPage)} out: for { result, err := b.client.GetScrobbles(page, perPage) if err != nil { - p.Export.Abort() - progress <- p + progress <- p.Complete() results <- models.ListensResult{Error: err} return } @@ -92,27 +87,25 @@ out: for _, scrobble := range result.List { if scrobble.ListenedAt > oldestTimestamp.Unix() { - p.Export.Elapsed += 1 + p.Elapsed += 1 listens = append(listens, scrobble.AsListen()) } else { break out } } - p.Export.TotalItems = len(listens) - p.Export.Total += int64(perPage) + p.Total += int64(perPage) progress <- p page += 1 } sort.Sort(listens) - p.Export.Complete() - progress <- p + progress <- p.Complete() results <- models.ListensResult{Items: listens} } -func (b *MalojaApiBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { - p := models.TransferProgress{}.FromImportResult(importResult, false) +func (b *MalojaApiBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { + p := models.Progress{}.FromImportResult(importResult) for _, listen := range export.Items { scrobble := NewScrobble{ Title: listen.TrackName, @@ -133,7 +126,7 @@ func (b *MalojaApiBackend) ImportListens(export models.ListensResult, importResu importResult.UpdateTimestamp(listen.ListenedAt) importResult.ImportCount += 1 - progress <- p.FromImportResult(importResult, false) + progress <- p.FromImportResult(importResult) } return importResult, nil diff --git a/internal/backends/scrobblerlog/scrobblerlog.go b/internal/backends/scrobblerlog/scrobblerlog.go index db4e349..7890971 100644 --- a/internal/backends/scrobblerlog/scrobblerlog.go +++ b/internal/backends/scrobblerlog/scrobblerlog.go @@ -131,14 +131,10 @@ func (b *ScrobblerLogBackend) FinishImport() error { return b.file.Close() } -func (b *ScrobblerLogBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { +func (b *ScrobblerLogBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) { file, err := os.Open(b.filePath) - p := models.TransferProgress{ - Export: &models.Progress{}, - } if err != nil { - p.Export.Abort() - progress <- p + progress <- models.Progress{}.Abort() results <- models.ListensResult{Error: err} return } @@ -147,8 +143,7 @@ func (b *ScrobblerLogBackend) ExportListens(oldestTimestamp time.Time, results c err = b.log.Parse(file, b.ignoreSkipped) if err != nil { - p.Export.Abort() - progress <- p + progress <- models.Progress{}.Complete() results <- models.ListensResult{Error: err} return } @@ -162,13 +157,11 @@ func (b *ScrobblerLogBackend) ExportListens(oldestTimestamp time.Time, results c } } sort.Sort(listens) - p.Export.Total = int64(len(listens)) - p.Export.Complete() - progress <- p + progress <- models.Progress{Total: int64(len(listens))}.Complete() results <- models.ListensResult{Items: listens} } -func (b *ScrobblerLogBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.TransferProgress) (models.ImportResult, error) { +func (b *ScrobblerLogBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) { records := make([]scrobblerlog.Record, len(export.Items)) for i, listen := range export.Items { records[i] = listenToRecord(listen) @@ -180,7 +173,7 @@ func (b *ScrobblerLogBackend) ImportListens(export models.ListensResult, importR importResult.UpdateTimestamp(lastTimestamp) importResult.ImportCount += len(export.Items) - progress <- models.TransferProgress{}.FromImportResult(importResult, false) + progress <- models.Progress{}.FromImportResult(importResult) return importResult, nil } diff --git a/internal/backends/spotify/spotify.go b/internal/backends/spotify/spotify.go index 5d45087..be48dfe 100644 --- a/internal/backends/spotify/spotify.go +++ b/internal/backends/spotify/spotify.go @@ -1,5 +1,5 @@ /* -Copyright © 2023-2025 Philipp Wolfer +Copyright © 2023 Philipp Wolfer This file is part of Scotty. @@ -95,22 +95,18 @@ func (b *SpotifyApiBackend) OAuth2Setup(token oauth2.TokenSource) error { return nil } -func (b *SpotifyApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { +func (b *SpotifyApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) { startTime := time.Now() minTime := oldestTimestamp totalDuration := startTime.Sub(oldestTimestamp) - p := models.TransferProgress{ - Export: &models.Progress{ - Total: int64(totalDuration.Seconds()), - }, - } + + p := models.Progress{Total: int64(totalDuration.Seconds())} for { result, err := b.client.RecentlyPlayedAfter(minTime, MaxItemsPerGet) if err != nil { - p.Export.Abort() - progress <- p + progress <- p.Abort() results <- models.ListensResult{Error: err} return } @@ -122,8 +118,7 @@ func (b *SpotifyApiBackend) ExportListens(oldestTimestamp time.Time, results cha // Set minTime to the newest returned listen after, err := strconv.ParseInt(result.Cursors.After, 10, 64) if err != nil { - p.Export.Abort() - progress <- p + progress <- p.Abort() results <- models.ListensResult{Error: err} return } else if after <= minTime.Unix() { @@ -151,28 +146,22 @@ func (b *SpotifyApiBackend) ExportListens(oldestTimestamp time.Time, results cha } sort.Sort(listens) - p.Export.TotalItems += len(listens) - p.Export.Elapsed = int64(totalDuration.Seconds() - remainingTime.Seconds()) + p.Elapsed = int64(totalDuration.Seconds() - remainingTime.Seconds()) progress <- p results <- models.ListensResult{Items: listens, OldestTimestamp: minTime} } results <- models.ListensResult{OldestTimestamp: minTime} - p.Export.Complete() - progress <- p + progress <- p.Complete() } -func (b *SpotifyApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.TransferProgress) { +func (b *SpotifyApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) { // Choose a high offset, we attempt to search the loves backwards starting // at the oldest one. offset := math.MaxInt32 perPage := MaxItemsPerGet - p := models.TransferProgress{ - Export: &models.Progress{ - Total: int64(perPage), - }, - } + p := models.Progress{Total: int64(perPage)} totalCount := 0 exportCount := 0 @@ -180,8 +169,7 @@ out: for { result, err := b.client.UserTracks(offset, perPage) if err != nil { - p.Export.Abort() - progress <- p + progress <- p.Abort() results <- models.LovesResult{Error: err} return } @@ -189,7 +177,7 @@ out: // The offset was higher then the actual number of tracks. Adjust the offset // and continue. if offset >= result.Total { - p.Export.Total = int64(result.Total) + p.Total = int64(result.Total) totalCount = result.Total offset = max(result.Total-perPage, 0) continue @@ -213,7 +201,7 @@ out: exportCount += len(loves) sort.Sort(loves) results <- models.LovesResult{Items: loves, Total: totalCount} - p.Export.Elapsed += int64(count) + p.Elapsed += int64(count) progress <- p if offset <= 0 { @@ -228,8 +216,7 @@ out: } results <- models.LovesResult{Total: exportCount} - p.Export.Complete() - progress <- p + progress <- p.Complete() } func (l Listen) AsListen() models.Listen { diff --git a/internal/backends/spotifyhistory/spotifyhistory.go b/internal/backends/spotifyhistory/spotifyhistory.go index d5c87bb..23309f3 100644 --- a/internal/backends/spotifyhistory/spotifyhistory.go +++ b/internal/backends/spotifyhistory/spotifyhistory.go @@ -1,5 +1,5 @@ /* -Copyright © 2023-2025 Philipp Wolfer +Copyright © 2023 Philipp Wolfer This file is part of Scotty. @@ -72,27 +72,21 @@ func (b *SpotifyHistoryBackend) InitConfig(config *config.ServiceConfig) error { return nil } -func (b *SpotifyHistoryBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.TransferProgress) { +func (b *SpotifyHistoryBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) { files, err := filepath.Glob(path.Join(b.dirPath, historyFileGlob)) - p := models.TransferProgress{ - Export: &models.Progress{}, - } - if err != nil { - p.Export.Abort() - progress <- p + progress <- models.Progress{}.Abort() results <- models.ListensResult{Error: err} return } slices.Sort(files) fileCount := int64(len(files)) - p.Export.Total = fileCount + p := models.Progress{Total: fileCount} for i, filePath := range files { history, err := readHistoryFile(filePath) if err != nil { - p.Export.Abort() - progress <- p + progress <- models.Progress{}.Abort() results <- models.ListensResult{Error: err} return } @@ -103,13 +97,11 @@ func (b *SpotifyHistoryBackend) ExportListens(oldestTimestamp time.Time, results }) sort.Sort(listens) results <- models.ListensResult{Items: listens} - p.Export.Elapsed = int64(i) - p.Export.TotalItems += len(listens) + p.Elapsed = int64(i) progress <- p } - p.Export.Complete() - progress <- p + progress <- p.Complete() } func readHistoryFile(filePath string) (StreamingHistory, error) { diff --git a/internal/backends/subsonic/subsonic.go b/internal/backends/subsonic/subsonic.go index 2098688..370765e 100644 --- a/internal/backends/subsonic/subsonic.go +++ b/internal/backends/subsonic/subsonic.go @@ -1,5 +1,5 @@ /* -Copyright © 2023-2025 Philipp Wolfer +Copyright © 2023 Philipp Wolfer This file is part of Scotty. @@ -63,30 +63,25 @@ func (b *SubsonicApiBackend) InitConfig(config *config.ServiceConfig) error { return nil } -func (b *SubsonicApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.TransferProgress) { +func (b *SubsonicApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) { err := b.client.Authenticate(b.password) - p := models.TransferProgress{ - Export: &models.Progress{}, - } if err != nil { - p.Export.Abort() - progress <- p + progress <- models.Progress{}.Abort() results <- models.LovesResult{Error: err} return } starred, err := b.client.GetStarred2(map[string]string{}) if err != nil { - p.Export.Abort() - progress <- p + progress <- models.Progress{}.Abort() results <- models.LovesResult{Error: err} return } loves := b.filterSongs(starred.Song, oldestTimestamp) - p.Export.Total = int64(len(loves)) - p.Export.Complete() - progress <- p + progress <- models.Progress{ + Total: int64(loves.Len()), + }.Complete() results <- models.LovesResult{Items: loves} } diff --git a/internal/cli/progress.go b/internal/cli/progress.go index e93ec18..88339ef 100644 --- a/internal/cli/progress.go +++ b/internal/cli/progress.go @@ -28,18 +28,7 @@ import ( "go.uploadedlobster.com/scotty/internal/models" ) -type progressBarUpdater struct { - wg *sync.WaitGroup - progress *mpb.Progress - exportBar *mpb.Bar - importBar *mpb.Bar - updateChan chan models.TransferProgress - lastExportUpdate time.Time - totalItems int - importedItems int -} - -func setupProgressBars(updateChan chan models.TransferProgress) progressBarUpdater { +func progressBar(exportProgress chan models.Progress, importProgress chan models.Progress) *mpb.Progress { wg := &sync.WaitGroup{} p := mpb.New( mpb.WithWaitGroup(wg), @@ -48,81 +37,16 @@ func setupProgressBars(updateChan chan models.TransferProgress) progressBarUpdat mpb.WithAutoRefresh(), ) - u := progressBarUpdater{ - wg: wg, - progress: p, - exportBar: initExportProgressBar(p, i18n.Tr("exporting")), - importBar: initImportProgressBar(p, i18n.Tr("importing")), - updateChan: updateChan, - } + exportBar := setupProgressBar(p, i18n.Tr("exporting")) + importBar := setupProgressBar(p, i18n.Tr("importing")) + go updateProgressBar(exportBar, wg, exportProgress) + go updateProgressBar(importBar, wg, importProgress) - go u.update() - return u + return p } -func (u *progressBarUpdater) close() { - close(u.updateChan) - u.progress.Wait() -} - -func (u *progressBarUpdater) update() { - u.wg.Add(1) - defer u.wg.Done() - u.lastExportUpdate = time.Now() - for progress := range u.updateChan { - if progress.Export != nil { - u.updateExportProgress(progress.Export) - } - - if progress.Import != nil { - if int64(u.totalItems) > progress.Import.Total { - progress.Import.Total = int64(u.totalItems) - } - u.updateImportProgress(progress.Import) - } - } -} - -func (u *progressBarUpdater) updateExportProgress(progress *models.Progress) { - bar := u.exportBar - u.totalItems = progress.TotalItems - - if progress.Aborted { - bar.Abort(false) - return - } - - oldIterTime := u.lastExportUpdate - u.lastExportUpdate = time.Now() - elapsedTime := u.lastExportUpdate.Sub(oldIterTime) - bar.EwmaSetCurrent(progress.Elapsed, elapsedTime) - bar.SetTotal(progress.Total, progress.Completed) -} - -func (u *progressBarUpdater) updateImportProgress(progress *models.Progress) { - bar := u.importBar - - if progress.Aborted { - bar.Abort(false) - return - } - - bar.SetCurrent(progress.Elapsed) - bar.SetTotal(progress.Total, progress.Completed) -} - -func initExportProgressBar(p *mpb.Progress, name string) *mpb.Bar { - return initProgressBar(p, name, - decor.EwmaETA(decor.ET_STYLE_GO, 0, decor.WC{C: decor.DSyncWidth})) -} - -func initImportProgressBar(p *mpb.Progress, name string) *mpb.Bar { - return initProgressBar(p, name, decor.Counters(0, "%d / %d")) -} - -func initProgressBar(p *mpb.Progress, name string, progressDecorator decor.Decorator) *mpb.Bar { +func setupProgressBar(p *mpb.Progress, name string) *mpb.Bar { green := color.New(color.FgGreen).SprintFunc() - red := color.New(color.FgHiRed, color.Bold).SprintFunc() return p.New(0, mpb.BarStyle(), mpb.PrependDecorators( @@ -136,8 +60,8 @@ func initProgressBar(p *mpb.Progress, name string, progressDecorator decor.Decor mpb.AppendDecorators( decor.OnComplete( decor.OnAbort( - progressDecorator, - red(i18n.Tr("aborted")), + decor.EwmaETA(decor.ET_STYLE_GO, 0, decor.WC{C: decor.DSyncWidth}), + i18n.Tr("aborted"), ), i18n.Tr("done"), ), @@ -145,3 +69,19 @@ func initProgressBar(p *mpb.Progress, name string, progressDecorator decor.Decor ), ) } + +func updateProgressBar(bar *mpb.Bar, wg *sync.WaitGroup, progressChan chan models.Progress) { + wg.Add(1) + defer wg.Done() + lastIterTime := time.Now() + for progress := range progressChan { + if progress.Aborted { + bar.Abort(false) + return + } + oldIterTime := lastIterTime + lastIterTime = time.Now() + bar.EwmaSetCurrent(progress.Elapsed, lastIterTime.Sub(oldIterTime)) + bar.SetTotal(progress.Total, progress.Completed) + } +} diff --git a/internal/cli/transfer.go b/internal/cli/transfer.go index 62dd079..0391d0d 100644 --- a/internal/cli/transfer.go +++ b/internal/cli/transfer.go @@ -19,7 +19,6 @@ import ( "errors" "fmt" "strconv" - "sync" "time" "github.com/spf13/cobra" @@ -110,21 +109,19 @@ func (c *TransferCmd[E, I, R]) Transfer(exp backends.ExportProcessor[R], imp bac printTimestamp("From timestamp: %v (%v)", timestamp) // Prepare progress bars - progressChan := make(chan models.TransferProgress) - progress := setupProgressBars(progressChan) - - wg := &sync.WaitGroup{} + exportProgress := make(chan models.Progress) + importProgress := make(chan models.Progress) + progress := progressBar(exportProgress, importProgress) // Export from source exportChan := make(chan R, 1000) - go exp.Process(wg, timestamp, exportChan, progressChan) + go exp.Process(timestamp, exportChan, exportProgress) // Import into target resultChan := make(chan models.ImportResult) - go imp.Process(wg, exportChan, resultChan, progressChan) + go imp.Process(exportChan, resultChan, importProgress) result := <-resultChan - wg.Wait() - progress.close() + progress.Wait() // Update timestamp err = c.updateTimestamp(&result, timestamp) diff --git a/internal/models/interfaces.go b/internal/models/interfaces.go index bb97dac..1c593d0 100644 --- a/internal/models/interfaces.go +++ b/internal/models/interfaces.go @@ -1,5 +1,5 @@ /* -Copyright © 2023-2025 Philipp Wolfer +Copyright © 2023 Philipp Wolfer This file is part of Scotty. @@ -55,7 +55,7 @@ type ListensExport interface { // Returns a list of all listens newer then oldestTimestamp. // The returned list of listens is supposed to be ordered by the // Listen.ListenedAt timestamp, with the oldest entry first. - ExportListens(oldestTimestamp time.Time, results chan ListensResult, progress chan TransferProgress) + ExportListens(oldestTimestamp time.Time, results chan ListensResult, progress chan Progress) } // Must be implemented by services supporting the import of listens. @@ -63,7 +63,7 @@ type ListensImport interface { ImportBackend // Imports the given list of listens. - ImportListens(export ListensResult, importResult ImportResult, progress chan TransferProgress) (ImportResult, error) + ImportListens(export ListensResult, importResult ImportResult, progress chan Progress) (ImportResult, error) } // Must be implemented by services supporting the export of loves. @@ -73,7 +73,7 @@ type LovesExport interface { // Returns a list of all loves newer then oldestTimestamp. // The returned list of listens is supposed to be ordered by the // Love.Created timestamp, with the oldest entry first. - ExportLoves(oldestTimestamp time.Time, results chan LovesResult, progress chan TransferProgress) + ExportLoves(oldestTimestamp time.Time, results chan LovesResult, progress chan Progress) } // Must be implemented by services supporting the import of loves. @@ -81,5 +81,5 @@ type LovesImport interface { ImportBackend // Imports the given list of loves. - ImportLoves(export LovesResult, importResult ImportResult, progress chan TransferProgress) (ImportResult, error) + ImportLoves(export LovesResult, importResult ImportResult, progress chan Progress) (ImportResult, error) } diff --git a/internal/models/models.go b/internal/models/models.go index 081266d..b8f9121 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -1,5 +1,5 @@ /* -Copyright © 2023-2025 Philipp Wolfer +Copyright © 2023 Philipp Wolfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -209,25 +209,11 @@ func (i *ImportResult) Log(t LogEntryType, msg string) { }) } -type TransferProgress struct { - Export *Progress - Import *Progress -} - -func (p TransferProgress) FromImportResult(result ImportResult, completed bool) TransferProgress { - importProgress := Progress{ - Completed: completed, - }.FromImportResult(result) - p.Import = &importProgress - return p -} - type Progress struct { - TotalItems int - Total int64 - Elapsed int64 - Completed bool - Aborted bool + Total int64 + Elapsed int64 + Completed bool + Aborted bool } func (p Progress) FromImportResult(result ImportResult) Progress { @@ -236,11 +222,13 @@ func (p Progress) FromImportResult(result ImportResult) Progress { return p } -func (p *Progress) Complete() { +func (p Progress) Complete() Progress { p.Elapsed = p.Total p.Completed = true + return p } -func (p *Progress) Abort() { +func (p Progress) Abort() Progress { p.Aborted = true + return p }