use std::collections::HashSet;
use std::path::Path;
use std::time::Duration;
use ito_domain::audit::event::AuditEvent;
use super::store::{AuditEventStore, audit_storage_location_key, default_audit_store};
use super::worktree::discover_worktrees;
#[derive(Debug, Clone)]
pub struct StreamConfig {
pub poll_interval: Duration,
pub all_worktrees: bool,
pub last: usize,
}
impl Default for StreamConfig {
fn default() -> Self {
Self {
poll_interval: Duration::from_millis(500),
all_worktrees: false,
last: 10,
}
}
}
pub struct StreamSource {
pub label: String,
store: Box<dyn AuditEventStore>,
offset: usize,
}
#[derive(Debug)]
pub struct StreamEvent {
pub event: AuditEvent,
pub source: String,
}
pub fn read_initial_events(
ito_path: &Path,
config: &StreamConfig,
) -> (Vec<StreamEvent>, Vec<StreamSource>) {
let mut sources = Vec::new();
let mut events = Vec::new();
let mut seen_locations = HashSet::new();
let main_store = default_audit_store(ito_path);
let main_key = audit_storage_location_key(&main_store.location());
let main_events = main_store.read_all();
let start = main_events.len().saturating_sub(config.last);
for event in &main_events[start..] {
events.push(StreamEvent {
event: event.clone(),
source: "main".to_string(),
});
}
sources.push(StreamSource {
label: "main".to_string(),
store: main_store,
offset: main_events.len(),
});
seen_locations.insert(main_key);
if config.all_worktrees {
let worktrees = discover_worktrees(ito_path);
for wt in &worktrees {
if wt.is_main {
continue; }
let wt_ito_path = wt.path.join(".ito");
if !wt_ito_path.exists() {
continue;
}
let wt_store = default_audit_store(&wt_ito_path);
let wt_key = audit_storage_location_key(&wt_store.location());
if !seen_locations.insert(wt_key) {
continue;
}
let wt_events = wt_store.read_all();
let label = wt
.branch
.clone()
.unwrap_or_else(|| wt.path.display().to_string());
let start = wt_events.len().saturating_sub(config.last);
for event in &wt_events[start..] {
events.push(StreamEvent {
event: event.clone(),
source: label.clone(),
});
}
sources.push(StreamSource {
label,
store: wt_store,
offset: wt_events.len(),
});
}
}
(events, sources)
}
pub fn poll_new_events(sources: &mut [StreamSource]) -> Vec<StreamEvent> {
let mut new_events = Vec::new();
for source in sources.iter_mut() {
let current_events = source.store.read_all();
if current_events.len() <= source.offset {
continue;
}
for event in ¤t_events[source.offset..] {
new_events.push(StreamEvent {
event: event.clone(),
source: source.label.clone(),
});
}
source.offset = current_events.len();
}
new_events
}
#[cfg(test)]
#[path = "stream_tests.rs"]
mod stream_tests;