diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/dispatch.dart b/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/dispatch.dart index cd95941a15..a750a72856 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/dispatch.dart +++ b/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/dispatch.dart @@ -73,7 +73,9 @@ Future> _extractPayload( case FFIStatusCode.Ok: return FlowySuccess(Uint8List.fromList(response.payload)); case FFIStatusCode.Err: - return FlowyFailure(Uint8List.fromList(response.payload)); + final errorBytes = Uint8List.fromList(response.payload); + ErrorCodeNotifier.receiveErrorBytes(errorBytes); + return FlowyFailure(errorBytes); case FFIStatusCode.Internal: final error = utf8.decode(response.payload); Log.error("Dispatch internal error: $error"); diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/error.dart b/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/error.dart index 4019f6723f..af8bf24a8b 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/error.dart +++ b/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/error.dart @@ -1,4 +1,8 @@ +import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/dart-ffi/protobuf.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart'; +import 'package:flutter/foundation.dart'; class FlowyInternalError { late FFIStatusCode _statusCode; @@ -20,15 +24,13 @@ class FlowyInternalError { return "$_statusCode: $_error"; } - FlowyInternalError( - {required FFIStatusCode statusCode, required String error}) { + FlowyInternalError({ + required FFIStatusCode statusCode, + required String error, + }) { _statusCode = statusCode; _error = error; } - - factory FlowyInternalError.from(FFIResponse resp) { - return FlowyInternalError(statusCode: resp.code, error: ""); - } } class StackTraceError { @@ -48,3 +50,48 @@ class StackTraceError { return '${error.runtimeType}. Stack trace: $trace'; } } + +class ErrorCodeNotifier extends ChangeNotifier { + // Static instance + static final ErrorCodeNotifier _instance = ErrorCodeNotifier._(); + + // Factory constructor to return the same instance + factory ErrorCodeNotifier() { + return _instance; + } + + FlowyError? _error; + + static void receiveError(FlowyError error) { + if (_instance._error?.code != error.code) { + _instance._error = error; + _instance.notifyListeners(); + } + } + + static void receiveErrorBytes(Uint8List bytes) { + try { + final error = FlowyError.fromBuffer(bytes); + if (_instance._error?.code != error.code) { + _instance._error = error; + _instance.notifyListeners(); + } + } catch (e) { + Log.error("Can not parse error bytes: $e"); + } + } + + static void onError( + void Function(FlowyError error) onError, + bool Function(ErrorCode code)? onErrorIf, + ) { + _instance.addListener(() { + final error = _instance._error; + if (error != null) { + if (onErrorIf == null || onErrorIf(error.code)) { + onError(error); + } + } + }); + } +} diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index f5afd6106a..22f577a27c 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -2103,6 +2103,7 @@ dependencies = [ "client-api", "collab", "collab-entity", + "flowy-error", "lib-infra", ] diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 0b9591d58a..44c3167eee 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -1918,6 +1918,7 @@ dependencies = [ "client-api", "collab", "collab-entity", + "flowy-error", "lib-infra", ] diff --git a/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs b/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs index a197bc1708..720a03598b 100644 --- a/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs +++ b/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs @@ -414,11 +414,11 @@ impl DatabaseCloudService for ServerProvider { } fn summary_database_row( - &self, - workspace_id: &str, - object_id: &str, - summary_row: SummaryRowContent, - ) -> FutureResult { + &self, + workspace_id: &str, + object_id: &str, + summary_row: SummaryRowContent, + ) -> FutureResult { let workspace_id = workspace_id.to_string(); let server = self.get_server(); let object_id = object_id.to_string(); @@ -435,7 +435,7 @@ impl DatabaseCloudService for ServerProvider { workspace_id: &str, translate_row: TranslateRowContent, language: &str, - ) -> FutureResult { + ) -> FutureResult { let workspace_id = workspace_id.to_string(); let server = self.get_server(); let language = language.to_string(); diff --git a/frontend/rust-lib/flowy-database-pub/Cargo.toml b/frontend/rust-lib/flowy-database-pub/Cargo.toml index fb258183a8..91426a5c87 100644 --- a/frontend/rust-lib/flowy-database-pub/Cargo.toml +++ b/frontend/rust-lib/flowy-database-pub/Cargo.toml @@ -11,3 +11,4 @@ collab-entity = { workspace = true } collab = { workspace = true } anyhow.workspace = true client-api = { workspace = true } +flowy-error = { workspace = true } \ No newline at end of file diff --git a/frontend/rust-lib/flowy-database-pub/src/cloud.rs b/frontend/rust-lib/flowy-database-pub/src/cloud.rs index 3a43eb36da..a07bf3729f 100644 --- a/frontend/rust-lib/flowy-database-pub/src/cloud.rs +++ b/frontend/rust-lib/flowy-database-pub/src/cloud.rs @@ -4,6 +4,7 @@ use collab::core::collab::DataSource; use collab_entity::CollabType; use lib_infra::future::FutureResult; use std::collections::HashMap; +use flowy_error::FlowyError; pub type CollabDocStateByOid = HashMap; pub type SummaryRowContent = HashMap; @@ -40,14 +41,14 @@ pub trait DatabaseCloudService: Send + Sync { workspace_id: &str, object_id: &str, summary_row: SummaryRowContent, - ) -> FutureResult; + ) -> FutureResult; fn translate_database_row( &self, workspace_id: &str, translate_row: TranslateRowContent, language: &str, - ) -> FutureResult; + ) -> FutureResult; } pub struct DatabaseSnapshot { diff --git a/frontend/rust-lib/flowy-database2/Cargo.toml b/frontend/rust-lib/flowy-database2/Cargo.toml index 8a5be01139..cc32f944f5 100644 --- a/frontend/rust-lib/flowy-database2/Cargo.toml +++ b/frontend/rust-lib/flowy-database2/Cargo.toml @@ -17,10 +17,11 @@ flowy-derive.workspace = true flowy-notification = { workspace = true } parking_lot.workspace = true protobuf.workspace = true -flowy-error = { workspace = true, features = [ +flowy-error = { path = "../flowy-error", features = [ "impl_from_dispatch_error", "impl_from_collab_database", -] } +]} + lib-dispatch = { workspace = true } tokio = { workspace = true, features = ["sync"] } bytes.workspace = true diff --git a/frontend/rust-lib/flowy-error/src/code.rs b/frontend/rust-lib/flowy-error/src/code.rs index c36c5df211..d7d8c8aee0 100644 --- a/frontend/rust-lib/flowy-error/src/code.rs +++ b/frontend/rust-lib/flowy-error/src/code.rs @@ -286,6 +286,12 @@ pub enum ErrorCode { #[error("Local AI unavailable")] LocalAIUnavailable = 99, + + #[error("File storage limit exceeded")] + FileStorageLimitExceeded = 100, + + #[error("AI Response limit exceeded")] + AIResponseLimitExceeded = 101, } impl ErrorCode { diff --git a/frontend/rust-lib/flowy-error/src/errors.rs b/frontend/rust-lib/flowy-error/src/errors.rs index a442a224ca..b832778d53 100644 --- a/frontend/rust-lib/flowy-error/src/errors.rs +++ b/frontend/rust-lib/flowy-error/src/errors.rs @@ -72,6 +72,14 @@ impl FlowyError { self.code == ErrorCode::LocalVersionNotSupport } + pub fn is_file_limit_exceeded(&self) -> bool { + self.code == ErrorCode::FileStorageLimitExceeded + } + + pub fn is_ai_response_limit_exceeded(&self) -> bool { + self.code == ErrorCode::AIResponseLimitExceeded + } + static_flowy_error!(internal, ErrorCode::Internal); static_flowy_error!(record_not_found, ErrorCode::RecordNotFound); static_flowy_error!(workspace_initialize, ErrorCode::WorkspaceInitializeError); diff --git a/frontend/rust-lib/flowy-error/src/impl_from/cloud.rs b/frontend/rust-lib/flowy-error/src/impl_from/cloud.rs index b7af102c26..bb4059164f 100644 --- a/frontend/rust-lib/flowy-error/src/impl_from/cloud.rs +++ b/frontend/rust-lib/flowy-error/src/impl_from/cloud.rs @@ -24,6 +24,8 @@ impl From for FlowyError { AppErrorCode::UserUnAuthorized => ErrorCode::UserUnauthorized, AppErrorCode::WorkspaceLimitExceeded => ErrorCode::WorkspaceLimitExceeded, AppErrorCode::WorkspaceMemberLimitExceeded => ErrorCode::WorkspaceMemberLimitExceeded, + AppErrorCode::AIResponseLimitExceeded => ErrorCode::AIResponseLimitExceeded, + AppErrorCode::FileStorageLimitExceeded => ErrorCode::FileStorageLimitExceeded, _ => ErrorCode::Internal, }; diff --git a/frontend/rust-lib/flowy-error/src/impl_from/mod.rs b/frontend/rust-lib/flowy-error/src/impl_from/mod.rs index b3d0351cd4..7ab7eda248 100644 --- a/frontend/rust-lib/flowy-error/src/impl_from/mod.rs +++ b/frontend/rust-lib/flowy-error/src/impl_from/mod.rs @@ -13,7 +13,7 @@ pub mod reqwest; #[cfg(feature = "impl_from_sqlite")] pub mod database; -#[cfg(feature = "impl_from_collab_document")] +#[cfg(any(feature = "impl_from_collab_document", feature = "impl_from_collab_folder", feature = "impl_from_collab_database"))] pub mod collab; #[cfg(feature = "impl_from_collab_persistence")] diff --git a/frontend/rust-lib/flowy-server/src/af_cloud/impls/database.rs b/frontend/rust-lib/flowy-server/src/af_cloud/impls/database.rs index f44a82ad3e..a3407c4148 100644 --- a/frontend/rust-lib/flowy-server/src/af_cloud/impls/database.rs +++ b/frontend/rust-lib/flowy-server/src/af_cloud/impls/database.rs @@ -16,6 +16,7 @@ use flowy_database_pub::cloud::{ CollabDocStateByOid, DatabaseCloudService, DatabaseSnapshot, SummaryRowContent, TranslateRowContent, TranslateRowResponse, }; +use flowy_error::FlowyError; use lib_infra::future::FutureResult; use crate::af_cloud::define::ServerUser; @@ -124,9 +125,9 @@ where fn summary_database_row( &self, workspace_id: &str, - _object_id: &str, + object_id: &str, summary_row: SummaryRowContent, - ) -> FutureResult { + ) -> FutureResult { let workspace_id = workspace_id.to_string(); let try_get_client = self.inner.try_get_client(); FutureResult::new(async move { @@ -148,7 +149,7 @@ where workspace_id: &str, translate_row: TranslateRowContent, language: &str, - ) -> FutureResult { + ) -> FutureResult { let language = language.to_string(); let workspace_id = workspace_id.to_string(); let try_get_client = self.inner.try_get_client(); diff --git a/frontend/rust-lib/flowy-server/src/local_server/impls/database.rs b/frontend/rust-lib/flowy-server/src/local_server/impls/database.rs index d0bd3b0552..1bef7db39b 100644 --- a/frontend/rust-lib/flowy-server/src/local_server/impls/database.rs +++ b/frontend/rust-lib/flowy-server/src/local_server/impls/database.rs @@ -8,16 +8,17 @@ use flowy_database_pub::cloud::{ CollabDocStateByOid, DatabaseCloudService, DatabaseSnapshot, SummaryRowContent, TranslateRowContent, TranslateRowResponse, }; +use flowy_error::FlowyError; use lib_infra::future::FutureResult; pub(crate) struct LocalServerDatabaseCloudServiceImpl(); impl DatabaseCloudService for LocalServerDatabaseCloudServiceImpl { fn get_database_object_doc_state( - &self, - object_id: &str, - collab_type: CollabType, - _workspace_id: &str, + &self, + object_id: &str, + collab_type: CollabType, + workspace_id: &str, ) -> FutureResult>, Error> { let object_id = object_id.to_string(); // create the minimal required data for the given collab type @@ -62,9 +63,9 @@ impl DatabaseCloudService for LocalServerDatabaseCloudServiceImpl { fn batch_get_database_object_doc_state( &self, - _object_ids: Vec, - _object_ty: CollabType, - _workspace_id: &str, + object_ids: Vec, + object_ty: CollabType, + workspace_id: &str, ) -> FutureResult { FutureResult::new(async move { Ok(CollabDocStateByOid::default()) }) } @@ -79,20 +80,20 @@ impl DatabaseCloudService for LocalServerDatabaseCloudServiceImpl { fn summary_database_row( &self, - _workspace_id: &str, - _object_id: &str, - _summary_row: SummaryRowContent, - ) -> FutureResult { + workspace_id: &str, + object_id: &str, + summary_row: SummaryRowContent, + ) -> FutureResult { // TODO(lucas): local ai FutureResult::new(async move { Ok("".to_string()) }) } fn translate_database_row( &self, - _workspace_id: &str, - _translate_row: TranslateRowContent, - _language: &str, - ) -> FutureResult { + workspace_id: &str, + translate_row: TranslateRowContent, + language: &str, + ) -> FutureResult { // TODO(lucas): local ai FutureResult::new(async move { Ok(TranslateRowResponse::default()) }) } diff --git a/frontend/rust-lib/flowy-server/src/supabase/api/database.rs b/frontend/rust-lib/flowy-server/src/supabase/api/database.rs index afba36d585..f4dcbc7845 100644 --- a/frontend/rust-lib/flowy-server/src/supabase/api/database.rs +++ b/frontend/rust-lib/flowy-server/src/supabase/api/database.rs @@ -6,6 +6,7 @@ use flowy_database_pub::cloud::{ CollabDocStateByOid, DatabaseCloudService, DatabaseSnapshot, SummaryRowContent, TranslateRowContent, TranslateRowResponse, }; +use flowy_error::FlowyError; use lib_dispatch::prelude::af_spawn; use lib_infra::future::FutureResult; @@ -29,10 +30,10 @@ where T: SupabaseServerService, { fn get_database_object_doc_state( - &self, - object_id: &str, - collab_type: CollabType, - _workspace_id: &str, + &self, + object_id: &str, + collab_type: CollabType, + workspace_id: &str, ) -> FutureResult>, Error> { let try_get_postgrest = self.server.try_get_weak_postgrest(); let object_id = object_id.to_string(); @@ -56,7 +57,7 @@ where &self, object_ids: Vec, object_ty: CollabType, - _workspace_id: &str, + workspace_id: &str, ) -> FutureResult { let try_get_postgrest = self.server.try_get_weak_postgrest(); let (tx, rx) = channel(); @@ -100,19 +101,19 @@ where fn summary_database_row( &self, - _workspace_id: &str, - _object_id: &str, - _summary_row: SummaryRowContent, - ) -> FutureResult { + workspace_id: &str, + object_id: &str, + summary_row: SummaryRowContent, + ) -> FutureResult { FutureResult::new(async move { Ok("".to_string()) }) } fn translate_database_row( &self, - _workspace_id: &str, - _translate_row: TranslateRowContent, - _language: &str, - ) -> FutureResult { + workspace_id: &str, + translate_row: TranslateRowContent, + language: &str, + ) -> FutureResult { FutureResult::new(async move { Ok(TranslateRowResponse::default()) }) } } diff --git a/frontend/rust-lib/flowy-storage/src/uploader.rs b/frontend/rust-lib/flowy-storage/src/uploader.rs index 9d6fb0ecb0..b909823860 100644 --- a/frontend/rust-lib/flowy-storage/src/uploader.rs +++ b/frontend/rust-lib/flowy-storage/src/uploader.rs @@ -10,7 +10,7 @@ use std::sync::atomic::{AtomicBool, AtomicU8}; use std::sync::{Arc, Weak}; use std::time::Duration; use tokio::sync::{watch, RwLock}; -use tracing::{info, trace}; +use tracing::{error, info, trace}; #[derive(Clone)] pub enum Signal { @@ -128,6 +128,11 @@ impl FileUploader { } => { let record = BoxAny::new(record); if let Err(err) = self.storage_service.start_upload(&chunks, &record).await { + if (err.is_file_limit_exceeded()) { + error!("Failed to upload file: {}", err); + self.pause(); + } + info!( "Failed to upload file: {}, retry_count:{}", err, retry_count @@ -154,6 +159,11 @@ impl FileUploader { .resume_upload(&workspace_id, &parent_dir, &file_id) .await { + if (err.is_file_limit_exceeded()) { + error!("Failed to upload file: {}", err); + self.pause(); + } + info!( "Failed to resume upload file: {}, retry_count:{}", err, retry_count