refactor: split modules

This commit is contained in:
2025-04-08 02:12:06 +08:00
parent 376d2b28d3
commit 2686fa1d76
94 changed files with 1125 additions and 580 deletions

View File

@@ -0,0 +1,2 @@
pub const BITTORRENT_MIME_TYPE: &str = "application/x-bittorrent";
pub const MAGNET_SCHEMA: &str = "magnet";

View File

@@ -0,0 +1,74 @@
use async_trait::async_trait;
use crate::{
DownloaderError,
bittorrent::task::{
TorrentCreationTrait, TorrentHashTrait, TorrentStateTrait, TorrentTaskTrait,
},
core::{DownloadIdSelectorTrait, DownloadSelectorTrait, DownloadTaskTrait, DownloaderTrait},
};
#[async_trait]
pub trait TorrentDownloaderTrait: DownloaderTrait
where
Self::State: TorrentStateTrait,
Self::Id: TorrentHashTrait,
Self::Task: TorrentTaskTrait<State = Self::State, Id = Self::Id>,
Self::Creation: TorrentCreationTrait<Task = Self::Task>,
Self::Selector: DownloadSelectorTrait<Task = Self::Task, Id = Self::Id>,
{
type IdSelector: DownloadIdSelectorTrait<Task = Self::Task, Id = Self::Id>;
async fn pause_downloads(
&self,
selector: <Self as DownloaderTrait>::Selector,
) -> Result<Self::IdSelector, DownloaderError> {
let hashes = <Self as TorrentDownloaderTrait>::query_torrent_hashes(self, selector).await?;
self.pause_torrents(hashes).await
}
async fn resume_downloads(
&self,
selector: <Self as DownloaderTrait>::Selector,
) -> Result<Self::IdSelector, DownloaderError> {
let hashes = <Self as TorrentDownloaderTrait>::query_torrent_hashes(self, selector).await?;
self.resume_torrents(hashes).await
}
async fn remove_downloads(
&self,
selector: <Self as DownloaderTrait>::Selector,
) -> Result<Self::IdSelector, DownloaderError> {
let hashes = <Self as TorrentDownloaderTrait>::query_torrent_hashes(self, selector).await?;
self.remove_torrents(hashes).await
}
async fn query_torrent_hashes(
&self,
selector: <Self as DownloaderTrait>::Selector,
) -> Result<Self::IdSelector, DownloaderError> {
let hashes = match selector.try_into_ids_only() {
Ok(hashes) => Self::IdSelector::from_iter(hashes),
Err(selector) => {
let tasks = self.query_downloads(selector).await?;
Self::IdSelector::from_iter(tasks.into_iter().map(|s| s.into_id()))
}
};
Ok(hashes)
}
async fn pause_torrents(
&self,
hashes: Self::IdSelector,
) -> Result<Self::IdSelector, DownloaderError>;
async fn resume_torrents(
&self,
hashes: Self::IdSelector,
) -> Result<Self::IdSelector, DownloaderError>;
async fn remove_torrents(
&self,
hashes: Self::IdSelector,
) -> Result<Self::IdSelector, DownloaderError>;
}

View File

@@ -0,0 +1,6 @@
pub mod defs;
pub mod downloader;
pub mod source;
pub mod task;
pub use defs::{BITTORRENT_MIME_TYPE, MAGNET_SCHEMA};

View File

