use std::collections::HashSet;
use iroh_blobs::Hash;
use iroh_docs::NamespaceId;
use crate::{Result, storage::node_db::NodeDatabase};
#[derive(Clone, Debug)]
pub struct SyncCheckpoint {
namespace_id: NamespaceId,
session_id: String,
database: NodeDatabase,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct EntryProgress {
pub entry_key: Vec<u8>,
pub hash: Hash,
pub size: u64,
pub status: String,
pub retries: u32,
pub error_message: Option<String>,
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct CheckpointProgress {
pub total: usize,
pub completed: usize,
pub failed: usize,
pub skipped: usize,
pub pending: usize,
pub bytes_transferred: u64,
pub bytes_total: Option<u64>,
pub percentage: f64,
}
impl SyncCheckpoint {
#[must_use]
pub fn new(database: &NodeDatabase, namespace_id: NamespaceId) -> Self {
let session_id = uuid::Uuid::new_v4().to_string();
Self {
namespace_id,
session_id,
database: database.clone(),
}
}
#[must_use]
pub fn session_id(&self) -> &str {
&self.session_id
}
#[must_use]
pub const fn namespace_id(&self) -> NamespaceId {
self.namespace_id
}
pub fn create_session(database: &NodeDatabase, namespace_id: NamespaceId) -> Result<Self> {
let cp = Self::new(database, namespace_id);
database.create_sync_checkpoint(&cp.namespace_id.to_string(), &cp.session_id)?;
Ok(cp)
}
pub fn mark_completed(&self, entry_key: &[u8], hash: Hash, size: u64) -> Result<()> {
self.database
.upsert_sync_entry(&crate::storage::node_db::SyncEntryParams {
namespace_id: &self.namespace_id.to_string(),
session_id: &self.session_id,
entry_key,
hash: &hash.to_string().into_bytes(),
size,
status: "completed",
retries: 0,
error_message: None,
})
}
pub fn mark_failed(&self, entry_key: &[u8], error: &str) -> Result<()> {
self.database
.upsert_sync_entry(&crate::storage::node_db::SyncEntryParams {
namespace_id: &self.namespace_id.to_string(),
session_id: &self.session_id,
entry_key,
hash: &[],
size: 0,
status: "failed",
retries: 0,
error_message: Some(error),
})
}
pub fn mark_skipped(&self, entry_key: &[u8], hash: Hash, size: u64) -> Result<()> {
self.database
.upsert_sync_entry(&crate::storage::node_db::SyncEntryParams {
namespace_id: &self.namespace_id.to_string(),
session_id: &self.session_id,
entry_key,
hash: &hash.to_string().into_bytes(),
size,
status: "skipped",
retries: 0,
error_message: None,
})
}
pub fn completed_entries(&self) -> Result<Vec<EntryProgress>> {
self.database
.list_sync_entries(&self.namespace_id.to_string(), &self.session_id, "completed")
}
pub fn failed_entries(&self) -> Result<Vec<EntryProgress>> {
self.database
.list_sync_entries(&self.namespace_id.to_string(), &self.session_id, "failed")
}
pub fn completed_keys(&self) -> Result<HashSet<Vec<u8>>> {
let entries = self.completed_entries()?;
Ok(entries.into_iter().map(|e| e.entry_key).collect())
}
pub fn progress(&self) -> Result<CheckpointProgress> {
self.database
.get_checkpoint_progress(&self.namespace_id.to_string(), &self.session_id)
}
pub fn complete(&self) -> Result<()> {
self.database
.update_checkpoint_status(&self.namespace_id.to_string(), &self.session_id, "completed")
}
pub const fn mark_incomplete(&self) -> Result<()> {
Ok(())
}
pub fn delete(&self) -> Result<()> {
self.database
.delete_sync_checkpoint(&self.namespace_id.to_string(), &self.session_id)
}
pub fn resume(database: &NodeDatabase, namespace_id: NamespaceId) -> Result<Option<Self>> {
let ns = namespace_id.to_string();
Ok(database.find_unfinished_checkpoint(&ns)?.map(|sid| Self {
namespace_id,
session_id: sid,
database: database.clone(),
}))
}
}