浏览代码

fix: high cpu usage

nathan 2 年之前
父节点
当前提交
c35db5c2a2
共有 32 个文件被更改,包括 452 次插入323 次删除
  1. 1 1
      frontend/appflowy_tauri/src-tauri/src/init.rs
  2. 54 16
      frontend/appflowy_tauri/src/appflowy_app/components/TestApiButton/DatabaseTestHelper.ts
  3. 4 0
      frontend/appflowy_tauri/src/appflowy_app/components/TestApiButton/TestAPI.tsx
  4. 104 11
      frontend/appflowy_tauri/src/appflowy_app/components/TestApiButton/TestGrid.tsx
  5. 9 8
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/cell_cache.ts
  6. 27 33
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/cell_controller.ts
  7. 14 11
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/cell_observer.ts
  8. 9 54
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/controller_builder.ts
  9. 3 3
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/data_parser.ts
  10. 0 2
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/data_persistence.ts
  11. 2 1
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/select_option_bd_svc.ts
  12. 4 4
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/database_controller.ts
  13. 24 25
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/field_controller.ts
  14. 4 4
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/field_observer.ts
  15. 1 1
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/type_option/type_option_context.ts
  16. 19 5
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/type_option/type_option_controller.ts
  17. 2 2
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/notifications/observer.ts
  18. 47 39
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/row/row_cache.ts
  19. 2 1
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/view/database_view_cache.ts
  20. 1 1
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/view/view_row_observer.ts
  21. 1 0
      frontend/rust-lib/flowy-codegen/src/ts_event/event_template.tera
  22. 21 25
      frontend/rust-lib/flowy-database/src/manager.rs
  23. 2 2
      frontend/rust-lib/flowy-database/src/services/database/database_editor.rs
  24. 4 4
      frontend/rust-lib/flowy-database/src/services/database_view/editor.rs
  25. 5 1
      frontend/rust-lib/flowy-database/src/services/database_view/editor_manager.rs
  26. 1 0
      frontend/rust-lib/flowy-database/src/services/field/type_options/type_option_cell.rs
  27. 20 13
      frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/block_impl.rs
  28. 11 4
      frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/database_impl.rs
  29. 20 13
      frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/view_impl.rs
  30. 5 6
      frontend/rust-lib/flowy-database/src/services/sort/controller.rs
  31. 11 4
      frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_rev_sqlite.rs
  32. 20 29
      frontend/rust-lib/flowy-task/src/scheduler.rs

+ 1 - 1
frontend/appflowy_tauri/src-tauri/src/init.rs

@@ -10,7 +10,7 @@ pub fn init_flowy_core() -> AppFlowyCore {
     }
     data_path.push("data");
 
-    std::env::set_var("RUST_LOG", "debug");
+    std::env::set_var("RUST_LOG", "trace");
     let server_config = get_client_server_configuration().unwrap();
     let config = AppFlowyCoreConfig::new(
         data_path.to_str().unwrap(),

+ 54 - 16
frontend/appflowy_tauri/src/appflowy_app/components/TestApiButton/DatabaseTestHelper.ts

@@ -11,7 +11,6 @@ import {
   SelectOptionCellController,
   TextCellController,
 } from '../../stores/effects/database/cell/controller_builder';
-import assert from 'assert';
 import { None, Option, Some } from 'ts-results';
 import { TypeOptionBackendService } from '../../stores/effects/database/field/type_option/type_option_bd_svc';
 import { DatabaseBackendService } from '../../stores/effects/database/database_bd_svc';
@@ -29,9 +28,16 @@ export async function openTestDatabase(viewId: string): Promise<DatabaseControll
   return new DatabaseController(viewId);
 }
 
