use super::planning_helpers::ReplaceDestination;
use super::planning_helpers::{
publish_binding_is_precondition, publish_child_name_absent_precondition,
publish_reject_tombstoned_path_ancestor, publish_resolve_parent_directory,
publish_resolve_replace_destination, PlannedOperation, PublishPathPlanningView,
};
use crate::commit::{CommitOp as ApiCommitOp, CommitPrecondition as ApiCommitPrecondition};
use crate::error::{CoreError, Result};
use crate::path::helpers::{ensure_mutation_path, final_component};
use loonfs_api::{AbsolutePath, DestinationBehavior, InodeKind};
use loonfs_objectstore::ObjectStore;
pub(super) async fn plan_publish_move_path<S: ObjectStore + ?Sized>(
from_path: &AbsolutePath,
to_path: &AbsolutePath,
behavior: DestinationBehavior,
view: &PublishPathPlanningView<'_, '_, '_, S>,
) -> Result<PlannedOperation> {
ensure_mutation_path(from_path)?;
ensure_mutation_path(to_path)?;
publish_reject_tombstoned_path_ancestor(view, from_path).await?;
publish_reject_tombstoned_path_ancestor(view, to_path).await?;
let source = view.metadata_state.resolve_visible_path(from_path).await?;
let target_parent = publish_resolve_parent_directory(view, to_path).await?;
let target_name = final_component(to_path)?;
let replaced =
publish_resolve_replace_destination(view, to_path, behavior, source.inode_id).await?;
let mut ops = Vec::new();
let mut preconditions = vec![publish_binding_is_precondition(view, &source).await?];
match &replaced {
ReplaceDestination::Replaced(existing) => {
ops.push(ApiCommitOp::DeleteFile {
inode_id: existing.inode_id,
});
preconditions.push(publish_binding_is_precondition(view, existing).await?);
preconditions.push(ApiCommitPrecondition::AncestorsNotSubtreeDeleted {
inode_id: existing.inode_id,
});
}
ReplaceDestination::Vacant => {
preconditions.push(publish_child_name_absent_precondition(
target_parent,
&target_name,
));
}
ReplaceDestination::SameInode => {
if target_name.as_str() == source.display_name {
return Err(CoreError::DestinationExists {
path: to_path.as_str().to_owned(),
existing_display_name: Some(source.display_name.clone()),
});
}
}
}
ops.push(ApiCommitOp::Rename {
inode_id: source.inode_id,
new_parent_inode_id: target_parent,
new_display_name: target_name.clone(),
});
preconditions.push(ApiCommitPrecondition::AncestorsNotSubtreeDeleted {
inode_id: source.inode_id,
});
preconditions.push(ApiCommitPrecondition::AncestorsNotSubtreeDeleted {
inode_id: target_parent,
});
Ok(PlannedOperation::new(ops, preconditions))
}
pub(super) async fn plan_publish_copy_file_path<S: ObjectStore + ?Sized>(
from_path: &AbsolutePath,
to_path: &AbsolutePath,
behavior: DestinationBehavior,
view: &PublishPathPlanningView<'_, '_, '_, S>,
) -> Result<PlannedOperation> {
ensure_mutation_path(from_path)?;
ensure_mutation_path(to_path)?;
publish_reject_tombstoned_path_ancestor(view, from_path).await?;
publish_reject_tombstoned_path_ancestor(view, to_path).await?;
let source = view.metadata_state.resolve_visible_path(from_path).await?;
if source.inode_kind != InodeKind::File {
return Err(CoreError::ExpectedFile {
path: from_path.as_str().to_owned(),
kind: source.inode_kind,
});
}
let replaced =
publish_resolve_replace_destination(view, to_path, behavior, source.inode_id).await?;
let revision = view
.metadata_state
.latest_revision_head(source.inode_id)
.await?
.ok_or_else(|| CoreError::PathNotFound(from_path.as_str().to_owned()))?;
let target_parent = publish_resolve_parent_directory(view, to_path).await?;
let target_name = final_component(to_path)?;
let mut ops = Vec::new();
let mut preconditions = vec![
publish_binding_is_precondition(view, &source).await?,
ApiCommitPrecondition::InodeRevisionIs {
inode_id: source.inode_id,
revision_no: revision.revision_no,
},
ApiCommitPrecondition::AncestorsNotSubtreeDeleted {
inode_id: source.inode_id,
},
];
match &replaced {
ReplaceDestination::SameInode => {
return Err(CoreError::DestinationExists {
path: to_path.as_str().to_owned(),
existing_display_name: Some(source.display_name.clone()),
})
}
ReplaceDestination::Replaced(existing) => {
let existing_revision = view
.metadata_state
.latest_revision_head(existing.inode_id)
.await?
.ok_or_else(|| CoreError::PathNotFound(to_path.as_str().to_owned()))?;
ops.push(ApiCommitOp::ReplaceFile {
inode_id: existing.inode_id,
base_revision_no: existing_revision.revision_no,
content_ref: revision.content_ref,
});
preconditions.push(publish_binding_is_precondition(view, existing).await?);
preconditions.push(ApiCommitPrecondition::InodeRevisionIs {
inode_id: existing.inode_id,
revision_no: existing_revision.revision_no,
});
preconditions.push(ApiCommitPrecondition::AncestorsNotSubtreeDeleted {
inode_id: existing.inode_id,
});
}
ReplaceDestination::Vacant => {
ops.push(ApiCommitOp::CreateFile {
parent_inode_id: target_parent,
display_name: target_name.clone(),
content_ref: revision.content_ref,
});
preconditions.push(publish_child_name_absent_precondition(
target_parent,
&target_name,
));
}
}
preconditions.push(ApiCommitPrecondition::AncestorsNotSubtreeDeleted {
inode_id: target_parent,
});
Ok(PlannedOperation::new(ops, preconditions))
}