Add method for renaming/moving a file

This commit is contained in:
Rafał Harabień 2018-06-06 13:56:59 +02:00
parent 21b51af6d5
commit 66c976bc3b
4 changed files with 125 additions and 12 deletions

View File

@ -1,5 +1,4 @@
TODO
====
* move file API
* format volume API
* better no_std support

View File

@ -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);

View File

@ -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
}

View File

@ -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)
}