pub mod backend;
pub mod sync;
pub mod types;
#[cfg(test)]
mod tests;
pub use backend::{FileBackend, MemoryBackend, StoreBackend};
pub use types::{Slice, StoreEvent, SyncMessage};
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use serde::de::DeserializeOwned;
use serde::Serialize;
use tokio::sync::{broadcast, mpsc, RwLock};
use tokio::task::JoinHandle;
use crate::network::NetworkProvider;
use crate::node::Node;
pub(crate) struct StoreInner<T> {
pub(crate) store_id: String,
pub(crate) device_id: String,
pub(crate) local: RwLock<Option<Slice<T>>>,
pub(crate) remotes: RwLock<HashMap<String, Slice<T>>>,
pub(crate) version: AtomicU64,
pub(crate) event_tx: broadcast::Sender<StoreEvent<T>>,
pub(crate) backend: Arc<dyn StoreBackend>,
}
pub struct SyncedStore<T> {
pub(crate) inner: Arc<StoreInner<T>>,
broadcast_tx: mpsc::UnboundedSender<SyncMessage>,
task_handle: tokio::sync::Mutex<Option<JoinHandle<()>>>,
}
impl<T: Serialize + DeserializeOwned + Clone + Send + Sync + 'static> SyncedStore<T> {
pub fn new<N: NetworkProvider + 'static>(
node: Arc<Node<N>>,
store_id: &str,
) -> Arc<Self> {
Self::new_with_backend(node, store_id, Arc::new(MemoryBackend))
}
pub fn new_with_backend<N: NetworkProvider + 'static>(
node: Arc<Node<N>>,
store_id: &str,
backend: Arc<dyn StoreBackend>,
) -> Arc<Self> {
let device_id = node.local_info().id;
let (event_tx, _) = broadcast::channel(256);
let (broadcast_tx, broadcast_rx) = mpsc::unbounded_channel();
let (local, initial_version) =
match backend.load(store_id, &device_id) {
Some((data, version)) => {
match serde_json::from_slice::<T>(&data) {
Ok(typed) => {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
let slice = Slice {
device_id: device_id.clone(),
data: typed,
version,
updated_at: now,
};
tracing::info!(
store = store_id,
version = version,
"synced_store: restored local slice from backend"
);
(Some(slice), version)
}
Err(e) => {
tracing::warn!(
store = store_id,
"synced_store: failed to deserialize persisted data, starting fresh: {e}"
);
(None, 0)
}
}
}
None => (None, 0),
};
let inner = Arc::new(StoreInner {
store_id: store_id.to_string(),
device_id,
local: RwLock::new(local),
remotes: RwLock::new(HashMap::new()),
version: AtomicU64::new(initial_version),
event_tx,
backend,
});
let task_handle = sync::spawn_sync_task(node, inner.clone(), broadcast_rx);
Arc::new(Self {
inner,
broadcast_tx,
task_handle: tokio::sync::Mutex::new(Some(task_handle)),
})
}
pub async fn set(&self, data: T) {
let version = self.inner.version.fetch_add(1, Ordering::SeqCst) + 1;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
let slice = Slice {
device_id: self.inner.device_id.clone(),
data: data.clone(),
version,
updated_at: now,
};
{
let mut local = self.inner.local.write().await;
*local = Some(slice);
}
if let Ok(serialized) = serde_json::to_vec(&data) {
self.inner.backend.save(
&self.inner.store_id,
&self.inner.device_id,
&serialized,
version,
);
}
if let Ok(serialized) = serde_json::to_value(&data) {
let msg = SyncMessage::Update {
device_id: self.inner.device_id.clone(),
data: serialized,
version,
updated_at: now,
};
let _ = self.broadcast_tx.send(msg);
}
let _ = self.inner.event_tx.send(StoreEvent::LocalChanged(data));
}
pub async fn local(&self) -> Option<T> {
let local = self.inner.local.read().await;
local.as_ref().map(|s| s.data.clone())
}
pub async fn get(&self, device_id: &str) -> Option<Slice<T>> {
let remotes = self.inner.remotes.read().await;
remotes.get(device_id).cloned()
}
pub async fn all(&self) -> HashMap<String, Slice<T>> {
let mut result = HashMap::new();
{
let local = self.inner.local.read().await;
if let Some(slice) = local.as_ref() {
result.insert(slice.device_id.clone(), slice.clone());
}
}
{
let remotes = self.inner.remotes.read().await;
for (id, slice) in remotes.iter() {
result.insert(id.clone(), slice.clone());
}
}
result
}
pub async fn device_ids(&self) -> Vec<String> {
let mut ids = Vec::new();
{
let local = self.inner.local.read().await;
if let Some(slice) = local.as_ref() {
ids.push(slice.device_id.clone());
}
}
{
let remotes = self.inner.remotes.read().await;
ids.extend(remotes.keys().cloned());
}
ids
}
pub fn subscribe(&self) -> broadcast::Receiver<StoreEvent<T>> {
self.inner.event_tx.subscribe()
}
pub fn store_id(&self) -> &str {
&self.inner.store_id
}
pub fn device_id(&self) -> &str {
&self.inner.device_id
}
pub fn version(&self) -> u64 {
self.inner.version.load(Ordering::SeqCst)
}
pub async fn stop(&self) {
let mut handle = self.task_handle.lock().await;
if let Some(h) = handle.take() {
h.abort();
tracing::info!(store = self.inner.store_id.as_str(), "synced_store: stopped");
}
}
}
impl<T> Drop for SyncedStore<T> {
fn drop(&mut self) {
if let Ok(mut handle) = self.task_handle.try_lock() {
if let Some(h) = handle.take() {
h.abort();
}
}
}
}