mirror of
https://github.com/Zenithsiz/dcb.git
synced 2026-02-05 00:39:31 +00:00
Improved DrvFsWriter interface by using the DirWriterList trait.
Fixed `DirWriter::write_entries` not writing all entries correctly. Added `drv-packer` binary.
This commit is contained in:
parent
26972d128b
commit
3d54f23022
@ -12,6 +12,7 @@ members = [
|
||||
"dcb-tools/dcb-extractor",
|
||||
"dcb-tools/dcb-decompiler",
|
||||
"dcb-tools/drv-extractor",
|
||||
"dcb-tools/drv-packer",
|
||||
"dcb-tools/pak-extractor",
|
||||
"dcb-tools/model-set-extractor",
|
||||
]
|
||||
|
||||
@ -6,7 +6,7 @@ pub mod error;
|
||||
pub mod file;
|
||||
|
||||
// Exports
|
||||
pub use dir::{DirEntryReader, DirEntryWriter, DirReader, DirWriter};
|
||||
pub use dir::{DirEntryReader, DirEntryWriter, DirReader, DirWriter, DirWriterList};
|
||||
pub use error::{FromReaderError, ToWriterError};
|
||||
pub use file::{FileReader, FileWriter};
|
||||
|
||||
@ -30,11 +30,11 @@ pub struct DrvFsWriter;
|
||||
|
||||
impl DrvFsWriter {
|
||||
/// Creates a `.DRV` filesystem
|
||||
pub fn write_fs<W: io::Write + io::Seek, R: io::Read, I: ExactSizeIterator<Item = Result<DirEntryWriter<R, I>, io::Error>>>(
|
||||
writer: &mut W, root_entries: I,
|
||||
) -> Result<(), ToWriterError> {
|
||||
pub fn write_fs<W: io::Write + io::Seek, L: DirWriterList>(
|
||||
writer: &mut W, root_entries: L, root_entries_len: u32,
|
||||
) -> Result<(), ToWriterError<L::Error>> {
|
||||
// Get the root and write it
|
||||
let root = DirWriter::new(root_entries);
|
||||
let root = DirWriter::new(root_entries, root_entries_len);
|
||||
root.write_entries(writer).map_err(ToWriterError::RootDir)?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@ -68,72 +68,106 @@ impl DirReader {
|
||||
}
|
||||
}
|
||||
|
||||
/// Directory writer
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub struct DirWriter<R: io::Read, I: ExactSizeIterator<Item = Result<DirEntryWriter<R, I>, io::Error>>> {
|
||||
/// Iterator over all entries
|
||||
entries: I,
|
||||
/// Directory list
|
||||
pub trait DirWriterList: Sized + std::fmt::Debug {
|
||||
/// Reader used for the files in this directory
|
||||
type FileReader: std::fmt::Debug + io::Read;
|
||||
|
||||
/// Directory lister
|
||||
type DirList: DirWriterList;
|
||||
|
||||
/// Error type for each entry
|
||||
type Error: std::error::Error + 'static;
|
||||
|
||||
/// Iterator
|
||||
type Iter: Iterator<Item = Result<DirEntryWriter<Self>, Self::Error>>;
|
||||
|
||||
/// Converts this list into an iterator
|
||||
fn into_iter(self) -> Self::Iter;
|
||||
}
|
||||
|
||||
impl<R: io::Read, I: ExactSizeIterator<Item = Result<DirEntryWriter<R, I>, io::Error>>> DirWriter<R, I> {
|
||||
/// Directory writer
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub struct DirWriter<L: DirWriterList> {
|
||||
/// Writer list
|
||||
entries: L,
|
||||
|
||||
/// Number of entries
|
||||
entries_len: u32,
|
||||
}
|
||||
|
||||
impl<L: DirWriterList> DirWriter<L> {
|
||||
/// Creates a new directory writer.
|
||||
pub fn new(entries: I) -> Self {
|
||||
Self { entries }
|
||||
pub fn new(entries: L, entries_len: u32) -> Self {
|
||||
Self { entries, entries_len }
|
||||
}
|
||||
|
||||
/// Returns the number of entries
|
||||
pub fn entries_len(&self) -> u32 {
|
||||
u32::try_from(self.entries.len()).expect("Too many entries")
|
||||
self.entries_len
|
||||
}
|
||||
|
||||
/// Returns this directory's size
|
||||
pub fn size(&self) -> u32 {
|
||||
// Note: `+1` for the terminator
|
||||
(self.entries_len() + 1) * 0x20
|
||||
}
|
||||
|
||||
/// Writes all entries into a writer
|
||||
pub fn write_entries<W: io::Write + io::Seek>(self, writer: &mut W) -> Result<(), WriteEntriesError> {
|
||||
///
|
||||
/// Returns the number of sectors written by this directory
|
||||
pub fn write_entries<W: io::Write + io::Seek>(self, writer: &mut W) -> Result<u32, WriteEntriesError<L::Error>> {
|
||||
// Get the sector we're currently on
|
||||
let sector_pos = writer.stream_position().map_err(WriteEntriesError::GetPos)? / 2048;
|
||||
let sector_pos = u32::try_from(sector_pos).expect("`.DRV` file is too big");
|
||||
|
||||
// Get the starting sector pos for each entry
|
||||
// Note: We start right after this directory
|
||||
let start_sector_pos = sector_pos + (self.entries_len() * 0x20 + 2047) / 2048;
|
||||
|
||||
// Get all the entries with their sector positions
|
||||
let entries = self
|
||||
.entries
|
||||
.scan(start_sector_pos, |cur_sector_pos, res| match res {
|
||||
Ok(entry) => {
|
||||
let sector_pos = *cur_sector_pos;
|
||||
*cur_sector_pos += (entry.size() + 2047) / 2048;
|
||||
Some(Ok((entry, sector_pos)))
|
||||
},
|
||||
Err(err) => Some(Err(err)),
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(WriteEntriesError::GetEntry)?;
|
||||
|
||||
// Write each entry in the directory
|
||||
for (entry, sector_pos) in &entries {
|
||||
// Write the bytes
|
||||
let mut entry_bytes = [0; 0x20];
|
||||
entry.to_bytes(&mut entry_bytes, *sector_pos);
|
||||
|
||||
// And write them
|
||||
writer.write_all(&entry_bytes).map_err(WriteEntriesError::WriteEntryInDir)?;
|
||||
let start_pos = writer.stream_position().map_err(WriteEntriesError::GetPos)?;
|
||||
if start_pos % 2048 != 0 {
|
||||
return Err(WriteEntriesError::WriterAtSectorStart);
|
||||
}
|
||||
let start_sector_pos = u32::try_from(start_pos / 2048).map_err(|_err| WriteEntriesError::WriterSectorPastMax)?;
|
||||
|
||||
// Get the starting sector position for the first entry.
|
||||
// Note: We start right after this directory
|
||||
// Note: `+2047` is to pad this directory to the next sector, if not empty.
|
||||
let mut cur_sector_pos = start_sector_pos + (self.size() + 2047) / 2048;
|
||||
|
||||
// Our directory to write after writing all entries
|
||||
let mut dir_bytes = vec![];
|
||||
|
||||
// For each entry, write it and add it to our directory bytes
|
||||
for entry in self.entries.into_iter() {
|
||||
// Get the entry
|
||||
let entry = entry.map_err(WriteEntriesError::GetEntry)?;
|
||||
|
||||
// Write the entry on our directory
|
||||
let mut entry_bytes = [0; 0x20];
|
||||
entry.to_bytes(&mut entry_bytes, cur_sector_pos);
|
||||
dir_bytes.extend_from_slice(&entry_bytes);
|
||||
|
||||
// Then write each entry
|
||||
for (entry, sector_pos) in entries {
|
||||
// Seek to the entry
|
||||
writer
|
||||
.seek(SeekFrom::Start(u64::from(sector_pos) * 2048))
|
||||
.seek(SeekFrom::Start(u64::from(cur_sector_pos) * 2048))
|
||||
.map_err(WriteEntriesError::SeekToEntry)?;
|
||||
|
||||
// Write the entry
|
||||
match entry.into_kind() {
|
||||
DirEntryWriterKind::File(file) => file.into_writer(writer).map_err(WriteEntriesError::WriteFile)?,
|
||||
// Write the entry on the file
|
||||
let sector_size = match entry.into_kind() {
|
||||
DirEntryWriterKind::File(file) => {
|
||||
let size = file.size();
|
||||
file.write(writer).map_err(WriteEntriesError::WriteFile)?;
|
||||
(size + 2047) / 2048
|
||||
},
|
||||
DirEntryWriterKind::Dir(dir) => dir.write_entries(writer).map_err(|err| WriteEntriesError::WriteDir(Box::new(err)))?,
|
||||
}
|
||||
};
|
||||
|
||||
// Update our sector pos
|
||||
cur_sector_pos += sector_size;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// Then write our directory
|
||||
writer
|
||||
.seek(SeekFrom::Start(u64::from(start_sector_pos) * 2048))
|
||||
.map_err(WriteEntriesError::SeekToEntries)?;
|
||||
|
||||
writer.write_all(&dir_bytes).map_err(WriteEntriesError::WriteEntries)?;
|
||||
|
||||
Ok(cur_sector_pos - start_sector_pos)
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,12 +7,12 @@ pub mod error;
|
||||
pub use error::FromBytesError;
|
||||
|
||||
// Imports
|
||||
use super::{DirReader, DirWriter};
|
||||
use super::{DirReader, DirWriter, DirWriterList};
|
||||
use crate::drv::{FileReader, FileWriter};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use chrono::NaiveDateTime;
|
||||
use dcb_util::{array_split, array_split_mut, ascii_str_arr::AsciiChar, AsciiStrArr};
|
||||
use std::{convert::TryFrom, io};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
/// A directory entry kind
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
@ -102,18 +102,18 @@ impl DirEntryReader {
|
||||
}
|
||||
|
||||
/// A directory entry kind
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub enum DirEntryWriterKind<R: io::Read, I: ExactSizeIterator<Item = Result<DirEntryWriter<R, I>, io::Error>>> {
|
||||
#[derive(Debug)]
|
||||
pub enum DirEntryWriterKind<L: DirWriterList> {
|
||||
/// A file
|
||||
File(FileWriter<R>),
|
||||
File(FileWriter<L::FileReader>),
|
||||
|
||||
/// Directory
|
||||
Dir(DirWriter<R, I>),
|
||||
Dir(DirWriter<L>),
|
||||
}
|
||||
|
||||
/// A directory entry reader
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub struct DirEntryWriter<R: io::Read, I: ExactSizeIterator<Item = Result<DirEntryWriter<R, I>, io::Error>>> {
|
||||
#[derive(Debug)]
|
||||
pub struct DirEntryWriter<L: DirWriterList> {
|
||||
/// Entry name
|
||||
name: AsciiStrArr<0x10>,
|
||||
|
||||
@ -121,12 +121,12 @@ pub struct DirEntryWriter<R: io::Read, I: ExactSizeIterator<Item = Result<DirEnt
|
||||
date: NaiveDateTime,
|
||||
|
||||
/// Entry kind
|
||||
kind: DirEntryWriterKind<R, I>,
|
||||
kind: DirEntryWriterKind<L>,
|
||||
}
|
||||
|
||||
impl<R: io::Read, I: ExactSizeIterator<Item = Result<DirEntryWriter<R, I>, io::Error>>> DirEntryWriter<R, I> {
|
||||
impl<L: DirWriterList> DirEntryWriter<L> {
|
||||
/// Creates a new entry writer from it's name, date and kind
|
||||
pub fn new(name: AsciiStrArr<0x10>, date: NaiveDateTime, kind: DirEntryWriterKind<R, I>) -> Self {
|
||||
pub fn new(name: AsciiStrArr<0x10>, date: NaiveDateTime, kind: DirEntryWriterKind<L>) -> Self {
|
||||
Self { name, date, kind }
|
||||
}
|
||||
|
||||
@ -134,17 +134,17 @@ impl<R: io::Read, I: ExactSizeIterator<Item = Result<DirEntryWriter<R, I>, io::E
|
||||
pub fn size(&self) -> u32 {
|
||||
match &self.kind {
|
||||
DirEntryWriterKind::File(file) => file.size(),
|
||||
DirEntryWriterKind::Dir(dir) => dir.entries_len() * 0x20,
|
||||
DirEntryWriterKind::Dir(dir) => dir.size(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns this entry's kind
|
||||
pub fn kind(&self) -> &DirEntryWriterKind<R, I> {
|
||||
pub fn kind(&self) -> &DirEntryWriterKind<L> {
|
||||
&self.kind
|
||||
}
|
||||
|
||||
/// Returns this entry's kind
|
||||
pub fn into_kind(self) -> DirEntryWriterKind<R, I> {
|
||||
pub fn into_kind(self) -> DirEntryWriterKind<L> {
|
||||
self.kind
|
||||
}
|
||||
|
||||
@ -171,6 +171,8 @@ impl<R: io::Read, I: ExactSizeIterator<Item = Result<DirEntryWriter<R, I>, io::E
|
||||
},
|
||||
DirEntryWriterKind::Dir(_) => {
|
||||
*bytes.kind = 0x80;
|
||||
|
||||
LittleEndian::write_u32(bytes.size, 0);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -26,18 +26,22 @@ pub enum ReadEntryError {
|
||||
|
||||
/// Error for [`DirWriter::to_writer`](super::DirWriter::to_writer)
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum WriteEntriesError {
|
||||
pub enum WriteEntriesError<E: std::error::Error + 'static> {
|
||||
/// Unable to get position
|
||||
#[error("Unable to get position")]
|
||||
GetPos(#[source] io::Error),
|
||||
|
||||
/// Writer was not at sector star
|
||||
#[error("Writer was not at sector start")]
|
||||
WriterAtSectorStart,
|
||||
|
||||
/// Writer current sector was past max
|
||||
#[error("Writer current sector was past `u32::MAX`")]
|
||||
WriterSectorPastMax,
|
||||
|
||||
/// Unable to get entry
|
||||
#[error("Unable to get entry")]
|
||||
GetEntry(#[source] io::Error),
|
||||
|
||||
/// Unable to write entry in directory
|
||||
#[error("Unable to write entry in directory")]
|
||||
WriteEntryInDir(#[source] io::Error),
|
||||
GetEntry(#[source] E),
|
||||
|
||||
/// Unable to seek to entry
|
||||
#[error("Unable to seek to entry")]
|
||||
@ -50,4 +54,12 @@ pub enum WriteEntriesError {
|
||||
/// Unable to write directory
|
||||
#[error("Unable to write directory")]
|
||||
WriteDir(#[source] Box<Self>),
|
||||
|
||||
/// Unable to seek to entries
|
||||
#[error("Unable to seek to entries")]
|
||||
SeekToEntries(#[source] io::Error),
|
||||
|
||||
/// Unable to write all entries
|
||||
#[error("Unable to write entries")]
|
||||
WriteEntries(#[source] io::Error),
|
||||
}
|
||||
|
||||
@ -13,8 +13,8 @@ pub enum FromReaderError {
|
||||
|
||||
/// Error for [`DrvFsWriter::to_writer`](super::DrvFsWriter::to_writer)
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ToWriterError {
|
||||
pub enum ToWriterError<E: std::error::Error + 'static> {
|
||||
/// Unable to write root directory
|
||||
#[error("Unable to write root directory")]
|
||||
RootDir(#[source] dir::WriteEntriesError),
|
||||
RootDir(#[source] dir::WriteEntriesError<E>),
|
||||
}
|
||||
|
||||
@ -90,8 +90,8 @@ impl<R: io::Read> FileWriter<R> {
|
||||
}
|
||||
|
||||
/// Writes this file to a writer
|
||||
pub fn into_writer<W: io::Write>(mut self, writer: &mut W) -> Result<(), io::Error> {
|
||||
let written = std::io::copy(&mut self.reader, writer)?;
|
||||
pub fn write<W: io::Write>(self, writer: &mut W) -> Result<(), io::Error> {
|
||||
let written = std::io::copy(&mut self.reader.take(u64::from(self.size)), writer)?;
|
||||
assert_eq!(written, u64::from(self.size));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
25
dcb-tools/drv-packer/Cargo.toml
Normal file
25
dcb-tools/drv-packer/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "drv-packer"
|
||||
version = "0.1.0"
|
||||
authors = ["Filipe Rodrigues <filipejacintorodrigues1@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
# Dcb
|
||||
dcb-util = { path = "../../dcb-util" }
|
||||
dcb-io = { path = "../../dcb-io" }
|
||||
|
||||
# Util
|
||||
filetime = "0.2.14"
|
||||
chrono = "0.4.19"
|
||||
|
||||
# Cmd
|
||||
clap = "2.33.3"
|
||||
|
||||
# Logging
|
||||
log = "0.4.13"
|
||||
simplelog = "0.9.0"
|
||||
|
||||
# Error handling
|
||||
anyhow = "1.0.38"
|
||||
thiserror = "1.0.23"
|
||||
65
dcb-tools/drv-packer/src/cli.rs
Normal file
65
dcb-tools/drv-packer/src/cli.rs
Normal file
@ -0,0 +1,65 @@
|
||||
//! Cli manager
|
||||
|
||||
// Imports
|
||||
use clap::{App as ClapApp, Arg as ClapArg};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Data from the command line
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub struct CliData {
|
||||
/// Input directory
|
||||
pub input_dir: PathBuf,
|
||||
|
||||
/// The output file
|
||||
pub output_file: PathBuf,
|
||||
}
|
||||
|
||||
impl CliData {
|
||||
/// Constructs all of the cli data given and returns it
|
||||
pub fn new() -> Self {
|
||||
// Get all matches from cli
|
||||
let matches = ClapApp::new("Drv Packer")
|
||||
.version("0.0")
|
||||
.author("Filipe [...] <[...]@gmail.com>")
|
||||
.about("Packs a folder into a `.drv` filesystem")
|
||||
.arg(
|
||||
ClapArg::with_name("INPUT_DIR")
|
||||
.help("Sets the input directory to use")
|
||||
.required(true)
|
||||
.index(1),
|
||||
)
|
||||
.arg(
|
||||
ClapArg::with_name("OUTPUT")
|
||||
.help("Sets the output file to use")
|
||||
.short("o")
|
||||
.long("output")
|
||||
.takes_value(true)
|
||||
.required(false),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
// Get the input filename
|
||||
// Note: required
|
||||
let input_dir = matches
|
||||
.value_of("INPUT_DIR")
|
||||
.map(Path::new)
|
||||
.map(Path::to_path_buf)
|
||||
.expect("Unable to get required argument `INPUT_FILE`");
|
||||
|
||||
// Try to get the output, else use the input filename + `.drv`
|
||||
let output_file = match matches.value_of("OUTPUT") {
|
||||
Some(output) => PathBuf::from(output),
|
||||
None => {
|
||||
let extension = match input_dir.extension() {
|
||||
Some(extension) => format!("{}.DRV", extension.to_string_lossy()),
|
||||
None => ".DRV".to_string(),
|
||||
};
|
||||
|
||||
input_dir.with_extension(extension)
|
||||
},
|
||||
};
|
||||
|
||||
// Return the data
|
||||
Self { input_dir, output_file }
|
||||
}
|
||||
}
|
||||
25
dcb-tools/drv-packer/src/logger.rs
Normal file
25
dcb-tools/drv-packer/src/logger.rs
Normal file
@ -0,0 +1,25 @@
|
||||
//! Logger
|
||||
|
||||
// Imports
|
||||
use log::LevelFilter;
|
||||
use simplelog::{CombinedLogger, Config, SharedLogger, TermLogger, TerminalMode, WriteLogger};
|
||||
|
||||
/// The type of logger required to pass to `CombinedLogger::init`
|
||||
type BoxedLogger = Box<dyn SharedLogger>;
|
||||
|
||||
/// Initializes the global logger
|
||||
pub fn init() {
|
||||
// All loggers to try and initialize
|
||||
let loggers = [
|
||||
Some(TermLogger::new(LevelFilter::Info, Config::default(), TerminalMode::Stderr)).map(|logger| BoxedLogger::from(logger)),
|
||||
std::fs::File::create("latest.log")
|
||||
.ok()
|
||||
.map(|file| WriteLogger::new(LevelFilter::Debug, Config::default(), file))
|
||||
.map(|logger| BoxedLogger::from(logger)),
|
||||
];
|
||||
|
||||
// Filter all logger that actually work and initialize them
|
||||
if CombinedLogger::init(std::array::IntoIter::new(loggers).filter_map(std::convert::identity).collect()).is_err() {
|
||||
log::warn!("Logger was already initialized");
|
||||
}
|
||||
}
|
||||
208
dcb-tools/drv-packer/src/main.rs
Normal file
208
dcb-tools/drv-packer/src/main.rs
Normal file
@ -0,0 +1,208 @@
|
||||
//! `.DRV` packer
|
||||
|
||||
// Features
|
||||
#![feature(array_value_iter, try_blocks, seek_convenience)]
|
||||
|
||||
// Modules
|
||||
mod cli;
|
||||
mod logger;
|
||||
|
||||
// Imports
|
||||
use anyhow::Context;
|
||||
use dcb_io::drv::{dir::entry::DirEntryWriterKind, DirEntryWriter, DirWriter, DirWriterList, DrvFsWriter, FileWriter};
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
fs,
|
||||
io::{self, Seek},
|
||||
path::{Path, PathBuf},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
|
||||
fn main() -> Result<(), anyhow::Error> {
|
||||
// Initialize the logger
|
||||
logger::init();
|
||||
|
||||
// Get all data from cli
|
||||
let cli::CliData { input_dir, output_file } = cli::CliData::new();
|
||||
|
||||
// Try to pack the filesystem
|
||||
self::pack_filesystem(&input_dir, &output_file).context("Unable to pack `drv` file")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Extracts a `.drv` file to `output_dir`.
|
||||
fn pack_filesystem(input_dir: &Path, output_file: &Path) -> Result<(), anyhow::Error> {
|
||||
// Create the output file
|
||||
let mut output_file = fs::File::create(output_file).context("Unable to create output file")?;
|
||||
|
||||
// Create the filesystem writer
|
||||
let (root_entries, root_entries_len) = DirList::new(input_dir).context("Unable to read root directory")?;
|
||||
DrvFsWriter::write_fs(&mut output_file, root_entries, root_entries_len).context("Unable to write filesystem")
|
||||
}
|
||||
|
||||
/// Directory list
|
||||
#[derive(Debug)]
|
||||
struct DirList {
|
||||
/// Directory read
|
||||
dir: fs::ReadDir,
|
||||
}
|
||||
|
||||
impl DirList {
|
||||
/// Creates a new iterator from a path
|
||||
fn new(path: &Path) -> Result<(Self, u32), DirListNewError> {
|
||||
// Get the length
|
||||
let len = fs::read_dir(path)
|
||||
.map_err(|err| DirListNewError::ReadDir(path.to_path_buf(), err))?
|
||||
.count();
|
||||
let len = u32::try_from(len).map_err(|_err| DirListNewError::TooManyEntries)?;
|
||||
|
||||
// And read the directory
|
||||
let dir = fs::read_dir(path).map_err(|err| DirListNewError::ReadDir(path.to_path_buf(), err))?;
|
||||
|
||||
Ok((Self { dir }, len))
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for [`DirList::new`]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum DirListNewError {
|
||||
/// Unable to read directory
|
||||
#[error("Unable to read directory {}", _0.display())]
|
||||
ReadDir(PathBuf, #[source] io::Error),
|
||||
|
||||
/// Too many entries in directory
|
||||
#[error("Too many entries in directory")]
|
||||
TooManyEntries,
|
||||
}
|
||||
|
||||
/// Error for [`Iterator::Item`]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum NextError {
|
||||
/// Unable to read entry
|
||||
#[error("Unable to read entry")]
|
||||
ReadEntry(#[source] io::Error),
|
||||
|
||||
/// Unable to read entry metadata
|
||||
#[error("Unable to read entry metadata")]
|
||||
ReadMetadata(#[source] io::Error),
|
||||
|
||||
/// Entry had no name
|
||||
#[error("Entry had no name")]
|
||||
NoEntryName,
|
||||
|
||||
/// Invalid file name
|
||||
#[error("Invalid file name")]
|
||||
InvalidEntryName(#[source] dcb_util::ascii_str_arr::FromBytesError<0x10>),
|
||||
|
||||
/// File had no file name
|
||||
#[error("file had no file name")]
|
||||
NoFileExtension,
|
||||
|
||||
/// Invalid extension
|
||||
#[error("Invalid extension")]
|
||||
InvalidFileExtension(#[source] dcb_util::ascii_str_arr::FromBytesError<0x3>),
|
||||
|
||||
/// Unable to get entry date
|
||||
#[error("Unable to get entry date")]
|
||||
EntryDate(#[source] io::Error),
|
||||
|
||||
/// Unable to get entry date as time since epoch
|
||||
#[error("Unable to get entry date as time since epoch")]
|
||||
EntryDateSinceEpoch(#[source] std::time::SystemTimeError),
|
||||
|
||||
/// Unable to get entry date as `i64` seconds since epoch
|
||||
#[error("Unable to get entry date as `i64` seconds since epoch")]
|
||||
EntryDateI64Secs,
|
||||
|
||||
/// Unable to open file
|
||||
#[error("Unable to open file")]
|
||||
OpenFile(#[source] io::Error),
|
||||
|
||||
/// Unable to get file size
|
||||
#[error("Unable to get file size")]
|
||||
FileSize(#[source] io::Error),
|
||||
|
||||
/// File was too big
|
||||
#[error("File was too big")]
|
||||
FileTooBig,
|
||||
|
||||
/// Unable to open directory
|
||||
#[error("Unable to open directory")]
|
||||
OpenDir(#[source] DirListNewError),
|
||||
}
|
||||
|
||||
impl DirWriterList for DirList {
|
||||
type DirList = Self;
|
||||
type Error = NextError;
|
||||
type FileReader = std::fs::File;
|
||||
type Iter = Self;
|
||||
|
||||
fn into_iter(self) -> Self::Iter {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for DirList {
|
||||
type Item = Result<DirEntryWriter<<Self as DirWriterList>::DirList>, <Self as DirWriterList>::Error>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// Get the next entry
|
||||
let entry = self.dir.next()?;
|
||||
|
||||
// Then read it
|
||||
let res = try {
|
||||
// Read the entry and it's metadata
|
||||
let entry = entry.map_err(NextError::ReadEntry)?;
|
||||
let metadata = entry.metadata().map_err(NextError::ReadMetadata)?;
|
||||
let path = entry.path();
|
||||
let name = path
|
||||
.file_stem()
|
||||
.ok_or(NextError::NoEntryName)?
|
||||
.try_into()
|
||||
.map_err(NextError::InvalidEntryName)?;
|
||||
let secs_since_epoch = metadata
|
||||
.modified()
|
||||
.map_err(NextError::EntryDate)?
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.map_err(NextError::EntryDateSinceEpoch)?
|
||||
.as_secs();
|
||||
let date = chrono::NaiveDateTime::from_timestamp(i64::try_from(secs_since_epoch).map_err(|_err| NextError::EntryDateI64Secs)?, 0);
|
||||
|
||||
// Check if it's a directory or file
|
||||
let kind = match metadata.is_file() {
|
||||
true => {
|
||||
let mut file = std::fs::File::open(&path).map_err(NextError::OpenFile)?;
|
||||
let size = file
|
||||
.stream_len()
|
||||
.map_err(NextError::FileSize)?
|
||||
.try_into()
|
||||
.map_err(|_err| NextError::FileTooBig)?;
|
||||
let extension = path
|
||||
.extension()
|
||||
.ok_or(NextError::NoFileExtension)?
|
||||
.try_into()
|
||||
.map_err(NextError::InvalidFileExtension)?;
|
||||
|
||||
log::info!("{} ({} bytes)", path.display(), size);
|
||||
|
||||
let file = FileWriter::new(extension, file, size);
|
||||
DirEntryWriterKind::File(file)
|
||||
},
|
||||
false => {
|
||||
let (entries, entries_len) = Self::new(&path).map_err(NextError::OpenDir)?;
|
||||
|
||||
log::info!("{} ({} entries)", path.display(), entries_len);
|
||||
|
||||
let dir = DirWriter::new(entries, entries_len);
|
||||
DirEntryWriterKind::Dir(dir)
|
||||
},
|
||||
};
|
||||
|
||||
DirEntryWriter::new(name, date, kind)
|
||||
};
|
||||
|
||||
Some(res)
|
||||
}
|
||||
}
|
||||
@ -340,6 +340,15 @@ impl<const N: usize> TryFrom<&str> for AsciiStrArr<N> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> TryFrom<&std::ffi::OsStr> for AsciiStrArr<N> {
|
||||
type Error = FromBytesError<N>;
|
||||
|
||||
fn try_from(s: &std::ffi::OsStr) -> Result<Self, Self::Error> {
|
||||
// TODO: Not allocate here, although `OsStr` doesn't provide a `as_bytes` impl, so we can't do much
|
||||
Self::from_bytes(s.to_string_lossy().as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> std::str::FromStr for AsciiStrArr<N> {
|
||||
type Err = FromUtf8Error<N>;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user