1package daemon
2
3import (
4 "github.com/bnema/vev/internal/domain"
5 "github.com/bnema/vev/internal/usecase/layout"
6)
7
8// movePaneCommit owns the single architecture-locked publication section. Its
9// inputs are immutable admission/fence snapshots; outputs form the postcommit
10// plan. publishLocked performs all fallible checks before its first write.
11type movePaneCommit struct {
12 req movePaneRequest
13 source, destination *session
14 sourceTab, destinationTab *tab
15 movedPane *pane
16 sourceAttachments []*attachedClient
17 sourceTransports map[*attachedClient]transportSnapshot
18 sourceGeneration uint64
19 destinationGeneration uint64
20 frozenEffects frozenAttachmentEffectGates
21 err error
22
23 syncCleanup syncTimerCleanup
24 sourceEmpty bool
25 sourceTabRemoved bool
26 retiredParked []parkedAttachmentRetirement
27 retiredAttachments []detachedAttachmentSnapshot
28 sourceMetadata domain.CatalogueMetadataUpdate
29 sourceMetadataValid bool
30 destinationMetadata domain.CatalogueMetadataUpdate
31 destinationMetadataValid bool
32}
33
34func (c *movePaneCommit) publishLocked(d *Daemon) bool {
35 var candidate *movePaneCandidate
36 var err error
37 d.mu.Lock()
38 if d.closing || d.sessions[c.source.id] != c.source || d.sessions[c.destination.id] != c.destination {
39 d.mu.Unlock()
40 return false
41 }
42 d.notices.routingMu.Lock()
43 unlockSessions := lockAttachmentSessions(c.source, c.destination)
44 defer unlockSessions()
45 defer d.notices.routingMu.Unlock()
46 defer d.mu.Unlock()
47
48 if !moveSessionLocatorCurrentLocked(c.source, c.req.Source) || !moveSessionLocatorCurrentLocked(c.destination, c.req.Destination) {
49 return false
50 }
51 if c.req.Attachment != nil && !attachmentRegisteredLocked(c.source, c.req.Attachment) {
52 return false
53 }
54 if c.req.AttachmentToken.ac != nil && (c.req.AttachmentToken.ac != c.req.Attachment || !moveAttachmentTokenCurrentLocked(c.req.AttachmentToken, c.source)) {
55 return false
56 }
57
58 unlockTabs := lockMoveTabs(c.sourceTab, c.destinationTab)
59 c.movedPane.mu.Lock()
60 defer c.movedPane.mu.Unlock()
61 defer unlockTabs()
62
63 if !moveTabMemberLocked(c.source, c.sourceTab) || !moveTabMemberLocked(c.destination, c.destinationTab) || c.sourceTab == c.destinationTab ||
64 c.sourceTab.layoutGeneration != c.sourceGeneration || c.destinationTab.layoutGeneration != c.destinationGeneration ||
65 c.movedPane.stableID != string(c.req.SourcePaneID) || c.sourceTab.panes[c.movedPane.id] != c.movedPane ||
66 c.sourceTab.tree == nil || c.sourceTab.tree.Root == nil || !layout.ContainsLeaf(c.sourceTab.tree.Root, c.movedPane.id) ||
67 c.destinationTab.tree == nil || c.destinationTab.tree.Root == nil {
68 return false
69 }
70 owner := c.movedPane.ownerSnapshot()
71 if owner != nil && (owner.session != c.source || owner.tab != c.sourceTab) {
72 return false
73 }
74 candidate, err = prepareMovePaneCandidate(c.sourceTab, c.destinationTab, c.movedPane)
75 if err != nil {
76 c.err = err
77 return false
78 }
79 if candidate.sourceGeneration != c.sourceGeneration || candidate.destinationGeneration != c.destinationGeneration {
80 return false
81 }
82
83 sourceWillEmpty := candidate.removeSourceTab && len(c.source.tabs) == 1
84 if sourceWillEmpty && c.source != c.destination && !sameMoveAttachmentsLocked(c.source, c.sourceAttachments) {
85 return false
86 }
87
88 // Everything above is fallible. Shared topology and owner publication are
89 // committed once; attachment views are repaired after these locks release.
90 if candidate.removeSourceTab {
91 idx := indexMoveTabLocked(c.source, c.sourceTab)
92 if idx < 0 {
93 return false
94 }
95 if len(c.source.tabs) > 1 {
96 c.source.prepareAttachmentViewsForRemovedTabLocked(c.sourceTab, idx)
97 }
98 c.source.tabs = append(c.source.tabs[:idx], c.source.tabs[idx+1:]...)
99 c.sourceTabRemoved = true
100 } else {
101 c.sourceTab.tree = candidate.sourceTree
102 c.sourceTab.bumpLayoutGenerationLocked()
103 }
104 delete(c.sourceTab.panes, candidate.sourceID)
105 if d.beforeMovePaneCommit != nil {
106 d.beforeMovePaneCommit()
107 }
108 c.destinationTab.tree = candidate.destinationTree
109 c.destinationTab.nextPaneID = candidate.destinationNextPaneID
110 c.destinationTab.panes[candidate.destinationID] = c.movedPane
111 c.destinationTab.bumpLayoutGenerationLocked()
112 c.movedPane.id = candidate.destinationID
113 oldOwner, newOwner := c.movedPane.publishOwnerLocked(c.destination, c.destinationTab, 0)
114 c.syncCleanup.append(d.migratePaneSyncOwnerLocked(c.movedPane, oldOwner, newOwner))
115
116 if sourceWillEmpty {
117 d.unregisterSessionLocked(c.source)
118 c.retiredAttachments = detachMoveAttachmentsLocked(c.source, c.sourceTransports)
119 c.source.tabs = nil
120 d.purgeParkingForSessionLocked(c.source)
121 c.retiredParked = d.purgeParkedForSessionLocked(c.source)
122 }
123 c.sourceEmpty = len(c.source.tabs) == 0
124 if !c.source.ephemeral {
125 c.sourceMetadata = c.source.persistRecordLocked(max(d.nowUnixNano(), c.source.createdAt, int64(1))).MetadataUpdate()
126 c.sourceMetadataValid = true
127 }
128 if !c.destination.ephemeral && c.destination != c.source {
129 c.destinationMetadata = c.destination.persistRecordLocked(max(d.nowUnixNano(), c.destination.createdAt, int64(1))).MetadataUpdate()
130 c.destinationMetadataValid = true
131 }
132 return true
133}