use crate::proto::grpc::file::FileInfo;
#[derive(Debug, Clone)]
pub struct BlockReadPlan {
pub block_id: i64,
pub block_index: u64,
pub offset_in_block: u64,
pub length: u64,
}
pub struct BlockMapper;
impl BlockMapper {
pub fn plan_read(file_info: &FileInfo, offset: u64, length: u64) -> Vec<BlockReadPlan> {
let block_size = file_info.block_size_bytes.unwrap_or(64 * 1024 * 1024) as u64;
let file_length = file_info.length.unwrap_or(0) as u64;
if block_size == 0 || length == 0 || offset >= file_length {
return Vec::new();
}
let effective_length = std::cmp::min(length, file_length.saturating_sub(offset));
if effective_length == 0 {
return Vec::new();
}
let mut plans = Vec::new();
let mut remaining = effective_length;
let mut current_offset = offset;
while remaining > 0 {
let block_index = current_offset / block_size;
let offset_in_block = current_offset % block_size;
let bytes_in_block = std::cmp::min(remaining, block_size - offset_in_block);
let block_id = file_info
.block_ids
.get(block_index as usize)
.copied()
.unwrap_or(-1);
plans.push(BlockReadPlan {
block_id,
block_index,
offset_in_block,
length: bytes_in_block,
});
current_offset += bytes_in_block;
remaining -= bytes_in_block;
}
plans
}
pub fn plan_write(block_size: u64, file_offset: u64, length: u64) -> Vec<BlockWritePlan> {
if block_size == 0 || length == 0 {
return Vec::new();
}
let mut plans = Vec::new();
let mut remaining = length;
let mut current_offset = file_offset;
while remaining > 0 {
let block_index = current_offset / block_size;
let offset_in_block = current_offset % block_size;
let bytes_in_block = std::cmp::min(remaining, block_size - offset_in_block);
plans.push(BlockWritePlan {
block_index,
offset_in_block,
length: bytes_in_block,
});
current_offset += bytes_in_block;
remaining -= bytes_in_block;
}
plans
}
}
#[derive(Debug, Clone)]
pub struct BlockWritePlan {
pub block_index: u64,
pub offset_in_block: u64,
pub length: u64,
}
#[cfg(test)]
mod tests {
use super::*;
fn make_file_info(block_size: i64, file_length: i64, block_ids: Vec<i64>) -> FileInfo {
FileInfo {
block_size_bytes: Some(block_size),
length: Some(file_length),
block_ids,
..Default::default()
}
}
#[test]
fn test_single_block_full_read() {
let info = make_file_info(64 * 1024 * 1024, 32 * 1024 * 1024, vec![100]);
let plans = BlockMapper::plan_read(&info, 0, 32 * 1024 * 1024);
assert_eq!(plans.len(), 1);
assert_eq!(plans[0].block_id, 100);
assert_eq!(plans[0].block_index, 0);
assert_eq!(plans[0].offset_in_block, 0);
assert_eq!(plans[0].length, 32 * 1024 * 1024);
}
#[test]
fn test_cross_block_read() {
let block_size = 64 * 1024 * 1024_u64;
let info = make_file_info(
block_size as i64,
(block_size * 3) as i64,
vec![100, 200, 300],
);
let offset = 70 * 1024 * 1024;
let length = 100 * 1024 * 1024;
let plans = BlockMapper::plan_read(&info, offset, length);
assert_eq!(plans.len(), 2);
assert_eq!(plans[0].block_id, 200);
assert_eq!(plans[0].block_index, 1);
assert_eq!(plans[0].offset_in_block, 6 * 1024 * 1024);
assert_eq!(plans[0].length, 58 * 1024 * 1024);
assert_eq!(plans[1].block_id, 300);
assert_eq!(plans[1].block_index, 2);
assert_eq!(plans[1].offset_in_block, 0);
assert_eq!(plans[1].length, 42 * 1024 * 1024);
}
#[test]
fn test_read_clamped_to_file_length() {
let info = make_file_info(64 * 1024 * 1024, 10 * 1024 * 1024, vec![100]);
let plans = BlockMapper::plan_read(&info, 0, 100 * 1024 * 1024);
assert_eq!(plans.len(), 1);
assert_eq!(plans[0].length, 10 * 1024 * 1024);
}
#[test]
fn test_read_past_eof() {
let info = make_file_info(64 * 1024 * 1024, 10 * 1024 * 1024, vec![100]);
let plans = BlockMapper::plan_read(&info, 10 * 1024 * 1024, 100);
assert!(plans.is_empty());
}
#[test]
fn test_zero_length_read() {
let info = make_file_info(64 * 1024 * 1024, 100, vec![100]);
let plans = BlockMapper::plan_read(&info, 0, 0);
assert!(plans.is_empty());
}
#[test]
fn test_write_plan_cross_block() {
let block_size = 64 * 1024 * 1024;
let plans = BlockMapper::plan_write(block_size, 60 * 1024 * 1024, 10 * 1024 * 1024);
assert_eq!(plans.len(), 2);
assert_eq!(plans[0].block_index, 0);
assert_eq!(plans[0].offset_in_block, 60 * 1024 * 1024);
assert_eq!(plans[0].length, 4 * 1024 * 1024);
assert_eq!(plans[1].block_index, 1);
assert_eq!(plans[1].offset_in_block, 0);
assert_eq!(plans[1].length, 6 * 1024 * 1024);
}
}