use config::Config;
use log::info;
use nom::branch::alt;
use nom::combinator::map;
use nom::IResult;
use std::fmt::{Display, Formatter, Result};
use crate::{
disk_format::{
apple::{
self,
disk::{apple_disk_parser, AppleDisk, AppleDiskData, AppleDiskGuess},
},
commodore::d64::{d64_disk_parser, D64Disk, D64DiskGuess},
stx::disk::{stx_disk_parser, STXDisk, STXDiskGuess},
},
error::{Error, ErrorKind, InvalidErrorKind},
init,
};
#[allow(clippy::large_enum_variant)]
pub enum DiskImage<'a> {
D64(D64Disk<'a>),
STX(STXDisk<'a>),
Apple(AppleDisk<'a>),
}
impl Display for DiskImage<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
DiskImage::D64(_) => write!(f, "D64 Disk"),
DiskImage::STX(_) => write!(f, "STX Disk"),
DiskImage::Apple(d) => write!(f, "Apple Disk: {}", d),
}
}
}
pub trait DiskImageParser<'a, 'b> {
fn parse_disk_image(
&'a self,
config: &'b Config,
filename: &str,
) -> std::result::Result<DiskImage<'a>, Error>;
}
pub trait TestParser<'a, 'b> {
fn parse_disk_image(
self,
config: &'b Config,
filename: &str,
) -> std::result::Result<DiskImage<'a>, Error>;
}
pub trait DiskImageSaver {
fn save_disk_image(
&self,
config: &Config,
selected_filename: Option<&str>,
filename: &str,
) -> std::result::Result<(), crate::error::Error>;
}
pub enum DiskImageGuess<'a> {
D64(D64DiskGuess<'a>),
STX(STXDiskGuess<'a>),
Apple(AppleDiskGuess<'a>),
}
impl<'a> Display for DiskImageGuess<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
DiskImageGuess::D64(_) => write!(f, "D64 Disk"),
DiskImageGuess::STX(_) => write!(f, "STX Disk"),
DiskImageGuess::Apple(d) => write!(f, "Apple Disk: {}", d),
}
}
}
impl<'a, 'b> TestParser<'a, 'b> for DiskImageGuess<'a> {
fn parse_disk_image(
self,
config: &'b Config,
_filename: &str,
) -> std::result::Result<DiskImage<'a>, crate::error::Error> {
init();
match self {
DiskImageGuess::D64(_) => Err(Error::new(ErrorKind::Unimplemented(String::from(
"Error parsing image from guess",
)))),
DiskImageGuess::STX(_) => Err(Error::new(ErrorKind::Unimplemented(String::from(
"Error parsing image from guess",
)))),
DiskImageGuess::Apple(guess) => {
let parser_result = apple_disk_parser(guess, config);
match parser_result {
Ok(res) => Ok(DiskImage::Apple(res.1)),
Err(e) => Err(Error::new(ErrorKind::Invalid(InvalidErrorKind::Invalid(
nom::Err::Error(e).to_string(),
)))),
}
}
}
}
}
impl DiskImageSaver for DiskImage<'_> {
fn save_disk_image(
&self,
config: &Config,
selected_filename: Option<&str>,
filename: &str,
) -> std::result::Result<(), crate::error::Error> {
match self {
DiskImage::STX(image_data) => {
image_data.save_disk_image(config, None, filename)?;
Ok(())
}
DiskImage::Apple(apple_image) => match &apple_image.data {
AppleDiskData::Nibble(nibble_image) => {
nibble_image.save_disk_image(config, None, filename)?;
Ok(())
}
AppleDiskData::DOS(dos_image) => {
info!("Saving DOS 3.3 file");
dos_image.save_disk_image(config, selected_filename, filename)?;
Ok(())
}
_ => {
info!("Unsupported image for file saving");
Err(crate::error::Error::new(
crate::error::ErrorKind::Unimplemented(String::from(
"Saving unknown Apple disk images not implemented\n",
)),
))
}
},
_ => {
info!("Unsupported image for file saving");
Err(crate::error::Error::new(
crate::error::ErrorKind::Unimplemented(String::from(
"Saving unknown disk images not implemented\n",
)),
))
}
}
}
}
pub fn file_parser<'a, 'b>(
filename: &str,
data: &'a [u8],
config: &'b Config,
) -> IResult<&'a [u8], DiskImage<'a>> {
let guess_image_type = format_from_filename_and_data(filename, data);
info!(
"config ignore-checksums: {:?}",
config.get_bool("ignore-checksums")
);
match guess_image_type {
Some(i) => match i {
DiskImageGuess::Apple(guess) => {
info!("Attempting to parse Apple disk");
let res = apple_disk_parser(guess, config)?;
Ok((res.0, DiskImage::Apple(res.1)))
}
_ => panic!("Exiting"),
},
None => disk_image_parser(data),
}
}
pub fn disk_image_parser(i: &[u8]) -> IResult<&[u8], DiskImage> {
alt((
map(d64_disk_parser, DiskImage::D64),
map(stx_disk_parser, DiskImage::STX),
))(i)
}
impl<'a, 'b> DiskImageParser<'a, 'b> for Vec<u8> {
fn parse_disk_image(
&'a self,
config: &'b Config,
filename: &str,
) -> std::result::Result<DiskImage<'a>, Error> {
init();
let result = file_parser(filename, self, config);
match result {
Ok(res) => Ok(res.1),
Err(e) => Err(Error::new(ErrorKind::Invalid(InvalidErrorKind::Invalid(
nom::Err::Error(e).to_string(),
)))),
}
}
}
pub fn format_from_filename_and_data<'a>(
filename: &str,
data: &'a [u8],
) -> Option<DiskImageGuess<'a>> {
let apple_res = apple::disk::format_from_filename_and_data(filename, data);
apple_res.map(DiskImageGuess::Apple)
}
pub fn disk_image_data(disk_image: &DiskImage) -> Option<Vec<u8>> {
match disk_image {
DiskImage::STX(image_data) => {
Some(
image_data
.stx_tracks
.iter()
.filter(|s| s.sector_data.is_some())
.flat_map(|s| s.sector_data.as_ref().unwrap().iter())
.flat_map(|bytes| (*bytes).iter())
.copied()
.collect(),
)
}
_ => {
info!("Unsupported image for file saving");
None
}
}
}
#[cfg(test)]
mod tests {
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use super::apple::disk::{Encoding, Format};
use super::AppleDiskGuess;
use super::{format_from_filename_and_data, DiskImageGuess};
#[test]
fn format_from_filename_works() {
let filename = "testdata/test-image_format_from_filename_works.dsk";
let path = Path::new(&filename);
let mut file = OpenOptions::new()
.create(true)
.write(true)
.open(path)
.unwrap_or_else(|e| {
panic!("Couldn't open file: {}", e);
});
let data: [u8; 143360] = [0; 143360];
file.write_all(&data).unwrap_or_else(|e| {
panic!("Error writing test file: {}", e);
});
file.flush().unwrap_or_else(|e| {
panic!("Couldn't flush file stream: {}", e);
});
let guess = format_from_filename_and_data(filename, &data).unwrap_or_else(|| {
panic!("Invalid filename guess");
});
match guess {
DiskImageGuess::Apple(g) => {
assert_eq!(
g,
AppleDiskGuess::new(Encoding::Plain, Format::DOS33(143360), &data)
);
}
_ => {
panic!("Invalid filename guess");
}
}
std::fs::remove_file(filename).unwrap_or_else(|e| {
panic!("Error removing test file: {}", e);
});
}
}