use std::fmt;
use radicle::cob::{ObjectId, TypeName};
use radicle::git::Oid;
use radicle::prelude::RepoId;
use crate::entry::ActionEntry;
pub trait FeedStorage {
type Error: std::error::Error + Send + Sync + 'static;
fn initialize(&mut self) -> Result<(), Self::Error>;
fn get_last_processed_operation(
&self,
rid: &RepoId,
cob_id: &ObjectId,
cob_type: &TypeName,
) -> Result<Option<Oid>, Self::Error>;
fn update_last_processed_operation(
&mut self,
rid: &RepoId,
cob_id: &ObjectId,
cob_type: &TypeName,
last_action_id: &Oid,
) -> Result<(), Self::Error>;
fn operation_exists(&self, operation_id: &Oid) -> Result<bool, Self::Error>;
fn insert_batch(&mut self, entries: &[ActionEntry]) -> Result<(), Self::Error>;
#[allow(dead_code)]
fn get_stats(&self) -> Result<StorageStats, Self::Error>;
fn load_all_sorted(&self) -> Result<Vec<ActionEntry>, Self::Error> {
Ok(Vec::new())
}
}
#[derive(Debug, Default)]
pub struct StorageStats {
pub total_actions: u64,
pub actions_by_type: std::collections::HashMap<String, u64>,
pub tracked_objects: u64,
}
impl fmt::Display for StorageStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "=== Storage Statistics ===")?;
writeln!(f, "Total actions: {}", self.total_actions)?;
for (kind, count) in &self.actions_by_type {
writeln!(f, "{}: {}", kind, count)?;
}
writeln!(f, "Tracked objects: {}", self.tracked_objects)?;
Ok(())
}
}
pub mod error {
use snafu::Snafu;
#[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub enum StorageError {
#[snafu(display("Storage error: {message}"))]
Generic { message: String },
#[snafu(display("Storage initialization error: {message}"))]
Initialization { message: String },
#[snafu(display("Storage retrieval error: {message}"))]
Retrieval { message: String },
#[snafu(display("Storage insertion error: {message}"))]
Insertion { message: String },
#[snafu(display("Storage update error: {message}"))]
Update { message: String },
}
}
pub struct MemoryStorage {
actions: std::collections::HashMap<String, ActionEntry>,
last_processed: std::collections::HashMap<String, Oid>,
}
impl MemoryStorage {
pub fn new() -> Self {
Self {
actions: std::collections::HashMap::new(),
last_processed: std::collections::HashMap::new(),
}
}
fn tracking_key(rid: &RepoId, cob_id: &ObjectId, cob_type: &TypeName) -> String {
format!("{}:{}:{}", rid, cob_id, cob_type)
}
}
impl Default for MemoryStorage {
fn default() -> Self {
Self::new()
}
}
impl FeedStorage for MemoryStorage {
type Error = error::StorageError;
fn initialize(&mut self) -> Result<(), Self::Error> {
Ok(())
}
fn get_last_processed_operation(
&self,
rid: &RepoId,
cob_id: &ObjectId,
cob_type: &TypeName,
) -> Result<Option<Oid>, Self::Error> {
let key = Self::tracking_key(rid, cob_id, cob_type);
Ok(self.last_processed.get(&key).copied())
}
fn update_last_processed_operation(
&mut self,
rid: &RepoId,
cob_id: &ObjectId,
cob_type: &TypeName,
last_operation_id: &Oid,
) -> Result<(), Self::Error> {
let key = Self::tracking_key(rid, cob_id, cob_type);
self.last_processed.insert(key, *last_operation_id);
Ok(())
}
fn operation_exists(&self, operation_id: &Oid) -> Result<bool, Self::Error> {
Ok(self.actions.contains_key(&operation_id.to_string()))
}
fn insert_batch(&mut self, entries: &[ActionEntry]) -> Result<(), Self::Error> {
for entry in entries {
self.actions
.insert(entry.operation_id.to_string(), entry.clone());
}
Ok(())
}
fn get_stats(&self) -> Result<StorageStats, Self::Error> {
let mut stats = StorageStats {
total_actions: self.actions.len() as u64,
tracked_objects: self.last_processed.len() as u64,
..Default::default()
};
for entry in self.actions.values() {
*stats
.actions_by_type
.entry(entry.typename.to_string().clone())
.or_insert(0) += 1;
}
Ok(stats)
}
fn load_all_sorted(&self) -> Result<Vec<ActionEntry>, Self::Error> {
let mut entries: Vec<_> = self.actions.values().cloned().collect();
entries.sort_by_key(|entry| entry.timestamp);
Ok(entries)
}
}