diff --git a/crates/notebook/src/fs.rs b/crates/notebook/src/fs.rs index e8917c598c304acc66767b9dfc8bcffeeb2d2425..ac0713243c8be77c802290d5aa34dcdc6983ab61 100644 --- a/crates/notebook/src/fs.rs +++ b/crates/notebook/src/fs.rs @@ -6,6 +6,21 @@ pub use onestore::{confirm_file, place_file, read_file, read_file_limited, super #[cfg(not(target_arch = "wasm32"))] pub use std::{fs::*, path::absolute, process::id as process_id}; +/// Moves the file `from` to `to`, refusing to replace a file there. Android forbids apps hard +/// links, the usual way to do this. +#[cfg(not(target_arch = "wasm32"))] +pub fn rename_new( + from: impl AsRef, + to: impl AsRef, +) -> std::io::Result<()> { + tempfile::TempPath::try_from_path(from.as_ref())? + .persist_noclobber(to) + .map_err(|error| { + let _ = error.path.keep(); + error.error + }) +} + /// `Transaction::commit_file` through this file system. #[cfg(not(target_arch = "wasm32"))] pub fn commit_file( diff --git a/crates/notebook/src/fs/web.rs b/crates/notebook/src/fs/web.rs index 0f3e51ef2d89a95fd1354389c18f962cb3c30c01..e6b40df58b705fdb03e1b52cd6dc50febec5a3c7 100644 --- a/crates/notebook/src/fs/web.rs +++ b/crates/notebook/src/fs/web.rs @@ -414,14 +414,13 @@ pub fn copy(from: impl AsRef, to: impl AsRef) -> io::Result { Ok(bytes.len() as u64) } -/// A copy, as nothing here writes a file under two names at once: a link is made to put a -/// finished file in place, then the first name removed. -pub fn hard_link(original: impl AsRef, link: impl AsRef) -> io::Result<()> { - let bytes = read(original)?; - let link = normal(link.as_ref()); - let data = with(|files| files.create(link))?; +/// Moves the file `from` to `to`, refusing to replace a file there. +pub fn rename_new(from: impl AsRef, to: impl AsRef) -> io::Result<()> { + let bytes = read(&from)?; + let to = normal(to.as_ref()); + let data = with(|files| files.create(to))?; data.borrow_mut().bytes = bytes; - Ok(()) + remove_file(from) } pub fn create_dir(path: impl AsRef) -> io::Result<()> { diff --git a/crates/notebook/src/lib.rs b/crates/notebook/src/lib.rs index 15f146b6120c043fb75514cbf26627b8c52ef548..73398e66c899664b2781125bd79bd7e535da44d2 100644 --- a/crates/notebook/src/lib.rs +++ b/crates/notebook/src/lib.rs @@ -171,7 +171,7 @@ impl Replica { /// `create` without opening the cache it made, as for an offline copy. pub(crate) fn seed(path: &Path, source: &[u8], key: Option<&Key>) -> Result<()> { validate(source, key)?; - // Built under a name of its own and linked into place whole, so that a cache that + // Built under a name of its own and moved into place whole, so that a cache that // exists is complete, and two threads making the same one never share a build. static BUILDS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); let build = BUILDS.fetch_add(1, std::sync::atomic::Ordering::Relaxed); @@ -179,13 +179,13 @@ impl Replica { building.push(format!(".creating-{}-{build}", fs::process_id())); let building = PathBuf::from(building); let built = Self::build(&building, source); - let linked = built.and_then(|()| Ok(fs::hard_link(&building, path)?)); + let placed = built.and_then(|()| Ok(fs::rename_new(&building, path)?)); for suffix in ["", "-wal", "-shm"] { let mut file = building.as_os_str().to_owned(); file.push(suffix); let _ = fs::remove_file(file); } - linked + placed } fn build(path: &Path, source: &[u8]) -> Result<()> {