Add method for renaming/moving a file
This commit is contained in:
parent
21b51af6d5
commit
66c976bc3b
1
TODO.md
1
TODO.md
@ -1,5 +1,4 @@
|
||||
TODO
|
||||
====
|
||||
* move file API
|
||||
* format volume API
|
||||
* better no_std support
|
||||
|
80
src/dir.rs
80
src/dir.rs
@ -147,7 +147,8 @@ impl <'a, 'b> Dir<'a, 'b> {
|
||||
match r {
|
||||
Err(ref err) if err.kind() == ErrorKind::NotFound => {
|
||||
let short_name = short_name_gen.generate()?;
|
||||
Ok(self.create_entry(name, short_name, FileAttributes::from_bits_truncate(0), None)?.to_file())
|
||||
let sfn_entry = self.create_sfn_entry(short_name, FileAttributes::from_bits_truncate(0), None);
|
||||
Ok(self.write_entry(name, sfn_entry)?.to_file())
|
||||
},
|
||||
Err(err) => Err(err),
|
||||
Ok(e) => Ok(e.to_file()),
|
||||
@ -172,13 +173,16 @@ impl <'a, 'b> Dir<'a, 'b> {
|
||||
let cluster = self.fs.alloc_cluster(None)?;
|
||||
// create entry in parent directory
|
||||
let short_name = short_name_gen.generate()?;
|
||||
let entry = self.create_entry(name, short_name, FileAttributes::DIRECTORY, Some(cluster))?;
|
||||
let sfn_entry = self.create_sfn_entry(short_name, FileAttributes::DIRECTORY, Some(cluster));
|
||||
let entry = self.write_entry(name, sfn_entry)?;
|
||||
let mut dir = entry.to_dir();
|
||||
// create special entries "." and ".."
|
||||
let dot_sfn = ShortNameGenerator::new(".").generate().unwrap();
|
||||
dir.create_entry(".", dot_sfn, FileAttributes::DIRECTORY, entry.first_cluster())?;
|
||||
let sfn_entry = self.create_sfn_entry(dot_sfn, FileAttributes::DIRECTORY, entry.first_cluster());
|
||||
dir.write_entry(".", sfn_entry)?;
|
||||
let dotdot_sfn = ShortNameGenerator::new("..").generate().unwrap();
|
||||
dir.create_entry("..", dotdot_sfn, FileAttributes::DIRECTORY, self.stream.first_cluster())?;
|
||||
let sfn_entry = self.create_sfn_entry(dotdot_sfn, FileAttributes::DIRECTORY, self.stream.first_cluster());
|
||||
dir.write_entry("..", sfn_entry)?;
|
||||
Ok(dir)
|
||||
},
|
||||
Err(err) => Err(err),
|
||||
@ -236,6 +240,56 @@ impl <'a, 'b> Dir<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Renames or moves existing file or directory.
|
||||
///
|
||||
/// Destination directory can be cloned source directory in case of rename without moving operation.
|
||||
/// Make sure there is no reference to this file (no File instance) or filesystem corruption
|
||||
/// can happen.
|
||||
pub fn rename(&mut self, src_path: &str, dst_dir: &mut Dir, dst_path: &str) -> io::Result<()> {
|
||||
// traverse source path
|
||||
let (name, rest_opt) = split_path(src_path);
|
||||
if let Some(rest) = rest_opt {
|
||||
let e = self.find_entry(name, None)?;
|
||||
return e.to_dir().rename(rest, dst_dir, dst_path);
|
||||
}
|
||||
// traverse destination path
|
||||
let (name, rest_opt) = split_path(dst_path);
|
||||
if let Some(rest) = rest_opt {
|
||||
let e = dst_dir.find_entry(name, None)?;
|
||||
return self.rename(src_path, &mut e.to_dir(), rest);
|
||||
}
|
||||
// move/rename file
|
||||
self.rename_internal(src_path, dst_dir, dst_path)
|
||||
}
|
||||
|
||||
fn rename_internal(&mut self, src_name: &str, dst_dir: &mut Dir, dst_name: &str) -> io::Result<()> {
|
||||
trace!("moving {} to {}", src_name, dst_name);
|
||||
// find existing file
|
||||
let e = self.find_entry(src_name, None)?;
|
||||
// free long and short name entries
|
||||
let mut stream = self.stream.clone();
|
||||
stream.seek(SeekFrom::Start(e.offset_range.0 as u64))?;
|
||||
let num = (e.offset_range.1 - e.offset_range.0) as usize / DIR_ENTRY_SIZE as usize;
|
||||
for _ in 0..num {
|
||||
let mut data = DirEntryData::deserialize(&mut stream)?;
|
||||
trace!("removing LFN entry {:?}", data);
|
||||
data.set_free();
|
||||
stream.seek(SeekFrom::Current(-(DIR_ENTRY_SIZE as i64)))?;
|
||||
data.serialize(&mut stream)?;
|
||||
}
|
||||
// check if destionation filename is unused
|
||||
let mut short_name_gen = ShortNameGenerator::new(dst_name);
|
||||
let r = dst_dir.find_entry(dst_name, Some(&mut short_name_gen));
|
||||
if r.is_ok() {
|
||||
return Err(io::Error::new(ErrorKind::AlreadyExists, "destination file already exists"))
|
||||
}
|
||||
// save new directory entry
|
||||
let short_name = short_name_gen.generate()?;
|
||||
let sfn_entry = e.data.renamed(short_name);
|
||||
dst_dir.write_entry(dst_name, sfn_entry)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_free_entries(&mut self, num_entries: usize) -> io::Result<DirRawStream<'a, 'b>> {
|
||||
let mut stream = self.stream.clone();
|
||||
let mut first_free = 0;
|
||||
@ -293,18 +347,22 @@ impl <'a, 'b> Dir<'a, 'b> {
|
||||
Ok((stream, start_pos))
|
||||
}
|
||||
|
||||
fn create_entry(&mut self, name: &str, short_name: [u8; 11], attrs: FileAttributes, first_cluster: Option<u32>) -> io::Result<DirEntry<'a, 'b>> {
|
||||
trace!("create_entry {}", name);
|
||||
// check if name doesn't contain unsupported characters
|
||||
validate_long_name(name)?;
|
||||
// generate long entries
|
||||
let (mut stream, start_pos) = self.create_lfn_entries(&name, &short_name)?;
|
||||
// create and write short name entry
|
||||
fn create_sfn_entry(&self, short_name: [u8; 11], attrs: FileAttributes, first_cluster: Option<u32>) -> DirFileEntryData {
|
||||
let mut raw_entry = DirFileEntryData::new(short_name, attrs);
|
||||
raw_entry.set_first_cluster(first_cluster, self.fs.fat_type());
|
||||
raw_entry.reset_created();
|
||||
raw_entry.reset_accessed();
|
||||
raw_entry.reset_modified();
|
||||
raw_entry
|
||||
}
|
||||
|
||||
fn write_entry(&mut self, name: &str, raw_entry: DirFileEntryData) -> io::Result<DirEntry<'a, 'b>> {
|
||||
trace!("write_entry {}", name);
|
||||
// check if name doesn't contain unsupported characters
|
||||
validate_long_name(name)?;
|
||||
// generate long entries
|
||||
let (mut stream, start_pos) = self.create_lfn_entries(&name, raw_entry.name())?;
|
||||
// write short name entry
|
||||
raw_entry.serialize(&mut stream)?;
|
||||
let end_pos = stream.seek(io::SeekFrom::Current(0))?;
|
||||
let abs_pos = stream.abs_pos().map(|p| p - DIR_ENTRY_SIZE);
|
||||
|
@ -101,6 +101,12 @@ impl DirFileEntryData {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn renamed(&self, new_name: [u8; 11]) -> Self {
|
||||
let mut sfn_entry = self.clone();
|
||||
sfn_entry.name = new_name;
|
||||
sfn_entry
|
||||
}
|
||||
|
||||
pub(crate) fn name(&self) -> &[u8; 11] {
|
||||
&self.name
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ const FAT32_IMG: &str = "fat32.img";
|
||||
const IMG_DIR: &str = "resources";
|
||||
const TMP_DIR: &str = "tmp";
|
||||
const TEST_STR: &str = "Hi there Rust programmer!\n";
|
||||
const TEST_STR2: &str = "Rust is cool!\n";
|
||||
|
||||
fn call_with_fs(f: &Fn(FileSystem) -> (), filename: &str, test_seq: u32) {
|
||||
let _ = env_logger::try_init();
|
||||
@ -219,3 +220,52 @@ fn test_create_dir_fat16() {
|
||||
fn test_create_dir_fat32() {
|
||||
call_with_fs(&test_create_dir, FAT32_IMG, 5)
|
||||
}
|
||||
|
||||
fn test_rename_file(fs: FileSystem) {
|
||||
let mut root_dir = fs.root_dir();
|
||||
let mut parent_dir = root_dir.open_dir("very/long/path").unwrap();
|
||||
let entries = parent_dir.iter().map(|r| r.unwrap()).collect::<Vec<_>>();
|
||||
let names = entries.iter().map(|r| r.file_name()).collect::<Vec<_>>();
|
||||
assert_eq!(names, [".", "..", "test.txt"]);
|
||||
assert_eq!(entries[2].len(), 14);
|
||||
let stats = fs.stats().unwrap();
|
||||
|
||||
let mut parent_dir_cloned = parent_dir.clone();
|
||||
parent_dir.rename("test.txt", &mut parent_dir_cloned, "new-long-name.txt").unwrap();
|
||||
let entries = parent_dir.iter().map(|r| r.unwrap()).collect::<Vec<_>>();
|
||||
let names = entries.iter().map(|r| r.file_name()).collect::<Vec<_>>();
|
||||
assert_eq!(names, [".", "..", "new-long-name.txt"]);
|
||||
assert_eq!(entries[2].len(), TEST_STR2.len() as u64);
|
||||
let mut file = parent_dir.open_file("new-long-name.txt").unwrap();
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).unwrap();
|
||||
assert_eq!(str::from_utf8(&buf).unwrap(), TEST_STR2);
|
||||
|
||||
parent_dir.rename("new-long-name.txt", &mut root_dir, "moved-file.txt").unwrap();
|
||||
let entries = root_dir.iter().map(|r| r.unwrap()).collect::<Vec<_>>();
|
||||
let names = entries.iter().map(|r| r.file_name()).collect::<Vec<_>>();
|
||||
assert_eq!(names, ["long.txt", "short.txt", "very", "very-long-dir-name", "moved-file.txt"]);
|
||||
assert_eq!(entries[4].len(), TEST_STR2.len() as u64);
|
||||
let mut file = root_dir.open_file("moved-file.txt").unwrap();
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf).unwrap();
|
||||
assert_eq!(str::from_utf8(&buf).unwrap(), TEST_STR2);
|
||||
|
||||
let new_stats = fs.stats().unwrap();
|
||||
assert_eq!(new_stats.free_clusters, stats.free_clusters);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rename_file_fat12() {
|
||||
call_with_fs(&test_rename_file, FAT12_IMG, 6)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rename_file_fat16() {
|
||||
call_with_fs(&test_rename_file, FAT16_IMG, 6)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rename_file_fat32() {
|
||||
call_with_fs(&test_rename_file, FAT32_IMG, 6)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user