looks ok
This commit is contained in:
parent
5ee9db0007
commit
6a8c7635f1
8 changed files with 122 additions and 78 deletions
|
|
@ -69,31 +69,40 @@ pub async fn create_document(
|
|||
.map_err(server_error)?;
|
||||
|
||||
if let Some(latest_version) = latest_version {
|
||||
let is_mergeable_text = is_file_type_mergable(
|
||||
&sanitized_relative_path,
|
||||
&state.config.server.mergeable_file_extensions,
|
||||
) && !is_binary(&latest_version.content)
|
||||
&& !is_binary(&new_content);
|
||||
|
||||
if is_mergeable_text || new_content == latest_version.content {
|
||||
return update_document::update_document(
|
||||
// Only merge with an existing document the client couldn't have
|
||||
// known about: its creation is newer than the client's last seen
|
||||
// vault update to avoid creating cycles by merging two documents into one.
|
||||
// This could happen if both clients know of document A at path P1,
|
||||
// but client 2 moves it to P2 while client 1 creates a new document at P2,
|
||||
// then client 1 would merge its new document with the moved version of A at P2
|
||||
// that client 2 resulting in two files (P1 and P2) with the same doc id (A).
|
||||
if latest_version.creation_vault_update_id > request.last_seen_vault_update_id {
|
||||
let is_mergeable_text = is_file_type_mergable(
|
||||
&sanitized_relative_path,
|
||||
Vec::new(),
|
||||
vault_id,
|
||||
latest_version.document_id,
|
||||
&request.relative_path,
|
||||
new_content,
|
||||
user,
|
||||
device_id,
|
||||
state,
|
||||
transaction,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
&state.config.server.mergeable_file_extensions,
|
||||
) && !is_binary(&latest_version.content)
|
||||
&& !is_binary(&new_content);
|
||||
|
||||
// For non-mergeable (binary) files with different content, don't
|
||||
// merge, create a separate document at a deconflicted path so
|
||||
// neither client's data is silently overwritten.
|
||||
if is_mergeable_text || new_content == latest_version.content {
|
||||
return update_document::update_document(
|
||||
&sanitized_relative_path,
|
||||
Vec::new(),
|
||||
vault_id,
|
||||
latest_version.document_id,
|
||||
&request.relative_path,
|
||||
new_content,
|
||||
user,
|
||||
device_id,
|
||||
state,
|
||||
transaction,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// For non-mergeable (binary) files with different content, don't
|
||||
// merge, create a separate document at a deconflicted path so
|
||||
// neither client's data is silently overwritten.
|
||||
}
|
||||
}
|
||||
|
||||
let document_id = uuid::Uuid::new_v4();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use anyhow::Context;
|
||||
use anyhow::{Context, anyhow};
|
||||
use axum::{
|
||||
Extension, Json,
|
||||
extract::{Path, State},
|
||||
|
|
@ -17,7 +17,7 @@ use crate::{
|
|||
},
|
||||
},
|
||||
config::user_config::User,
|
||||
errors::{SyncServerError, server_error, write_transaction_error},
|
||||
errors::{SyncServerError, not_found_error, server_error, write_transaction_error},
|
||||
utils::normalize::normalize,
|
||||
};
|
||||
|
||||
|
|
@ -60,9 +60,18 @@ pub async fn delete_document(
|
|||
.await
|
||||
.map_err(server_error)?;
|
||||
|
||||
if let Some(latest_version) = &latest_version
|
||||
&& latest_version.is_deleted
|
||||
{
|
||||
let Some(latest_version) = latest_version else {
|
||||
transaction
|
||||
.rollback()
|
||||
.await
|
||||
.context("Failed to roll back transaction")
|
||||
.map_err(server_error)?;
|
||||
return Err(not_found_error(anyhow!(
|
||||
"Document `{document_id}` not found in vault `{vault_id}`"
|
||||
)));
|
||||
};
|
||||
|
||||
if latest_version.is_deleted {
|
||||
transaction
|
||||
.rollback()
|
||||
.await
|
||||
|
|
@ -70,15 +79,13 @@ pub async fn delete_document(
|
|||
.map_err(server_error)?;
|
||||
|
||||
info!("Document `{document_id}` has already been deleted",);
|
||||
return Ok(Json(latest_version.clone().into()));
|
||||
return Ok(Json(latest_version.into()));
|
||||
}
|
||||
|
||||
let new_vault_update_id = last_update_id + 1;
|
||||
let (latest_relative_path, latest_content, creation_vault_update_id) =
|
||||
latest_version.map_or_else(
|
||||
|| (String::new(), Vec::new(), new_vault_update_id),
|
||||
|version| (version.relative_path, version.content, version.creation_vault_update_id),
|
||||
);
|
||||
let latest_relative_path = latest_version.relative_path;
|
||||
let latest_content = latest_version.content;
|
||||
let creation_vault_update_id = latest_version.creation_vault_update_id;
|
||||
|
||||
let new_version = StoredDocumentVersion {
|
||||
vault_update_id: new_vault_update_id,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ use crate::app_state::database::models::VaultUpdateId;
|
|||
pub struct CreateDocumentVersion {
|
||||
pub relative_path: String,
|
||||
|
||||
#[ts(type = "number")]
|
||||
pub last_seen_vault_update_id: VaultUpdateId,
|
||||
|
||||
#[ts(as = "Vec<u8>")]
|
||||
#[form_data(limit = "unlimited")]
|
||||
pub content: FieldData<Bytes>,
|
||||
|
|
|
|||
|
|
@ -147,7 +147,14 @@ pub async fn restore_document_version(
|
|||
let (content_changed, path_changed) = match ¤t_latest {
|
||||
Some(prev) => (
|
||||
prev.content != new_version.content || prev.is_deleted,
|
||||
prev.relative_path != new_version.relative_path,
|
||||
// Mirror `update_document`: `path_changed` is true when the
|
||||
// stored path differs from either the prior stored path (peers
|
||||
// need to learn about the move) *or* from the path the caller
|
||||
// implicitly requested (`target_version.relative_path`, so the
|
||||
// origin learns if the server deduped its requested restore
|
||||
// path).
|
||||
prev.relative_path != new_version.relative_path
|
||||
|| target_version.relative_path != new_version.relative_path,
|
||||
),
|
||||
// No prior version (shouldn't happen in practice — target_version
|
||||
// already proved the document exists — but treat defensively).
|
||||
|
|
|
|||
|
|
@ -115,7 +115,6 @@ async fn websocket(
|
|||
let mut broadcast_receiver = match state
|
||||
.broadcasts
|
||||
.get_receiver(vault_id.clone(), max_clients)
|
||||
.await
|
||||
{
|
||||
Ok(receiver) => receiver,
|
||||
Err(err) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue