Parking bsvfs WIP experiment.

This commit is contained in:
Simon Boyé
2023-08-11 23:14:54 +02:00
parent 7230127f26
commit 5a94fdd4a9
13 changed files with 1596 additions and 135 deletions

View File

@@ -1,12 +1,13 @@
[package]
name = "cas-core"
name = "bsvfs"
version = "0.1.0"
authors = ["Simon Boyé <sim.boye@gmail.com>"]
edition = "2021"
license = "AGPL-3.0-or-later"
[dependencies]
thiserror = "1.0.25"
camino = { version = "1.0.7" }
thiserror = "1.0.25"
[dev-dependencies]
tempfile = "3.2.0"

491
bsvfs/src/fs.rs Normal file
View File

@@ -0,0 +1,491 @@
// This file is part of bsv.
//
// bsv is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// bsv is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
// more details.
//
// You should have received a copy of the Affero GNU General Public License
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
use std::{
io::{Read, Result, Seek, Write},
time::SystemTime,
};
use camino::{Utf8Path, Utf8PathBuf};
// FileType
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FileType {
Dir,
File,
Symlink,
Other,
}
impl FileType {
pub fn is_dir(&self) -> bool {
*self == FileType::Dir
}
pub fn is_file(&self) -> bool {
*self == FileType::File
}
pub fn is_symlink(&self) -> bool {
*self == FileType::Symlink
}
}
// Permissions
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Permissions {
read: bool,
write: bool,
execute: bool,
}
impl Permissions {
pub fn new(read: bool, write: bool, execute: bool) -> Self {
Permissions { read, write, execute }
}
pub fn is_read(&self) -> bool {
self.read
}
pub fn is_write(&self) -> bool {
self.write
}
pub fn is_execute(&self) -> bool {
self.execute
}
}
impl std::fmt::Display for Permissions {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
use std::fmt::Write;
f.write_char(if self.read { 'r' } else { '-' })?;
f.write_char(if self.write { 'w' } else { '-' })?;
f.write_char(if self.execute { 'x' } else { '-' })?;
Ok(())
}
}
impl std::str::FromStr for Permissions {
type Err = ParsePermissionError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
if s.len() == 3 {
let mut chars = s.chars();
let read = parse_permission_char(&mut chars, 'r')?;
let write = parse_permission_char(&mut chars, 'w')?;
let execute = parse_permission_char(&mut chars, 'x')?;
assert!(chars.next().is_none());
Ok(Self { read, write, execute })
}
else {
Err(ParsePermissionError::InvalidLength(s.len()))
}
}
}
fn parse_permission_char(chars: &mut std::str::Chars, bit_char: char)
-> std::result::Result<bool, ParsePermissionError>
{
let c = chars.next().unwrap();
if c == bit_char || c == '-' {
Ok(c == bit_char)
}
else {
Err(ParsePermissionError::InvalidChar(bit_char, c))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ParsePermissionError {
InvalidLength(usize),
InvalidChar(char, char),
}
impl std::fmt::Display for ParsePermissionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidLength(length) => write!(f, "expected 3 characters, got {}", length),
Self::InvalidChar(expected, actual) => write!(f, "exected character {}, got {}", expected, actual),
}
}
}
impl std::error::Error for ParsePermissionError {
}
// Metadata
pub trait Metadata {
fn file_type(&self) -> FileType;
fn is_dir(&self) -> bool {
self.file_type().is_dir()
}
fn is_file(&self) -> bool {
self.file_type().is_file()
}
fn is_symlink(&self) -> bool {
self.file_type().is_symlink()
}
fn len(&self) -> u64;
fn permissions(&self) -> Permissions;
fn modified(&self) -> Result<SystemTime>;
fn created(&self) -> Result<SystemTime>;
}
// File
const OPEN_READ: u8 = 0x01;
const OPEN_WRITE: u8 = 0x02;
const OPEN_APPEND: u8 = 0x04;
const OPEN_TRUNCATE: u8 = 0x08;
const OPEN_CREATE: u8 = 0x10;
const OPEN_CREATE_NEW: u8 = 0x20;
#[derive(Debug, PartialEq, Eq)]
pub struct OpenOptions {
options: u8,
}
impl OpenOptions {
pub fn new() -> Self {
OpenOptions {
options: 0,
}
}
pub fn is_read(&self) -> bool {
(self.options & OPEN_READ) != 0
}
pub fn is_write(&self) -> bool {
(self.options & OPEN_WRITE) != 0
}
pub fn is_append(&self) -> bool {
(self.options & OPEN_APPEND) != 0
}
pub fn is_truncate(&self) -> bool {
(self.options & OPEN_TRUNCATE) != 0
}
pub fn is_create(&self) -> bool {
(self.options & OPEN_CREATE) != 0
}
pub fn is_create_new(&self) -> bool {
(self.options & OPEN_CREATE_NEW) != 0
}
pub fn read(&mut self, read: bool) -> &mut Self {
if read {
self.options |= OPEN_READ;
} else {
self.options &= !OPEN_READ;
}
self
}
pub fn write(&mut self, write: bool) -> &mut Self {
if write {
self.options |= OPEN_WRITE;
} else {
self.options &= !OPEN_WRITE;
}
self
}
pub fn append(&mut self, append: bool) -> &mut Self {
if append {
self.options |= OPEN_APPEND;
} else {
self.options &= !OPEN_APPEND;
}
self
}
pub fn truncate(&mut self, truncate: bool) -> &mut Self {
if truncate {
self.options |= OPEN_TRUNCATE;
} else {
self.options &= !OPEN_TRUNCATE;
}
self
}
pub fn create(&mut self, create: bool) -> &mut Self {
if create {
self.options |= OPEN_CREATE;
} else {
self.options &= !OPEN_CREATE;
}
self
}
pub fn create_new(&mut self, create_new: bool) -> &mut Self {
if create_new {
self.options |= OPEN_CREATE_NEW;
} else {
self.options &= !OPEN_CREATE_NEW;
}
self
}
}
pub trait File: Sized + Read + Write + Seek {
type Metadata: Metadata;
fn metadata(&self) -> Result<Self::Metadata>;
fn set_len(&self, size: u64) -> Result<()>;
fn sync_data(&self) -> Result<()>;
fn sync_all(&self) -> Result<()>;
}
// DirEntry
pub trait DirEntry {
type Metadata: Metadata;
fn path(&self) -> Result<Utf8PathBuf>;
fn metadata(&self) -> Result<Self::Metadata>;
fn file_type(&self) -> Result<FileType> {
Ok(self.metadata()?.file_type())
}
fn file_name(&self) -> Result<String> {
Ok(
self.path()?
.file_name()
.expect("DirEntry::path() returned an invalid path with no file name")
.to_string()
)
}
}
// FileSystem
pub trait FileSystem {
type File: File;
type DirEntry: DirEntry;
type ReadDir: Iterator<Item=Result<Self::DirEntry>>;
fn open_with_options<P: AsRef<Utf8Path>>(&self, path: P, options: &OpenOptions) -> Result<Self::File>;
fn open<P: AsRef<Utf8Path>>(&self, path: P) -> Result<Self::File> {
self.open_with_options(
path,
OpenOptions::new()
.read(true)
)
}
fn create_or_truncate<P: AsRef<Utf8Path>>(&self, path: P) -> Result<Self::File> {
self.open_with_options(
path,
OpenOptions::new()
.write(true)
.create(true)
)
}
fn create_new<P: AsRef<Utf8Path>>(&self, path: P) -> Result<Self::File> {
self.open_with_options(
path,
OpenOptions::new()
.write(true)
.create_new(true)
)
}
fn read_dir<P: AsRef<Utf8Path>>(&self, path: P) -> Result<Self::ReadDir>;
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use super::*;
#[test]
fn test_file_type() {
let dir = FileType::Dir;
assert!(dir.is_dir());
assert!(!dir.is_file());
assert!(!dir.is_symlink());
let file = FileType::File;
assert!(!file.is_dir());
assert!(file.is_file());
assert!(!file.is_symlink());
let symlink = FileType::Symlink;
assert!(!symlink.is_dir());
assert!(!symlink.is_file());
assert!(symlink.is_symlink());
}
#[test]
fn test_open_options() {
let mut options = OpenOptions::new();
assert!(!options.is_read());
assert!(!options.is_write());
assert!(!options.is_append());
assert!(!options.is_truncate());
assert!(!options.is_create());
assert!(!options.is_create_new());
options.read(true);
assert!(options.is_read());
assert!(!options.is_write());
assert!(!options.is_append());
assert!(!options.is_truncate());
assert!(!options.is_create());
assert!(!options.is_create_new());
options.read(false);
assert!(!options.is_read());
assert!(!options.is_write());
assert!(!options.is_append());
assert!(!options.is_truncate());
assert!(!options.is_create());
assert!(!options.is_create_new());
options.write(true);
assert!(!options.is_read());
assert!(options.is_write());
assert!(!options.is_append());
assert!(!options.is_truncate());
assert!(!options.is_create());
assert!(!options.is_create_new());
options.write(false);
assert!(!options.is_read());
assert!(!options.is_write());
assert!(!options.is_append());
assert!(!options.is_truncate());
assert!(!options.is_create());
assert!(!options.is_create_new());
options.append(true);
assert!(!options.is_read());
assert!(!options.is_write());
assert!(options.is_append());
assert!(!options.is_truncate());
assert!(!options.is_create());
assert!(!options.is_create_new());
options.append(false);
assert!(!options.is_read());
assert!(!options.is_write());
assert!(!options.is_append());
assert!(!options.is_truncate());
assert!(!options.is_create());
assert!(!options.is_create_new());
options.truncate(true);
assert!(!options.is_read());
assert!(!options.is_write());
assert!(!options.is_append());
assert!(options.is_truncate());
assert!(!options.is_create());
assert!(!options.is_create_new());
options.truncate(false);
assert!(!options.is_read());
assert!(!options.is_write());
assert!(!options.is_append());
assert!(!options.is_truncate());
assert!(!options.is_create());
assert!(!options.is_create_new());
options.create(true);
assert!(!options.is_read());
assert!(!options.is_write());
assert!(!options.is_append());
assert!(!options.is_truncate());
assert!(options.is_create());
assert!(!options.is_create_new());
options.create(false);
assert!(!options.is_read());
assert!(!options.is_write());
assert!(!options.is_append());
assert!(!options.is_truncate());
assert!(!options.is_create());
assert!(!options.is_create_new());
options.create_new(true);
assert!(!options.is_read());
assert!(!options.is_write());
assert!(!options.is_append());
assert!(!options.is_truncate());
assert!(!options.is_create());
assert!(options.is_create_new());
options.create_new(false);
assert!(!options.is_read());
assert!(!options.is_write());
assert!(!options.is_append());
assert!(!options.is_truncate());
assert!(!options.is_create());
assert!(!options.is_create_new());
}
#[test]
fn test_permission_display() {
let expected = [
"---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx",
];
for i in 0..8 {
let read = (i & 0x04) != 0;
let write = (i & 0x02) != 0;
let execute = (i & 0x01) != 0;
let perm = Permissions { read, write, execute };
let perm_str = perm.to_string();
assert_eq!(perm_str, expected[i]);
}
}
#[test]
fn test_permission_from_str() {
let perms = [
"---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx",
];
for i in 0..8 {
let read = (i & 0x04) != 0;
let write = (i & 0x02) != 0;
let execute = (i & 0x01) != 0;
let perm = Permissions::from_str(perms[i]).unwrap();
let expected = Permissions { read, write, execute };
assert_eq!(perm, expected);
}
assert_eq!(Permissions::from_str("rw"), Err(ParsePermissionError::InvalidLength(2)));
assert_eq!(Permissions::from_str("rw--"), Err(ParsePermissionError::InvalidLength(4)));
assert_eq!(Permissions::from_str("-x-"), Err(ParsePermissionError::InvalidChar('w', 'x')));
assert_eq!(Permissions::from_str("123"), Err(ParsePermissionError::InvalidChar('r', '1')));
}
}

