use std::iter::ExactSizeIterator;
use bip_bencode::{BencodeMut, BMutAccess};
use bip_util::sha::{self, ShaHash};
use accessor::{Accessor, IntoAccessor};
use error::ParseResult;
use parse;
mod buffer;
mod worker;
const ALL_OPT_MAX_PIECE_LENGTH: usize = 16 * 1024 * 1024;
const BALANCED_MAX_PIECES_SIZE: usize = 40000;
const BALANCED_MIN_PIECE_LENGTH: usize = 512 * 1024;
const FILE_SIZE_MAX_PIECES_SIZE: usize = 20000;
const FILE_SIZE_MIN_PIECE_LENGTH: usize = 1 * 1024 * 1024;
const TRANSFER_MAX_PIECES_SIZE: usize = 60000;
const TRANSFER_MIN_PIECE_LENGTH: usize = 1 * 1024;
pub enum PieceLength {
OptBalanced,
OptFileSize,
OptTransfer,
Custom(usize),
}
pub struct MetainfoBuilder<'a> {
root: BencodeMut<'a>,
info: InfoBuilder<'a>
}
impl<'a> MetainfoBuilder<'a> {
pub fn new() -> MetainfoBuilder<'a> {
MetainfoBuilder {
root: BencodeMut::new_dict(),
info: InfoBuilder::new()
}
}
pub fn set_main_tracker(mut self, opt_tracker_url: Option<&'a str>) -> MetainfoBuilder<'a> {
{
let dict_access = self.root.dict_mut().unwrap();
opt_tracker_url
.and_then(|tracker_url| dict_access.insert(parse::ANNOUNCE_URL_KEY.into(), ben_bytes!(tracker_url)))
.or_else(|| dict_access.remove(parse::ANNOUNCE_URL_KEY));
}
self
}
pub fn set_creation_date(mut self, opt_secs_epoch: Option<i64>) -> MetainfoBuilder<'a> {
{
let dict_access = self.root.dict_mut().unwrap();
opt_secs_epoch
.and_then(|secs_epoch| dict_access.insert(parse::CREATION_DATE_KEY.into(), ben_int!(secs_epoch)))
.or_else(|| dict_access.remove(parse::CREATION_DATE_KEY));
}
self
}
pub fn set_comment(mut self, opt_comment: Option<&'a str>) -> MetainfoBuilder<'a> {
{
let dict_access = self.root.dict_mut().unwrap();
opt_comment
.and_then(|comment| dict_access.insert(parse::COMMENT_KEY.into(), ben_bytes!(comment)))
.or_else(|| dict_access.remove(parse::COMMENT_KEY));
}
self
}
pub fn set_created_by(mut self, opt_created_by: Option<&'a str>) -> MetainfoBuilder<'a> {
{
let dict_access = self.root.dict_mut().unwrap();
opt_created_by
.and_then(|created_by| dict_access.insert(parse::CREATED_BY_KEY.into(), ben_bytes!(created_by)))
.or_else(|| dict_access.remove(parse::CREATED_BY_KEY));
}
self
}
pub fn set_private_flag(mut self, opt_is_private: Option<bool>) -> MetainfoBuilder<'a> {
self.info = self.info.set_private_flag(opt_is_private);
self
}
pub fn set_piece_length(mut self, piece_length: PieceLength) -> MetainfoBuilder<'a> {
self.info = self.info.set_piece_length(piece_length);
self
}
pub fn build<A, C>(self, threads: usize, accessor: A, progress: C) -> ParseResult<Vec<u8>>
where A: IntoAccessor,
C: FnMut(f64) + Send + 'static
{
let accessor = try!(accessor.into_accessor());
build_with_accessor(threads, accessor, progress, Some(self.root), self.info.info, self.info.piece_length)
}
}
pub struct InfoBuilder<'a> {
info: BencodeMut<'a>,
piece_length: PieceLength
}
impl<'a> InfoBuilder<'a> {
pub fn new() -> InfoBuilder<'a> {
InfoBuilder{ info: BencodeMut::new_dict(), piece_length: PieceLength::OptBalanced }
}
pub fn set_private_flag(mut self, opt_is_private: Option<bool>) -> InfoBuilder<'a> {
let opt_numeric_is_private = opt_is_private.map(|is_private| if is_private{ 1 } else { 0 });
{
let dict_access = self.info.dict_mut().unwrap();
opt_numeric_is_private
.and_then(|numeric_is_private| dict_access.insert(parse::PRIVATE_KEY.into(), ben_int!(numeric_is_private)))
.or_else(|| dict_access.remove(parse::PRIVATE_KEY));
}
self
}
pub fn set_piece_length(mut self, piece_length: PieceLength) -> InfoBuilder<'a> {
self.piece_length = piece_length;
self
}
pub fn build<A, C>(self, threads: usize, accessor: A, progress: C) -> ParseResult<Vec<u8>>
where A: IntoAccessor,
C: FnMut(f64) + Send + 'static
{
let accessor = try!(accessor.into_accessor());
build_with_accessor(threads, accessor, progress, None, self.info, self.piece_length)
}
}
fn build_with_accessor<'a, A, C>(threads: usize,
accessor: A,
progress: C,
opt_root: Option<BencodeMut<'a>>,
info: BencodeMut<'a>,
piece_length: PieceLength) -> ParseResult<Vec<u8>>
where A: Accessor,
C: FnMut(f64) + Send + 'static {
if threads == 0 {
panic!("bip_metainfo: Cannot Build Metainfo File With threads == 0");
}
let mut files_info = Vec::new();
try!(accessor.access_metadata(|len, path| {
let path_list: Vec<String> = path.iter()
.map(|os_str| os_str.to_string_lossy().into_owned())
.collect();
files_info.push((len, path_list));
}));
let total_files_len = files_info.iter().fold(0, |acc, nex| acc + nex.0);
let piece_length = determine_piece_length(total_files_len, piece_length);
let total_num_pieces = ((total_files_len as f64) / (piece_length as f64)).ceil() as u64;
let pieces_list = try!(worker::start_hasher_workers(&accessor,
piece_length,
total_num_pieces,
threads,
progress));
let pieces = map_pieces_list(pieces_list.into_iter().map(|(_, piece)| piece));
let mut single_file_name = String::new();
let access_directory = accessor.access_directory().map(|path| path.to_string_lossy());
let opt_root = opt_root;
let mut info = info;
{
let info_access = info.dict_mut().unwrap();
info_access.insert(parse::PIECE_LENGTH_KEY.into(), ben_int!(piece_length as i64));
info_access.insert(parse::PIECES_KEY.into(), ben_bytes!(&pieces[..]));
match (&access_directory, files_info.len() > 1) {
(&Some(ref directory), _) => {
let mut bencode_files = BencodeMut::new_list();
{
let bencode_files_access = bencode_files.list_mut().unwrap();
for &(len, ref path) in files_info.iter() {
let mut bencode_path = BencodeMut::new_list();
{
let bencode_path_access = bencode_path.list_mut().unwrap();
for path_element in path.iter() {
bencode_path_access.push(ben_bytes!(&path_element[..]));
}
}
bencode_files_access.push(ben_map!{
parse::LENGTH_KEY => ben_int!(len as i64),
parse::PATH_KEY => bencode_path
});
}
}
info_access.insert(parse::NAME_KEY.into(), ben_bytes!(directory.as_ref()));
info_access.insert(parse::FILES_KEY.into(), bencode_files);
}
(&None, true) => {
let mut bencode_files = BencodeMut::new_list();
{
let bencode_files_access = bencode_files.list_mut().unwrap();
for &(len, ref path) in files_info.iter() {
let mut bencode_path = BencodeMut::new_list();
{
let bencode_path_access = bencode_path.list_mut().unwrap();
for path_element in path.iter() {
bencode_path_access.push(ben_bytes!(&path_element[..]));
}
}
bencode_files_access.push(ben_map!{
parse::LENGTH_KEY => ben_int!(len as i64),
parse::PATH_KEY => bencode_path
});
}
}
info_access.insert(parse::NAME_KEY.into(), ben_bytes!(""));
info_access.insert(parse::FILES_KEY.into(), bencode_files);
}
(&None, false) => {
for name_component in files_info[0].1.iter() {
single_file_name.push_str(name_component);
}
info_access.insert(parse::LENGTH_KEY.into(), ben_int!(files_info[0].0 as i64));
info_access.insert(parse::NAME_KEY.into(), ben_bytes!(&single_file_name[..]));
}
}
}
if let Some(mut root) = opt_root {
root.dict_mut().unwrap().insert(parse::INFO_KEY.into(), info);
Ok(root.encode())
} else {
Ok(info.encode())
}
}
fn determine_piece_length(total_file_size: u64, piece_length: PieceLength) -> usize {
match piece_length {
PieceLength::Custom(len) => len,
PieceLength::OptBalanced => {
calculate_piece_length(total_file_size,
BALANCED_MAX_PIECES_SIZE,
BALANCED_MIN_PIECE_LENGTH)
}
PieceLength::OptFileSize => {
calculate_piece_length(total_file_size,
FILE_SIZE_MAX_PIECES_SIZE,
FILE_SIZE_MIN_PIECE_LENGTH)
}
PieceLength::OptTransfer => {
calculate_piece_length(total_file_size,
TRANSFER_MAX_PIECES_SIZE,
TRANSFER_MIN_PIECE_LENGTH)
}
}
}
fn calculate_piece_length(total_file_size: u64,
max_pieces_size: usize,
min_piece_length: usize)
-> usize {
let num_pieces = (max_pieces_size as f64) / (sha::SHA_HASH_LEN as f64);
let piece_length = ((total_file_size as f64) / num_pieces + 0.5) as usize;
let pot_piece_length = piece_length.next_power_of_two();
match (pot_piece_length > min_piece_length, pot_piece_length < ALL_OPT_MAX_PIECE_LENGTH) {
(true, true) => pot_piece_length,
(false, _) => min_piece_length,
(_, false) => ALL_OPT_MAX_PIECE_LENGTH,
}
}
fn map_pieces_list<I>(pieces: I) -> Vec<u8>
where I: Iterator<Item = ShaHash> + ExactSizeIterator
{
let mut concated_pieces = Vec::with_capacity(pieces.len() * sha::SHA_HASH_LEN);
for piece in pieces {
concated_pieces.extend_from_slice(piece.as_ref());
}
concated_pieces
}