git_iris/companion/
mod.rs1mod branch_memory;
7mod session;
8mod storage;
9mod watcher;
10
11pub use branch_memory::{BranchMemory, FileFocus};
12pub use session::{FileActivity, SessionState};
13pub use storage::CompanionStorage;
14pub use watcher::{CompanionEvent, FileWatcherService};
15
16use anyhow::Result;
17use std::path::PathBuf;
18use std::sync::Arc;
19use tokio::sync::mpsc;
20
21pub struct CompanionService {
23 repo_path: PathBuf,
25 session: Arc<parking_lot::RwLock<SessionState>>,
27 storage: CompanionStorage,
29 watcher: Option<FileWatcherService>,
31 event_rx: mpsc::UnboundedReceiver<CompanionEvent>,
33 _event_tx: mpsc::UnboundedSender<CompanionEvent>,
35}
36
37impl CompanionService {
38 pub fn new(repo_path: PathBuf, branch: &str) -> Result<Self> {
44 let (event_tx, event_rx) = mpsc::unbounded_channel();
45
46 let storage = CompanionStorage::new(&repo_path)?;
48
49 let session = match storage.load_session() {
52 Ok(Some(mut session)) if session.branch == branch => {
53 repo_path.clone_into(&mut session.repo_path);
54 session
55 }
56 Ok(Some(session)) => {
57 tracing::info!(
58 "Ignoring session data for branch {} while starting on {}",
59 session.branch,
60 branch
61 );
62 SessionState::new(repo_path.clone(), branch.to_owned())
63 }
64 Ok(None) => SessionState::new(repo_path.clone(), branch.to_owned()),
65 Err(e) => {
66 tracing::warn!("Failed to load companion session; starting fresh: {}", e);
67 SessionState::new(repo_path.clone(), branch.to_owned())
68 }
69 };
70
71 let session = Arc::new(parking_lot::RwLock::new(session));
72
73 let watcher = match FileWatcherService::new(&repo_path, event_tx.clone()) {
75 Ok(w) => {
76 tracing::info!("Companion file watcher started");
77 Some(w)
78 }
79 Err(e) => {
80 tracing::warn!(
81 "Failed to start file watcher: {}. Companion will run without live updates.",
82 e
83 );
84 None
85 }
86 };
87
88 Ok(Self {
89 repo_path,
90 session,
91 storage,
92 watcher,
93 event_rx,
94 _event_tx: event_tx,
95 })
96 }
97
98 #[must_use]
100 pub fn session(&self) -> &Arc<parking_lot::RwLock<SessionState>> {
101 &self.session
102 }
103
104 pub fn load_branch_memory(&self, branch: &str) -> Result<Option<BranchMemory>> {
110 self.storage.load_branch_memory(branch)
111 }
112
113 pub fn save_branch_memory(&self, memory: &BranchMemory) -> Result<()> {
119 self.storage.save_branch_memory(memory)
120 }
121
122 pub fn save_session(&self) -> Result<()> {
128 let session = self.session.read();
129 self.storage.save_session(&session)
130 }
131
132 pub fn touch_file(&self, path: PathBuf) {
134 let mut session = self.session.write();
135 session.touch_file(path);
136 }
137
138 pub fn record_commit(&self, hash: String) {
140 let mut session = self.session.write();
141 session.record_commit(hash);
142 }
143
144 pub fn try_recv_event(&mut self) -> Option<CompanionEvent> {
146 self.event_rx.try_recv().ok()
147 }
148
149 #[must_use]
151 pub fn has_watcher(&self) -> bool {
152 self.watcher.is_some()
153 }
154
155 #[must_use]
157 pub fn repo_path(&self) -> &PathBuf {
158 &self.repo_path
159 }
160}
161
162impl Drop for CompanionService {
163 fn drop(&mut self) {
164 if let Err(e) = self.save_session() {
166 tracing::warn!("Failed to save session on shutdown: {}", e);
167 }
168 }
169}
170
171#[cfg(test)]
172mod tests;