13 changed files with 1595 additions and 134 deletions
@ -1,12 +1,13 @@ |
|||||
[package] |
[package] |
||||
name = "cas-core" |
name = "bsvfs" |
||||
version = "0.1.0" |
version = "0.1.0" |
||||
authors = ["Simon Boyé <sim.boye@gmail.com>"] |
authors = ["Simon Boyé <sim.boye@gmail.com>"] |
||||
edition = "2021" |
edition = "2021" |
||||
license = "AGPL-3.0-or-later" |
license = "AGPL-3.0-or-later" |
||||
|
|
||||
[dependencies] |
[dependencies] |
||||
thiserror = "1.0.25" |
|
||||
camino = { version = "1.0.7" } |
camino = { version = "1.0.7" } |
||||
|
thiserror = "1.0.25" |
||||
|
|
||||
[dev-dependencies] |
[dev-dependencies] |
||||
|
tempfile = "3.2.0" |
||||
|
|||||
@ -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'))); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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::*, |
||||
|
}; |
||||
@ -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);
|
||||
|
// }
|
||||
|
} |
||||
Loading…
Reference in new issue