@@ -0,0 +1,224 @@
use std::{
borrow::Cow,
fmt::{Debug, Formatter},
};
use bytes::Bytes;
use fetch::{bytes::fetch_bytes, client::core::HttpClientTrait};
use librqbit_core::{magnet::Magnet, torrent_metainfo, torrent_metainfo::TorrentMetaV1Owned};
use snafu::ResultExt;
use url::Url;
use util::errors::AnyhowResultExt;
use super::defs::MAGNET_SCHEMA;
use crate::errors::{DownloadFetchSnafu, DownloaderError, MagnetFormatSnafu, TorrentMetaSnafu};
pub trait HashTorrentSourceTrait: Sized {
fn hash_info(&self) -> Cow<'_, str>;
}
pub struct MagnetUrlSource {
pub magnet: Magnet,
pub url: String,
}
impl MagnetUrlSource {
pub fn from_url(url: String) -> Result<Self, DownloaderError> {
let magnet = Magnet::parse(&url)
.to_dyn_boxed()
.context(MagnetFormatSnafu {
message: url.clone(),
})?;
Ok(Self { magnet, url })
}
}
impl HashTorrentSourceTrait for MagnetUrlSource {
fn hash_info(&self) -> Cow<'_, str> {
let hash_info = self
.magnet
.as_id32()
.map(|s| s.as_string())
.or_else(|| self.magnet.as_id20().map(|s| s.as_string()))
.unwrap_or_else(|| unreachable!("hash of magnet must existed"));
hash_info.into()
}
}
impl Debug for MagnetUrlSource {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MagnetUrlSource")
.field("url", &self.url)
.finish()
}
}
impl Clone for MagnetUrlSource {
fn clone(&self) -> Self {
Self {
magnet: Magnet::parse(&self.url).unwrap(),
url: self.url.clone(),
}
}
}
impl PartialEq for MagnetUrlSource {
fn eq(&self, other: &Self) -> bool {
self.url == other.url
}
}
impl Eq for MagnetUrlSource {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TorrentUrlSource {
pub url: String,
}
impl TorrentUrlSource {
pub fn from_url(url: String) -> Result<Self, DownloaderError> {
Ok(Self { url })
}
}
#[derive(Clone)]
pub struct TorrentFileSource {
pub url: Option<String>,
pub payload: Bytes,
pub meta: Box<TorrentMetaV1Owned>,
pub filename: String,
}
impl TorrentFileSource {
pub fn from_bytes(
filename: String,
bytes: Bytes,
url: Option<String>,
) -> Result<Self, DownloaderError> {
let meta = torrent_metainfo::torrent_from_bytes(bytes.as_ref())
.to_dyn_boxed()
.with_context(|_| TorrentMetaSnafu {
message: format!(
"filename = {}, url = {}",
filename,
url.as_deref().unwrap_or_default()
),
})?
.to_owned();
Ok(TorrentFileSource {
url,
payload: bytes,
meta: Box::new(meta),
filename,
})
}
pub async fn from_url_and_http_client(
client: &impl HttpClientTrait,
url: String,
) -> Result<TorrentFileSource, DownloaderError> {
let payload = fetch_bytes(client, &url)
.await
.boxed()
.with_context(|_| DownloadFetchSnafu { url: url.clone() })?;
let filename = Url::parse(&url)
.boxed()
.and_then(|s| {
s.path_segments()
.and_then(|mut p| p.next_back())
.map(String::from)
.ok_or_else(|| anyhow::anyhow!("invalid url"))
.to_dyn_boxed()
})
.with_context(|_| DownloadFetchSnafu { url: url.clone() })?;
Self::from_bytes(filename, payload, Some(url))
}
}
impl HashTorrentSourceTrait for TorrentFileSource {
fn hash_info(&self) -> Cow<'_, str> {
self.meta.info_hash.as_string().into()
}
}
impl Debug for TorrentFileSource {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TorrentFileSource")
.field("hash", &self.meta.info_hash.as_string())
.finish()
}
}
#[derive(Clone, Debug)]
pub enum UrlTorrentSource {
MagnetUrl(MagnetUrlSource),
TorrentUrl(TorrentUrlSource),
}
impl UrlTorrentSource {
pub fn from_url(url: String) -> Result<Self, DownloaderError> {
let url_ = Url::parse(&url)?;
let source = if url_.scheme() == MAGNET_SCHEMA {
Self::from_magnet_url(url)?
} else {
Self::from_torrent_url(url)?
};
Ok(source)
}
pub fn from_magnet_url(url: String) -> Result<Self, DownloaderError> {
let magnet_source = MagnetUrlSource::from_url(url)?;
Ok(Self::MagnetUrl(magnet_source))
}
pub fn from_torrent_url(url: String) -> Result<Self, DownloaderError> {
let torrent_source = TorrentUrlSource::from_url(url)?;
Ok(Self::TorrentUrl(torrent_source))
}
}
#[derive(Debug, Clone)]
pub enum HashTorrentSource {
MagnetUrl(MagnetUrlSource),
TorrentFile(TorrentFileSource),
}
impl HashTorrentSource {
pub async fn from_url_and_http_client(
client: &impl HttpClientTrait,
url: String,
) -> Result<Self, DownloaderError> {
let url_ = Url::parse(&url)?;
let source = if url_.scheme() == MAGNET_SCHEMA {
Self::from_magnet_url(url)?
} else {
Self::from_torrent_url_and_http_client(client, url).await?
};
Ok(source)
}
pub fn from_magnet_url(url: String) -> Result<Self, DownloaderError> {
let magnet_source = MagnetUrlSource::from_url(url)?;
Ok(Self::MagnetUrl(magnet_source))
}
pub async fn from_torrent_url_and_http_client(
client: &impl HttpClientTrait,
url: String,
) -> Result<Self, DownloaderError> {
let torrent_source = TorrentFileSource::from_url_and_http_client(client, url).await?;
Ok(Self::TorrentFile(torrent_source))
}
}
impl HashTorrentSourceTrait for HashTorrentSource {
fn hash_info(&self) -> Cow<'_, str> {
match self {
HashTorrentSource::MagnetUrl(m) => m.hash_info(),
HashTorrentSource::TorrentFile(t) => t.hash_info(),
}
}
}

View File

@@ -0,0 +1,43 @@
use std::{borrow::Cow, hash::Hash};
use quirks_path::{Path, PathBuf};
use crate::{
bittorrent::source::HashTorrentSource,
core::{DownloadCreationTrait, DownloadIdTrait, DownloadStateTrait, DownloadTaskTrait},
};
pub const TORRENT_TAG_NAME: &str = "konobangu";
pub trait TorrentHashTrait: DownloadIdTrait + Send + Hash {}
pub type SimpleTorrentHash = String;
impl DownloadIdTrait for SimpleTorrentHash {}
impl TorrentHashTrait for SimpleTorrentHash {}
pub trait TorrentStateTrait: DownloadStateTrait {}
pub trait TorrentTaskTrait: DownloadTaskTrait
where
Self::State: TorrentStateTrait,
Self::Id: TorrentHashTrait,
{
fn hash_info(&self) -> &str;
fn name(&self) -> Cow<'_, str> {
Cow::Borrowed(self.hash_info())
}
fn tags(&self) -> impl Iterator<Item = Cow<'_, str>>;
fn category(&self) -> Option<Cow<'_, str>>;
}
pub trait TorrentCreationTrait: DownloadCreationTrait {
fn save_path(&self) -> &Path;
fn save_path_mut(&mut self) -> &mut PathBuf;
fn sources_mut(&mut self) -> &mut Vec<HashTorrentSource>;
}