1use crate::catalog::{Catalog, db_err};
4use orbok_core::{OrbokResult, StorageCategory, now_iso8601};
5use rusqlite::params;
6
7#[derive(Debug, Clone)]
9pub struct StorageRow {
10 pub category: StorageCategory,
11 pub size_bytes: u64,
12 pub item_count: u64,
13 pub updated_at: String,
14}
15
16pub struct StorageAccountingRepository<'a> {
17 catalog: &'a Catalog,
18}
19
20impl<'a> StorageAccountingRepository<'a> {
21 pub fn new(catalog: &'a Catalog) -> Self {
22 Self { catalog }
23 }
24
25 pub fn upsert(
27 &self,
28 category: StorageCategory,
29 size_bytes: u64,
30 item_count: u64,
31 ) -> OrbokResult<()> {
32 let conn = self.catalog.lock();
33 conn.execute(
34 "INSERT INTO storage_accounting (category, size_bytes, item_count, updated_at) \
35 VALUES (?1, ?2, ?3, ?4) \
36 ON CONFLICT(category) DO UPDATE SET size_bytes = ?2, item_count = ?3, updated_at = ?4",
37 params![category.as_str(), size_bytes as i64, item_count as i64, now_iso8601()],
38 )
39 .map_err(db_err)?;
40 Ok(())
41 }
42
43 pub fn all(&self) -> OrbokResult<Vec<StorageRow>> {
45 let conn = self.catalog.lock();
46 let mut stmt = conn
47 .prepare("SELECT category, size_bytes, item_count, updated_at FROM storage_accounting")
48 .map_err(db_err)?;
49 let rows = stmt
50 .query_map([], |row| {
51 Ok((
52 row.get::<_, String>(0)?,
53 row.get::<_, i64>(1)?,
54 row.get::<_, i64>(2)?,
55 row.get::<_, String>(3)?,
56 ))
57 })
58 .map_err(db_err)?;
59 let mut out = Vec::new();
60 for row in rows {
61 let (cat, size, count, updated) = row.map_err(db_err)?;
62 if let Some(category) = StorageCategory::ALL
64 .iter()
65 .find(|c| c.as_str() == cat)
66 .copied()
67 {
68 out.push(StorageRow {
69 category,
70 size_bytes: size as u64,
71 item_count: count as u64,
72 updated_at: updated,
73 });
74 }
75 }
76 Ok(out)
77 }
78}