diff --git a/radicle-node/src/runtime.rs b/radicle-node/src/runtime.rs index f3d2a158..ad0339fa 100644 --- a/radicle-node/src/runtime.rs +++ b/radicle-node/src/runtime.rs @@ -20,6 +20,7 @@ use radicle::node::address::Store as _; use radicle::node::notifications; use radicle::node::Handle as _; use radicle::profile::Home; +use radicle::storage; use radicle::Storage; use radicle::{cob, git}; @@ -48,6 +49,9 @@ pub enum Error { /// A node database error. #[error("node database error: {0}")] Database(#[from] node::db::Error), + /// A storage error. + #[error("storage error: {0}")] + Storage(#[from] storage::Error), /// A policies database error. #[error("policies database error: {0}")] Policy(#[from] policy::Error), diff --git a/radicle-node/src/service.rs b/radicle-node/src/service.rs index 2ff250f4..2f82c76b 100644 --- a/radicle-node/src/service.rs +++ b/radicle-node/src/service.rs @@ -1765,6 +1765,8 @@ where /// Update our routing table with our local node's inventory. fn sync_inventory(&mut self) -> Result { + self.storage.refresh()?; // Refresh storage inventory cache. + let inventory = self.storage.inventory()?; let result = self.sync_routing(&inventory, self.node_id(), self.time())?; diff --git a/radicle-node/src/tests/e2e.rs b/radicle-node/src/tests/e2e.rs index 3712a2b0..d9fd26f6 100644 --- a/radicle-node/src/tests/e2e.rs +++ b/radicle-node/src/tests/e2e.rs @@ -182,6 +182,9 @@ fn test_replication() { log::debug!(target: "test", "Fetch complete with {}", bob.id); + alice.storage.refresh().unwrap(); + bob.storage.refresh().unwrap(); + let inventory = alice.storage.inventory().unwrap(); let alice_repo = alice.storage.repository(acme).unwrap(); let bob_repo = bob.storage.repository(acme).unwrap(); diff --git a/radicle/src/profile.rs b/radicle/src/profile.rs index dd0c4fae..8453080e 100644 --- a/radicle/src/profile.rs +++ b/radicle/src/profile.rs @@ -109,6 +109,8 @@ pub enum Error { Repository(#[from] storage::RepositoryError), #[error(transparent)] CobsCache(#[from] cob::cache::Error), + #[error(transparent)] + Storage(#[from] storage::Error), } #[derive(Debug, Error)] diff --git a/radicle/src/storage.rs b/radicle/src/storage.rs index a6308138..cb643b24 100644 --- a/radicle/src/storage.rs +++ b/radicle/src/storage.rs @@ -122,6 +122,8 @@ pub enum Error { Ext(#[from] git::ext::Error), #[error("invalid repository identifier {0:?}")] InvalidId(std::ffi::OsString), + #[error("inventory: {0}")] + Inventory(io::Error), #[error("i/o: {0}")] Io(#[from] io::Error), } @@ -372,6 +374,8 @@ pub trait ReadStorage { /// Get the inventory of repositories hosted under this storage. /// This function should typically only return public repositories. fn inventory(&self) -> Result; + /// Refresh storage inventory. + fn refresh(&self) -> Result<(), Error>; /// Open or create a read-only repository. fn repository(&self, rid: RepoId) -> Result; /// Get a repository's identity if it exists. @@ -630,6 +634,10 @@ where self.deref().inventory() } + fn refresh(&self) -> Result<(), Error> { + self.deref().refresh() + } + fn get(&self, rid: RepoId) -> Result>, RepositoryError> { self.deref().get(rid) } diff --git a/radicle/src/storage/git.rs b/radicle/src/storage/git.rs index b4e04806..981580cb 100644 --- a/radicle/src/storage/git.rs +++ b/radicle/src/storage/git.rs @@ -5,6 +5,7 @@ pub mod transport; use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; use std::{fs, io}; use crypto::{Signer, Verified}; @@ -88,6 +89,7 @@ impl<'a> TryFrom> for Ref { pub struct Storage { path: PathBuf, info: UserInfo, + inventory: Arc>>, } impl ReadStorage for Storage { @@ -114,13 +116,33 @@ impl ReadStorage for Storage { } fn inventory(&self) -> Result { - let repos = self.repositories()?; + match self.inventory.lock() { + Ok(locked) => Ok(locked.clone()), + Err(poisoned) => { + let inv = poisoned.into_inner(); + Ok(inv.clone()) + } + } + } - Ok(repos + fn refresh(&self) -> Result<(), Error> { + let repos = self.repositories()?; + let rids = repos .into_iter() .filter(|r| r.doc.visibility.is_public()) .map(|r| r.rid) - .collect()) + .collect(); + + match self.inventory.lock() { + Ok(mut locked) => { + *locked = rids; + } + Err(poisoned) => { + let mut inv = poisoned.into_inner(); + *inv = rids; + } + } + Ok(()) } fn repository(&self, rid: RepoId) -> Result { @@ -155,17 +177,23 @@ impl WriteStorage for Storage { } impl Storage { - // TODO: Return a better error when not found. - pub fn open>(path: P, info: UserInfo) -> Result { + /// Open a new storage instance and load its inventory. + pub fn open>(path: P, info: UserInfo) -> Result { let path = path.as_ref().to_path_buf(); match fs::create_dir_all(&path) { Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {} - Err(err) => return Err(err), + Err(err) => return Err(Error::Io(err)), Ok(()) => {} } + let storage = Self { + path, + info, + inventory: Arc::new(Mutex::new(vec![])), + }; + storage.refresh()?; - Ok(Self { path, info }) + Ok(storage) } /// Create a [`Repository`] in a temporary directory. @@ -1248,11 +1276,11 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let signer = MockSigner::default(); let storage = fixtures::storage(dir.path(), &signer).unwrap(); - let inv = storage.inventory().unwrap(); + let inv = storage.repositories().unwrap(); let proj = inv.first().unwrap(); - let mut refs = git::remote_refs(&git::Url::from(*proj)).unwrap(); + let mut refs = git::remote_refs(&git::Url::from(proj.rid)).unwrap(); - let project = storage.repository(*proj).unwrap(); + let project = storage.repository(proj.rid).unwrap(); let remotes = project.remotes().unwrap(); // Strip the remote refs of sigrefs so we can compare them. diff --git a/radicle/src/test/fixtures.rs b/radicle/src/test/fixtures.rs index 3b1beef5..98fc591b 100644 --- a/radicle/src/test/fixtures.rs +++ b/radicle/src/test/fixtures.rs @@ -10,6 +10,7 @@ use crate::rad; use crate::storage::git::transport; use crate::storage::git::Storage; use crate::storage::refs::SignedRefs; +use crate::storage::ReadStorage; /// The birth of the radicle project, January 1st, 2018. pub const RADICLE_EPOCH: i64 = 1514817556; @@ -46,6 +47,7 @@ pub fn storage, G: Signer>(path: P, signer: &G) -> Result, G: Signer>( signer, storage, )?; + storage.refresh()?; Ok((id, refs, working, head)) } diff --git a/radicle/src/test/storage.rs b/radicle/src/test/storage.rs index 6b4252e7..4a160701 100644 --- a/radicle/src/test/storage.rs +++ b/radicle/src/test/storage.rs @@ -79,6 +79,10 @@ impl ReadStorage for MockStorage { Ok(self.repos.keys().cloned().collect::>()) } + fn refresh(&self) -> Result<(), Error> { + Ok(()) + } + fn repository(&self, rid: RepoId) -> Result { self.repos .get(&rid)