Files
MusicFS/crates/musicfs-client/src/origins/local/file_io.rs
T
2026-07-01 16:31:55 +02:00

216 lines
6.2 KiB
Rust

use std::{
fs,
io::{self, Read, Seek, SeekFrom},
path::Path,
};
pub fn read_bytes_at(path: &Path, offset: u64, len: usize) -> io::Result<Vec<u8>> {
let mut f = fs::File::open(path)?;
f.seek(SeekFrom::Start(offset))?;
let mut buf = vec![0u8; len];
let n = f.read(&mut buf)?;
buf.truncate(n);
return Ok(buf);
}
fn assemble_virtual_read(
reader: &dyn Fn(u64, usize) -> io::Result<Vec<u8>>,
header: &[u8],
pic_hdrs: &[Vec<u8>],
pic_ranges: &[(u64, u64)],
real_audio_start: u64,
offset: u64,
size: u32,
) -> io::Result<Vec<u8>> {
let end = offset + size as u64;
let header_end = header.len() as u64;
if end <= header_end {
return Ok(header[offset as usize..end as usize].to_vec());
}
let mut buf = Vec::with_capacity(size as usize);
if offset < header_end {
buf.extend_from_slice(&header[offset as usize..]);
}
let mut virt_pos = header_end;
for (pic_hdr, (data_real_offset, data_len)) in pic_hdrs.iter().zip(pic_ranges.iter()) {
let pic_hdr_len = pic_hdr.len() as u64;
let pic_hdr_end = virt_pos + pic_hdr_len;
let pic_end = pic_hdr_end + data_len;
if end <= virt_pos {
break;
}
if offset >= pic_end {
virt_pos = pic_end;
continue;
}
let hdr_from = (offset.max(virt_pos) - virt_pos) as usize;
let hdr_to = ((end.min(pic_hdr_end)) - virt_pos) as usize;
if hdr_from < hdr_to {
buf.extend_from_slice(&pic_hdr[hdr_from..hdr_to.min(pic_hdr.len())]);
}
let data_start = offset.max(pic_hdr_end);
let data_end = end.min(pic_end);
if data_start < data_end {
let real_off = data_real_offset + (data_start - pic_hdr_end);
let bytes = reader(real_off, (data_end - data_start) as usize)?;
buf.extend_from_slice(&bytes);
}
virt_pos = pic_end;
}
let audio_start = offset.max(virt_pos);
if audio_start < end {
let real_off = real_audio_start + (audio_start - virt_pos);
let bytes = reader(real_off, (end - audio_start) as usize)?;
buf.extend_from_slice(&bytes);
}
return Ok(buf);
}
pub fn assemble_flac_read(
reader: &dyn Fn(u64, usize) -> io::Result<Vec<u8>>,
header: &[u8],
pic_hdrs: &[Vec<u8>],
pic_ranges: &[(u64, u64)],
real_audio_start: u64,
offset: u64,
size: u32,
) -> io::Result<Vec<u8>> {
assemble_virtual_read(
reader,
header,
pic_hdrs,
pic_ranges,
real_audio_start,
offset,
size,
)
}
pub fn assemble_mp3_read(
reader: &dyn Fn(u64, usize) -> io::Result<Vec<u8>>,
header: &[u8],
pic_hdrs: &[Vec<u8>],
pic_ranges: &[(u64, u64)],
real_audio_start: u64,
offset: u64,
size: u32,
) -> io::Result<Vec<u8>> {
assemble_virtual_read(
reader,
header,
pic_hdrs,
pic_ranges,
real_audio_start,
offset,
size,
)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use std::path::PathBuf;
fn file_reader(path: PathBuf) -> impl Fn(u64, usize) -> io::Result<Vec<u8>> {
return move |offset, len| read_bytes_at(&path, offset, len);
}
#[test]
fn read_bytes_at_full_file() {
let mut f = tempfile::NamedTempFile::new().unwrap();
f.write_all(b"hello world").unwrap();
f.flush().unwrap();
let path = f.path();
let result = read_bytes_at(path, 0, 11).unwrap();
assert_eq!(result, b"hello world");
}
#[test]
fn read_bytes_at_offset() {
let mut f = tempfile::NamedTempFile::new().unwrap();
f.write_all(b"abcdefghij").unwrap();
f.flush().unwrap();
let path = f.path();
let result = read_bytes_at(path, 3, 4).unwrap();
assert_eq!(result, b"defg");
}
#[test]
fn assemble_flac_read_header_only() {
let header = b"HEADERDATA";
let noop = |_, _| Ok(vec![]);
let result = assemble_flac_read(&noop, header, &[], &[], 0, 2, 4).unwrap();
assert_eq!(result, b"ADER");
}
#[test]
fn assemble_flac_read_audio_region() {
let mut f = tempfile::NamedTempFile::new().unwrap();
let content: Vec<u8> = (0..20).collect();
f.write_all(&content).unwrap();
f.flush().unwrap();
let path = f.path().to_path_buf();
let reader = file_reader(path);
let header = b"HDR";
let result = assemble_flac_read(&reader, header, &[], &[], 10, 5, 4).unwrap();
// offset=5, header_len=3, so we read from header[3..] (0 bytes) + audio at real_off=10+(5-3)=12
// content[12..16] = [12, 13, 14, 15]
assert_eq!(result, vec![12, 13, 14, 15]);
}
#[test]
fn assemble_flac_read_header_audio_boundary() {
let mut f = tempfile::NamedTempFile::new().unwrap();
let content: Vec<u8> = (0..110).collect();
f.write_all(&content).unwrap();
f.flush().unwrap();
let path = f.path().to_path_buf();
let reader = file_reader(path);
let header = b"ABCD";
let result = assemble_flac_read(&reader, header, &[], &[], 100, 2, 6).unwrap();
// offset=2, size=6, header_len=4
// First 2 bytes from header[2..4] = "CD"
// Remaining 4 bytes from audio at real_off=100+(2+2-4)=100
// content[100..104] = [100, 101, 102, 103]
let expected = [b'C', b'D', 100, 101, 102, 103];
assert_eq!(result, expected);
}
#[test]
fn assemble_mp3_read_header_only() {
let header = b"ID3\x04\x00DATAHERE";
let noop = |_, _| Ok(vec![]);
let result = assemble_mp3_read(&noop, header, &[], &[], 0, 4, 4).unwrap();
assert_eq!(result, &header[4..8]);
}
#[test]
fn assemble_mp3_read_audio_region() {
let mut f = tempfile::NamedTempFile::new().unwrap();
let content: Vec<u8> = (0..20).collect();
f.write_all(&content).unwrap();
f.flush().unwrap();
let path = f.path().to_path_buf();
let reader = file_reader(path);
let header = b"ID3";
let result = assemble_mp3_read(&reader, header, &[], &[], 10, 5, 4).unwrap();
assert_eq!(result, vec![12, 13, 14, 15]);
}
}