pub mod package;
use std::io::{Read, Seek, SeekFrom};
use std::collections::BTreeMap;
use std::fs::{File, ReadDir};
use std::sync::{Arc, Mutex};
use std::path::PathBuf;
use std::{fs, io};
use indexmap::IndexMap;
use package::{PackageReader, PackageFileReader};
const PACKAGES_DIR_NAME: &'static str = "packages";
#[derive(Debug, Clone)]
pub struct ResFilesystem {
shared: Arc<Shared>,
}
#[derive(Debug)]
struct Shared {
dir_path: PathBuf,
mutable: Mutex<SharedMut>,
}
#[derive(Debug)]
struct SharedMut {
pending_package_path: Vec<PathBuf>,
package_reader_cache: IndexMap<PathBuf, PackageReader<File>>,
package_open_errors: Vec<(PathBuf, io::Error)>,
node_cache: NodeCache,
}
impl ResFilesystem {
pub fn new<P: Into<PathBuf>>(dir_path: P) -> io::Result<Self> {
let dir_path = dir_path.into();
let mut pending_package_cache = Vec::new();
for entry in fs::read_dir(dir_path.join(PACKAGES_DIR_NAME))? {
let entry = entry?;
let entry_type = entry.file_type()?;
if !entry_type.is_file() {
continue;
}
if !entry.file_name().as_encoded_bytes().ends_with(b".pkg") {
continue;
}
pending_package_cache.push(entry.path());
}
Ok(Self {
shared: Arc::new(Shared {
dir_path,
mutable: Mutex::new(SharedMut {
pending_package_path: pending_package_cache,
package_reader_cache: IndexMap::new(),
package_open_errors: Vec::new(),
node_cache: NodeCache::new(),
}),
}),
})
}
pub fn read<P: AsRef<str>>(&self, file_path: P) -> io::Result<ResReadFile> {
let file_path = file_path.as_ref();
if file_path.starts_with('/') {
return Err(io::ErrorKind::NotFound.into());
}
let native_file_path = self.shared.dir_path.join(file_path);
if native_file_path.is_file() {
match File::open(native_file_path) {
Ok(file) => return Ok(ResReadFile(ReadFileInner::Native(file))),
Err(_) => (), }
}
self.shared.mutable.lock().unwrap()
.read(file_path)
.map(|reader| ResReadFile(ReadFileInner::Package(reader)))
}
pub fn read_dir<P: AsRef<str>>(&self, dir_path: P) -> io::Result<ResReadDir> {
let dir_path = dir_path.as_ref();
if dir_path.starts_with('/') {
return Err(io::ErrorKind::NotFound.into());
}
let dir_path = dir_path.strip_suffix('/').unwrap_or(dir_path);
let native_dir_path = self.shared.dir_path.join(dir_path);
let native_read_dir = fs::read_dir(native_dir_path).ok();
let mut mutable = self.shared.mutable.lock().unwrap();
let mut dir_index = None;
while dir_index.is_none() {
if let Some((find_dir_index, _)) = mutable.node_cache.find_dir(dir_path) {
dir_index = Some(find_dir_index);
} else if !mutable.try_open_pending_package() {
if native_read_dir.is_none() {
return Err(io::ErrorKind::NotFound.into());
} else {
break;
}
}
}
Ok(ResReadDir {
dir_path: Arc::from(dir_path),
native_read_dir,
package_read_dir: dir_index.map(|dir_index| PackageReadDir {
shared: Arc::clone(&self.shared),
dir_index,
remaining_names: Vec::new(),
last_children_count: 0,
last_children_last_node_index: 0,
}),
})
}
}
impl SharedMut {
fn try_read(&mut self, file_path: &str) -> io::Result<Option<PackageFileReader<File>>> {
if let Some((_, file_info)) = self.node_cache.find_file(file_path) {
let (
package_path,
package_reader,
) = self.package_reader_cache.get_index_mut(file_info.package_index).unwrap();
let mut file_reader = package_reader.read_by_index(file_info.file_index)?;
return file_reader.try_clone_with(File::open(package_path)?).map(Some);
} else {
Ok(None)
}
}
fn try_open_pending_package(&mut self) -> bool {
while let Some(package_path) = self.pending_package_path.pop() {
let package_file = match File::open(&package_path) {
Ok(file) => file,
Err(e) => {
self.package_open_errors.push((package_path, e));
continue;
}
};
let package_reader = match PackageReader::new(package_file) {
Ok(reader) => reader,
Err(e) => {
self.package_open_errors.push((package_path, e));
continue;
}
};
let (
package_index,
prev_package,
) = self.package_reader_cache.insert_full(package_path, package_reader);
debug_assert!(prev_package.is_none(), "duplicate package reader");
self.node_cache.index_package(package_index, &self.package_reader_cache[package_index]);
return true;
}
false
}
fn read(&mut self, file_path: &str) -> io::Result<PackageFileReader<File>> {
if let Some(file_reader) = self.try_read(file_path)? {
return Ok(file_reader);
}
while self.try_open_pending_package() {
if let Some(file_reader) = self.try_read(file_path)? {
return Ok(file_reader);
}
}
Err(io::ErrorKind::NotFound.into())
}
}
#[derive(Debug)]
pub struct ResReadFile(ReadFileInner);
#[derive(Debug)]
enum ReadFileInner {
Package(PackageFileReader<File>),
Native(File),
}
impl Read for ResReadFile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match &mut self.0 {
ReadFileInner::Package(package) => package.read(buf),
ReadFileInner::Native(file) => file.read(buf),
}
}
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
match &mut self.0 {
ReadFileInner::Package(package) => package.read_exact(buf),
ReadFileInner::Native(file) => file.read_exact(buf),
}
}
}
impl Seek for ResReadFile {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
match &mut self.0 {
ReadFileInner::Package(package) => package.seek(pos),
ReadFileInner::Native(file) => file.seek(pos),
}
}
fn stream_position(&mut self) -> io::Result<u64> {
match &mut self.0 {
ReadFileInner::Package(package) => package.stream_position(),
ReadFileInner::Native(file) => file.stream_position(),
}
}
}
#[derive(Debug)]
pub struct ResReadDir {
dir_path: Arc<str>,
native_read_dir: Option<ReadDir>,
package_read_dir: Option<PackageReadDir>,
}
#[derive(Debug)]
struct PackageReadDir {
shared: Arc<Shared>,
dir_index: usize,
remaining_names: Vec<(Arc<str>, usize)>,
last_children_count: usize,
last_children_last_node_index: usize,
}
impl Iterator for ResReadDir {
type Item = io::Result<ResDirEntry>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(native_read_dir) = &mut self.native_read_dir {
match native_read_dir.next() {
Some(Ok(entry)) => {
let file_name = entry.file_name();
let file_type = entry.file_type().unwrap();
let file_name = file_name.to_str().unwrap();
return Some(Ok(ResDirEntry {
dir_path: Arc::clone(&self.dir_path),
name: Arc::from(file_name),
is_dir: file_type.is_dir(),
}))
},
Some(Err(e)) => return Some(Err(e)),
None => (),
}
}
if let Some(package_read_dir) = &mut self.package_read_dir {
let mut mutable = package_read_dir.shared.mutable.lock().unwrap();
loop {
let dir_info = mutable.node_cache.get_dir(package_read_dir.dir_index).unwrap();
if dir_info.children.len() != package_read_dir.last_children_count {
debug_assert!(dir_info.children.len() > package_read_dir.last_children_count);
let mut max_child_index = 0;
for (child_name, &child_index) in &dir_info.children {
max_child_index = max_child_index.max(child_index);
if child_index >= package_read_dir.last_children_last_node_index {
package_read_dir.remaining_names.push((Arc::clone(child_name), child_index));
}
}
package_read_dir.last_children_count = dir_info.children.len();
package_read_dir.last_children_last_node_index = max_child_index + 1;
}
if let Some((node_name, node_index)) = package_read_dir.remaining_names.pop() {
return Some(Ok(ResDirEntry {
dir_path: Arc::clone(&self.dir_path),
name: node_name,
is_dir: mutable.node_cache.get_dir(node_index).is_some(),
}));
}
if !mutable.try_open_pending_package() {
return None; }
}
}
None
}
}
pub struct ResDirEntry {
dir_path: Arc<str>,
name: Arc<str>,
is_dir: bool,
}
impl ResDirEntry {
#[inline]
pub fn name(&self) -> &str {
&self.name
}
pub fn path(&self) -> String {
format!("{}/{}", self.dir_path, self.name)
}
#[inline]
pub fn is_dir(&self) -> bool {
self.is_dir
}
#[inline]
pub fn is_file(&self) -> bool {
!self.is_dir
}
}
#[derive(Debug)]
struct NodeCache {
nodes: Vec<NodeInfo>,
dir_count: usize,
dir_children_max_count: usize,
node_name_max_len: usize,
}
#[derive(Debug)]
enum NodeInfo {
File(FileInfo),
Dir(DirInfo)
}
#[derive(Debug)]
struct FileInfo {
package_index: usize,
file_index: usize,
}
#[derive(Debug, Default)]
struct DirInfo {
children: BTreeMap<Arc<str>, usize>,
}
impl NodeCache {
fn new() -> Self {
Self {
nodes: vec![NodeInfo::Dir(DirInfo::default())],
dir_count: 0,
dir_children_max_count: 0,
node_name_max_len: 0,
}
}
fn index_package(&mut self, package_index: usize, package_reader: &PackageReader<File>) {
let mut last_dir_index = 0;
let mut last_dir_path = "";
for (file_index, file_path) in package_reader.names().enumerate() {
let (mut dir_path, file_name) = match file_path.rfind('/') {
Some(last_sep_index) => file_path.split_at(last_sep_index + 1),
None => ("", file_path),
};
debug_assert!(!file_name.is_empty(), "package names should only contains files");
self.node_name_max_len = self.node_name_max_len.max(file_name.len());
let mut current_dir_index;
if dir_path.starts_with(last_dir_path) {
dir_path = &dir_path[last_dir_path.len()..];
current_dir_index = last_dir_index;
} else {
current_dir_index = 0;
}
if !dir_path.is_empty() {
for dir_part in dir_path[..dir_path.len() - 1].split('/') {
self.node_name_max_len = self.node_name_max_len.max(dir_part.len());
let inner_len = self.nodes.len();
let dir = self.nodes[current_dir_index]
.as_dir_mut()
.expect("trying to make a directory where a file already exists");
if let Some(&child_index) = dir.children.get(dir_part) {
current_dir_index = child_index;
} else {
current_dir_index = inner_len;
dir.children.insert(Arc::from(dir_part), inner_len);
self.dir_children_max_count = self.dir_children_max_count.max(dir.children.len());
self.nodes.push(NodeInfo::Dir(DirInfo::default()));
self.dir_count += 1;
}
}
}
if last_dir_index != current_dir_index {
last_dir_index = current_dir_index;
last_dir_path = dir_path;
}
let inner_len = self.nodes.len();
let dir = self.nodes[current_dir_index]
.as_dir_mut()
.expect("current directory should effectively be a directory");
let prev_child = dir.children.insert(Arc::from(file_name), inner_len);
self.dir_children_max_count = self.dir_children_max_count.max(dir.children.len());
debug_assert!(prev_child.is_none(), "overwriting a file");
self.nodes.push(NodeInfo::File(FileInfo {
package_index,
file_index,
}));
}
}
fn find_dir(&self, dir_path: &str) -> Option<(usize, &DirInfo)> {
let mut current_dir_index = 0;
if !dir_path.is_empty() {
for dir_part in dir_path.split('/') {
current_dir_index = *self.nodes[current_dir_index]
.as_dir()?
.children
.get(dir_part)?;
}
}
self.nodes[current_dir_index]
.as_dir()
.map(|dir| (current_dir_index, dir))
}
fn find_file(&self, file_path: &str) -> Option<(usize, &FileInfo)> {
let (dir_path, file_name) = file_path.rsplit_once('/').unwrap_or(("", file_path));
let (_, dir) = self.find_dir(dir_path)?;
let file_index = *dir.children.get(file_name)?;
self.nodes[file_index]
.as_file()
.map(|file| (file_index, file))
}
fn get_dir(&self, index: usize) -> Option<&DirInfo> {
self.nodes.get(index)?.as_dir()
}
}
impl NodeInfo {
#[inline]
fn as_file(&self) -> Option<&FileInfo> {
match self {
NodeInfo::File(file) => Some(file),
NodeInfo::Dir(_) => None,
}
}
#[inline]
fn as_dir(&self) -> Option<&DirInfo> {
match self {
NodeInfo::File(_) => None,
NodeInfo::Dir(dir) => Some(dir),
}
}
#[inline]
fn as_dir_mut(&mut self) -> Option<&mut DirInfo> {
match self {
NodeInfo::File(_) => None,
NodeInfo::Dir(dir) => Some(dir),
}
}
}