/* Copyright © 2023-2025 Philipp Wolfer This file is part of Scotty. 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 Foundation, either version 3 of the License, or (at your option) any later version. Scotty is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Scotty. If not, see . */ package cli import ( "sync" "time" "github.com/fatih/color" "github.com/vbauerster/mpb/v8" "github.com/vbauerster/mpb/v8/decor" "go.uploadedlobster.com/scotty/internal/i18n" "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 { wg := &sync.WaitGroup{} p := mpb.New( mpb.WithWaitGroup(wg), mpb.WithOutput(color.Output), // mpb.WithWidth(64), mpb.WithAutoRefresh(), ) u := progressBarUpdater{ wg: wg, progress: p, exportBar: initExportProgressBar(p, i18n.Tr("exporting")), importBar: initImportProgressBar(p, i18n.Tr("importing")), updateChan: updateChan, } go u.update() return u } func (u *progressBarUpdater) wait() { // FIXME: This should probably be closed elsewhere 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 { green := color.New(color.FgGreen).SprintFunc() red := color.New(color.FgHiRed, color.Bold).SprintFunc() return p.New(0, mpb.BarStyle(), mpb.PrependDecorators( decor.Name(" "), decor.OnComplete( decor.Spinner(nil, decor.WC{W: 2, C: decor.DindentRight}), green("✓ "), ), decor.Name(name, decor.WCSyncWidthR), ), mpb.AppendDecorators( decor.OnComplete( decor.OnAbort( progressDecorator, red(i18n.Tr("aborted")), ), i18n.Tr("done"), ), decor.Name(" "), ), ) }