1package daemon
2
3import "github.com/bnema/vev/internal/domain/layout"
4
5// movePaneCommit owns pane-private validation and publication. The shared
6// moveTransaction already holds daemon, routing, ordered Session, and resize
7// fences. Every error below is returned before the first topology write.
8type movePaneCommit struct {
9 req movePaneRequest
10 source, destination *session
11 sourceTab, destinationTab *tab
12 movedPane *pane
13 sourceGeneration uint64
14 destinationGeneration uint64
15}
16
17func (c *movePaneCommit) publishTopologyLocked(d *Daemon) (moveTopologyPublication, error) {
18 unlockTabs := lockMoveTabs(c.sourceTab, c.destinationTab)
19 c.movedPane.mu.Lock()
20 defer c.movedPane.mu.Unlock()
21 defer unlockTabs()
22
23 if !moveTabMemberLocked(c.source, c.sourceTab) || !moveTabMemberLocked(c.destination, c.destinationTab) || c.sourceTab == c.destinationTab ||
24 c.sourceTab.layoutGeneration != c.sourceGeneration || c.destinationTab.layoutGeneration != c.destinationGeneration ||
25 c.movedPane.stableID != string(c.req.SourcePaneID) || c.sourceTab.panes[c.movedPane.id] != c.movedPane ||
26 c.sourceTab.tree == nil || c.sourceTab.tree.Root == nil || !layout.ContainsLeaf(c.sourceTab.tree.Root, c.movedPane.id) ||
27 c.destinationTab.tree == nil || c.destinationTab.tree.Root == nil {
28 return moveTopologyPublication{}, errMoveStaleTarget
29 }
30 owner := c.movedPane.ownerSnapshot()
31 if owner != nil && (owner.session != c.source || owner.tab != c.sourceTab) {
32 return moveTopologyPublication{}, errMoveStaleTarget
33 }
34 candidate, err := prepareMovePaneCandidate(c.sourceTab, c.destinationTab, c.movedPane)
35 if err != nil {
36 return moveTopologyPublication{}, err
37 }
38 if candidate.sourceGeneration != c.sourceGeneration || candidate.destinationGeneration != c.destinationGeneration {
39 return moveTopologyPublication{}, errMoveStaleTarget
40 }
41 idx := -1
42 if candidate.removeSourceTab {
43 idx = indexMoveTabLocked(c.source, c.sourceTab)
44 if idx < 0 {
45 return moveTopologyPublication{}, errMoveStaleTarget
46 }
47 }
48
49 // Everything above is fallible. The remaining topology and owner writes form
50 // one in-memory publication and cannot turn into a Move rejection.
51 if candidate.removeSourceTab {
52 if len(c.source.tabs) > 1 {
53 c.source.prepareAttachmentViewsForRemovedTabLocked(c.sourceTab, idx)
54 }
55 c.source.tabs = append(c.source.tabs[:idx], c.source.tabs[idx+1:]...)
56 } else {
57 c.sourceTab.tree = candidate.sourceTree
58 c.sourceTab.bumpLayoutGenerationLocked()
59 }
60 delete(c.sourceTab.panes, candidate.sourceID)
61 if d.beforeMovePaneCommit != nil {
62 d.beforeMovePaneCommit()
63 }
64 c.destinationTab.tree = candidate.destinationTree
65 c.destinationTab.nextPaneID = candidate.destinationNextPaneID
66 c.destinationTab.panes[candidate.destinationID] = c.movedPane
67 c.destinationTab.bumpLayoutGenerationLocked()
68 c.movedPane.id = candidate.destinationID
69 oldOwner, newOwner := c.movedPane.publishOwnerLocked(c.destination, c.destinationTab, 0)
70 var syncCleanup syncTimerCleanup
71 syncCleanup.append(d.migratePaneSyncOwnerLocked(c.movedPane, oldOwner, newOwner))
72
73 return moveTopologyPublication{
74 sourceTab: c.sourceTab,
75 destinationTab: c.destinationTab,
76 movedPane: c.movedPane,
77 syncCleanup: syncCleanup,
78 sourceTabRemoved: candidate.removeSourceTab,
79 sourceEmpty: len(c.source.tabs) == 0,
80 }, nil
81}