v0.7.0 4c16dd8

same_peer_switch.go RawHistoryFolder

1package daemon
2
3import (
4	"github.com/bnema/vev/internal/domain"
5	"github.com/bnema/vev/internal/protocol"
6)
7
8func (ac *attachedClient) offerSamePeerTarget(target protocol.ExactSessionTarget) {
9	ac.samePeerOfferMu.Lock()
10	ac.samePeerOffer = &target
11	ac.samePeerOfferMu.Unlock()
12}
13
14// offerSamePeerAttachTarget arms the exact target before publishing its
15// endpoint-empty handoff, then rolls the offer back if publication fails.
16func offerSamePeerAttachTarget(effect *attachmentEffect, target protocol.ExactSessionTarget) error {
17	if effect == nil || effect.ac == nil || !effect.current() {
18		return errAttachmentTransition
19	}
20	handoff := protocol.AttachTarget{
21		Session:           target.SessionName,
22		Intent:            protocol.IntentAttach,
23		ExactTarget:       &target,
24		EnvironmentPolicy: protocol.EnvironmentPolicyDaemonOwned,
25		SamePeer:          true,
26	}
27	if protocol.ValidateAttachTarget(handoff) != nil {
28		return errAttachmentTransition
29	}
30	effect.ac.offerSamePeerTarget(target)
31	if err := effect.sendControl(handoff); err != nil {
32		effect.ac.clearSamePeerOffer()
33		return err
34	}
35	return nil
36}
37
38func (ac *attachedClient) clearSamePeerOffer() {
39	ac.samePeerOfferMu.Lock()
40	ac.samePeerOffer = nil
41	ac.samePeerOfferMu.Unlock()
42}
43
44// consumeSamePeerOffer linearizes a switch request with the daemon's pending
45// endpoint-empty offer: while one is pending, only its exact target is
46// accepted. Without a pending offer the request is the client's own picker
47// choice. It grants no more than a fresh exact attach would on this
48// authenticated connection, and samePeerTarget still requires a live session
49// with the exact lifecycle.
50func (ac *attachedClient) consumeSamePeerOffer(target protocol.ExactSessionTarget) bool {
51	ac.samePeerOfferMu.Lock()
52	defer ac.samePeerOfferMu.Unlock()
53	if ac.samePeerOffer == nil {
54		return true
55	}
56	if *ac.samePeerOffer != target {
57		return false
58	}
59	ac.samePeerOffer = nil
60	return true
61}
62
63// switchSamePeerForAttachment commits one client-confirmed endpoint-empty
64// target. The request never names an endpoint: its authority is the current
65// authenticated attachment plus the target's exact lifecycle identity.
66func (d *Daemon) switchSamePeerForAttachment(effect *attachmentEffect, request protocol.SamePeerSwitchRequest) {
67	if err := request.Validate(); err != nil || !effect.current() || effect.sess == nil || effect.ac == nil {
68		return
69	}
70	if !effect.ac.consumeSamePeerOffer(request.Target) {
71		d.sendSamePeerSwitchFailure(effect, request.RequestID, protocol.SamePeerSwitchStaleTarget)
72		return
73	}
74
75	target, targetTabIndex, ok := d.samePeerTarget(request)
76	if !ok || target == effect.sess {
77		d.sendSamePeerSwitchFailure(effect, request.RequestID, protocol.SamePeerSwitchStaleTarget)
78		return
79	}
80
81	capability := effect.capability()
82	transition, err := d.transitionAttachment(attachmentTransitionRequest{
83		source:            effect.sess,
84		target:            target,
85		next:              effect.ac,
86		expectedTransport: effect.transport,
87		sourceCapability:  &capability,
88		sourceEffect:      effect,
89		action:            "same-peer-switch",
90		expectedTargetLifecycle: &attachmentLifecycleFence{
91			name:             request.Target.SessionName,
92			incarnation:      request.Target.LifecycleID,
93			checkIncarnation: true,
94		},
95		activateTargetTab: true,
96		targetTabIndex:    targetTabIndex,
97		ready:             true,
98	})
99	if err != nil {
100		d.sendSamePeerSwitchFailure(effect, request.RequestID, protocol.SamePeerSwitchStaleTarget)
101		return
102	}
103
104	if fresh, admitted := effect.ac.beginAttachmentEffect(transition.published); admitted {
105		fresh.End()
106	}
107	d.touchMRU(target)
108	d.deferAttachmentTransitionCleanups(transition)
109	d.firstPaintForTransition(transition.published)
110}
111
112// samePeerTarget resolves the exact target and the client-owned tab cursor
113// under the daemon/session locks. A missing cursor deliberately falls back to
114// the target session's current default rather than leaking the source view.
115func (d *Daemon) samePeerTarget(request protocol.SamePeerSwitchRequest) (*session, int, bool) {
116	d.mu.Lock()
117	defer d.mu.Unlock()
118
119	target := d.findByNameLocked(request.Target.SessionName)
120	if target == nil {
121		return nil, 0, false
122	}
123	target.mu.Lock()
124	defer target.mu.Unlock()
125	if target.incarnation != request.Target.LifecycleID || len(target.tabs) == 0 {
126		return nil, 0, false
127	}
128	tabIndex := 0
129	if request.PreferredTabID == "" {
130		return target, tabIndex, true
131	}
132	for index, tab := range target.tabs {
133		if tab != nil && domain.TabStableID(tab.stableID) == request.PreferredTabID {
134			return target, index, true
135		}
136	}
137	return target, tabIndex, true
138}
139
140func (d *Daemon) sendSamePeerSwitchFailure(effect *attachmentEffect, requestID uint64, code protocol.SamePeerSwitchFailureCode) {
141	if effect == nil || effect.ac == nil {
142		return
143	}
144	sender := effect
145	if !sender.current() {
146		var admitted bool
147		sender, admitted = effect.ac.beginAttachmentEffect(effect.capability())
148		if !admitted {
149			return
150		}
151		defer sender.End()
152	}
153	failure := protocol.SamePeerSwitchFailure{RequestID: requestID, Code: code}
154	if failure.Validate() != nil {
155		return
156	}
157	if err := sender.sendControl(failure); err != nil {
158		d.detachOnAttachmentSendError(sender.capability(), sender.transport.transport)
159	}
160}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close