386
bsvfs/src/fs_impl.rs Normal file
View File

@@ -0,0 +1,386 @@
// This file is part of bsv.
//
// bsv is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// bsv is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
// more details.
//
// You should have received a copy of the Affero GNU General Public License
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
use std::{
error::Error,
ffi::OsString,
fmt::{Arguments, Display},
fs::{
self,
DirEntry as StdDirEntry,
File as StdFile,
Metadata as StdMetadata,
ReadDir as StdReadDir,
},
io::{self, IoSlice, IoSliceMut, Read, Result, Seek, SeekFrom, Write},
time::SystemTime,
};
use camino::{Utf8Path, Utf8PathBuf};
use crate::{
fs::*,
};
#[derive(Clone, Debug)]
struct NonUtf8Filename {
filename: OsString,
}
impl NonUtf8Filename {
fn new(filename: OsString) -> Self {
Self {
filename: filename,
}
}
}
impl Display for NonUtf8Filename {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Non UTF-8 filename: '{:?}'", self.filename)
}
}
impl Error for NonUtf8Filename {
}
fn as_io_error<E>(error: E) -> io::Error
where E: Into<Box<dyn Error + Send + Sync>>,
{
io::Error::new(io::ErrorKind::Other, error)
}
fn as_string(os_string: OsString) -> Result<String> {
os_string.into_string()
.map_err(|filename| {
io::Error::new(
io::ErrorKind::Other,
NonUtf8Filename::new(filename),
)
})
}
fn as_filetype(std_filetype: fs::FileType) -> FileType {
if std_filetype.is_dir() {
FileType::Dir
} else if std_filetype.is_file() {
FileType::File
} else if std_filetype.is_symlink() {
FileType::Symlink
} else {
FileType::Other
}
}
pub struct SysMetadata {
metadata: StdMetadata,
}
impl Metadata for SysMetadata {
fn file_type(&self) -> FileType {
as_filetype(self.metadata.file_type())
}
fn len(&self) -> u64 {
self.metadata.len()
}
#[cfg(not(unix))]
fn permissions(&self) -> Permissions {
Permissions(true, self.metadata.permissions().readonly(), false)
}
#[cfg(unix)]
fn permissions(&self) -> Permissions {
use std::os::unix::fs::MetadataExt;
let mode = self.metadata.mode();
Permissions::new(
mode & 0o100 != 0,
mode & 0o200 != 0,
mode & 0o400 != 0,
)
}
fn modified(&self) -> Result<SystemTime> {
Ok(self.metadata.modified()?)
}
fn created(&self) -> Result<SystemTime> {
Ok(self.metadata.created()?)
}
}
impl From<StdMetadata> for SysMetadata {
fn from(value: StdMetadata) -> Self {
SysMetadata {
metadata: value
}
}
}
pub struct SysFile {
file: StdFile,
}
impl File for SysFile {
type Metadata = SysMetadata;
fn metadata(&self) -> Result<Self::Metadata> {
Ok(self.file.metadata()?.into())
}
fn set_len(&self, size: u64) -> Result<()> {
Ok(self.file.set_len(size)?)
}
fn sync_data(&self) -> Result<()> {
Ok(self.file.sync_data()?)
}
fn sync_all(&self) -> Result<()> {
Ok(self.file.sync_all()?)
}
}
impl Read for SysFile {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
self.file.read(buf)
}
fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> {
self.file.read_vectored(bufs)
}
// fn is_read_vectored(&self) -> bool {
// self.file.is_read_vectored()
// }
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
self.file.read_to_end(buf)
}
fn read_to_string(&mut self, buf: &mut String) -> Result<usize> {
self.file.read_to_string(buf)
}
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
self.file.read_exact(buf)
}
// fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> Result<()> {
// self.file.read_buf(buf)
// }
// fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> Result<()> {
// self.file.read_buf_exact(cursor)
// }
}
impl Seek for SysFile {
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
self.file.seek(pos)
}
fn rewind(&mut self) -> Result<()> {
self.file.rewind()
}
// fn stream_len(&mut self) -> Result<u64> {
// self.file.stream_len()
// }
fn stream_position(&mut self) -> Result<u64> {
self.file.stream_position()
}
}
impl Write for SysFile {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
self.file.write(buf)
}
fn flush(&mut self) -> Result<()> {
self.file.flush()
}
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> {
self.file.write_vectored(bufs)
}
// fn is_write_vectored(&self) -> bool {
// self.file.is_write_vectored()
// }
fn write_all(&mut self, buf: &[u8]) -> Result<()> {
self.file.write_all(buf)
}
// fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<()> {
// self.file.write_all_vectored(bufs)
// }
fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()> {
self.file.write_fmt(fmt)
}
}
pub struct SysDirEntry {
dir_entry: StdDirEntry,
}
impl DirEntry for SysDirEntry {
type Metadata = SysMetadata;
fn path(&self) -> Result<Utf8PathBuf> {
Utf8PathBuf::try_from(
self.dir_entry.path()
).map_err(as_io_error)
}
fn metadata(&self) -> Result<Self::Metadata> {
Ok(self.dir_entry.metadata()?.into())
}
fn file_type(&self) -> Result<FileType> {
Ok(as_filetype(self.dir_entry.file_type()?))
}
fn file_name(&self) -> Result<String> {
as_string(self.dir_entry.file_name())
}
}
impl From<StdDirEntry> for SysDirEntry {
fn from(dir_entry: StdDirEntry) -> Self {
SysDirEntry {
dir_entry: dir_entry,
}
}
}
pub struct SysReadDir {
read_dir: StdReadDir,
}
impl Iterator for SysReadDir {
type Item = Result<SysDirEntry>;
fn next(&mut self) -> Option<Self::Item> {
match self.read_dir.next() {
Some(Ok(dir_entry)) => Some(Ok(dir_entry.into())),
Some(Err(error)) => Some(Err(error)),
None => None,
}
}
}
pub struct SysFileSystem;
impl SysFileSystem {
pub fn new() -> Self {
Self{}
}
}
impl FileSystem for SysFileSystem {
type File = SysFile;
type DirEntry = SysDirEntry;
type ReadDir = SysReadDir;
fn open_with_options<P: AsRef<Utf8Path>>(&self, path: P, options: &OpenOptions) -> Result<Self::File> {
Ok(SysFile{
file: StdFile::options()
.read(options.is_read())
.write(options.is_write())
.append(options.is_append())
.truncate(options.is_truncate())
.create(options.is_create())
.create_new(options.is_create_new())
.open(path.as_ref().as_std_path())?,
})
}
fn read_dir<P: AsRef<Utf8Path>>(&self, path: P) -> Result<SysReadDir> {
Ok(SysReadDir {
read_dir: fs::read_dir(path.as_ref().as_std_path())?,
})
}
}
#[cfg(test)]
mod test {
use camino::Utf8PathBuf;
use super::*;
use crate::FileSystem;
#[test]
fn test_file_read_write() {
let dir = tempfile::tempdir().expect("failed to create temp test dir");
let file_path = Utf8PathBuf::from_path_buf(dir.path().join("test.txt")).unwrap();
let message = b"Hello world!\nThis is a test\n";
let fs = SysFileSystem::new();
let mut file = fs.create_new(&file_path).unwrap();
file.write(message).unwrap();
drop(file);
let mut file = fs.open(&file_path).unwrap();
let mut buf = [0u8; 256];
let read_size = file.read(&mut buf).unwrap();
assert_eq!(read_size, message.len());
assert_eq!(&buf[..message.len()], message);
}
#[test]
fn test_read_dir() {
let dir = tempfile::tempdir().expect("failed to create temp test dir");
let dir_path = Utf8Path::from_path(dir.path()).unwrap();
let file0_path = dir_path.join("file.txt");
let file1_path = dir_path.join("zzz.txt");
let dir0_path = dir_path.join("some_dir");
let fs = SysFileSystem::new();
{
let mut file = fs.create_new(file0_path).unwrap();
file.write(b"file.txt").unwrap();
}
{
let mut file = fs.create_new(file1_path).unwrap();
file.write(b"zzz.txt").unwrap();
}
fs::create_dir(dir0_path).unwrap();
let mut entries = fs.read_dir(dir_path).unwrap()
.collect::<Result<Vec<_>>>().unwrap();
entries.sort_by_key(|entry| entry.file_name().unwrap());
let mut it = entries.iter();
let file0_entry = it.next().unwrap();
assert_eq!(file0_entry.file_name().unwrap(), "file.txt");
assert_eq!(file0_entry.file_type().unwrap(), FileType::File);
let file0_metadata = file0_entry.metadata().unwrap();
assert!(file0_metadata.is_file());
assert_eq!(file0_metadata.len(), 8);
let dir0_entry = it.next().unwrap();
assert_eq!(dir0_entry.file_name().unwrap(), "some_dir");
assert_eq!(dir0_entry.file_type().unwrap(), FileType::Dir);
let dir0_metadata = dir0_entry.metadata().unwrap();
assert!(dir0_metadata.is_dir());
let file1_entry = it.next().unwrap();
assert_eq!(file1_entry.file_name().unwrap(), "zzz.txt");
assert_eq!(file1_entry.file_type().unwrap(), FileType::File);
let file1_metadata = file1_entry.metadata().unwrap();
assert!(file1_metadata.is_file());
assert_eq!(file1_metadata.len(), 7);
}
}

