Add automatic update of timestamps (created, accessed, modified).

Works only if chrono feature is enabled (default).
This commit is contained in:
Rafał Harabień 2017-11-08 00:18:31 +01:00
parent cdde3ca468
commit b38ea78f3e
7 changed files with 116 additions and 16 deletions

View File

@ -10,7 +10,7 @@ use fatfs::{FileSystem, BufStream};
fn main() {
let file = File::open("resources/fat32.img").unwrap();
let mut buf_rdr = BufStream::new(file);
let fs = FileSystem::new(&mut buf_rdr).unwrap();
let fs = FileSystem::new(&mut buf_rdr, true).unwrap();
let mut root_dir = fs.root_dir();
let mut file = root_dir.open_file(&env::args().nth(1).unwrap()).unwrap();
let mut buf = vec![];

View File

@ -25,7 +25,7 @@ fn format_file_size(size: u64) -> String {
fn main() {
let file = File::open("resources/fat32.img").unwrap();
let mut buf_rdr = BufStream::new(file);
let fs = FileSystem::new(&mut buf_rdr).unwrap();
let fs = FileSystem::new(&mut buf_rdr, true).unwrap();
let mut root_dir = fs.root_dir();
let dir = match env::args().nth(1) {
None => root_dir,

View File

@ -7,7 +7,7 @@ use std::cmp;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
#[cfg(feature = "chrono")]
use chrono::{TimeZone, Local};
use chrono::{TimeZone, Local, Datelike, Timelike};
#[cfg(feature = "chrono")]
use chrono;
@ -133,19 +133,81 @@ impl DirFileEntryData {
self.size = size;
}
pub fn is_dir(&self) -> bool {
pub(crate) fn is_dir(&self) -> bool {
self.attrs.contains(FileAttributes::DIRECTORY)
}
pub fn is_file(&self) -> bool {
pub(crate) fn is_file(&self) -> bool {
!self.is_dir()
}
fn created(&self) -> DateTime {
DateTime::from_u16(self.create_date, self.create_time_1)
}
fn accessed(&self) -> Date {
Date::from_u16(self.access_date)
}
fn modified(&self) -> DateTime {
DateTime::from_u16(self.modify_date, self.modify_time)
}
fn set_created(&mut self, date_time: DateTime) {
self.create_date = date_time.date.to_u16();
self.create_time_1 = date_time.time.to_u16();
}
fn set_accessed(&mut self, date: Date) {
self.access_date = date.to_u16();
}
pub(crate) fn set_modified(&mut self, date_time: DateTime) {
self.modify_date = date_time.date.to_u16();
self.modify_time = date_time.time.to_u16();
}
#[cfg(feature = "chrono")]
pub(crate) fn reset_created(&mut self) {
let now = DateTime::from(chrono::Local::now());
self.set_created(now);
}
#[cfg(feature = "chrono")]
pub(crate) fn reset_accessed(&mut self) -> bool {
let now = Date::from(chrono::Local::now().date());
if now == self.accessed() {
false
} else {
self.set_accessed(now);
true
}
}
#[cfg(feature = "chrono")]
pub(crate) fn reset_modified(&mut self) {
let now = DateTime::from(chrono::Local::now());
self.set_modified(now);
}
#[cfg(not(feature = "chrono"))]
pub(crate) fn reset_created(&mut self) {
// nop - user controls timestamps manually
false
}
#[cfg(not(feature = "chrono"))]
pub(crate) fn reset_accessed(&mut self) -> bool {
// nop - user controls timestamps manually
false
}
#[cfg(not(feature = "chrono"))]
pub(crate) fn reset_modified(&mut self) {
// nop - user controls timestamps manually
false
}
pub(crate) fn serialize(&self, wrt: &mut Write) -> io::Result<()> {
wrt.write_all(&self.name)?;
wrt.write_u8(self.attrs.bits())?;
@ -286,7 +348,7 @@ impl DirEntryData {
}
/// DOS compatible date
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Date {
pub year: u16,
pub month: u16,
@ -294,6 +356,7 @@ pub struct Date {
}
impl Date {
pub(crate) fn from_u16(dos_date: u16) -> Self {
let (year, month, day) = ((dos_date >> 9) + 1980, (dos_date >> 5) & 0xF, dos_date & 0x1F);
Date { year, month, day }
@ -305,7 +368,7 @@ impl Date {
}
/// DOS compatible time
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Time {
pub hour: u16,
pub min: u16,
@ -324,7 +387,7 @@ impl Time {
}
/// DOS compatible date and time
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DateTime {
pub date: Date,
pub time: Time,
@ -354,6 +417,31 @@ impl From<DateTime> for chrono::DateTime<Local> {
}
}
#[cfg(feature = "chrono")]
impl From<chrono::Date<Local>> for Date {
fn from(date: chrono::Date<Local>) -> Self {
Date {
year: date.year() as u16,
month: date.month() as u16,
day: date.day() as u16,
}
}
}
#[cfg(feature = "chrono")]
impl From<chrono::DateTime<Local>> for DateTime {
fn from(date_time: chrono::DateTime<Local>) -> Self {
DateTime {
date: Date::from(date_time.date()),
time: Time {
hour: date_time.hour() as u16,
min: date_time.minute() as u16,
sec: date_time.second() as u16,
},
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct FileEntryInfo {
pub(crate) data: DirFileEntryData,
@ -458,17 +546,17 @@ impl <'a, 'b> DirEntry<'a, 'b> {
/// Returns file creation date and time.
pub fn created(&self) -> DateTime {
DateTime::from_u16(self.data.create_date, self.data.create_time_1)
self.data.created()
}
/// Returns file last access date.
pub fn accessed(&self) -> Date {
Date::from_u16(self.data.access_date)
self.data.accessed()
}
/// Returns file last modification date and time.
pub fn modified(&self) -> DateTime {
DateTime::from_u16(self.data.modify_date, self.data.modify_time)
self.data.modified()
}
}
@ -750,6 +838,7 @@ impl <'a, 'b> Dir<'a, 'b> {
..Default::default()
};
raw_entry.set_first_cluster(first_cluster, self.fs.fat_type);
raw_entry.reset_created();
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

@ -36,10 +36,11 @@ impl <'a, 'b> File<'a, 'b> {
let offset = self.offset;
match self.entry {
Some(ref mut e) => {
e.data.reset_modified();
if e.data.size().map_or(false, |s| offset > s) {
e.data.set_size(offset);
self.entry_dirty = true;
}
self.entry_dirty = true;
},
_ => {},
}
@ -102,7 +103,7 @@ impl <'a, 'b> File<'a, 'b> {
/// Set date and time of last modification for this file.
///
/// Note: this library doesn't know current time so changing timestamp must be done manually.
/// Note: if chrono feature is enabled (default) library automatically updates all timestamps
pub fn set_modified(&mut self, date_time: DateTime) {
match self.entry {
Some(ref mut e) => {
@ -187,6 +188,11 @@ impl<'a, 'b> Read for File<'a, 'b> {
}
self.offset += read_bytes as u32;
self.current_cluster = Some(current_cluster);
match self.entry {
Some(ref mut e) if !self.fs.read_only => self.entry_dirty |= e.data.reset_accessed(),
_ => {},
}
Ok(read_bytes)
}
}

View File

@ -86,14 +86,18 @@ pub struct FileSystem<'a> {
pub(crate) boot: BootRecord,
pub(crate) first_data_sector: u32,
pub(crate) root_dir_sectors: u32,
pub(crate) read_only: bool,
}
impl <'a> FileSystem<'a> {
/// Creates new filesystem object instance.
///
/// read_only argument is a hint for library. It doesnt prevent user from writing to file.
/// For now it prevents accessed date field automatic update in dir entry.
///
/// Note: creating multiple filesystem objects with one underlying device/disk image can
/// cause filesystem corruption.
pub fn new<T: ReadWriteSeek>(disk: &'a mut T) -> io::Result<FileSystem<'a>> {
pub fn new<T: ReadWriteSeek>(disk: &'a mut T, read_only: bool) -> io::Result<FileSystem<'a>> {
let boot = Self::read_boot_record(disk)?;
if boot.boot_sig != [0x55, 0xAA] {
return Err(Error::new(ErrorKind::Other, "invalid signature"));
@ -113,6 +117,7 @@ impl <'a> FileSystem<'a> {
boot,
first_data_sector,
root_dir_sectors,
read_only,
})
}

View File

@ -17,7 +17,7 @@ fn call_with_fs(f: &Fn(FileSystem) -> (), filename: &str) {
let _ = env_logger::init();
let file = fs::File::open(filename).unwrap();
let mut buf_file = BufStream::new(file);
let fs = FileSystem::new(&mut buf_file).unwrap();
let fs = FileSystem::new(&mut buf_file, true).unwrap();
f(fs);
}

View File

@ -25,7 +25,7 @@ fn call_with_fs(f: &Fn(FileSystem) -> (), filename: &str, test_seq: u32) {
{
let file = fs::OpenOptions::new().read(true).write(true).open(&tmp_path).unwrap();
let mut buf_file = BufStream::new(file);
let fs = FileSystem::new(&mut buf_file).unwrap();
let fs = FileSystem::new(&mut buf_file, false).unwrap();
f(fs);
}
fs::remove_file(tmp_path).unwrap();