279 lines
8.2 KiB
Rust
279 lines
8.2 KiB
Rust
use std::{str::FromStr, sync::Arc};
|
|
|
|
use async_trait::async_trait;
|
|
use librqbit::{
|
|
AddTorrent, AddTorrentOptions, ManagedTorrent, Session, SessionOptions, api::TorrentIdOrHash,
|
|
};
|
|
use librqbit_core::Id20;
|
|
use snafu::ResultExt;
|
|
use tracing::instrument;
|
|
use util::errors::AnyhowResultExt;
|
|
|
|
use super::task::{RqbitCreation, RqbitHash, RqbitSelector, RqbitState, RqbitTask};
|
|
use crate::{
|
|
DownloaderError,
|
|
bittorrent::{
|
|
downloader::TorrentDownloaderTrait,
|
|
source::{HashTorrentSource, HashTorrentSourceTrait},
|
|
},
|
|
core::{DownloadIdSelector, DownloaderTrait},
|
|
errors::RqbitSnafu,
|
|
};
|
|
|
|
#[derive(Debug)]
|
|
pub struct RqbitDownloaderCreation {
|
|
pub save_path: String,
|
|
pub subscriber_id: i32,
|
|
pub downloader_id: i32,
|
|
}
|
|
|
|
impl RqbitDownloaderCreation {}
|
|
|
|
pub struct RqbitDownloader {
|
|
pub save_path: String,
|
|
pub subscriber_id: i32,
|
|
pub downloader_id: i32,
|
|
pub session: Arc<Session>,
|
|
}
|
|
|
|
impl RqbitDownloader {
|
|
#[instrument(level = "debug")]
|
|
pub async fn from_creation(
|
|
creation: RqbitDownloaderCreation,
|
|
) -> Result<Arc<Self>, DownloaderError> {
|
|
let session_opt = SessionOptions {
|
|
..Default::default()
|
|
};
|
|
let session = Session::new_with_opts(creation.save_path.clone().into(), session_opt)
|
|
.await
|
|
.to_dyn_boxed()
|
|
.context(RqbitSnafu {})?;
|
|
Ok(Arc::new(Self {
|
|
session,
|
|
save_path: creation.save_path,
|
|
subscriber_id: creation.subscriber_id,
|
|
downloader_id: creation.downloader_id,
|
|
}))
|
|
}
|
|
|
|
pub async fn add_torrent(
|
|
&self,
|
|
source: HashTorrentSource,
|
|
opt: Option<AddTorrentOptions>,
|
|
) -> Result<RqbitHash, DownloaderError> {
|
|
let hash = Id20::from_str(&source.hash_info() as &str)
|
|
.to_dyn_boxed()
|
|
.context(RqbitSnafu {})?;
|
|
|
|
let source = match source {
|
|
HashTorrentSource::TorrentFile(file) => AddTorrent::TorrentFileBytes(file.payload),
|
|
HashTorrentSource::MagnetUrl(magnet) => AddTorrent::Url(magnet.url.into()),
|
|
};
|
|
let response = self
|
|
.session
|
|
.add_torrent(source, opt)
|
|
.await
|
|
.to_dyn_boxed()
|
|
.context(RqbitSnafu {})?;
|
|
|
|
let handle = response
|
|
.into_handle()
|
|
.ok_or_else(|| anyhow::anyhow!("failed to get handle of add torrent task"))
|
|
.to_dyn_boxed()
|
|
.context(RqbitSnafu {})?;
|
|
|
|
handle
|
|
.wait_until_initialized()
|
|
.await
|
|
.to_dyn_boxed()
|
|
.context(RqbitSnafu {})?;
|
|
|
|
Ok(hash)
|
|
}
|
|
|
|
fn query_torrent_impl(&self, hash: RqbitHash) -> Result<Arc<ManagedTorrent>, DownloaderError> {
|
|
let torrent = self
|
|
.session
|
|
.get(TorrentIdOrHash::Hash(hash))
|
|
.ok_or_else(|| anyhow::anyhow!("could not find torrent by hash {}", hash.as_string()))
|
|
.to_dyn_boxed()
|
|
.context(RqbitSnafu {})?;
|
|
|
|
Ok(torrent)
|
|
}
|
|
|
|
pub fn query_torrent(&self, hash: RqbitHash) -> Result<RqbitTask, DownloaderError> {
|
|
let torrent = self.query_torrent_impl(hash)?;
|
|
|
|
let task = RqbitTask::from_query(torrent)?;
|
|
|
|
Ok(task)
|
|
}
|
|
|
|
pub async fn pause_torrent(&self, hash: RqbitHash) -> Result<(), DownloaderError> {
|
|
let t = self.query_torrent_impl(hash)?;
|
|
self.session
|
|
.pause(&t)
|
|
.await
|
|
.to_dyn_boxed()
|
|
.context(RqbitSnafu {})?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn resume_torrent(&self, hash: RqbitHash) -> Result<(), DownloaderError> {
|
|
let t = self.query_torrent_impl(hash)?;
|
|
self.session
|
|
.unpause(&t)
|
|
.await
|
|
.to_dyn_boxed()
|
|
.context(RqbitSnafu {})?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn delete_torrent(&self, hash: RqbitHash) -> Result<(), DownloaderError> {
|
|
self.session
|
|
.delete(TorrentIdOrHash::Hash(hash), true)
|
|
.await
|
|
.to_dyn_boxed()
|
|
.context(RqbitSnafu {})?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl DownloaderTrait for RqbitDownloader {
|
|
type State = RqbitState;
|
|
type Id = RqbitHash;
|
|
type Task = RqbitTask;
|
|
type Creation = RqbitCreation;
|
|
type Selector = RqbitSelector;
|
|
|
|
#[instrument(level = "debug", skip(self))]
|
|
async fn add_downloads(
|
|
&self,
|
|
creation: RqbitCreation,
|
|
) -> Result<Vec<<Self as DownloaderTrait>::Id>, DownloaderError> {
|
|
let mut sources = creation.sources;
|
|
if sources.len() == 1 {
|
|
let hash = self
|
|
.add_torrent(
|
|
sources.pop().unwrap(),
|
|
Some(AddTorrentOptions {
|
|
paused: false,
|
|
output_folder: Some(self.save_path.clone()),
|
|
..Default::default()
|
|
}),
|
|
)
|
|
.await?;
|
|
Ok(vec![hash])
|
|
} else {
|
|
let tasks = sources
|
|
.into_iter()
|
|
.map(|s| {
|
|
self.add_torrent(
|
|
s,
|
|
Some(AddTorrentOptions {
|
|
paused: false,
|
|
output_folder: Some(self.save_path.clone()),
|
|
..Default::default()
|
|
}),
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let results = futures::future::try_join_all(tasks).await?;
|
|
Ok(results)
|
|
}
|
|
}
|
|
|
|
async fn pause_downloads(
|
|
&self,
|
|
selector: <Self as DownloaderTrait>::Selector,
|
|
) -> Result<impl IntoIterator<Item = Self::Id>, DownloaderError> {
|
|
<Self as TorrentDownloaderTrait>::pause_downloads(self, selector).await
|
|
}
|
|
|
|
async fn resume_downloads(
|
|
&self,
|
|
selector: <Self as DownloaderTrait>::Selector,
|
|
) -> Result<impl IntoIterator<Item = Self::Id>, DownloaderError> {
|
|
<Self as TorrentDownloaderTrait>::resume_downloads(self, selector).await
|
|
}
|
|
|
|
async fn remove_downloads(
|
|
&self,
|
|
selector: <Self as DownloaderTrait>::Selector,
|
|
) -> Result<impl IntoIterator<Item = Self::Id>, DownloaderError> {
|
|
<Self as TorrentDownloaderTrait>::remove_downloads(self, selector).await
|
|
}
|
|
|
|
#[instrument(level = "debug", skip(self))]
|
|
async fn query_downloads(
|
|
&self,
|
|
selector: RqbitSelector,
|
|
) -> Result<Vec<<Self as DownloaderTrait>::Task>, DownloaderError> {
|
|
let hashes = selector.into_iter();
|
|
|
|
let tasks = hashes
|
|
.map(|h| self.query_torrent(h))
|
|
.collect::<Result<Vec<_>, DownloaderError>>()?;
|
|
|
|
Ok(tasks)
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl TorrentDownloaderTrait for RqbitDownloader {
|
|
type IdSelector = DownloadIdSelector<Self::Task>;
|
|
|
|
#[instrument(level = "debug", skip(self))]
|
|
async fn pause_torrents(
|
|
&self,
|
|
selector: Self::IdSelector,
|
|
) -> Result<Self::IdSelector, DownloaderError> {
|
|
let mut hashes: Vec<_> = selector.clone();
|
|
|
|
if hashes.len() == 1 {
|
|
self.pause_torrent(hashes.pop().unwrap()).await?;
|
|
} else {
|
|
futures::future::try_join_all(hashes.into_iter().map(|h| self.pause_torrent(h)))
|
|
.await?;
|
|
}
|
|
Ok(selector)
|
|
}
|
|
|
|
#[instrument(level = "debug", skip(self))]
|
|
async fn resume_torrents(
|
|
&self,
|
|
selector: Self::IdSelector,
|
|
) -> Result<Self::IdSelector, DownloaderError> {
|
|
let mut hashes: Vec<_> = selector.clone();
|
|
|
|
if hashes.len() == 1 {
|
|
self.resume_torrent(hashes.pop().unwrap()).await?;
|
|
} else {
|
|
futures::future::try_join_all(hashes.into_iter().map(|h| self.resume_torrent(h)))
|
|
.await?;
|
|
}
|
|
Ok(selector)
|
|
}
|
|
|
|
#[instrument(level = "debug", skip(self))]
|
|
async fn remove_torrents(
|
|
&self,
|
|
selector: Self::IdSelector,
|
|
) -> Result<Self::IdSelector, DownloaderError> {
|
|
let mut hashes: Vec<_> = selector.clone();
|
|
|
|
if hashes.len() == 1 {
|
|
self.delete_torrent(hashes.pop().unwrap()).await?;
|
|
} else {
|
|
futures::future::try_join_all(hashes.into_iter().map(|h| self.delete_torrent(h)))
|
|
.await?;
|
|
}
|
|
Ok(selector)
|
|
}
|
|
}
|