-export async function assertTextCell(rowInfo: RowInfo, databaseController: DatabaseController, expectedContent: string) {
-  const cellController = await makeTextCellController(rowInfo, databaseController).then((result) => result.unwrap());
-  cellController.subscribeChanged({
+export async function assertTextCell(
+  fieldId: string,
+  rowInfo: RowInfo,
+  databaseController: DatabaseController,
+  expectedContent: string
+) {
+  const cellController = await makeTextCellController(fieldId, rowInfo, databaseController).then((result) =>
+    result.unwrap()
+  );
+  await cellController.subscribeChanged({
     onCellChanged: (value) => {
       const cellContent = value.unwrap();
       if (cellContent !== expectedContent) {
@@ -39,55 +45,78 @@ export async function assertTextCell(rowInfo: RowInfo, databaseController: Datab
       }
     },
   });
-  cellController.getCellData();
+  await cellController.getCellData();
 }
 
-export async function editTextCell(rowInfo: RowInfo, databaseController: DatabaseController, content: string) {
-  const cellController = await makeTextCellController(rowInfo, databaseController).then((result) => result.unwrap());
+export async function editTextCell(
+  fieldId: string,
+  rowInfo: RowInfo,
+  databaseController: DatabaseController,
+  content: string
+) {
+  const cellController = await makeTextCellController(fieldId, rowInfo, databaseController).then((result) =>
+    result.unwrap()
+  );
   await cellController.saveCellData(content);
 }
 
 export async function makeTextCellController(
+  fieldId: string,
   rowInfo: RowInfo,
   databaseController: DatabaseController
 ): Promise<Option<TextCellController>> {
-  const builder = await makeCellControllerBuilder(rowInfo, FieldType.RichText, databaseController).then((result) =>
-    result.unwrap()
+  const builder = await makeCellControllerBuilder(fieldId, rowInfo, FieldType.RichText, databaseController).then(
+    (result) => result.unwrap()
   );
   return Some(builder.build() as TextCellController);
 }
 
 export async function makeNumberCellController(
+  fieldId: string,
   rowInfo: RowInfo,
   databaseController: DatabaseController
 ): Promise<Option<NumberCellController>> {
-  const builder = await makeCellControllerBuilder(rowInfo, FieldType.Number, databaseController).then((result) =>
-    result.unwrap()
+  const builder = await makeCellControllerBuilder(fieldId, rowInfo, FieldType.Number, databaseController).then(
+    (result) => result.unwrap()
   );
   return Some(builder.build() as NumberCellController);
 }
 
 export async function makeSingleSelectCellController(
+  fieldId: string,
   rowInfo: RowInfo,
   databaseController: DatabaseController
 ): Promise<Option<SelectOptionCellController>> {
-  const builder = await makeCellControllerBuilder(rowInfo, FieldType.SingleSelect, databaseController).then((result) =>
-    result.unwrap()
+  const builder = await makeCellControllerBuilder(fieldId, rowInfo, FieldType.SingleSelect, databaseController).then(
+    (result) => result.unwrap()
+  );
+  return Some(builder.build() as SelectOptionCellController);
+}
+
+export async function makeMultiSelectCellController(
+  fieldId: string,
+  rowInfo: RowInfo,
+  databaseController: DatabaseController
+): Promise<Option<SelectOptionCellController>> {
+  const builder = await makeCellControllerBuilder(fieldId, rowInfo, FieldType.MultiSelect, databaseController).then(
+    (result) => result.unwrap()
   );
   return Some(builder.build() as SelectOptionCellController);
 }
 
 export async function makeDateCellController(
+  fieldId: string,
   rowInfo: RowInfo,
   databaseController: DatabaseController
 ): Promise<Option<DateCellController>> {
-  const builder = await makeCellControllerBuilder(rowInfo, FieldType.DateTime, databaseController).then((result) =>
-    result.unwrap()
+  const builder = await makeCellControllerBuilder(fieldId, rowInfo, FieldType.DateTime, databaseController).then(
+    (result) => result.unwrap()
   );
   return Some(builder.build() as DateCellController);
 }
 
 export async function makeCellControllerBuilder(
+  fieldId: string,
   rowInfo: RowInfo,
   fieldType: FieldType,
   databaseController: DatabaseController
@@ -99,7 +128,7 @@ export async function makeCellControllerBuilder(
   const cellByFieldId = await rowController.loadCells();
   for (const cellIdentifier of cellByFieldId.values()) {
     const builder = new CellControllerBuilder(cellIdentifier, cellCache, fieldController);
-    if (cellIdentifier.fieldType === fieldType) {
+    if (cellIdentifier.fieldId === fieldId) {
       return Some(builder);
     }
   }
@@ -107,6 +136,15 @@ export async function makeCellControllerBuilder(
   return None;
 }
 
+export function findFirstFieldInfoWithFieldType(rowInfo: RowInfo, fieldType: FieldType) {
+  const fieldInfo = rowInfo.fieldInfos.find((element) => element.field.field_type === fieldType);
+  if (fieldInfo === undefined) {
+    return None;
+  } else {
+    return Some(fieldInfo);
+  }
+}
+
 export async function assertFieldName(viewId: string, fieldId: string, fieldType: FieldType, expected: string) {
   const svc = new TypeOptionBackendService(viewId);
   const typeOptionPB = await svc.getTypeOption(fieldId, fieldType).then((result) => result.unwrap());

+ 4 - 0
frontend/appflowy_tauri/src/appflowy_app/components/TestApiButton/TestAPI.tsx

@@ -10,6 +10,8 @@ import {
   TestEditCell,
   TestEditField,
   TestGetSingleSelectFieldData,
+  TestSwitchFromMultiSelectToText,
+  TestSwitchFromSingleSelectToNumber,
 } from './TestGrid';
 
 export const TestAPI = () => {
@@ -26,6 +28,8 @@ export const TestAPI = () => {
         <TestEditField></TestEditField>
         <TestCreateNewField></TestCreateNewField>
         <TestDeleteField></TestDeleteField>
+        <TestSwitchFromSingleSelectToNumber></TestSwitchFromSingleSelectToNumber>
+        <TestSwitchFromMultiSelectToText></TestSwitchFromMultiSelectToText>
       </ul>
     </React.Fragment>
   );

+ 104 - 11
frontend/appflowy_tauri/src/appflowy_app/components/TestApiButton/TestGrid.tsx

@@ -1,6 +1,8 @@
 import React from 'react';
 import {
   FieldType,
+  NumberFormat,
+  NumberTypeOptionPB,
   SelectOptionCellDataPB,
   SingleSelectTypeOptionPB,
   ViewLayoutTypePB,
@@ -13,7 +15,10 @@ import {
   assertTextCell,
   createTestDatabaseView,
   editTextCell,
+  findFirstFieldInfoWithFieldType,
+  makeMultiSelectCellController,
   makeSingleSelectCellController,
+  makeTextCellController,
   openTestDatabase,
 } from './DatabaseTestHelper';
 import {
@@ -23,7 +28,10 @@ import {
 import { TypeOptionController } from '../../stores/effects/database/field/type_option/type_option_controller';
 import { None, Some } from 'ts-results';
 import { RowBackendService } from '../../stores/effects/database/row/row_bd_svc';
-import { makeSingleSelectTypeOptionContext } from '../../stores/effects/database/field/type_option/type_option_context';
+import {
+  makeNumberTypeOptionContext,
+  makeSingleSelectTypeOptionContext,
+} from '../../stores/effects/database/field/type_option/type_option_context';
 
 export const TestCreateGrid = () => {
   async function createBuildInGrid() {
@@ -33,11 +41,11 @@ export const TestCreateGrid = () => {
       onViewChanged: (databasePB) => {
         Log.debug('Did receive database:' + databasePB);
       },
-      onRowsChanged: async (rows) => {
-        if (rows.length !== 3) {
-          throw Error('Expected number of rows is 3, but receive ' + rows.length + view.id);
-        }
-      },
+      // onRowsChanged: async (rows) => {
+      //   if (rows.length !== 3) {
+      //     throw Error('Expected number of rows is 3, but receive ' + rows.length);
+      //   }
+      // },
       onFieldsChanged: (fields) => {
         if (fields.length !== 3) {
           throw Error('Expected number of fields is 3, but receive ' + fields.length);
@@ -59,8 +67,9 @@ export const TestEditCell = () => {
 
     for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
       const cellContent = index.toString();
-      await editTextCell(row, databaseController, cellContent);
-      await assertTextCell(row, databaseController, cellContent);
+      const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.RichText).unwrap();
+      await editTextCell(fieldInfo.field.id, row, databaseController, cellContent);
+      await assertTextCell(fieldInfo.field.id, row, databaseController, cellContent);
     }
   }
 
@@ -111,10 +120,11 @@ export const TestCreateSelectOptionInCell = () => {
     await databaseController.open().then((result) => result.unwrap());
     for (const [index, row] of databaseController.databaseViewCache.rowInfos.entries()) {
       if (index === 0) {
-        const cellController = await makeSingleSelectCellController(row, databaseController).then((result) =>
-          result.unwrap()
+        const fieldInfo = findFirstFieldInfoWithFieldType(row, FieldType.SingleSelect).unwrap();
+        const cellController = await makeSingleSelectCellController(fieldInfo.field.id, row, databaseController).then(
+          (result) => result.unwrap()
         );
-        cellController.subscribeChanged({
+        await cellController.subscribeChanged({
           onCellChanged: (value) => {
             const option: SelectOptionCellDataPB = value.unwrap();
             console.log(option);
@@ -167,6 +177,89 @@ export const TestGetSingleSelectFieldData = () => {
   return TestButton('Test get single-select column data', testGetSingleSelectFieldData);
 };
 
+export const TestSwitchFromSingleSelectToNumber = () => {
+  async function testSwitchFromSingleSelectToNumber() {
+    const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
+    const databaseController = await openTestDatabase(view.id);
+    await databaseController.open().then((result) => result.unwrap());
+
+    // Find the single select column
+    const singleSelect = databaseController.fieldController.fieldInfos.find(
+      (fieldInfo) => fieldInfo.field.field_type === FieldType.SingleSelect
+    )!;
+    const typeOptionController = new TypeOptionController(view.id, Some(singleSelect));
+    await typeOptionController.switchToField(FieldType.Number);
+
+    // Check the number type option
+    const numberTypeOptionContext = makeNumberTypeOptionContext(typeOptionController);
+    const numberTypeOption: NumberTypeOptionPB = await numberTypeOptionContext
+      .getTypeOption()
+      .then((result) => result.unwrap());
+    const format: NumberFormat = numberTypeOption.format;
+    if (format !== NumberFormat.Num) {
+      throw Error('The default format should be number');
+    }
+
+    await databaseController.dispose();
+  }
+
+  return TestButton('Test switch from single-select to number column', testSwitchFromSingleSelectToNumber);
+};
+
+export const TestSwitchFromMultiSelectToText = () => {
+  async function testSwitchFromMultiSelectToRichText() {
+    const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);
+    const databaseController = await openTestDatabase(view.id);
+    await databaseController.open().then((result) => result.unwrap());
+
+    // Create multi-select field
+    const typeOptionController = new TypeOptionController(view.id, None, FieldType.MultiSelect);
+    await typeOptionController.initialize();
+
+    // Insert options to first row
+    const row = databaseController.databaseViewCache.rowInfos[0];
+    const multiSelectField = typeOptionController.getFieldInfo();
+    // const multiSelectField = findFirstFieldInfoWithFieldType(row, FieldType.MultiSelect).unwrap();
+    const selectOptionCellController = await makeMultiSelectCellController(
+      multiSelectField.field.id,
+      row,
+      databaseController
+    ).then((result) => result.unwrap());
+    const backendSvc = new SelectOptionCellBackendService(selectOptionCellController.cellIdentifier);
+    await backendSvc.createOption({ name: 'A' });
+    await backendSvc.createOption({ name: 'B' });
+    await backendSvc.createOption({ name: 'C' });
+
+    const selectOptionCellData = await selectOptionCellController.getCellData().then((result) => result.unwrap());
+    if (selectOptionCellData.options.length !== 3) {
+      throw Error('The options should equal to 3');
+    }
+
+    if (selectOptionCellData.select_options.length !== 3) {
+      throw Error('The selected options should equal to 3');
+    }
+    await selectOptionCellController.dispose();
+
+    // Switch to RichText field type
+    await typeOptionController.switchToField(FieldType.RichText).then((result) => result.unwrap());
+    if (typeOptionController.fieldType !== FieldType.RichText) {
+      throw Error('The field type should be text');
+    }
+
+    const textCellController = await makeTextCellController(multiSelectField.field.id, row, databaseController).then(
+      (result) => result.unwrap()
+    );
+    const cellContent = await textCellController.getCellData();
+    if (cellContent.unwrap() !== 'A,B,C') {
+      throw Error('The cell content should be A,B,C, but receive: ' + cellContent.unwrap());
+    }
+
+    await databaseController.dispose();
+  }
+
+  return TestButton('Test switch from multi-select to text column', testSwitchFromMultiSelectToRichText);
+};
+
 export const TestEditField = () => {
   async function testEditField() {
     const view = await createTestDatabaseView(ViewLayoutTypePB.Grid);

+ 9 - 8
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/cell_cache.ts

@@ -35,18 +35,19 @@ export class CellCache {
   };
 
   get<T>(key: CellCacheKey): Option<T> {
-    const inner = this.cellDataByFieldId.get(key.fieldId);
-    if (inner === undefined) {
+    const cellDataByRowId = this.cellDataByFieldId.get(key.fieldId);
+    if (cellDataByRowId === undefined) {
       return None;
     } else {
-      const value = inner.get(key.rowId);
-      if (typeof value === typeof undefined || typeof value === typeof null) {
+      const value = cellDataByRowId.get(key.rowId);
+      if (typeof value === typeof undefined) {
         return None;
       }
-      if (value satisfies T) {
-        return Some(value as T);
-      }
-      return None;
+
+      // if (value satisfies T) {
+      //   return Some(value as T);
+      // }
+      return Some(value);
     }
   }
 }

+ 27 - 33
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/cell_controller.ts

@@ -8,28 +8,29 @@ import { ChangeNotifier } from '../../../../utils/change_notifier';
 import { CellObserver } from './cell_observer';
 import { Log } from '../../../../utils/log';
 import { Err, None, Ok, Option, Some } from 'ts-results';
+import { DatabaseFieldObserver } from '../field/field_observer';
 
-export abstract class CellFieldNotifier {
-  abstract subscribeOnFieldChanged(callback: () => void): void;
-}
+type Callbacks<T> = { onCellChanged: (value: Option<T>) => void; onFieldChanged?: () => void };
 
 export class CellController<T, D> {
   private fieldBackendService: FieldBackendService;
   private cellDataNotifier: CellDataNotifier<Option<T>>;
   private cellObserver: CellObserver;
   private readonly cacheKey: CellCacheKey;
+  private readonly fieldNotifier: DatabaseFieldObserver;
+  private subscribeCallbacks?: Callbacks<T>;
 
   constructor(
     public readonly cellIdentifier: CellIdentifier,
     private readonly cellCache: CellCache,
-    private readonly fieldNotifier: CellFieldNotifier,
     private readonly cellDataLoader: CellDataLoader<T>,
     private readonly cellDataPersistence: CellDataPersistence<D>
   ) {
     this.fieldBackendService = new FieldBackendService(cellIdentifier.viewId, cellIdentifier.fieldId);
-    this.cacheKey = new CellCacheKey(cellIdentifier.rowId, cellIdentifier.fieldId);
+    this.cacheKey = new CellCacheKey(cellIdentifier.fieldId, cellIdentifier.rowId);
     this.cellDataNotifier = new CellDataNotifier(cellCache.get<T>(this.cacheKey));
     this.cellObserver = new CellObserver(cellIdentifier.rowId, cellIdentifier.fieldId);
+    this.fieldNotifier = new DatabaseFieldObserver(cellIdentifier.fieldId);
     void this.cellObserver.subscribe({
       /// 1.Listen on user edit event and load the new cell data if needed.
       /// For example:
@@ -40,21 +41,23 @@ export class CellController<T, D> {
         await this._loadCellData();
       },
     });
-  }
 
-  subscribeChanged = (callbacks: { onCellChanged: (value: Option<T>) => void; onFieldChanged?: () => void }) => {
     /// 2.Listen on the field event and load the cell data if needed.
-    this.fieldNotifier.subscribeOnFieldChanged(async () => {
-      callbacks.onFieldChanged?.();
-
-      /// reloadOnFieldChanged should be true if you need to load the data when the corresponding field is changed
-      /// For example:
-      ///   ¥12 -> $12
-      if (this.cellDataLoader.reloadOnFieldChanged) {
-        await this._loadCellData();
-      }
+    void this.fieldNotifier.subscribe({
+      onFieldChanged: () => {
+        this.subscribeCallbacks?.onFieldChanged?.();
+        /// reloadOnFieldChanged should be true if you need to load the data when the corresponding field is changed
+        /// For example:
+        ///   ¥12 -> $12
+        if (this.cellDataLoader.reloadOnFieldChanged) {
+          void this._loadCellData();
+        }
+      },
     });
+  }
 
+  subscribeChanged = async (callbacks: Callbacks<T>) => {
+    this.subscribeCallbacks = callbacks;
     this.cellDataNotifier.observer.subscribe((cellData) => {
       if (cellData !== null) {
         callbacks.onCellChanged(cellData);
@@ -78,21 +81,21 @@ export class CellController<T, D> {
     }
   };
 
-  /// Return the cell data if it exists in the cache
-  /// If the cell data is not exist, it will load the cell
-  /// data from the backend and then the [onCellChanged] will
-  /// get called
-  getCellData = (): Option<T> => {
+  /// Return the cell data immediately if it exists in the cache
+  /// Otherwise, it will load the cell data from the backend. The
+  /// subscribers of the [onCellChanged] will get noticed
+  getCellData = async (): Promise<Option<T>> => {
     const cellData = this.cellCache.get<T>(this.cacheKey);
     if (cellData.none) {
-      void this._loadCellData();
+      await this._loadCellData();
+      return this.cellCache.get<T>(this.cacheKey);
     }
     return cellData;
   };
 
   private _loadCellData = () => {
     return this.cellDataLoader.loadData().then((result) => {
-      if (result.ok && result.val !== undefined) {
+      if (result.ok) {
         this.cellCache.insert(this.cacheKey, result.val);
         this.cellDataNotifier.cellData = Some(result.val);
       } else {
@@ -104,19 +107,10 @@ export class CellController<T, D> {
 
   dispose = async () => {
     await this.cellObserver.unsubscribe();
+    await this.fieldNotifier.unsubscribe();
   };
 }
 
-export class CellFieldNotifierImpl extends CellFieldNotifier {
-  constructor(private readonly fieldController: FieldController) {
-    super();
-  }
-
-  subscribeOnFieldChanged(callback: () => void): void {
-    this.fieldController.subscribeOnFieldsChanged(callback);
-  }
-}
-
 class CellDataNotifier<T> extends ChangeNotifier<T | null> {
   _cellData: T | null;
 

+ 14 - 11
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/cell_observer.ts

@@ -8,24 +8,27 @@ type UpdateCellNotifiedValue = Result<void, FlowyError>;
 export type CellChangedCallback = (value: UpdateCellNotifiedValue) => void;
 
 export class CellObserver {
-  private _notifier?: ChangeNotifier<UpdateCellNotifiedValue>;
-  private _listener?: DatabaseNotificationObserver;
+  private notifier?: ChangeNotifier<UpdateCellNotifiedValue>;
+  private listener?: DatabaseNotificationObserver;
 
   constructor(public readonly rowId: string, public readonly fieldId: string) {}
 
   subscribe = async (callbacks: { onCellChanged: CellChangedCallback }) => {
-    this._notifier = new ChangeNotifier();
-    this._notifier?.observer.subscribe(callbacks.onCellChanged);
+    this.notifier = new ChangeNotifier();
+    this.notifier?.observer.subscribe(callbacks.onCellChanged);
 
-    this._listener = new DatabaseNotificationObserver({
-      viewId: this.rowId + ':' + this.fieldId,
+    this.listener = new DatabaseNotificationObserver({
+      // The rowId combine with fieldId can identifier the cell.
+      // This format rowId:fieldId is also defined in the backend,
+      // so don't change this.
+      id: this.rowId + ':' + this.fieldId,
       parserHandler: (notification, result) => {
         switch (notification) {
           case DatabaseNotification.DidUpdateCell:
             if (result.ok) {
-              this._notifier?.notify(Ok.EMPTY);
+              this.notifier?.notify(Ok.EMPTY);
             } else {
-              this._notifier?.notify(result);
+              this.notifier?.notify(result);
             }
             return;
           default:
@@ -33,11 +36,11 @@ export class CellObserver {
         }
       },
     });
-    await this._listener.start();
+    await this.listener.start();
   };
 
   unsubscribe = async () => {
-    this._notifier?.unsubscribe();
-    await this._listener?.stop();
+    this.notifier?.unsubscribe();
+    await this.listener?.stop();
   };
 }

+ 9 - 54
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/controller_builder.ts

@@ -1,11 +1,6 @@
-import {
-  DateCellDataPB,
-  FieldType,
-  SelectOptionCellDataPB,
-  URLCellDataPB,
-} from '../../../../../services/backend/models/flowy-database';
+import { DateCellDataPB, FieldType, SelectOptionCellDataPB, URLCellDataPB } from '../../../../../services/backend';
 import { CellIdentifier } from './cell_bd_svc';
-import { CellController, CellFieldNotifierImpl } from './cell_controller';
+import { CellController } from './cell_controller';
 import {
   CellDataLoader,
   DateCellDataParser,
@@ -34,15 +29,11 @@ export class CalendarData {
 export type URLCellController = CellController<URLCellDataPB, string>;
 
 export class CellControllerBuilder {
-  _fieldNotifier: CellFieldNotifierImpl;
-
   constructor(
     public readonly cellIdentifier: CellIdentifier,
     public readonly cellCache: CellCache,
     public readonly fieldController: FieldController
-  ) {
-    this._fieldNotifier = new CellFieldNotifierImpl(this.fieldController);
-  }
+  ) {}
 
   ///
   build = () => {
@@ -68,77 +59,41 @@ export class CellControllerBuilder {
     const loader = new CellDataLoader(this.cellIdentifier, new SelectOptionCellDataParser(), true);
     const persistence = new TextCellDataPersistence(this.cellIdentifier);
 
-    return new CellController<SelectOptionCellDataPB, string>(
-      this.cellIdentifier,
-      this.cellCache,
-      this._fieldNotifier,
-      loader,
-      persistence
-    );
+    return new CellController<SelectOptionCellDataPB, string>(this.cellIdentifier, this.cellCache, loader, persistence);
   };
 
   makeURLCellController = (): URLCellController => {
     const loader = new CellDataLoader(this.cellIdentifier, new URLCellDataParser());
     const persistence = new TextCellDataPersistence(this.cellIdentifier);
 
-    return new CellController<URLCellDataPB, string>(
-      this.cellIdentifier,
-      this.cellCache,
-      this._fieldNotifier,
-      loader,
-      persistence
-    );
+    return new CellController<URLCellDataPB, string>(this.cellIdentifier, this.cellCache, loader, persistence);
   };
 
   makeDateCellController = (): DateCellController => {
     const loader = new CellDataLoader(this.cellIdentifier, new DateCellDataParser(), true);
     const persistence = new DateCellDataPersistence(this.cellIdentifier);
 
-    return new CellController<DateCellDataPB, CalendarData>(
-      this.cellIdentifier,
-      this.cellCache,
-      this._fieldNotifier,
-      loader,
-      persistence
-    );
+    return new CellController<DateCellDataPB, CalendarData>(this.cellIdentifier, this.cellCache, loader, persistence);
   };
 
   makeNumberCellController = (): NumberCellController => {
     const loader = new CellDataLoader(this.cellIdentifier, new StringCellDataParser(), true);
     const persistence = new TextCellDataPersistence(this.cellIdentifier);
 
-    return new CellController<string, string>(
-      this.cellIdentifier,
-      this.cellCache,
-      this._fieldNotifier,
-      loader,
-      persistence
-    );
+    return new CellController<string, string>(this.cellIdentifier, this.cellCache, loader, persistence);
   };
 
   makeTextCellController = (): TextCellController => {
     const loader = new CellDataLoader(this.cellIdentifier, new StringCellDataParser());
     const persistence = new TextCellDataPersistence(this.cellIdentifier);
 
-    return new CellController<string, string>(
-      this.cellIdentifier,
-      this.cellCache,
-      this._fieldNotifier,
-      loader,
-      persistence
-    );
+    return new CellController<string, string>(this.cellIdentifier, this.cellCache, loader, persistence);
   };
 
   makeCheckboxCellController = (): CheckboxCellController => {
     const loader = new CellDataLoader(this.cellIdentifier, new StringCellDataParser());
     const persistence = new TextCellDataPersistence(this.cellIdentifier);
 
-    return new CellController<string, string>(
-      this.cellIdentifier,
-      this.cellCache,
-      this._fieldNotifier,
-      loader,
-      persistence
-    );
+    return new CellController<string, string>(this.cellIdentifier, this.cellCache, loader, persistence);
   };
 }

+ 3 - 3
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/data_parser.ts

@@ -7,11 +7,11 @@ import { Err, Ok } from 'ts-results';
 import { Log } from '../../../../utils/log';
 
 abstract class CellDataParser<T> {
-  abstract parserData(data: Uint8Array): T | undefined;
+  abstract parserData(data: Uint8Array): T;
 }
 
 class CellDataLoader<T> {
-  _service = new CellBackendService();
+  private service = new CellBackendService();
 
   constructor(
     readonly cellId: CellIdentifier,
@@ -20,7 +20,7 @@ class CellDataLoader<T> {
   ) {}
 
   loadData = async () => {
-    const result = await this._service.getCell(this.cellId);
+    const result = await this.service.getCell(this.cellId);
     if (result.ok) {
       return Ok(this.parser.parserData(result.val.data));
     } else {

+ 0 - 2
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/data_persistence.ts

@@ -27,10 +27,8 @@ export class DateCellDataPersistence extends CellDataPersistence<CalendarData> {
 
   save(data: CalendarData): Promise<Result<void, FlowyError>> {
     const payload = DateChangesetPB.fromObject({ cell_path: _makeCellPath(this.cellIdentifier) });
-
     payload.date = data.date.getUTCMilliseconds.toString();
     payload.is_utc = true;
-
     if (data.time !== undefined) {
       payload.time = data.time;
     }

+ 2 - 1
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/select_option_bd_svc.ts

@@ -30,6 +30,7 @@ export class SelectOptionBackendService {
 export class SelectOptionCellBackendService {
   constructor(public readonly cellIdentifier: CellIdentifier) {}
 
+  // Creates a new option and insert this option to the cell
   createOption = async (params: { name: string; isSelect?: boolean }) => {
     const payload = CreateSelectOptionPayloadPB.fromObject({
       option_name: params.name,
@@ -39,7 +40,7 @@ export class SelectOptionCellBackendService {
 
     const result = await DatabaseEventCreateSelectOption(payload);
     if (result.ok) {
-      return this._insertOption(result.val, params.isSelect || true);
+      return await this._insertOption(result.val, params.isSelect || true);
     } else {
       return result;
     }

+ 4 - 4
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/database_controller.ts

@@ -5,7 +5,7 @@ import { DatabasePB } from '../../../../services/backend/models/flowy-database/g
 import { RowChangedReason, RowInfo } from './row/row_cache';
 import { Err, Ok } from 'ts-results';
 
-export type SubscribeCallback = {
+export type SubscribeCallbacks = {
   onViewChanged?: (data: DatabasePB) => void;
   onRowsChanged?: (rowInfos: readonly RowInfo[], reason: RowChangedReason) => void;
   onFieldsChanged?: (fieldInfos: readonly FieldInfo[]) => void;
@@ -15,7 +15,7 @@ export class DatabaseController {
   private backendService: DatabaseBackendService;
   fieldController: FieldController;
   databaseViewCache: DatabaseViewCache;
-  private _callback?: SubscribeCallback;
+  private _callback?: SubscribeCallbacks;
 
   constructor(public readonly viewId: string) {
     this.backendService = new DatabaseBackendService(viewId);
@@ -23,9 +23,9 @@ export class DatabaseController {
     this.databaseViewCache = new DatabaseViewCache(viewId, this.fieldController);
   }
 
-  subscribe = (callbacks: SubscribeCallback) => {
+  subscribe = (callbacks: SubscribeCallbacks) => {
     this._callback = callbacks;
-    this.fieldController.subscribeOnFieldsChanged(callbacks.onFieldsChanged);
+    this.fieldController.subscribeOnNumOfFieldsChanged(callbacks.onFieldsChanged);
     this.databaseViewCache.getRowCache().subscribeOnRowsChanged((reason) => {
       this._callback?.onRowsChanged?.(this.databaseViewCache.rowInfos, reason);
     });

+ 24 - 25
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/field_controller.ts

@@ -1,49 +1,49 @@
 import { Log } from '../../../../utils/log';
 import { DatabaseBackendService } from '../database_bd_svc';
 import { DatabaseFieldChangesetObserver } from './field_observer';
-import { FieldIdPB, FieldPB, IndexFieldPB } from '../../../../../services/backend/models/flowy-database/field_entities';
+import { FieldIdPB, FieldPB, IndexFieldPB } from '../../../../../services/backend';
 import { ChangeNotifier } from '../../../../utils/change_notifier';
 
 export class FieldController {
-  private fieldListener: DatabaseFieldChangesetObserver;
   private backendService: DatabaseBackendService;
-  private fieldNotifier = new FieldNotifier([]);
+  private numOfFieldsObserver: DatabaseFieldChangesetObserver;
+  private numOfFieldsNotifier = new NumOfFieldsNotifier([]);
 
   constructor(public readonly viewId: string) {
     this.backendService = new DatabaseBackendService(viewId);
-    this.fieldListener = new DatabaseFieldChangesetObserver(viewId);
+    this.numOfFieldsObserver = new DatabaseFieldChangesetObserver(viewId);
   }
 
   dispose = async () => {
-    this.fieldNotifier.unsubscribe();
-    await this.fieldListener.unsubscribe();
+    this.numOfFieldsNotifier.unsubscribe();
+    await this.numOfFieldsObserver.unsubscribe();
   };
 
   get fieldInfos(): readonly FieldInfo[] {
-    return this.fieldNotifier.fieldInfos;
+    return this.numOfFieldsNotifier.fieldInfos;
   }
 
   getField = (fieldId: string): FieldInfo | undefined => {
-    return this.fieldNotifier.fieldInfos.find((element) => element.field.id === fieldId);
+    return this.numOfFieldsNotifier.fieldInfos.find((element) => element.field.id === fieldId);
   };
 
   loadFields = async (fieldIds: FieldIdPB[]) => {
     const result = await this.backendService.getFields(fieldIds);
     if (result.ok) {
-      this.fieldNotifier.fieldInfos = result.val.map((field) => new FieldInfo(field));
+      this.numOfFieldsNotifier.fieldInfos = result.val.map((field) => new FieldInfo(field));
     } else {
       Log.error(result.val);
     }
   };
 
-  subscribeOnFieldsChanged = (callback?: (fieldInfos: readonly FieldInfo[]) => void) => {
-    return this.fieldNotifier.observer.subscribe((fieldInfos) => {
+  subscribeOnNumOfFieldsChanged = (callback?: (fieldInfos: readonly FieldInfo[]) => void) => {
+    return this.numOfFieldsNotifier.observer.subscribe((fieldInfos) => {
       callback?.(fieldInfos);
     });
   };
 
   listenOnFieldChanges = async () => {
-    await this.fieldListener.subscribe({
+    await this.numOfFieldsObserver.subscribe({
       onFieldsChanged: (result) => {
         if (result.ok) {
           const changeset = result.val;
@@ -57,7 +57,7 @@ export class FieldController {
     });
   };
 
-  _deleteFields = (deletedFields: FieldIdPB[]) => {
+  private _deleteFields = (deletedFields: FieldIdPB[]) => {
     if (deletedFields.length === 0) {
       return;
     }
@@ -68,10 +68,10 @@ export class FieldController {
     };
     const newFieldInfos = [...this.fieldInfos];
     newFieldInfos.filter(predicate);
-    this.fieldNotifier.fieldInfos = newFieldInfos;
+    this.numOfFieldsNotifier.fieldInfos = newFieldInfos;
   };
 
-  _insertFields = (insertedFields: IndexFieldPB[]) => {
+  private _insertFields = (insertedFields: IndexFieldPB[]) => {
     if (insertedFields.length === 0) {
       return;
     }
@@ -84,29 +84,28 @@ export class FieldController {
         newFieldInfos.push(fieldInfo);
       }
     });
-    this.fieldNotifier.fieldInfos = newFieldInfos;
+    this.numOfFieldsNotifier.fieldInfos = newFieldInfos;
   };
 
-  _updateFields = (updatedFields: FieldPB[]) => {
+  private _updateFields = (updatedFields: FieldPB[]) => {
     if (updatedFields.length === 0) {
       return;
     }
 
     const newFieldInfos = [...this.fieldInfos];
     updatedFields.forEach((updatedField) => {
-      newFieldInfos.map((element) => {
-        if (element.field.id === updatedField.id) {
-          return updatedField;
-        } else {
-          return element;
-        }
+      const index = newFieldInfos.findIndex((fieldInfo) => {
+        return fieldInfo.field.id === updatedField.id;
       });
+      if (index !== -1) {
+        newFieldInfos.splice(index, 1, new FieldInfo(updatedField));
+      }
     });
-    this.fieldNotifier.fieldInfos = newFieldInfos;
+    this.numOfFieldsNotifier.fieldInfos = newFieldInfos;
   };
 }
 
-class FieldNotifier extends ChangeNotifier<FieldInfo[]> {
+class NumOfFieldsNotifier extends ChangeNotifier<FieldInfo[]> {
   constructor(private _fieldInfos: FieldInfo[]) {
     super();
   }

+ 4 - 4
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/field_observer.ts

@@ -17,7 +17,7 @@ export class DatabaseFieldChangesetObserver {
     this.notifier?.observer.subscribe(callbacks.onFieldsChanged);
 
     this.listener = new DatabaseNotificationObserver({
-      viewId: this.viewId,
+      id: this.viewId,
       parserHandler: (notification, result) => {
         switch (notification) {
           case DatabaseNotification.DidUpdateFields:
@@ -50,12 +50,12 @@ export class DatabaseFieldObserver {
 
   constructor(public readonly fieldId: string) {}
 
-  subscribe = async (callbacks: { onFieldsChanged: FieldNotificationCallback }) => {
+  subscribe = async (callbacks: { onFieldChanged: FieldNotificationCallback }) => {
     this._notifier = new ChangeNotifier();
-    this._notifier?.observer.subscribe(callbacks.onFieldsChanged);
+    this._notifier?.observer.subscribe(callbacks.onFieldChanged);
 
     this._listener = new DatabaseNotificationObserver({
-      viewId: this.fieldId,
+      id: this.fieldId,
       parserHandler: (notification, result) => {
         switch (notification) {
           case DatabaseNotification.DidUpdateField:

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/type_option/type_option_context.ts

@@ -172,7 +172,7 @@ export class TypeOptionContext<T> {
     this.fieldObserver = new DatabaseFieldObserver(controller.fieldId);
 
     void this.fieldObserver.subscribe({
-      onFieldsChanged: () => {
+      onFieldChanged: () => {
         void this.getTypeOption();
       },
     });

+ 19 - 5
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/field/type_option/type_option_controller.ts

@@ -13,14 +13,20 @@ export class TypeOptionController {
   private typeOptionBackendSvc: TypeOptionBackendService;
 
   // Must call [initialize] if the passed-in fieldInfo is None
-  constructor(public readonly viewId: string, private initialFieldInfo: Option<FieldInfo> = None) {
+  constructor(
+    public readonly viewId: string,
+    private readonly initialFieldInfo: Option<FieldInfo> = None,
+    private readonly defaultFieldType: FieldType = FieldType.RichText
+  ) {
     this.typeOptionData = None;
     this.typeOptionBackendSvc = new TypeOptionBackendService(viewId);
   }
 
+  // It will create a new field for the defaultFieldType if the [initialFieldInfo] is None.
+  // Otherwise, it will get the type option of the [initialFieldInfo]
   initialize = async () => {
     if (this.initialFieldInfo.none) {
-      await this.createTypeOption();
+      await this.createTypeOption(this.defaultFieldType);
     } else {
       await this.getTypeOption();
     }
@@ -45,8 +51,16 @@ export class TypeOptionController {
     return new FieldInfo(this.typeOptionData.val.field);
   };
 
-  switchToField = (fieldType: FieldType) => {
-    return this.typeOptionBackendSvc.updateTypeOptionType(this.fieldId, fieldType);
+  switchToField = async (fieldType: FieldType) => {
+    const result = await this.typeOptionBackendSvc.updateTypeOptionType(this.fieldId, fieldType);
+    if (result.ok) {
+      const getResult = await this.typeOptionBackendSvc.getTypeOption(this.fieldId, fieldType);
+      if (getResult.ok) {
+        this.updateTypeOptionData(getResult.val);
+      }
+      return getResult;
+    }
+    return result;
   };
 
   setFieldName = async (name: string) => {
@@ -96,7 +110,7 @@ export class TypeOptionController {
     });
   };
 
-  private createTypeOption = (fieldType: FieldType = FieldType.RichText) => {
+  private createTypeOption = (fieldType: FieldType) => {
     return this.typeOptionBackendSvc.createTypeOption(fieldType).then((result) => {
       if (result.ok) {
         this.updateTypeOptionData(result.val);

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/notifications/observer.ts

@@ -6,10 +6,10 @@ import { Result } from 'ts-results';
 export type ParserHandler = (notification: DatabaseNotification, result: Result<Uint8Array, FlowyError>) => void;
 
 export class DatabaseNotificationObserver extends AFNotificationObserver<DatabaseNotification> {
-  constructor(params: { viewId?: string; parserHandler: ParserHandler }) {
+  constructor(params: { id?: string; parserHandler: ParserHandler }) {
     const parser = new DatabaseNotificationParser({
       callback: params.parserHandler,
-      id: params.viewId,
+      id: params.id,
     });
     super(parser);
   }

+ 47 - 39
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/row/row_cache.ts

@@ -19,26 +19,26 @@ import { Log } from '../../../../utils/log';
 export type CellByFieldId = Map<string, CellIdentifier>;
 
 export class RowCache {
-  private readonly _rowList: RowList;
-  private readonly _cellCache: CellCache;
-  private readonly _notifier: RowChangeNotifier;
+  private readonly rowList: RowList;
+  private readonly cellCache: CellCache;
+  private readonly notifier: RowChangeNotifier;
 
   constructor(public readonly viewId: string, private readonly getFieldInfos: () => readonly FieldInfo[]) {
-    this._rowList = new RowList();
-    this._cellCache = new CellCache(viewId);
-    this._notifier = new RowChangeNotifier();
+    this.rowList = new RowList();
+    this.cellCache = new CellCache(viewId);
+    this.notifier = new RowChangeNotifier();
   }
 
   get rows(): readonly RowInfo[] {
-    return this._rowList.rows;
+    return this.rowList.rows;
   }
 
   getCellCache = () => {
-    return this._cellCache;
+    return this.cellCache;
   };
 
   loadCells = async (rowId: string): Promise<CellByFieldId> => {
-    const opRow = this._rowList.getRow(rowId);
+    const opRow = this.rowList.getRow(rowId);
     if (opRow.some) {
       return this._toCellMap(opRow.val.row.id, this.getFieldInfos());
     } else {
@@ -54,7 +54,7 @@ export class RowCache {
   };
 
   subscribeOnRowsChanged = (callback: (reason: RowChangedReason, cellMap?: Map<string, CellIdentifier>) => void) => {
-    return this._notifier.observer.subscribe((change) => {
+    return this.notifier.observer.subscribe((change) => {
       if (change.rowId !== undefined) {
         callback(change.reason, this._toCellMap(change.rowId, this.getFieldInfos()));
       } else {
@@ -65,18 +65,19 @@ export class RowCache {
 
   onFieldUpdated = (fieldInfo: FieldInfo) => {
     // Remove the cell data if the corresponding field was changed
-    this._cellCache.removeWithFieldId(fieldInfo.field.id);
+    this.cellCache.removeWithFieldId(fieldInfo.field.id);
   };
 
-  onNumberOfFieldsUpdated = () => {
-    this._notifier.withChange(RowChangedReason.FieldDidChanged);
+  onNumberOfFieldsUpdated = (fieldInfos: readonly FieldInfo[]) => {
+    this.rowList.setFieldInfos(fieldInfos);
+    this.notifier.withChange(RowChangedReason.FieldDidChanged);
   };
 
   initializeRows = (rows: RowPB[]) => {
     rows.forEach((rowPB) => {
-      this._rowList.push(this._toRowInfo(rowPB));
+      this.rowList.push(this._toRowInfo(rowPB));
     });
-    this._notifier.withChange(RowChangedReason.ReorderRows);
+    this.notifier.withChange(RowChangedReason.ReorderRows);
   };
 
   applyRowsChanged = (changeset: RowsChangesetPB) => {
@@ -91,15 +92,15 @@ export class RowCache {
   };
 
   applyReorderRows = (rowIds: string[]) => {
-    this._rowList.reorderByRowIds(rowIds);
-    this._notifier.withChange(RowChangedReason.ReorderRows);
+    this.rowList.reorderByRowIds(rowIds);
+    this.notifier.withChange(RowChangedReason.ReorderRows);
   };
 
   applyReorderSingleRow = (reorderRow: ReorderSingleRowPB) => {
-    const rowInfo = this._rowList.getRow(reorderRow.row_id);
+    const rowInfo = this.rowList.getRow(reorderRow.row_id);
     if (rowInfo !== undefined) {
-      this._rowList.move({ rowId: reorderRow.row_id, fromIndex: reorderRow.old_index, toIndex: reorderRow.new_index });
-      this._notifier.withChange(RowChangedReason.ReorderSingleRow, reorderRow.row_id);
+      this.rowList.move({ rowId: reorderRow.row_id, fromIndex: reorderRow.old_index, toIndex: reorderRow.new_index });
+      this.notifier.withChange(RowChangedReason.ReorderSingleRow, reorderRow.row_id);
     }
   };
 
@@ -108,14 +109,14 @@ export class RowCache {
       return;
     }
     const updatedRow = opRow.row;
-    const option = this._rowList.getRowWithIndex(updatedRow.id);
+    const option = this.rowList.getRowWithIndex(updatedRow.id);
     if (option.some) {
       const { rowInfo, index } = option.val;
-      this._rowList.remove(rowInfo.row.id);
-      this._rowList.insert(index, rowInfo.copyWith({ row: updatedRow }));
+      this.rowList.remove(rowInfo.row.id);
+      this.rowList.insert(index, rowInfo.copyWith({ row: updatedRow }));
     } else {
       const newRowInfo = new RowInfo(this.viewId, this.getFieldInfos(), updatedRow);
-      this._rowList.push(newRowInfo);
+      this.rowList.push(newRowInfo);
     }
   };
 
@@ -126,9 +127,9 @@ export class RowCache {
 
   private _deleteRows = (rowIds: string[]) => {
     rowIds.forEach((rowId) => {
-      const deletedRow = this._rowList.remove(rowId);
+      const deletedRow = this.rowList.remove(rowId);
       if (deletedRow !== undefined) {
-        this._notifier.withChange(RowChangedReason.Delete, deletedRow.rowInfo.row.id);
+        this.notifier.withChange(RowChangedReason.Delete, deletedRow.rowInfo.row.id);
       }
     });
   };
@@ -136,9 +137,9 @@ export class RowCache {
   private _insertRows = (rows: InsertedRowPB[]) => {
     rows.forEach((insertedRow) => {
       const rowInfo = this._toRowInfo(insertedRow.row);
-      const insertedIndex = this._rowList.insert(insertedRow.index, rowInfo);
+      const insertedIndex = this.rowList.insert(insertedRow.index, rowInfo);
       if (insertedIndex !== undefined) {
-        this._notifier.withChange(RowChangedReason.Insert, insertedIndex.rowId);
+        this.notifier.withChange(RowChangedReason.Insert, insertedIndex.rowId);
       }
     });
   };
@@ -152,39 +153,39 @@ export class RowCache {
     updatedRows.forEach((updatedRow) => {
       updatedRow.field_ids.forEach((fieldId) => {
         const key = new CellCacheKey(fieldId, updatedRow.row.id);
-        this._cellCache.remove(key);
+        this.cellCache.remove(key);
       });
 
       rowInfos.push(this._toRowInfo(updatedRow.row));
     });
 
-    const updatedIndexs = this._rowList.insertRows(rowInfos);
+    const updatedIndexs = this.rowList.insertRows(rowInfos);
     updatedIndexs.forEach((row) => {
-      this._notifier.withChange(RowChangedReason.Update, row.rowId);
+      this.notifier.withChange(RowChangedReason.Update, row.rowId);
     });
   };
 
   private _hideRows = (rowIds: string[]) => {
     rowIds.forEach((rowId) => {
-      const deletedRow = this._rowList.remove(rowId);
+      const deletedRow = this.rowList.remove(rowId);
       if (deletedRow !== undefined) {
-        this._notifier.withChange(RowChangedReason.Delete, deletedRow.rowInfo.row.id);
+        this.notifier.withChange(RowChangedReason.Delete, deletedRow.rowInfo.row.id);
       }
     });
   };
 
   private _displayRows = (insertedRows: InsertedRowPB[]) => {
     insertedRows.forEach((insertedRow) => {
-      const insertedIndex = this._rowList.insert(insertedRow.index, this._toRowInfo(insertedRow.row));
+      const insertedIndex = this.rowList.insert(insertedRow.index, this._toRowInfo(insertedRow.row));
 
       if (insertedIndex !== undefined) {
-        this._notifier.withChange(RowChangedReason.Insert, insertedIndex.rowId);
+        this.notifier.withChange(RowChangedReason.Insert, insertedIndex.rowId);
       }
     });
   };
 
   dispose = async () => {
-    this._notifier.dispose();
+    this.notifier.dispose();
   };
 
   private _toRowInfo = (rowPB: RowPB) => {
@@ -219,7 +220,6 @@ class RowList {
       return Some(rowInfo);
     }
   };
-
   getRowWithIndex = (rowId: string): Option<{ rowInfo: RowInfo; index: number }> => {
     const rowInfo = this._rowInfoByRowId.get(rowId);
     if (rowInfo !== undefined) {
@@ -322,6 +322,14 @@ class RowList {
   includes = (rowId: string): boolean => {
     return this._rowInfoByRowId.has(rowId);
   };
+
+  setFieldInfos = (fieldInfos: readonly FieldInfo[]) => {
+    const newRowInfos: RowInfo[] = [];
+    this._rowInfos.forEach((rowInfo) => {
+      newRowInfos.push(rowInfo.copyWith({ fieldInfos: fieldInfos }));
+    });
+    this._rowInfos = newRowInfos;
+  };
 }
 
 export class RowInfo {
@@ -331,8 +339,8 @@ export class RowInfo {
     public readonly row: RowPB
   ) {}
 
-  copyWith = (params: { row?: RowPB }) => {
-    return new RowInfo(this.viewId, this.fieldInfos, params.row || this.row);
+  copyWith = (params: { row?: RowPB; fieldInfos?: readonly FieldInfo[] }) => {
+    return new RowInfo(this.viewId, params.fieldInfos || this.fieldInfos, params.row || this.row);
   };
 }
 

+ 2 - 1
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/view/database_view_cache.ts

@@ -12,10 +12,11 @@ export class DatabaseViewCache {
   constructor(public readonly viewId: string, fieldController: FieldController) {
     this.rowsObserver = new DatabaseViewRowsObserver(viewId);
     this.rowCache = new RowCache(viewId, () => fieldController.fieldInfos);
-    this.fieldSubscription = fieldController.subscribeOnFieldsChanged((fieldInfos) => {
+    this.fieldSubscription = fieldController.subscribeOnNumOfFieldsChanged((fieldInfos) => {
       fieldInfos.forEach((fieldInfo) => {
         this.rowCache.onFieldUpdated(fieldInfo);
       });
+      this.rowCache.onNumberOfFieldsUpdated(fieldInfos);
     });
   }
 

+ 1 - 1
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/view/view_row_observer.ts

@@ -38,7 +38,7 @@ export class DatabaseViewRowsObserver {
     this.reorderSingleRowNotifier.observer.subscribe(callbacks.onReorderSingleRow);
 
     this._listener = new DatabaseNotificationObserver({
-      viewId: this.viewId,
+      id: this.viewId,
       parserHandler: (notification, result) => {
         switch (notification) {
           case DatabaseNotification.DidUpdateViewRowsVisibility:

+ 1 - 0
frontend/rust-lib/flowy-codegen/src/ts_event/event_template.tera

@@ -27,6 +27,7 @@ export async function {{ event_func_name }}(): Promise<Result<{{ output_deserial
         console.log({{ event_func_name }}.name, object);
         return Ok(object);
     {%- else %}
+        console.log({{ event_func_name }}.name);
         return Ok.EMPTY;
     {%- endif %}
     } else {

+ 21 - 25
frontend/rust-lib/flowy-database/src/manager.rs

@@ -31,7 +31,7 @@ use flowy_task::TaskDispatcher;
 
 use revision_model::Revision;
 use std::sync::Arc;
-use tokio::sync::RwLock;
+use tokio::sync::{RwLock, RwLockWriteGuard, TryLockError};
 
 pub trait DatabaseUser: Send + Sync {
   fn user_id(&self) -> Result<String, FlowyError>;
@@ -118,7 +118,6 @@ impl DatabaseManager {
     Ok(())
   }
 
-  #[tracing::instrument(level = "debug", skip_all, err)]
   pub async fn create_database_block<T: AsRef<str>>(
     &self,
     block_id: T,
@@ -141,33 +140,32 @@ impl DatabaseManager {
       .await
   }
 
+  #[tracing::instrument(level = "debug", skip_all)]
   pub async fn close_database_view<T: AsRef<str>>(&self, view_id: T) -> FlowyResult<()> {
     let view_id = view_id.as_ref();
     let database_info = self.database_ref_indexer.get_database_with_view(view_id)?;
     tracing::Span::current().record("database_id", &database_info.database_id);
 
-    let mut should_remove_editor = false;
-    if let Some(database_editor) = self
-      .editors_by_database_id
-      .write()
-      .await
-      .get(&database_info.database_id)
-    {
-      database_editor.close_view_editor(view_id).await;
-      should_remove_editor = database_editor.number_of_ref_views().await == 0;
-      if should_remove_editor {
-        database_editor.dispose().await;
-      }
+    match self.editors_by_database_id.try_write() {
+      Ok(mut write_guard) => {
+        if let Some(database_editor) = write_guard.remove(&database_info.database_id) {
+          database_editor.close_view_editor(view_id).await;
+          if database_editor.number_of_ref_views().await == 0 {
+            database_editor.dispose().await;
+          } else {
+            self
+              .editors_by_database_id
+              .write()
+              .await
+              .insert(database_info.database_id, database_editor);
+          }
+        }
+      },
+      Err(_) => {
+        tracing::error!("Try to get the lock of editors_by_database_id failed");
+      },
     }
 
-    if should_remove_editor {
-      tracing::debug!("Close database base editor: {}", database_info.database_id);
-      self
-        .editors_by_database_id
-        .write()
-        .await
-        .remove(&database_info.database_id);
-    }
     Ok(())
   }
 
@@ -235,12 +233,10 @@ impl DatabaseManager {
     pool: Arc<ConnectionPool>,
   ) -> Result<Arc<DatabaseEditor>, FlowyError> {
     let user = self.database_user.clone();
-    tracing::debug!("Open database view: {}", view_id);
     let (base_view_pad, base_view_rev_manager) =
       make_database_view_revision_pad(view_id, user.clone()).await?;
     let mut database_id = base_view_pad.database_id.clone();
-
-    tracing::debug!("Open database: {}", database_id);
+    tracing::debug!("Open database: {} with view: {}", database_id, view_id);
     if database_id.is_empty() {
       // Before the database_id concept comes up, we used the view_id directly. So if
       // the database_id is empty, which means we can used the view_id. After the version 0.1.1,

+ 2 - 2
frontend/rust-lib/flowy-database/src/services/database/database_editor.rs

@@ -116,13 +116,13 @@ impl DatabaseEditor {
     self.database_views.open(view_editor).await
   }
 
-  #[tracing::instrument(name = "Close database editor view", level = "debug", skip_all)]
+  #[tracing::instrument(level = "debug", skip_all)]
   pub async fn close_view_editor(&self, view_id: &str) {
-    self.rev_manager.generate_snapshot().await;
     self.database_views.close(view_id).await;
   }
 
   pub async fn dispose(&self) {
+    self.rev_manager.generate_snapshot().await;
     self.database_blocks.close().await;
     self.rev_manager.close().await;
   }

+ 4 - 4
frontend/rust-lib/flowy-database/src/services/database_view/editor.rs

@@ -178,12 +178,12 @@ impl DatabaseViewEditor {
     .await
   }
 
-  #[tracing::instrument(name = "close grid view editor", level = "trace", skip_all)]
+  #[tracing::instrument(name = "close database view editor", level = "trace", skip_all)]
   pub async fn close(&self) {
     self.rev_manager.generate_snapshot().await;
     self.rev_manager.close().await;
     self.filter_controller.close().await;
-    self.sort_controller.read().await.close().await;
+    self.sort_controller.write().await.close().await;
   }
 
   pub async fn handle_block_event(&self, event: Cow<'_, DatabaseBlockEvent>) {
@@ -509,8 +509,8 @@ impl DatabaseViewEditor {
         .did_receive_changes(SortChangeset::from_insert(sort_type))
         .await
     };
-    self.notify_did_update_sort(changeset).await;
     drop(sort_controller);
+    self.notify_did_update_sort(changeset).await;
     Ok(sort_rev)
   }
 
@@ -539,7 +539,7 @@ impl DatabaseViewEditor {
 
   pub async fn v_delete_all_sorts(&self) -> FlowyResult<()> {
     let all_sorts = self.v_get_all_sorts().await;
-    self.sort_controller.write().await.delete_all_sorts().await;
+    // self.sort_controller.write().await.delete_all_sorts().await;
     self
       .modify(|pad| {
         let changeset = pad.delete_all_sorts()?;

+ 5 - 1
frontend/rust-lib/flowy-database/src/services/database_view/editor_manager.rs

@@ -60,7 +60,11 @@ impl DatabaseViews {
   }
 
   pub async fn close(&self, view_id: &str) {
-    self.view_editors.write().await.remove(view_id).await;
+    if let Ok(mut view_editors) = self.view_editors.try_write() {
+      view_editors.remove(view_id).await;
+    } else {
+      tracing::error!("Try to get the lock of view_editors failed");
+    }
   }
 
   pub async fn number_of_views(&self) -> usize {

+ 1 - 0
frontend/rust-lib/flowy-database/src/services/field/type_options/type_option_cell.rs

@@ -289,6 +289,7 @@ where
     decoded_field_type: &FieldType,
     field_rev: &FieldRevision,
   ) -> FlowyResult<BoxCellData> {
+    // tracing::debug!("get_cell_data: {:?}", std::any::type_name::<Self>());
     let cell_data = if self.transformable() {
       match self.transform_type_option_cell_str(&cell_str, decoded_field_type, field_rev) {
         None => self.get_decoded_cell_data(cell_str, decoded_field_type, field_rev)?,

+ 20 - 13
frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/block_impl.rs

@@ -22,7 +22,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
 
   fn create_revision_records(&self, revision_records: Vec<SyncRecord>) -> Result<(), Self::Error> {
     let conn = self.pool.get().map_err(internal_error)?;
-    GridMetaRevisionSql::create(revision_records, &conn)?;
+    DatabaseBlockMetaRevisionSql::create(revision_records, &conn)?;
     Ok(())
   }
 
@@ -36,7 +36,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
     rev_ids: Option<Vec<i64>>,
   ) -> Result<Vec<SyncRecord>, Self::Error> {
     let conn = self.pool.get().map_err(internal_error)?;
-    let records = GridMetaRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?;
+    let records = DatabaseBlockMetaRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?;
     Ok(records)
   }
 
@@ -47,7 +47,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
   ) -> Result<Vec<SyncRecord>, Self::Error> {
     let conn = &*self.pool.get().map_err(internal_error)?;
     let revisions =
-      GridMetaRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?;
+      DatabaseBlockMetaRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?;
     Ok(revisions)
   }
 
@@ -55,7 +55,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
     let conn = &*self.pool.get().map_err(internal_error)?;
     conn.immediate_transaction::<_, FlowyError, _>(|| {
       for changeset in changesets {
-        GridMetaRevisionSql::update(changeset, conn)?;
+        DatabaseBlockMetaRevisionSql::update(changeset, conn)?;
       }
       Ok(())
     })?;
@@ -68,7 +68,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
     rev_ids: Option<Vec<i64>>,
   ) -> Result<(), Self::Error> {
     let conn = &*self.pool.get().map_err(internal_error)?;
-    GridMetaRevisionSql::delete(object_id, rev_ids, conn)?;
+    DatabaseBlockMetaRevisionSql::delete(object_id, rev_ids, conn)?;
     Ok(())
   }
 
@@ -80,8 +80,8 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseBlockRevisionPersi
   ) -> Result<(), Self::Error> {
     let conn = self.pool.get().map_err(internal_error)?;
     conn.immediate_transaction::<_, FlowyError, _>(|| {
-      GridMetaRevisionSql::delete(object_id, deleted_rev_ids, &conn)?;
-      GridMetaRevisionSql::create(inserted_records, &conn)?;
+      DatabaseBlockMetaRevisionSql::delete(object_id, deleted_rev_ids, &conn)?;
+      DatabaseBlockMetaRevisionSql::create(inserted_records, &conn)?;
       Ok(())
     })
   }
@@ -96,8 +96,8 @@ impl SQLiteDatabaseBlockRevisionPersistence {
   }
 }
 
-struct GridMetaRevisionSql();
-impl GridMetaRevisionSql {
+struct DatabaseBlockMetaRevisionSql();
+impl DatabaseBlockMetaRevisionSql {
   fn create(revision_records: Vec<SyncRecord>, conn: &SqliteConnection) -> Result<(), FlowyError> {
     // Batch insert: https://diesel.rs/guides/all-about-inserts.html
 
@@ -105,7 +105,8 @@ impl GridMetaRevisionSql {
       .into_iter()
       .map(|record| {
         tracing::trace!(
-          "[GridMetaRevisionSql] create revision: {}:{:?}",
+          "[{}] create revision: {}:{:?}",
+          std::any::type_name::<Self>(),
           record.revision.object_id,
           record.revision.rev_id
         );
@@ -133,7 +134,8 @@ impl GridMetaRevisionSql {
       .filter(dsl::object_id.eq(changeset.object_id));
     let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?;
     tracing::debug!(
-      "[GridMetaRevisionSql] update revision:{} state:to {:?}",
+      "[{}] update revision:{} state:to {:?}",
+      std::any::type_name::<Self>(),
       changeset.rev_id,
       changeset.state
     );
@@ -193,7 +195,8 @@ impl GridMetaRevisionSql {
 
     if let Some(rev_ids) = rev_ids {
       tracing::trace!(
-        "[GridMetaRevisionSql] Delete revision: {}:{:?}",
+        "[{}] Delete revision: {}:{:?}",
+        std::any::type_name::<Self>(),
         object_id,
         rev_ids
       );
@@ -201,7 +204,11 @@ impl GridMetaRevisionSql {
     }
 
     let affected_row = sql.execute(conn)?;
-    tracing::trace!("[GridMetaRevisionSql] Delete {} rows", affected_row);
+    tracing::trace!(
+      "[{}] Delete {} rows",
+      std::any::type_name::<Self>(),
+      affected_row
+    );
     Ok(())
   }
 }

+ 11 - 4
frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/database_impl.rs

@@ -104,7 +104,8 @@ impl DatabaseRevisionSql {
       .into_iter()
       .map(|record| {
         tracing::trace!(
-          "[GridRevisionSql] create revision: {}:{:?}",
+          "[{}] create revision: {}:{:?}",
+          std::any::type_name::<Self>(),
           record.revision.object_id,
           record.revision.rev_id
         );
@@ -132,7 +133,8 @@ impl DatabaseRevisionSql {
       .filter(dsl::object_id.eq(changeset.object_id));
     let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?;
     tracing::debug!(
-      "[GridRevisionSql] update revision:{} state:to {:?}",
+      "[{}] update revision:{} state:to {:?}",
+      std::any::type_name::<Self>(),
       changeset.rev_id,
       changeset.state
     );
@@ -192,7 +194,8 @@ impl DatabaseRevisionSql {
 
     if let Some(rev_ids) = rev_ids {
       tracing::trace!(
-        "[GridRevisionSql] Delete revision: {}:{:?}",
+        "[{}] Delete revision: {}:{:?}",
+        std::any::type_name::<Self>(),
         object_id,
         rev_ids
       );
@@ -200,7 +203,11 @@ impl DatabaseRevisionSql {
     }
 
     let affected_row = sql.execute(conn)?;
-    tracing::trace!("[GridRevisionSql] Delete {} rows", affected_row);
+    tracing::trace!(
+      "[{}] Delete {} rows",
+      std::any::type_name::<Self>(),
+      affected_row
+    );
     Ok(())
   }
 }

+ 20 - 13
frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/view_impl.rs

@@ -31,7 +31,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
 
   fn create_revision_records(&self, revision_records: Vec<SyncRecord>) -> Result<(), Self::Error> {
     let conn = self.pool.get().map_err(internal_error)?;
-    GridViewRevisionSql::create(revision_records, &conn)?;
+    DatabaseViewRevisionSql::create(revision_records, &conn)?;
     Ok(())
   }
 
@@ -45,7 +45,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
     rev_ids: Option<Vec<i64>>,
   ) -> Result<Vec<SyncRecord>, Self::Error> {
     let conn = self.pool.get().map_err(internal_error)?;
-    let records = GridViewRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?;
+    let records = DatabaseViewRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?;
     Ok(records)
   }
 
@@ -56,7 +56,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
   ) -> Result<Vec<SyncRecord>, Self::Error> {
     let conn = &*self.pool.get().map_err(internal_error)?;
     let revisions =
-      GridViewRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?;
+      DatabaseViewRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?;
     Ok(revisions)
   }
 
@@ -64,7 +64,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
     let conn = &*self.pool.get().map_err(internal_error)?;
     conn.immediate_transaction::<_, FlowyError, _>(|| {
       for changeset in changesets {
-        GridViewRevisionSql::update(changeset, conn)?;
+        DatabaseViewRevisionSql::update(changeset, conn)?;
       }
       Ok(())
     })?;
@@ -77,7 +77,7 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
     rev_ids: Option<Vec<i64>>,
   ) -> Result<(), Self::Error> {
     let conn = &*self.pool.get().map_err(internal_error)?;
-    GridViewRevisionSql::delete(object_id, rev_ids, conn)?;
+    DatabaseViewRevisionSql::delete(object_id, rev_ids, conn)?;
     Ok(())
   }
 
@@ -89,22 +89,23 @@ impl RevisionDiskCache<Arc<ConnectionPool>> for SQLiteDatabaseViewRevisionPersis
   ) -> Result<(), Self::Error> {
     let conn = self.pool.get().map_err(internal_error)?;
     conn.immediate_transaction::<_, FlowyError, _>(|| {
-      GridViewRevisionSql::delete(object_id, deleted_rev_ids, &conn)?;
-      GridViewRevisionSql::create(inserted_records, &conn)?;
+      DatabaseViewRevisionSql::delete(object_id, deleted_rev_ids, &conn)?;
+      DatabaseViewRevisionSql::create(inserted_records, &conn)?;
       Ok(())
     })
   }
 }
 
-struct GridViewRevisionSql();
-impl GridViewRevisionSql {
+struct DatabaseViewRevisionSql();
+impl DatabaseViewRevisionSql {
   fn create(revision_records: Vec<SyncRecord>, conn: &SqliteConnection) -> Result<(), FlowyError> {
     // Batch insert: https://diesel.rs/guides/all-about-inserts.html
     let records = revision_records
       .into_iter()
       .map(|record| {
         tracing::trace!(
-          "[GridViewRevisionSql] create revision: {}:{:?}",
+          "[{}] create revision: {}:{:?}",
+          std::any::type_name::<Self>(),
           record.revision.object_id,
           record.revision.rev_id
         );
@@ -132,7 +133,8 @@ impl GridViewRevisionSql {
       .filter(dsl::object_id.eq(changeset.object_id));
     let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?;
     tracing::debug!(
-      "[GridViewRevisionSql] update revision:{} state:to {:?}",
+      "[{}] update revision:{} state:to {:?}",
+      std::any::type_name::<Self>(),
       changeset.rev_id,
       changeset.state
     );
@@ -192,7 +194,8 @@ impl GridViewRevisionSql {
 
     if let Some(rev_ids) = rev_ids {
       tracing::trace!(
-        "[GridViewRevisionSql] Delete revision: {}:{:?}",
+        "[{}] Delete revision: {}:{:?}",
+        std::any::type_name::<Self>(),
         object_id,
         rev_ids
       );
@@ -200,7 +203,11 @@ impl GridViewRevisionSql {
     }
 
     let affected_row = sql.execute(conn)?;
-    tracing::trace!("[GridViewRevisionSql] Delete {} rows", affected_row);
+    tracing::trace!(
+      "[{}] Delete {} rows",
+      std::any::type_name::<Self>(),
+      affected_row
+    );
     Ok(())
   }
 }

+ 5 - 6
frontend/rust-lib/flowy-database/src/services/sort/controller.rs

@@ -63,12 +63,11 @@ impl SortController {
   }
 
   pub async fn close(&self) {
-    self
-      .task_scheduler
-      .write()
-      .await
-      .unregister_handler(&self.handler_id)
-      .await;
+    if let Ok(mut task_scheduler) = self.task_scheduler.try_write() {
+      // task_scheduler.unregister_handler(&self.handler_id).await;
+    } else {
+      tracing::error!("Try to get the lock of task_scheduler failed");
+    }
   }
 
   pub async fn did_receive_row_changed(&self, row_id: &str) {

+ 11 - 4
frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_rev_sqlite.rs

@@ -106,7 +106,8 @@ impl FolderRevisionSql {
       .into_iter()
       .map(|record| {
         tracing::trace!(
-          "[TextRevisionSql] create revision: {}:{:?}",
+          "[{}] create revision: {}:{:?}",
+          std::any::type_name::<Self>(),
           record.revision.object_id,
           record.revision.rev_id
         );
@@ -135,7 +136,8 @@ impl FolderRevisionSql {
       .filter(dsl::doc_id.eq(changeset.object_id));
     let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?;
     tracing::debug!(
-      "[TextRevisionSql] update revision:{} state:to {:?}",
+      "[{}] update revision:{} state:to {:?}",
+      std::any::type_name::<Self>(),
       changeset.rev_id,
       changeset.state
     );
@@ -193,7 +195,8 @@ impl FolderRevisionSql {
 
     if let Some(rev_ids) = rev_ids {
       tracing::trace!(
-        "[TextRevisionSql] Delete revision: {}:{:?}",
+        "[{}] Delete revision: {}:{:?}",
+        std::any::type_name::<Self>(),
         object_id,
         rev_ids
       );
@@ -201,7 +204,11 @@ impl FolderRevisionSql {
     }
 
     let affected_row = sql.execute(conn)?;
-    tracing::trace!("[TextRevisionSql] Delete {} rows", affected_row);
+    tracing::trace!(
+      "[{}] Delete {} rows",
+      std::any::type_name::<Self>(),
+      affected_row
+    );
     Ok(())
   }
 }

+ 20 - 29
frontend/rust-lib/flowy-task/src/scheduler.rs

@@ -5,6 +5,7 @@ use anyhow::Error;
 use lib_infra::async_trait::async_trait;
 use lib_infra::future::BoxResultFuture;
 use lib_infra::ref_map::{RefCountHashMap, RefCountValue};
+use std::collections::HashMap;
 use std::sync::Arc;
 use std::time::Duration;
 
@@ -15,7 +16,7 @@ pub struct TaskDispatcher {
   queue: TaskQueue,
   store: TaskStore,
   timeout: Duration,
-  handlers: RefCountHashMap<RefCountTaskHandler>,
+  handlers: HashMap<String, Arc<dyn TaskHandler>>,
 
   notifier: watch::Sender<bool>,
   pub(crate) notifier_rx: Option<watch::Receiver<bool>>,
@@ -28,7 +29,7 @@ impl TaskDispatcher {
       queue: TaskQueue::new(),
       store: TaskStore::new(),
       timeout,
-      handlers: RefCountHashMap::new(),
+      handlers: HashMap::new(),
       notifier,
       notifier_rx: Some(notifier_rx),
     }
@@ -39,13 +40,17 @@ impl TaskDispatcher {
     T: TaskHandler,
   {
     let handler_id = handler.handler_id().to_owned();
-    self
-      .handlers
-      .insert(handler_id, RefCountTaskHandler(Arc::new(handler)));
+    self.handlers.insert(handler_id, Arc::new(handler));
   }
 
   pub async fn unregister_handler<T: AsRef<str>>(&mut self, handler_id: T) {
-    self.handlers.remove(handler_id.as_ref()).await;
+    if let Some(handler) = self.handlers.remove(handler_id.as_ref()) {
+      tracing::trace!(
+        "{}:{} is unregistered",
+        handler.handler_name(),
+        handler.handler_id()
+      );
+    }
   }
 
   pub fn stop(&mut self) {
@@ -54,6 +59,7 @@ impl TaskDispatcher {
     self.store.clear();
   }
 
+  #[tracing::instrument(level = "trace", skip_all)]
   pub(crate) async fn process_next_task(&mut self) -> Option<()> {
     let pending_task = self.queue.mut_head(|list| list.pop())?;
     let mut task = self.store.remove_task(&pending_task.id)?;
@@ -69,25 +75,25 @@ impl TaskDispatcher {
     let content = task.content.take()?;
     if let Some(handler) = self.handlers.get(&task.handler_id) {
       task.set_state(TaskState::Processing);
-      tracing::trace!(
-        "Run {} task with content: {:?}",
-        handler.handler_name(),
-        content
-      );
+      tracing::trace!("{} task is running", handler.handler_name(),);
       match tokio::time::timeout(self.timeout, handler.run(content)).await {
         Ok(result) => match result {
-          Ok(_) => task.set_state(TaskState::Done),
+          Ok(_) => {
+            tracing::trace!("{} task is done", handler.handler_name(),);
+            task.set_state(TaskState::Done)
+          },
           Err(e) => {
-            tracing::error!("Process {} task failed: {:?}", handler.handler_name(), e);
+            tracing::error!("{} task is failed: {:?}", handler.handler_name(), e);
             task.set_state(TaskState::Failure);
           },
         },
         Err(e) => {
-          tracing::error!("Process {} task timeout: {:?}", handler.handler_name(), e);
+          tracing::error!("{} task is timeout: {:?}", handler.handler_name(), e);
           task.set_state(TaskState::Timeout);
         },
       }
     } else {
+      tracing::trace!("{} is cancel", task.handler_id);
       task.set_state(TaskState::Cancel);
     }
     let _ = ret.send(task.into());
@@ -197,18 +203,3 @@ where
     (**self).run(content)
   }
 }
-#[derive(Clone)]
-struct RefCountTaskHandler(Arc<dyn TaskHandler>);
-
-#[async_trait]
-impl RefCountValue for RefCountTaskHandler {
-  async fn did_remove(&self) {}
-}
-
-impl std::ops::Deref for RefCountTaskHandler {
-  type Target = Arc<dyn TaskHandler>;
-
-  fn deref(&self) -> &Self::Target {
-    &self.0
-  }
-}