#[cfg(not(feature = "std"))]
use spin::{RwLock, RwLockReadGuard as ReadGuard, RwLockWriteGuard as WriteGuard};
#[cfg(feature = "std")]
use std::sync::{
PoisonError, RwLock, RwLockReadGuard as ReadGuard, RwLockWriteGuard as WriteGuard,
};
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::vec::Vec;
use crate::error::ErrorKind;
use super::{FileId, FileInfo, FileSystem, Tag, TagPattern};
type FileData = Vec<Box<[u8]>>;
type TagData = BTreeMap<FileId, BTreeSet<Tag>>;
#[derive(Debug)]
pub enum Error {
FileNotFound(FileId),
Poisoned,
}
#[cfg(feature = "std")]
impl<T> From<PoisonError<T>> for Error {
fn from(_: PoisonError<T>) -> Error {
Error::Poisoned
}
}
impl crate::error::Error for Error {
fn file_not_found(id: FileId) -> Self {
Self::FileNotFound(id)
}
fn generic_kind(&self) -> ErrorKind<'_> {
match self {
Self::FileNotFound(id) => ErrorKind::FileNotFound(*id),
Self::Poisoned => ErrorKind::State,
}
}
}
pub struct InMemoryFs {
files: RwLock<FileData>,
tags: RwLock<TagData>,
}
impl InMemoryFs {
pub fn new() -> InMemoryFs {
InMemoryFs {
files: RwLock::new(Vec::new()),
tags: RwLock::new(BTreeMap::new()),
}
}
fn read_files(&self) -> Result<ReadGuard<'_, FileData>, Error> {
#[cfg(feature = "std")]
let out = self.files.read()?;
#[cfg(not(feature = "std"))]
let out = self.files.read();
Ok(out)
}
fn write_files(&self) -> Result<WriteGuard<'_, FileData>, Error> {
#[cfg(feature = "std")]
let out = self.files.write()?;
#[cfg(not(feature = "std"))]
let out = self.files.write();
Ok(out)
}
fn read_tags(&self) -> Result<ReadGuard<'_, TagData>, Error> {
#[cfg(feature = "std")]
let out = self.tags.read()?;
#[cfg(not(feature = "std"))]
let out = self.tags.read();
Ok(out)
}
fn write_tags(&self) -> Result<WriteGuard<'_, TagData>, Error> {
#[cfg(feature = "std")]
let out = self.tags.write()?;
#[cfg(not(feature = "std"))]
let out = self.tags.write();
Ok(out)
}
fn assert_file_exists(&self, id: FileId) -> Result<(), Error> {
self.read_tags()?
.get(&id)
.map(|_| ())
.ok_or(Error::FileNotFound(id))
}
}
impl Default for InMemoryFs {
fn default() -> Self {
InMemoryFs::new()
}
}
impl FileSystem for InMemoryFs {
type Error = Error;
fn add_file<I>(&self, data: &[u8], tags: I) -> Result<FileId, Self::Error>
where
I: IntoIterator<Item = Tag>,
{
let new_id = {
let mut files = self.write_files()?;
files.push(data.to_owned().into_boxed_slice());
FileId::from_u64_unchecked(files.len() as u64 + 255)
};
let mut tags_map = self.write_tags()?;
tags_map.insert(new_id, tags.into_iter().collect());
Ok(new_id)
}
fn edit_file<I>(
&self,
id: FileId,
data: Option<&[u8]>,
tags: Option<I>,
) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Tag>,
{
self.assert_file_exists(id)?;
if let Some(data) = data {
let mut files = self.write_files()?;
files[(id.into_u64_unchecked() - 255) as usize] = data.to_owned().into_boxed_slice();
}
if let Some(tags) = tags {
let mut tags_map = self.write_tags()?;
tags_map.insert(id, tags.into_iter().collect());
}
Ok(())
}
fn remove_file(&self, id: FileId) -> Result<(), Self::Error> {
self.assert_file_exists(id)?;
let mut files = self.write_files()?;
files[(id.into_u64_unchecked() - 255) as usize] = Box::new([]) as Box<[u8]>;
let mut tags_map = self.write_tags()?;
tags_map.remove(&id);
Ok(())
}
fn search_tags<P>(&self, tags: P) -> Result<Vec<FileId>, Self::Error>
where
P: TagPattern,
{
let mut out = Vec::new();
for (id, file_tags) in self.read_tags()?.iter() {
if tags.match_tags(file_tags) {
out.push(*id)
}
}
Ok(out)
}
fn get_info(&self, id: FileId) -> Result<FileInfo, Self::Error> {
self.assert_file_exists(id)?;
Ok(FileInfo {
id,
data: self.read_files()?[(id.into_u64_unchecked() - 255) as usize].clone(),
tags: self.read_tags()?.get(&id).unwrap().clone(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_add_file() {
let ifs = InMemoryFs::new();
let id = ifs.add_file(&[0, 1, 2], []).unwrap();
assert_eq!(id, FileId::from_u64_unchecked(256));
}
#[test]
pub fn test_search_files() {
let ifs = InMemoryFs::new();
let first = ifs
.add_file(&[0, 1, 2], [Tag::named("a"), Tag::named("b")])
.unwrap();
let second = ifs.add_file(&[0, 1, 2], [Tag::named("a")]).unwrap();
let third = ifs.add_file(&[0, 1, 2], [Tag::named("b")]).unwrap();
let fourth = ifs
.add_file(&[0, 1, 2], [Tag::named("c"), Tag::named("a")])
.unwrap();
let items = ifs.search_tags(Tag::named("a")).unwrap();
assert!(items.contains(&first) && items.contains(&second) && items.contains(&fourth));
assert!(!items.contains(&third));
let items = ifs.search_tags(Tag::named("b")).unwrap();
assert!(items.contains(&first) && items.contains(&third));
assert!(!items.contains(&second) && !items.contains(&fourth));
}
}