From 9f45405c648d246227d2653be4e22243b979094c Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 13 Nov 2023 12:03:24 +0100 Subject: [PATCH] Add table and types for tracking sync status We add the necessary database table to keep track of which node has synced which of our repos. This will be useful for giving the user information on the sync status of every local repository. --- radicle/src/node/address/schema.sql | 14 ++++ radicle/src/node/address/store.rs | 126 +++++++++++++++++++++++----- radicle/src/node/address/types.rs | 24 +++++- 3 files changed, 141 insertions(+), 23 deletions(-) diff --git a/radicle/src/node/address/schema.sql b/radicle/src/node/address/schema.sql index 3abd3c70..eb2eb8ff 100644 --- a/radicle/src/node/address/schema.sql +++ b/radicle/src/node/address/schema.sql @@ -60,3 +60,17 @@ create table if not exists "announcements" ( -- unique ("node", "repo", "type") ) strict; + +-- Repository sync status. +create table if not exists "repo-sync-status" ( + -- Repository ID. + "repo" text not null, + -- Node ID. + "node" text not null references "nodes" ("id"), + -- Head of your `rad/sigrefs` branch that was synced. + "head" text not null, + -- When this entry was last updated. + "timestamp" integer not null, + -- + unique ("repo", "node") +) strict; diff --git a/radicle/src/node/address/store.rs b/radicle/src/node/address/store.rs index d99fbd62..2737fc34 100644 --- a/radicle/src/node/address/store.rs +++ b/radicle/src/node/address/store.rs @@ -6,10 +6,11 @@ use localtime::LocalTime; use sqlite as sql; use thiserror::Error; +use crate::git::Oid; use crate::node; use crate::node::address::{KnownAddress, Source}; use crate::node::{Address, Alias, AliasError, AliasStore, NodeId}; -use crate::prelude::Timestamp; +use crate::prelude::{Id, Timestamp}; use crate::sql::transaction; use super::types; @@ -93,26 +94,7 @@ impl Store for Book { let alias = Alias::from_str(row.read::<&str, _>("alias"))?; let timestamp = row.read::("timestamp") as Timestamp; let pow = row.read::("pow") as u32; - let mut addrs = Vec::new(); - - let mut stmt = self - .db - .prepare("SELECT type, value, source FROM addresses WHERE node = ?")?; - stmt.bind((1, node))?; - - for row in stmt.into_iter() { - let row = row?; - let _typ = row.read::("type"); - let addr = row.read::("value"); - let source = row.read::("source"); - - addrs.push(KnownAddress { - addr, - source, - last_success: None, - last_attempt: None, - }); - } + let addrs = self.addresses(node)?; Ok(Some(types::Node { features, @@ -126,6 +108,35 @@ impl Store for Book { } } + fn addresses(&self, node: &NodeId) -> Result, Error> { + let mut addrs = Vec::new(); + let mut stmt = self.db.prepare( + "SELECT type, value, source, last_attempt, last_success FROM addresses WHERE node = ?", + )?; + stmt.bind((1, node))?; + + for row in stmt.into_iter() { + let row = row?; + let _typ = row.read::("type"); + let addr = row.read::("value"); + let source = row.read::("source"); + let last_attempt = row + .read::, _>("last_attempt") + .map(|t| LocalTime::from_millis(t as u128)); + let last_success = row + .read::, _>("last_success") + .map(|t| LocalTime::from_millis(t as u128)); + + addrs.push(KnownAddress { + addr, + source, + last_success, + last_attempt, + }); + } + Ok(addrs) + } + fn len(&self) -> Result { let row = self .db @@ -201,6 +212,62 @@ impl Store for Book { .map_err(Error::from) } + fn synced( + &mut self, + rid: &Id, + nid: &NodeId, + at: Oid, + timestamp: Timestamp, + ) -> Result { + let mut stmt = self.db.prepare( + "INSERT INTO repo-sync-status (repo, node, head, timestamp) + VALUES (?1, ?2, ?3, ?4) + ON CONFLICT DO UPDATE + SET head = ?3, timestamp = ?4 + WHERE timestamp < ?4", + )?; + stmt.bind((1, rid))?; + stmt.bind((2, nid))?; + stmt.bind((3, at.to_string().as_str()))?; + stmt.bind((3, timestamp as i64))?; + stmt.next()?; + + Ok(self.db.change_count() > 0) + } + + fn seeds( + &self, + rid: &Id, + ) -> Result> + '_>, Error> { + let mut stmt = self.db.prepare( + "SELECT node, head, timestamp + FROM `repo-sync-status` + WHERE repo = ?", + )?; + stmt.bind((1, rid))?; + + Ok(Box::new(stmt.into_iter().map(|row| { + let row = row?; + let nid = row.try_read::("node")?; + let oid = row.try_read::<&str, _>("head")?; + let oid = Oid::from_str(oid).map_err(|e| { + Error::Internal(sql::Error { + code: None, + message: Some(format!("sql: invalid oid '{oid}': {e}")), + }) + })?; + let timestamp = row.try_read::("timestamp")?; + let timestamp = LocalTime::from_millis(timestamp as u128); + let addresses = self.addresses(&nid)?; + + Ok(types::Seed { + nid, + addresses, + synced_at: types::SyncedAt { oid, timestamp }, + }) + }))) + } + fn entries(&self) -> Result>, Error> { let mut stmt = self .db @@ -282,8 +349,10 @@ impl AliasStore for Book { /// /// Used to store node addresses and metadata. pub trait Store { - /// Get a known peer address. + /// Get the information we have about a node. fn get(&self, id: &NodeId) -> Result, Error>; + /// Get the addresses of a node. + fn addresses(&self, node: &NodeId) -> Result, Error>; /// Insert a node with associated addresses into the store. /// /// Returns `true` if the node or addresses were updated, and `false` otherwise. @@ -298,6 +367,19 @@ pub trait Store { ) -> Result; /// Remove an address from the store. fn remove(&mut self, id: &NodeId) -> Result; + /// Mark a repo as synced on the given node. + fn synced( + &mut self, + rid: &Id, + nid: &NodeId, + at: Oid, + timestamp: Timestamp, + ) -> Result; + /// Get nodes that have synced the given repo. + fn seeds( + &self, + rid: &Id, + ) -> Result> + '_>, Error>; /// Returns the number of addresses. fn len(&self) -> Result; /// Returns true if there are no addresses. diff --git a/radicle/src/node/address/types.rs b/radicle/src/node/address/types.rs index 5cbdf26a..20c80989 100644 --- a/radicle/src/node/address/types.rs +++ b/radicle/src/node/address/types.rs @@ -7,7 +7,7 @@ use nonempty::NonEmpty; use crate::collections::RandomMap; use crate::node::{Address, Alias}; -use crate::prelude::Timestamp; +use crate::prelude::{NodeId, Timestamp}; use crate::{node, profile}; /// A map with the ability to randomly select values. @@ -176,3 +176,25 @@ impl std::fmt::Display for Source { } } } + +/// Holds an oid and timestamp. +#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SyncedAt { + /// Head of `rad/sigrefs`. + pub oid: git_ext::Oid, + /// When these refs were synced. + pub timestamp: LocalTime, +} + +/// Seed of a specific repository. +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Seed { + /// The Node ID. + pub nid: NodeId, + /// Known addresses for this node. + pub addresses: Vec, + /// Sync information for a given repo. + pub synced_at: SyncedAt, +}