2023-11-24 12:44:36 +00:00
|
|
|
pub mod initializing;
|
2023-11-23 15:41:40 +00:00
|
|
|
pub mod live;
|
2023-11-24 12:44:36 +00:00
|
|
|
pub mod paused;
|
|
|
|
|
pub mod utils;
|
2023-11-19 18:15:18 +00:00
|
|
|
|
2023-11-24 09:30:21 +00:00
|
|
|
use std::net::SocketAddr;
|
2023-11-24 14:08:02 +00:00
|
|
|
use std::path::Path;
|
2023-11-24 09:30:21 +00:00
|
|
|
use std::path::PathBuf;
|
2023-11-23 17:14:08 +00:00
|
|
|
use std::sync::Arc;
|
2023-11-24 09:30:21 +00:00
|
|
|
use std::time::Duration;
|
2023-11-23 17:14:08 +00:00
|
|
|
|
2023-11-24 14:08:02 +00:00
|
|
|
use anyhow::bail;
|
2023-11-24 09:30:21 +00:00
|
|
|
use anyhow::Context;
|
2023-11-23 17:14:08 +00:00
|
|
|
use buffers::ByteString;
|
|
|
|
|
use librqbit_core::id20::Id20;
|
2023-11-24 12:44:36 +00:00
|
|
|
use librqbit_core::peer_id::generate_peer_id;
|
2023-11-24 12:47:33 +00:00
|
|
|
|
2023-11-23 17:14:08 +00:00
|
|
|
use librqbit_core::torrent_metainfo::TorrentMetaV1Info;
|
2023-11-23 15:41:40 +00:00
|
|
|
pub use live::*;
|
2023-11-23 17:14:08 +00:00
|
|
|
use parking_lot::RwLock;
|
2023-11-24 12:47:33 +00:00
|
|
|
|
2023-11-24 14:08:02 +00:00
|
|
|
use tokio_stream::StreamExt;
|
|
|
|
|
use tracing::trace_span;
|
2023-11-23 17:14:08 +00:00
|
|
|
use url::Url;
|
|
|
|
|
|
2023-11-24 14:29:05 +00:00
|
|
|
use crate::chunk_tracker::ChunkTracker;
|
2023-11-24 14:08:02 +00:00
|
|
|
use crate::spawn_utils::spawn;
|
|
|
|
|
use crate::spawn_utils::BlockingSpawner;
|
2023-11-24 09:30:21 +00:00
|
|
|
|
2023-11-24 12:44:36 +00:00
|
|
|
use initializing::TorrentStateInitializing;
|
2023-11-24 09:30:21 +00:00
|
|
|
|
2023-11-24 12:44:36 +00:00
|
|
|
use self::paused::TorrentStatePaused;
|
2023-11-24 09:30:21 +00:00
|
|
|
|
2023-11-24 12:44:36 +00:00
|
|
|
pub enum ManagedTorrentState {
|
2023-11-24 14:08:02 +00:00
|
|
|
Initializing(Arc<TorrentStateInitializing>),
|
2023-11-24 12:44:36 +00:00
|
|
|
Paused(TorrentStatePaused),
|
2023-11-24 09:30:21 +00:00
|
|
|
Live(Arc<TorrentStateLive>),
|
2023-11-24 12:44:36 +00:00
|
|
|
Error(anyhow::Error),
|
2023-11-24 14:08:02 +00:00
|
|
|
|
|
|
|
|
// This is used when swapping between states, outside world should never see it.
|
|
|
|
|
None,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ManagedTorrentState {
|
|
|
|
|
fn assert_paused(self) -> TorrentStatePaused {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Paused(paused) => paused,
|
|
|
|
|
_ => panic!("Expected paused state"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn take(&mut self) -> Self {
|
|
|
|
|
std::mem::replace(self, Self::None)
|
|
|
|
|
}
|
2023-11-23 17:14:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) struct ManagedTorrentLocked {
|
|
|
|
|
pub state: ManagedTorrentState,
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 12:44:36 +00:00
|
|
|
#[derive(Default)]
|
|
|
|
|
pub(crate) struct ManagedTorrentOptions {
|
|
|
|
|
pub force_tracker_interval: Option<Duration>,
|
|
|
|
|
pub peer_connect_timeout: Option<Duration>,
|
|
|
|
|
pub peer_read_write_timeout: Option<Duration>,
|
|
|
|
|
pub overwrite: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-23 17:14:08 +00:00
|
|
|
pub struct ManagedTorrentInfo {
|
|
|
|
|
pub info: TorrentMetaV1Info<ByteString>,
|
|
|
|
|
pub info_hash: Id20,
|
2023-11-24 09:30:21 +00:00
|
|
|
pub out_dir: PathBuf,
|
|
|
|
|
pub spawner: BlockingSpawner,
|
|
|
|
|
pub trackers: Vec<Url>,
|
2023-11-24 12:44:36 +00:00
|
|
|
pub peer_id: Id20,
|
|
|
|
|
pub(crate) options: ManagedTorrentOptions,
|
2023-11-23 17:14:08 +00:00
|
|
|
}
|
|
|
|
|
|
2023-11-24 09:30:21 +00:00
|
|
|
pub struct ManagedTorrent {
|
2023-11-23 17:14:08 +00:00
|
|
|
pub info: Arc<ManagedTorrentInfo>,
|
2023-11-24 14:08:02 +00:00
|
|
|
only_files: Option<Vec<usize>>,
|
2023-11-24 09:30:21 +00:00
|
|
|
locked: RwLock<ManagedTorrentLocked>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ManagedTorrent {
|
|
|
|
|
pub fn info(&self) -> &ManagedTorrentInfo {
|
|
|
|
|
&self.info
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn info_hash(&self) -> Id20 {
|
|
|
|
|
self.info.info_hash
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn only_files(&self) -> Option<Vec<usize>> {
|
2023-11-24 14:08:02 +00:00
|
|
|
self.only_files.clone()
|
2023-11-24 09:30:21 +00:00
|
|
|
}
|
|
|
|
|
|
2023-11-24 12:44:36 +00:00
|
|
|
pub fn with_state<R>(&self, f: impl FnOnce(&ManagedTorrentState) -> R) -> R {
|
|
|
|
|
f(&self.locked.read().state)
|
2023-11-24 09:30:21 +00:00
|
|
|
}
|
|
|
|
|
|
2023-11-24 14:29:05 +00:00
|
|
|
pub fn with_chunk_tracker<R>(&self, f: impl FnOnce(&ChunkTracker) -> R) -> anyhow::Result<R> {
|
|
|
|
|
let g = self.locked.read();
|
|
|
|
|
match &g.state {
|
|
|
|
|
ManagedTorrentState::Paused(p) => Ok(f(&p.chunk_tracker)),
|
|
|
|
|
ManagedTorrentState::Live(l) => Ok(f(&l.lock_read("chunk_tracker").chunks)),
|
|
|
|
|
_ => bail!("no chunk tracker, torrent neither paused nor live"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 09:30:21 +00:00
|
|
|
pub fn live(&self) -> Option<Arc<TorrentStateLive>> {
|
|
|
|
|
let g = self.locked.read();
|
|
|
|
|
match &g.state {
|
|
|
|
|
ManagedTorrentState::Live(live) => Some(live.clone()),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 14:08:02 +00:00
|
|
|
pub fn start(
|
|
|
|
|
self: &Arc<Self>,
|
|
|
|
|
initial_peers: Vec<SocketAddr>,
|
|
|
|
|
peer_rx: Option<impl StreamExt<Item = SocketAddr> + Unpin + Send + Sync + 'static>,
|
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
|
let mut g = self.locked.write();
|
|
|
|
|
match &g.state {
|
|
|
|
|
ManagedTorrentState::Live(_) => {
|
|
|
|
|
bail!("torrent is already live");
|
|
|
|
|
}
|
|
|
|
|
ManagedTorrentState::Initializing(init) => {
|
|
|
|
|
let init = init.clone();
|
|
|
|
|
let t = self.clone();
|
|
|
|
|
spawn(trace_span!("initialize_and_start"), async move {
|
|
|
|
|
match init.check().await {
|
|
|
|
|
Ok(paused) => {
|
|
|
|
|
let live = TorrentStateLive::new(paused);
|
|
|
|
|
t.locked.write().state = ManagedTorrentState::Live(live.clone());
|
|
|
|
|
|
|
|
|
|
let live = Arc::downgrade(&live);
|
|
|
|
|
spawn(trace_span!("peer_adder"), async move {
|
|
|
|
|
{
|
|
|
|
|
let live: Arc<TorrentStateLive> =
|
|
|
|
|
live.upgrade().context("no longer live")?;
|
|
|
|
|
for peer in initial_peers {
|
|
|
|
|
live.add_peer_if_not_seen(peer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(mut peer_rx) = peer_rx {
|
|
|
|
|
while let Some(peer) = peer_rx.next().await {
|
|
|
|
|
live.upgrade()
|
|
|
|
|
.context("no longer live")?
|
|
|
|
|
.add_peer_if_not_seen(peer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
let result = anyhow::anyhow!("{:?}", err);
|
|
|
|
|
t.locked.write().state = ManagedTorrentState::Error(err);
|
|
|
|
|
Err(result)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
ManagedTorrentState::Paused(_) => {
|
|
|
|
|
let paused = g.state.take().assert_paused();
|
|
|
|
|
let live = TorrentStateLive::new(paused);
|
|
|
|
|
g.state = ManagedTorrentState::Live(live);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
ManagedTorrentState::Error(_) => {
|
|
|
|
|
bail!("starting torrents from error state not implemented")
|
|
|
|
|
}
|
|
|
|
|
ManagedTorrentState::None => bail!("bug: torrent is in empty state"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn pause(&self) -> anyhow::Result<()> {
|
|
|
|
|
let mut g = self.locked.write();
|
|
|
|
|
match &g.state {
|
|
|
|
|
ManagedTorrentState::Live(live) => {
|
|
|
|
|
let paused = live.pause()?;
|
|
|
|
|
g.state = ManagedTorrentState::Paused(paused);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
ManagedTorrentState::Initializing(_) => {
|
|
|
|
|
bail!("torrent is initializing, can't pause");
|
|
|
|
|
}
|
|
|
|
|
ManagedTorrentState::Paused(_) => {
|
|
|
|
|
bail!("torrent is already paused");
|
|
|
|
|
}
|
|
|
|
|
ManagedTorrentState::Error(_) => {
|
|
|
|
|
bail!("can't pause torrent in error state")
|
|
|
|
|
}
|
|
|
|
|
ManagedTorrentState::None => bail!("bug: torrent is in empty state"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 09:30:21 +00:00
|
|
|
pub async fn wait_until_completed(&self) -> anyhow::Result<()> {
|
|
|
|
|
// TODO: rewrite
|
|
|
|
|
self.live()
|
|
|
|
|
.context("torrent isn't live")?
|
|
|
|
|
.wait_until_completed()
|
|
|
|
|
.await;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-11-23 17:14:08 +00:00
|
|
|
}
|
2023-11-24 09:30:21 +00:00
|
|
|
|
|
|
|
|
pub struct ManagedTorrentBuilder {
|
|
|
|
|
info: TorrentMetaV1Info<ByteString>,
|
|
|
|
|
info_hash: Id20,
|
|
|
|
|
output_folder: PathBuf,
|
|
|
|
|
force_tracker_interval: Option<Duration>,
|
|
|
|
|
peer_connect_timeout: Option<Duration>,
|
|
|
|
|
peer_read_write_timeout: Option<Duration>,
|
|
|
|
|
only_files: Option<Vec<usize>>,
|
|
|
|
|
trackers: Vec<Url>,
|
|
|
|
|
peer_id: Option<Id20>,
|
|
|
|
|
overwrite: bool,
|
|
|
|
|
spawner: Option<BlockingSpawner>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ManagedTorrentBuilder {
|
|
|
|
|
pub fn new<P: AsRef<Path>>(
|
|
|
|
|
info: TorrentMetaV1Info<ByteString>,
|
|
|
|
|
info_hash: Id20,
|
|
|
|
|
output_folder: P,
|
|
|
|
|
) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
info,
|
|
|
|
|
info_hash,
|
|
|
|
|
output_folder: output_folder.as_ref().into(),
|
|
|
|
|
spawner: None,
|
|
|
|
|
force_tracker_interval: None,
|
|
|
|
|
peer_connect_timeout: None,
|
|
|
|
|
peer_read_write_timeout: None,
|
|
|
|
|
only_files: None,
|
|
|
|
|
trackers: Default::default(),
|
|
|
|
|
peer_id: None,
|
|
|
|
|
overwrite: false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn only_files(&mut self, only_files: Vec<usize>) -> &mut Self {
|
|
|
|
|
self.only_files = Some(only_files);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn trackers(&mut self, trackers: Vec<Url>) -> &mut Self {
|
|
|
|
|
self.trackers = trackers;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn overwrite(&mut self, overwrite: bool) -> &mut Self {
|
|
|
|
|
self.overwrite = overwrite;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn force_tracker_interval(&mut self, force_tracker_interval: Duration) -> &mut Self {
|
|
|
|
|
self.force_tracker_interval = Some(force_tracker_interval);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn spawner(&mut self, spawner: BlockingSpawner) -> &mut Self {
|
|
|
|
|
self.spawner = Some(spawner);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn peer_id(&mut self, peer_id: Id20) -> &mut Self {
|
|
|
|
|
self.peer_id = Some(peer_id);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn peer_connect_timeout(&mut self, timeout: Duration) -> &mut Self {
|
|
|
|
|
self.peer_connect_timeout = Some(timeout);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn peer_read_write_timeout(&mut self, timeout: Duration) -> &mut Self {
|
|
|
|
|
self.peer_read_write_timeout = Some(timeout);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn build(self) -> ManagedTorrentHandle {
|
2023-11-24 12:44:36 +00:00
|
|
|
let info = Arc::new(ManagedTorrentInfo {
|
|
|
|
|
info: self.info,
|
|
|
|
|
info_hash: self.info_hash,
|
|
|
|
|
out_dir: self.output_folder,
|
|
|
|
|
trackers: self.trackers.into_iter().collect(),
|
|
|
|
|
spawner: self.spawner.unwrap_or_default(),
|
|
|
|
|
peer_id: self.peer_id.unwrap_or_else(generate_peer_id),
|
|
|
|
|
options: ManagedTorrentOptions {
|
|
|
|
|
force_tracker_interval: self.force_tracker_interval,
|
|
|
|
|
peer_connect_timeout: self.peer_connect_timeout,
|
|
|
|
|
peer_read_write_timeout: self.peer_read_write_timeout,
|
|
|
|
|
overwrite: self.overwrite,
|
|
|
|
|
},
|
|
|
|
|
});
|
2023-11-24 14:08:02 +00:00
|
|
|
let initializing = Arc::new(TorrentStateInitializing::new(
|
|
|
|
|
info.clone(),
|
|
|
|
|
self.only_files.clone(),
|
|
|
|
|
));
|
2023-11-24 09:30:21 +00:00
|
|
|
Arc::new(ManagedTorrent {
|
2023-11-24 14:08:02 +00:00
|
|
|
only_files: self.only_files,
|
2023-11-24 09:30:21 +00:00
|
|
|
locked: RwLock::new(ManagedTorrentLocked {
|
2023-11-24 12:44:36 +00:00
|
|
|
state: ManagedTorrentState::Initializing(initializing),
|
2023-11-24 09:30:21 +00:00
|
|
|
}),
|
2023-11-24 12:44:36 +00:00
|
|
|
info,
|
2023-11-24 09:30:21 +00:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub type ManagedTorrentHandle = Arc<ManagedTorrent>;
|