diff --git a/mod_util/src/mod_loader.rs b/mod_util/src/mod_loader.rs index 9359192..41060ab 100644 --- a/mod_util/src/mod_loader.rs +++ b/mod_util/src/mod_loader.rs @@ -5,6 +5,7 @@ use std::{ path::{Path, PathBuf}, }; +use zip::read::ZipFile; use zip::ZipArchive; use crate::mod_info::{ModInfo, Version}; @@ -158,12 +159,61 @@ impl Mod { self.internal.get_file(path) } + // Returns a list of all files/subdirectories that live in `dir`. + pub fn read_dir(&self, dir: &str) -> Result> { + self.internal.read_dir(dir) + } + #[must_use] pub const fn wube_mods() -> [&'static str; 5] { ["core", "base", "elevated-rails", "quality", "space-age"] } } +/// An object which represents a file or folder inside of a Mod, like `fs::DirEntry`. +#[derive(Debug)] +pub struct ModEntry { + path: PathBuf, + is_file: bool, + // Can add all sorts of fancy info as we like +} + +impl ModEntry { + pub fn path(&self) -> &PathBuf { + &self.path + } + pub fn is_file(&self) -> bool { + self.is_file + } + pub fn is_dir(&self) -> bool { + !self.is_file + } + // I don't think we have to worry about symlinks... +} + +impl TryFrom for ModEntry { + type Error = ModError; + + fn try_from(item: std::fs::DirEntry) -> Result { + let metadata = item.metadata()?; + Ok(ModEntry { + path: item.path(), + is_file: metadata.is_file(), + }) + } +} + +impl TryFrom<&ZipFile<'_, File>> for ModEntry { + type Error = ModError; + + fn try_from(item: &ZipFile<'_, File>) -> Result { + Ok(ModEntry { + path: item.name().into(), + is_file: item.is_file(), + }) + } +} + #[derive(Debug)] enum ModType { Folder { @@ -258,6 +308,47 @@ impl ModType { } } } + + fn read_dir(&self, dir: &str) -> Result> { + match self { + Self::Folder { path } => { + let path = path.join(dir); + if !path.exists() { + return Err(ModError::PathDoesNotExist(path)); + } + + return std::fs::read_dir(&path)? + .filter_map(|x| x.ok()) + .map(|x| ModEntry::try_from(x)) + .collect(); + } + Self::Zip { + internal_prefix, + zip, + .. + } => { + let path = internal_prefix.clone() + dir; + let mut zip = zip.try_borrow_mut()?; + + if let Err(_) = &zip.by_name(&path) { + return Err(ModError::PathDoesNotExist(path.into())); + } + + // We need to copy all of the `&str`s to `String`s so we don't + // keep the immutable ref to `zip` alive and trigger a double borrow + let entries: Vec = zip + .file_names() + .filter(|&x| x.starts_with(&path) && x != path) + .map(|x| x.into()) + .collect(); + + return entries + .iter() + .map(|x| ModEntry::try_from(&zip.by_name(x)?)) + .collect(); + } + } + } } fn get_zip_internal_folder(path: impl AsRef, zip: &ZipArchive) -> Result {