diff --git a/radicle-cli/src/commands/node/tracking.rs b/radicle-cli/src/commands/node/tracking.rs index 05f32cd4..3e80b59e 100644 --- a/radicle-cli/src/commands/node/tracking.rs +++ b/radicle-cli/src/commands/node/tracking.rs @@ -6,7 +6,7 @@ use term::Element; use super::TrackingMode; -pub fn run(store: &tracking::store::Config, mode: TrackingMode) -> anyhow::Result<()> { +pub fn run(store: &tracking::store::ConfigReader, mode: TrackingMode) -> anyhow::Result<()> { match mode { TrackingMode::Repos => print_repos(store)?, TrackingMode::Nodes => print_nodes(store)?, @@ -14,7 +14,7 @@ pub fn run(store: &tracking::store::Config, mode: TrackingMode) -> anyhow::Resul Ok(()) } -fn print_repos(store: &tracking::store::Config) -> anyhow::Result<()> { +fn print_repos(store: &tracking::store::ConfigReader) -> anyhow::Result<()> { let mut t = term::Table::new(term::table::TableOptions::bordered()); t.push([ term::format::default(String::from("RID")), @@ -39,7 +39,7 @@ fn print_repos(store: &tracking::store::Config) -> anyhow::Result<()> { Ok(()) } -fn print_nodes(store: &tracking::store::Config) -> anyhow::Result<()> { +fn print_nodes(store: &tracking::store::ConfigReader) -> anyhow::Result<()> { let mut t = term::Table::new(term::table::TableOptions::bordered()); t.push([ term::format::default(String::from("DID")), diff --git a/radicle-node/src/service.rs b/radicle-node/src/service.rs index 86d8da2d..7ba0da01 100644 --- a/radicle-node/src/service.rs +++ b/radicle-node/src/service.rs @@ -32,7 +32,7 @@ use crate::prelude::*; use crate::runtime::Emitter; use crate::service::message::{Announcement, AnnouncementMessage, Ping}; use crate::service::message::{NodeAnnouncement, RefsAnnouncement}; -use crate::service::tracking::Scope; +use crate::service::tracking::{store::Write, Scope}; use crate::storage; use crate::storage::{Namespaces, ReadStorage}; use crate::storage::{ReadRepository, RefUpdate}; @@ -185,7 +185,7 @@ pub struct Service { /// Node address manager. addresses: A, /// Tracking policy configuration. - tracking: tracking::Config, + tracking: tracking::Config, /// State relating to gossip. gossip: Gossip, /// Peer sessions, currently or recently connected. @@ -244,7 +244,7 @@ where routing: R, storage: S, addresses: A, - tracking: tracking::Config, + tracking: tracking::Config, signer: G, rng: Rng, node: NodeAnnouncement, @@ -349,7 +349,7 @@ where } /// Get the tracking policy. - pub fn tracking(&self) -> &tracking::Config { + pub fn tracking(&self) -> &tracking::Config { &self.tracking } diff --git a/radicle-node/src/service/tracking.rs b/radicle-node/src/service/tracking.rs index e4730586..cbc843c8 100644 --- a/radicle-node/src/service/tracking.rs +++ b/radicle-node/src/service/tracking.rs @@ -1,3 +1,4 @@ +use core::fmt; use std::collections::HashSet; use std::ops; @@ -11,6 +12,7 @@ use radicle::storage::{Namespaces, ReadRepository as _, ReadStorage}; use crate::prelude::Id; use crate::service::NodeId; +pub use crate::node::tracking::store; pub use crate::node::tracking::store::Config as Store; pub use crate::node::tracking::store::Error; pub use crate::node::tracking::{Alias, Node, Policy, Repo, Scope}; @@ -42,19 +44,30 @@ pub enum NamespacesError { } /// Tracking configuration. -#[derive(Debug)] -pub struct Config { +pub struct Config { /// Default policy, if a policy for a specific node or repository was not found. policy: Policy, /// Default scope, if a scope for a specific repository was not found. scope: Scope, /// Underlying configuration store. - store: Store, + store: Store, } -impl Config { +// N.b. deriving `Debug` will require `T: Debug` so we manually +// implement it here. +impl fmt::Debug for Config { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Config") + .field("policy", &self.policy) + .field("scope", &self.scope) + .field("store", &self.store) + .finish() + } +} + +impl Config { /// Create a new tracking configuration. - pub fn new(policy: Policy, scope: Scope, store: Store) -> Self { + pub fn new(policy: Policy, scope: Scope, store: Store) -> Self { Self { policy, scope, @@ -139,15 +152,15 @@ impl Config { } } -impl ops::Deref for Config { - type Target = Store; +impl ops::Deref for Config { + type Target = Store; fn deref(&self) -> &Self::Target { &self.store } } -impl ops::DerefMut for Config { +impl ops::DerefMut for Config { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.store } diff --git a/radicle-node/src/test/peer.rs b/radicle-node/src/test/peer.rs index fc6dac4c..b73af79c 100644 --- a/radicle-node/src/test/peer.rs +++ b/radicle-node/src/test/peer.rs @@ -153,7 +153,7 @@ where mut config: Config, ) -> Self { let routing = routing::Table::memory().unwrap(); - let tracking = tracking::Store::memory().unwrap(); + let tracking = tracking::Store::::memory().unwrap(); let tracking = tracking::Config::new(config.policy, config.scope, tracking); let tempdir = tempfile::tempdir().unwrap(); let id = *config.signer.public_key(); diff --git a/radicle/src/node/tracking/store.rs b/radicle/src/node/tracking/store.rs index f21d3029..0f5cd109 100644 --- a/radicle/src/node/tracking/store.rs +++ b/radicle/src/node/tracking/store.rs @@ -1,4 +1,5 @@ #![allow(clippy::type_complexity)] +use std::marker::PhantomData; use std::path::Path; use std::{fmt, io, ops::Not as _, str::FromStr, time}; @@ -25,30 +26,31 @@ pub enum Error { Internal(#[from] sql::Error), } +/// Read-only type witness. +pub struct Read; +/// Read-write type witness. +pub struct Write; + +/// Read only config. +pub type ConfigReader = Config; +/// Read-write config. +pub type ConfigWriter = Config; + /// Tracking configuration. -pub struct Config { +pub struct Config { db: sql::Connection, + _marker: PhantomData, } -impl fmt::Debug for Config { +impl fmt::Debug for Config { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Config(..)") } } -impl Config { +impl Config { const SCHEMA: &str = include_str!("schema.sql"); - /// Open a policy store at the given path. Creates a new store if it - /// doesn't exist. - pub fn open>(path: P) -> Result { - let mut db = sql::Connection::open(path)?; - db.set_busy_timeout(DB_WRITE_TIMEOUT.as_millis() as usize)?; - db.execute(Self::SCHEMA)?; - - Ok(Self { db }) - } - /// Same as [`Self::open`], but in read-only mode. This is useful to have multiple /// open databases, as no locking is required. pub fn reader>(path: P) -> Result { @@ -57,7 +59,39 @@ impl Config { db.set_busy_timeout(DB_READ_TIMEOUT.as_millis() as usize)?; db.execute(Self::SCHEMA)?; - Ok(Self { db }) + Ok(Self { + db, + _marker: PhantomData, + }) + } + + /// Create a new in-memory address book. + pub fn memory() -> Result { + let db = + sql::Connection::open_with_flags(":memory:", sqlite::OpenFlags::new().set_read_only())?; + db.execute(Self::SCHEMA)?; + + Ok(Self { + db, + _marker: PhantomData, + }) + } +} + +impl Config { + const SCHEMA: &str = include_str!("schema.sql"); + + /// Open a policy store at the given path. Creates a new store if it + /// doesn't exist. + pub fn open>(path: P) -> Result { + let mut db = sql::Connection::open(path)?; + db.set_busy_timeout(DB_WRITE_TIMEOUT.as_millis() as usize)?; + db.execute(Self::SCHEMA)?; + + Ok(Self { + db, + _marker: PhantomData, + }) } /// Create a new in-memory address book. @@ -65,7 +99,18 @@ impl Config { let db = sql::Connection::open(":memory:")?; db.execute(Self::SCHEMA)?; - Ok(Self { db }) + Ok(Self { + db, + _marker: PhantomData, + }) + } + + /// Get a read-only version of this store. + pub fn read_only(self) -> ConfigReader { + Config { + db: self.db, + _marker: PhantomData, + } } /// Track a node. @@ -155,7 +200,11 @@ impl Config { Ok(self.db.change_count() > 0) } +} +/// `Read` methods for `Config`. This implies that a +/// `Config` can access these functions as well. +impl Config { /// Check if a node is tracked. pub fn is_node_tracked(&self, id: &NodeId) -> Result { Ok(matches!( @@ -265,7 +314,7 @@ impl Config { } } -impl AliasStore for Config { +impl AliasStore for Config { /// Retrieve `alias` of given node. /// Calls `Self::node_policy` under the hood. fn alias(&self, nid: &NodeId) -> Option { diff --git a/radicle/src/profile.rs b/radicle/src/profile.rs index 14f8755b..0ff10cff 100644 --- a/radicle/src/profile.rs +++ b/radicle/src/profile.rs @@ -208,7 +208,7 @@ impl Profile { } /// Return a read-only handle to the tracking configuration of the node. - pub fn tracking(&self) -> Result { + pub fn tracking(&self) -> Result { let path = self.home.node().join(node::TRACKING_DB_FILE); let config = tracking::store::Config::reader(path)?; @@ -261,7 +261,7 @@ impl Profile { /// Holds multiple alias stores, and will try /// them one by one when asking for an alias. pub struct Aliases { - tracking: Option, + tracking: Option, addresses: Option, }