View File

@@ -0,0 +1,32 @@
// This file is part of bsv.
//
// bsv is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// bsv is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
// more details.
//
// You should have received a copy of the Affero GNU General Public License
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
extern crate camino;
extern crate thiserror;
#[cfg(test)]
extern crate tempfile;
// mod error;
mod fs;
mod fs_impl;
mod mem_fs;
pub use crate::{
fs::*,
fs_impl::*,
};

468
bsvfs/src/mem_fs.rs Normal file
View File

@@ -0,0 +1,468 @@
// This file is part of bsv.
//
// bsv is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// bsv is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
// more details.
//
// You should have received a copy of the Affero GNU General Public License
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
use std::{
fmt::{Arguments},
io::{Cursor, Error, IoSlice, IoSliceMut, Read, Result, Seek, SeekFrom, Write, ErrorKind},
time::SystemTime, rc::Rc, cell::RefCell,
};
use camino::{Utf8Path, Utf8PathBuf};
use crate::{
fs::*,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MemMetadata {
file_type: FileType,
len: u64,
permissions: Permissions,
modified: SystemTime,
created: SystemTime,
}
impl Metadata for MemMetadata {
fn file_type(&self) -> FileType {
self.file_type
}
fn len(&self) -> u64 {
self.len
}
fn permissions(&self) -> Permissions {
self.permissions.clone()
}
fn modified(&self) -> Result<SystemTime> {
Ok(self.modified)
}
fn created(&self) -> Result<SystemTime> {
Ok(self.created)
}
}
#[derive(Clone, Debug)]
pub struct MemFile {
metadata: MemMetadata,
data: RefCell<Cursor<Vec<u8>>>,
}
impl File for MemFile {
type Metadata = MemMetadata;
// fn open_with_options<P: AsRef<Utf8Path>>(path: P, options: &OpenOptions) -> Result<Self> {
// Ok(SysFile{
// file: StdFile::options()
// .read(options.is_read())
// .write(options.is_write())
// .append(options.is_append())
// .truncate(options.is_truncate())
// .create(options.is_create())
// .create_new(options.is_create_new())
// .open(path.as_ref().as_std_path())?,
// })
// }
fn metadata(&self) -> Result<Self::Metadata> {
Ok(self.metadata.clone())
}
fn set_len(&self, size: u64) -> Result<()> {
let mut data = self.data.borrow_mut();
let pos = data.position();
data.get_mut().resize(size as usize, 0);
if size < pos {
data.seek(SeekFrom::Start(pos));
}
Ok(())
}
fn sync_data(&self) -> Result<()> {
Ok(())
}
fn sync_all(&self) -> Result<()> {
Ok(())
}
}
impl Read for MemFile {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
let data = self.data.get_mut();
data.read(buf)
}
fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> {
let data = self.data.get_mut();
data.read_vectored(bufs)
}
// fn is_read_vectored(&self) -> bool {
// self.data.is_read_vectored()
// }
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
let data = self.data.get_mut();
data.read_to_end(buf)
}
fn read_to_string(&mut self, buf: &mut String) -> Result<usize> {
let data = self.data.get_mut();
data.read_to_string(buf)
}
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
let data = self.data.get_mut();
data.read_exact(buf)
}
// fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> Result<()> {
// self.data.read_buf(buf)
// }
// fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> Result<()> {
// self.data.read_buf_exact(cursor)
// }
}
impl Seek for MemFile {
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
let data = self.data.get_mut();
data.seek(pos)
}
fn rewind(&mut self) -> Result<()> {
let data = self.data.get_mut();
data.rewind()
}
// fn stream_len(&mut self) -> Result<u64> {
// self.data.stream_len()
// }
fn stream_position(&mut self) -> Result<u64> {
let data = self.data.get_mut();
data.stream_position()
}
}
impl Write for MemFile {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
let data = self.data.get_mut();
data.write(buf)
}
fn flush(&mut self) -> Result<()> {
let data = self.data.get_mut();
data.flush()
}
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> {
let data = self.data.get_mut();
data.write_vectored(bufs)
}
// fn is_write_vectored(&self) -> bool {
// self.data.is_write_vectored()
// }
fn write_all(&mut self, buf: &[u8]) -> Result<()> {
let data = self.data.get_mut();
data.write_all(buf)
}
// fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<()> {
// self.data.write_all_vectored(bufs)
// }
fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()> {
let data = self.data.get_mut();
data.write_fmt(fmt)
}
}
#[derive(Clone, Debug)]
struct MemDir {
metadata: MemMetadata,
items: Vec<Rc<RefCell<DirItem>>>,
}
impl MemDir {
fn new() -> Self {
let time = SystemTime::now();
Self::new_with_time(&time)
}
fn new_with_time(time: &SystemTime) -> Self {
MemDir {
metadata: MemMetadata {
file_type: FileType::Dir,
len: 0,
permissions: Permissions::new(true, true, false),
modified: time.clone(),
created: time.clone(),
},
items: vec![],
}
}
fn get(&self, name: &str) -> Option<&DirItem> {
None
}
fn get_mut(&self, name: &str) -> Option<&mut DirItem> {
None
}
fn search(&self, name: &str) -> std::result::Result<usize, usize> {
self.items.binary_search_by(
|item| item.borrow().name().cmp(name),
)
}
}
#[derive(Clone, Debug)]
enum DirItem {
Dir(String, MemDir),
File(String, MemFile),
Err(String, ErrorKind, String),
}
impl DirItem {
fn name(&self) -> &str {
match self {
DirItem::Dir(ref name, _) => name,
DirItem::File(ref name, _) => name,
DirItem::Err(ref name, _, _) => name,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MemDirEntry {
path: Utf8PathBuf,
metadata: MemMetadata,
}
impl DirEntry for MemDirEntry {
type Metadata = MemMetadata;
fn path(&self) -> Result<Utf8PathBuf> {
Ok(self.path.clone())
}
fn metadata(&self) -> Result<Self::Metadata> {
Ok(self.metadata.clone())
}
fn file_type(&self) -> Result<FileType> {
Ok(self.metadata.file_type())
}
fn file_name(&self) -> Result<String> {
Ok(self.path.file_name().unwrap().into())
}
}
pub struct MemReadDir {
base_path: Utf8PathBuf,
items: Vec<DirItem>,
index: usize,
}
impl Iterator for MemReadDir {
type Item = Result<MemDirEntry>;
fn next(&mut self) -> Option<Self::Item> {
match self.items.get(self.index) {
Some(DirItem::Dir(ref name, ref dir)) => {
let mut path = self.base_path.clone();
path.push(name);
self.index += 1;
Some(Ok(MemDirEntry{path, metadata: dir.metadata.clone()}))
},
Some(DirItem::File(ref name, ref file)) => {
let mut path = self.base_path.clone();
path.push(name);
self.index += 1;
Some(Ok(MemDirEntry{path, metadata: file.metadata.clone()}))
},
Some(DirItem::Err(_, kind, ref msg)) => {
self.index += 1;
Some(Err(Error::new(*kind, msg.clone())))
},
None => None,
}
}
}
pub struct MemFileSystem {
root: MemDir,
}
impl MemFileSystem {
pub fn new() -> Self {
Self {
root: MemDir::new_with_time(&SystemTime::UNIX_EPOCH),
}
}
pub fn mkdir<P: AsRef<Utf8Path>>(&mut self, path: P, parents: bool, exist_ok: bool) -> Result<()> {
use camino::Utf8Component::*;
let mut parents = Vec::new();
let mut iter = path.as_ref().components();
match iter.next() {
Some(Prefix(_)) => {
return make_err("Windows-like path are not supported by MemFileSystem");
},
Some(RootDir) => {
parents.push(&mut self.root);
},
Some(_) => {
return make_err("path must be absolute");
},
None => {
return make_err("path is empty")
},
}
for comp in iter {
match comp {
Prefix(_) |
RootDir => {
panic!();
},
CurDir => {},
ParentDir => {
if parents.len() <= 1 {
return make_err("invalid path")
}
parents.pop();
},
Normal(name) => {
let maybe_item = parents.last().unwrap().get_mut(name);
match maybe_item {
Some(DirItem::Dir(_, ref mut dir)) => {
parents.push(dir);
},
Some(DirItem::File(_, _)) => {
return make_err("item is a file")
}
Some(DirItem::Err(_, kind, msg)) => {
return Err(std::io::Error::new(*kind, &**msg));
}
None => {},
}
},
}
}
make_err("TODO")
}
}
impl FileSystem for MemFileSystem {
type File = MemFile;
type DirEntry = MemDirEntry;
type ReadDir = MemReadDir;
fn open_with_options<P: AsRef<Utf8Path>>(&self, path: P, options: &OpenOptions) -> Result<Self::File> {
make_err("TODO")
}
fn read_dir<P: AsRef<Utf8Path>>(&self, path: P) -> Result<MemReadDir> {
Ok(MemReadDir {
base_path: path.as_ref().into(),
items: vec![], // TODO
index: 0,
})
}
}
fn make_error(message: &str) -> std::io::Error {
std::io::Error::new(
std::io::ErrorKind::Other,
message,
)
}
fn make_err<T>(message: &str) -> Result<T> {
Err(make_error(message))
}
#[cfg(test)]
mod test {
// use camino::Utf8PathBuf;
// use super::*;
// #[test]
// fn test_file_read_write() {
// let dir = tempfile::tempdir().expect("failed to create temp test dir");
// let file_path = Utf8PathBuf::from_path_buf(dir.path().join("test.txt")).unwrap();
// let message = b"Hello world!\nThis is a test\n";
// let mut file = SysFile::create_new(&file_path).unwrap();
// file.write(message).unwrap();
// drop(file);
// let mut file = SysFile::open(&file_path).unwrap();
// let mut buf = [0u8; 256];
// let read_size = file.read(&mut buf).unwrap();
// assert_eq!(read_size, message.len());
// assert_eq!(&buf[..message.len()], message);
// }
// #[test]
// fn test_read_dir() {
// let dir = tempfile::tempdir().expect("failed to create temp test dir");
// let dir_path = Utf8Path::from_path(dir.path()).unwrap();
// let file0_path = dir_path.join("file.txt");
// let file1_path = dir_path.join("zzz.txt");
// let dir0_path = dir_path.join("some_dir");
// {
// let mut file = SysFile::create_new(file0_path).unwrap();
// file.write(b"file.txt").unwrap();
// }
// {
// let mut file = SysFile::create_new(file1_path).unwrap();
// file.write(b"zzz.txt").unwrap();
// }
// fs::create_dir(dir0_path).unwrap();
// let mut entries = SysFileSystem::read_dir(dir_path).unwrap()
// .collect::<Result<Vec<_>>>().unwrap();
// entries.sort_by_key(|entry| entry.file_name().unwrap());
// let mut it = entries.iter();
// let file0_entry = it.next().unwrap();
// assert_eq!(file0_entry.file_name().unwrap(), "file.txt");
// assert_eq!(file0_entry.file_type().unwrap(), FileType::File);
// let file0_metadata = file0_entry.metadata().unwrap();
// assert!(file0_metadata.is_file());
// assert_eq!(file0_metadata.len(), 8);
// let dir0_entry = it.next().unwrap();
// assert_eq!(dir0_entry.file_name().unwrap(), "some_dir");
// assert_eq!(dir0_entry.file_type().unwrap(), FileType::Dir);
// let dir0_metadata = dir0_entry.metadata().unwrap();
// assert!(dir0_metadata.is_dir());
// let file1_entry = it.next().unwrap();
// assert_eq!(file1_entry.file_name().unwrap(), "zzz.txt");
// assert_eq!(file1_entry.file_type().unwrap(), FileType::File);
// let file1_metadata = file1_entry.metadata().unwrap();
// assert!(file1_metadata.is_file());
// assert_eq!(file1_metadata.len(), 7);
// }
}