feat: real-time progress with per-folder bar, speed and ETA
- orchestrator: progress events now carry account-level cumulative copied/ skipped plus current folder done/total, throttled to ~3/sec per account - web: RUN CONTROL counters and account copied/skipped read live WS values (DB only advances per folder, so the summary lagged); new Progress column shows a bar, percent, avg messages/sec and folder ETA while running Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMHQTtnQtQqL8muAXHr9kd
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/vasyansk/imap-copier/internal/crypto"
|
||||
"github.com/vasyansk/imap-copier/internal/imapx"
|
||||
@@ -267,15 +268,36 @@ func (o *Orchestrator) runAccount(ctx context.Context, task store.Task, runID in
|
||||
}
|
||||
|
||||
var copied, skipped, errs int64
|
||||
// Account-level live progress state (all callbacks run on this goroutine,
|
||||
// so plain vars are race-free). base* = totals from completed folders;
|
||||
// c/s inside OnProgress are cumulative within the current folder.
|
||||
var baseCopied, baseSkipped int64
|
||||
var curFolder string
|
||||
var curTotal int64
|
||||
var lastEmit time.Time
|
||||
deps := imapx.CopyDeps{
|
||||
IsMigrated: func(k string) (bool, error) { return o.store.IsMigrated(ctx, a.ID, k) },
|
||||
MarkMigrated: func(folder, k string) error { return o.store.MarkMigrated(ctx, a.ID, folder, k) },
|
||||
OnProgress: func(c, s int) {
|
||||
o.hub.Publish(wshub.Event{Type: "progress", TaskID: task.ID,
|
||||
Data: map[string]any{"account_id": a.ID, "copied": c, "skipped": s}})
|
||||
now := time.Now()
|
||||
done := c + s
|
||||
// throttle to ~3/sec per account, but always emit folder completion
|
||||
if now.Sub(lastEmit) < 350*time.Millisecond && int64(done) < curTotal {
|
||||
return
|
||||
}
|
||||
lastEmit = now
|
||||
o.hub.Publish(wshub.Event{Type: "progress", TaskID: task.ID, Data: map[string]any{
|
||||
"account_id": a.ID,
|
||||
"copied": baseCopied + int64(c),
|
||||
"skipped": baseSkipped + int64(s),
|
||||
"folder": curFolder,
|
||||
"folder_done": done,
|
||||
"folder_total": curTotal,
|
||||
}})
|
||||
},
|
||||
// Fires after EXAMINE (before the long fetch) with the folder's message count.
|
||||
OnFolder: func(srcFolder, dstFolder string, total int64) {
|
||||
curFolder, curTotal = srcFolder, total
|
||||
o.hub.Publish(wshub.Event{Type: "folder", TaskID: task.ID, Data: map[string]any{
|
||||
"account_id": a.ID, "src_login": a.SrcLogin,
|
||||
"folder": srcFolder, "dst_folder": dstFolder, "messages": total,
|
||||
@@ -301,6 +323,8 @@ func (o *Orchestrator) runAccount(ctx context.Context, task store.Task, runID in
|
||||
copied += int64(res.Copied)
|
||||
skipped += int64(res.Skipped)
|
||||
errs += int64(res.Errors)
|
||||
baseCopied += int64(res.Copied)
|
||||
baseSkipped += int64(res.Skipped)
|
||||
_ = o.store.IncAccountCounters(ctx, a.ID, int64(res.Copied), int64(res.Skipped), int64(res.Errors))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user