use super::planning_helpers::{
is_missing_visible_path, publish_binding_is_precondition,
publish_child_name_absent_precondition, publish_ensure_parent_directories,
publish_reject_tombstoned_path_ancestor, publish_resolve_parent_directory, PlannedOperation,
PublishPathPlanningView,
};
use crate::commit::{
CommitOp as ApiCommitOp, CommitPrecondition as ApiCommitPrecondition, CommitValidationError,
};
use crate::error::{CoreError, Result};
use crate::path::helpers::{ensure_mutation_path, final_component};
use loonfs_api::{
AbsolutePath, ChangeSeq, ContentRef, DestinationBehavior, InodeId, InodeKind, RevisionNo,
};
use loonfs_objectstore::ObjectStore;
pub(super) async fn plan_publish_create_directory<S: ObjectStore + ?Sized>(
absolute_path: &AbsolutePath,
parents: bool,
view: &PublishPathPlanningView<'_, '_, '_, S>,
) -> Result<PlannedOperation> {
ensure_mutation_path(absolute_path)?;
publish_reject_tombstoned_path_ancestor(view, absolute_path).await?;
match view
.metadata_state
.resolve_visible_path(absolute_path)
.await
{
Ok(existing) => {
return Err(CoreError::DestinationExists {
path: absolute_path.as_str().to_owned(),
existing_display_name: Some(existing.display_name),
});
}
Err(error) if is_missing_visible_path(&error) => {}
Err(error) => return Err(error),
}
let mut ops = Vec::new();
let parent_inode_id = if parents {
let mut next_inode_id = view.next_inode_id;
publish_ensure_parent_directories(absolute_path, view, &mut ops, &mut next_inode_id).await?
} else {
publish_resolve_parent_directory(view, absolute_path).await?
};
let display_name = final_component(absolute_path)?;
ops.push(ApiCommitOp::CreateDirectory {
parent_inode_id,
display_name: display_name.clone(),
});
let mut preconditions = Vec::new();
if view
.metadata_state
.visible_inode(parent_inode_id)
.await?
.is_some()
{
preconditions.push(publish_child_name_absent_precondition(
parent_inode_id,
&display_name,
));
preconditions.push(ApiCommitPrecondition::AncestorsNotSubtreeDeleted {
inode_id: parent_inode_id,
});
}
Ok(PlannedOperation::new(ops, preconditions))
}
pub(super) async fn plan_publish_undelete<S: ObjectStore + ?Sized>(
inode_id: InodeId,
deleted_at_seq: ChangeSeq,
absolute_path: Option<&AbsolutePath>,
view: &PublishPathPlanningView<'_, '_, '_, S>,
) -> Result<PlannedOperation> {
let (parent_inode_id, display_name) = match absolute_path {
Some(absolute_path) => {
ensure_mutation_path(absolute_path)?;
publish_reject_tombstoned_path_ancestor(view, absolute_path).await?;
match view
.metadata_state
.resolve_visible_path(absolute_path)
.await
{
Ok(existing) => {
return Err(CoreError::DestinationExists {
path: absolute_path.as_str().to_owned(),
existing_display_name: Some(existing.display_name),
});
}
Err(error) if is_missing_visible_path(&error) => {}
Err(error) => return Err(error),
}
let parent_inode_id = publish_resolve_parent_directory(view, absolute_path).await?;
(parent_inode_id, final_component(absolute_path)?.clone())
}
None => {
let Some(deletion) = view
.metadata_state
.recoverable_deletion(deleted_at_seq, inode_id)
.await?
else {
return Err(CommitValidationError::UndeleteTargetNotDeleted { inode_id }.into());
};
match (deletion.parent_inode_id, deletion.display_name) {
(Some(parent_inode_id), Some(display_name)) => (parent_inode_id, display_name),
_ => {
return Err(CoreError::InvalidCommitRequest(
"the deletion recorded no binding to restore into; \
pass a destination path"
.to_owned(),
));
}
}
}
};
Ok(PlannedOperation::new(
vec![ApiCommitOp::Undelete {
inode_id,
deleted_at_seq,
parent_inode_id,
display_name: display_name.clone(),
}],
vec![
publish_child_name_absent_precondition(parent_inode_id, &display_name),
ApiCommitPrecondition::AncestorsNotSubtreeDeleted {
inode_id: parent_inode_id,
},
],
))
}
pub(super) async fn plan_publish_put_file_content_ref<S: ObjectStore + ?Sized>(
absolute_path: &AbsolutePath,
content_ref: ContentRef,
behavior: DestinationBehavior,
expected_revision_no: Option<RevisionNo>,
view: &PublishPathPlanningView<'_, '_, '_, S>,
) -> Result<PlannedOperation> {
ensure_mutation_path(absolute_path)?;
if expected_revision_no.is_some() && behavior == DestinationBehavior::NoReplace {
return Err(CoreError::InvalidCommitRequest(
"expected_revision_no asserts an existing file revision, which \
contradicts no_replace; use replace behavior with the guard"
.to_owned(),
));
}
publish_reject_tombstoned_path_ancestor(view, absolute_path).await?;
let target = view
.metadata_state
.resolve_visible_path(absolute_path)
.await;
let mut ops = Vec::new();
let mut next_inode_id = view.next_inode_id;
let final_parent_inode =
publish_ensure_parent_directories(absolute_path, view, &mut ops, &mut next_inode_id)
.await?;
let final_name = final_component(absolute_path)?;
let mut preconditions = Vec::new();
match target {
Ok(existing) => {
if behavior == DestinationBehavior::NoReplace {
return Err(CoreError::DestinationExists {
path: absolute_path.as_str().to_owned(),
existing_display_name: Some(existing.display_name.clone()),
});
}
if existing.inode_kind != InodeKind::File {
return Err(CoreError::ExpectedFile {
path: absolute_path.as_str().to_owned(),
kind: existing.inode_kind,
});
}
let revision = view
.metadata_state
.latest_revision_head(existing.inode_id)
.await?
.ok_or_else(|| CoreError::PathNotFound(absolute_path.as_str().to_owned()))?;
let base_revision_no = expected_revision_no.unwrap_or(revision.revision_no);
preconditions.push(publish_binding_is_precondition(view, &existing).await?);
ops.push(ApiCommitOp::ReplaceFile {
inode_id: existing.inode_id,
base_revision_no,
content_ref: content_ref.clone(),
});
preconditions.push(ApiCommitPrecondition::InodeRevisionIs {
inode_id: existing.inode_id,
revision_no: base_revision_no,
});
preconditions.push(ApiCommitPrecondition::AncestorsNotSubtreeDeleted {
inode_id: existing.inode_id,
});
}
Err(error) if is_missing_visible_path(&error) => {
if expected_revision_no.is_some() {
return Err(CoreError::PathNotFound(absolute_path.as_str().to_owned()));
}
ops.push(ApiCommitOp::CreateFile {
parent_inode_id: final_parent_inode,
display_name: final_name.clone(),
content_ref,
});
if view
.metadata_state
.visible_inode(final_parent_inode)
.await?
.is_some()
{
preconditions.push(publish_child_name_absent_precondition(
final_parent_inode,
&final_name,
));
preconditions.push(ApiCommitPrecondition::AncestorsNotSubtreeDeleted {
inode_id: final_parent_inode,
});
}
}
Err(other) => return Err(other),
}
Ok(PlannedOperation::new(ops, preconditions))
}