diff --git a/crates/librqbit/src/api.rs b/crates/librqbit/src/api.rs index 2538b3b..57d02d3 100644 --- a/crates/librqbit/src/api.rs +++ b/crates/librqbit/src/api.rs @@ -1,4 +1,4 @@ -use std::{net::SocketAddr, sync::Arc}; +use std::{collections::HashSet, net::SocketAddr, sync::Arc}; use anyhow::Context; use buffers::ByteBufOwned; @@ -122,6 +122,18 @@ impl Api { Ok(Default::default()) } + pub fn api_torrent_action_update_only_files( + &self, + idx: TorrentId, + only_files: &HashSet, + ) -> Result { + let handle = self.mgr_handle(idx)?; + self.session + .update_only_files(&handle, only_files) + .context("error updating only_files")?; + Ok(Default::default()) + } + pub fn api_set_rust_log(&self, new_value: String) -> Result { let tx = self .rust_log_reload_tx diff --git a/crates/librqbit/src/chunk_tracker.rs b/crates/librqbit/src/chunk_tracker.rs index 19b1642..72e86ee 100644 --- a/crates/librqbit/src/chunk_tracker.rs +++ b/crates/librqbit/src/chunk_tracker.rs @@ -1,11 +1,7 @@ use std::collections::HashSet; use anyhow::Context; -use buffers::ByteBufOwned; -use librqbit_core::{ - lengths::{ChunkInfo, Lengths, ValidPieceIndex}, - torrent_metainfo::{TorrentMetaV1Info, TorrentMetaV1Owned}, -}; +use librqbit_core::lengths::{ChunkInfo, Lengths, ValidPieceIndex}; use peer_binary_protocol::Piece; use tracing::{debug, trace}; diff --git a/crates/librqbit/src/http_api.rs b/crates/librqbit/src/http_api.rs index d710b22..44fc9d9 100644 --- a/crates/librqbit/src/http_api.rs +++ b/crates/librqbit/src/http_api.rs @@ -181,6 +181,21 @@ impl HttpApi { state.api_torrent_action_delete(idx).map(axum::Json) } + #[derive(Deserialize)] + struct UpdateOnlyFilesRequest { + only_files: Vec, + } + + async fn torrent_action_update_only_files( + State(state): State, + Path(idx): Path, + axum::Json(req): axum::Json, + ) -> Result { + state + .api_torrent_action_update_only_files(idx, &req.only_files.into_iter().collect()) + .map(axum::Json) + } + async fn set_rust_log( State(state): State, new_value: String, @@ -215,7 +230,11 @@ impl HttpApi { .route("/torrents/:id/pause", post(torrent_action_pause)) .route("/torrents/:id/start", post(torrent_action_start)) .route("/torrents/:id/forget", post(torrent_action_forget)) - .route("/torrents/:id/delete", post(torrent_action_delete)); + .route("/torrents/:id/delete", post(torrent_action_delete)) + .route( + "/torrents/:id/update_only_files", + post(torrent_action_update_only_files), + ); } #[cfg(feature = "webui")] diff --git a/crates/librqbit/src/session.rs b/crates/librqbit/src/session.rs index 101a6d4..7698dc7 100644 --- a/crates/librqbit/src/session.rs +++ b/crates/librqbit/src/session.rs @@ -107,7 +107,7 @@ impl SessionDatabase { .collect(), info_hash: torrent.info_hash().as_string(), info: torrent.info().info.clone(), - only_files: torrent.only_files.clone(), + only_files: torrent.only_files().clone(), is_paused: torrent .with_state(|s| matches!(s, ManagedTorrentState::Paused(_))), output_folder: torrent.info().out_dir.clone(), @@ -1137,6 +1137,18 @@ impl Session { Ok(()) } + pub fn update_only_files( + self: &Arc, + handle: &ManagedTorrentHandle, + only_files: &HashSet, + ) -> anyhow::Result<()> { + let need_to_unpause = handle.update_only_files(only_files)?; + if need_to_unpause { + self.unpause(handle)?; + } + Ok(()) + } + pub fn tcp_listen_port(&self) -> Option { self.tcp_listen_port } diff --git a/crates/librqbit/src/torrent_state/mod.rs b/crates/librqbit/src/torrent_state/mod.rs index a01a7f4..e698374 100644 --- a/crates/librqbit/src/torrent_state/mod.rs +++ b/crates/librqbit/src/torrent_state/mod.rs @@ -67,6 +67,7 @@ impl ManagedTorrentState { pub(crate) struct ManagedTorrentLocked { pub state: ManagedTorrentState, + pub(crate) only_files: Option>, } #[derive(Default)] @@ -91,7 +92,6 @@ pub struct ManagedTorrentInfo { pub struct ManagedTorrent { pub info: Arc, - pub(crate) only_files: Option>, locked: RwLock, } @@ -109,7 +109,7 @@ impl ManagedTorrent { } pub fn only_files(&self) -> Option> { - self.only_files.clone() + self.locked.read().only_files.clone() } pub fn with_state(&self, f: impl FnOnce(&ManagedTorrentState) -> R) -> R { @@ -298,7 +298,7 @@ impl ManagedTorrent { ManagedTorrentState::Error(_) => { let initializing = Arc::new(TorrentStateInitializing::new( self.info.clone(), - self.only_files.clone(), + g.only_files.clone(), )); g.state = ManagedTorrentState::Initializing(initializing.clone()); drop(g); @@ -407,6 +407,45 @@ impl ManagedTorrent { } .boxed() } + + // Returns true if needed to unpause torrent. + // This is just implementation detail - it's easier to pause/unpause than to tinker with internals. + pub(crate) fn update_only_files(&self, only_files: &HashSet) -> anyhow::Result { + if only_files.is_empty() { + anyhow::bail!("you need to select at least one file"); + } + let file_count = self.info().info.iter_file_lengths()?.count(); + for f in only_files.iter().copied() { + if f >= file_count { + anyhow::bail!("only_files contains invalid value {f}") + } + } + + // if live, need to update chunk tracker + // - if already finished: need to pause, then unpause (to reopen files etc) + // if paused, need to update chunk tracker + + let mut g = self.locked.write(); + let need_to_unpause = match &mut g.state { + ManagedTorrentState::Initializing(_) => bail!("can't update initializing torrent"), + ManagedTorrentState::Error(_) => false, + ManagedTorrentState::None => false, + ManagedTorrentState::Paused(p) => { + p.update_only_files(only_files)?; + false + } + ManagedTorrentState::Live(l) => { + let mut p = l.pause()?; + let e = p.update_only_files(only_files); + g.state = ManagedTorrentState::Paused(p); + e?; + true + } + }; + + g.only_files = Some(only_files.iter().copied().collect()); + Ok(need_to_unpause) + } } pub struct ManagedTorrentBuilder { @@ -507,9 +546,9 @@ impl ManagedTorrentBuilder { self.only_files.clone(), )); Ok(Arc::new(ManagedTorrent { - only_files: self.only_files, locked: RwLock::new(ManagedTorrentLocked { state: ManagedTorrentState::Initializing(initializing), + only_files: self.only_files, }), info, })) diff --git a/crates/librqbit/src/torrent_state/paused.rs b/crates/librqbit/src/torrent_state/paused.rs index b8ac7c1..5f06278 100644 --- a/crates/librqbit/src/torrent_state/paused.rs +++ b/crates/librqbit/src/torrent_state/paused.rs @@ -1,4 +1,4 @@ -use std::{fs::File, path::PathBuf, sync::Arc}; +use std::{collections::HashSet, fs::File, path::PathBuf, sync::Arc}; use parking_lot::Mutex; @@ -15,11 +15,13 @@ pub struct TorrentStatePaused { pub(crate) needed_bytes: u64, } -// impl TorrentStatePaused { -// pub fn get_have_bytes(&self) -> u64 { -// self.have_bytes -// } -// pub fn get_needed_bytes(&self) -> u64 { -// self.needed_bytes -// } -// } +impl TorrentStatePaused { + pub(crate) fn update_only_files(&mut self, only_files: &HashSet) -> anyhow::Result<()> { + let hn = self + .chunk_tracker + .update_only_files(self.info.info.iter_file_lengths()?, only_files)?; + self.have_bytes = hn.have_bytes; + self.needed_bytes = hn.needed_bytes; + Ok(()) + } +}