diff --git a/crates/librqbit/src/storage/filesystem/fs.rs b/crates/librqbit/src/storage/filesystem/fs.rs index 38a38929c..912201937 100644 --- a/crates/librqbit/src/storage/filesystem/fs.rs +++ b/crates/librqbit/src/storage/filesystem/fs.rs @@ -109,6 +109,14 @@ impl TorrentStorage for FilesystemStorage { })) } + fn release_files(&self) -> anyhow::Result<()> { + for file in &self.opened_files { + file.close(); + } + + Ok(()) + } + fn remove_directory_if_empty(&self, path: &Path) -> anyhow::Result<()> { let path = self.output_folder.join(path); if !path.is_dir() { diff --git a/crates/librqbit/src/storage/filesystem/opened_file.rs b/crates/librqbit/src/storage/filesystem/opened_file.rs index a0aed8dc9..4aae8763e 100644 --- a/crates/librqbit/src/storage/filesystem/opened_file.rs +++ b/crates/librqbit/src/storage/filesystem/opened_file.rs @@ -152,6 +152,15 @@ impl OpenedFile { }) } + pub fn close(&self) { + let mut g = self.file.write(); + g.fd = None; + #[cfg(windows)] + { + g.tried_marking_sparse = false; + } + } + pub fn lock_read(&self) -> crate::Result> { RwLockReadGuard::try_map(self.file.read(), |f| f.as_ref()) .ok() diff --git a/crates/librqbit/src/storage/mod.rs b/crates/librqbit/src/storage/mod.rs index 8a51e928a..f6b249757 100644 --- a/crates/librqbit/src/storage/mod.rs +++ b/crates/librqbit/src/storage/mod.rs @@ -163,6 +163,11 @@ pub trait TorrentStorage: Send + Sync { /// This is used to make the underlying object useless when e.g. pausing the torrent. fn take(&self) -> anyhow::Result>; + /// Release opened file handles while keeping storage paths and torrent state available. + fn release_files(&self) -> anyhow::Result<()> { + Ok(()) + } + /// Callback called every time a piece has completed and has been validated. /// Default implementation does nothing, but can be override in trait implementations. fn on_piece_completed(&self, _piece_index: ValidPieceIndex) -> anyhow::Result<()> { @@ -191,6 +196,10 @@ impl TorrentStorage for Box { (**self).take() } + fn release_files(&self) -> anyhow::Result<()> { + (**self).release_files() + } + fn remove_directory_if_empty(&self, path: &Path) -> anyhow::Result<()> { (**self).remove_directory_if_empty(path) } diff --git a/crates/librqbit/src/torrent_state/mod.rs b/crates/librqbit/src/torrent_state/mod.rs index cfe483cdd..8198d78dc 100644 --- a/crates/librqbit/src/torrent_state/mod.rs +++ b/crates/librqbit/src/torrent_state/mod.rs @@ -391,11 +391,30 @@ impl ManagedTorrent { } g.state = ManagedTorrentState::Paused(paused); + if start_paused + && let ManagedTorrentState::Paused(paused) = &g.state + && let Err(error) = paused.files.release_files() + { + warn!( + id=?t.shared.id, + info_hash=?t.shared.info_hash, + error=?error, + "error releasing files after paused initial check" + ); + } t.state_change_notify.notify_waiters(); _start(&t, peer_rx, start_paused, session, Some(g), token) } Err(err) => { if init.is_pause_requested() { + if let Err(error) = init.files.release_files() { + warn!( + id=?init.shared.id, + info_hash=?init.shared.info_hash, + error=?error, + "error releasing files after paused initial check" + ); + } debug!("initial check paused"); t.state_change_notify.notify_waiters(); return Ok(()); @@ -477,6 +496,7 @@ impl ManagedTorrent { match &g.state { ManagedTorrentState::Live(live) => { let paused = live.pause()?; + paused.files.release_files()?; g.state = ManagedTorrentState::Paused(paused); g.paused = true; self.state_change_notify.notify_waiters(); @@ -490,7 +510,12 @@ impl ManagedTorrent { Ok(()) } ManagedTorrentState::Paused(_) => { - bail!("torrent is already paused"); + let paused = g.state.take().assert_paused(); + paused.files.release_files()?; + g.state = ManagedTorrentState::Paused(paused); + g.paused = true; + self.state_change_notify.notify_waiters(); + Ok(()) } ManagedTorrentState::Error(_) => { bail!("can't pause torrent in error state")