feat(orchestrator): build copy plan from per-account mapping + exclusions
This commit is contained in:
@@ -16,6 +16,34 @@ import (
|
||||
var ErrNotTested = errors.New("accounts not fully tested")
|
||||
var ErrAlreadyRunning = errors.New("task already running")
|
||||
|
||||
// folderPlan is one source folder scheduled for copy and its destination name.
|
||||
type folderPlan struct {
|
||||
src, dst string
|
||||
total int64
|
||||
}
|
||||
|
||||
// planFolders decides which of an account's source folders to copy and where.
|
||||
// Excluded source folders are dropped; a folder absent from mapping keeps its
|
||||
// own name. Order follows the input folder list.
|
||||
func planFolders(folders []string, mapping map[string]string, excluded []string) []folderPlan {
|
||||
skip := make(map[string]struct{}, len(excluded))
|
||||
for _, e := range excluded {
|
||||
skip[e] = struct{}{}
|
||||
}
|
||||
plan := make([]folderPlan, 0, len(folders))
|
||||
for _, f := range folders {
|
||||
if _, ok := skip[f]; ok {
|
||||
continue
|
||||
}
|
||||
df := f
|
||||
if m, ok := mapping[f]; ok && m != "" {
|
||||
df = m
|
||||
}
|
||||
plan = append(plan, folderPlan{src: f, dst: df})
|
||||
}
|
||||
return plan
|
||||
}
|
||||
|
||||
type Orchestrator struct {
|
||||
store *store.Store
|
||||
hub *wshub.Hub
|
||||
@@ -269,27 +297,19 @@ func (o *Orchestrator) runAccount(ctx context.Context, task store.Task, runID in
|
||||
return o.accountFailed(ctx, task.ID, a, srcEP, dstEP, "src", err)
|
||||
}
|
||||
|
||||
// Planning pass: EXAMINE every folder up front to learn the total message
|
||||
// count, so the UI can show an accurate overall bar / ETA before copying.
|
||||
type folderPlan struct {
|
||||
src, dst string
|
||||
total int64
|
||||
}
|
||||
plan := make([]folderPlan, 0, len(folders))
|
||||
// Planning pass: decide folders from the account's own config, then EXAMINE
|
||||
// each to learn message counts for the overall progress bar.
|
||||
plan := planFolders(folders, a.FolderMapping, a.ExcludedFolders)
|
||||
var grandTotal int64
|
||||
for _, folder := range folders {
|
||||
for i := range plan {
|
||||
if actx.Err() != nil {
|
||||
break
|
||||
}
|
||||
df := folder
|
||||
if m, ok := task.FolderMapping[folder]; ok {
|
||||
df = m
|
||||
}
|
||||
n, cerr := imapx.FolderMessageCount(src, folder)
|
||||
n, cerr := imapx.FolderMessageCount(src, plan[i].src)
|
||||
if cerr != nil && actx.Err() == nil {
|
||||
slog.Warn("count folder failed", "account", a.ID, "folder", folder, "err", cerr)
|
||||
slog.Warn("count folder failed", "account", a.ID, "folder", plan[i].src, "err", cerr)
|
||||
}
|
||||
plan = append(plan, folderPlan{src: folder, dst: df, total: n})
|
||||
plan[i].total = n
|
||||
grandTotal += n
|
||||
}
|
||||
o.hub.Publish(wshub.Event{Type: "plan", TaskID: task.ID, Data: map[string]any{
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package orchestrator
|
||||
|
||||
import "testing"
|
||||
|
||||
func names(ps []folderPlan) []string {
|
||||
out := make([]string, len(ps))
|
||||
for i, p := range ps {
|
||||
out[i] = p.src + "->" + p.dst
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestPlanFolders(t *testing.T) {
|
||||
folders := []string{"INBOX", "Спам", "Trash", "Sent"}
|
||||
|
||||
// Empty config: every folder syncs to its own name, in order.
|
||||
got := planFolders(folders, nil, nil)
|
||||
if len(got) != 4 || got[0].src != "INBOX" || got[0].dst != "INBOX" {
|
||||
t.Fatalf("empty config: %v", names(got))
|
||||
}
|
||||
|
||||
// Rename + exclusion.
|
||||
got = planFolders(folders,
|
||||
map[string]string{"Спам": "Spam"},
|
||||
[]string{"Trash"})
|
||||
if len(got) != 3 {
|
||||
t.Fatalf("want 3 plans (Trash excluded), got %v", names(got))
|
||||
}
|
||||
for _, p := range got {
|
||||
if p.src == "Trash" {
|
||||
t.Fatalf("excluded folder present: %v", names(got))
|
||||
}
|
||||
if p.src == "Спам" && p.dst != "Spam" {
|
||||
t.Fatalf("rename not applied: %v", names(got))
|
||||
}
|
||||
if p.src == "INBOX" && p.dst != "INBOX" {
|
||||
t.Fatalf("unmapped folder should keep its name: %v", names(got))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user