public API

This commit is contained in:
programmer programmer 2022-10-11 16:14:46 +02:00
parent 984543fcb9
commit ea3b22fccc
18 changed files with 520 additions and 425 deletions

657
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ const EI: u64 = PI << 10;
#[derive(Debug, PartialEq, Copy, Clone, PartialOrd, Ord, Eq, Serialize, Deserialize, Default)] #[derive(Debug, PartialEq, Copy, Clone, PartialOrd, Ord, Eq, Serialize, Deserialize, Default)]
#[serde(transparent)] #[serde(transparent)]
pub(crate) struct Bytes(pub(crate) u64); pub struct Bytes(pub(crate) u64);
impl Bytes { impl Bytes {
pub(crate) fn kib() -> Self { pub(crate) fn kib() -> Self {

View File

@ -1,6 +1,6 @@
use crate::common::*; use crate::common::*;
pub(crate) struct Env { pub struct Env {
args: Vec<OsString>, args: Vec<OsString>,
dir: PathBuf, dir: PathBuf,
input: Box<dyn InputStream>, input: Box<dyn InputStream>,

View File

@ -2,7 +2,7 @@ use crate::common::*;
#[derive(Debug, Snafu)] #[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))] #[snafu(visibility(pub(crate)))]
pub(crate) enum Error { pub enum Error {
#[snafu(display("Failed to parse announce URL: {}", source))] #[snafu(display("Failed to parse announce URL: {}", source))]
AnnounceUrlParse { source: url::ParseError }, AnnounceUrlParse { source: url::ParseError },
#[snafu(display("Failed to parse byte count `{}`: {}", text, source))] #[snafu(display("Failed to parse byte count `{}`: {}", text, source))]

View File

@ -1,7 +1,7 @@
use crate::common::*; use crate::common::*;
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub(crate) struct HostPort { pub struct HostPort {
host: Host, host: Host,
port: u16, port: u16,
} }

View File

@ -2,7 +2,7 @@ use crate::common::*;
#[derive(Debug, Snafu)] #[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))] #[snafu(visibility(pub(crate)))]
pub(crate) enum HostPortParseError { pub enum HostPortParseError {
#[snafu(display("Failed to parse host `{}`: {}", text, source))] #[snafu(display("Failed to parse host `{}`: {}", text, source))]
Host { Host {
text: String, text: String,

View File

@ -1,22 +1,22 @@
use crate::common::*; use crate::common::*;
#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] #[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
pub(crate) struct Info { pub struct Info {
#[serde( #[serde(
skip_serializing_if = "Option::is_none", skip_serializing_if = "Option::is_none",
default, default,
with = "unwrap_or_skip" with = "unwrap_or_skip"
)] )]
pub(crate) private: Option<bool>, pub private: Option<bool>,
#[serde(rename = "piece length")] #[serde(rename = "piece length")]
pub(crate) piece_length: Bytes, pub piece_length: Bytes,
pub(crate) name: String, pub name: String,
#[serde( #[serde(
skip_serializing_if = "Option::is_none", skip_serializing_if = "Option::is_none",
default, default,
with = "unwrap_or_skip" with = "unwrap_or_skip"
)] )]
pub(crate) source: Option<String>, pub source: Option<String>,
pub(crate) pieces: PieceList, pub(crate) pieces: PieceList,
#[serde(flatten)] #[serde(flatten)]
pub(crate) mode: Mode, pub(crate) mode: Mode,
@ -26,7 +26,7 @@ pub(crate) struct Info {
with = "unwrap_or_skip", with = "unwrap_or_skip",
rename = "update-url" rename = "update-url"
)] )]
pub(crate) update_url: Option<Url>, pub update_url: Option<Url>,
} }
impl Info { impl Info {

View File

@ -1,12 +1,12 @@
use crate::common::*; use crate::common::*;
#[derive(Debug, Eq, PartialEq, Copy, Clone)] #[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub(crate) struct Infohash { pub struct Infohash {
inner: Sha1Digest, pub inner: Sha1Digest,
} }
impl Infohash { impl Infohash {
pub(crate) fn from_input(input: &Input) -> Result<Infohash, Error> { pub fn from_input(input: &Input) -> Result<Infohash, Error> {
let value = Value::from_bencode(input.data()).map_err(|error| Error::MetainfoDecode { let value = Value::from_bencode(input.data()).map_err(|error| Error::MetainfoDecode {
input: input.source().clone(), input: input.source().clone(),
error, error,
@ -43,7 +43,7 @@ impl Infohash {
} }
} }
pub(crate) fn from_bencoded_info_dict(info: &[u8]) -> Infohash { pub fn from_bencoded_info_dict(info: &[u8]) -> Infohash {
Infohash { Infohash {
inner: Sha1Digest::from_data(info), inner: Sha1Digest::from_data(info),
} }

View File

@ -1,20 +1,20 @@
use crate::common::*; use crate::common::*;
pub(crate) struct Input { pub struct Input {
source: InputTarget, source: InputTarget,
data: Vec<u8>, data: Vec<u8>,
} }
impl Input { impl Input {
pub(crate) fn new(source: InputTarget, data: Vec<u8>) -> Input { pub fn new(source: InputTarget, data: Vec<u8>) -> Input {
Self { source, data } Self { source, data }
} }
pub(crate) fn data(&self) -> &[u8] { pub fn data(&self) -> &[u8] {
&self.data &self.data
} }
pub(crate) fn source(&self) -> &InputTarget { pub fn source(&self) -> &InputTarget {
&self.source &self.source
} }

View File

@ -1,13 +1,13 @@
use crate::common::*; use crate::common::*;
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
pub(crate) enum InputTarget { pub enum InputTarget {
Path(PathBuf), Path(PathBuf),
Stdin, Stdin,
} }
impl InputTarget { impl InputTarget {
pub(crate) fn try_from_os_str(text: &OsStr) -> Result<Self, OsString> { pub fn try_from_os_str(text: &OsStr) -> Result<Self, OsString> {
text text
.try_into() .try_into()
.map_err(|err: Error| OsString::from(err.to_string())) .map_err(|err: Error| OsString::from(err.to_string()))

View File

@ -41,85 +41,85 @@
#[cfg(test)] #[cfg(test)]
#[macro_use] #[macro_use]
mod assert_matches; pub mod assert_matches;
#[macro_use] #[macro_use]
mod errln; pub mod errln;
#[macro_use] #[macro_use]
mod err; pub mod err;
#[macro_use] #[macro_use]
mod out; pub mod out;
#[macro_use] #[macro_use]
mod outln; pub mod outln;
#[cfg(test)] #[cfg(test)]
#[macro_use] #[macro_use]
mod test_env; pub mod test_env;
#[cfg(test)] #[cfg(test)]
mod test_env_builder; pub mod test_env_builder;
#[cfg(test)] #[cfg(test)]
mod capture; pub mod capture;
mod arguments; pub mod arguments;
mod bytes; pub mod bytes;
mod common; pub mod common;
mod consts; pub mod consts;
mod env; pub mod env;
mod error; pub mod error;
mod file_error; pub mod file_error;
mod file_info; pub mod file_info;
mod file_path; pub mod file_path;
mod file_status; pub mod file_status;
mod files; pub mod files;
mod hasher; pub mod hasher;
mod host_port; pub mod host_port;
mod host_port_parse_error; pub mod host_port_parse_error;
mod info; pub mod info;
mod infohash; pub mod infohash;
mod input; pub mod input;
mod input_stream; pub mod input_stream;
mod input_target; pub mod input_target;
mod into_u64; pub mod into_u64;
mod into_usize; pub mod into_usize;
mod invariant; pub mod invariant;
mod lint; pub mod lint;
mod linter; pub mod linter;
mod magnet_link; pub mod magnet_link;
mod magnet_link_parse_error; pub mod magnet_link_parse_error;
mod md5_digest; pub mod md5_digest;
mod metainfo; pub mod metainfo;
mod metainfo_error; pub mod metainfo_error;
mod mode; pub mod mode;
mod options; pub mod options;
mod output_stream; pub mod output_stream;
mod output_target; pub mod output_target;
mod piece_length_picker; pub mod piece_length_picker;
mod piece_list; pub mod piece_list;
mod platform; pub mod platform;
mod platform_interface; pub mod platform_interface;
mod print; pub mod print;
mod reckoner; pub mod reckoner;
mod run; pub mod run;
mod sha1_digest; pub mod sha1_digest;
mod shell; pub mod shell;
mod sort_key; pub mod sort_key;
mod sort_order; pub mod sort_order;
mod sort_spec; pub mod sort_spec;
mod status; pub mod status;
mod step; pub mod step;
mod style; pub mod style;
mod subcommand; pub mod subcommand;
mod table; pub mod table;
mod torrent_summary; pub mod torrent_summary;
mod use_color; pub mod use_color;
mod verifier; pub mod verifier;
mod walker; pub mod walker;
mod xor_args; pub mod xor_args;
#[cfg(feature = "bench")] #[cfg(feature = "bench")]
pub mod bench; pub mod bench;

View File

@ -1,17 +1,17 @@
use crate::common::*; use crate::common::*;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub(crate) struct MagnetLink { pub struct MagnetLink {
infohash: Infohash, pub infohash: Infohash,
name: Option<String>, pub name: Option<String>,
peers: Vec<HostPort>, pub peers: Vec<HostPort>,
trackers: Vec<Url>, pub trackers: Vec<Url>,
indices: BTreeSet<u64>, pub indices: BTreeSet<u64>,
} }
impl MagnetLink { impl MagnetLink {
/// See `Info::infohash_lossy` for details on when this function is lossy. /// See `Info::infohash_lossy` for details on when this function is lossy.
pub(crate) fn from_metainfo_lossy(metainfo: &Metainfo) -> Result<MagnetLink> { pub fn from_metainfo_lossy(metainfo: &Metainfo) -> Result<MagnetLink> {
let mut link = Self::with_infohash(metainfo.infohash_lossy()?); let mut link = Self::with_infohash(metainfo.infohash_lossy()?);
link.set_name(metainfo.info.name.clone()); link.set_name(metainfo.info.name.clone());
@ -23,7 +23,7 @@ impl MagnetLink {
Ok(link) Ok(link)
} }
pub(crate) fn with_infohash(infohash: Infohash) -> Self { pub fn with_infohash(infohash: Infohash) -> Self {
MagnetLink { MagnetLink {
infohash, infohash,
name: None, name: None,
@ -34,23 +34,23 @@ impl MagnetLink {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn set_name(&mut self, name: impl Into<String>) { pub fn set_name(&mut self, name: impl Into<String>) {
self.name = Some(name.into()); self.name = Some(name.into());
} }
pub(crate) fn add_peer(&mut self, peer: HostPort) { pub fn add_peer(&mut self, peer: HostPort) {
self.peers.push(peer); self.peers.push(peer);
} }
pub(crate) fn add_tracker(&mut self, tracker: Url) { pub fn add_tracker(&mut self, tracker: Url) {
self.trackers.push(tracker); self.trackers.push(tracker);
} }
pub(crate) fn add_index(&mut self, index: u64) { pub fn add_index(&mut self, index: u64) {
self.indices.insert(index); self.indices.insert(index);
} }
pub(crate) fn to_url(&self) -> Url { pub fn to_url(&self) -> Url {
let mut url = Url::parse("magnet:").invariant_unwrap("`magnet:` is valid URL"); let mut url = Url::parse("magnet:").invariant_unwrap("`magnet:` is valid URL");
let mut query = format!("xt=urn:btih:{}", self.infohash); let mut query = format!("xt=urn:btih:{}", self.infohash);
@ -85,7 +85,7 @@ impl MagnetLink {
url url
} }
fn parse(text: &str) -> Result<Self, MagnetLinkParseError> { pub fn parse(text: &str) -> Result<Self, MagnetLinkParseError> {
let url = Url::parse(&text).context(magnet_link_parse_error::Url)?; let url = Url::parse(&text).context(magnet_link_parse_error::Url)?;
if url.scheme() != "magnet" { if url.scheme() != "magnet" {

View File

@ -2,7 +2,7 @@ use crate::common::*;
#[derive(Debug, Snafu)] #[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))] #[snafu(visibility(pub(crate)))]
pub(crate) enum MagnetLinkParseError { pub enum MagnetLinkParseError {
#[snafu(display("Failed to parse hex string `{}`: {}", text, source))] #[snafu(display("Failed to parse hex string `{}`: {}", text, source))]
HexParse { HexParse {
text: String, text: String,

View File

@ -1,68 +1,68 @@
use crate::common::*; use crate::common::*;
#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] #[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
pub(crate) struct Metainfo { pub struct Metainfo {
#[serde( #[serde(
skip_serializing_if = "Option::is_none", skip_serializing_if = "Option::is_none",
default, default,
with = "unwrap_or_skip" with = "unwrap_or_skip"
)] )]
pub(crate) announce: Option<String>, pub announce: Option<String>,
#[serde( #[serde(
rename = "announce-list", rename = "announce-list",
skip_serializing_if = "Option::is_none", skip_serializing_if = "Option::is_none",
default, default,
with = "unwrap_or_skip" with = "unwrap_or_skip"
)] )]
pub(crate) announce_list: Option<Vec<Vec<String>>>, pub announce_list: Option<Vec<Vec<String>>>,
#[serde( #[serde(
skip_serializing_if = "Option::is_none", skip_serializing_if = "Option::is_none",
default, default,
with = "unwrap_or_skip" with = "unwrap_or_skip"
)] )]
pub(crate) comment: Option<String>, pub comment: Option<String>,
#[serde( #[serde(
rename = "created by", rename = "created by",
skip_serializing_if = "Option::is_none", skip_serializing_if = "Option::is_none",
default, default,
with = "unwrap_or_skip" with = "unwrap_or_skip"
)] )]
pub(crate) created_by: Option<String>, pub created_by: Option<String>,
#[serde( #[serde(
rename = "creation date", rename = "creation date",
skip_serializing_if = "Option::is_none", skip_serializing_if = "Option::is_none",
default, default,
with = "unwrap_or_skip" with = "unwrap_or_skip"
)] )]
pub(crate) creation_date: Option<u64>, pub creation_date: Option<u64>,
#[serde( #[serde(
skip_serializing_if = "Option::is_none", skip_serializing_if = "Option::is_none",
default, default,
with = "unwrap_or_skip" with = "unwrap_or_skip"
)] )]
pub(crate) encoding: Option<String>, pub encoding: Option<String>,
pub(crate) info: Info, pub info: Info,
#[serde( #[serde(
skip_serializing_if = "Option::is_none", skip_serializing_if = "Option::is_none",
default, default,
with = "unwrap_or_skip" with = "unwrap_or_skip"
)] )]
pub(crate) nodes: Option<Vec<HostPort>>, pub nodes: Option<Vec<HostPort>>,
} }
impl Metainfo { impl Metainfo {
pub(crate) fn from_input(input: &Input) -> Result<Metainfo> { pub fn from_input(input: &Input) -> Result<Metainfo> {
Self::deserialize(input.source(), input.data()) Self::deserialize(input.source(), input.data())
} }
pub(crate) fn deserialize(source: &InputTarget, data: &[u8]) -> Result<Metainfo, Error> { pub fn deserialize(source: &InputTarget, data: &[u8]) -> Result<Metainfo, Error> {
let metainfo = bendy::serde::de::from_bytes(&data).context(error::MetainfoDeserialize { let metainfo = bendy::serde::de::from_bytes(&data).context(error::MetainfoDeserialize {
input: source.clone(), input: source.clone(),
})?; })?;
Ok(metainfo) Ok(metainfo)
} }
pub(crate) fn serialize(&self) -> Result<Vec<u8>, Error> { pub fn serialize(&self) -> Result<Vec<u8>, Error> {
bendy::serde::ser::to_bytes(&self).context(error::MetainfoSerialize) bendy::serde::ser::to_bytes(&self).context(error::MetainfoSerialize)
} }
@ -99,7 +99,7 @@ impl Metainfo {
self.info.content_size() self.info.content_size()
} }
pub(crate) fn trackers<'a>(&'a self) -> impl Iterator<Item = Result<Url>> + 'a { pub fn trackers<'a>(&'a self) -> impl Iterator<Item = Result<Url>> + 'a {
let mut seen = HashSet::new(); let mut seen = HashSet::new();
iter::once(&self.announce) iter::once(&self.announce)
.flatten() .flatten()

View File

@ -1,7 +1,7 @@
use crate::common::*; use crate::common::*;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub(crate) enum MetainfoError { pub enum MetainfoError {
Type, Type,
InfoMissing, InfoMissing,
InfoType, InfoType,

View File

@ -1,22 +1,22 @@
use crate::common::*; use crate::common::*;
#[derive(Debug, Eq, PartialEq, Copy, Clone)] #[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub(crate) struct Sha1Digest { pub struct Sha1Digest {
bytes: [u8; Self::LENGTH], pub bytes: [u8; Self::LENGTH],
} }
impl Sha1Digest { impl Sha1Digest {
pub(crate) const LENGTH: usize = 20; pub const LENGTH: usize = 20;
pub(crate) fn from_bytes(bytes: [u8; Self::LENGTH]) -> Self { pub fn from_bytes(bytes: [u8; Self::LENGTH]) -> Self {
Sha1Digest { bytes } Sha1Digest { bytes }
} }
pub(crate) fn bytes(self) -> [u8; Self::LENGTH] { pub fn bytes(self) -> [u8; Self::LENGTH] {
self.bytes self.bytes
} }
pub(crate) fn from_data(data: impl AsRef<[u8]>) -> Self { pub fn from_data(data: impl AsRef<[u8]>) -> Self {
Sha1::from(data).digest().into() Sha1::from(data).digest().into()
} }
} }

View File

@ -4,7 +4,7 @@ use structopt::clap;
#[derive(Copy, Clone, EnumVariantNames, IntoStaticStr, EnumString, EnumIter, Debug)] #[derive(Copy, Clone, EnumVariantNames, IntoStaticStr, EnumString, EnumIter, Debug)]
#[strum(serialize_all = "kebab-case")] #[strum(serialize_all = "kebab-case")]
pub(crate) enum Shell { pub enum Shell {
Zsh, Zsh,
Bash, Bash,
Fish, Fish,

View File

@ -1,30 +1,30 @@
use crate::common::*; use crate::common::*;
pub(crate) struct TorrentSummary { pub struct TorrentSummary {
infohash: Infohash, infohash: Infohash,
metainfo: Metainfo, metainfo: Metainfo,
size: Bytes, size: Bytes,
} }
#[derive(Serialize)] #[derive(Serialize)]
pub(crate) struct TorrentSummaryJson { pub struct TorrentSummaryJson {
name: String, pub name: String,
comment: Option<String>, pub comment: Option<String>,
creation_date: Option<u64>, pub creation_date: Option<u64>,
created_by: Option<String>, pub created_by: Option<String>,
source: Option<String>, pub source: Option<String>,
info_hash: String, pub info_hash: String,
torrent_size: u64, pub torrent_size: u64,
content_size: u64, pub content_size: u64,
private: bool, pub private: bool,
tracker: Option<String>, pub tracker: Option<String>,
announce_list: Vec<Vec<String>>, pub announce_list: Vec<Vec<String>>,
update_url: Option<String>, pub update_url: Option<String>,
dht_nodes: Vec<String>, pub dht_nodes: Vec<String>,
piece_size: u64, pub piece_size: u64,
piece_count: usize, pub piece_count: usize,
file_count: usize, pub file_count: usize,
files: Vec<String>, pub files: Vec<String>,
} }
impl TorrentSummary { impl TorrentSummary {
@ -37,14 +37,14 @@ impl TorrentSummary {
} }
/// See `Info::infohash_lossy` for details on when this function is lossy. /// See `Info::infohash_lossy` for details on when this function is lossy.
pub(crate) fn from_metainfo_lossy(metainfo: Metainfo) -> Result<Self> { pub fn from_metainfo_lossy(metainfo: Metainfo) -> Result<Self> {
let bytes = metainfo.serialize()?; let bytes = metainfo.serialize()?;
let size = Bytes(bytes.len().into_u64()); let size = Bytes(bytes.len().into_u64());
let infohash = metainfo.infohash_lossy()?; let infohash = metainfo.infohash_lossy()?;
Ok(Self::new(metainfo, infohash, size)) Ok(Self::new(metainfo, infohash, size))
} }
pub(crate) fn from_input(input: &Input) -> Result<Self> { pub fn from_input(input: &Input) -> Result<Self> {
let metainfo = Metainfo::from_input(input)?; let metainfo = Metainfo::from_input(input)?;
let infohash = Infohash::from_input(input)?; let infohash = Infohash::from_input(input)?;
let size = Bytes(input.data().len().into_u64()); let size = Bytes(input.data().len().into_u64());
@ -52,7 +52,7 @@ impl TorrentSummary {
Ok(Self::new(metainfo, infohash, size)) Ok(Self::new(metainfo, infohash, size))
} }
pub(crate) fn write(&self, env: &mut Env) -> Result<()> { pub fn write(&self, env: &mut Env) -> Result<()> {
let table = self.table(); let table = self.table();
if env.out().is_term() { if env.out().is_term() {
@ -168,14 +168,14 @@ impl TorrentSummary {
table table
} }
pub(crate) fn write_json(&self, env: &mut Env) -> Result<()> { pub fn write_json(&self, env: &mut Env) -> Result<()> {
let data = self.torrent_summary_data(); let data = self.torrent_summary_data();
let json = serde_json::to_string(&data).context(error::JsonSerialize)?; let json = serde_json::to_string(&data).context(error::JsonSerialize)?;
outln!(env, "{}", json)?; outln!(env, "{}", json)?;
Ok(()) Ok(())
} }
fn torrent_summary_data(&self) -> TorrentSummaryJson { pub fn torrent_summary_data(&self) -> TorrentSummaryJson {
let (file_count, files) = match &self.metainfo.info.mode { let (file_count, files) = match &self.metainfo.info.mode {
Mode::Single { .. } => (1, vec![self.metainfo.info.name.clone()]), Mode::Single { .. } => (1, vec![self.metainfo.info.name.clone()]),
Mode::Multiple { files } => ( Mode::Multiple { files } => (