use std::collections::BTreeMap;
use std::path::PathBuf;
use super::error::MetainfoError;
use crate::bencode::Value;
#[derive(Debug, Clone)]
pub struct FileTreeEntry {
pub length: u64,
pub pieces_root: Option<[u8; 32]>,
pub attr: Option<String>,
}
#[derive(Debug, Clone)]
pub enum FileTree {
File(FileTreeEntry),
Directory(BTreeMap<String, FileTree>),
}
#[derive(Debug, Clone)]
pub struct FlattenedFile {
pub path: PathBuf,
pub length: u64,
pub pieces_root: Option<[u8; 32]>,
pub attr: Option<String>,
}
impl FileTree {
pub fn from_bencode(value: &Value) -> Result<Self, MetainfoError> {
parse_file_tree_node(value)
}
pub fn flatten(&self) -> Vec<FlattenedFile> {
let mut files = Vec::new();
flatten_recursive(self, PathBuf::new(), &mut files);
files
}
pub fn is_file(&self) -> bool {
matches!(self, FileTree::File(_))
}
pub fn is_directory(&self) -> bool {
matches!(self, FileTree::Directory(_))
}
pub fn as_file(&self) -> Option<&FileTreeEntry> {
match self {
FileTree::File(entry) => Some(entry),
FileTree::Directory(_) => None,
}
}
pub fn as_directory(&self) -> Option<&BTreeMap<String, FileTree>> {
match self {
FileTree::File(_) => None,
FileTree::Directory(children) => Some(children),
}
}
}
fn parse_file_tree_node(value: &Value) -> Result<FileTree, MetainfoError> {
let dict = value
.as_dict()
.ok_or(MetainfoError::InvalidField("file tree"))?;
if let Some(file_info) = dict.get(b"".as_slice()) {
let file_dict = file_info
.as_dict()
.ok_or(MetainfoError::InvalidField("file tree entry"))?;
let length = file_dict
.get(b"length".as_slice())
.and_then(|v| v.as_integer())
.ok_or(MetainfoError::MissingField("length"))? as u64;
let pieces_root = file_dict
.get(b"pieces root".as_slice())
.and_then(|v| v.as_bytes())
.and_then(|b| {
if b.len() == 32 {
let mut arr = [0u8; 32];
arr.copy_from_slice(b);
Some(arr)
} else {
None
}
});
let attr = file_dict
.get(b"attr".as_slice())
.and_then(|v| v.as_str())
.map(String::from);
return Ok(FileTree::File(FileTreeEntry {
length,
pieces_root,
attr,
}));
}
let mut children = BTreeMap::new();
for (key, value) in dict {
let name = std::str::from_utf8(key)
.map_err(|_| MetainfoError::InvalidField("file tree key"))?
.to_string();
if name.is_empty() {
continue;
}
let child = parse_file_tree_node(value)?;
children.insert(name, child);
}
Ok(FileTree::Directory(children))
}
fn flatten_recursive(tree: &FileTree, current_path: PathBuf, files: &mut Vec<FlattenedFile>) {
match tree {
FileTree::File(entry) => {
files.push(FlattenedFile {
path: current_path,
length: entry.length,
pieces_root: entry.pieces_root,
attr: entry.attr.clone(),
});
}
FileTree::Directory(children) => {
for (name, child) in children {
let child_path = current_path.join(name);
flatten_recursive(child, child_path, files);
}
}
}
}