1package daemon
2
3import (
4 "time"
5)
6
7// inventoriedLiveSession pairs a live session pointer with its immutable
8// snapshot view. The pointer preserves exact current-session identity while
9// the view carries all display/policy facts, so projections never re-lock
10// the registry to re-read the same session.
11type inventoriedLiveSession struct {
12 sess *session
13 view sessionView
14}
15
16// localSessionInventory is the remote-free capture consumed by the prepared
17// local projections: the local catalogue export, the local navigation source
18// group, and the local picker/control projection. It copies active and
19// inactive records of this daemon only.
20//
21// d.mu is released before any per-session snapshot, preserving the
22// established lock ordering. Copied values are coherent per exact target,
23// not a distributed atomic transaction; callers revalidate at selection.
24type localSessionInventory struct {
25 live []inventoriedLiveSession
26 stopped []inactiveSession
27 now time.Time
28}
29
30// sessionInventory is the daemon-owned common capture consumed by the
31// palette, picker, and catalog export projections. The daemon observes only
32// its own sessions: other daemons reach the palette through the client's
33// route snapshot.
34type sessionInventory struct {
35 localSessionInventory
36}
37
38// captureLocalSessionInventory copies registry pointers and inactive records
39// under d.mu, then snapshots each live session without holding that lock. When
40// refreshTitles is true the focused-title refresh runs per session after the
41// registry lock is released, matching the historical picker/catalog cost;
42// palette passes false to avoid rich tab-title work it never displays. It
43// performs no remote-directory read.
44func (d *Daemon) captureLocalSessionInventory(opts viewOptions, refreshTitles bool) localSessionInventory {
45 var sessions []*session
46 var stopped []inactiveSession
47 if d != nil {
48 d.mu.Lock()
49 sessions = d.sessionsSnapshotLocked()
50 stopped = make([]inactiveSession, 0, len(d.inactive))
51 for _, entry := range d.inactive {
52 stopped = append(stopped, entry)
53 }
54 d.mu.Unlock()
55 }
56 if refreshTitles && d != nil {
57 for _, sess := range sessions {
58 d.refreshSessionFocusedTitles(sess)
59 }
60 }
61 live := make([]inventoriedLiveSession, 0, len(sessions))
62 for _, sess := range sessions {
63 if sess == nil {
64 continue
65 }
66 live = append(live, inventoriedLiveSession{sess: sess, view: sess.snapshotView(opts)})
67 }
68 return localSessionInventory{live: live, stopped: stopped, now: d.daemonNow()}
69}
70
71// captureSessionInventory is the palette, picker, and catalog capture.
72func (d *Daemon) captureSessionInventory(opts viewOptions, refreshTitles bool) sessionInventory {
73 return sessionInventory{localSessionInventory: d.captureLocalSessionInventory(opts, refreshTitles)}
74}
75
76// resumableStopped returns stopped sessions eligible for resume display,
77// preserving the palette's historical canResume projection.
78func (inv localSessionInventory) resumableStopped() []inactiveSession {
79 out := make([]inactiveSession, 0, len(inv.stopped))
80 for _, entry := range inv.stopped {
81 if entry.canResume() {
82 out = append(out, entry)
83 }
84 }
85 return out
86}
87
88// visibleStopped returns non-purging stopped sessions, preserving the
89// picker and catalog export projections.
90func (inv localSessionInventory) visibleStopped() []inactiveSession {
91 out := make([]inactiveSession, 0, len(inv.stopped))
92 for _, entry := range inv.stopped {
93 if entry.visible() {
94 out = append(out, entry)
95 }
96 }
97 return out
98}