Radish alpha
r
rad:z4D5UCArafTzTQpDZNQRuqswh3ury
Radicle desktop app
Radicle
Git
Provide a `sync_status` store
Merged did:key:z6MkkfM3...sVz5 opened 1 year ago

The sync_status would update every 10 seconds with the latest state of all repos, to be displayed in the repo where needed.

7 files changed +136 -3 53d49e0b 365674bc
modified 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);
@@ -63,6 +70,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() {
                        log::debug!("node: spawned node event subscription.");
                        while let Ok(events) = node.subscribe(std::time::Duration::MAX) {
added 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;
+
};
added 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 };
modified 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;

added 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<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,
+
        }
+
    }
+
}
modified src/App.svelte
@@ -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();
    }
modified src/lib/events.ts
@@ -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(),
+
);