use super::*;
use crate::pristine::InodeMetadata;
use std::collections::HashMap;
use std::time::SystemTime;
#[derive(Debug)]
pub struct Memory {
pub files: FileTree,
pub last_modified: SystemTime,
}
#[derive(Debug, Default)]
pub struct FileTree {
children: HashMap<String, Inode>,
}
#[derive(Debug)]
enum Inode {
File {
meta: InodeMetadata,
last_modified: SystemTime,
contents: Vec<u8>,
},
Directory {
meta: InodeMetadata,
last_modified: SystemTime,
children: FileTree,
},
}
impl Default for Memory {
fn default() -> Self {
Self {
files: FileTree::default(),
last_modified: SystemTime::now(),
}
}
}
impl Memory {
pub fn new() -> Self {
Self::default()
}
pub fn list_files(&self) -> Vec<String> {
let mut result = Vec::new();
let mut current_files = vec![(String::new(), &self.files)];
let mut next_files = Vec::new();
loop {
if current_files.is_empty() {
break;
}
for (path, tree) in current_files.iter() {
for (name, inode) in tree.children.iter() {
let mut path = path.clone();
crate::path::push(&mut path, name);
match inode {
Inode::File { .. } => {
result.push(path);
}
Inode::Directory { ref children, .. } => {
result.push(path.clone());
next_files.push((path, children))
}
}
}
}
std::mem::swap(&mut current_files, &mut next_files);
next_files.clear();
}
result
}
pub fn add_file(&mut self, file: &str, file_contents: Vec<u8>) {
let file_meta = InodeMetadata::new(0o644, false);
let last = SystemTime::now();
self.add_inode(
file,
Inode::File {
meta: file_meta,
last_modified: last,
contents: file_contents,
},
)
}
pub fn add_dir(&mut self, file: &str) {
let file_meta = InodeMetadata::new(0o755, true);
let last = SystemTime::now();
self.add_inode(
file,
Inode::Directory {
meta: file_meta,
last_modified: last,
children: FileTree {
children: HashMap::new(),
},
},
)
}
fn add_inode(&mut self, file: &str, inode: Inode) {
let mut file_tree = &mut self.files;
let last = SystemTime::now();
self.last_modified = last;
let file = file.split('/').filter(|c| !c.is_empty());
let mut p = file.peekable();
while let Some(f) = p.next() {
if p.peek().is_some() {
let entry = file_tree
.children
.entry(f.to_string())
.or_insert(Inode::Directory {
meta: InodeMetadata::new(0o755, true),
children: FileTree {
children: HashMap::new(),
},
last_modified: last,
});
match *entry {
Inode::Directory {
ref mut children, ..
} => file_tree = children,
_ => panic!("Not a directory"),
}
} else {
file_tree.children.insert(f.to_string(), inode);
break;
}
}
}
fn get_file(&self, file: &str) -> Option<&Inode> {
debug!("get_file {:?}", file);
debug!("repo = {:?}", self);
let mut t = Some(&self.files);
let mut inode = None;
let it = file.split('/').filter(|c| !c.is_empty());
for c in it {
debug!("c = {:?}", c);
inode = t.take().unwrap().children.get(c);
debug!("inode = {:?}", inode);
match inode {
Some(Inode::Directory { ref children, .. }) => t = Some(children),
_ => break,
}
}
inode
}
fn get_file_mut<'a>(&'a mut self, file: &str) -> Option<&'a mut Inode> {
debug!("get_file_mut {:?}", file);
debug!("repo = {:?}", self);
let mut t = Some(&mut self.files);
let mut it = file.split('/').filter(|c| !c.is_empty()).peekable();
self.last_modified = SystemTime::now();
while let Some(c) = it.next() {
debug!("c = {:?}", c);
let inode_ = t.take().unwrap().children.get_mut(c);
debug!("inode = {:?}", inode_);
if it.peek().is_none() {
return inode_;
}
match inode_ {
Some(Inode::Directory {
ref mut children, ..
}) => t = Some(children),
_ => return None,
}
}
None
}
fn remove_path_(&mut self, path: &str) -> Option<Inode> {
debug!("remove_path {:?}", path);
debug!("repo = {:?}", self);
let mut t = Some(&mut self.files);
let mut it = path.split('/').filter(|c| !c.is_empty());
let mut c = it.next().unwrap();
self.last_modified = SystemTime::now();
loop {
debug!("c = {:?}", c);
let next_c = it.next();
let t_ = t.take().unwrap();
let next_c = if let Some(next_c) = next_c {
next_c
} else {
return t_.children.remove(c);
};
let inode = t_.children.get_mut(c);
c = next_c;
debug!("inode = {:?}", inode);
match inode {
Some(Inode::Directory {
ref mut children, ..
}) => t = Some(children),
_ => return None,
}
}
}
}
#[derive(Debug, Error)]
pub enum Error {
#[error("Path not found: {path}")]
NotFound { path: String },
}
impl WorkingCopy for Memory {
type Error = Error;
fn create_dir_all(&mut self, file: &str) -> Result<(), Self::Error> {
if self.get_file(file).is_none() {
let last = SystemTime::now();
self.add_inode(
file,
Inode::Directory {
meta: InodeMetadata::new(0o755, true),
children: FileTree {
children: HashMap::new(),
},
last_modified: last,
},
);
}
Ok(())
}
fn file_metadata(&self, file: &str) -> Result<InodeMetadata, Self::Error> {
match self.get_file(file) {
Some(Inode::Directory { meta, .. }) => Ok(*meta),
Some(Inode::File { meta, .. }) => Ok(*meta),
None => Err(Error::NotFound {
path: file.to_string(),
}),
}
}
fn read_file(&self, file: &str, buffer: &mut Vec<u8>) -> Result<(), Self::Error> {
match self.get_file(file) {
Some(Inode::Directory { .. }) => panic!("Not a file: {:?}", file),
Some(Inode::File { ref contents, .. }) => {
buffer.extend(contents);
Ok(())
}
None => Err(Error::NotFound {
path: file.to_string(),
}),
}
}
fn modified_time(&self, _file: &str) -> Result<std::time::SystemTime, Self::Error> {
Ok(self.last_modified)
}
fn remove_path(&mut self, path: &str) -> Result<(), Self::Error> {
self.remove_path_(path);
Ok(())
}
fn rename(&mut self, old: &str, new: &str) -> Result<(), Self::Error> {
debug!("rename {:?} to {:?}", old, new);
if let Some(inode) = self.remove_path_(old) {
self.add_inode(new, inode)
}
Ok(())
}
fn set_permissions(&mut self, file: &str, permissions: u16) -> Result<(), Self::Error> {
debug!("set_permissions {:?}", file);
match self.get_file_mut(file) {
Some(Inode::File { ref mut meta, .. }) => {
*meta = InodeMetadata::new(permissions as usize, false);
}
Some(Inode::Directory { ref mut meta, .. }) => {
*meta = InodeMetadata::new(permissions as usize, true);
}
None => panic!("file not found: {:?}", file),
}
Ok(())
}
fn write_file<A, E: std::error::Error, F: FnOnce(&mut dyn std::io::Write) -> Result<A, E>>(
&mut self,
file: &str,
writer: F,
) -> Result<A, WriteError<E>> {
match self.get_file_mut(file) {
Some(Inode::File {
ref mut contents, ..
}) => {
contents.clear();
writer(contents).map_err(WriteError::E)
}
None => {
let mut contents = Vec::new();
let last_modified = SystemTime::now();
let a = writer(&mut contents).map_err(WriteError::E)?;
self.add_inode(
file,
Inode::File {
meta: InodeMetadata::new(0o644, false),
contents,
last_modified,
},
);
Ok(a)
}
_ => panic!("not a file: {:?}", file),
}
}
}