pub mod dir_tree;
pub mod virtual_path;
use std::fmt::Debug;
pub use dir_tree::*;
pub use virtual_path::*;
use crate::{
NameMismatchError,
update::{
DiffError, FailedUpdateApplication, IsModified, VfsDiff, VfsDiffList, VfsUpdate,
VfsUpdateApplicationError,
},
};
#[derive(Debug, Clone)]
pub struct Vfs<SyncInfo> {
root: VfsNode<SyncInfo>,
}
impl<SyncInfo> Vfs<SyncInfo> {
pub fn root(&self) -> &VfsNode<SyncInfo> {
&self.root
}
pub fn root_mut(&mut self) -> &mut VfsNode<SyncInfo> {
&mut self.root
}
pub fn structural_eq<OtherSyncInfo>(&self, other: &Vfs<OtherSyncInfo>) -> bool {
self.root.structural_eq(other.root())
}
pub fn find_dir(&self, path: &VirtualPath) -> Result<&DirTree<SyncInfo>, InvalidPathError> {
self.root.find_dir(path)
}
pub fn find_file(&self, path: &VirtualPath) -> Result<&FileMeta<SyncInfo>, InvalidPathError> {
self.root.find_file(path)
}
pub fn find_node(&self, path: &VirtualPath) -> Option<&VfsNode<SyncInfo>> {
self.root.find_node(path)
}
pub fn find_dir_mut(
&mut self,
path: &VirtualPath,
) -> Result<&mut DirTree<SyncInfo>, InvalidPathError> {
self.root.find_dir_mut(path)
}
pub fn find_file_mut(
&mut self,
path: &VirtualPath,
) -> Result<&mut FileMeta<SyncInfo>, InvalidPathError> {
self.root.find_file_mut(path)
}
pub fn find_node_mut(&mut self, path: &VirtualPath) -> Option<&mut VfsNode<SyncInfo>> {
self.root.find_node_mut(path)
}
pub fn find_conflict(&self, path: &VirtualPath) -> Option<&VfsDiff> {
let node = self.find_node(path)?;
match node.state() {
NodeState::Conflict(update) => Some(update),
_ => None,
}
}
}
impl<SyncInfo: Clone> Vfs<SyncInfo> {
pub fn apply_update(
&mut self,
update: &VfsUpdate<SyncInfo>,
loaded_vfs: &Vfs<SyncInfo>,
) -> Result<(), VfsUpdateApplicationError> {
let path = update.path().to_owned();
let parent = self
.root_mut()
.find_dir_mut(path.parent().ok_or(VfsUpdateApplicationError::PathIsRoot)?)?;
parent.force_resync();
parent.remove_child_if(update.path().name(), |child| {
child.state().is_err() && update.is_creation()
});
match update {
VfsUpdate::DirCreated(update) => {
let child = VfsNode::Dir(update.clone().into());
if path.name() != child.name() {
return Err(NameMismatchError {
expected: path.name().to_string(),
found: child.name().to_string(),
}
.into());
}
if parent.insert_child(child) {
Ok(())
} else {
Err(VfsUpdateApplicationError::DirExists(path.to_owned()))
}
}
VfsUpdate::DirRemoved(path) => self
.root_mut()
.as_dir_mut()?
.delete_dir(path)
.map_err(|e| e.into()),
VfsUpdate::FileCreated(update) => {
let child = VfsNode::File(FileMeta::new(
path.name(),
update.file_size(),
update.sync_info().clone(),
));
if parent.insert_child(child) {
Ok(())
} else {
Err(VfsUpdateApplicationError::FileExists(path.to_owned()))
}
}
VfsUpdate::FileModified(update) => {
let file = self.root_mut().find_file_mut(update.path())?;
file.set_size(update.file_size());
let state = file.state_mut();
*state = NodeState::Ok(update.sync_info().clone());
Ok(())
}
VfsUpdate::FileRemoved(path) => self
.root_mut()
.as_dir_mut()?
.delete_file(path)
.map_err(|e| e.into()),
VfsUpdate::FailedApplication(failure) => {
let mut node = loaded_vfs
.find_node(failure.path())
.or_else(|| self.find_node(failure.path()))
.ok_or_else(|| {
VfsUpdateApplicationError::InvalidPath(InvalidPathError::NotFound(
failure.path().to_owned(),
))
})?
.clone();
let state = NodeState::Error(failure.clone());
node.set_state(state);
let parent = self
.root_mut()
.find_dir_mut(path.parent().unwrap())
.unwrap();
parent.replace_child(node);
Ok(())
}
VfsUpdate::Conflict(update) => {
let mut node = loaded_vfs
.find_node(update.path())
.or_else(|| self.find_node(update.path()))
.ok_or_else(|| {
VfsUpdateApplicationError::InvalidPath(InvalidPathError::NotFound(
update.path().to_owned(),
))
})?
.clone();
let state = NodeState::Conflict(update.clone());
node.set_state(state);
let parent = self
.root_mut()
.find_dir_mut(path.parent().unwrap())
.unwrap();
parent.replace_child(node);
Ok(())
}
}
}
pub fn update_node_state(
&mut self,
path: &VirtualPath,
state: NodeState<SyncInfo>,
) -> Result<(), VfsUpdateApplicationError> {
let parent = self
.root_mut()
.find_dir_mut(path.parent().ok_or(VfsUpdateApplicationError::PathIsRoot)?)?;
parent.force_resync();
let node = self.root_mut().find_node_mut(path).ok_or_else(|| {
VfsUpdateApplicationError::InvalidPath(InvalidPathError::NotFound(path.to_owned()))
})?;
node.set_state(state);
Ok(())
}
}
impl<SyncInfo: IsModified + Clone> Vfs<SyncInfo> {
pub fn diff(&self, other: &Vfs<SyncInfo>) -> Result<VfsDiffList, DiffError> {
self.root.diff(other.root(), VirtualPath::root())
}
}
impl<SyncInfo> Vfs<SyncInfo> {
pub fn new(root: VfsNode<SyncInfo>) -> Self {
Self { root }
}
pub fn empty() -> Self {
Self {
root: VfsNode::Dir(DirTree::new_force_resync("")),
}
}
pub fn get_errors(&self) -> Vec<(VirtualPathBuf, FailedUpdateApplication)> {
self.root().get_errors_list(VirtualPath::root())
}
pub fn get_conflicts(&self) -> Vec<VirtualPathBuf> {
self.root().get_conflicts_list(VirtualPath::root())
}
}
impl<SyncInfo> From<&Vfs<SyncInfo>> for Vfs<()> {
fn from(value: &Vfs<SyncInfo>) -> Self {
Self {
root: (&value.root).into(),
}
}
}
#[cfg(test)]
mod test {
use crate::{
test_utils::ShallowTestSyncInfo,
update::{VfsDirCreation, VfsFileUpdate},
};
use super::*;
use crate::test_utils::TestNode::{D, F};
#[test]
fn test_apply_update() {
let base = D(
"",
vec![
D("Doc", vec![F("f1.md"), F("f2.pdf")]),
D("a", vec![D("b", vec![D("c", vec![])])]),
D("e", vec![D("g", vec![F("tmp.txt")])]),
],
)
.into_node();
let mut vfs = Vfs::new(base.clone());
let updated = D(
"",
vec![
D("Doc", vec![F("f1.md"), F("f2.pdf")]),
D("a", vec![D("b", vec![D("c", vec![])])]),
D(
"e",
vec![D(
"g",
vec![F("tmp.txt"), D("h", vec![F("file.bin"), D("i", vec![])])],
)],
),
],
)
.into_node();
let new_dir = D("h", vec![F("file.bin"), D("i", vec![])]).into_dir();
let update = VfsUpdate::DirCreated(VfsDirCreation::new(
&VirtualPathBuf::new("/e/g").unwrap(),
new_dir,
));
let ref_vfs = Vfs::new(updated);
vfs.apply_update(&update, &ref_vfs).unwrap();
assert!(vfs.diff(&ref_vfs).unwrap().is_empty());
let mut vfs = Vfs::new(base.clone());
let updated = D(
"",
vec![
D("Doc", vec![F("f1.md"), F("f2.pdf")]),
D("a", vec![D("b", vec![D("c", vec![])])]),
D("e", vec![]),
],
)
.into_node();
let update = VfsUpdate::DirRemoved(VirtualPathBuf::new("/e/g").unwrap());
let ref_vfs = Vfs::new(updated);
vfs.apply_update(&update, &ref_vfs).unwrap();
assert!(vfs.diff(&ref_vfs).unwrap().is_empty());
let mut vfs = Vfs::new(base.clone());
let updated = D(
"",
vec![
D("Doc", vec![F("f1.md"), F("f2.pdf")]),
D("a", vec![D("b", vec![D("c", vec![])])]),
D("e", vec![D("g", vec![F("tmp.txt"), F("file.bin")])]),
],
)
.into_node();
let new_file_info = ShallowTestSyncInfo::new(0);
let update = VfsUpdate::FileCreated(VfsFileUpdate::new(
&VirtualPathBuf::new("/e/g/file.bin").unwrap(),
0,
new_file_info,
));
let ref_vfs = Vfs::new(updated);
vfs.apply_update(&update, &ref_vfs).unwrap();
assert!(vfs.diff(&ref_vfs).unwrap().is_empty());
let mut vfs = Vfs::new(base.clone());
let updated = D(
"",
vec![
D("Doc", vec![F("f1.md"), F("f2.pdf")]),
D("a", vec![D("b", vec![D("c", vec![])])]),
D("e", vec![D("g", vec![F("tmp.txt")])]),
],
)
.into_node();
let new_file_info = ShallowTestSyncInfo::new(0);
let update = VfsUpdate::FileModified(VfsFileUpdate::new(
&VirtualPathBuf::new("/Doc/f1.md").unwrap(),
0,
new_file_info,
));
let ref_vfs = Vfs::new(updated);
vfs.apply_update(&update, &ref_vfs).unwrap();
assert!(vfs.diff(&ref_vfs).unwrap().is_empty());
let mut vfs = Vfs::new(base.clone());
let updated = D(
"",
vec![
D("Doc", vec![F("f1.md")]),
D("a", vec![D("b", vec![D("c", vec![])])]),
D("e", vec![D("g", vec![F("tmp.txt")])]),
],
)
.into_node();
let update = VfsUpdate::FileRemoved(VirtualPathBuf::new("/Doc/f2.pdf").unwrap());
let ref_vfs = Vfs::new(updated);
vfs.apply_update(&update, &ref_vfs).unwrap();
assert!(vfs.diff(&ref_vfs).unwrap().is_empty());
}
#[test]
fn test_apply_update_root() {
let base = D(
"",
vec![
D("Doc", vec![F("f1.md"), F("f2.pdf")]),
D("a", vec![D("b", vec![D("c", vec![])])]),
D("e", vec![D("g", vec![F("tmp.txt")])]),
],
)
.into_node();
let mut vfs = Vfs::new(base.clone());
let updated = D(
"",
vec![
D("Doc", vec![F("f1.md"), F("f2.pdf")]),
D("a", vec![D("b", vec![D("c", vec![])])]),
D("e", vec![D("g", vec![F("tmp.txt")])]),
D("h", vec![F("file.bin"), D("i", vec![])]),
],
)
.into_node();
let new_dir = D("h", vec![F("file.bin"), D("i", vec![])]).into_dir();
let update = VfsUpdate::DirCreated(VfsDirCreation::new(
&VirtualPathBuf::new("/").unwrap(),
new_dir,
));
let ref_vfs = Vfs::new(updated);
vfs.apply_update(&update, &ref_vfs).unwrap();
assert!(vfs.diff(&ref_vfs).unwrap().is_empty());
let mut vfs = Vfs::new(base.clone());
let updated = D(
"",
vec![
D("Doc", vec![F("f1.md"), F("f2.pdf")]),
D("a", vec![D("b", vec![D("c", vec![])])]),
],
)
.into_node();
let update = VfsUpdate::DirRemoved(VirtualPathBuf::new("/e").unwrap());
let ref_vfs = Vfs::new(updated);
vfs.apply_update(&update, &ref_vfs).unwrap();
assert!(vfs.diff(&ref_vfs).unwrap().is_empty());
let mut vfs = Vfs::new(base.clone());
let updated = D(
"",
vec![
D("Doc", vec![F("f1.md"), F("f2.pdf")]),
D("a", vec![D("b", vec![D("c", vec![])])]),
D("e", vec![D("g", vec![F("tmp.txt")])]),
F("file.bin"),
],
)
.into_node();
let new_file_info = ShallowTestSyncInfo::new(0);
let update = VfsUpdate::FileCreated(VfsFileUpdate::new(
&VirtualPathBuf::new("/file.bin").unwrap(),
0,
new_file_info,
));
let ref_vfs = Vfs::new(updated.clone());
vfs.apply_update(&update, &ref_vfs).unwrap();
assert!(vfs.diff(&ref_vfs).unwrap().is_empty());
let mut vfs = ref_vfs;
let new_file_info = ShallowTestSyncInfo::new(0);
let update = VfsUpdate::FileModified(VfsFileUpdate::new(
&VirtualPathBuf::new("/file.bin").unwrap(),
0,
new_file_info,
));
let ref_vfs = Vfs::new(updated.clone());
vfs.apply_update(&update, &ref_vfs).unwrap();
assert!(vfs.diff(&ref_vfs).unwrap().is_empty());
let mut vfs = ref_vfs;
let updated = D(
"",
vec![
D("Doc", vec![F("f1.md"), F("f2.pdf")]),
D("a", vec![D("b", vec![D("c", vec![])])]),
D("e", vec![D("g", vec![F("tmp.txt")])]),
],
)
.into_node();
let ref_vfs = Vfs::new(updated);
let update = VfsUpdate::FileRemoved(VirtualPathBuf::new("/file.bin").unwrap());
vfs.apply_update(&update, &ref_vfs).unwrap();
assert!(vfs.diff(&ref_vfs).unwrap().is_empty());
}
#[test]
fn test_invalid_update() {
let base = D(
"",
vec![
D("Doc", vec![F("f1.md"), F("f2.pdf")]),
D("a", vec![D("b", vec![D("c", vec![])])]),
D("e", vec![D("g", vec![F("tmp.txt")])]),
],
)
.into_node();
let mut vfs = Vfs::new(base.clone());
let ref_vfs = Vfs::new(base.clone());
let update = VfsUpdate::FileRemoved(VirtualPathBuf::new("/e/f/h").unwrap());
assert!(vfs.apply_update(&update, &ref_vfs).is_err());
let mut vfs = Vfs::new(base.clone());
let new_file_info = ShallowTestSyncInfo::new(0);
let update = VfsUpdate::FileCreated(VfsFileUpdate::new(
&VirtualPathBuf::new("/e/g/tmp.txt").unwrap(),
0,
new_file_info,
));
assert!(vfs.apply_update(&update, &ref_vfs).is_err());
let mut vfs = Vfs::new(base.clone());
let update = VfsUpdate::FileRemoved(VirtualPathBuf::new("/Doc/f3.doc").unwrap());
assert!(vfs.apply_update(&update, &ref_vfs).is_err());
}
}