diff --git a/backend/bindings/ficsitcli/process.go b/backend/bindings/ficsitcli/process.go index fad7c8ab..b75f59ca 100644 --- a/backend/bindings/ficsitcli/process.go +++ b/backend/bindings/ficsitcli/process.go @@ -5,11 +5,14 @@ import ( "log/slog" "time" + "github.com/dustin/go-humanize" "github.com/pkg/errors" "github.com/puzpuzpuz/xsync/v3" "github.com/satisfactorymodding/ficsit-cli/cli" - "github.com/satisfactorymodding/ficsit-cli/utils" + ficsitUtils "github.com/satisfactorymodding/ficsit-cli/utils" wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime" + + "github.com/satisfactorymodding/SatisfactoryModManager/backend/utils" ) func (f *FicsitCLI) validateInstall(installation *InstallationInfo, progressItem string) error { @@ -21,8 +24,8 @@ func (f *FicsitCLI) validateInstall(installation *InstallationInfo, progressItem defer f.setProgress(f.progress) type modProgress struct { - downloadProgress utils.GenericProgress - extractProgress utils.GenericProgress + downloadProgress ficsitUtils.GenericProgress + extractProgress ficsitUtils.GenericProgress downloading bool complete bool } @@ -33,16 +36,19 @@ func (f *FicsitCLI) validateInstall(installation *InstallationInfo, progressItem defer progressTicker.Stop() defer close(done) + downloadProgressTracker := utils.NewProgressTracker(time.Second * 5) + extractProgressTracker := utils.NewProgressTracker(time.Second * 5) + go func() { for { select { case <-done: return case <-progressTicker.C: - downloadBytesProgress := utils.GenericProgress{} - extractBytesProgress := utils.GenericProgress{} - downloadModsProgress := utils.GenericProgress{} - extractModsProgress := utils.GenericProgress{} + downloadBytesProgress := ficsitUtils.GenericProgress{} + extractBytesProgress := ficsitUtils.GenericProgress{} + downloadModsProgress := ficsitUtils.GenericProgress{} + extractModsProgress := ficsitUtils.GenericProgress{} hasDownloading := false @@ -73,19 +79,36 @@ func (f *FicsitCLI) validateInstall(installation *InstallationInfo, progressItem return true }) + downloadProgressTracker.Add(downloadBytesProgress.Completed) + downloadProgressTracker.Total = downloadBytesProgress.Total + extractProgressTracker.Add(extractBytesProgress.Completed) + extractProgressTracker.Total = extractBytesProgress.Total + if hasDownloading { if downloadBytesProgress.Total != 0 { f.setProgress(&Progress{ - Item: progressItem, - Message: fmt.Sprintf("Downloading %d/%d mods", downloadModsProgress.Completed, downloadModsProgress.Total), + Item: progressItem, + Message: fmt.Sprintf( + "Downloading %d/%d mods: %s/%s, %s/s, %s", + downloadModsProgress.Completed, downloadModsProgress.Total, + humanize.Bytes(uint64(downloadBytesProgress.Completed)), humanize.Bytes(uint64(downloadBytesProgress.Total)), + humanize.Bytes(uint64(downloadProgressTracker.Speed())), + downloadProgressTracker.ETA().Round(time.Second), + ), Progress: downloadBytesProgress.Percentage(), }) } } else { if extractBytesProgress.Total != 0 { f.setProgress(&Progress{ - Item: progressItem, - Message: fmt.Sprintf("Extracting %d/%d mods", extractModsProgress.Completed, extractModsProgress.Total), + Item: progressItem, + Message: fmt.Sprintf( + "Extracting %d/%d mods: %s/%s, %s/s, %s", + extractModsProgress.Completed, extractModsProgress.Total, + humanize.Bytes(uint64(extractBytesProgress.Completed)), humanize.Bytes(uint64(extractBytesProgress.Total)), + humanize.Bytes(uint64(extractProgressTracker.Speed())), + extractProgressTracker.ETA().Round(time.Second), + ), Progress: extractBytesProgress.Percentage(), }) } diff --git a/backend/utils/progress.go b/backend/utils/progress.go new file mode 100644 index 00000000..17edf913 --- /dev/null +++ b/backend/utils/progress.go @@ -0,0 +1,62 @@ +package utils + +import ( + "maps" + "time" +) + +type ProgressTracker struct { + windowSize time.Duration + data map[time.Time]int64 + Total int64 +} + +func NewProgressTracker(windowSize time.Duration) *ProgressTracker { + return &ProgressTracker{ + windowSize: windowSize, + data: make(map[time.Time]int64), + } +} + +func (pt *ProgressTracker) Add(value int64) { + pt.Total += value + pt.data[time.Now()] = value + pt.evict() +} + +func (pt *ProgressTracker) Speed() float64 { + pt.evict() + var first, last time.Time + for t := range pt.data { + if first.IsZero() || t.Before(first) { + first = t + } + if last.IsZero() || t.After(last) { + last = t + } + } + firstData := pt.data[first] + lastData := pt.data[last] + return float64(lastData-firstData) / pt.windowSize.Seconds() +} + +func (pt *ProgressTracker) ETA() time.Duration { + speed := pt.Speed() + if speed == 0 { + return 0 + } + var latest time.Time + for t := range pt.data { + if latest.IsZero() || t.After(latest) { + latest = t + } + } + latestData := pt.data[latest] + return time.Duration(float64(pt.Total-latestData)/speed) * time.Second +} + +func (pt *ProgressTracker) evict() { + maps.DeleteFunc(pt.data, func(t time.Time, _ int64) bool { + return time.Since(t) > pt.windowSize + }) +} diff --git a/go.mod b/go.mod index 7978238f..af27d9a1 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ toolchain go1.21.5 require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/andygrunwald/vdf v1.1.0 + github.com/dustin/go-humanize v1.0.1 github.com/lmittmann/tint v1.0.3 github.com/minio/selfupdate v0.6.0 github.com/mitchellh/go-ps v1.0.0 diff --git a/go.sum b/go.sum index 78445e80..7c083b7a 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=