summaryrefslogtreecommitdiff
path: root/gui/internal/model/plan.go
diff options
context:
space:
mode:
Diffstat (limited to 'gui/internal/model/plan.go')
-rw-r--r--gui/internal/model/plan.go37
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)
}