mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-28 16:41:52 +08:00
feat(video): 事务化切换与前端统一编排,增强视频输入格式支持
- 后端:切换事务+transition_id,/stream/mode 返回 switching/transition_id 与实际 codec - 事件:新增 mode_switching/mode_ready,config/webrtc_ready/mode_changed 关联事务 - 编码/格式:扩展 NV21/NV16/NV24/RGB/BGR 输入与转换链路,RKMPP direct input 优化 - 前端:useVideoSession 统一切换,失败回退真实切回 MJPEG,菜单格式同步修复 - 清理:useVideoStream 降级为 MJPEG-only
This commit is contained in:
@@ -33,13 +33,13 @@ fn get_cluster_size(total_sectors: u64) -> u32 {
|
||||
/// For example: 32KB cluster = 64 sectors = 2^6, so shift = 6
|
||||
fn sectors_per_cluster_shift(cluster_size: u32) -> u8 {
|
||||
match cluster_size {
|
||||
4096 => 3, // 8 sectors (4KB)
|
||||
8192 => 4, // 16 sectors (8KB)
|
||||
16384 => 5, // 32 sectors (16KB)
|
||||
32768 => 6, // 64 sectors (32KB)
|
||||
65536 => 7, // 128 sectors (64KB)
|
||||
131072 => 8, // 256 sectors (128KB)
|
||||
262144 => 9, // 512 sectors (256KB)
|
||||
4096 => 3, // 8 sectors (4KB)
|
||||
8192 => 4, // 16 sectors (8KB)
|
||||
16384 => 5, // 32 sectors (16KB)
|
||||
32768 => 6, // 64 sectors (32KB)
|
||||
65536 => 7, // 128 sectors (64KB)
|
||||
131072 => 8, // 256 sectors (128KB)
|
||||
262144 => 9, // 512 sectors (256KB)
|
||||
_ => {
|
||||
// Fallback: calculate dynamically
|
||||
let sectors = cluster_size / 512;
|
||||
@@ -75,11 +75,7 @@ struct ExfatBootSector {
|
||||
}
|
||||
|
||||
impl ExfatBootSector {
|
||||
fn new(
|
||||
volume_length: u64,
|
||||
cluster_size: u32,
|
||||
volume_serial: u32,
|
||||
) -> Self {
|
||||
fn new(volume_length: u64, cluster_size: u32, volume_serial: u32) -> Self {
|
||||
let sector_size: u32 = 512;
|
||||
let sectors_per_cluster = cluster_size / sector_size;
|
||||
let spc_shift = sectors_per_cluster_shift(cluster_size);
|
||||
@@ -106,7 +102,8 @@ impl ExfatBootSector {
|
||||
// Cluster 3...: Upcase table (128KB, may span multiple clusters)
|
||||
// Next available: Root directory
|
||||
const UPCASE_TABLE_SIZE: u64 = 128 * 1024;
|
||||
let upcase_clusters = ((UPCASE_TABLE_SIZE + cluster_size as u64 - 1) / cluster_size as u64) as u32;
|
||||
let upcase_clusters =
|
||||
((UPCASE_TABLE_SIZE + cluster_size as u64 - 1) / cluster_size as u64) as u32;
|
||||
let first_cluster_of_root = 3 + upcase_clusters;
|
||||
|
||||
Self {
|
||||
@@ -235,7 +232,7 @@ fn create_bitmap_entry(start_cluster: u32, size: u64) -> [u8; 32] {
|
||||
let mut entry = [0u8; 32];
|
||||
entry[0] = ENTRY_TYPE_BITMAP;
|
||||
entry[1] = 0; // BitmapFlags
|
||||
// Reserved: bytes 2-19
|
||||
// Reserved: bytes 2-19
|
||||
entry[20..24].copy_from_slice(&start_cluster.to_le_bytes());
|
||||
entry[24..32].copy_from_slice(&size.to_le_bytes());
|
||||
entry
|
||||
@@ -306,7 +303,8 @@ pub fn format_exfat<W: Write + Seek>(
|
||||
|
||||
// Calculate how many clusters the upcase table needs (128KB)
|
||||
const UPCASE_TABLE_SIZE: u64 = 128 * 1024;
|
||||
let upcase_clusters = ((UPCASE_TABLE_SIZE + cluster_size as u64 - 1) / cluster_size as u64) as u32;
|
||||
let upcase_clusters =
|
||||
((UPCASE_TABLE_SIZE + cluster_size as u64 - 1) / cluster_size as u64) as u32;
|
||||
let root_cluster = 3 + upcase_clusters; // Root comes after bitmap and upcase
|
||||
|
||||
// FAT entries: cluster 0 and 1 are reserved
|
||||
@@ -349,13 +347,14 @@ pub fn format_exfat<W: Write + Seek>(
|
||||
|
||||
// Cluster 2: Allocation Bitmap
|
||||
let bitmap_size = (boot_sector.cluster_count + 7) / 8;
|
||||
let _bitmap_clusters = ((bitmap_size as u64 + cluster_size as u64 - 1) / cluster_size as u64).max(1);
|
||||
let _bitmap_clusters =
|
||||
((bitmap_size as u64 + cluster_size as u64 - 1) / cluster_size as u64).max(1);
|
||||
let mut bitmap = vec![0u8; cluster_size as usize];
|
||||
|
||||
// Mark clusters 2, 3..3+upcase_clusters-1, root_cluster as used
|
||||
// Cluster 2: bitmap
|
||||
bitmap[0] |= 0b00000100; // Bit 2
|
||||
// Clusters 3..3+upcase_clusters-1: upcase table
|
||||
// Clusters 3..3+upcase_clusters-1: upcase table
|
||||
for i in 0..upcase_clusters {
|
||||
let cluster = 3 + i;
|
||||
let byte_idx = (cluster / 8) as usize;
|
||||
|
||||
@@ -53,8 +53,7 @@ impl FatCache {
|
||||
if self.entries.is_empty() {
|
||||
return false;
|
||||
}
|
||||
cluster >= self.start_cluster
|
||||
&& cluster < self.start_cluster + self.entries.len() as u32
|
||||
cluster >= self.start_cluster && cluster < self.start_cluster + self.entries.len() as u32
|
||||
}
|
||||
|
||||
/// Get a FAT entry from cache (if present)
|
||||
@@ -243,7 +242,9 @@ impl ExfatFs {
|
||||
|
||||
/// Get the byte offset of a FAT entry
|
||||
fn fat_entry_offset(&self, cluster: u32) -> u64 {
|
||||
self.partition_offset + self.fat_offset as u64 * self.bytes_per_sector as u64 + cluster as u64 * 4
|
||||
self.partition_offset
|
||||
+ self.fat_offset as u64 * self.bytes_per_sector as u64
|
||||
+ cluster as u64 * 4
|
||||
}
|
||||
|
||||
/// Load a FAT segment into cache starting from the given cluster
|
||||
@@ -287,7 +288,10 @@ impl ExfatFs {
|
||||
|
||||
// Should be in cache now
|
||||
self.fat_cache.get(cluster).ok_or_else(|| {
|
||||
VentoyError::FilesystemError(format!("Failed to cache FAT entry for cluster {}", cluster))
|
||||
VentoyError::FilesystemError(format!(
|
||||
"Failed to cache FAT entry for cluster {}",
|
||||
cluster
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -490,9 +494,9 @@ impl ExfatFs {
|
||||
fn extend_cluster_chain(&mut self, first_cluster: u32) -> Result<u32> {
|
||||
// Find the last cluster in the chain
|
||||
let chain = self.read_cluster_chain(first_cluster)?;
|
||||
let last_cluster = *chain.last().ok_or_else(|| {
|
||||
VentoyError::FilesystemError("Empty cluster chain".to_string())
|
||||
})?;
|
||||
let last_cluster = *chain
|
||||
.last()
|
||||
.ok_or_else(|| VentoyError::FilesystemError("Empty cluster chain".to_string()))?;
|
||||
|
||||
// Allocate one new cluster
|
||||
let new_cluster = self.allocate_clusters(1)?;
|
||||
@@ -532,7 +536,12 @@ impl ExfatFs {
|
||||
}
|
||||
|
||||
/// Create file directory entries for a new file
|
||||
fn create_file_entries(name: &str, first_cluster: u32, size: u64, is_dir: bool) -> Vec<[u8; 32]> {
|
||||
fn create_file_entries(
|
||||
name: &str,
|
||||
first_cluster: u32,
|
||||
size: u64,
|
||||
is_dir: bool,
|
||||
) -> Vec<[u8; 32]> {
|
||||
let name_utf16: Vec<u16> = name.encode_utf16().collect();
|
||||
let name_entries_needed = (name_utf16.len() + 14) / 15; // 15 chars per name entry
|
||||
let secondary_count = 1 + name_entries_needed; // Stream + Name entries
|
||||
@@ -552,18 +561,22 @@ impl ExfatFs {
|
||||
.map(|d| d.as_secs() as u32)
|
||||
.unwrap_or(0);
|
||||
// DOS timestamp format (simplified)
|
||||
let dos_time = ((now / 2) & 0x1F) | (((now / 60) & 0x3F) << 5) | (((now / 3600) & 0x1F) << 11);
|
||||
let dos_time =
|
||||
((now / 2) & 0x1F) | (((now / 60) & 0x3F) << 5) | (((now / 3600) & 0x1F) << 11);
|
||||
let dos_date = 1 | (1 << 5) | ((45) << 9); // Jan 1, 2025
|
||||
file_entry[8..12].copy_from_slice(&(dos_date as u32 | ((dos_time as u32) << 16)).to_le_bytes());
|
||||
file_entry[12..16].copy_from_slice(&(dos_date as u32 | ((dos_time as u32) << 16)).to_le_bytes());
|
||||
file_entry[16..20].copy_from_slice(&(dos_date as u32 | ((dos_time as u32) << 16)).to_le_bytes());
|
||||
file_entry[8..12]
|
||||
.copy_from_slice(&(dos_date as u32 | ((dos_time as u32) << 16)).to_le_bytes());
|
||||
file_entry[12..16]
|
||||
.copy_from_slice(&(dos_date as u32 | ((dos_time as u32) << 16)).to_le_bytes());
|
||||
file_entry[16..20]
|
||||
.copy_from_slice(&(dos_date as u32 | ((dos_time as u32) << 16)).to_le_bytes());
|
||||
entries.push(file_entry);
|
||||
|
||||
// 2. Stream Extension Entry (0xC0)
|
||||
let mut stream_entry = [0u8; 32];
|
||||
stream_entry[0] = ENTRY_TYPE_STREAM;
|
||||
stream_entry[1] = 0x03; // GeneralSecondaryFlags: AllocationPossible | NoFatChain (for contiguous)
|
||||
// For non-contiguous files, use 0x01
|
||||
// For non-contiguous files, use 0x01
|
||||
if size > 0 {
|
||||
stream_entry[1] = 0x01; // AllocationPossible, use FAT chain
|
||||
}
|
||||
@@ -588,7 +601,8 @@ impl ExfatFs {
|
||||
for i in 0..15 {
|
||||
if char_index < name_utf16.len() {
|
||||
let offset = 2 + i * 2;
|
||||
name_entry[offset..offset + 2].copy_from_slice(&name_utf16[char_index].to_le_bytes());
|
||||
name_entry[offset..offset + 2]
|
||||
.copy_from_slice(&name_utf16[char_index].to_le_bytes());
|
||||
char_index += 1;
|
||||
}
|
||||
}
|
||||
@@ -603,7 +617,11 @@ impl ExfatFs {
|
||||
}
|
||||
|
||||
/// Find a file entry in a specific directory cluster
|
||||
fn find_entry_in_directory(&mut self, dir_cluster: u32, name: &str) -> Result<Option<FileEntryLocation>> {
|
||||
fn find_entry_in_directory(
|
||||
&mut self,
|
||||
dir_cluster: u32,
|
||||
name: &str,
|
||||
) -> Result<Option<FileEntryLocation>> {
|
||||
let target_name_lower = name.to_lowercase();
|
||||
|
||||
// Read all clusters in the directory chain
|
||||
@@ -747,7 +765,11 @@ impl ExfatFs {
|
||||
///
|
||||
/// If no free slot is found in existing clusters, this method will
|
||||
/// automatically extend the directory by allocating a new cluster.
|
||||
fn find_free_slot_in_directory(&mut self, dir_cluster: u32, entries_needed: usize) -> Result<(u32, u32)> {
|
||||
fn find_free_slot_in_directory(
|
||||
&mut self,
|
||||
dir_cluster: u32,
|
||||
entries_needed: usize,
|
||||
) -> Result<(u32, u32)> {
|
||||
let dir_clusters = self.read_cluster_chain(dir_cluster)?;
|
||||
|
||||
for &cluster in &dir_clusters {
|
||||
@@ -759,10 +781,12 @@ impl ExfatFs {
|
||||
while i < cluster_data.len() {
|
||||
let entry_type = cluster_data[i];
|
||||
|
||||
if entry_type == ENTRY_TYPE_END || entry_type == 0x00
|
||||
if entry_type == ENTRY_TYPE_END
|
||||
|| entry_type == 0x00
|
||||
|| entry_type == ENTRY_TYPE_DELETED_FILE
|
||||
|| entry_type == ENTRY_TYPE_DELETED_STREAM
|
||||
|| entry_type == ENTRY_TYPE_DELETED_NAME {
|
||||
|| entry_type == ENTRY_TYPE_DELETED_NAME
|
||||
{
|
||||
if consecutive_free == 0 {
|
||||
slot_start = i;
|
||||
}
|
||||
@@ -795,7 +819,8 @@ impl ExfatFs {
|
||||
// This is critical: when we extend a directory, we need to clear any END markers
|
||||
// that may exist in previous clusters, otherwise list_files will stop prematurely
|
||||
let dir_clusters_before = self.read_cluster_chain(dir_cluster)?;
|
||||
for &cluster in &dir_clusters_before[..dir_clusters_before.len()-1] { // Exclude the newly added cluster
|
||||
for &cluster in &dir_clusters_before[..dir_clusters_before.len() - 1] {
|
||||
// Exclude the newly added cluster
|
||||
let mut cluster_data = self.read_cluster(cluster)?;
|
||||
|
||||
// Scan for END markers and replace them with 0xFF (invalid entry, will be skipped)
|
||||
@@ -815,14 +840,23 @@ impl ExfatFs {
|
||||
/// Find a free slot in the root directory for new entries (backward compatible)
|
||||
#[allow(dead_code)]
|
||||
fn find_free_directory_slot(&mut self, entries_needed: usize) -> Result<u32> {
|
||||
let (_, offset) = self.find_free_slot_in_directory(self.first_cluster_of_root, entries_needed)?;
|
||||
let (_, offset) =
|
||||
self.find_free_slot_in_directory(self.first_cluster_of_root, entries_needed)?;
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
/// Create an entry in a specific directory
|
||||
fn create_entry_in_directory(&mut self, dir_cluster: u32, name: &str, first_cluster: u32, size: u64, is_dir: bool) -> Result<()> {
|
||||
fn create_entry_in_directory(
|
||||
&mut self,
|
||||
dir_cluster: u32,
|
||||
name: &str,
|
||||
first_cluster: u32,
|
||||
size: u64,
|
||||
is_dir: bool,
|
||||
) -> Result<()> {
|
||||
let entries = Self::create_file_entries(name, first_cluster, size, is_dir);
|
||||
let (slot_cluster, slot_offset) = self.find_free_slot_in_directory(dir_cluster, entries.len())?;
|
||||
let (slot_cluster, slot_offset) =
|
||||
self.find_free_slot_in_directory(dir_cluster, entries.len())?;
|
||||
|
||||
let mut cluster_data = self.read_cluster(slot_cluster)?;
|
||||
|
||||
@@ -854,7 +888,10 @@ impl ExfatFs {
|
||||
}
|
||||
|
||||
// Check if already exists
|
||||
if self.find_entry_in_directory(parent_cluster, name)?.is_some() {
|
||||
if self
|
||||
.find_entry_in_directory(parent_cluster, name)?
|
||||
.is_some()
|
||||
{
|
||||
return Err(VentoyError::FilesystemError(format!(
|
||||
"Entry '{}' already exists",
|
||||
name
|
||||
@@ -903,7 +940,11 @@ impl ExfatFs {
|
||||
// ==================== Public File Operations ====================
|
||||
|
||||
/// List files in a specific directory cluster
|
||||
fn list_files_in_directory(&mut self, dir_cluster: u32, current_path: &str) -> Result<Vec<FileInfo>> {
|
||||
fn list_files_in_directory(
|
||||
&mut self,
|
||||
dir_cluster: u32,
|
||||
current_path: &str,
|
||||
) -> Result<Vec<FileInfo>> {
|
||||
let dir_clusters = self.read_cluster_chain(dir_cluster)?;
|
||||
|
||||
// Pre-allocate Vec based on estimated entries
|
||||
@@ -1038,7 +1079,12 @@ impl ExfatFs {
|
||||
}
|
||||
|
||||
/// Write file data to allocated clusters and create directory entry
|
||||
fn write_file_data_and_entry(&mut self, dir_cluster: u32, name: &str, data: &[u8]) -> Result<()> {
|
||||
fn write_file_data_and_entry(
|
||||
&mut self,
|
||||
dir_cluster: u32,
|
||||
name: &str,
|
||||
data: &[u8],
|
||||
) -> Result<()> {
|
||||
// Calculate clusters needed
|
||||
let clusters_needed = if data.is_empty() {
|
||||
0
|
||||
@@ -1121,7 +1167,13 @@ impl ExfatFs {
|
||||
/// Path can include directories, e.g., "iso/linux/ubuntu.iso"
|
||||
/// If create_parents is true, intermediate directories will be created.
|
||||
/// If overwrite is true, existing files will be replaced.
|
||||
pub fn write_file_path(&mut self, path: &str, data: &[u8], create_parents: bool, overwrite: bool) -> Result<()> {
|
||||
pub fn write_file_path(
|
||||
&mut self,
|
||||
path: &str,
|
||||
data: &[u8],
|
||||
create_parents: bool,
|
||||
overwrite: bool,
|
||||
) -> Result<()> {
|
||||
let resolved = self.resolve_path(path, create_parents)?;
|
||||
|
||||
// Validate filename
|
||||
@@ -1177,9 +1229,9 @@ impl ExfatFs {
|
||||
|
||||
/// Read a file from the filesystem (root directory)
|
||||
pub fn read_file(&mut self, name: &str) -> Result<Vec<u8>> {
|
||||
let location = self.find_file_entry(name)?.ok_or_else(|| {
|
||||
VentoyError::FilesystemError(format!("File '{}' not found", name))
|
||||
})?;
|
||||
let location = self
|
||||
.find_file_entry(name)?
|
||||
.ok_or_else(|| VentoyError::FilesystemError(format!("File '{}' not found", name)))?;
|
||||
|
||||
self.read_file_from_location(&location)
|
||||
}
|
||||
@@ -1224,9 +1276,9 @@ impl ExfatFs {
|
||||
|
||||
/// Delete a file from the filesystem (root directory)
|
||||
pub fn delete_file(&mut self, name: &str) -> Result<()> {
|
||||
let location = self.find_file_entry(name)?.ok_or_else(|| {
|
||||
VentoyError::FilesystemError(format!("File '{}' not found", name))
|
||||
})?;
|
||||
let location = self
|
||||
.find_file_entry(name)?
|
||||
.ok_or_else(|| VentoyError::FilesystemError(format!("File '{}' not found", name)))?;
|
||||
|
||||
// Free cluster chain
|
||||
if location.first_cluster >= 2 {
|
||||
@@ -1244,9 +1296,9 @@ impl ExfatFs {
|
||||
pub fn delete_path(&mut self, path: &str) -> Result<()> {
|
||||
let resolved = self.resolve_path(path, false)?;
|
||||
|
||||
let location = resolved.location.ok_or_else(|| {
|
||||
VentoyError::FilesystemError(format!("'{}' not found", path))
|
||||
})?;
|
||||
let location = resolved
|
||||
.location
|
||||
.ok_or_else(|| VentoyError::FilesystemError(format!("'{}' not found", path)))?;
|
||||
|
||||
// If it's a directory, check if it's empty
|
||||
if location.is_directory {
|
||||
@@ -1275,9 +1327,9 @@ impl ExfatFs {
|
||||
pub fn delete_recursive(&mut self, path: &str) -> Result<()> {
|
||||
let resolved = self.resolve_path(path, false)?;
|
||||
|
||||
let location = resolved.location.ok_or_else(|| {
|
||||
VentoyError::FilesystemError(format!("'{}' not found", path))
|
||||
})?;
|
||||
let location = resolved
|
||||
.location
|
||||
.ok_or_else(|| VentoyError::FilesystemError(format!("'{}' not found", path)))?;
|
||||
|
||||
if location.is_directory {
|
||||
// Get all contents and delete them first
|
||||
@@ -1344,7 +1396,12 @@ impl<'a> ExfatFileWriter<'a> {
|
||||
}
|
||||
|
||||
/// Create a new file writer with overwrite option
|
||||
pub fn create_overwrite(fs: &'a mut ExfatFs, name: &str, total_size: u64, overwrite: bool) -> Result<Self> {
|
||||
pub fn create_overwrite(
|
||||
fs: &'a mut ExfatFs,
|
||||
name: &str,
|
||||
total_size: u64,
|
||||
overwrite: bool,
|
||||
) -> Result<Self> {
|
||||
let root_cluster = fs.first_cluster_of_root;
|
||||
Self::create_in_directory(fs, root_cluster, name, total_size, overwrite)
|
||||
}
|
||||
@@ -1353,7 +1410,13 @@ impl<'a> ExfatFileWriter<'a> {
|
||||
///
|
||||
/// If create_parents is true, intermediate directories will be created.
|
||||
/// If overwrite is true, existing files will be replaced.
|
||||
pub fn create_at_path(fs: &'a mut ExfatFs, path: &str, total_size: u64, create_parents: bool, overwrite: bool) -> Result<Self> {
|
||||
pub fn create_at_path(
|
||||
fs: &'a mut ExfatFs,
|
||||
path: &str,
|
||||
total_size: u64,
|
||||
create_parents: bool,
|
||||
overwrite: bool,
|
||||
) -> Result<Self> {
|
||||
let resolved = fs.resolve_path(path, create_parents)?;
|
||||
|
||||
// Handle existing file
|
||||
@@ -1378,11 +1441,23 @@ impl<'a> ExfatFileWriter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
Self::create_in_directory(fs, resolved.parent_cluster, &resolved.name, total_size, false)
|
||||
Self::create_in_directory(
|
||||
fs,
|
||||
resolved.parent_cluster,
|
||||
&resolved.name,
|
||||
total_size,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Internal: Create a file writer in a specific directory
|
||||
fn create_in_directory(fs: &'a mut ExfatFs, dir_cluster: u32, name: &str, total_size: u64, overwrite: bool) -> Result<Self> {
|
||||
fn create_in_directory(
|
||||
fs: &'a mut ExfatFs,
|
||||
dir_cluster: u32,
|
||||
name: &str,
|
||||
total_size: u64,
|
||||
overwrite: bool,
|
||||
) -> Result<Self> {
|
||||
// Validate filename
|
||||
if name.is_empty() || name.len() > 255 {
|
||||
return Err(VentoyError::FilesystemError(
|
||||
@@ -1473,7 +1548,9 @@ impl<'a> ExfatFileWriter<'a> {
|
||||
/// This must be called after all data has been written.
|
||||
pub fn finish(self) -> Result<()> {
|
||||
// Write any remaining data in buffer
|
||||
if !self.cluster_buffer.is_empty() && self.current_cluster_index < self.allocated_clusters.len() {
|
||||
if !self.cluster_buffer.is_empty()
|
||||
&& self.current_cluster_index < self.allocated_clusters.len()
|
||||
{
|
||||
let cluster = self.allocated_clusters[self.current_cluster_index];
|
||||
self.fs.write_cluster(cluster, &self.cluster_buffer)?;
|
||||
}
|
||||
@@ -1485,7 +1562,13 @@ impl<'a> ExfatFileWriter<'a> {
|
||||
self.allocated_clusters[0]
|
||||
};
|
||||
|
||||
self.fs.create_entry_in_directory(self.dir_cluster, &self.name, first_cluster, self.total_size, false)?;
|
||||
self.fs.create_entry_in_directory(
|
||||
self.dir_cluster,
|
||||
&self.name,
|
||||
first_cluster,
|
||||
self.total_size,
|
||||
false,
|
||||
)?;
|
||||
self.fs.file.flush()?;
|
||||
|
||||
Ok(())
|
||||
@@ -1517,9 +1600,9 @@ pub struct ExfatFileReader<'a> {
|
||||
impl<'a> ExfatFileReader<'a> {
|
||||
/// Open a file for reading from root directory
|
||||
pub fn open(fs: &'a mut ExfatFs, name: &str) -> Result<Self> {
|
||||
let location = fs.find_file_entry(name)?.ok_or_else(|| {
|
||||
VentoyError::FilesystemError(format!("File '{}' not found", name))
|
||||
})?;
|
||||
let location = fs
|
||||
.find_file_entry(name)?
|
||||
.ok_or_else(|| VentoyError::FilesystemError(format!("File '{}' not found", name)))?;
|
||||
|
||||
if location.is_directory {
|
||||
return Err(VentoyError::FilesystemError(format!(
|
||||
@@ -1635,7 +1718,8 @@ impl<'a> Read for ExfatFileReader<'a> {
|
||||
|
||||
// Read the cluster and copy data
|
||||
{
|
||||
let cluster_data = self.read_cluster_cached(cluster_index)
|
||||
let cluster_data = self
|
||||
.read_cluster_cached(cluster_index)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
|
||||
|
||||
// Copy data to buffer
|
||||
@@ -1676,11 +1760,7 @@ impl ExfatFs {
|
||||
/// Read a file to a writer (streaming)
|
||||
///
|
||||
/// This is useful for reading large files without loading them into memory.
|
||||
pub fn read_file_to_writer<W: Write>(
|
||||
&mut self,
|
||||
name: &str,
|
||||
writer: &mut W,
|
||||
) -> Result<u64> {
|
||||
pub fn read_file_to_writer<W: Write>(&mut self, name: &str, writer: &mut W) -> Result<u64> {
|
||||
let mut reader = ExfatFileReader::open(self, name)?;
|
||||
Self::do_stream_read(&mut reader, writer)
|
||||
}
|
||||
@@ -1701,13 +1781,13 @@ impl ExfatFs {
|
||||
let mut total_bytes = 0u64;
|
||||
|
||||
loop {
|
||||
let bytes_read = reader.read(&mut buffer).map_err(|e| {
|
||||
VentoyError::Io(e)
|
||||
})?;
|
||||
let bytes_read = reader.read(&mut buffer).map_err(|e| VentoyError::Io(e))?;
|
||||
if bytes_read == 0 {
|
||||
break;
|
||||
}
|
||||
writer.write_all(&buffer[..bytes_read]).map_err(VentoyError::Io)?;
|
||||
writer
|
||||
.write_all(&buffer[..bytes_read])
|
||||
.map_err(VentoyError::Io)?;
|
||||
total_bytes += bytes_read as u64;
|
||||
}
|
||||
|
||||
@@ -1755,7 +1835,8 @@ impl ExfatFs {
|
||||
create_parents: bool,
|
||||
overwrite: bool,
|
||||
) -> Result<()> {
|
||||
let mut writer = ExfatFileWriter::create_at_path(self, path, size, create_parents, overwrite)?;
|
||||
let mut writer =
|
||||
ExfatFileWriter::create_at_path(self, path, size, create_parents, overwrite)?;
|
||||
Self::do_stream_write(&mut writer, reader)?;
|
||||
writer.finish()
|
||||
}
|
||||
@@ -1804,8 +1885,13 @@ mod tests {
|
||||
file.set_len(size).unwrap();
|
||||
|
||||
// Format data partition (this will use 4KB clusters for 64MB volume)
|
||||
crate::exfat::format::format_exfat(&mut file, layout.data_offset(), layout.data_size(), "TEST")
|
||||
.unwrap();
|
||||
crate::exfat::format::format_exfat(
|
||||
&mut file,
|
||||
layout.data_offset(),
|
||||
layout.data_size(),
|
||||
"TEST",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
drop(file);
|
||||
|
||||
@@ -1826,21 +1912,12 @@ mod tests {
|
||||
let data = format!("content {}", i);
|
||||
let mut cursor = Cursor::new(data.as_bytes());
|
||||
|
||||
fs.write_file_from_reader(
|
||||
&filename,
|
||||
&mut cursor,
|
||||
data.len() as u64,
|
||||
)?;
|
||||
fs.write_file_from_reader(&filename, &mut cursor, data.len() as u64)?;
|
||||
}
|
||||
|
||||
// Verify all files were created
|
||||
let files = fs.list_files().unwrap();
|
||||
assert_eq!(
|
||||
files.len(),
|
||||
50,
|
||||
"Expected 50 files, found {}",
|
||||
files.len()
|
||||
);
|
||||
assert_eq!(files.len(), 50, "Expected 50 files, found {}", files.len());
|
||||
|
||||
// Verify we can read all files back
|
||||
for i in 0..50 {
|
||||
@@ -1882,8 +1959,13 @@ mod tests {
|
||||
file.set_len(size).unwrap();
|
||||
|
||||
// Format data partition
|
||||
crate::exfat::format::format_exfat(&mut file, layout.data_offset(), layout.data_size(), "TEST")
|
||||
.unwrap();
|
||||
crate::exfat::format::format_exfat(
|
||||
&mut file,
|
||||
layout.data_offset(),
|
||||
layout.data_size(),
|
||||
"TEST",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
drop(file);
|
||||
|
||||
@@ -1903,7 +1985,8 @@ mod tests {
|
||||
assert_eq!(reader.position(), 0);
|
||||
|
||||
let mut read_data = Vec::new();
|
||||
let bytes_read = reader.read_to_end(&mut read_data)
|
||||
let bytes_read = reader
|
||||
.read_to_end(&mut read_data)
|
||||
.map_err(|e| VentoyError::Io(e))?;
|
||||
|
||||
assert_eq!(bytes_read, test_data.len());
|
||||
@@ -1932,22 +2015,32 @@ mod tests {
|
||||
let mut reader = ExfatFileReader::open(&mut fs, "large_file.bin")?;
|
||||
|
||||
// Seek to middle
|
||||
reader.seek(SeekFrom::Start(10000)).map_err(|e| VentoyError::Io(e))?;
|
||||
reader
|
||||
.seek(SeekFrom::Start(10000))
|
||||
.map_err(|e| VentoyError::Io(e))?;
|
||||
assert_eq!(reader.position(), 10000);
|
||||
|
||||
let mut buffer = [0u8; 10];
|
||||
reader.read_exact(&mut buffer).map_err(|e| VentoyError::Io(e))?;
|
||||
reader
|
||||
.read_exact(&mut buffer)
|
||||
.map_err(|e| VentoyError::Io(e))?;
|
||||
assert_eq!(&buffer, &test_data[10000..10010]);
|
||||
|
||||
// Seek from current position
|
||||
reader.seek(SeekFrom::Current(-5)).map_err(|e| VentoyError::Io(e))?;
|
||||
reader
|
||||
.seek(SeekFrom::Current(-5))
|
||||
.map_err(|e| VentoyError::Io(e))?;
|
||||
assert_eq!(reader.position(), 10005);
|
||||
|
||||
// Seek from end
|
||||
reader.seek(SeekFrom::End(-100)).map_err(|e| VentoyError::Io(e))?;
|
||||
reader
|
||||
.seek(SeekFrom::End(-100))
|
||||
.map_err(|e| VentoyError::Io(e))?;
|
||||
assert_eq!(reader.position(), test_data.len() as u64 - 100);
|
||||
|
||||
reader.read_exact(&mut buffer).map_err(|e| VentoyError::Io(e))?;
|
||||
reader
|
||||
.read_exact(&mut buffer)
|
||||
.map_err(|e| VentoyError::Io(e))?;
|
||||
let expected_start = test_data.len() - 100;
|
||||
assert_eq!(&buffer, &test_data[expected_start..expected_start + 10]);
|
||||
}
|
||||
@@ -1981,8 +2074,13 @@ mod tests {
|
||||
file.set_len(size).unwrap();
|
||||
|
||||
// Format data partition
|
||||
crate::exfat::format::format_exfat(&mut file, layout.data_offset(), layout.data_size(), "TEST")
|
||||
.unwrap();
|
||||
crate::exfat::format::format_exfat(
|
||||
&mut file,
|
||||
layout.data_offset(),
|
||||
layout.data_size(),
|
||||
"TEST",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
drop(file);
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ pub fn to_uppercase_simple(ch: u16) -> u16 {
|
||||
// Greek lowercase (α-ω and variants)
|
||||
0x03B1..=0x03C1 => ch - 32, // α-ρ -> Α-Ρ
|
||||
0x03C3..=0x03C9 => ch - 32, // σ-ω -> Σ-Ω
|
||||
0x03C2 => 0x03A3, // ς (final sigma) -> Σ
|
||||
0x03C2 => 0x03A3, // ς (final sigma) -> Σ
|
||||
|
||||
// Cyrillic lowercase (а-я)
|
||||
0x0430..=0x044F => ch - 32, // а-я -> А-Я
|
||||
|
||||
@@ -22,7 +22,11 @@ impl VentoyImage {
|
||||
let size = parse_size(size_str)?;
|
||||
let layout = PartitionLayout::calculate(size)?;
|
||||
|
||||
println!("[INFO] Creating {}MB image: {}", size / (1024 * 1024), path.display());
|
||||
println!(
|
||||
"[INFO] Creating {}MB image: {}",
|
||||
size / (1024 * 1024),
|
||||
path.display()
|
||||
);
|
||||
|
||||
// Create sparse file
|
||||
let mut file = File::create(path)?;
|
||||
@@ -247,7 +251,11 @@ impl VentoyImage {
|
||||
///
|
||||
/// This is the preferred method for large files as it doesn't load
|
||||
/// the entire file into memory.
|
||||
pub fn read_file_to_writer<W: std::io::Write>(&self, path: &str, writer: &mut W) -> Result<u64> {
|
||||
pub fn read_file_to_writer<W: std::io::Write>(
|
||||
&self,
|
||||
path: &str,
|
||||
writer: &mut W,
|
||||
) -> Result<u64> {
|
||||
let mut fs = ExfatFs::open(&self.path, &self.layout)?;
|
||||
fs.read_file_path_to_writer(path, writer)
|
||||
}
|
||||
|
||||
@@ -45,4 +45,4 @@ pub use error::{Result, VentoyError};
|
||||
pub use exfat::FileInfo;
|
||||
pub use image::VentoyImage;
|
||||
pub use partition::{parse_size, PartitionLayout};
|
||||
pub use resources::{init_resources, get_resource_dir, is_initialized, required_files};
|
||||
pub use resources::{get_resource_dir, init_resources, is_initialized, required_files};
|
||||
|
||||
@@ -4,7 +4,7 @@ use clap::{Parser, Subcommand};
|
||||
use std::path::PathBuf;
|
||||
use std::process::ExitCode;
|
||||
|
||||
use ventoy_img::{VentoyImage, Result, VentoyError};
|
||||
use ventoy_img::{Result, VentoyError, VentoyImage};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "ventoy-img")]
|
||||
@@ -103,11 +103,33 @@ fn main() -> ExitCode {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let result = match cli.command {
|
||||
Commands::Create { size, output, label } => cmd_create(&output, &size, &label),
|
||||
Commands::Add { image, file, dest, force, parents } => cmd_add(&image, &file, dest.as_deref(), force, parents),
|
||||
Commands::List { image, path, recursive } => cmd_list(&image, path.as_deref(), recursive),
|
||||
Commands::Remove { image, path, recursive } => cmd_remove(&image, &path, recursive),
|
||||
Commands::Mkdir { image, path, parents } => cmd_mkdir(&image, &path, parents),
|
||||
Commands::Create {
|
||||
size,
|
||||
output,
|
||||
label,
|
||||
} => cmd_create(&output, &size, &label),
|
||||
Commands::Add {
|
||||
image,
|
||||
file,
|
||||
dest,
|
||||
force,
|
||||
parents,
|
||||
} => cmd_add(&image, &file, dest.as_deref(), force, parents),
|
||||
Commands::List {
|
||||
image,
|
||||
path,
|
||||
recursive,
|
||||
} => cmd_list(&image, path.as_deref(), recursive),
|
||||
Commands::Remove {
|
||||
image,
|
||||
path,
|
||||
recursive,
|
||||
} => cmd_remove(&image, &path, recursive),
|
||||
Commands::Mkdir {
|
||||
image,
|
||||
path,
|
||||
parents,
|
||||
} => cmd_mkdir(&image, &path, parents),
|
||||
Commands::Info { image } => cmd_info(&image),
|
||||
};
|
||||
|
||||
@@ -138,7 +160,13 @@ fn cmd_create(output: &PathBuf, size: &str, label: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cmd_add(image: &PathBuf, file: &PathBuf, dest: Option<&str>, force: bool, parents: bool) -> Result<()> {
|
||||
fn cmd_add(
|
||||
image: &PathBuf,
|
||||
file: &PathBuf,
|
||||
dest: Option<&str>,
|
||||
force: bool,
|
||||
parents: bool,
|
||||
) -> Result<()> {
|
||||
if !file.exists() {
|
||||
return Err(VentoyError::FileNotFound(file.display().to_string()));
|
||||
}
|
||||
@@ -234,18 +262,23 @@ fn cmd_info(image: &PathBuf) -> Result<()> {
|
||||
println!();
|
||||
println!("Partition Layout:");
|
||||
println!(" Data partition:");
|
||||
println!(" Start: sector {} (offset {})",
|
||||
println!(
|
||||
" Start: sector {} (offset {})",
|
||||
layout.data_start_sector,
|
||||
format_size(layout.data_offset()));
|
||||
println!(" Size: {} sectors ({})",
|
||||
format_size(layout.data_offset())
|
||||
);
|
||||
println!(
|
||||
" Size: {} sectors ({})",
|
||||
layout.data_size_sectors,
|
||||
format_size(layout.data_size()));
|
||||
format_size(layout.data_size())
|
||||
);
|
||||
println!(" EFI partition:");
|
||||
println!(" Start: sector {} (offset {})",
|
||||
println!(
|
||||
" Start: sector {} (offset {})",
|
||||
layout.efi_start_sector,
|
||||
format_size(layout.efi_offset()));
|
||||
println!(" Size: {} sectors (32 MB)",
|
||||
layout.efi_size_sectors);
|
||||
format_size(layout.efi_offset())
|
||||
);
|
||||
println!(" Size: {} sectors (32 MB)", layout.efi_size_sectors);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user