diff --git a/crates/mobile/src/library.rs b/crates/mobile/src/library.rs index d1afb35381eb36a9c89cd01949af5b80d97a1457..be36bdb89a276b9c63e333e481de6f311ef619a0 100644 --- a/crates/mobile/src/library.rs +++ b/crates/mobile/src/library.rs @@ -603,15 +603,16 @@ impl Library { .ok_or("The notebook hasn’t listed this section")?; let replicas = self.share_replicas().ok_or("Not a share")?; let cache = replicas.join(format!("{identity}.sqlite")); - let replica = if cache.exists() { - Replica::open(&cache)? - } else { - let client = self - .client() - .ok_or("The server can’t be reached, and this section hasn’t been opened here before.")?; - std::fs::create_dir_all(&replicas)?; - Replica::create(&cache, &client.read_storage(&file, LIMIT)?)? - }; + std::fs::create_dir_all(&replicas)?; + let replica = Replica::open_or_create(&cache, || { + let client = self.client().ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::NotConnected, + "The server can’t be reached, and this section hasn’t been opened here before.", + ) + })?; + Ok(client.read_storage(&file, LIMIT)?) + })?; let server = Arc::clone(server); Ok(session::Section::resume_smb( file, diff --git a/crates/notebook/src/lib.rs b/crates/notebook/src/lib.rs index d2adae845b478671db2681b1729dcfab0d5b6c6f..57cd05807d7330971d171e21a64cd9eb1d67900f 100644 --- a/crates/notebook/src/lib.rs +++ b/crates/notebook/src/lib.rs @@ -128,6 +128,23 @@ impl Replica { Self::start(cache_connection(path.as_ref())?) } + /// Opens the cache at `path`, first creating it from the image `source` reads where there + /// is none. A cache another thread creates meanwhile, as the background makes an offline + /// copy, is opened instead. + pub fn open_or_create( + path: impl AsRef, + source: impl FnOnce() -> Result>, + ) -> Result { + let path = path.as_ref(); + if !path.exists() { + match Self::seed(path, &source()?) { + Err(Error::Io(error)) if error.kind() == io::ErrorKind::AlreadyExists => {} + seeded => seeded?, + } + } + Self::open(path) + } + /// `create` without opening the cache it made, as for an offline copy. pub(crate) fn seed(path: &Path, source: &[u8]) -> Result<()> { validate(source)?; diff --git a/crates/notebook/src/session.rs b/crates/notebook/src/session.rs index d3ab0efd5cfd284200113d80f4994c2f555bee61..0a0cff293d77544aeaaf4ee8a1fd460f6f11083b 100644 --- a/crates/notebook/src/session.rs +++ b/crates/notebook/src/session.rs @@ -1650,11 +1650,7 @@ impl Section { let store = Store::parse(&source)?; let identity = RevisionIndex::parse(&store)?.root; let cache = replica(&identity.guid, &source)?; - let replica = if cache.exists() { - Replica::open(&cache)? - } else { - Replica::create(&cache, &source)? - }; + let replica = Replica::open_or_create(&cache, || Ok(source))?; let remote = file.clone(); Self::start(file, replica, move || connect(&remote), notify) } diff --git a/crates/notebook/tests/cache.rs b/crates/notebook/tests/cache.rs index 545ba39de0784f7fe857485181ad01d1b0f5539e..b30c501790eaa6de68ff82ce6f967278e3c69ee7 100644 --- a/crates/notebook/tests/cache.rs +++ b/crates/notebook/tests/cache.rs @@ -59,6 +59,24 @@ fn typed(space: ExGuid, text: ExGuid, with: &str) -> Edit { } } +/// A cache another thread creates while this one reads its section, as the background +/// makes a section's offline copy while the section opens, is opened instead of refused. +#[test] +fn a_cache_created_meanwhile_opens() { + let source = onestore::create_section("Raced.one", "Raced", "Fixture").unwrap(); + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("raced.sqlite"); + let replica = Replica::open_or_create(&path, || { + drop(Replica::create(&path, &source)?); + Ok(source.clone()) + }) + .unwrap(); + assert_eq!(replica.pages().unwrap()[0].1, "Raced"); + drop(replica); + let reopened = Replica::open_or_create(&path, || unreachable!()).unwrap(); + assert_eq!(reopened.pages().unwrap()[0].1, "Raced"); +} + #[test] fn cache_reopen_preserves_the_base_and_queued_edits() { let dir = tempfile::tempdir().unwrap(); diff --git a/crates/snowbound/src/library.rs b/crates/snowbound/src/library.rs index da8b5bce609d5ca4c31f92efce1726f15fb48a5e..fcfe45dff9be0371c87d1c7b371aea8edc394934 100644 --- a/crates/snowbound/src/library.rs +++ b/crates/snowbound/src/library.rs @@ -671,14 +671,9 @@ impl Library { }; let cache = notebook.replica_path(path)?; std::fs::create_dir_all(cache.parent().unwrap_or(&self.cache))?; - let replica = if cache.exists() { - notebook::Replica::open(&cache) - } else { - notebook::Replica::create( - &cache, - &server.connect()?.read_storage(&file, LIMIT)?, - ) - }; + let replica = notebook::Replica::open_or_create(&cache, || { + Ok(server.connect()?.read_storage(&file, LIMIT)?) + }); replica.and_then(|replica| { let server = Arc::clone(server); let connect = move || server.connect();