Support reading volume label from root directory
This commit is contained in:
parent
5dea8b5f8a
commit
6f10042784
1
TODO.md
1
TODO.md
@ -1,6 +1,5 @@
|
||||
TODO
|
||||
====
|
||||
* support for a volume label file in the root directory
|
||||
* format volume API
|
||||
* add method for getting `DirEntry` from a path (possible names: metadata, lookup)
|
||||
* do not create LFN entries if the name fits in a SFN entry
|
||||
|
52
src/dir.rs
52
src/dir.rs
@ -106,11 +106,7 @@ impl<'a, T: ReadWriteSeek + 'a> Dir<'a, T> {
|
||||
|
||||
/// Creates directory entries iterator.
|
||||
pub fn iter(&self) -> DirIter<'a, T> {
|
||||
DirIter {
|
||||
stream: self.stream.clone(),
|
||||
fs: self.fs,
|
||||
err: false,
|
||||
}
|
||||
DirIter::new(self.stream.clone(), self.fs, true)
|
||||
}
|
||||
|
||||
fn find_entry(
|
||||
@ -138,6 +134,16 @@ impl<'a, T: ReadWriteSeek + 'a> Dir<'a, T> {
|
||||
Err(io::Error::new(ErrorKind::NotFound, "No such file or directory"))
|
||||
}
|
||||
|
||||
pub(crate) fn find_volume_entry(&self) -> io::Result<Option<DirEntry<'a, T>>> {
|
||||
for r in DirIter::new(self.stream.clone(), self.fs, false) {
|
||||
let e = r?;
|
||||
if e.data.is_volume() {
|
||||
return Ok(Some(e));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn check_for_existence(&self, name: &str, is_dir: Option<bool>) -> io::Result<DirEntryOrShortName<'a, T>> {
|
||||
let mut short_name_gen = ShortNameGenerator::new(name);
|
||||
loop {
|
||||
@ -453,10 +459,28 @@ impl<'a, T: ReadWriteSeek> Clone for Dir<'a, T> {
|
||||
pub struct DirIter<'a, T: ReadWriteSeek + 'a> {
|
||||
stream: DirRawStream<'a, T>,
|
||||
fs: &'a FileSystem<T>,
|
||||
skip_volume: bool,
|
||||
err: bool,
|
||||
}
|
||||
|
||||
impl<'a, T: ReadWriteSeek> DirIter<'a, T> {
|
||||
fn new(stream: DirRawStream<'a, T>, fs: &'a FileSystem<T>, skip_volume: bool) -> Self {
|
||||
DirIter {
|
||||
stream, fs, skip_volume,
|
||||
err: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn should_ship_entry(&self, raw_entry: &DirEntryData) -> bool {
|
||||
if raw_entry.is_deleted() {
|
||||
return true;
|
||||
}
|
||||
match raw_entry {
|
||||
DirEntryData::File(sfn_entry) => self.skip_volume && sfn_entry.is_volume(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_dir_entry(&mut self) -> io::Result<Option<DirEntry<'a, T>>> {
|
||||
#[cfg(feature = "alloc")]
|
||||
let mut lfn_buf = LongNameBuilder::new();
|
||||
@ -465,19 +489,19 @@ impl<'a, T: ReadWriteSeek> DirIter<'a, T> {
|
||||
loop {
|
||||
let raw_entry = DirEntryData::deserialize(&mut self.stream)?;
|
||||
offset += DIR_ENTRY_SIZE;
|
||||
match raw_entry {
|
||||
DirEntryData::File(data) => {
|
||||
// Check if this is end of dif
|
||||
if data.is_end() {
|
||||
// Check if this is end of dir
|
||||
if raw_entry.is_end() {
|
||||
return Ok(None);
|
||||
}
|
||||
// Check if this is deleted or volume ID entry
|
||||
if data.is_deleted() || data.is_volume() {
|
||||
if self.should_ship_entry(&raw_entry) {
|
||||
#[cfg(feature = "alloc")]
|
||||
lfn_buf.clear();
|
||||
begin_offset = offset;
|
||||
continue;
|
||||
}
|
||||
match raw_entry {
|
||||
DirEntryData::File(data) => {
|
||||
// Get entry position on volume
|
||||
let abs_pos = self.stream.abs_pos().map(|p| p - DIR_ENTRY_SIZE);
|
||||
// Check if LFN checksum is valid
|
||||
@ -496,13 +520,6 @@ impl<'a, T: ReadWriteSeek> DirIter<'a, T> {
|
||||
}));
|
||||
},
|
||||
DirEntryData::Lfn(data) => {
|
||||
// Check if this is deleted entry
|
||||
if data.is_deleted() {
|
||||
#[cfg(feature = "alloc")]
|
||||
lfn_buf.clear();
|
||||
begin_offset = offset;
|
||||
continue;
|
||||
}
|
||||
// Append to LFN buffer
|
||||
#[cfg(feature = "alloc")]
|
||||
lfn_buf.process(&data);
|
||||
@ -519,6 +536,7 @@ impl<'a, T: ReadWriteSeek> Clone for DirIter<'a, T> {
|
||||
stream: self.stream.clone(),
|
||||
fs: self.fs,
|
||||
err: self.err,
|
||||
skip_volume: self.skip_volume,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
35
src/fs.rs
35
src/fs.rs
@ -494,11 +494,44 @@ impl<T: ReadWriteSeek> FileSystem<T> {
|
||||
/// Note: File with `VOLUME_ID` attribute in root directory is ignored by this library.
|
||||
/// Only label from BPB is used.
|
||||
pub fn volume_label_as_bytes(&self) -> &[u8] {
|
||||
const PADDING: u8 = 0x20;
|
||||
let full_label_slice = &self.bpb.volume_label;
|
||||
let len = full_label_slice.iter().rposition(|b| *b != 0x20).map(|p| p + 1).unwrap_or(0);
|
||||
let len = full_label_slice.iter().rposition(|b| *b != PADDING).map(|p| p + 1).unwrap_or(0);
|
||||
&full_label_slice[..len]
|
||||
}
|
||||
|
||||
/// Returns a volume label from root directory as `String`.
|
||||
///
|
||||
/// It finds file with `VOLUME_ID` attribute and returns its short name.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn read_volume_label_from_root_dir(&self) -> io::Result<Option<String>> {
|
||||
// Note: DirEntry::file_short_name() cannot be used because it interprets name as 8.3
|
||||
// (adds dot before an extension)
|
||||
let volume_label_opt = self.read_volume_label_from_root_dir_as_bytes()?;
|
||||
if let Some(volume_label) = volume_label_opt {
|
||||
const PADDING: u8 = 0x20;
|
||||
// Strip label padding
|
||||
let len = volume_label.iter().rposition(|b| *b != PADDING).map(|p| p + 1).unwrap_or(0);
|
||||
let label_slice = &volume_label[..len];
|
||||
// Decode volume label from OEM codepage
|
||||
let volume_label_iter = label_slice.iter().cloned();
|
||||
let char_iter = volume_label_iter.map(|c| self.options.oem_cp_converter.decode(c));
|
||||
// Build string from character iterator
|
||||
Ok(Some(String::from_iter(char_iter)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a volume label from root directory as byte array.
|
||||
///
|
||||
/// Label is encoded in the OEM codepage.
|
||||
/// It finds file with `VOLUME_ID` attribute and returns its short name.
|
||||
pub fn read_volume_label_from_root_dir_as_bytes(&self) -> io::Result<Option<[u8; 11]>> {
|
||||
let entry_opt = self.root_dir().find_volume_entry()?;
|
||||
Ok(entry_opt.map(|e| *e.raw_short_name()))
|
||||
}
|
||||
|
||||
/// Returns a root directory object allowing for futher penetration of a filesystem structure.
|
||||
pub fn root_dir<'b>(&'b self) -> Dir<'b, T> {
|
||||
let root_rdr = {
|
||||
|
@ -187,6 +187,7 @@ fn test_get_file_by_path_fat32() {
|
||||
fn test_volume_metadata(fs: FileSystem, fat_type: FatType) {
|
||||
assert_eq!(fs.volume_id(), 0x12345678);
|
||||
assert_eq!(fs.volume_label(), "Test!");
|
||||
assert_eq!(&fs.read_volume_label_from_root_dir().unwrap().unwrap(), "Test!");
|
||||
assert_eq!(fs.fat_type(), fat_type);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user