From 365674bc5872c8e62fbc44bd7ddddb7ea32bf2ea Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 7 Feb 2025 14:14:27 +0100 Subject: [PATCH] Provide a `sync_status` store The `sync_status` would update every 10 seconds with the latest state of all repos, to be displayed in the repo where needed. --- crates/radicle-tauri/src/lib.rs | 34 ++++++++++- .../radicle-types/bindings/repo/SyncStatus.ts | 20 +++++++ .../radicle-types/bindings/repo/SyncedAt.ts | 6 ++ crates/radicle-types/src/cobs.rs | 1 + crates/radicle-types/src/cobs/repo.rs | 57 +++++++++++++++++++ src/App.svelte | 15 ++++- src/lib/events.ts | 6 ++ 7 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 crates/radicle-types/bindings/repo/SyncStatus.ts create mode 100644 crates/radicle-types/bindings/repo/SyncedAt.ts create mode 100644 crates/radicle-types/src/cobs/repo.rs diff --git a/crates/radicle-tauri/src/lib.rs b/crates/radicle-tauri/src/lib.rs index c9f6adde2..e67296889 100644 --- a/crates/radicle-tauri/src/lib.rs +++ b/crates/radicle-tauri/src/lib.rs @@ -1,10 +1,13 @@ mod commands; +use std::collections::BTreeMap; + use tauri::{Emitter, Manager}; use radicle::cob::cache::COBS_DB_FILE; -use radicle::node::{Handle, NOTIFICATIONS_DB_FILE}; -use radicle::Node; +use radicle::identity::RepoId; +use radicle::node::{Handle, Node, NOTIFICATIONS_DB_FILE}; +use radicle::storage::ReadStorage; use radicle_types::domain; use radicle_types::error::Error; @@ -34,6 +37,8 @@ pub fn run() { hint: "Could not load radicle profile", }), }?; + let public_key = profile.public_key; + let repositories = profile.storage.repositories()?; let inbox_db = radicle_types::outbound::sqlite::Sqlite::reader( profile.node().join(NOTIFICATIONS_DB_FILE), @@ -46,9 +51,11 @@ pub fn run() { let events_handler = app.handle().clone(); let node_handler = app.handle().clone(); + let seed_handler = app.handle().clone(); let node = Node::new(profile.socket()); let node_status = node.clone(); + let mut node_seeds = node.clone(); app.manage(inbox_service); app.manage(patch_service); @@ -61,6 +68,29 @@ pub fn run() { } }); + tauri::async_runtime::spawn(async move { + loop { + let mut sync_status = + BTreeMap::>::new(); + for repo in &repositories { + if let Ok(seeds) = node_seeds.seeds(repo.rid).map(Into::>::into) { + if let Some(status) = seeds.iter().find_map( + |radicle::node::Seed { nid, sync, .. }| { + (*nid == public_key).then_some(sync.clone()) + }, + ) { + sync_status.insert(repo.rid, status.map(Into::into)); + } else { + // The local node wasn't found in the seed nodes table. + sync_status.insert(repo.rid, None); + } + } + } + let _ = seed_handler.emit("sync_status", sync_status); + tokio::time::sleep(std::time::Duration::from_secs(10)).await; + } + }); + tauri::async_runtime::spawn(async move { loop { if node.is_running() { diff --git a/crates/radicle-types/bindings/repo/SyncStatus.ts b/crates/radicle-types/bindings/repo/SyncStatus.ts new file mode 100644 index 000000000..9f259d77c --- /dev/null +++ b/crates/radicle-types/bindings/repo/SyncStatus.ts @@ -0,0 +1,20 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SyncedAt } from "./SyncedAt"; + +export type SyncStatus = { + "status": "synced"; + /** + * At what ref was the remote synced at. + */ + at: SyncedAt; +} | { + "status": "outOfSync"; + /** + * Local head of our `rad/sigrefs`. + */ + local: SyncedAt; + /** + * Remote head of our `rad/sigrefs`. + */ + remote: SyncedAt; +}; diff --git a/crates/radicle-types/bindings/repo/SyncedAt.ts b/crates/radicle-types/bindings/repo/SyncedAt.ts new file mode 100644 index 000000000..1ce3bcc28 --- /dev/null +++ b/crates/radicle-types/bindings/repo/SyncedAt.ts @@ -0,0 +1,6 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Holds an oid and timestamp. + */ +export type SyncedAt = { oid: string; timestamp: number }; diff --git a/crates/radicle-types/src/cobs.rs b/crates/radicle-types/src/cobs.rs index 91aa7353e..52653b0c3 100644 --- a/crates/radicle-types/src/cobs.rs +++ b/crates/radicle-types/src/cobs.rs @@ -7,6 +7,7 @@ use radicle::node::{Alias, AliasStore}; pub mod diff; pub mod issue; +pub mod repo; pub mod stream; pub mod thread; diff --git a/crates/radicle-types/src/cobs/repo.rs b/crates/radicle-types/src/cobs/repo.rs new file mode 100644 index 000000000..850d5d547 --- /dev/null +++ b/crates/radicle-types/src/cobs/repo.rs @@ -0,0 +1,57 @@ +use localtime::LocalTime; + +#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize, ts_rs::TS)] +#[serde(tag = "status")] +#[serde(rename_all = "camelCase")] +#[ts(export)] +#[ts(export_to = "repo/")] +pub enum SyncStatus { + /// We're in sync. + #[serde(rename_all = "camelCase")] + Synced { + /// At what ref was the remote synced at. + at: SyncedAt, + }, + /// We're out of sync. + #[serde(rename_all = "camelCase")] + OutOfSync { + /// Local head of our `rad/sigrefs`. + local: SyncedAt, + /// Remote head of our `rad/sigrefs`. + remote: SyncedAt, + }, +} + +impl From for SyncStatus { + fn from(value: radicle::node::SyncStatus) -> Self { + match value { + radicle::node::SyncStatus::Synced { at } => SyncStatus::Synced { at: at.into() }, + radicle::node::SyncStatus::OutOfSync { local, remote } => SyncStatus::OutOfSync { + local: local.into(), + remote: remote.into(), + }, + } + } +} + +/// Holds an oid and timestamp. +#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, ts_rs::TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +#[ts(export_to = "repo/")] +pub struct SyncedAt { + #[ts(as = "String")] + pub oid: radicle::git::Oid, + #[serde(with = "radicle::serde_ext::localtime::time")] + #[ts(type = "number")] + pub timestamp: LocalTime, +} + +impl From for SyncedAt { + fn from(value: radicle::node::SyncedAt) -> Self { + Self { + oid: value.oid, + timestamp: value.timestamp, + } + } +} diff --git a/src/App.svelte b/src/App.svelte index ce8d1ca42..626903e20 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,13 +1,15 @@