mod branch_memory;
mod session;
mod storage;
mod watcher;
pub use branch_memory::{BranchMemory, FileFocus};
pub use session::{FileActivity, SessionState};
pub use storage::CompanionStorage;
pub use watcher::{CompanionEvent, FileWatcherService};
use anyhow::Result;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::mpsc;
pub struct CompanionService {
repo_path: PathBuf,
session: Arc<parking_lot::RwLock<SessionState>>,
storage: CompanionStorage,
watcher: Option<FileWatcherService>,
event_rx: mpsc::UnboundedReceiver<CompanionEvent>,
_event_tx: mpsc::UnboundedSender<CompanionEvent>,
}
impl CompanionService {
pub fn new(repo_path: PathBuf, branch: &str) -> Result<Self> {
let (event_tx, event_rx) = mpsc::unbounded_channel();
let storage = CompanionStorage::new(&repo_path)?;
let session = match storage.load_session() {
Ok(Some(mut session)) if session.branch == branch => {
repo_path.clone_into(&mut session.repo_path);
session
}
Ok(Some(session)) => {
tracing::info!(
"Ignoring session data for branch {} while starting on {}",
session.branch,
branch
);
SessionState::new(repo_path.clone(), branch.to_owned())
}
Ok(None) => SessionState::new(repo_path.clone(), branch.to_owned()),
Err(e) => {
tracing::warn!("Failed to load companion session; starting fresh: {}", e);
SessionState::new(repo_path.clone(), branch.to_owned())
}
};
let session = Arc::new(parking_lot::RwLock::new(session));
let watcher = match FileWatcherService::new(&repo_path, event_tx.clone()) {
Ok(w) => {
tracing::info!("Companion file watcher started");
Some(w)
}
Err(e) => {
tracing::warn!(
"Failed to start file watcher: {}. Companion will run without live updates.",
e
);
None
}
};
Ok(Self {
repo_path,
session,
storage,
watcher,
event_rx,
_event_tx: event_tx,
})
}
#[must_use]
pub fn session(&self) -> &Arc<parking_lot::RwLock<SessionState>> {
&self.session
}
pub fn load_branch_memory(&self, branch: &str) -> Result<Option<BranchMemory>> {
self.storage.load_branch_memory(branch)
}
pub fn save_branch_memory(&self, memory: &BranchMemory) -> Result<()> {
self.storage.save_branch_memory(memory)
}
pub fn save_session(&self) -> Result<()> {
let session = self.session.read();
self.storage.save_session(&session)
}
pub fn touch_file(&self, path: PathBuf) {
let mut session = self.session.write();
session.touch_file(path);
}
pub fn record_commit(&self, hash: String) {
let mut session = self.session.write();
session.record_commit(hash);
}
pub fn try_recv_event(&mut self) -> Option<CompanionEvent> {
self.event_rx.try_recv().ok()
}
#[must_use]
pub fn has_watcher(&self) -> bool {
self.watcher.is_some()
}
#[must_use]
pub fn repo_path(&self) -> &PathBuf {
&self.repo_path
}
}
impl Drop for CompanionService {
fn drop(&mut self) {
if let Err(e) = self.save_session() {
tracing::warn!("Failed to save session on shutdown: {}", e);
}
}
}
#[cfg(test)]
mod tests;