use crate::prelude::*;
use lava_torrent::torrent::v1::Torrent as LavaTorrent;
use sha1::{Digest, Sha1};
use std::io::{Read, copy as io_copy, empty as io_empty};
pub(crate) struct TorrentPieceHasher {
stream: Box<dyn Read>,
piece_length: u64,
hasher: Sha1,
}
impl TorrentPieceHasher {
pub(crate) fn new(stream: Box<dyn Read>, piece_length: u64) -> Self {
Self {
stream,
piece_length,
hasher: Sha1::new(),
}
}
pub(crate) fn open(torrent: &LavaTorrent, directory: &Path) -> Result<Self, SourceIssue> {
let paths = get_file_paths(torrent, directory);
trace!("Opening {} content files", paths.len());
let stream = open_content_stream(&paths)?;
let piece_length =
u64::try_from(torrent.piece_length).expect("piece length should fit in u64");
Ok(Self::new(stream, piece_length))
}
}
impl Iterator for TorrentPieceHasher {
type Item = Result<[u8; 20], Failure<TorrentVerifyAction>>;
fn next(&mut self) -> Option<Self::Item> {
let mut piece = self.stream.by_ref().take(self.piece_length);
match io_copy(&mut piece, &mut self.hasher) {
Ok(0) => None,
Ok(_) => {
let mut digest = [0_u8; 20];
digest.copy_from_slice(self.hasher.finalize_reset().as_slice());
Some(Ok(digest))
}
Err(error) => Some(Err(Failure::new(TorrentVerifyAction::HashContent, error))),
}
}
}
fn open_content_stream(paths: &[PathBuf]) -> Result<Box<dyn Read>, SourceIssue> {
let mut stream: Box<dyn Read> = Box::new(io_empty());
for path in paths {
let file = match File::open(path) {
Ok(f) => f,
Err(e) if e.kind() == ErrorKind::NotFound => {
return Err(SourceIssue::MissingFile { path: path.clone() });
}
Err(e) => {
return Err(SourceIssue::OpenFile {
path: path.clone(),
error: e.to_string(),
});
}
};
stream = Box::new(stream.chain(file));
}
Ok(stream)
}
fn get_file_paths(torrent: &LavaTorrent, directory: &Path) -> Vec<PathBuf> {
match &torrent.files {
Some(files) => files.iter().map(|f| directory.join(&f.path)).collect(),
None => vec![directory.join(&torrent.name)],
}
}