use std::collections::BTreeMap;
use chrono::{NaiveDate,NaiveTime};
use log::{debug,warn,trace};
use super::types::*;
use crate::fs::{FileImage,Attributes};
use crate::{STDRESULT,DYNERR};
use a2kit_macro::{DiskStructError,DiskStruct};
use a2kit_macro_derive::DiskStruct;
pub const DIR_ENTRY_SIZE: usize = 32;
const FREE: u8 = 0xe5;
const FREE_AND_NO_MORE: u8 = 0x00;
pub const READ_ONLY: u8 = 1;
pub const HIDDEN: u8 = 2;
pub const SYSTEM: u8 = 4;
pub const VOLUME_ID: u8 = 8;
pub const DIRECTORY: u8 = 16;
pub const ARCHIVE: u8 = 32;
pub const LONG_NAME: u8 = 15;
fn update_attrib(curr: u8,what: Attributes) -> u8 {
let mut ans = curr;
let mut change_flag = |setting: bool,flag: u8| {
if setting {
ans |= flag;
} else {
ans &= u8::MAX ^ flag;
}
};
if let Some(setting) = what.backup {
change_flag(setting,ARCHIVE);
}
if let Some(setting) = what.write {
change_flag(!setting,READ_ONLY);
}
if let Some(setting) = what.hidden {
change_flag(setting,HIDDEN);
}
if let Some(setting) = what.system {
change_flag(setting,SYSTEM);
}
if let Some(setting) = what.dir {
change_flag(setting,DIRECTORY);
}
if let Some(setting) = what.vol {
change_flag(setting,VOLUME_ID);
}
ans
}
#[derive(Clone)]
pub struct FileInfo {
pub wildcard: String,
pub idx: usize,
pub name: String,
pub typ: String,
pub read_only: bool,
pub hidden: bool,
pub system: bool,
pub volume_id: bool,
pub directory: bool,
pub archived: bool,
pub create_date: Option<NaiveDate>,
pub create_time: Option<NaiveTime>,
pub write_date: Option<NaiveDate>,
pub write_time: Option<NaiveTime>,
pub access_date: Option<NaiveDate>,
pub eof: usize,
pub cluster1: Option<Ptr>
}
#[derive(PartialEq)]
pub enum EntryType {
Free,
FreeAndNoMore,
File,
Directory,
VolumeLabel,
LongName
}
pub struct EntryLocation {
pub cluster1: Option<Ptr>,
pub entry: Ptr,
pub dir: Directory
}
#[derive(DiskStruct)]
pub struct Entry {
name: [u8;8],
ext: [u8;3],
attr: u8,
nt_res: u8,
creation_tenth: u8,
creation_time: [u8;2],
creation_date: [u8;2],
access_date: [u8;2],
cluster1_high: [u8;2],
write_time: [u8;2],
write_date: [u8;2],
cluster1_low: [u8;2],
file_size: [u8;4]
}
pub struct Directory {
entries: Vec<[u8;DIR_ENTRY_SIZE]>
}
impl FileInfo {
pub fn create_root(cluster1: usize) -> Self {
Self {
wildcard: String::new(),
idx: 0,
name: "".to_string(),
typ: "".to_string(),
read_only: false,
hidden: false,
system: false,
volume_id: false,
directory: true,
archived: false,
create_date: None,
create_time: None,
write_date: None,
write_time: None,
access_date: None,
eof: 0,
cluster1: match cluster1 {
0 => None,
_ => Some(Ptr::Cluster(cluster1))
}
}
}
pub fn create_wildcard(pattern: &str) -> Self {
Self {
wildcard: String::from(pattern),
idx: 0,
name: "".to_string(),
typ: "".to_string(),
read_only: false,
hidden: false,
system: false,
volume_id: false,
directory: true,
archived: false,
create_date: None,
create_time: None,
write_date: None,
write_time: None,
access_date: None,
eof: 0,
cluster1: None
}
}
}
impl Entry {
pub fn create(name: &str, time: Option<chrono::NaiveDateTime>) -> Self {
let now = match time {
Some(t) => t,
None => chrono::Local::now().naive_local()
};
let tenths = super::pack::pack_tenths(Some(now));
let time = super::pack::pack_time(Some(now));
let date = super::pack::pack_date(Some(now));
let (base,ext) = super::pack::string_to_file_name(name);
Self {
name: base,
ext,
attr: 0,
nt_res: 0,
creation_tenth: tenths,
creation_time: time,
creation_date: date,
access_date: date,
cluster1_high: [0,0],
write_time: time,
write_date: date,
cluster1_low: [0,0],
file_size: [0,0,0,0]
}
}
pub fn create_label(name: &str, time: Option<chrono::NaiveDateTime>) -> Self {
let now = match time {
Some(t) => t,
None => chrono::Local::now().naive_local()
};
let tenths = super::pack::pack_tenths(Some(now));
let time = super::pack::pack_time(Some(now));
let date = super::pack::pack_date(Some(now));
let (base,ext) = super::pack::string_to_label_name(name);
Self {
name: base,
ext,
attr: VOLUME_ID,
nt_res: 0,
creation_tenth: tenths,
creation_time: time,
creation_date: date,
access_date: date,
cluster1_high: [0,0],
write_time: time,
write_date: date,
cluster1_low: [0,0],
file_size: [0,0,0,0]
}
}
pub fn erase(&mut self,none_follow: bool) {
match none_follow {
true => self.name[0] = FREE_AND_NO_MORE,
false => self.name[0] = FREE
}
}
pub fn set_cluster(&mut self,cluster: usize) {
let [b1,b2,b3,b4] = u32::to_le_bytes(cluster as u32);
self.cluster1_low = [b1,b2];
self.cluster1_high = [b3,b4];
}
pub fn create_subdir(name: &str,parent_cluster: usize,new_cluster: usize,block_size: usize,time: Option<chrono::NaiveDateTime>) -> (Self,Vec<u8>) {
let mut dot = Entry::create(".",time);
let mut dotdot = Entry::create("..",time);
dot.attr = DIRECTORY;
dot.set_cluster(new_cluster);
dotdot.attr = DIRECTORY;
dotdot.set_cluster(parent_cluster);
let mut dir = Directory::new();
dir.expand(block_size/DIR_ENTRY_SIZE);
dir.set_entry(&Ptr::Entry(0),&dot);
dir.set_entry(&Ptr::Entry(1),&dotdot);
dot.rename(name);
(dot,dir.to_bytes())
}
pub fn name(&self,label: bool) -> String {
let prim = super::pack::file_name_to_string(self.name, self.ext);
match label {
true => prim.replace(".",""),
false => prim
}
}
pub fn rename(&mut self,new_name: &str) {
let (name,ext) = super::pack::string_to_file_name(new_name);
self.name = name;
self.ext = ext;
}
pub fn eof(&self) -> usize {
u32::from_le_bytes(self.file_size) as usize
}
pub fn metadata_to_fimg(&self,fimg: &mut FileImage) {
fimg.set_eof(self.eof());
fimg.access = vec![self.attr];
fimg.fs_type = self.ext.to_vec();
fimg.aux = vec![];
fimg.created = [vec![self.creation_tenth],self.creation_time.to_vec(),self.creation_date.to_vec()].concat();
fimg.modified = [self.write_time.to_vec(),self.write_date.to_vec()].concat();
fimg.version = vec![];
fimg.min_version = vec![];
}
pub fn fimg_to_metadata(&mut self,fimg: &FileImage,use_fimg_time: bool) -> STDRESULT {
self.file_size = match fimg.eof[0..4].try_into() {
Ok(x) => x,
Err(e) => return Err(Box::new(e))
};
self.attr = fimg.access[0];
if use_fimg_time {
self.creation_tenth =fimg.created[0];
self.creation_time = match fimg.created[1..3].try_into() {
Ok(x) => x,
Err(e) => return Err(Box::new(e))
};
self.creation_date = match fimg.created[3..5].try_into() {
Ok(x) => x,
Err(e) => return Err(Box::new(e))
};
self.write_time = match fimg.modified[0..2].try_into() {
Ok(x) => x,
Err(e) => return Err(Box::new(e))
};
self.write_date = match fimg.modified[2..4].try_into() {
Ok(x) => x,
Err(e) => return Err(Box::new(e))
};
self.access_date = match fimg.modified[2..4].try_into() {
Ok(x) => x,
Err(e) => return Err(Box::new(e))
};
}
Ok(())
}
pub fn get_attr(&self,mask: u8) -> bool {
(self.attr & mask) > 0
}
pub fn set_attr(&mut self,attrib: Attributes) {
self.attr = update_attrib(self.attr, attrib);
}
pub fn standardize(offset: usize) -> Vec<usize> {
let ans = vec![13,14,15,16,17,18,19,22,23,24,25];
ans.iter().map(|x| x + offset).collect()
}
}
impl DiskStruct for Directory {
fn new() -> Self {
let entries: Vec<[u8;DIR_ENTRY_SIZE]> = Vec::new();
Self {
entries
}
}
fn to_bytes(&self) -> Vec<u8> {
let mut ans: Vec<u8> = Vec::new();
for x in &self.entries {
ans.append(&mut x.to_vec());
}
return ans;
}
fn update_from_bytes(&mut self,bytes: &[u8]) -> Result<(),DiskStructError> {
self.entries = Vec::new();
let num_entries = bytes.len()/DIR_ENTRY_SIZE;
if bytes.len()%DIR_ENTRY_SIZE!=0 {
warn!("directory buffer wrong size");
}
for i in 0..num_entries {
let entry_buf = match bytes[i*DIR_ENTRY_SIZE..(i+1)*DIR_ENTRY_SIZE].try_into() {
Ok(buf) => buf,
Err(_) => return Err(DiskStructError::OutOfData)
};
self.entries.push(entry_buf);
}
Ok(())
}
fn from_bytes(bytes: &[u8]) -> Result<Self,DiskStructError> {
let mut ans = Self::new();
ans.update_from_bytes(bytes)?;
Ok(ans)
}
fn len(&self) -> usize {
return DIR_ENTRY_SIZE*(self.entries.len());
}
}
impl Directory {
pub fn num_entries(&self) -> usize {
self.entries.len()
}
pub fn expand(&mut self,count: usize) {
for _i in 0..count {
self.entries.push([0;32]);
}
}
pub fn get_type(&self,ptr: &Ptr) -> EntryType {
let (idx,nm0,attr) = match ptr {
Ptr::Entry(i) => (*i,self.entries[*i][0],self.entries[*i][11]),
_ => panic!("wrong pointer type")
};
trace!("entry {} has name[0] {} and attr {}",idx,nm0,attr);
match (nm0,attr) {
(0xe5,_) => EntryType::Free,
(0x00,_) => EntryType::FreeAndNoMore,
(_,a) if a & LONG_NAME >= LONG_NAME => EntryType::LongName,
(_,a) if a & VOLUME_ID > 0 => EntryType::VolumeLabel,
(_,a) if a & DIRECTORY > 0 => EntryType::Directory,
_ => EntryType::File
}
}
pub fn get_raw_entry(&self,ptr: &Ptr) -> [u8;DIR_ENTRY_SIZE] {
match ptr {
Ptr::Entry(i) => self.entries[*i].clone(),
_ => panic!("wrong pointer type")
}
}
pub fn get_entry(&self,ptr: &Ptr) -> Entry {
match ptr {
Ptr::Entry(idx) => Entry::from_bytes(&self.entries[*idx]).expect("unexpected size"),
_ => panic!("wrong pointer type")
}
}
pub fn set_entry(&mut self,ptr: &Ptr,entry: &Entry) {
match ptr {
Ptr::Entry(idx) => {
self.entries[*idx] = entry.to_bytes().try_into().expect("unexpected size")
},
_ => panic!("wrong pointer type")
}
}
pub fn find_label(&self) -> Option<Entry> {
for i in 0..self.num_entries() {
let ptr = Ptr::Entry(i);
if self.get_type(&ptr)==EntryType::VolumeLabel {
return Some(self.get_entry(&ptr));
}
}
None
}
fn add_file(&self,ans: &mut BTreeMap<String,FileInfo>,fat_typ: usize,entry_idx: usize) -> Result<bool,DYNERR> {
let entry = self.get_entry(&Ptr::Entry(entry_idx));
let (name,typ) = super::pack::file_name_to_split_string(entry.name, entry.ext);
let key = [name.clone(),".".to_string(),typ.clone()].concat();
trace!("entry in use: {}",key);
if ans.contains_key(&key) {
debug!("duplicate file {} in directory",key);
return Err(Box::new(Error::DuplicateFile));
}
let mut cluster1 = u16::from_le_bytes(entry.cluster1_low) as usize;
if fat_typ == 32 {
cluster1 += (u16::MAX as usize) * (u16::from_le_bytes(entry.cluster1_high) as usize);
}
let finfo: FileInfo = FileInfo {
wildcard: String::new(),
idx: entry_idx,
name,
typ,
read_only: (entry.attr & READ_ONLY) > 0,
hidden: (entry.attr & HIDDEN) > 0,
system: (entry.attr & SYSTEM) > 0,
volume_id: (entry.attr & VOLUME_ID) > 0,
directory: (entry.attr & DIRECTORY) > 0,
archived: (entry.attr & ARCHIVE) > 0,
write_date: super::pack::unpack_date(entry.write_date),
write_time: super::pack::unpack_time(entry.write_time,0),
create_date: super::pack::unpack_date(entry.creation_date),
create_time: super::pack::unpack_time(entry.creation_time,entry.creation_tenth),
access_date: super::pack::unpack_date(entry.access_date),
eof: u32::from_le_bytes(entry.file_size) as usize,
cluster1: Some(Ptr::Cluster(cluster1))
};
ans.insert(key.clone(),finfo);
Ok(super::pack::is_name_valid(&key))
}
pub fn build_files(&self,fat_typ: usize) -> Result<BTreeMap<String,FileInfo>,DYNERR> {
let mut bad_names = 0;
let mut ans = BTreeMap::new();
for i in 0..self.num_entries() {
let etyp = self.get_type(&Ptr::Entry(i));
if etyp==EntryType::Free {
continue;
}
if etyp==EntryType::FreeAndNoMore {
break;
}
if bad_names > 2 {
debug!("after {} bad file names rejecting disk",bad_names);
return Err(Box::new(Error::Syntax));
}
if !self.add_file(&mut ans,fat_typ,i)? {
bad_names += 1;
}
}
Ok(ans)
}
pub fn sort_on_entry_index(&self,files: &BTreeMap<String,FileInfo>) -> BTreeMap<usize,FileInfo> {
let mut ans = BTreeMap::new();
for f in files.values() {
ans.insert(f.idx,f.clone());
}
ans
}
}
pub fn get_file<'a>(name: &str,files: &'a BTreeMap<String,FileInfo>) -> Option<&'a FileInfo> {
let mut trimmed = name.trim_end().to_string();
if !name.contains(".") {
trimmed += ".";
}
if let Some(finfo) = files.get(&trimmed) {
return Some(finfo);
}
if let Some(finfo) = files.get(&trimmed.to_uppercase()) {
return Some(finfo);
}
return None;
}