use crate::common::*; pub(crate) struct TorrentSummary { infohash: Infohash, metainfo: Metainfo, size: Bytes, } #[derive(Serialize)] pub(crate) struct TorrentSummaryJson { name: String, comment: Option, creation_date: Option, created_by: Option, source: Option, info_hash: String, torrent_size: u64, content_size: u64, private: bool, tracker: Option, announce_list: Vec>, update_url: Option, dht_nodes: Vec, piece_size: u64, piece_count: usize, file_count: usize, files: Vec, } impl TorrentSummary { fn new(metainfo: Metainfo, infohash: Infohash, size: Bytes) -> Self { Self { infohash, metainfo, size, } } /// See `Info::infohash_lossy` for details on when this function is lossy. pub(crate) fn from_metainfo_lossy(metainfo: Metainfo) -> Result { let bytes = metainfo.serialize()?; let size = Bytes(bytes.len().into_u64()); let infohash = metainfo.infohash_lossy()?; Ok(Self::new(metainfo, infohash, size)) } pub(crate) fn from_input(input: &Input) -> Result { let metainfo = Metainfo::from_input(input)?; let infohash = Infohash::from_input(input)?; let size = Bytes(input.data().len().into_u64()); Ok(Self::new(metainfo, infohash, size)) } pub(crate) fn write(&self, env: &mut Env) -> Result<()> { let table = self.table(); if env.out().is_term() { let style = env.out().style(); table .write_human_readable(env.out_mut(), style) .context(error::Stdout)?; } else { table .write_tab_delimited(env.out_mut()) .context(error::Stdout)?; } Ok(()) } fn table(&self) -> Table { let mut table = Table::new(); table.row("Name", &self.metainfo.info.name); if let Some(comment) = &self.metainfo.comment { table.row("Comment", comment); } if let Some(creation_date) = self.metainfo.creation_date { #[allow(clippy::as_conversions)] table.row( "Creation Date", Utc.timestamp( creation_date .min(i64::max_value() as u64) .try_into() .invariant_unwrap("min with i64 is always valid i64"), 0, ), ); } if let Some(created_by) = &self.metainfo.created_by { table.row("Created By", created_by); } if let Some(source) = &self.metainfo.info.source { table.row("Source", source); } table.row("Info Hash", self.infohash); table.size("Torrent Size", self.size); table.size("Content Size", self.metainfo.content_size()); table.row( "Private", if self.metainfo.info.private.unwrap_or(false) { "yes" } else { "no" }, ); if let Some(announce) = &self.metainfo.announce { table.row("Tracker", announce); } if let Some(tiers) = &self.metainfo.announce_list { let mut value = Vec::new(); for (i, tier) in tiers.iter().enumerate() { value.push((format!("Tier {}", i + 1), tier.clone())); } table.tiers("Announce List", value); } if let Some(update_url) = &self.metainfo.info.update_url { table.row("Update URL", update_url); } if let Some(nodes) = &self.metainfo.nodes { table.list( "DHT Nodes", nodes .iter() .map(ToString::to_string) .collect::>(), ); } table.size("Piece Size", self.metainfo.info.piece_length); table.row("Piece Count", self.metainfo.info.pieces.count()); match &self.metainfo.info.mode { Mode::Single { .. } => { table.row("File Count", 1); table.row("Files", &self.metainfo.info.name); } Mode::Multiple { files } => { table.row("File Count", files.len()); table.directory( "Files", &self.metainfo.info.name, files .iter() .map(|file_info| file_info.path.clone()) .collect(), ); } }; table } pub(crate) fn write_json(&self, env: &mut Env) -> Result<()> { let data = self.torrent_summary_data(); let json = serde_json::to_string(&data).context(error::JsonSerialize)?; outln!(env, "{}", json)?; Ok(()) } fn torrent_summary_data(&self) -> TorrentSummaryJson { let (file_count, files) = match &self.metainfo.info.mode { Mode::Single { .. } => (1, vec![self.metainfo.info.name.to_owned()]), Mode::Multiple { files } => ( files.len(), files .iter() .map(|file_info| { format!( "{}", file_info .path .absolute(Path::new(&self.metainfo.info.name)) .as_path() .display() ) }) .collect(), ), }; TorrentSummaryJson { name: self.metainfo.info.name.to_owned(), comment: self.metainfo.comment.clone(), creation_date: self.metainfo.creation_date, created_by: self.metainfo.created_by.clone(), source: self.metainfo.info.source.clone(), info_hash: self.infohash.to_string(), torrent_size: self.size.count(), content_size: self.metainfo.content_size().count(), private: self.metainfo.info.private.unwrap_or_default(), tracker: self.metainfo.announce.clone(), announce_list: self .metainfo .announce_list .as_ref() .map(Clone::clone) .unwrap_or_default(), update_url: self .metainfo .info .update_url .as_ref() .map(ToString::to_string), dht_nodes: self .metainfo .nodes .as_ref() .unwrap_or(&Vec::new()) .iter() .map(ToString::to_string) .collect::>(), piece_size: self.metainfo.info.piece_length.count(), piece_count: self.metainfo.info.pieces.count(), file_count, files, } } }