use std::collections::HashSet;
use futures::future::try_join_all;
use serde::{Deserialize, Serialize};
use crate::{
Error, NameMismatchError,
concrete::{ConcreteFS, ConcreteFileCloneResult, FSBackend, FsBackendError, Named},
sorted_vec::{Sortable, SortedVec},
vfs::{
DeleteNodeError, DirTree, InvalidPathError, NodeState, Vfs, VirtualPath, VirtualPathBuf,
},
};
#[derive(Error, Debug)]
pub enum DiffError {
#[error("the sync info for the path {0:?} are not valid")]
InvalidSyncInfo(VirtualPathBuf),
#[error("the path in 'local' and 'remote' FS do not point to the same node and can't be diff")]
NodeMismatch(#[from] NameMismatchError),
}
#[derive(Error, Debug)]
pub enum VfsUpdateApplicationError {
#[error("the path provided in the update is invalid")]
InvalidPath(#[from] InvalidPathError),
#[error("the name of the created dir does not match the path of the update")]
NameMismatch(#[from] NameMismatchError),
#[error("the file at {0:?} already exists")]
FileExists(VirtualPathBuf),
#[error("the dir at {0:?} already exists")]
DirExists(VirtualPathBuf),
#[error("cannot apply an update to the root dir itself")]
PathIsRoot,
#[error("failed to delete a node")]
DeleteError(#[from] DeleteNodeError),
}
#[derive(Error, Debug)]
pub enum ReconciliationError {
#[error("error from {fs_name} during reconciliation")]
FsBackendError {
fs_name: String,
source: FsBackendError,
},
#[error(
"the nodes in 'local' and 'remote' FS do not point to the same node and can't be reconciled"
)]
NodeMismatch(#[from] NameMismatchError),
#[error("invalid path on {fs_name} provided for reconciliation")]
InvalidPath {
fs_name: String,
source: InvalidPathError,
},
#[error("failed to diff vfs nodes")]
DiffError(#[from] DiffError),
}
impl ReconciliationError {
pub fn concrete<E: Into<FsBackendError>>(fs_name: &str, source: E) -> Self {
Self::FsBackendError {
fs_name: fs_name.to_string(),
source: source.into(),
}
}
pub fn invalid_path<E: Into<InvalidPathError>>(fs_name: &str, source: E) -> Self {
Self::InvalidPath {
fs_name: fs_name.to_string(),
source: source.into(),
}
}
}
#[derive(Debug)]
pub enum ModificationState {
ShallowUnmodified,
RecursiveUnmodified,
Modified,
}
pub trait IsModified {
fn modification_state(&self, reference: &Self) -> ModificationState;
fn is_modified(&self, reference: &Self) -> bool {
match self.modification_state(reference) {
ModificationState::ShallowUnmodified => false,
ModificationState::RecursiveUnmodified => false,
ModificationState::Modified => true,
}
}
}
impl<SyncInfo: IsModified> IsModified for NodeState<SyncInfo> {
fn modification_state(&self, reference: &Self) -> ModificationState {
match (self, reference) {
(NodeState::Ok(self_sync), NodeState::Ok(other_sync)) => {
self_sync.modification_state(other_sync)
}
(NodeState::Conflict(_), _) | (_, NodeState::Conflict(_)) => {
ModificationState::ShallowUnmodified
}
_ => ModificationState::Modified,
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum UpdateKind {
DirCreated,
DirRemoved,
FileCreated,
FileRemoved,
FileModified,
}
impl UpdateKind {
pub fn is_removal(self) -> bool {
matches!(self, Self::DirRemoved | Self::FileRemoved)
}
pub fn is_creation(self) -> bool {
matches!(self, Self::DirCreated | Self::FileCreated)
}
pub fn inverse(self) -> Self {
match self {
UpdateKind::DirCreated => UpdateKind::DirRemoved,
UpdateKind::DirRemoved => UpdateKind::DirCreated,
UpdateKind::FileCreated => UpdateKind::FileRemoved,
UpdateKind::FileModified => UpdateKind::FileModified,
UpdateKind::FileRemoved => UpdateKind::FileCreated,
}
}
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub enum UpdateTarget {
Local,
Remote,
Both,
}
impl UpdateTarget {
pub fn invert(self) -> Self {
match self {
UpdateTarget::Local => UpdateTarget::Remote,
UpdateTarget::Remote => UpdateTarget::Local,
UpdateTarget::Both => UpdateTarget::Both,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct VfsDiff {
path: VirtualPathBuf, kind: UpdateKind,
}
impl VfsDiff {
pub fn dir_created(path: VirtualPathBuf) -> Self {
Self {
path,
kind: UpdateKind::DirCreated,
}
}
pub fn dir_removed(path: VirtualPathBuf) -> Self {
Self {
path,
kind: UpdateKind::DirRemoved,
}
}
pub fn file_created(path: VirtualPathBuf) -> Self {
Self {
path,
kind: UpdateKind::FileCreated,
}
}
pub fn file_modified(path: VirtualPathBuf) -> Self {
Self {
path,
kind: UpdateKind::FileModified,
}
}
pub fn file_removed(path: VirtualPathBuf) -> Self {
Self {
path,
kind: UpdateKind::FileRemoved,
}
}
pub fn path(&self) -> &VirtualPath {
&self.path
}
pub fn kind(&self) -> UpdateKind {
self.kind
}
fn reconcile<LocalSyncInfo: Named, RemoteSyncInfo: Named>(
&self,
remote_update: &VfsDiff,
vfs_local: &Vfs<LocalSyncInfo>,
vfs_remote: &Vfs<RemoteSyncInfo>,
) -> Result<SortedVec<VirtualReconciledUpdate>, ReconciliationError> {
if self.path() != remote_update.path() {
return Err(NameMismatchError {
found: self.path().name().to_string(),
expected: remote_update.path().name().to_string(),
}
.into());
}
let mut reconciled = SortedVec::new();
match (self.kind, remote_update.kind) {
(UpdateKind::DirCreated, UpdateKind::DirCreated) => {
let dir_local = vfs_local
.find_dir(&self.path)
.map_err(|e| ReconciliationError::invalid_path(LocalSyncInfo::TYPE_NAME, e))?;
let dir_remote = vfs_remote
.find_dir(&remote_update.path)
.map_err(|e| ReconciliationError::invalid_path(RemoteSyncInfo::TYPE_NAME, e))?;
let reconciled = dir_local.reconciliation_diff(
dir_remote,
self.path().parent().unwrap_or(VirtualPath::root()),
)?;
Ok(reconciled)
}
(UpdateKind::DirRemoved, UpdateKind::DirRemoved) => Ok(reconciled),
(UpdateKind::FileModified, UpdateKind::FileModified)
| (UpdateKind::FileCreated, UpdateKind::FileCreated) => {
let file_local = vfs_local
.find_file(&self.path)
.map_err(|e| ReconciliationError::invalid_path(RemoteSyncInfo::TYPE_NAME, e))?;
let file_remote = vfs_remote
.find_file(&remote_update.path)
.map_err(|e| ReconciliationError::invalid_path(RemoteSyncInfo::TYPE_NAME, e))?;
let update = if file_local.size() == file_remote.size() {
VirtualReconciledUpdate::backend_check_both(self)
} else {
VirtualReconciledUpdate::conflict_both(self)
};
reconciled.insert(update);
Ok(reconciled)
}
(UpdateKind::FileRemoved, UpdateKind::FileRemoved) => Ok(reconciled),
(_, _) => {
reconciled.insert(VirtualReconciledUpdate::conflict_local(self));
reconciled.insert(VirtualReconciledUpdate::conflict_remote(remote_update));
Ok(reconciled)
}
}
}
pub fn is_removal(&self) -> bool {
self.kind.is_removal()
}
pub fn is_creation(&self) -> bool {
self.kind.is_creation()
}
pub fn invert(self) -> Self {
let kind = self.kind.inverse();
Self {
path: self.path,
kind,
}
}
}
impl Sortable for VfsDiff {
type Key = VirtualPath;
fn key(&self) -> &Self::Key {
self.path()
}
}
pub type VfsDiffList = SortedVec<VfsDiff>;
impl VfsDiffList {
pub(crate) fn merge<SyncInfo: Named, RemoteSyncInfo: Named>(
&self,
remote_updates: VfsDiffList,
local_vfs: &Vfs<SyncInfo>,
remote_vfs: &Vfs<RemoteSyncInfo>,
) -> Result<SortedVec<VirtualReconciledUpdate>, ReconciliationError> {
let res = self.iter_zip_map(
&remote_updates,
|local_item| {
Ok(SortedVec::from_vec(vec![
VirtualReconciledUpdate::applicable_remote(local_item),
]))
},
|local_item, remote_item| local_item.reconcile(remote_item, local_vfs, remote_vfs),
|remote_item| {
Ok(SortedVec::from_vec(vec![
VirtualReconciledUpdate::applicable_local(remote_item),
]))
},
)?;
Ok(SortedVec::unchecked_flatten(res))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct ApplicableUpdate {
update: VfsDiff, target: UpdateTarget,
}
impl ApplicableUpdate {
pub fn new(target: UpdateTarget, update: &VfsDiff) -> Self {
Self {
target,
update: update.clone(),
}
}
pub fn target(&self) -> UpdateTarget {
self.target
}
pub fn update(&self) -> &VfsDiff {
&self.update
}
pub fn path(&self) -> &VirtualPath {
self.update.path()
}
pub fn is_removal(&self) -> bool {
self.update.is_removal()
}
pub fn invert_target(&self) -> Self {
Self {
update: self.update.clone(),
target: self.target.invert(),
}
}
pub fn invert(self) -> Self {
Self {
update: self.update.invert(),
target: self.target.invert(),
}
}
}
impl From<ApplicableUpdate> for VfsDiff {
fn from(value: ApplicableUpdate) -> Self {
value.update
}
}
impl Sortable for ApplicableUpdate {
type Key = Self;
fn key(&self) -> &Self::Key {
self
}
}
impl SortedVec<ApplicableUpdate> {
pub fn split_local_remote(self) -> (Vec<VfsDiff>, Vec<VfsDiff>) {
let mut local = Vec::new();
let mut remote = Vec::new();
for update in self.into_iter() {
match update.target() {
UpdateTarget::Local => local.push(update.update),
UpdateTarget::Remote => remote.push(update.update),
UpdateTarget::Both => {
local.push(update.update.clone());
remote.push(update.update)
}
}
}
(local, remote)
}
pub fn remove_duplicates(self) -> Self {
let mut seen = HashSet::new();
let mut result = Vec::new();
for update in self.into_iter() {
if seen.insert((update.path().to_owned(), update.target())) {
result.push(update);
}
}
SortedVec::unchecked_from_vec(result)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum VirtualReconciledUpdate {
Applicable(ApplicableUpdate),
NeedBackendCheck(ApplicableUpdate),
Conflict(ApplicableUpdate),
}
impl VirtualReconciledUpdate {
fn update(&self) -> &ApplicableUpdate {
match self {
VirtualReconciledUpdate::Applicable(update) => update,
VirtualReconciledUpdate::NeedBackendCheck(update) => update,
VirtualReconciledUpdate::Conflict(update) => update,
}
}
pub(crate) fn applicable_local(update: &VfsDiff) -> Self {
Self::Applicable(ApplicableUpdate::new(UpdateTarget::Local, update))
}
pub(crate) fn applicable_remote(update: &VfsDiff) -> Self {
Self::Applicable(ApplicableUpdate::new(UpdateTarget::Remote, update))
}
pub(crate) fn conflict_local(update: &VfsDiff) -> Self {
Self::Conflict(ApplicableUpdate::new(UpdateTarget::Local, update))
}
pub(crate) fn conflict_remote(update: &VfsDiff) -> Self {
Self::Conflict(ApplicableUpdate::new(UpdateTarget::Remote, update))
}
pub(crate) fn conflict_both(update: &VfsDiff) -> Self {
Self::Conflict(ApplicableUpdate::new(UpdateTarget::Both, update))
}
pub(crate) fn backend_check_both(update: &VfsDiff) -> Self {
Self::NeedBackendCheck(ApplicableUpdate::new(UpdateTarget::Both, update))
}
async fn resolve_concrete<LocalBackend: FSBackend, RemoteBackend: FSBackend>(
self,
local_concrete: &ConcreteFS<LocalBackend>,
remote_concrete: &ConcreteFS<RemoteBackend>,
) -> Result<Option<ReconciledUpdate>, ReconciliationError> {
match self {
VirtualReconciledUpdate::Applicable(update) => {
Ok(Some(ReconciledUpdate::Applicable(update)))
}
VirtualReconciledUpdate::NeedBackendCheck(update) => {
if local_concrete
.eq_file(remote_concrete, update.path())
.await
.map_err(|(e, name)| ReconciliationError::concrete(name, e))?
{
Ok(None)
} else {
Ok(Some(ReconciledUpdate::Conflict(update)))
}
}
VirtualReconciledUpdate::Conflict(conflict) => {
Ok(Some(ReconciledUpdate::Conflict(conflict)))
}
}
}
}
impl Sortable for VirtualReconciledUpdate {
type Key = ApplicableUpdate;
fn key(&self) -> &Self::Key {
self.update()
}
}
impl SortedVec<VirtualReconciledUpdate> {
pub(crate) async fn resolve_concrete<Backend: FSBackend, OtherBackend: FSBackend>(
self,
concrete_self: &ConcreteFS<Backend>,
concrete_other: &ConcreteFS<OtherBackend>,
) -> Result<SortedVec<ReconciledUpdate>, ReconciliationError> {
let updates = try_join_all(
self.into_iter()
.map(|update| update.resolve_concrete(concrete_self, concrete_other)),
)
.await?;
let updates = updates.into_iter().flatten().collect();
Ok(SortedVec::unchecked_from_vec(updates))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ReconciledUpdate {
Applicable(ApplicableUpdate),
Conflict(ApplicableUpdate),
}
impl ReconciledUpdate {
pub fn path(&self) -> &VirtualPath {
match self {
ReconciledUpdate::Applicable(update) => update.path(),
ReconciledUpdate::Conflict(update) => update.path(),
}
}
pub fn update(&self) -> &ApplicableUpdate {
match self {
ReconciledUpdate::Applicable(update) => update,
ReconciledUpdate::Conflict(update) => update,
}
}
pub fn applicable_local(update: &VfsDiff) -> Self {
Self::Applicable(ApplicableUpdate::new(UpdateTarget::Local, update))
}
pub fn applicable_remote(update: &VfsDiff) -> Self {
Self::Applicable(ApplicableUpdate::new(UpdateTarget::Remote, update))
}
pub fn conflict_local(update: &VfsDiff) -> Self {
Self::Conflict(ApplicableUpdate::new(UpdateTarget::Local, update))
}
pub fn conflict_remote(update: &VfsDiff) -> Self {
Self::Conflict(ApplicableUpdate::new(UpdateTarget::Remote, update))
}
pub fn conflict_both(update: &VfsDiff) -> Self {
Self::Conflict(ApplicableUpdate::new(UpdateTarget::Both, update))
}
}
impl Sortable for ReconciledUpdate {
type Key = ApplicableUpdate;
fn key(&self) -> &Self::Key {
self.update()
}
}
impl SortedVec<ReconciledUpdate> {
pub(crate) fn resolve_ancestor_conflicts(self) -> Self {
let mut resolved = Vec::new();
let mut iter = self.into_iter().peekable();
while let Some(mut parent_update) = iter.next() {
match &parent_update {
ReconciledUpdate::Applicable(update) if update.is_removal() => {
let mut conflict = false;
let mut updates = Vec::new();
while let Some(mut next_update) = iter
.next_if(|next_update| next_update.path().is_inside(parent_update.path()))
{
conflict = true;
next_update =
ReconciledUpdate::Conflict(next_update.update().invert_target());
updates.push(next_update);
}
if conflict {
parent_update =
ReconciledUpdate::Conflict(parent_update.update().invert_target())
}
resolved.push(parent_update);
resolved.extend(updates);
}
_ =>
{
resolved.push(parent_update);
continue;
}
}
}
SortedVec::unchecked_from_vec(resolved)
}
}
#[derive(Clone, Debug)]
pub struct AppliedDirCreation<SrcSyncInfo, DstSyncInfo> {
path: VirtualPathBuf,
src_dir: DirTree<SrcSyncInfo>,
dst_dir: DirTree<DstSyncInfo>,
}
impl<SrcSyncInfo, DstSyncInfo> AppliedDirCreation<SrcSyncInfo, DstSyncInfo> {
pub fn new(
parent_path: &VirtualPath,
src_dir: DirTree<SrcSyncInfo>,
dst_dir: DirTree<DstSyncInfo>,
) -> Self {
let name = src_dir.name();
let mut path = parent_path.to_owned();
path.push(name);
Self {
path,
src_dir,
dst_dir,
}
}
}
impl<SrcSyncInfo, DstSyncInfo> From<AppliedDirCreation<SrcSyncInfo, DstSyncInfo>>
for (VfsDirCreation<SrcSyncInfo>, VfsDirCreation<DstSyncInfo>)
{
fn from(value: AppliedDirCreation<SrcSyncInfo, DstSyncInfo>) -> Self {
let src_update = VfsDirCreation {
path: value.path.clone(),
dir: value.src_dir,
};
let dst_update = VfsDirCreation {
path: value.path,
dir: value.dst_dir,
};
(src_update, dst_update)
}
}
#[derive(Clone, Debug)]
pub struct AppliedFileUpdate<SrcSyncInfo, DstSyncInfo> {
path: VirtualPathBuf,
src_file_info: SrcSyncInfo,
dst_file_info: DstSyncInfo,
file_size: u64,
}
impl<SrcSyncInfo, DstSyncInfo> AppliedFileUpdate<SrcSyncInfo, DstSyncInfo> {
pub fn new(
path: &VirtualPath,
file_size: u64,
src_file_info: SrcSyncInfo,
dst_file_info: DstSyncInfo,
) -> Self {
Self {
path: path.to_owned(),
file_size,
src_file_info,
dst_file_info,
}
}
pub fn from_clone_result(
path: &VirtualPath,
clone_result: ConcreteFileCloneResult<SrcSyncInfo, DstSyncInfo>,
) -> Self {
let file_size = clone_result.file_size();
let (src_file_info, dst_file_info) = clone_result.into();
Self::new(path, file_size, src_file_info, dst_file_info)
}
}
impl<SrcSyncInfo, DstSyncInfo> From<AppliedFileUpdate<SrcSyncInfo, DstSyncInfo>>
for (VfsFileUpdate<SrcSyncInfo>, VfsFileUpdate<DstSyncInfo>)
{
fn from(value: AppliedFileUpdate<SrcSyncInfo, DstSyncInfo>) -> Self {
let src_update = VfsFileUpdate {
path: value.path.clone(),
file_info: value.src_file_info,
file_size: value.file_size,
};
let dst_update = VfsFileUpdate {
path: value.path,
file_info: value.dst_file_info,
file_size: value.file_size,
};
(src_update, dst_update)
}
}
#[derive(Clone, Debug)]
pub enum AppliedUpdate<SrcSyncInfo, DstSyncInfo> {
DirCreated(AppliedDirCreation<SrcSyncInfo, DstSyncInfo>),
DirRemoved(VirtualPathBuf),
DirRemovedSkipped(VirtualPathBuf),
FileCreated(AppliedFileUpdate<SrcSyncInfo, DstSyncInfo>),
FileModified(AppliedFileUpdate<SrcSyncInfo, DstSyncInfo>),
FileRemoved(VirtualPathBuf),
FileRemovedSkipped(VirtualPathBuf),
FailedApplication(FailedUpdateApplication),
}
impl<SrcSyncInfo, DstSyncInfo> From<AppliedUpdate<SrcSyncInfo, DstSyncInfo>>
for (VfsUpdate<SrcSyncInfo>, Option<VfsUpdate<DstSyncInfo>>)
{
fn from(value: AppliedUpdate<SrcSyncInfo, DstSyncInfo>) -> Self {
match value {
AppliedUpdate::DirCreated(dir_creation) => {
let (src_update, dst_update) = dir_creation.into();
(
VfsUpdate::DirCreated(src_update),
Some(VfsUpdate::DirCreated(dst_update)),
)
}
AppliedUpdate::DirRemoved(path) => (
VfsUpdate::DirRemoved(path.clone()),
Some(VfsUpdate::DirRemoved(path)),
),
AppliedUpdate::DirRemovedSkipped(path) => (VfsUpdate::DirRemoved(path.clone()), None),
AppliedUpdate::FileCreated(file_update) => {
let (src_update, dst_update) = file_update.into();
(
VfsUpdate::FileCreated(src_update),
Some(VfsUpdate::FileCreated(dst_update)),
)
}
AppliedUpdate::FileModified(file_update) => {
let (src_update, dst_update) = file_update.into();
(
VfsUpdate::FileModified(src_update),
Some(VfsUpdate::FileModified(dst_update)),
)
}
AppliedUpdate::FileRemoved(path) => (
VfsUpdate::FileRemoved(path.clone()),
Some(VfsUpdate::FileRemoved(path)),
),
AppliedUpdate::FileRemovedSkipped(path) => (VfsUpdate::FileRemoved(path.clone()), None),
AppliedUpdate::FailedApplication(failed_update) => {
(VfsUpdate::FailedApplication(failed_update.clone()), None)
}
}
}
}
impl<SrcSyncInfo, DstSyncInfo> AppliedUpdate<SrcSyncInfo, DstSyncInfo> {
pub fn path(&self) -> &VirtualPath {
match self {
AppliedUpdate::DirCreated(dir_creation) => &dir_creation.path,
AppliedUpdate::DirRemoved(path) => path,
AppliedUpdate::DirRemovedSkipped(path) => path,
AppliedUpdate::FileCreated(file_update) => &file_update.path,
AppliedUpdate::FileModified(file_update) => &file_update.path,
AppliedUpdate::FileRemoved(path) => path,
AppliedUpdate::FileRemovedSkipped(path) => path,
AppliedUpdate::FailedApplication(failed_update) => failed_update.path(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FailedUpdateApplication {
error: String,
update: VfsDiff,
}
impl FailedUpdateApplication {
pub fn new(update: VfsDiff, error: FsBackendError) -> Self {
Self {
update,
error: format!("{error}"),
}
}
pub fn path(&self) -> &VirtualPath {
self.update.path()
}
pub fn update(&self) -> &VfsDiff {
&self.update
}
pub fn error(&self) -> &str {
&self.error
}
}
#[derive(Clone, Debug)]
pub struct VfsDirCreation<SyncInfo> {
path: VirtualPathBuf,
dir: DirTree<SyncInfo>,
}
impl<SyncInfo> VfsDirCreation<SyncInfo> {
pub fn new(parent_path: &VirtualPath, dir: DirTree<SyncInfo>) -> Self {
let name = dir.name();
let mut path = parent_path.to_owned();
path.push(name);
Self {
path: path.to_owned(),
dir,
}
}
pub fn path(&self) -> &VirtualPath {
&self.path
}
pub fn dir(&self) -> &DirTree<SyncInfo> {
&self.dir
}
}
impl<SyncInfo> From<VfsDirCreation<SyncInfo>> for DirTree<SyncInfo> {
fn from(value: VfsDirCreation<SyncInfo>) -> Self {
value.dir
}
}
#[derive(Clone, Debug)]
pub struct VfsFileUpdate<SyncInfo> {
path: VirtualPathBuf,
file_info: SyncInfo,
file_size: u64,
}
impl<SyncInfo> VfsFileUpdate<SyncInfo> {
pub fn new(path: &VirtualPath, file_size: u64, file_info: SyncInfo) -> Self {
Self {
path: path.to_owned(),
file_info,
file_size,
}
}
pub fn path(&self) -> &VirtualPath {
&self.path
}
pub fn file_size(&self) -> u64 {
self.file_size
}
pub fn sync_info(&self) -> &SyncInfo {
&self.file_info
}
}
#[derive(Debug)]
pub enum VfsUpdate<SyncInfo> {
DirCreated(VfsDirCreation<SyncInfo>),
DirRemoved(VirtualPathBuf),
FileCreated(VfsFileUpdate<SyncInfo>),
FileModified(VfsFileUpdate<SyncInfo>),
FileRemoved(VirtualPathBuf),
FailedApplication(FailedUpdateApplication),
Conflict(VfsDiff),
}
impl<SyncInfo> VfsUpdate<SyncInfo> {
pub fn path(&self) -> &VirtualPath {
match self {
Self::DirCreated(update) => update.path(),
Self::FileCreated(update) | Self::FileModified(update) => update.path(),
Self::DirRemoved(path) | Self::FileRemoved(path) => path,
Self::FailedApplication(failed_update) => failed_update.path(),
Self::Conflict(update) => update.path(),
}
}
pub fn is_err(&self) -> bool {
matches!(self, Self::FailedApplication(_))
}
pub fn is_removal(&self) -> bool {
matches!(self, Self::DirRemoved(_) | Self::FileRemoved(_))
}
pub fn is_creation(&self) -> bool {
matches!(self, Self::DirCreated(_) | Self::FileCreated(_))
}
}
impl<SyncInfo> Sortable for VfsUpdate<SyncInfo> {
type Key = VirtualPath;
fn key(&self) -> &Self::Key {
self.path()
}
}