Deentangled classes a bit
This commit is contained in:
parent
9038630622
commit
85e33741b7
2 changed files with 84 additions and 59 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fs::{File, OpenOptions},
|
fs::{File, OpenOptions},
|
||||||
|
ops::Deref,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicU64, Ordering},
|
atomic::{AtomicU64, Ordering},
|
||||||
|
|
@ -17,6 +18,7 @@ use reqwest::Url;
|
||||||
use size_format::SizeFormatterBinary as SF;
|
use size_format::SizeFormatterBinary as SF;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
buffers::{ByteBuf, ByteString},
|
||||||
chunk_tracker::ChunkTracker,
|
chunk_tracker::ChunkTracker,
|
||||||
file_ops::FileOps,
|
file_ops::FileOps,
|
||||||
http_api::make_and_run_http_api,
|
http_api::make_and_run_http_api,
|
||||||
|
|
@ -24,13 +26,14 @@ use crate::{
|
||||||
peer_id::generate_peer_id,
|
peer_id::generate_peer_id,
|
||||||
spawn_utils::{spawn, BlockingSpawner},
|
spawn_utils::{spawn, BlockingSpawner},
|
||||||
speed_estimator::SpeedEstimator,
|
speed_estimator::SpeedEstimator,
|
||||||
torrent_metainfo::TorrentMetaV1Owned,
|
torrent_metainfo::{TorrentMetaV1Info, TorrentMetaV1Owned},
|
||||||
torrent_state::{AtomicStats, TorrentState, TorrentStateLocked},
|
torrent_state::{AtomicStats, TorrentState, TorrentStateLocked},
|
||||||
tracker_comms::{TrackerError, TrackerRequest, TrackerRequestEvent, TrackerResponse},
|
tracker_comms::{TrackerError, TrackerRequest, TrackerRequestEvent, TrackerResponse},
|
||||||
type_aliases::Sha1,
|
type_aliases::Sha1,
|
||||||
};
|
};
|
||||||
pub struct TorrentManagerBuilder {
|
pub struct TorrentManagerBuilder {
|
||||||
torrent: TorrentMetaV1Owned,
|
info: TorrentMetaV1Info<ByteString>,
|
||||||
|
info_hash: [u8; 20],
|
||||||
overwrite: bool,
|
overwrite: bool,
|
||||||
output_folder: PathBuf,
|
output_folder: PathBuf,
|
||||||
only_files: Option<Vec<usize>>,
|
only_files: Option<Vec<usize>>,
|
||||||
|
|
@ -39,9 +42,14 @@ pub struct TorrentManagerBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TorrentManagerBuilder {
|
impl TorrentManagerBuilder {
|
||||||
pub fn new<P: AsRef<Path>>(torrent: TorrentMetaV1Owned, output_folder: P) -> Self {
|
pub fn new<P: AsRef<Path>>(
|
||||||
|
info: TorrentMetaV1Info<ByteString>,
|
||||||
|
info_hash: [u8; 20],
|
||||||
|
output_folder: P,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
torrent,
|
info,
|
||||||
|
info_hash,
|
||||||
overwrite: false,
|
overwrite: false,
|
||||||
output_folder: output_folder.as_ref().into(),
|
output_folder: output_folder.as_ref().into(),
|
||||||
only_files: None,
|
only_files: None,
|
||||||
|
|
@ -70,9 +78,10 @@ impl TorrentManagerBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start_manager(self) -> anyhow::Result<TorrentManagerHandle> {
|
pub fn start_manager(self) -> anyhow::Result<TorrentManagerHandle> {
|
||||||
TorrentManager::start(
|
TorrentManager::start(
|
||||||
self.torrent,
|
self.info,
|
||||||
|
self.info_hash,
|
||||||
self.output_folder,
|
self.output_folder,
|
||||||
self.overwrite,
|
self.overwrite,
|
||||||
self.only_files,
|
self.only_files,
|
||||||
|
|
@ -84,10 +93,21 @@ impl TorrentManagerBuilder {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TorrentManagerHandle {
|
pub struct TorrentManagerHandle {
|
||||||
manager: TorrentManager,
|
manager: Arc<TorrentManager>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TorrentManagerHandle {
|
impl TorrentManagerHandle {
|
||||||
|
pub fn add_tracker(&self, url: Url) -> bool {
|
||||||
|
let mgr = self.manager.clone();
|
||||||
|
if mgr.trackers.lock().insert(url.clone()) {
|
||||||
|
spawn(format!("tracker monitor {}", url), async move {
|
||||||
|
mgr.single_tracker_monitor(url).await
|
||||||
|
});
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
pub async fn cancel(&self) -> anyhow::Result<()> {
|
pub async fn cancel(&self) -> anyhow::Result<()> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
@ -98,21 +118,24 @@ impl TorrentManagerHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct TorrentManager {
|
struct TorrentManager {
|
||||||
state: Arc<TorrentState>,
|
state: Arc<TorrentState>,
|
||||||
speed_estimator: Arc<SpeedEstimator>,
|
speed_estimator: Arc<SpeedEstimator>,
|
||||||
|
trackers: Mutex<HashSet<Url>>,
|
||||||
force_tracker_interval: Option<Duration>,
|
force_tracker_interval: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_lengths(torrent: &TorrentMetaV1Owned) -> anyhow::Result<Lengths> {
|
fn make_lengths<ByteBuf: Clone + Deref<Target = [u8]>>(
|
||||||
let total_length = torrent.info.iter_file_lengths().sum();
|
torrent: &TorrentMetaV1Info<ByteBuf>,
|
||||||
Lengths::new(total_length, torrent.info.piece_length, None)
|
) -> anyhow::Result<Lengths> {
|
||||||
|
let total_length = torrent.iter_file_lengths().sum();
|
||||||
|
Lengths::new(total_length, torrent.piece_length, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TorrentManager {
|
impl TorrentManager {
|
||||||
pub fn start<P: AsRef<Path>>(
|
pub fn start<P: AsRef<Path>>(
|
||||||
torrent: TorrentMetaV1Owned,
|
info: TorrentMetaV1Info<ByteString>,
|
||||||
|
info_hash: [u8; 20],
|
||||||
out: P,
|
out: P,
|
||||||
overwrite: bool,
|
overwrite: bool,
|
||||||
only_files: Option<Vec<usize>>,
|
only_files: Option<Vec<usize>>,
|
||||||
|
|
@ -121,9 +144,9 @@ impl TorrentManager {
|
||||||
) -> anyhow::Result<TorrentManagerHandle> {
|
) -> anyhow::Result<TorrentManagerHandle> {
|
||||||
let files = {
|
let files = {
|
||||||
let mut files =
|
let mut files =
|
||||||
Vec::<Arc<Mutex<File>>>::with_capacity(torrent.info.iter_file_lengths().count());
|
Vec::<Arc<Mutex<File>>>::with_capacity(info.iter_file_lengths().count());
|
||||||
|
|
||||||
for (path_bits, _) in torrent.info.iter_filenames_and_lengths() {
|
for (path_bits, _) in info.iter_filenames_and_lengths() {
|
||||||
let mut full_path = out.as_ref().to_owned();
|
let mut full_path = out.as_ref().to_owned();
|
||||||
for bit in path_bits.iter_components() {
|
for bit in path_bits.iter_components() {
|
||||||
full_path.push(
|
full_path.push(
|
||||||
|
|
@ -155,12 +178,12 @@ impl TorrentManager {
|
||||||
};
|
};
|
||||||
|
|
||||||
let peer_id = generate_peer_id();
|
let peer_id = generate_peer_id();
|
||||||
let lengths = make_lengths(&torrent).context("unable to compute Lengths from torrent")?;
|
let lengths = make_lengths(&info).context("unable to compute Lengths from torrent")?;
|
||||||
debug!("computed lengths: {:?}", &lengths);
|
debug!("computed lengths: {:?}", &lengths);
|
||||||
|
|
||||||
info!("Doing initial checksum validation, this might take a while...");
|
info!("Doing initial checksum validation, this might take a while...");
|
||||||
let initial_check_results = FileOps::<Sha1>::new(&torrent.info, &files, &lengths)
|
let initial_check_results =
|
||||||
.initial_check(only_files.as_deref())?;
|
FileOps::<Sha1>::new(&info, &files, &lengths).initial_check(only_files.as_deref())?;
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"Initial check results: have {}, needed {}",
|
"Initial check results: have {}, needed {}",
|
||||||
|
|
@ -175,8 +198,8 @@ impl TorrentManager {
|
||||||
);
|
);
|
||||||
|
|
||||||
let state = Arc::new(TorrentState {
|
let state = Arc::new(TorrentState {
|
||||||
info_hash: torrent.info_hash,
|
info_hash,
|
||||||
torrent: torrent.info,
|
torrent: info,
|
||||||
peer_id,
|
peer_id,
|
||||||
locked: Arc::new(RwLock::new(TorrentStateLocked {
|
locked: Arc::new(RwLock::new(TorrentStateLocked {
|
||||||
peers: Default::default(),
|
peers: Default::default(),
|
||||||
|
|
@ -193,14 +216,17 @@ impl TorrentManager {
|
||||||
});
|
});
|
||||||
let estimator = Arc::new(SpeedEstimator::new(5));
|
let estimator = Arc::new(SpeedEstimator::new(5));
|
||||||
|
|
||||||
let mgr = Self {
|
let mgr = Arc::new(Self {
|
||||||
state,
|
state,
|
||||||
speed_estimator: estimator.clone(),
|
speed_estimator: estimator.clone(),
|
||||||
|
trackers: Mutex::new(HashSet::new()),
|
||||||
force_tracker_interval,
|
force_tracker_interval,
|
||||||
};
|
});
|
||||||
|
|
||||||
spawn("tracker monitor", mgr.clone().task_tracker_monitor());
|
spawn("stats printer", {
|
||||||
spawn("stats printer", mgr.clone().stats_printer());
|
let this = mgr.clone();
|
||||||
|
async move { this.stats_printer().await }
|
||||||
|
});
|
||||||
spawn(
|
spawn(
|
||||||
"http api",
|
"http api",
|
||||||
make_and_run_http_api(mgr.state.clone(), estimator.clone()),
|
make_and_run_http_api(mgr.state.clone(), estimator.clone()),
|
||||||
|
|
@ -221,7 +247,7 @@ impl TorrentManager {
|
||||||
Ok(mgr.into_handle())
|
Ok(mgr.into_handle())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn stats_printer(self) -> anyhow::Result<()> {
|
async fn stats_printer(&self) -> anyhow::Result<()> {
|
||||||
loop {
|
loop {
|
||||||
let live_peer_stats = self.state.locked.read().peers.stats();
|
let live_peer_stats = self.state.locked.read().peers.stats();
|
||||||
let seen_peers_count = self.state.locked.read().peers.seen().len();
|
let seen_peers_count = self.state.locked.read().peers.seen().len();
|
||||||
|
|
@ -257,34 +283,7 @@ impl TorrentManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn task_tracker_monitor(self) -> anyhow::Result<()> {
|
fn into_handle(self: Arc<Self>) -> TorrentManagerHandle {
|
||||||
let mut seen_trackers = HashSet::new();
|
|
||||||
let mut tracker_futures = FuturesUnordered::new();
|
|
||||||
let parse_url = |url: &[u8]| -> anyhow::Result<Url> {
|
|
||||||
let url = std::str::from_utf8(url).context("error parsing tracker URL")?;
|
|
||||||
let url = Url::parse(url).context("error parsing tracker URL")?;
|
|
||||||
Ok(url)
|
|
||||||
};
|
|
||||||
for tracker in self.state.torrent.iter_announce() {
|
|
||||||
if seen_trackers.contains(&tracker) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
seen_trackers.insert(tracker);
|
|
||||||
let tracker_url = match parse_url(tracker) {
|
|
||||||
Ok(url) => url,
|
|
||||||
Err(e) => {
|
|
||||||
warn!("ignoring tracker: {:#}", e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
tracker_futures.push(self.clone().single_tracker_monitor(tracker_url));
|
|
||||||
}
|
|
||||||
|
|
||||||
while tracker_futures.next().await.is_some() {}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_handle(self) -> TorrentManagerHandle {
|
|
||||||
TorrentManagerHandle { manager: self }
|
TorrentManagerHandle { manager: self }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -308,7 +307,7 @@ impl TorrentManager {
|
||||||
Ok(response.interval)
|
Ok(response.interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn single_tracker_monitor(self, mut tracker_url: Url) -> anyhow::Result<()> {
|
async fn single_tracker_monitor(&self, mut tracker_url: Url) -> anyhow::Result<()> {
|
||||||
let mut event = Some(TrackerRequestEvent::Started);
|
let mut event = Some(TrackerRequestEvent::Started);
|
||||||
loop {
|
loop {
|
||||||
let request = TrackerRequest {
|
let request = TrackerRequest {
|
||||||
|
|
@ -330,8 +329,7 @@ impl TorrentManager {
|
||||||
let request_query = request.as_querystring();
|
let request_query = request.as_querystring();
|
||||||
tracker_url.set_query(Some(&request_query));
|
tracker_url.set_query(Some(&request_query));
|
||||||
|
|
||||||
let this = self.clone();
|
match self.tracker_one_request(tracker_url.clone()).await {
|
||||||
match this.tracker_one_request(tracker_url.clone()).await {
|
|
||||||
Ok(interval) => {
|
Ok(interval) => {
|
||||||
event = None;
|
event = None;
|
||||||
let interval = self
|
let interval = self
|
||||||
|
|
|
||||||
35
src/main.rs
35
src/main.rs
|
|
@ -7,7 +7,8 @@ use librqbit::{
|
||||||
torrent_manager::TorrentManagerBuilder,
|
torrent_manager::TorrentManagerBuilder,
|
||||||
torrent_metainfo::{torrent_from_bytes, TorrentMetaV1Owned},
|
torrent_metainfo::{torrent_from_bytes, TorrentMetaV1Owned},
|
||||||
};
|
};
|
||||||
use log::info;
|
use log::{info, warn};
|
||||||
|
use reqwest::Url;
|
||||||
|
|
||||||
async fn torrent_from_url(url: &str) -> anyhow::Result<TorrentMetaV1Owned> {
|
async fn torrent_from_url(url: &str) -> anyhow::Result<TorrentMetaV1Owned> {
|
||||||
let response = reqwest::get(url)
|
let response = reqwest::get(url)
|
||||||
|
|
@ -178,7 +179,28 @@ fn main() -> anyhow::Result<()> {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut builder = TorrentManagerBuilder::new(torrent, opts.output_folder);
|
let trackers = torrent
|
||||||
|
.iter_announce()
|
||||||
|
.filter_map(|tracker| {
|
||||||
|
let url = match std::str::from_utf8(tracker.as_ref()) {
|
||||||
|
Ok(url) => url,
|
||||||
|
Err(_) => {
|
||||||
|
warn!("cannot parse tracker url as utf-8, ignoring");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match Url::parse(url) {
|
||||||
|
Ok(url) => Some(url),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("cannot parse tracker URL {}: {}", url, e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut builder =
|
||||||
|
TorrentManagerBuilder::new(torrent.info, torrent.info_hash, opts.output_folder);
|
||||||
builder.overwrite(opts.overwrite).spawner(spawner);
|
builder.overwrite(opts.overwrite).spawner(spawner);
|
||||||
if let Some(only_files) = only_files {
|
if let Some(only_files) = only_files {
|
||||||
builder.only_files(only_files);
|
builder.only_files(only_files);
|
||||||
|
|
@ -188,8 +210,13 @@ fn main() -> anyhow::Result<()> {
|
||||||
builder.force_tracker_interval(Duration::from_secs(interval));
|
builder.force_tracker_interval(Duration::from_secs(interval));
|
||||||
}
|
}
|
||||||
|
|
||||||
let manager_handle = builder.start_manager().await?;
|
let handle = builder.start_manager()?;
|
||||||
manager_handle.wait_until_completed().await?;
|
|
||||||
|
for url in trackers {
|
||||||
|
handle.add_tracker(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
handle.wait_until_completed().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue