pub mod dos3x;
pub mod prodos;
pub mod pascal;
pub mod cpm;
use std::fmt;
use std::str::FromStr;
use std::collections::{BTreeMap,HashMap};
use log::{warn,error};
use crate::img;
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("high level file format is wrong")]
FileFormat
}
#[derive(PartialEq,Eq,Clone,Copy,Hash)]
pub enum Block {
D13([usize;2]),
DO([usize;2]),
PO(usize),
CPM((usize,u8,u16))
}
impl Block {
pub fn get_lsecs(&self,secs_per_track: usize) -> Vec<[usize;2]> {
match self {
Self::D13([t,s]) => vec![[*t,*s]],
Self::DO([t,s]) => vec![[*t,*s]],
Self::PO(_) => panic!("function `get_lsecs` not appropriate for ProDOS"),
Self::CPM((block,bsh,off)) => {
let mut ans: Vec<[usize;2]> = Vec::new();
let lsecs_per_block = 1 << bsh;
for sec_count in block*lsecs_per_block..(block+1)*lsecs_per_block {
ans.push([*off as usize + sec_count/secs_per_track , 1 + sec_count%secs_per_track]);
}
ans
}
}
}
}
impl fmt::Display for Block {
fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::D13([t,s]) => write!(f,"D13 track {} sector {}",t,s),
Self::DO([t,s]) => write!(f,"DOS track {} sector {}",t,s),
Self::PO(b) => write!(f,"ProDOS block {}",b),
Self::CPM((b,s,o)) => write!(f,"CPM block {} shift {} offset {}",b,s,o)
}
}
}
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 trait TextEncoder {
fn new(line_terminator: Vec<u8>) -> Self where Self: Sized;
fn encode(&self,txt: &str) -> Option<Vec<u8>>;
fn decode(&self,raw: &[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 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 created: Vec<u8>,
pub modified: Vec<u8>,
pub version: Vec<u8>,
pub min_version: Vec<u8>,
pub chunks: HashMap<usize,Vec<u8>>
}
impl FileImage {
pub fn fimg_version() -> String {
"2.0.0".to_string()
}
pub fn ordered_indices(&self) -> Vec<usize> {
let copy = self.chunks.clone();
let mut idx_list = copy.into_keys().collect::<Vec<usize>>();
idx_list.sort_unstable();
return idx_list;
}
pub fn end(&self) -> usize {
match self.ordered_indices().pop() {
Some(idx) => idx+1,
None => 0
}
}
pub fn sequence(&self) -> Vec<u8> {
let mut ans: Vec<u8> = Vec::new();
for chunk in self.ordered_indices() {
match self.chunks.get(&chunk) {
Some(v) => ans.append(&mut v.clone()),
_ => panic!("unreachable")
};
}
return ans;
}
pub fn sequence_limited(&self,max_len: usize) -> Vec<u8> {
let mut ans = self.sequence();
if max_len < ans.len() {
ans = ans[0..max_len].to_vec();
}
return ans;
}
pub fn desequence(&mut self, dat: &[u8]) {
let mut mark = 0;
let mut idx = 0;
if dat.len()==0 {
self.eof = vec![0;self.eof.len()];
return;
}
loop {
let mut end = mark + self.chunk_len;
if end > dat.len() {
end = dat.len();
}
self.chunks.insert(idx,dat[mark..end].to_vec());
mark = end;
if mark == dat.len() {
self.eof = Self::fix_le_vec(dat.len(),self.eof.len());
return;
}
idx += 1;
}
}
fn fix_le_vec(val: usize,min_len: usize) -> Vec<u8> {
let mut ans = usize::to_le_bytes(val).to_vec();
let mut count = 0;
for byte in ans.iter().rev() {
if *byte>0 {
break;
}
count += 1;
}
for _i in 0..count {
ans.pop();
}
for _i in ans.len()..min_len {
ans.push(0);
}
ans
}
fn usize_from_truncated_le_bytes(bytes: &[u8]) -> usize {
let mut ans: usize = 0;
for i in 0..bytes.len() {
if i == usize::BITS as usize/8 {
break;
}
ans += (bytes[i] as usize) << (i*8);
}
ans
}
pub fn parse_hex_to_vec(key: &str,parsed: &json::JsonValue) -> Result<Vec<u8>,DYNERR> {
if let Some(s) = parsed[key].as_str() {
if let Ok(bytes) = hex::decode(s) {
return Ok(bytes);
}
}
error!("a record is missing in the file image");
return Err(Box::new(Error::FileImageFormat));
}
pub fn parse_usize(key: &str,parsed: &json::JsonValue) -> Result<usize,DYNERR> {
if let Some(val) = parsed[key].as_usize() {
return Ok(val);
}
error!("a record is missing in the file image");
return Err(Box::new(Error::FileImageFormat));
}
pub fn parse_str(key: &str,parsed: &json::JsonValue) -> Result<String,DYNERR> {
if let Some(s) = parsed[key].as_str() {
return Ok(s.to_string());
}
error!("a record is missing in the file image");
return Err(Box::new(Error::FileImageFormat));
}
pub fn from_json(json_str: &str) -> Result<FileImage,DYNERR> {
let parsed = json::parse(json_str)?;
let fimg_version = FileImage::parse_str("fimg_version",&parsed)?;
let fs = FileImage::parse_str("file_system",&parsed)?;
let chunk_len = FileImage::parse_usize("chunk_len", &parsed)?;
let fs_type = FileImage::parse_hex_to_vec("fs_type",&parsed)?;
let aux = FileImage::parse_hex_to_vec("aux",&parsed)?;
let eof = FileImage::parse_hex_to_vec("eof",&parsed)?;
let access = FileImage::parse_hex_to_vec("access",&parsed)?;
let created = FileImage::parse_hex_to_vec("created",&parsed)?;
let modified = FileImage::parse_hex_to_vec("modified",&parsed)?;
let version = FileImage::parse_hex_to_vec("version",&parsed)?;
let min_version = FileImage::parse_hex_to_vec("min_version",&parsed)?;
let mut chunks: HashMap<usize,Vec<u8>> = HashMap::new();
let map_obj = &parsed["chunks"];
if map_obj.entries().len()==0 {
warn!("file image contains metadata, but no data");
}
for (key,hex) in map_obj.entries() {
let prev_len = chunks.len();
if let Ok(num) = usize::from_str(key) {
if let Some(hex_str) = hex.as_str() {
if let Ok(dat) = hex::decode(hex_str) {
chunks.insert(num,dat);
}
}
}
if chunks.len()==prev_len {
error!("could not read hex string from chunk");
return Err(Box::new(Error::FileImageFormat));
}
}
return Ok(Self {
fimg_version,
file_system: fs.to_string(),
chunk_len,
eof,
fs_type,
aux,
access,
created,
modified,
version,
min_version,
chunks
});
}
pub fn to_json(&self,indent: u16) -> String {
let mut json_map = json::JsonValue::new_object();
let mut sorted : BTreeMap<usize,Vec<u8>> = BTreeMap::new();
for (c,v) in &self.chunks {
sorted.insert(*c,v.clone());
}
for (c,v) in &sorted {
json_map[c.to_string()] = json::JsonValue::String(hex::encode_upper(v));
}
let ans = json::object! {
fimg_version: self.fimg_version.clone(),
file_system: self.file_system.clone(),
chunk_len: self.chunk_len,
eof: hex::encode_upper(self.eof.clone()),
fs_type: hex::encode_upper(self.fs_type.clone()),
aux: hex::encode_upper(self.aux.clone()),
access: hex::encode_upper(self.access.clone()),
created: hex::encode_upper(self.created.clone()),
modified: hex::encode_upper(self.modified.clone()),
version: hex::encode_upper(self.version.clone()),
min_version: hex::encode_upper(self.min_version.clone()),
chunks: json_map
};
if indent > 0 {
return json::stringify_pretty(ans, indent);
} else {
return json::stringify(ans);
}
}}
pub struct Records {
pub record_len: usize,
pub map: HashMap<usize,String>
}
impl Records {
pub fn new(record_len: usize) -> Self {
Self {
record_len,
map: HashMap::new()
}
}
pub fn add_record(&mut self,num: usize,fields: &str) {
self.map.insert(num,fields.to_string());
}
pub fn from_fimg(fimg: &FileImage,record_length: usize,encoder: impl TextEncoder) -> Result<Records,DYNERR> {
if record_length==0 {
return Err(Box::new(Error::FileFormat));
}
let mut ans = Records::new(record_length);
let mut list: Vec<usize> = Vec::new();
let chunk_len = fimg.chunk_len;
for c in fimg.chunks.keys() {
let start_rec = c*chunk_len/record_length + match c*chunk_len%record_length { x if x>0 => 1, _ => 0 };
let end_rec = (c+1)*chunk_len/record_length + match (c+1)*chunk_len%record_length { x if x>0 => 1, _ => 0 };
for r in start_rec..end_rec {
list.push(r);
}
}
for r in list {
let start_chunk = r*record_length/chunk_len;
let end_chunk = 1 + (r+1)*record_length/chunk_len;
let start_offset = r*record_length%chunk_len;
let mut bytes: Vec<u8> = Vec::new();
let mut complete = true;
for chunk_num in start_chunk..end_chunk {
match fimg.chunks.get(&chunk_num) {
Some(chunk) => {
for i in chunk {
bytes.push(*i);
}
},
_ => complete = false
}
}
if complete && start_offset < bytes.len() {
let actual_end = usize::min(start_offset+record_length,bytes.len());
if let Some(long_str) = encoder.decode(&bytes[start_offset..actual_end].to_vec()) {
if let Some(partial) = long_str.split("\u{0000}").next() {
if partial.len()>0 {
ans.map.insert(r,partial.to_string());
}
} else {
if long_str.len()>0 {
ans.map.insert(r,long_str);
}
}
}
}
}
return Ok(ans);
}
pub fn update_fimg(&self,ans: &mut FileImage,require_first: bool,encoder: impl TextEncoder) -> STDRESULT {
let chunk_len = ans.chunk_len;
let mut eof: usize = 0;
if require_first {
ans.chunks.insert(0,vec![0;chunk_len]);
}
for (rec_num,fields) in &self.map {
match encoder.encode(fields) {
Some(data_bytes) => {
let logical_chunk = self.record_len * rec_num / chunk_len;
let end_logical_chunk = 1 + (self.record_len * (rec_num+1) - 1) / chunk_len;
let fwd_offset = self.record_len * rec_num % chunk_len;
for lb in logical_chunk..end_logical_chunk {
let start_byte = match lb {
l if l==logical_chunk => fwd_offset,
_ => 0
};
let end_byte = match lb {
l if l==end_logical_chunk-1 => fwd_offset + data_bytes.len() - chunk_len*(end_logical_chunk-logical_chunk-1),
_ => chunk_len
};
let mut buf = match ans.chunks.contains_key(&lb) {
true => ans.chunks.get(&lb).unwrap().clone(),
false => Vec::new()
};
for _i in buf.len()..end_byte as usize {
buf.push(0);
}
for i in start_byte..end_byte {
buf[i as usize] = data_bytes[chunk_len*(lb-logical_chunk) + i - fwd_offset];
}
eof = usize::max(lb*512 + buf.len(),eof);
ans.chunks.insert(lb as usize,buf);
}
},
None => return Err(Box::new(std::fmt::Error))
}
}
ans.eof = FileImage::fix_le_vec(eof,ans.eof.len());
return Ok(());
}
pub fn from_json(json_str: &str) -> Result<Records,DYNERR> {
match json::parse(json_str) {
Ok(parsed) => {
let maybe_type = parsed["fimg_type"].as_str();
let maybe_len = parsed["record_length"].as_usize();
if let (Some(typ),Some(len)) = (maybe_type,maybe_len) {
if typ=="rec" {
let mut records: HashMap<usize,String> = HashMap::new();
let map_obj = &parsed["records"];
if map_obj.entries().len()==0 {
error!("no object entries in json records");
return Err(Box::new(Error::FileImageFormat));
}
for (key,lines) in map_obj.entries() {
if let Ok(num) = usize::from_str(key) {
let mut fields = String::new();
for maybe_field in lines.members() {
if let Some(line) = maybe_field.as_str() {
fields = fields + line + "\n";
} else {
error!("record is not a string");
return Err(Box::new(Error::FileImageFormat));
}
}
records.insert(num,fields);
} else {
error!("key is not a number");
return Err(Box::new(Error::FileImageFormat));
}
}
return Ok(Self {
record_len: len,
map: records
});
} else {
error!("json metadata type mismatch");
return Err(Box::new(Error::FileImageFormat));
}
}
error!("json records missing metadata");
Err(Box::new(Error::FileImageFormat))
},
Err(_e) => Err(Box::new(Error::FileImageFormat))
}
}
pub fn to_json(&self,indent: u16) -> String {
let mut json_map = json::JsonValue::new_object();
for (r,l) in &self.map {
let mut json_array = json::JsonValue::new_array();
for line in l.lines() {
json_array.push(line).expect("error while building JSON array");
}
json_map[r.to_string()] = json_array;
}
let ans = json::object! {
fimg_type: "rec",
record_length: self.record_len,
records: json_map
};
if indent > 0 {
return json::stringify_pretty(ans, indent);
} else {
return json::stringify(ans);
}
}
}
impl fmt::Display for Records {
fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (idx,fields) in &self.map {
write!(f,"Record {}",idx).expect("format error");
for field in fields.lines() {
write!(f," {}",field).expect("format error");
}
}
write!(f,"Record Count = {}",self.map.len())
}
}
pub trait DiskFS {
fn new_fimg(&self,chunk_len: usize) -> FileImage;
fn catalog_to_stdout(&mut self, path: &str) -> STDRESULT;
fn create(&mut self,path: &str) -> STDRESULT;
fn delete(&mut self,path: &str) -> STDRESULT;
fn rename(&mut self,path: &str,name: &str) -> STDRESULT;
fn lock(&mut self,path: &str) -> STDRESULT;
fn unlock(&mut self,path: &str) -> STDRESULT;
fn retype(&mut self,path: &str,new_type: &str,sub_type: &str) -> STDRESULT;
fn bload(&mut self,path: &str) -> Result<(u16,Vec<u8>),DYNERR>;
fn bsave(&mut self,path: &str, dat: &[u8],start_addr: u16,trailing: Option<&[u8]>) -> Result<usize,DYNERR>;
fn load(&mut self,path: &str) -> Result<(u16,Vec<u8>),DYNERR>;
fn save(&mut self,path: &str, dat: &[u8], typ: ItemType,trailing: Option<&[u8]>) -> Result<usize,DYNERR>;
fn read_raw(&mut self,path: &str,trunc: bool) -> Result<(u16,Vec<u8>),DYNERR>;
fn write_raw(&mut self,path: &str, dat: &[u8]) -> Result<usize,DYNERR>;
fn read_text(&mut self,path: &str) -> Result<(u16,Vec<u8>),DYNERR>;
fn write_text(&mut self,path: &str, dat: &[u8]) -> Result<usize,DYNERR>;
fn read_records(&mut self,path: &str,record_length: usize) -> Result<Records,DYNERR>;
fn write_records(&mut self,path: &str, records: &Records) -> Result<usize,DYNERR>;
fn read_any(&mut self,path: &str) -> Result<FileImage,DYNERR>;
fn write_any(&mut self,path: &str,fimg: &FileImage) -> Result<usize,DYNERR>;
fn read_block(&mut self,num: &str) -> Result<(u16,Vec<u8>),DYNERR>;
fn write_block(&mut self, num: &str, dat: &[u8]) -> Result<usize,DYNERR>;
fn decode_text(&self,dat: &[u8]) -> Result<String,DYNERR>;
fn encode_text(&self,s: &str) -> Result<Vec<u8>,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>;
}