From b17405d42e24d89d16a96f2546615d371c9787ae Mon Sep 17 00:00:00 2001 From: root Date: Sat, 19 Sep 2026 15:57:58 +0000 Subject: [PATCH] fix(desktop): retry file URL without stale entity id (#159) The entity id cached in local inventory can go stale when the remote file is re-uploaded or migrated, which made hydration/opening fail permanently with "entity not exist" (40077). Both the on-demand hydration path and the download task now retry letting the server pick the primary entity. Authored By: TDvorak Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- desktop/crates/cloudreve-api/src/error.rs | 7 +++++++ .../cloudreve-sync/src/drive/commands.rs | 20 ++++++++++++++----- .../cloudreve-sync/src/tasks/download.rs | 18 ++++++++++++----- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/desktop/crates/cloudreve-api/src/error.rs b/desktop/crates/cloudreve-api/src/error.rs index 63c4b81e..bcd9d3fd 100644 --- a/desktop/crates/cloudreve-api/src/error.rs +++ b/desktop/crates/cloudreve-api/src/error.rs @@ -46,6 +46,7 @@ pub enum ErrorCode { IncorrectPassword = 40069, LockConflict = 40073, StaleVersion = 40076, + EntityNotExist = 40077, BatchOperationNotFullyCompleted = 40081, DomainNotLicensed = 40087, AnonymousAccessDenied = 40088, @@ -65,6 +66,7 @@ impl ErrorCode { 40069 => Some(Self::IncorrectPassword), 40073 => Some(Self::LockConflict), 40076 => Some(Self::StaleVersion), + 40077 => Some(Self::EntityNotExist), 40081 => Some(Self::BatchOperationNotFullyCompleted), 40087 => Some(Self::DomainNotLicensed), 40088 => Some(Self::AnonymousAccessDenied), @@ -198,6 +200,11 @@ impl ApiError { matches!(self, ApiError::AccessTokenExpired) } + /// Check if this error reports a missing entity (code 40077) + pub fn is_entity_not_exist(&self) -> bool { + matches!(self, ApiError::ApiError { code, .. } if *code == 40077) + } + /// Check if this error requires login pub fn requires_login(&self) -> bool { matches!( diff --git a/desktop/crates/cloudreve-sync/src/drive/commands.rs b/desktop/crates/cloudreve-sync/src/drive/commands.rs index 4bf1db03..cf9601ac 100644 --- a/desktop/crates/cloudreve-sync/src/drive/commands.rs +++ b/desktop/crates/cloudreve-sync/src/drive/commands.rs @@ -239,11 +239,21 @@ impl Mount { request.entity = Some(meta.etag.clone()); } } - let entity_url_res = self - .cr_client - .get_file_url(&request) - .await - .context("failed to get file url")?; + let entity_url_res = match self.cr_client.get_file_url(&request).await { + Err(e) if e.is_entity_not_exist() && request.entity.is_some() => { + // The entity id cached in local inventory can go stale when the + // remote file is re-uploaded or migrated; retry letting the + // server pick the primary entity. + tracing::info!(target: "drive::commands", path = %path.display(), "Preferred entity no longer exists, retrying without it"); + let mut retry = request.clone(); + retry.entity = None; + self.cr_client + .get_file_url(&retry) + .await + .context("failed to get file url")? + } + res => res.context("failed to get file url")?, + }; // Get the download URL from the response let download_url = entity_url_res diff --git a/desktop/crates/cloudreve-sync/src/tasks/download.rs b/desktop/crates/cloudreve-sync/src/tasks/download.rs index 6d1e6a24..3160cbfa 100644 --- a/desktop/crates/cloudreve-sync/src/tasks/download.rs +++ b/desktop/crates/cloudreve-sync/src/tasks/download.rs @@ -338,11 +338,19 @@ impl<'a> DownloadTask<'a> { .clone(); } - let entity_url_res = self - .cr_client - .get_file_url(&request) - .await - .context("failed to get file url")?; + let entity_url_res = match self.cr_client.get_file_url(&request).await { + Err(e) if e.is_entity_not_exist() && request.entity.is_some() => { + // Entity id cached from a previous file info response may be + // stale; retry letting the server pick the primary entity. + let mut retry = request.clone(); + retry.entity = None; + self.cr_client + .get_file_url(&retry) + .await + .context("failed to get file url")? + } + res => res.context("failed to get file url")?, + }; let download_url = entity_url_res .urls