use std::collections::BTreeMap;
use std::path::PathBuf;
use chrono::{DateTime, Utc};
use serde::Serialize;
use super::{EventKind, LedgerEvent, Origin};
pub const GIB: f64 = 1024.0 * 1024.0 * 1024.0;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct LiveWorktree {
pub path: PathBuf,
pub repo: PathBuf,
pub branch: Option<String>,
pub origin: Origin,
pub observed_only: bool,
pub session: Option<String>,
pub first_seen: DateTime<Utc>,
pub bytes: Option<u64>,
pub measured_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct LedgerState {
pub live: BTreeMap<PathBuf, LiveWorktree>,
}
pub fn fold(events: &[LedgerEvent]) -> LedgerState {
let mut live: BTreeMap<PathBuf, LiveWorktree> = BTreeMap::new();
for event in events {
match &event.kind {
EventKind::Created {
repo,
branch,
origin,
session,
} => {
live.entry(event.path.clone())
.or_insert_with(|| LiveWorktree {
path: event.path.clone(),
repo: repo.clone(),
branch: branch.clone(),
origin: *origin,
observed_only: false,
session: session.clone(),
first_seen: event.ts,
bytes: None,
measured_at: None,
});
}
EventKind::Observed {
repo,
branch,
origin,
} => {
live.entry(event.path.clone())
.or_insert_with(|| LiveWorktree {
path: event.path.clone(),
repo: repo.clone(),
branch: branch.clone(),
origin: *origin,
observed_only: true,
session: None,
first_seen: event.ts,
bytes: None,
measured_at: None,
});
}
EventKind::Measured { bytes } => {
if let Some(entry) = live.get_mut(&event.path) {
entry.bytes = Some(*bytes);
entry.measured_at = Some(event.ts);
}
}
EventKind::Removed => {
live.remove(&event.path);
}
}
}
LedgerState { live }
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ProjectSummary {
pub repo: PathBuf,
pub count: usize,
pub bytes: u64,
pub gib: f64,
pub unmeasured: usize,
}
pub fn gib(bytes: u64) -> f64 {
(bytes as f64 / GIB * 100.0).round() / 100.0
}
impl LedgerState {
pub fn by_project(&self) -> Vec<ProjectSummary> {
let mut groups: BTreeMap<&PathBuf, (usize, u64, usize)> = BTreeMap::new();
for wt in self.live.values() {
let g = groups.entry(&wt.repo).or_default();
g.0 += 1;
match wt.bytes {
Some(b) => g.1 += b,
None => g.2 += 1,
}
}
groups
.into_iter()
.map(|(repo, (count, bytes, unmeasured))| ProjectSummary {
repo: repo.clone(),
count,
bytes,
gib: gib(bytes),
unmeasured,
})
.collect()
}
}