diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-16 09:17:08 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-16 09:17:08 +0200 |
| commit | bcfeb773afcc28641ef2fb47749c91fd0767054a (patch) | |
| tree | f8593c29bad0b204f1ecc9cae650b55f3cc755d6 /gui/internal/model/plan.go | |
| parent | 4f98392c9a062e9d4dd8363a2ede9a919ad14006 (diff) | |
| download | krino-bcfeb773afcc28641ef2fb47749c91fd0767054a.tar.gz krino-bcfeb773afcc28641ef2fb47749c91fd0767054a.zip | |
gui: the Plan tab - scan, review, apply, with the directory locked
Diffstat (limited to 'gui/internal/model/plan.go')
| -rw-r--r-- | gui/internal/model/plan.go | 37 |
1 files changed, 33 insertions, 4 deletions
diff --git a/gui/internal/model/plan.go b/gui/internal/model/plan.go index 607fa29..7496347 100644 --- a/gui/internal/model/plan.go +++ b/gui/internal/model/plan.go @@ -11,6 +11,7 @@ import ( "sort" "krino/internal/engine" + "krino/internal/lock" "krino/internal/plan" ) @@ -40,6 +41,7 @@ type PlanTab struct { sess *engine.Session dp *engine.DirPlan + lock *lock.Lock } // Counts is the plan's summary line. @@ -47,19 +49,42 @@ type Counts struct { Scanned, Acting, Excluded, Skipped, Unmatched, Warned int } -// Plan locks dir and plans it, returning the tab to show. The lock is held -// until Apply or Close: what the user sees stays the truth while they -// choose (GUI design §3). +// Plan locks d and plans it, returning the tab to show. The lock is held +// until Close, so what the window shows stays the truth while the user +// chooses, and no other krino moves the files under them (GUI design §3). +// A directory another run is already working in is not planned at all: the +// error is lock.ErrHeld, naming the directory. func Plan(ctx context.Context, sess *engine.Session, d *engine.Dir) (*PlanTab, error) { + l, err := sess.Lock(ctx, d, false) + if err != nil { + return nil, fmt.Errorf("%s: %w", d.Name, err) + } + // Start from what this run has actually landed: a name a plan the user + // closed had reserved is free again, so looking twice never creeps up + // through name-1, name-2 (spec §7.4). + sess.FinishDirectory() dp, err := sess.Plan(ctx, d) if err != nil { + l.Release() return nil, err } - t := &PlanTab{Dir: d, sess: sess, dp: dp} + t := &PlanTab{Dir: d, sess: sess, dp: dp, lock: l} t.fill() return t, nil } +// Close releases the directory's lock. The session outlives the tab, so +// closing one plan to open another keeps the run - and its claims - going. +// Closing twice is not an error. +func (t *PlanTab) Close() error { + if t.lock == nil { + return nil + } + l := t.lock + t.lock = nil + return l.Release() +} + // fill turns the engine's plan into rows and counts. func (t *PlanTab) fill() { r := t.dp.Result @@ -192,6 +217,10 @@ func (t *PlanTab) Apply(ctx context.Context) (*engine.ApplyResult, error) { } res, err := t.sess.Apply(ctx, t.dp, approved) t.Applied = true + // The disk is now the truth: a destination this plan reserved but never + // used is free again, while one it did use stays protected for the rest + // of the run (spec §7.4). + t.sess.FinishDirectory() if res != nil { t.record(res) } |
