use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::time::Duration;
use crate::error::Result;
use crate::resume;
use super::engine;
use super::metainfo::Metainfo;
use super::picker::Bitfield;
use super::seed;
use super::storage::Storage;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SeedMode {
Off,
Forever,
UntilRatio(f64),
}
#[derive(Debug, Clone)]
pub struct TorrentOptions {
pub peer_id: [u8; 20],
pub listen_port: u16,
pub connect_timeout: Duration,
pub peer_timeout: Duration,
pub seed: SeedMode,
pub verbosity: u8,
pub recheck: bool,
}
impl Default for TorrentOptions {
fn default() -> Self {
TorrentOptions {
peer_id: [0u8; 20],
listen_port: 6881,
connect_timeout: Duration::from_secs(10),
peer_timeout: Duration::from_secs(30),
seed: SeedMode::Off,
verbosity: 0,
recheck: false,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Progress {
pub downloaded: u64,
pub total: u64,
pub pieces_complete: usize,
pub num_pieces: usize,
pub uploaded: u64,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Stats {
pub downloaded: u64,
pub uploaded: u64,
}
pub fn download(
meta: &Metainfo,
layout: Vec<(PathBuf, u64)>,
peers: &[SocketAddr],
opts: &TorrentOptions,
progress: &mut dyn FnMut(&Progress),
) -> Result<Stats> {
let peer_id = if opts.peer_id == [0u8; 20] {
super::generate_peer_id()?
} else {
opts.peer_id
};
let num_pieces = meta.num_pieces();
let single = layout.len() == 1;
let (final_single, storage_layout, state_path) = if single {
let (final_path, len) = layout[0].clone();
let part = resume::part_path(&final_path);
(Some(final_path), vec![(part.clone(), len)], part)
} else {
let sidecar = topdir(&layout, &meta.name).join(".rsurlpart");
(None, layout.clone(), sidecar)
};
let real_size = if single { meta.total_length } else { 0 };
let mut storage = Storage::create(storage_layout, meta.piece_length, meta.pieces.clone())?;
if opts.recheck {
if opts.verbosity >= 1 {
eprintln!("* rechecking on-disk data ({num_pieces} pieces)…");
}
storage.recheck();
} else if let Ok(Some(st)) = resume::read_state(&state_path) {
if st.kind == resume::Kind::Torrent {
if let Some(bits) = parse_meta(&st.meta, meta.info_hash, num_pieces) {
storage.restore_have(&bits);
}
}
}
let info_hash = meta.info_hash;
let save_path = state_path.clone();
let mut save = |bf: &Bitfield| {
let _ = resume::write_state(
&save_path,
real_size,
resume::Kind::Torrent,
&encode_meta(info_hash, bf),
);
};
let stats = match engine::run(
meta,
&mut storage,
peers,
peer_id,
opts,
progress,
&mut save,
) {
Ok(s) => s,
Err(e) => {
save(storage.bitfield());
return Err(e);
}
};
if let Some(final_path) = final_single {
drop(storage); resume::finalize(&state_path, &final_path, meta.total_length)?;
if opts.seed == SeedMode::Off {
return Ok(stats);
}
let mut ss = Storage::create(
vec![(final_path, meta.total_length)],
meta.piece_length,
meta.pieces.clone(),
)?;
ss.restore_have(&full_bitfield(num_pieces));
seed::run(meta, ss, peer_id, opts, stats, progress)
} else {
let _ = std::fs::remove_file(&state_path); if opts.seed == SeedMode::Off {
return Ok(stats);
}
seed::run(meta, storage, peer_id, opts, stats, progress)
}
}
pub fn download_window(
meta: &Metainfo,
out: PathBuf,
peers: &[SocketAddr],
opts: &TorrentOptions,
start: u64,
end: u64,
progress: &mut dyn FnMut(&Progress),
) -> Result<Stats> {
let peer_id = if opts.peer_id == [0u8; 20] {
super::generate_peer_id()?
} else {
opts.peer_id
};
let mut storage = Storage::create_window(
out,
meta.piece_length,
meta.pieces.clone(),
meta.total_length,
start,
end,
)?;
let mut nosave = |_: &Bitfield| {};
engine::run(
meta,
&mut storage,
peers,
peer_id,
opts,
progress,
&mut nosave,
)
}
fn encode_meta(info_hash: [u8; 20], bf: &Bitfield) -> Vec<u8> {
let mut v = Vec::with_capacity(20 + bf.as_bytes().len());
v.extend_from_slice(&info_hash);
v.extend_from_slice(bf.as_bytes());
v
}
fn parse_meta(meta: &[u8], expect: [u8; 20], num_pieces: usize) -> Option<Bitfield> {
if meta.len() < 20 || meta[..20] != expect {
return None;
}
Some(Bitfield::from_bytes(&meta[20..], num_pieces))
}
fn full_bitfield(num_pieces: usize) -> Bitfield {
let mut bf = Bitfield::new(num_pieces);
for i in 0..num_pieces {
bf.set(i);
}
bf
}
fn topdir(layout: &[(PathBuf, u64)], name: &str) -> PathBuf {
let want: &std::ffi::OsStr = name.as_ref();
let first = layout[0].0.as_path();
let mut cur = first;
while let Some(parent) = cur.parent() {
if parent.file_name() == Some(want) {
return parent.to_path_buf();
}
cur = parent;
}
first.parent().unwrap_or(Path::new(".")).to_path_buf()
}