use async_trait::async_trait;
use chrono::{DateTime, Utc};
use kanban_core::KanbanResult;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistenceMetadata {
pub instance_id: Uuid,
pub saved_at: DateTime<Utc>,
}
impl PersistenceMetadata {
pub fn new(instance_id: Uuid) -> Self {
Self {
instance_id,
saved_at: Utc::now(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoreSnapshot {
pub data: Vec<u8>,
pub metadata: PersistenceMetadata,
}
#[derive(Debug, Clone)]
pub enum PersistenceEvent {
Saved(PersistenceMetadata),
ExternalChangeDetected {
path: PathBuf,
saved_at: DateTime<Utc>,
},
ConflictDetected { reason: String },
Error(String),
}
#[async_trait]
pub trait PersistenceStore: Send + Sync {
async fn save(&self, snapshot: StoreSnapshot) -> KanbanResult<PersistenceMetadata>;
async fn load(&self) -> KanbanResult<(StoreSnapshot, PersistenceMetadata)>;
async fn exists(&self) -> bool;
fn path(&self) -> &Path;
}
#[async_trait]
pub trait ChangeDetector: Send + Sync {
async fn start_watching(&self, path: PathBuf) -> KanbanResult<()>;
async fn stop_watching(&self) -> KanbanResult<()>;
fn subscribe(&self) -> tokio::sync::broadcast::Receiver<ChangeEvent>;
fn is_watching(&self) -> bool;
}
#[derive(Debug, Clone)]
pub struct ChangeEvent {
pub path: PathBuf,
pub detected_at: DateTime<Utc>,
}
pub trait Serializer<T: Send + Sync>: Send + Sync {
fn serialize(&self, data: &T) -> KanbanResult<Vec<u8>>;
fn deserialize(&self, bytes: &[u8]) -> KanbanResult<T>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum FormatVersion {
V1,
V2,
}
impl FormatVersion {
pub fn as_u32(self) -> u32 {
match self {
Self::V1 => 1,
Self::V2 => 2,
}
}
pub fn from_u32(v: u32) -> Option<Self> {
match v {
1 => Some(Self::V1),
2 => Some(Self::V2),
_ => None,
}
}
}
#[async_trait]
pub trait MigrationStrategy: Send + Sync {
async fn detect_version(&self, path: &Path) -> KanbanResult<FormatVersion>;
async fn migrate(
&self,
from: FormatVersion,
to: FormatVersion,
path: &Path,
) -> KanbanResult<PathBuf>;
}
pub trait ConflictResolver: Send + Sync {
fn should_use_external(
&self,
local_metadata: &PersistenceMetadata,
external_metadata: &PersistenceMetadata,
) -> bool;
fn explain_resolution(
&self,
local_metadata: &PersistenceMetadata,
external_metadata: &PersistenceMetadata,
) -> String;
}