Merge pull request #7761 from AppFlowy-IO/do_not_initialize_collab

fix: 0.8.9 release bugs
This commit is contained in:
Nathan.fooo 2025-04-16 17:06:31 +08:00 committed by GitHub
commit 954e844a21
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 163 additions and 66 deletions

View file

@ -73,27 +73,24 @@ class RelatedRowDetailPageBloc
});
}
/// initialize bloc through the `database_id` and `row_id`. The process is as
/// follows:
/// 1. use the `database_id` to get the database meta, which contains the
/// `inline_view_id`
/// 2. use the `inline_view_id` to instantiate a `DatabaseController`.
/// 3. use the `row_id` with the DatabaseController` to create `RowController`
void _init(String databaseId, String initialRowId) async {
final databaseMeta =
await DatabaseEventGetDatabaseMeta(DatabaseIdPB(value: databaseId))
.send()
.fold<DatabaseMetaPB?>((s) => s, (f) => null);
if (databaseMeta == null) {
final viewId = await DatabaseEventGetDefaultDatabaseViewId(
DatabaseIdPB(value: databaseId),
).send().fold(
(pb) => pb.value,
(error) => null,
);
if (viewId == null) {
return;
}
final inlineView =
await ViewBackendService.getView(databaseMeta.inlineViewId)
.fold((viewPB) => viewPB, (f) => null);
if (inlineView == null) {
final databaseView = await ViewBackendService.getView(viewId)
.fold((viewPB) => viewPB, (f) => null);
if (databaseView == null) {
return;
}
final databaseController = DatabaseController(view: inlineView);
final databaseController = DatabaseController(view: databaseView);
await databaseController.open().fold(
(s) => databaseController.setIsLoading(false),
(f) => null,
@ -104,7 +101,7 @@ class RelatedRowDetailPageBloc
}
final rowController = RowController(
rowMeta: rowInfo.rowMeta,
viewId: inlineView.id,
viewId: databaseView.id,
rowCache: databaseController.rowCache,
);

View file

@ -12,6 +12,10 @@ class WindowSizeManager {
static const double maxWindowHeight = 8192.0;
static const double maxWindowWidth = 8192.0;
// Default windows size
static const double defaultWindowHeight = 960.0;
static const double defaultWindowWidth = 1280.0;
static const double maxScaleFactor = 2.0;
static const double minScaleFactor = 0.5;
@ -36,8 +40,8 @@ class WindowSizeManager {
Future<Size> getSize() async {
final defaultWindowSize = jsonEncode(
{
WindowSizeManager.height: minWindowHeight,
WindowSizeManager.width: minWindowWidth,
WindowSizeManager.height: defaultWindowHeight,
WindowSizeManager.width: defaultWindowWidth,
},
);
final windowSize = await getIt<KeyValueStorage>().get(KVKeys.windowSize);

View file

@ -244,7 +244,10 @@ class SidebarSectionsBloc
}
void _initial(UserProfilePB userProfile, String workspaceId) {
_workspaceService = WorkspaceService(workspaceId: workspaceId);
_workspaceService = WorkspaceService(
workspaceId: workspaceId,
userId: userProfile.id,
);
_listener = WorkspaceSectionsListener(
user: userProfile,

View file

@ -29,7 +29,7 @@ class SettingsBillingBloc
required Int64 userId,
}) : super(const _Initial()) {
_userService = UserBackendService(userId: userId);
_service = WorkspaceService(workspaceId: workspaceId);
_service = WorkspaceService(workspaceId: workspaceId, userId: userId);
_successListenable = getIt<SubscriptionSuccessListenable>();
_successListenable.addListener(_onPaymentSuccessful);

View file

@ -23,7 +23,10 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
required this.workspaceId,
required Int64 userId,
}) : super(const _Initial()) {
_service = WorkspaceService(workspaceId: workspaceId);
_service = WorkspaceService(
workspaceId: workspaceId,
userId: userId,
);
_userService = UserBackendService(userId: userId);
_successListenable = getIt<SubscriptionSuccessListenable>();
_successListenable.addListener(_onPaymentSuccessful);
@ -43,7 +46,7 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
FlowyError? error;
final usageResult = snapshots.first.fold(
(s) => s as WorkspaceUsagePB,
(s) => s as WorkspaceUsagePB?,
(f) {
error = f;
return null;

View file

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/settings/file_storage/file_storage_listener.dart';
import 'package:appflowy/workspace/application/subscription_success_listenable/subscription_success_listenable.dart';
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/dispatch/error.dart';
import 'package:appflowy_backend/log.dart';
@ -182,20 +183,24 @@ class SidebarPlanBloc extends Bloc<SidebarPlanEvent, SidebarPlanState> {
);
}
void _checkWorkspaceUsage() {
if (state.workspaceId != null) {
final payload = UserWorkspaceIdPB(workspaceId: state.workspaceId!);
UserEventGetWorkspaceUsage(payload).send().then((result) {
result.onSuccess(
(usage) {
if (isClosed) {
return;
}
add(SidebarPlanEvent.updateWorkspaceUsage(usage));
},
);
});
Future<void> _checkWorkspaceUsage() async {
if (state.workspaceId == null || state.userProfile == null) {
return;
}
await WorkspaceService(
workspaceId: state.workspaceId!,
userId: state.userProfile!.id,
).getWorkspaceUsage().then((result) {
result.fold(
(usage) {
if (!isClosed && usage != null) {
add(SidebarPlanEvent.updateWorkspaceUsage(usage));
}
},
(error) => Log.error("Failed to get workspace usage: $error"),
);
});
}
}

View file

@ -486,7 +486,10 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
}
void _initial(UserProfilePB userProfile, String workspaceId) {
_workspaceService = WorkspaceService(workspaceId: workspaceId);
_workspaceService = WorkspaceService(
workspaceId: workspaceId,
userId: userProfile.id,
);
this.userProfile = userProfile;
this.workspaceId = workspaceId;

View file

@ -1,15 +1,18 @@
import 'dart:async';
import 'package:appflowy/shared/af_role_pb_extension.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:fixnum/fixnum.dart' as fixnum;
class WorkspaceService {
WorkspaceService({required this.workspaceId});
WorkspaceService({required this.workspaceId, required this.userId});
final String workspaceId;
final fixnum.Int64 userId;
Future<FlowyResult<ViewPB, FlowyError>> createView({
required String name,
@ -82,7 +85,18 @@ class WorkspaceService {
return FolderEventMoveView(payload).send();
}
Future<FlowyResult<WorkspaceUsagePB, FlowyError>> getWorkspaceUsage() {
Future<FlowyResult<WorkspaceUsagePB?, FlowyError>> getWorkspaceUsage() async {
final request = WorkspaceMemberIdPB()..uid = userId;
final result = await UserEventGetMemberInfo(request).send();
final isOwner = result.fold(
(member) => member.role.isOwner,
(_) => false,
);
if (!isOwner) {
return FlowyResult.success(null);
}
final payload = UserWorkspaceIdPB(workspaceId: workspaceId);
return UserEventGetWorkspaceUsage(payload).send();
}

View file

@ -69,7 +69,10 @@ class AppFlowyUnitTest {
}
Future<void> _initialServices() async {
workspaceService = WorkspaceService(workspaceId: currentWorkspace.id);
workspaceService = WorkspaceService(
workspaceId: currentWorkspace.id,
userId: userProfile.id,
);
}
Future<ViewPB> createWorkspace() async {

View file

@ -277,7 +277,7 @@ impl AppFlowyCollabBuilder {
let collab_db = collab_db.clone();
let device_id = self.workspace_integrate.device_id()?;
let collab = tokio::task::spawn_blocking(move || {
let mut collab = CollabBuilder::new(object.uid, &object.object_id, data_source)
let collab = CollabBuilder::new(object.uid, &object.object_id, data_source)
.with_device_id(device_id)
.build()?;
let persistence_config = CollabPersistenceConfig::default();
@ -290,7 +290,6 @@ impl AppFlowyCollabBuilder {
persistence_config,
);
collab.add_plugin(Box::new(db_plugin));
collab.initialize();
Ok::<_, Error>(collab)
})
.await??;

View file

@ -865,17 +865,25 @@ pub(crate) async fn delete_group_handler(
}
#[tracing::instrument(level = "debug", skip(manager), err)]
pub(crate) async fn get_database_meta_handler(
pub(crate) async fn get_default_database_view_id_handler(
data: AFPluginData<DatabaseIdPB>,
manager: AFPluginState<Weak<DatabaseManager>>,
) -> DataResult<DatabaseMetaPB, FlowyError> {
) -> DataResult<DatabaseViewIdPB, FlowyError> {
let manager = upgrade_manager(manager)?;
let database_id = data.into_inner().value;
let inline_view_id = manager.get_database_inline_view_id(&database_id).await?;
let database_view_id = manager
.get_database_meta(&database_id)
.await?
.and_then(|mut d| d.linked_views.pop())
.ok_or_else(|| {
FlowyError::internal().with_context(format!(
"Can't find any database view for given database id: {}",
database_id
))
})?;
let data = DatabaseMetaPB {
database_id,
inline_view_id,
let data = DatabaseViewIdPB {
value: database_view_id,
};
data_result_ok(data)
}

View file

@ -64,7 +64,7 @@ pub fn init(database_manager: Weak<DatabaseManager>) -> AFPlugin {
.event(DatabaseEvent::CreateGroup, create_group_handler)
.event(DatabaseEvent::DeleteGroup, delete_group_handler)
// Database
.event(DatabaseEvent::GetDatabaseMeta, get_database_meta_handler)
.event(DatabaseEvent::GetDefaultDatabaseViewId, get_default_database_view_id_handler)
.event(DatabaseEvent::GetDatabases, get_databases_handler)
// Calendar
.event(DatabaseEvent::GetAllCalendarEvents, get_calendar_events_handler)
@ -305,8 +305,8 @@ pub enum DatabaseEvent {
#[event(input = "DeleteGroupPayloadPB")]
DeleteGroup = 115,
#[event(input = "DatabaseIdPB", output = "DatabaseMetaPB")]
GetDatabaseMeta = 119,
#[event(input = "DatabaseIdPB", output = "DatabaseViewIdPB")]
GetDefaultDatabaseViewId = 119,
/// Returns all the databases
#[event(output = "RepeatedDatabaseDescriptionPB")]

View file

@ -166,6 +166,15 @@ impl DatabaseManager {
items
}
pub async fn get_database_meta(&self, database_id: &str) -> FlowyResult<Option<DatabaseMeta>> {
let mut database_meta = None;
if let Some(lock) = self.workspace_database_manager.load_full() {
let wdb = lock.read().await;
database_meta = wdb.get_database_meta(database_id);
}
Ok(database_meta)
}
#[instrument(level = "trace", skip_all, err)]
pub async fn update_database_indexing(
&self,

View file

@ -138,7 +138,12 @@ impl FolderManager {
Arc::downgrade(&self.user),
);
self.folder_indexer.initialize().await;
let weak_folder_indexer = Arc::downgrade(&self.folder_indexer);
tokio::spawn(async move {
if let Some(folder_indexer) = weak_folder_indexer.upgrade() {
folder_indexer.initialize().await;
}
});
Ok(())
}

View file

@ -90,6 +90,7 @@ impl SearchHandler for DocumentSearchHandler {
return;
}
};
trace!("[Search] search result: {:?}", result_items);
// Prepare input for search summary generation.
let summary_input: Vec<SearchResult> = result_items
@ -122,11 +123,15 @@ impl SearchHandler for DocumentSearchHandler {
CreateSearchResultPBArgs::default()
.searching(false)
.search_result(Some(search_result))
.generating_ai_summary(true)
.generating_ai_summary(!result_items.is_empty())
.build()
.unwrap(),
);
if result_items.is_empty() {
return;
}
// Generate and yield search summary.
match cloud_service.generate_search_summary(&workspace_id, query.clone(), summary_input).await {
Ok(summary_result) => {

View file

@ -1,3 +1,5 @@
use super::entities::FolderIndexData;
use crate::entities::{LocalSearchResponseItemPB, ResultIconTypePB};
use crate::folder::schema::{
FolderSchema, FOLDER_ICON_FIELD_NAME, FOLDER_ICON_TY_FIELD_NAME, FOLDER_ID_FIELD_NAME,
FOLDER_TITLE_FIELD_NAME, FOLDER_WORKSPACE_ID_FIELD_NAME,
@ -7,27 +9,32 @@ use collab_folder::{folder_diff::FolderViewChange, View, ViewIcon, ViewIndexCont
use flowy_error::{FlowyError, FlowyResult};
use flowy_search_pub::entities::{FolderIndexManager, IndexManager, IndexableData};
use flowy_user::services::authenticate_user::AuthenticateUser;
use lib_infra::async_trait::async_trait;
use std::path::PathBuf;
use std::sync::{Arc, Weak};
use std::{collections::HashMap, fs};
use super::entities::FolderIndexData;
use crate::entities::{LocalSearchResponseItemPB, ResultIconTypePB};
use lib_infra::async_trait::async_trait;
use tantivy::{
collector::TopDocs, directory::MmapDirectory, doc, query::QueryParser, schema::Field, Document,
Index, IndexReader, IndexWriter, TantivyDocument, Term,
Index, IndexReader, IndexWriter, TantivyDocument, TantivyError, Term,
};
use tokio::sync::RwLock;
use tracing::{error, info};
use uuid::Uuid;
pub struct TantivyState {
pub path: PathBuf,
pub index: Index,
pub folder_schema: FolderSchema,
pub index_reader: IndexReader,
pub index_writer: IndexWriter,
}
impl Drop for TantivyState {
fn drop(&mut self) {
tracing::trace!("Dropping TantivyState at {:?}", self.path);
}
}
const FOLDER_INDEX_DIR: &str = "folder_index";
#[derive(Clone)]
@ -57,7 +64,19 @@ impl FolderIndexManagerImpl {
}
/// Initializes the state using the workspace directory.
async fn initialize_with_workspace(&self) -> FlowyResult<()> {
async fn initialize(&self) -> FlowyResult<()> {
if let Some(state) = self.state.write().await.take() {
info!("Re-initializing folder indexer");
drop(state);
}
// Since the directory lock may not be immediately released,
// a workaround is implemented by waiting for 3 seconds before proceeding further. This delay helps
// to avoid errors related to trying to open an index directory while an IndexWriter is still active.
//
// Also, we don't need to initialize the indexer immediately.
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
let auth_user = self
.auth_user
.upgrade()
@ -73,12 +92,26 @@ impl FolderIndexManagerImpl {
info!("Folder indexer initialized at: {:?}", index_path);
let folder_schema = FolderSchema::new();
let dir = MmapDirectory::open(index_path)?;
let dir = MmapDirectory::open(index_path.clone())?;
let index = Index::open_or_create(dir, folder_schema.schema.clone())?;
let index_reader = index.reader()?;
let index_writer = index.writer(50_000_000)?;
let index_writer = match index.writer::<_>(50_000_000) {
Ok(index_writer) => index_writer,
Err(err) => {
if let TantivyError::LockFailure(_, _) = err {
error!(
"Failed to acquire lock for index writer: {:?}, retry later",
err
);
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
}
index.writer::<_>(50_000_000)?
},
};
*self.state.write().await = Some(TantivyState {
path: index_path,
index,
folder_schema,
index_reader,
@ -295,7 +328,7 @@ impl IndexManager for FolderIndexManagerImpl {
#[async_trait]
impl FolderIndexManager for FolderIndexManagerImpl {
async fn initialize(&self) {
if let Err(e) = self.initialize_with_workspace().await {
if let Err(e) = self.initialize().await {
error!("Failed to initialize FolderIndexManager: {:?}", e);
}
}

View file

@ -896,7 +896,10 @@ pub async fn get_workspace_member_info(
manager: AFPluginState<Weak<UserManager>>,
) -> DataResult<WorkspaceMemberPB, FlowyError> {
let manager = upgrade_manager(manager)?;
let member = manager.get_workspace_member_info(param.uid).await?;
let workspace_id = manager.get_session()?.user_workspace.workspace_id()?;
let member = manager
.get_workspace_member_info(param.uid, &workspace_id)
.await?;
data_result_ok(member.into())
}

View file

@ -583,8 +583,11 @@ impl UserManager {
Ok(UseAISettingPB::from(settings))
}
pub async fn get_workspace_member_info(&self, uid: i64) -> FlowyResult<WorkspaceMember> {
let workspace_id = self.get_session()?.user_workspace.workspace_id()?;
pub async fn get_workspace_member_info(
&self,
uid: i64,
workspace_id: &Uuid,
) -> FlowyResult<WorkspaceMember> {
let db = self.authenticate_user.get_sqlite_connection(uid)?;
// Can opt in using memory cache
if let Ok(member_record) = select_workspace_member(db, &workspace_id.to_string(), uid) {
@ -603,7 +606,7 @@ impl UserManager {
}
let member = self
.get_workspace_member_info_from_remote(&workspace_id, uid)
.get_workspace_member_info_from_remote(workspace_id, uid)
.await?;
Ok(member)