use std::collections::HashMap;
use std::fs::File;
use std::hash::BuildHasherDefault;
use std::io::{BufReader, Write, BufWriter, Seek, SeekFrom, Read};
use std::iter::Iterator;
use std::path::{PathBuf, StripPrefixError};
use crate::ReadSeek;
use walkdir::{IntoIter, WalkDir};
use failure::Fail;
use rustc_hash::FxHasher;
use zip::ZipArchive;
use zip::result::ZipError;
pub type SourceId = usize;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum TrustLevel
{
UntrustedSource,
TrustedSource
}
pub struct SourceManager
{
sources : Vec<Box<dyn Source>>
}
impl SourceManager
{
pub fn new() -> SourceManager
{
SourceManager
{
sources : Vec::new()
}
}
pub fn add_source(&mut self, source : Box<dyn Source>) -> SourceId
{
self.sources.push(source);
self.sources.len()
}
pub fn source(&mut self, id : SourceId) -> Option<&mut Box<dyn Source>>
{
if id == 0 || id - 1 >= self.sources.len()
{
return None
}
Some(&mut self.sources[id - 1])
}
pub fn package_source_id(&self, package_name : &str) -> SourceId
{
for i in (0..self.sources.len()).rev()
{
if self.sources[i].has_package(package_name)
{
return i + 1;
}
}
0
}
pub fn package_source(&mut self, package_name : &str) -> Option<&mut Box<dyn Source>>
{
for source in self.sources.iter_mut().rev()
{
if source.has_package(package_name)
{
return Some(source);
}
}
None
}
pub fn clear(&mut self)
{
self.sources.clear();
}
}
#[derive(Debug, Fail)]
pub enum PackageError
{
#[fail(display = "Failed to access package source")]
Generic,
#[fail(display = "{}", _0)]
IoError(#[cause] std::io::Error),
#[fail(display = "Package item does not belong to the given prefix: {}", _0)]
PrefixMismatch(StripPrefixError),
#[fail(display = "The data object was not found")]
DataNotFound,
#[fail(display = "Operation not supported")]
NotSupported,
#[fail(display = "The package contained invalid data: {}", _0)]
BadData(String)
}
impl From<std::io::Error> for PackageError
{
fn from(error : std::io::Error) -> PackageError
{
PackageError::IoError(error)
}
}
pub trait Source
{
fn get_uri(&self) -> &str;
fn has_package(&self, package_name : &str) -> bool;
fn list_packages(&mut self) -> Vec<String>;
fn read_file<'a>(&'a mut self, package_name: &str, pathname: &str) -> Result<Box<dyn ReadSeek + 'a>, PackageError>;
#[allow(unused_variables)]
fn write_file<'a>(&'a mut self, package_name : &str, pathname : &str) -> Result<Box<dyn Write + 'a>, PackageError> { Err(PackageError::NotSupported) }
fn iter_entries<'a>(&'a mut self, package_name : &str, type_folder : &str) -> Box<dyn Iterator<Item = Result<String, PackageError>> + 'a>;
fn trust_level(&self, package_name : &str) -> TrustLevel;
}
struct EmptyEntryIter {}
impl EmptyEntryIter
{
fn new() -> EmptyEntryIter { EmptyEntryIter {} }
}
impl Iterator for EmptyEntryIter
{
type Item = Result<String, PackageError>;
fn next(&mut self) -> Option<Result<String, PackageError>>
{
None
}
}
struct FilesystemIter
{
basepath : PathBuf,
walkdir : IntoIter
}
impl FilesystemIter
{
fn new<P : Into<PathBuf>>(basepath : P) -> FilesystemIter
{
let path = basepath.into();
FilesystemIter
{
basepath : path.clone(),
walkdir : WalkDir::new(path).into_iter(),
}
}
}
impl Iterator for FilesystemIter
{
type Item = Result<String, PackageError>;
fn next(&mut self) -> Option<Result<String, PackageError>>
{
while let Some(dir_next) = self.walkdir.next()
{
let dir_entry = match dir_next
{
Ok(dir_entry) => { dir_entry }
Err(error) =>
{
if let Some(io_error) = error.io_error()
{
if io_error.kind() == std::io::ErrorKind::NotFound
{
return None;
}
}
return Some(Err(PackageError::IoError(error.into())));
}
};
if !dir_entry.file_type().is_file()
{
continue;
}
let path = dir_entry.path();
let filepath = match path.strip_prefix(self.basepath.clone())
{
Ok(filepath) => { filepath }
Err(error) =>
{
return Some(Err(PackageError::PrefixMismatch(error)));
}
};
let mut pathname = String::new();
let mut first_part = true;
for part in filepath.iter()
{
if !first_part
{
pathname += "/";
}
pathname += &part.to_string_lossy();
first_part = false;
}
return Some(Ok(pathname));
}
None
}
}
pub struct FilesystemSource
{
basedir : String,
package_list : Option<Vec<String>>,
trust : TrustLevel
}
impl FilesystemSource
{
pub fn new(directory : &str, trust : TrustLevel) -> FilesystemSource
{
FilesystemSource
{
basedir : directory.to_string(),
package_list : None,
trust
}
}
}
impl Source for FilesystemSource
{
fn get_uri(&self) -> &str
{
&self.basedir
}
fn has_package(&self, package_name : &str) -> bool
{
let mut path = PathBuf::from(&self.basedir);
path.push(package_name);
path.exists()
}
fn list_packages(&mut self) -> Vec<String>
{
if let Some(package_list) = &self.package_list
{
return package_list.clone();
}
let mut package_list : Vec<String> = Vec::new();
let path = PathBuf::from(&self.basedir);
let walkdir = WalkDir::new(path).min_depth(1).max_depth(1).into_iter();
for dir_entry in walkdir
{
if let Ok(entry) = dir_entry
{
if !entry.file_type().is_dir()
{
continue;
}
package_list.push(entry.file_name().to_string_lossy().to_string());
}
}
package_list.sort();
self.package_list = Some(package_list.clone());
package_list
}
fn read_file<'a>(&'a mut self, package_name: &str, pathname: &str) -> Result<Box<dyn ReadSeek + 'a>, PackageError>
{
let mut path = PathBuf::from(&self.basedir);
path.push(format!("{}/{}", package_name, pathname));
let file = BufReader::new(File::open(path).map_err(
|error| PackageError::IoError(error))?);
Ok(Box::new(file))
}
fn write_file<'a>(&'a mut self, package_name : &str, pathname : &str) -> Result<Box<dyn Write + 'a>, PackageError>
{
let mut path = PathBuf::from(&self.basedir);
path.push(format!("{}/{}", package_name, pathname));
let mut folder_path = path.clone();
if !pathname.ends_with("/")
{
folder_path.pop();
}
std::fs::create_dir_all(folder_path).map_err(
|error| PackageError::IoError(error))?;
let file = BufWriter::new(File::create(path).map_err(
|error| PackageError::IoError(error))?);
Ok(Box::new(file))
}
fn iter_entries<'a>(&'a mut self, package_name : &str, type_folder : &str) -> Box<dyn Iterator<Item = Result<String, PackageError>> + 'a>
{
let mut path = PathBuf::from(&self.basedir);
path.push(package_name);
path.push(type_folder);
Box::new(FilesystemIter::new(path))
}
fn trust_level(&self, _package_name: &str) -> TrustLevel
{
self.trust
}
}
fn package_error_for_zip_error(error : ZipError) -> PackageError
{
return match error
{
ZipError::Io(io_error) => { PackageError::IoError(io_error) }
ZipError::InvalidArchive(error_msg) => { PackageError::BadData(error_msg.to_string()) }
ZipError::UnsupportedArchive(error_msg) => { PackageError::BadData(error_msg.to_string()) }
ZipError::FileNotFound => { PackageError::DataNotFound }
}
}
struct FakeSeekReader<R : Read>
{
reader : R
}
impl<R : Read> FakeSeekReader<R>
{
fn new(reader : R) -> FakeSeekReader<R>
{
FakeSeekReader
{
reader
}
}
}
impl<R : Read> Read for FakeSeekReader<R>
{
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize>
{
self.reader.read(buf)
}
}
impl<R : Read> Seek for FakeSeekReader<R>
{
fn seek(&mut self, _pos: SeekFrom) -> std::io::Result<u64>
{
Err(std::io::Error::new(std::io::ErrorKind::Other, "Seek not supported on ZIP archives"))
}
}
struct ZipFolderIter<'a, T : Iterator<Item = &'a str>>
{
iter : T,
type_folder : String
}
impl<'a, T : Iterator<Item = &'a str>> ZipFolderIter<'a, T>
{
fn new(iter : T, type_folder : &str) -> ZipFolderIter<'a, T>
{
ZipFolderIter
{
iter,
type_folder : type_folder.to_string()
}
}
}
impl<'a, T : Iterator<Item = &'a str>> Iterator for ZipFolderIter<'a, T>
{
type Item = Result<String, PackageError>;
fn next(&mut self) -> Option<Result<String, PackageError>>
{
while let Some(item) = self.iter.next()
{
if let Some(filename) = item.strip_prefix(&format!("{}/", self.type_folder))
{
return Some(Ok(filename.to_string()));
}
}
None
}
}
pub struct ZipFolderSource
{
basedir : String,
extension : String,
package_list : Option<Vec<String>>,
loaded_zips : HashMap<String, ZipArchive<BufReader<File>>, BuildHasherDefault<FxHasher>>,
trust : TrustLevel
}
impl ZipFolderSource
{
pub fn new(directory : &str, trust : TrustLevel) -> ZipFolderSource
{
ZipFolderSource
{
basedir : directory.to_string(),
extension : String::from(".zip"),
package_list : None,
loaded_zips : HashMap::with_hasher(BuildHasherDefault::<FxHasher>::default()),
trust
}
}
pub fn with_extension(directory : &str, extension : &str, trust : TrustLevel) -> ZipFolderSource
{
let mut dot_extension = String::from(".");
dot_extension.push_str(extension);
ZipFolderSource
{
basedir : directory.to_string(),
extension : dot_extension,
package_list : None,
loaded_zips : HashMap::with_hasher(BuildHasherDefault::<FxHasher>::default()),
trust
}
}
pub fn zip_loaded(&self, package_name : &str) -> bool
{
self.loaded_zips.contains_key(package_name)
}
pub fn load_zip(&mut self, package_name : &str) -> Result<(), PackageError>
{
if self.zip_loaded(package_name)
{
return Ok(());
}
let mut path = PathBuf::from(&self.basedir);
let mut zip_name = String::from(package_name);
zip_name.push_str(&self.extension);
path.push(zip_name);
let file = BufReader::new(File::open(path).map_err(
|error| PackageError::IoError(error))?);
let archive = ZipArchive::new(file).map_err(
|error| package_error_for_zip_error(error))?;
self.loaded_zips.insert(package_name.to_string(), archive);
Ok(())
}
pub fn unload_zip(&mut self, package_name : &str)
{
self.loaded_zips.remove(package_name);
}
}
impl Source for ZipFolderSource
{
fn get_uri(&self) -> &str
{
&self.basedir
}
fn has_package(&self, package_name: &str) -> bool
{
let mut path = PathBuf::from(&self.basedir);
let mut zip_name = String::from(package_name);
zip_name.push_str(&self.extension);
path.push(zip_name);
path.exists()
}
fn list_packages(&mut self) -> Vec<String>
{
if let Some(package_list) = &self.package_list
{
return package_list.clone();
}
let mut package_list: Vec<String> = Vec::new();
let path = PathBuf::from(&self.basedir);
let walkdir = WalkDir::new(path).min_depth(1).max_depth(1).into_iter();
for dir_entry in walkdir
{
if let Ok(entry) = dir_entry
{
if !entry.file_type().is_file()
{
continue;
}
if let Some(stripped) = entry.file_name().to_string_lossy()
.strip_suffix(&self.extension)
{
package_list.push(stripped.to_string());
}
}
}
package_list.sort();
self.package_list = Some(package_list.clone());
package_list
}
fn read_file<'a>(&'a mut self, package_name: &str, pathname: &str) -> Result<Box<dyn ReadSeek + 'a>, PackageError>
{
self.load_zip(package_name)?;
if let Some(archive) = self.loaded_zips.get_mut(package_name)
{
match archive.by_name(pathname)
{
Ok(reader) => { return Ok(Box::new(FakeSeekReader::new(reader))); }
Err(error) => { return Err(package_error_for_zip_error(error)); }
}
}
Err(PackageError::IoError(std::io::Error::new(std::io::ErrorKind::NotFound, "Archive not found")))
}
fn iter_entries<'a>(&'a mut self, package_name: &str, type_folder: &str) -> Box<dyn Iterator<Item=Result<String, PackageError>> + 'a>
{
if self.load_zip(package_name).is_err()
{
return Box::new(EmptyEntryIter::new());
}
let mut iter : Box<dyn Iterator<Item=Result<String, PackageError>>> = Box::new(EmptyEntryIter::new());
if let Some(archive) = self.loaded_zips.get(package_name)
{
let archive_iter = archive.file_names();
iter = Box::new(ZipFolderIter::new(archive_iter, type_folder));
}
iter
}
fn trust_level(&self, _package_name: &str) -> TrustLevel
{
self.trust
}
}