use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AllocationMode {
#[default]
Sparse,
Full,
}
#[derive(Debug, Clone)]
pub struct FileEntry {
pub path: PathBuf,
pub length: u64,
pub offset: u64,
pub pieces_root: Option<[u8; 32]>,
pub is_padding: bool,
}
#[derive(Debug, Clone)]
pub struct PieceInfo {
pub index: u32,
pub hash: Vec<u8>,
pub offset: u64,
pub length: u64,
}
#[derive(Debug)]
pub struct PieceFileSpan {
pub file_index: usize,
pub file_offset: u64,
pub length: u64,
}
impl FileEntry {
pub fn new(path: PathBuf, length: u64, offset: u64) -> Self {
Self {
path,
length,
offset,
pieces_root: None,
is_padding: false,
}
}
pub fn new_v2(
path: PathBuf,
length: u64,
offset: u64,
pieces_root: Option<[u8; 32]>,
is_padding: bool,
) -> Self {
Self {
path,
length,
offset,
pieces_root,
is_padding,
}
}
pub fn byte_range(&self) -> std::ops::Range<u64> {
self.offset..self.offset + self.length
}
pub fn contains_offset(&self, offset: u64) -> bool {
offset >= self.offset && offset < self.offset + self.length
}
}
impl PieceInfo {
pub fn v1(index: u32, hash: [u8; 20], offset: u64, length: u64) -> Self {
Self {
index,
hash: hash.to_vec(),
offset,
length,
}
}
pub fn v2(index: u32, hash: [u8; 32], offset: u64, length: u64) -> Self {
Self {
index,
hash: hash.to_vec(),
offset,
length,
}
}
pub fn byte_range(&self) -> std::ops::Range<u64> {
self.offset..self.offset + self.length
}
pub fn hash_v1(&self) -> Option<[u8; 20]> {
if self.hash.len() == 20 {
let mut arr = [0u8; 20];
arr.copy_from_slice(&self.hash);
Some(arr)
} else {
None
}
}
pub fn hash_v2(&self) -> Option<[u8; 32]> {
if self.hash.len() == 32 {
let mut arr = [0u8; 32];
arr.copy_from_slice(&self.hash);
Some(arr)
} else {
None
}
}
pub fn is_v2(&self) -> bool {
self.hash.len() == 32
}
}
#[derive(Debug, Clone)]
pub struct V2PieceMap {
file_piece_ranges: Vec<(usize, u32, u32)>,
total_pieces: u32,
piece_length: u64,
}
impl V2PieceMap {
pub fn new(files: &[FileEntry], piece_length: u64) -> Self {
let mut file_piece_ranges = Vec::new();
let mut global_piece_index = 0u32;
for (file_idx, file) in files.iter().enumerate() {
if file.is_padding || file.length == 0 {
continue;
}
let piece_count = file.length.div_ceil(piece_length) as u32;
file_piece_ranges.push((file_idx, global_piece_index, piece_count));
global_piece_index += piece_count;
}
Self {
file_piece_ranges,
total_pieces: global_piece_index,
piece_length,
}
}
pub fn total_pieces(&self) -> u32 {
self.total_pieces
}
pub fn global_to_file(&self, global_index: u32) -> Option<(usize, u32)> {
for &(file_idx, first_global, piece_count) in &self.file_piece_ranges {
if global_index >= first_global && global_index < first_global + piece_count {
return Some((file_idx, global_index - first_global));
}
}
None
}
pub fn file_to_global(&self, file_index: usize, local_index: u32) -> Option<u32> {
for &(file_idx, first_global, piece_count) in &self.file_piece_ranges {
if file_idx == file_index && local_index < piece_count {
return Some(first_global + local_index);
}
}
None
}
pub fn piece_length(&self) -> u64 {
self.piece_length
}
pub fn file_ranges(&self) -> &[(usize, u32, u32)] {
&self.file_piece_ranges
}
pub fn build_piece_info(
&self,
files: &[FileEntry],
piece_hashes: &[[u8; 32]],
) -> Option<Vec<PieceInfo>> {
if piece_hashes.len() != self.total_pieces as usize {
return None;
}
let mut pieces = Vec::with_capacity(self.total_pieces as usize);
let mut global_index = 0u32;
for &(file_idx, _first_global, piece_count) in &self.file_piece_ranges {
let file = &files[file_idx];
for local_idx in 0..piece_count {
let offset_in_file = local_idx as u64 * self.piece_length;
let remaining = file.length.saturating_sub(offset_in_file);
let length = remaining.min(self.piece_length);
let hash = piece_hashes[global_index as usize];
pieces.push(PieceInfo::v2(
global_index,
hash,
offset_in_file, length,
));
global_index += 1;
}
}
Some(pieces)
}
}