Add tracker refresh interval parameter
This commit is contained in:
parent
c2affa8865
commit
2fc225cfa2
3 changed files with 66 additions and 49 deletions
|
|
@ -1,7 +1,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
fs::{File, OpenOptions},
|
fs::{File, OpenOptions},
|
||||||
net::SocketAddr,
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicU64, Ordering},
|
atomic::{AtomicU64, Ordering},
|
||||||
|
|
@ -21,7 +20,6 @@ use crate::{
|
||||||
chunk_tracker::ChunkTracker,
|
chunk_tracker::ChunkTracker,
|
||||||
file_ops::FileOps,
|
file_ops::FileOps,
|
||||||
lengths::Lengths,
|
lengths::Lengths,
|
||||||
peer_connection::{PeerConnection, WriterRequest},
|
|
||||||
spawn_utils::spawn,
|
spawn_utils::spawn,
|
||||||
torrent_metainfo::TorrentMetaV1Owned,
|
torrent_metainfo::TorrentMetaV1Owned,
|
||||||
torrent_state::{AtomicStats, TorrentState, TorrentStateLocked},
|
torrent_state::{AtomicStats, TorrentState, TorrentStateLocked},
|
||||||
|
|
@ -32,6 +30,7 @@ pub struct TorrentManagerBuilder {
|
||||||
overwrite: bool,
|
overwrite: bool,
|
||||||
output_folder: PathBuf,
|
output_folder: PathBuf,
|
||||||
only_files: Option<Vec<usize>>,
|
only_files: Option<Vec<usize>>,
|
||||||
|
force_tracker_interval: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TorrentManagerBuilder {
|
impl TorrentManagerBuilder {
|
||||||
|
|
@ -41,6 +40,7 @@ impl TorrentManagerBuilder {
|
||||||
overwrite: false,
|
overwrite: false,
|
||||||
output_folder: output_folder.as_ref().into(),
|
output_folder: output_folder.as_ref().into(),
|
||||||
only_files: None,
|
only_files: None,
|
||||||
|
force_tracker_interval: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,12 +54,18 @@ impl TorrentManagerBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn force_tracker_interval(&mut self, force_tracker_interval: Duration) -> &mut Self {
|
||||||
|
self.force_tracker_interval = Some(force_tracker_interval);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn start_manager(self) -> anyhow::Result<TorrentManagerHandle> {
|
pub async fn start_manager(self) -> anyhow::Result<TorrentManagerHandle> {
|
||||||
TorrentManager::start(
|
TorrentManager::start(
|
||||||
self.torrent,
|
self.torrent,
|
||||||
self.output_folder,
|
self.output_folder,
|
||||||
self.overwrite,
|
self.overwrite,
|
||||||
self.only_files,
|
self.only_files,
|
||||||
|
self.force_tracker_interval,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +88,8 @@ impl TorrentManagerHandle {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TorrentManager {
|
struct TorrentManager {
|
||||||
inner: Arc<TorrentState>,
|
state: Arc<TorrentState>,
|
||||||
|
force_tracker_interval: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_peer_id() -> [u8; 20] {
|
fn generate_peer_id() -> [u8; 20] {
|
||||||
|
|
@ -103,6 +110,7 @@ impl TorrentManager {
|
||||||
out: P,
|
out: P,
|
||||||
overwrite: bool,
|
overwrite: bool,
|
||||||
only_files: Option<Vec<usize>>,
|
only_files: Option<Vec<usize>>,
|
||||||
|
force_tracker_interval: Option<Duration>,
|
||||||
) -> anyhow::Result<TorrentManagerHandle> {
|
) -> anyhow::Result<TorrentManagerHandle> {
|
||||||
let files = {
|
let files = {
|
||||||
let mut files =
|
let mut files =
|
||||||
|
|
@ -160,7 +168,7 @@ impl TorrentManager {
|
||||||
);
|
);
|
||||||
|
|
||||||
let mgr = Self {
|
let mgr = Self {
|
||||||
inner: Arc::new(TorrentState {
|
state: Arc::new(TorrentState {
|
||||||
info_hash: torrent.info_hash,
|
info_hash: torrent.info_hash,
|
||||||
torrent,
|
torrent,
|
||||||
peer_id,
|
peer_id,
|
||||||
|
|
@ -178,6 +186,7 @@ impl TorrentManager {
|
||||||
needed: initial_check_results.needed_bytes,
|
needed: initial_check_results.needed_bytes,
|
||||||
lengths,
|
lengths,
|
||||||
}),
|
}),
|
||||||
|
force_tracker_interval,
|
||||||
};
|
};
|
||||||
|
|
||||||
spawn("tracker monitor", mgr.clone().task_tracker_monitor());
|
spawn("tracker monitor", mgr.clone().task_tracker_monitor());
|
||||||
|
|
@ -187,18 +196,18 @@ impl TorrentManager {
|
||||||
|
|
||||||
async fn stats_printer(self) -> anyhow::Result<()> {
|
async fn stats_printer(self) -> anyhow::Result<()> {
|
||||||
loop {
|
loop {
|
||||||
let live_peer_stats = self.inner.locked.read().peers.stats();
|
let live_peer_stats = self.state.locked.read().peers.stats();
|
||||||
let seen_peers_count = self.inner.locked.read().peers.seen().len();
|
let seen_peers_count = self.state.locked.read().peers.seen().len();
|
||||||
let have = self.inner.stats.have.load(Ordering::Relaxed);
|
let have = self.state.stats.have.load(Ordering::Relaxed);
|
||||||
let fetched = self.inner.stats.fetched_bytes.load(Ordering::Relaxed);
|
let fetched = self.state.stats.fetched_bytes.load(Ordering::Relaxed);
|
||||||
let needed = self.inner.needed;
|
let needed = self.state.needed;
|
||||||
let downloaded = self
|
let downloaded = self
|
||||||
.inner
|
.state
|
||||||
.stats
|
.stats
|
||||||
.downloaded_and_checked
|
.downloaded_and_checked
|
||||||
.load(Ordering::Relaxed);
|
.load(Ordering::Relaxed);
|
||||||
let remaining = needed - downloaded;
|
let remaining = needed - downloaded;
|
||||||
let uploaded = self.inner.stats.uploaded.load(Ordering::Relaxed);
|
let uploaded = self.state.stats.uploaded.load(Ordering::Relaxed);
|
||||||
let downloaded_pct = if downloaded == needed {
|
let downloaded_pct = if downloaded == needed {
|
||||||
100f64
|
100f64
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -229,7 +238,7 @@ impl TorrentManager {
|
||||||
let url = Url::parse(url).context("error parsing tracker URL")?;
|
let url = Url::parse(url).context("error parsing tracker URL")?;
|
||||||
Ok(url)
|
Ok(url)
|
||||||
};
|
};
|
||||||
for tracker in self.inner.torrent.iter_announce() {
|
for tracker in self.state.torrent.iter_announce() {
|
||||||
if seen_trackers.contains(&tracker) {
|
if seen_trackers.contains(&tracker) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -258,7 +267,7 @@ impl TorrentManager {
|
||||||
let response = crate::serde_bencode::from_bytes::<CompactTrackerResponse>(&bytes)?;
|
let response = crate::serde_bencode::from_bytes::<CompactTrackerResponse>(&bytes)?;
|
||||||
|
|
||||||
for peer in response.peers.iter_sockaddrs() {
|
for peer in response.peers.iter_sockaddrs() {
|
||||||
self.add_peer(peer);
|
self.state.add_peer(peer);
|
||||||
}
|
}
|
||||||
Ok(response.interval)
|
Ok(response.interval)
|
||||||
}
|
}
|
||||||
|
|
@ -267,12 +276,12 @@ impl TorrentManager {
|
||||||
let mut event = Some(TrackerRequestEvent::Started);
|
let mut event = Some(TrackerRequestEvent::Started);
|
||||||
loop {
|
loop {
|
||||||
let request = TrackerRequest {
|
let request = TrackerRequest {
|
||||||
info_hash: self.inner.torrent.info_hash,
|
info_hash: self.state.torrent.info_hash,
|
||||||
peer_id: self.inner.peer_id,
|
peer_id: self.state.peer_id,
|
||||||
port: 6778,
|
port: 6778,
|
||||||
uploaded: self.inner.get_uploaded(),
|
uploaded: self.state.get_uploaded(),
|
||||||
downloaded: self.inner.get_downloaded(),
|
downloaded: self.state.get_downloaded(),
|
||||||
left: self.inner.get_left_to_download(),
|
left: self.state.get_left_to_download(),
|
||||||
compact: true,
|
compact: true,
|
||||||
no_peer_id: false,
|
no_peer_id: false,
|
||||||
event,
|
event,
|
||||||
|
|
@ -289,14 +298,15 @@ impl TorrentManager {
|
||||||
match this.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 = 30;
|
let interval = self
|
||||||
let duration = Duration::from_secs(interval);
|
.force_tracker_interval
|
||||||
|
.unwrap_or(Duration::from_secs(interval));
|
||||||
debug!(
|
debug!(
|
||||||
"sleeping for {:?} after calling tracker {}",
|
"sleeping for {:?} after calling tracker {}",
|
||||||
duration,
|
interval,
|
||||||
tracker_url.host().unwrap()
|
tracker_url.host().unwrap()
|
||||||
);
|
);
|
||||||
tokio::time::sleep(duration).await;
|
tokio::time::sleep(interval).await;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("error calling the tracker {}: {:#}", tracker_url, e);
|
debug!("error calling the tracker {}: {:#}", tracker_url, e);
|
||||||
|
|
@ -305,27 +315,4 @@ impl TorrentManager {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_peer(&self, addr: SocketAddr) {
|
|
||||||
let (out_tx, out_rx) = tokio::sync::mpsc::channel::<WriterRequest>(1);
|
|
||||||
let handle = match self
|
|
||||||
.inner
|
|
||||||
.locked
|
|
||||||
.write()
|
|
||||||
.peers
|
|
||||||
.add_if_not_seen(addr, out_tx)
|
|
||||||
{
|
|
||||||
Some(handle) => handle,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let peer_connection = PeerConnection::new(self.inner.clone());
|
|
||||||
spawn(format!("manage_peer({})", handle), async move {
|
|
||||||
if let Err(e) = peer_connection.manage_peer(addr, handle, out_rx).await {
|
|
||||||
debug!("error managing peer {}: {:#}", handle, e)
|
|
||||||
};
|
|
||||||
peer_connection.into_state().drop_peer(handle);
|
|
||||||
Ok::<_, anyhow::Error>(())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,16 +9,16 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures::{stream::FuturesUnordered, StreamExt};
|
use futures::{stream::FuturesUnordered, StreamExt};
|
||||||
use log::{trace, warn};
|
use log::{debug, trace, warn};
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::{channel, Sender};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chunk_tracker::ChunkTracker,
|
chunk_tracker::ChunkTracker,
|
||||||
file_ops::FileOps,
|
file_ops::FileOps,
|
||||||
lengths::{ChunkInfo, Lengths, ValidPieceIndex},
|
lengths::{ChunkInfo, Lengths, ValidPieceIndex},
|
||||||
peer_binary_protocol::{Handshake, Message},
|
peer_binary_protocol::{Handshake, Message},
|
||||||
peer_connection::WriterRequest,
|
peer_connection::{PeerConnection, WriterRequest},
|
||||||
peer_state::{LivePeerState, PeerState},
|
peer_state::{LivePeerState, PeerState},
|
||||||
spawn_utils::spawn,
|
spawn_utils::spawn,
|
||||||
torrent_metainfo::TorrentMetaV1Owned,
|
torrent_metainfo::TorrentMetaV1Owned,
|
||||||
|
|
@ -333,4 +333,21 @@ impl TorrentState {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_peer(self: &Arc<Self>, addr: SocketAddr) {
|
||||||
|
let (out_tx, out_rx) = channel::<WriterRequest>(1);
|
||||||
|
let handle = match self.locked.write().peers.add_if_not_seen(addr, out_tx) {
|
||||||
|
Some(handle) => handle,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let peer_connection = PeerConnection::new(self.clone());
|
||||||
|
spawn(format!("manage_peer({})", handle), async move {
|
||||||
|
if let Err(e) = peer_connection.manage_peer(addr, handle, out_rx).await {
|
||||||
|
debug!("error managing peer {}: {:#}", handle, e)
|
||||||
|
};
|
||||||
|
peer_connection.into_state().drop_peer(handle);
|
||||||
|
Ok::<_, anyhow::Error>(())
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
src/main.rs
15
src/main.rs
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{fs::File, io::Read};
|
use std::{fs::File, io::Read, time::Duration};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::Clap;
|
use clap::Clap;
|
||||||
|
|
@ -60,6 +60,8 @@ struct Opts {
|
||||||
/// The filename of the .torrent file.
|
/// The filename of the .torrent file.
|
||||||
output_folder: String,
|
output_folder: String,
|
||||||
|
|
||||||
|
/// If set, only the file whose filename matching this regex will
|
||||||
|
/// be downloaded
|
||||||
#[clap(short = 'r', long = "filename-re")]
|
#[clap(short = 'r', long = "filename-re")]
|
||||||
only_files_matching_regex: Option<String>,
|
only_files_matching_regex: Option<String>,
|
||||||
|
|
||||||
|
|
@ -71,8 +73,15 @@ struct Opts {
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
list: bool,
|
list: bool,
|
||||||
|
|
||||||
|
/// The loglevel
|
||||||
#[clap(arg_enum, short = 'v')]
|
#[clap(arg_enum, short = 'v')]
|
||||||
log_level: Option<LogLevel>,
|
log_level: Option<LogLevel>,
|
||||||
|
|
||||||
|
/// The interval in seconds to poll trackers.
|
||||||
|
/// Trackers send the refresh interval when we connect to them. Often this is
|
||||||
|
/// pretty big, e.g. 30 minutes. This can force a certain value.
|
||||||
|
#[clap(short = 'i', long = "tracker-refresh-interval")]
|
||||||
|
force_tracker_interval: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_only_files(
|
fn compute_only_files(
|
||||||
|
|
@ -162,6 +171,10 @@ fn main() -> anyhow::Result<()> {
|
||||||
builder.only_files(only_files);
|
builder.only_files(only_files);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(interval) = opts.force_tracker_interval {
|
||||||
|
builder.force_tracker_interval(Duration::from_secs(interval));
|
||||||
|
}
|
||||||
|
|
||||||
let manager_handle = builder.start_manager().await?;
|
let manager_handle = builder.start_manager().await?;
|
||||||
manager_handle.wait_until_completed().await?;
|
manager_handle.wait_until_completed().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue