diff --git a/crates/librqbit/src/session.rs b/crates/librqbit/src/session.rs index 0f8ccae1d..e5ab8021e 100644 --- a/crates/librqbit/src/session.rs +++ b/crates/librqbit/src/session.rs @@ -787,34 +787,37 @@ impl Session { creation_date: None, info_hash: Id20::from_str(&storrent.info_hash)?, }; - futures.push({ - let session = self.clone(); - async move { - session - .add_torrent( - AddTorrent::TorrentInfo(Box::new(info)), - Some(AddTorrentOptions { - paused: storrent.is_paused, - output_folder: Some( - storrent - .output_folder - .to_str() - .context("broken path")? - .to_owned(), - ), - only_files: storrent.only_files, - overwrite: true, - preferred_id: Some(id), - ..Default::default() - }), - ) - .await - .map_err(|e| { - error!("error adding torrent from stored session: {:?}", e); - e - }) + futures.push( + { + let session = self.clone(); + async move { + session + .add_torrent( + AddTorrent::TorrentInfo(Box::new(info)), + Some(AddTorrentOptions { + paused: storrent.is_paused, + output_folder: Some( + storrent + .output_folder + .to_str() + .context("broken path")? + .to_owned(), + ), + only_files: storrent.only_files, + overwrite: true, + preferred_id: Some(id), + ..Default::default() + }), + ) + .await + .map_err(|e| { + error!("error adding torrent from stored session: {:?}", e); + e + }) + } } - }); + .instrument(error_span!(parent: None, "torrent", id)), + ); } futures::future::join_all(futures).await; Ok(()) diff --git a/crates/librqbit/src/storage/filesystem/fs.rs b/crates/librqbit/src/storage/filesystem/fs.rs index 4831a7699..d616b9853 100644 --- a/crates/librqbit/src/storage/filesystem/fs.rs +++ b/crates/librqbit/src/storage/filesystem/fs.rs @@ -67,6 +67,7 @@ impl TorrentStorage for FilesystemStorage { fn pwrite_all(&self, file_id: usize, offset: u64, buf: &[u8]) -> anyhow::Result<()> { let of = self.opened_files.get(file_id).context("no such file")?; + of.ensure_writeable()?; #[cfg(target_family = "unix")] { use std::os::unix::fs::FileExt; @@ -114,7 +115,7 @@ impl TorrentStorage for FilesystemStorage { if !path.is_dir() { anyhow::bail!("cannot remove dir: {path:?} is not a directory") } - if std::fs::read_dir(&path)?.count() == 0 { + if std::fs::read_dir(&path)?.next().is_none() { std::fs::remove_dir(&path).with_context(|| format!("error removing {path:?}")) } else { warn!("did not remove {path:?} as it was not empty"); @@ -133,17 +134,34 @@ impl TorrentStorage for FilesystemStorage { full_path.push(relative_path); std::fs::create_dir_all(full_path.parent().context("bug: no parent")?)?; - let file = if meta.options.allow_overwrite { - OpenOptions::new() + if meta.options.allow_overwrite { + // ensure file exists + let (file, writeable) = match OpenOptions::new() .create(true) - .truncate(false) .read(true) .write(true) + .append(false) + .truncate(false) .open(&full_path) - .with_context(|| format!("error opening {full_path:?} in read/write mode"))? + { + Ok(file) => (file, true), + Err(e) => { + warn!(?full_path, "error opening file in create+write mode: {e:?}"); + // open the file in read-only mode, will reopen in write mode later. + ( + OpenOptions::new() + .create(false) + .read(true) + .open(&full_path) + .with_context(|| format!("error opening {full_path:?}"))?, + false, + ) + } + }; + files.push(OpenedFile::new(full_path.clone(), file, writeable)); } else { // create_new does not seem to work with read(true), so calling this twice. - OpenOptions::new() + let file = OpenOptions::new() .create_new(true) .write(true) .open(&full_path) @@ -153,9 +171,9 @@ impl TorrentStorage for FilesystemStorage { &full_path ) })?; - OpenOptions::new().read(true).write(true).open(&full_path)? + OpenOptions::new().read(true).write(true).open(&full_path)?; + files.push(OpenedFile::new(full_path.clone(), file, true)); }; - files.push(OpenedFile::new(file)); } self.opened_files = files; diff --git a/crates/librqbit/src/storage/filesystem/opened_file.rs b/crates/librqbit/src/storage/filesystem/opened_file.rs index 88f24ddd7..3ac912b25 100644 --- a/crates/librqbit/src/storage/filesystem/opened_file.rs +++ b/crates/librqbit/src/storage/filesystem/opened_file.rs @@ -1,11 +1,17 @@ -use std::fs::File; +use std::{ + fs::File, + path::PathBuf, + sync::atomic::{AtomicBool, Ordering}, +}; use anyhow::Context; use parking_lot::RwLock; #[derive(Debug)] pub(crate) struct OpenedFile { + pub filename: PathBuf, pub file: RwLock, + pub is_writeable: AtomicBool, } pub(crate) fn dummy_file() -> anyhow::Result { @@ -21,9 +27,11 @@ pub(crate) fn dummy_file() -> anyhow::Result { } impl OpenedFile { - pub fn new(f: File) -> Self { + pub fn new(filename: PathBuf, f: File, is_writeable: bool) -> Self { Self { + filename, file: RwLock::new(f), + is_writeable: AtomicBool::new(is_writeable), } } @@ -37,7 +45,32 @@ impl OpenedFile { pub fn take_clone(&self) -> anyhow::Result { let f = self.take()?; Ok(Self { + filename: self.filename.clone(), file: RwLock::new(f), + is_writeable: AtomicBool::new(self.is_writeable.load(Ordering::SeqCst)), }) } + + pub fn ensure_writeable(&self) -> anyhow::Result<()> { + match self + .is_writeable + .compare_exchange(false, true, Ordering::SeqCst, Ordering::Relaxed) + { + Ok(_) => { + // Updated, need to reopen writeable + let mut g = self.file.write(); + let new_file = std::fs::OpenOptions::new() + .write(true) + .create(false) + .open(&self.filename) + .with_context(|| format!("error opening {:?} in write mode", self.filename))?; + *g = new_file; + } + Err(_) => { + // Didn't update, no need to reopen + } + } + + Ok(()) + } }