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.
This commit is contained in:
Sebastian Martinez
2025-02-10 11:55:14 -06:00
committed by Rūdolfs Ošiņš
parent 53d49e0bbf
commit 365674bc58
7 changed files with 136 additions and 3 deletions
+32 -2
View File
@@ -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::<RepoId, Option<radicle_types::cobs::repo::SyncStatus>>::new();
for repo in &repositories {
if let Ok(seeds) = node_seeds.seeds(repo.rid).map(Into::<Vec<_>>::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() {
@@ -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;
};
@@ -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 };
+1
View File
@@ -7,6 +7,7 @@ use radicle::node::{Alias, AliasStore};
pub mod diff;
pub mod issue;
pub mod repo;
pub mod stream;
pub mod thread;
+57
View File
@@ -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<radicle::node::SyncStatus> 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<radicle::node::SyncedAt> for SyncedAt {
fn from(value: radicle::node::SyncedAt) -> Self {
Self {
oid: value.oid,
timestamp: value.timestamp,
}
}
}
+14 -1
View File
@@ -1,13 +1,15 @@
<script lang="ts">
import type { UnlistenFn } from "@tauri-apps/api/event";
import type { SyncStatus } from "@bindings/repo/SyncStatus";
import { SvelteMap } from "svelte/reactivity";
import { onDestroy, onMount } from "svelte";
import { invoke } from "@app/lib/invoke";
import { listen } from "@tauri-apps/api/event";
import * as router from "@app/lib/router";
import { nodeRunning } from "@app/lib/events";
import { nodeRunning, syncStatus } from "@app/lib/events";
import { theme } from "@app/components/ThemeSwitch.svelte";
import { unreachable } from "@app/lib/utils";
@@ -25,6 +27,7 @@
let unlistenEvents: UnlistenFn | undefined = undefined;
let unlistenNodeEvents: UnlistenFn | undefined = undefined;
let unlistenSyncStatus: UnlistenFn | undefined = undefined;
onMount(async () => {
if (window.__TAURI_INTERNALS__) {
@@ -32,6 +35,13 @@
// Add handler for incoming events
});
unlistenSyncStatus = await listen<Record<string, SyncStatus>>(
"sync_status",
event => {
syncStatus.set(new SvelteMap(Object.entries(event.payload)));
},
);
unlistenNodeEvents = await listen<boolean>("node_running", event => {
nodeRunning.set(event.payload);
});
@@ -61,6 +71,9 @@
if (unlistenEvents) {
unlistenEvents();
}
if (unlistenSyncStatus) {
unlistenSyncStatus();
}
if (unlistenNodeEvents) {
unlistenNodeEvents();
}
+6
View File
@@ -1,3 +1,9 @@
import type { SyncStatus } from "@bindings/repo/SyncStatus";
import { SvelteMap } from "svelte/reactivity";
import { writable } from "svelte/store";
export const nodeRunning = writable<boolean>(false);
export const syncStatus = writable<SvelteMap<string, SyncStatus>>(
new SvelteMap(),
);