Parking bsvfs WIP experiment.
This commit is contained in:
@@ -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
491
bsvfs/src/fs.rs
Normal 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
386
bsvfs/src/fs_impl.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
468
bsvfs/src/mem_fs.rs
Normal 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);
|
||||
// }
|
||||
}
|
||||
Reference in New Issue
Block a user