pub mod dos3x;
pub mod prodos;
pub mod pascal;
pub mod cpm;
pub mod fat;
mod fimg;
mod recs;
use std::collections::HashMap;
use crate::img;
use crate::bios::Block;
use crate::commands::ItemType;
use crate::{STDRESULT,DYNERR};
#[derive(thiserror::Error,Debug)]
pub enum Error {
#[error("file system not compatible with request")]
FileSystemMismatch,
#[error("file image format is wrong")]
FileImageFormat,
#[error("incompatible or ill-formed version")]
UnexpectedVersion,
#[error("high level file format is wrong")]
FileFormat
}
pub enum UnpackedData {
Binary(Vec<u8>),
Records(Records),
Text(String)
}
pub fn add_ignorable_offsets(map: &mut HashMap<Block,Vec<usize>>,key: Block, offsets: Vec<usize>) {
if let Some(val) = map.get(&key) {
map.insert(key,[val.clone(),offsets].concat());
} else {
map.insert(key,offsets);
}
}
pub fn combine_ignorable_offsets(map: &mut HashMap<Block,Vec<usize>>,other: HashMap<Block,Vec<usize>>) {
for (k,v) in other.iter() {
add_ignorable_offsets(map, *k, v.clone());
}
}
pub fn null_fraction(candidate: &str) -> f64 {
let mut null_count = 0;
for c in candidate.chars() {
if c == '\u{0000}' {
null_count += 1;
}
}
if null_count > 0 {
log::debug!("candidate string had {} NULL",null_count);
}
null_count as f64 / candidate.len() as f64
}
fn universal_row(typ: &str, blocks: usize, name: &str) -> String {
format!("{:4} {:5} {}",typ,blocks,name)
}
pub trait TextConversion {
fn new(line_terminator: Vec<u8>) -> Self;
fn from_utf8(&self,txt: &str) -> Option<Vec<u8>>;
fn to_utf8(&self,src: &[u8]) -> Option<String>;
fn is_terminated(bytes: &[u8],term: &[u8]) -> bool {
if term.len()==0 {
return true;
}
if bytes.len()==0 || bytes.len() < term.len() {
return false;
}
for i in 0..term.len() {
if bytes[i+bytes.len()-term.len()]!=term[i] {
return false;
}
}
true
}
}
pub struct Attributes {
pub read: Option<bool>,
pub write: Option<bool>,
pub execute: Option<bool>,
pub create: Option<bool>,
pub rename: Option<bool>,
pub destroy: Option<bool>,
pub backup: Option<bool>,
pub hidden: Option<bool>,
pub system: Option<bool>,
pub vol: Option<bool>,
pub dir: Option<bool>
}
impl Attributes {
pub fn new() -> Self {
Self {
read: None,
write: None,
execute: None,
create: None,
rename: None,
destroy: None,
backup: None,
hidden: None,
system: None,
vol: None,
dir: None
}
}
fn vol(mut self,setting: bool) -> Self {
self.vol = Some(setting);
self
}
fn backup(mut self,setting: bool) -> Self {
self.backup = Some(setting);
self
}
fn hidden(mut self,setting: bool) -> Self {
self.hidden = Some(setting);
self
}
fn system(mut self,setting: bool) -> Self {
self.system = Some(setting);
self
}
pub fn read(mut self,setting: bool) -> Self {
self.read = Some(setting);
self
}
pub fn write(mut self,setting: bool) -> Self {
self.write = Some(setting);
self
}
pub fn destroy(mut self,setting: bool) -> Self {
self.destroy = Some(setting);
self
}
pub fn rename(mut self,setting: bool) -> Self {
self.rename = Some(setting);
self
}
}
pub struct FileImage {
pub fimg_version: String,
pub file_system: String,
pub chunk_len: usize,
pub eof: Vec<u8>,
pub fs_type: Vec<u8>,
pub aux: Vec<u8>,
pub access: Vec<u8>,
pub accessed: Vec<u8>,
pub created: Vec<u8>,
pub modified: Vec<u8>,
pub version: Vec<u8>,
pub min_version: Vec<u8>,
pub full_path: String,
pub chunks: HashMap<usize,Vec<u8>>
}
pub trait Packing {
fn set_path(&self,fimg: &mut FileImage,path: &str) -> STDRESULT;
fn get_load_address(&self,fimg: &FileImage) -> usize;
fn pack(&self,_fimg: &mut FileImage, _dat: &[u8], _load_addr: Option<usize>) -> STDRESULT {
log::error!("could not automatically pack");
Err(Box::new(crate::fs::Error::FileFormat))
}
fn unpack(&self,fimg: &FileImage) -> Result<UnpackedData,DYNERR>;
fn pack_raw(&self, fimg: &mut FileImage, dat: &[u8]) -> STDRESULT;
fn unpack_raw(&self,fimg: &FileImage,trunc: bool) -> Result<Vec<u8>,DYNERR>;
fn pack_bin(&self,fimg: &mut FileImage,dat: &[u8],load_addr: Option<usize>,trailing: Option<&[u8]>) -> STDRESULT;
fn unpack_bin(&self,fimg: &FileImage) -> Result<Vec<u8>,DYNERR>;
fn pack_txt(&self, fimg: &mut FileImage, txt: &str) -> STDRESULT;
fn unpack_txt(&self,fimg: &FileImage) -> Result<String,DYNERR>;
fn pack_tok(&self,fimg: &mut FileImage,tok: &[u8],lang: ItemType,trailing: Option<&[u8]>) -> STDRESULT;
fn unpack_tok(&self,fimg: &FileImage) -> Result<Vec<u8>,DYNERR>;
fn pack_rec_str(&self, fimg: &mut FileImage, json: &str) -> STDRESULT;
fn unpack_rec_str(&self,fimg: &FileImage,rec_len: Option<usize>,indent: Option<u16>) -> Result<String,DYNERR>;
fn pack_rec(&self, fimg: &mut FileImage, recs: &Records) -> STDRESULT;
fn unpack_rec(&self,fimg: &FileImage,rec_len: Option<usize>) -> Result<Records,DYNERR>;
fn pack_apple_single(&self,_fimg: &mut FileImage, _dat: &[u8], _load_addr: Option<usize>) -> STDRESULT {
log::error!("AppleSingle is not supported for this file system");
Err(Box::new(Error::FileSystemMismatch))
}
fn unpack_apple_single(&self,_fimg: &FileImage) -> Result<Vec<u8>,DYNERR> {
log::error!("AppleSingle is not supported for this file system");
Err(Box::new(Error::FileSystemMismatch))
}
}
pub struct Records {
pub record_len: usize,
pub map: HashMap<usize,String>
}
pub struct Stat {
pub fs_name: String,
pub label: String,
pub users: Vec<String>,
pub block_size: usize,
pub block_beg: usize,
pub block_end: usize,
pub free_blocks: usize,
pub raw: String
}
pub trait DiskFS {
fn new_fimg(&self, chunk_len: Option<usize>, set_time: bool, path: &str) -> Result<FileImage,DYNERR>;
fn stat(&mut self) -> Result<Stat,DYNERR>;
fn catalog_to_stdout(&mut self, path: &str) -> STDRESULT;
fn catalog_to_vec(&mut self, path: &str) -> Result<Vec<String>,DYNERR>;
fn glob(&mut self,pattern: &str,case_sensitive: bool) -> Result<Vec<String>,DYNERR>;
fn tree(&mut self,include_meta: bool,indent: Option<u16>) -> Result<String,DYNERR>;
fn create(&mut self,path: &str) -> STDRESULT;
fn delete(&mut self,path: &str) -> STDRESULT;
fn rename(&mut self,path: &str,name: &str) -> STDRESULT;
fn set_attrib(&mut self,path: &str,attrib: Attributes,password: Option<&str>) -> STDRESULT;
fn retype(&mut self,path: &str,new_type: &str,sub_type: &str) -> STDRESULT;
fn get(&mut self,path: &str) -> Result<FileImage,DYNERR>;
fn put(&mut self,fimg: &FileImage) -> Result<usize,DYNERR>;
fn read_block(&mut self,num: &str) -> Result<Vec<u8>,DYNERR>;
fn write_block(&mut self, num: &str, dat: &[u8]) -> Result<usize,DYNERR>;
fn standardize(&mut self,ref_con: u16) -> HashMap<Block,Vec<usize>>;
fn compare(&mut self,path: &std::path::Path,ignore: &HashMap<Block,Vec<usize>>);
fn get_img(&mut self) -> &mut Box<dyn img::DiskImage>;
fn put_at(&mut self,path: &str,fimg: &mut FileImage) -> Result<usize,DYNERR> {
fimg.set_path(path)?;
self.put(fimg)
}
fn bload(&mut self,path: &str) -> Result<(usize,Vec<u8>),DYNERR> {
let fimg = self.get(path)?;
Ok((fimg.get_load_address() as usize, fimg.unpack_bin()?))
}
fn bsave(&mut self,path: &str,dat: &[u8],load_addr: Option<usize>,trailing: Option<&[u8]>) -> Result<usize,DYNERR> {
let mut fimg = self.new_fimg(None, true, path)?;
fimg.pack_bin(dat,load_addr,trailing)?;
self.put(&fimg)
}
fn load(&mut self,path: &str) -> Result<(usize,Vec<u8>),DYNERR> {
let fimg = self.get(path)?;
Ok((fimg.get_load_address() as usize, fimg.unpack_tok()?))
}
fn save(&mut self,path: &str,dat: &[u8],lang: ItemType,trailing: Option<&[u8]>) -> Result<usize,DYNERR> {
let mut fimg = self.new_fimg(None, true, path)?;
fimg.pack_tok(dat,lang,trailing)?;
self.put(&fimg)
}
fn read_text(&mut self,path: &str) -> Result<String,DYNERR> {
self.get(path)?.unpack_txt()
}
fn write_text(&mut self,path: &str,txt: &str) -> Result<usize,DYNERR> {
let mut fimg = self.new_fimg(None, true, path)?;
fimg.pack_txt(txt)?;
self.put(&fimg)
}
fn read_records(&mut self,path: &str,rec_len: Option<usize>) -> Result<Records,DYNERR> {
self.get(path)?.unpack_rec(rec_len)
}
fn write_records(&mut self,path: &str,recs: &Records) -> Result<usize,DYNERR> {
let mut fimg = self.new_fimg(None, true, path)?;
fimg.pack_rec(recs)?;
self.put(&fimg)
}
}
impl Stat {
pub fn to_json(&self,indent: Option<u16>) -> String {
let mut ans = json::JsonValue::new_object();
ans["fs_name"] = json::JsonValue::String(self.fs_name.clone());
ans["label"] = json::JsonValue::String(self.label.clone());
ans["users"] = json::JsonValue::Array(self.users.iter().map(|s| json::JsonValue::String(s.clone())).collect());
ans["block_size"] = json::JsonValue::Number(self.block_size.into());
ans["block_beg"] = json::JsonValue::Number(self.block_beg.into());
ans["block_end"] = json::JsonValue::Number(self.block_end.into());
ans["free_blocks"] = json::JsonValue::Number(self.free_blocks.into());
match json::parse(&self.raw) { Ok(obj) => {
ans["raw"] = obj;
} _ => {
ans["raw"] = json::JsonValue::Null;
}}
if let Some(spaces) = indent {
return json::stringify_pretty(ans, spaces);
} else {
return json::stringify(ans);
}
}
}