From 6bb1c4e89ce84db19f24499aa3caf09bcad9077c Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Mon, 13 Feb 2023 09:29:49 +0800 Subject: [PATCH] feat: run rustfmt with custom defined fmt configuration (#1848) * chore: update rustfmt * chore: apply rustfmt format --- frontend/appflowy_tauri/src-tauri/build.rs | 2 +- .../appflowy_tauri/src-tauri/rustfmt.toml | 12 + frontend/appflowy_tauri/src-tauri/src/init.rs | 12 +- frontend/appflowy_tauri/src-tauri/src/main.rs | 52 +- .../src-tauri/src/notification.rs | 21 +- .../appflowy_tauri/src-tauri/src/request.rs | 40 +- frontend/rust-lib/dart-ffi/build.rs | 2 +- frontend/rust-lib/dart-ffi/src/c.rs | 26 +- frontend/rust-lib/dart-ffi/src/lib.rs | 153 +- .../dart-ffi/src/model/ffi_request.rs | 26 +- .../dart-ffi/src/model/ffi_response.rs | 50 +- .../dart-ffi/src/notification/sender.rs | 20 +- frontend/rust-lib/flowy-ast/src/ast.rs | 451 +++-- frontend/rust-lib/flowy-ast/src/ctxt.rs | 51 +- .../rust-lib/flowy-ast/src/event_attrs.rs | 237 +-- frontend/rust-lib/flowy-ast/src/node_attrs.rs | 167 +- frontend/rust-lib/flowy-ast/src/pb_attrs.rs | 693 ++++--- frontend/rust-lib/flowy-ast/src/symbol.rs | 30 +- frontend/rust-lib/flowy-ast/src/ty_ext.rs | 231 +-- .../src/client_database/block_revision_pad.rs | 780 +++---- .../src/client_database/database_builder.rs | 97 +- .../client_database/database_revision_pad.rs | 753 +++---- .../src/client_database/view_revision_pad.rs | 595 +++--- .../src/client_document/document_pad.rs | 437 ++-- .../extensions/delete/default_delete.rs | 26 +- .../delete/preserve_line_format_merge.rs | 105 +- .../extensions/format/resolve_block_format.rs | 94 +- .../format/resolve_inline_format.rs | 70 +- .../src/client_document/extensions/helper.rs | 66 +- .../extensions/insert/auto_exit_block.rs | 98 +- .../extensions/insert/auto_format.rs | 128 +- .../extensions/insert/default_insert.rs | 80 +- .../client_document/extensions/insert/mod.rs | 48 +- .../insert/preserve_block_format.rs | 110 +- .../insert/preserve_inline_format.rs | 182 +- .../insert/reset_format_on_new_line.rs | 78 +- .../src/client_document/extensions/mod.rs | 34 +- .../src/client_document/history.rs | 116 +- .../src/client_document/view.rs | 175 +- .../src/client_folder/builder.rs | 58 +- .../src/client_folder/folder_node.rs | 173 +- .../src/client_folder/folder_pad.rs | 1350 ++++++------ .../src/client_folder/trash_node.rs | 16 +- .../src/client_folder/util.rs | 88 +- .../src/client_folder/workspace_node.rs | 66 +- .../rust-lib/flowy-client-sync/src/lib.rs | 2 +- .../rust-lib/flowy-client-sync/src/util.rs | 158 +- .../tests/client_folder/folder_test.rs | 95 +- .../tests/client_folder/script.rs | 172 +- .../tests/client_folder/workspace_test.rs | 140 +- frontend/rust-lib/flowy-codegen/src/ast.rs | 62 +- .../src/dart_event/dart_event.rs | 249 +-- .../src/dart_event/event_template.rs | 92 +- .../rust-lib/flowy-codegen/src/flowy_toml.rs | 73 +- frontend/rust-lib/flowy-codegen/src/lib.rs | 4 +- .../flowy-codegen/src/protobuf_file/ast.rs | 300 +-- .../flowy-codegen/src/protobuf_file/mod.rs | 431 ++-- .../src/protobuf_file/proto_gen.rs | 242 +-- .../src/protobuf_file/proto_info.rs | 203 +- .../template/derive_meta/derive_meta.rs | 42 +- .../template/proto_file/enum_template.rs | 50 +- .../template/proto_file/struct_template.rs | 160 +- .../src/ts_event/event_template.rs | 103 +- .../flowy-codegen/src/ts_event/mod.rs | 290 +-- frontend/rust-lib/flowy-codegen/src/util.rs | 244 +-- .../src/deps_resolve/document_deps.rs | 149 +- .../src/deps_resolve/folder_deps.rs | 504 ++--- .../flowy-core/src/deps_resolve/grid_deps.rs | 113 +- .../flowy-core/src/deps_resolve/user_deps.rs | 16 +- frontend/rust-lib/flowy-core/src/lib.rs | 513 ++--- frontend/rust-lib/flowy-core/src/module.rs | 28 +- frontend/rust-lib/flowy-database/build.rs | 12 +- .../src/entities/cell_entities.rs | 199 +- .../src/entities/field_entities.rs | 774 +++---- .../filter_entities/checkbox_filter.rs | 60 +- .../filter_entities/checklist_filter.rs | 60 +- .../entities/filter_entities/date_filter.rs | 148 +- .../filter_entities/filter_changeset.rs | 66 +- .../entities/filter_entities/number_filter.rs | 91 +- .../filter_entities/select_option_filter.rs | 79 +- .../entities/filter_entities/text_filter.rs | 90 +- .../src/entities/filter_entities/util.rs | 314 +-- .../src/entities/grid_entities.rs | 173 +- .../entities/group_entities/configuration.rs | 74 +- .../src/entities/group_entities/group.rs | 221 +- .../group_entities/group_changeset.rs | 208 +- .../flowy-database/src/entities/parser.rs | 16 +- .../src/entities/row_entities.rs | 216 +- .../src/entities/setting_entities.rs | 215 +- .../src/entities/sort_entities.rs | 272 +-- .../src/entities/view_entities.rs | 82 +- .../flowy-database/src/event_handler.rs | 740 +++---- .../rust-lib/flowy-database/src/event_map.rs | 290 +-- .../rust-lib/flowy-database/src/macros.rs | 144 +- .../rust-lib/flowy-database/src/manager.rs | 440 ++-- .../flowy-database/src/notification.rs | 68 +- .../src/services/block_editor.rs | 352 ++-- .../src/services/block_manager.rs | 536 ++--- .../src/services/cell/cell_data_cache.rs | 123 +- .../src/services/cell/cell_operation.rs | 380 ++-- .../src/services/cell/type_cell_data.rs | 231 +-- .../src/services/field/field_builder.rs | 120 +- .../src/services/field/field_operation.rs | 50 +- .../src/services/field/type_option_builder.rs | 82 +- .../checkbox_type_option/checkbox_filter.rs | 64 +- .../checkbox_type_option/checkbox_tests.rs | 80 +- .../checkbox_type_option.rs | 181 +- .../checkbox_type_option_entities.rs | 98 +- .../date_type_option/date_filter.rs | 244 +-- .../date_type_option/date_tests.rs | 323 +-- .../date_type_option/date_type_option.rs | 359 ++-- .../date_type_option_entities.rs | 233 +-- .../type_options/number_type_option/format.rs | 170 +- .../number_type_option/number_filter.rs | 132 +- .../number_type_option/number_tests.rs | 1084 ++++++---- .../number_type_option/number_type_option.rs | 303 +-- .../number_type_option_entities.rs | 144 +- .../selection_type_option/checklist_filter.rs | 58 +- .../checklist_type_option.rs | 174 +- .../multi_select_type_option.rs | 432 ++-- .../selection_type_option/select_filter.rs | 521 ++--- .../select_type_option.rs | 696 +++---- .../single_select_type_option.rs | 353 ++-- .../type_option_transform.rs | 149 +- .../text_type_option/text_filter.rs | 134 +- .../text_type_option/text_tests.rs | 113 +- .../text_type_option/text_type_option.rs | 301 +-- .../field/type_options/type_option.rs | 177 +- .../field/type_options/type_option_cell.rs | 895 ++++---- .../type_options/url_type_option/url_tests.rs | 295 +-- .../url_type_option/url_type_option.rs | 169 +- .../url_type_option_entities.rs | 90 +- .../src/services/filter/controller.rs | 724 ++++--- .../src/services/filter/entities.rs | 149 +- .../src/services/filter/task.rs | 70 +- .../src/services/grid_editor.rs | 1801 +++++++++-------- .../src/services/grid_editor_trait_impl.rs | 131 +- .../src/services/group/action.rs | 155 +- .../src/services/group/configuration.rs | 776 +++---- .../src/services/group/controller.rs | 529 ++--- .../controller_impls/checkbox_controller.rs | 255 +-- .../controller_impls/default_controller.rs | 148 +- .../multi_select_controller.rs | 160 +- .../single_select_controller.rs | 162 +- .../select_option_controller/util.rs | 281 +-- .../group/controller_impls/url_controller.rs | 330 +-- .../src/services/group/entities.rs | 110 +- .../src/services/group/group_util.rs | 246 ++- .../src/services/persistence/block_index.rs | 54 +- .../src/services/persistence/kv.rs | 194 +- .../src/services/persistence/migration.rs | 101 +- .../src/services/persistence/mod.rs | 12 +- .../rev_sqlite/grid_block_sqlite_impl.rs | 379 ++-- .../persistence/rev_sqlite/grid_snapshot.rs | 160 +- .../rev_sqlite/grid_sqlite_impl.rs | 377 ++-- .../rev_sqlite/grid_view_sqlite_impl.rs | 379 ++-- .../flowy-database/src/services/retry.rs | 30 +- .../src/services/row/row_builder.rs | 218 +- .../src/services/row/row_loader.rs | 28 +- .../src/services/setting/setting_builder.rs | 54 +- .../src/services/sort/controller.rs | 432 ++-- .../src/services/sort/entities.rs | 105 +- .../flowy-database/src/services/sort/task.rs | 56 +- .../services/view_editor/changed_notifier.rs | 104 +- .../src/services/view_editor/editor.rs | 1646 ++++++++------- .../services/view_editor/editor_manager.rs | 527 ++--- .../src/services/view_editor/trait_impl.rs | 305 +-- frontend/rust-lib/flowy-database/src/util.rs | 372 ++-- .../tests/grid/block_test/block_test.rs | 58 +- .../tests/grid/block_test/row_test.rs | 194 +- .../tests/grid/block_test/script.rs | 726 +++---- .../tests/grid/block_test/util.rs | 214 +- .../tests/grid/cell_test/script.rs | 91 +- .../tests/grid/cell_test/test.rs | 150 +- .../tests/grid/database_editor.rs | 328 +-- .../tests/grid/field_test/script.rs | 287 +-- .../tests/grid/field_test/test.rs | 511 ++--- .../tests/grid/field_test/util.rs | 89 +- .../grid/filter_test/checkbox_filter_test.rs | 52 +- .../grid/filter_test/checklist_filter_test.rs | 56 +- .../grid/filter_test/date_filter_test.rs | 170 +- .../grid/filter_test/number_filter_test.rs | 180 +- .../filter_test/select_option_filter_test.rs | 210 +- .../grid/filter_test/text_filter_test.rs | 376 ++-- .../tests/grid/group_test/script.rs | 554 ++--- .../tests/grid/group_test/test.rs | 818 ++++---- .../tests/grid/group_test/url_group_test.rs | 260 +-- .../tests/grid/mock_data/board_mock_data.rs | 345 ++-- .../grid/mock_data/calendar_mock_data.rs | 2 +- .../tests/grid/mock_data/grid_mock_data.rs | 355 ++-- .../tests/grid/snapshot_test/script.rs | 144 +- .../tests/grid/snapshot_test/test.rs | 56 +- .../grid/sort_test/checkbox_and_text_test.rs | 86 +- .../tests/grid/sort_test/multi_sort_test.rs | 74 +- .../tests/grid/sort_test/script.rs | 291 +-- .../tests/grid/sort_test/single_sort_test.rs | 430 ++-- .../flowy-derive/src/dart_event/mod.rs | 2 +- frontend/rust-lib/flowy-derive/src/lib.rs | 34 +- .../rust-lib/flowy-derive/src/node/mod.rs | 278 +-- .../flowy-derive/src/proto_buf/deserialize.rs | 452 +++-- .../flowy-derive/src/proto_buf/enum_serde.rs | 60 +- .../flowy-derive/src/proto_buf/mod.rs | 53 +- .../flowy-derive/src/proto_buf/serialize.rs | 338 ++-- .../flowy-derive/src/proto_buf/util.rs | 154 +- .../rust-lib/flowy-derive/tests/progress.rs | 4 +- frontend/rust-lib/flowy-document/build.rs | 12 +- .../flowy-document/src/editor/document.rs | 145 +- .../src/editor/document_serde.rs | 574 +++--- .../flowy-document/src/editor/editor.rs | 170 +- .../rust-lib/flowy-document/src/editor/mod.rs | 6 +- .../flowy-document/src/editor/queue.rs | 134 +- .../rust-lib/flowy-document/src/entities.rs | 140 +- .../flowy-document/src/event_handler.rs | 49 +- .../rust-lib/flowy-document/src/event_map.rs | 26 +- frontend/rust-lib/flowy-document/src/lib.rs | 24 +- .../rust-lib/flowy-document/src/manager.rs | 567 +++--- .../flowy-document/src/old_editor/editor.rs | 439 ++-- .../flowy-document/src/old_editor/queue.rs | 465 ++--- .../flowy-document/src/old_editor/snapshot.rs | 18 +- .../src/old_editor/web_socket.rs | 263 +-- .../flowy-document/src/services/migration.rs | 100 +- .../services/persistence/delta_migration.rs | 346 ++-- .../src/services/persistence/mod.rs | 22 +- .../rev_sqlite/document_rev_sqlite_v0.rs | 455 +++-- .../rev_sqlite/document_rev_sqlite_v1.rs | 379 ++-- .../rev_sqlite/document_snapshot.rs | 118 +- .../flowy-document/tests/editor/mod.rs | 532 ++--- .../flowy-document/tests/editor/op_test.rs | 1041 +++++----- .../flowy-document/tests/editor/serde_test.rs | 127 +- .../tests/editor/undo_redo_test.rs | 540 ++--- .../new_document/document_compose_test.rs | 36 +- .../tests/new_document/script.rs | 204 +- .../flowy-document/tests/new_document/test.rs | 146 +- .../tests/old_document/old_document_test.rs | 164 +- .../tests/old_document/script.rs | 143 +- frontend/rust-lib/flowy-error/build.rs | 2 +- frontend/rust-lib/flowy-error/src/code.rs | 248 +-- frontend/rust-lib/flowy-error/src/errors.rs | 146 +- .../rust-lib/flowy-error/src/ext/database.rs | 12 +- .../rust-lib/flowy-error/src/ext/dispatch.rs | 8 +- .../flowy-error/src/ext/http_server.rs | 20 +- frontend/rust-lib/flowy-error/src/ext/ot.rs | 6 +- .../rust-lib/flowy-error/src/ext/reqwest.rs | 6 +- .../rust-lib/flowy-error/src/ext/serde.rs | 6 +- frontend/rust-lib/flowy-error/src/ext/sync.rs | 10 +- frontend/rust-lib/flowy-error/src/ext/user.rs | 38 +- frontend/rust-lib/flowy-error/src/ext/ws.rs | 8 +- frontend/rust-lib/flowy-folder/build.rs | 12 +- .../rust-lib/flowy-folder/src/entities/app.rs | 263 +-- .../entities/parser/app/app_color_style.rs | 10 +- .../src/entities/parser/app/app_desc.rs | 20 +- .../src/entities/parser/app/app_id.rs | 18 +- .../src/entities/parser/app/app_name.rs | 18 +- .../src/entities/parser/trash/trash_id.rs | 36 +- .../src/entities/parser/view/view_desc.rs | 18 +- .../src/entities/parser/view/view_id.rs | 18 +- .../src/entities/parser/view/view_name.rs | 26 +- .../entities/parser/view/view_thumbnail.rs | 20 +- .../parser/workspace/workspace_desc.rs | 18 +- .../entities/parser/workspace/workspace_id.rs | 18 +- .../parser/workspace/workspace_name.rs | 26 +- .../flowy-folder/src/entities/trash.rs | 211 +- .../flowy-folder/src/entities/view.rs | 399 ++-- .../flowy-folder/src/entities/workspace.rs | 146 +- .../rust-lib/flowy-folder/src/event_map.rs | 329 +-- frontend/rust-lib/flowy-folder/src/lib.rs | 4 +- frontend/rust-lib/flowy-folder/src/macros.rs | 62 +- frontend/rust-lib/flowy-folder/src/manager.rs | 470 +++-- .../rust-lib/flowy-folder/src/notification.rs | 62 +- .../src/services/app/controller.rs | 412 ++-- .../src/services/app/event_handler.rs | 72 +- .../src/services/folder_editor.rs | 221 +- .../src/services/persistence/migration.rs | 215 +- .../src/services/persistence/mod.rs | 209 +- .../rev_sqlite/folder_rev_sqlite.rs | 408 ++-- .../persistence/rev_sqlite/folder_snapshot.rs | 118 +- .../services/persistence/version_1/app_sql.rs | 242 +-- .../persistence/version_1/trash_sql.rs | 187 +- .../services/persistence/version_1/v1_impl.rs | 319 +-- .../persistence/version_1/view_sql.rs | 307 +-- .../persistence/version_1/workspace_sql.rs | 180 +- .../services/persistence/version_2/v2_impl.rs | 349 ++-- .../src/services/trash/controller.rs | 463 +++-- .../src/services/trash/event_handler.rs | 40 +- .../src/services/view/controller.rs | 874 ++++---- .../src/services/view/event_handler.rs | 148 +- .../flowy-folder/src/services/web_socket.rs | 211 +- .../src/services/workspace/controller.rs | 472 ++--- .../src/services/workspace/event_handler.rs | 136 +- frontend/rust-lib/flowy-folder/src/util.rs | 93 +- .../tests/workspace/folder_test.rs | 384 ++-- .../flowy-folder/tests/workspace/script.rs | 754 +++---- frontend/rust-lib/flowy-net/build.rs | 12 +- .../flowy-net/src/entities/network_state.rs | 52 +- frontend/rust-lib/flowy-net/src/event_map.rs | 12 +- .../rust-lib/flowy-net/src/handlers/mod.rs | 10 +- .../flowy-net/src/http_server/document.rs | 149 +- .../flowy-net/src/http_server/folder.rs | 653 +++--- .../flowy-net/src/http_server/user.rs | 162 +- .../flowy-net/src/local_server/mod.rs | 23 +- .../flowy-net/src/local_server/persistence.rs | 258 +-- .../flowy-net/src/local_server/server.rs | 707 ++++--- .../rust-lib/flowy-net/src/local_server/ws.rs | 125 +- frontend/rust-lib/flowy-net/src/request.rs | 410 ++-- frontend/rust-lib/flowy-net/src/response.rs | 22 +- frontend/rust-lib/flowy-notification/build.rs | 2 +- .../src/entities/subject.rs | 58 +- .../rust-lib/flowy-notification/src/lib.rs | 128 +- .../src/disk_cache_impl/file_persistence.rs | 76 +- .../flowy-revision-persistence/src/lib.rs | 179 +- .../flowy-revision/src/cache/memory.rs | 247 +-- .../flowy-revision/src/cache/reset.rs | 169 +- .../flowy-revision/src/conflict_resolve.rs | 250 +-- .../flowy-revision/src/rev_manager.rs | 672 +++--- .../flowy-revision/src/rev_persistence.rs | 775 +++---- .../rust-lib/flowy-revision/src/rev_queue.rs | 136 +- .../flowy-revision/src/rev_snapshot.rs | 276 +-- .../rust-lib/flowy-revision/src/ws_manager.rs | 683 ++++--- .../revision_test/local_revision_test.rs | 351 ++-- .../tests/revision_test/revision_disk_test.rs | 101 +- .../tests/revision_test/script.rs | 529 ++--- frontend/rust-lib/flowy-sqlite/src/kv/kv.rs | 266 +-- frontend/rust-lib/flowy-sqlite/src/lib.rs | 32 +- frontend/rust-lib/flowy-sqlite/src/macros.rs | 235 +-- frontend/rust-lib/flowy-sqlite/src/schema.rs | 32 +- .../flowy-sqlite/src/sqlite/conn_ext.rs | 30 +- .../flowy-sqlite/src/sqlite/database.rs | 64 +- .../flowy-sqlite/src/sqlite/errors.rs | 3 +- .../rust-lib/flowy-sqlite/src/sqlite/pool.rs | 174 +- .../flowy-sqlite/src/sqlite/pragma.rs | 255 +-- frontend/rust-lib/flowy-task/src/queue.rs | 156 +- frontend/rust-lib/flowy-task/src/scheduler.rs | 295 +-- frontend/rust-lib/flowy-task/src/store.rs | 66 +- frontend/rust-lib/flowy-task/src/task.rs | 174 +- .../flowy-task/tests/task_test/script.rs | 295 +-- .../tests/task_test/task_cancel_test.rs | 117 +- .../tests/task_test/task_order_test.rs | 124 +- .../rust-lib/flowy-test/src/event_builder.rs | 228 ++- frontend/rust-lib/flowy-test/src/helper.rs | 372 ++-- frontend/rust-lib/flowy-test/src/lib.rs | 62 +- frontend/rust-lib/flowy-user/build.rs | 12 +- .../rust-lib/flowy-user/src/entities/auth.rs | 66 +- .../flowy-user/src/entities/user_profile.rs | 164 +- .../flowy-user/src/entities/user_setting.rs | 110 +- frontend/rust-lib/flowy-user/src/event_map.rs | 112 +- .../flowy-user/src/handlers/auth_handler.rs | 20 +- .../flowy-user/src/handlers/user_handler.rs | 90 +- frontend/rust-lib/flowy-user/src/lib.rs | 2 +- .../rust-lib/flowy-user/src/notification.rs | 22 +- .../flowy-user/src/services/database.rs | 194 +- .../flowy-user/src/services/user_session.rs | 566 +++--- .../flowy-user/tests/event/auth_test.rs | 160 +- .../rust-lib/flowy-user/tests/event/helper.rs | 62 +- .../tests/event/user_profile_test.rs | 166 +- .../rust-lib/lib-dispatch/src/byte_trait.rs | 60 +- frontend/rust-lib/lib-dispatch/src/data.rs | 143 +- .../rust-lib/lib-dispatch/src/dispatcher.rs | 259 +-- .../lib-dispatch/src/errors/errors.rs | 139 +- frontend/rust-lib/lib-dispatch/src/lib.rs | 4 +- frontend/rust-lib/lib-dispatch/src/macros.rs | 10 +- .../lib-dispatch/src/module/container.rs | 91 +- .../rust-lib/lib-dispatch/src/module/data.rs | 73 +- .../lib-dispatch/src/module/module.rs | 292 +-- .../lib-dispatch/src/request/payload.rs | 70 +- .../lib-dispatch/src/request/request.rs | 130 +- .../lib-dispatch/src/response/builder.rs | 54 +- .../lib-dispatch/src/response/responder.rs | 34 +- .../lib-dispatch/src/response/response.rs | 88 +- frontend/rust-lib/lib-dispatch/src/runtime.rs | 38 +- .../lib-dispatch/src/service/boxed.rs | 142 +- .../lib-dispatch/src/service/handler.rs | 194 +- .../lib-dispatch/src/service/service.rs | 59 +- .../rust-lib/lib-dispatch/src/util/ready.rs | 30 +- .../rust-lib/lib-dispatch/tests/api/module.rs | 26 +- frontend/rust-lib/lib-log/src/layer.rs | 310 +-- frontend/rust-lib/lib-log/src/lib.rs | 101 +- frontend/rust-lib/rustfmt.toml | 28 +- shared-lib/document-model/src/document.rs | 66 +- .../src/configuration.rs | 146 +- shared-lib/flowy-client-ws/src/connection.rs | 241 +-- shared-lib/flowy-client-ws/src/ws.rs | 106 +- .../src/server_document/document_manager.rs | 503 ++--- .../src/server_document/document_pad.rs | 55 +- .../src/server_folder/folder_manager.rs | 442 ++-- .../src/server_folder/folder_pad.rs | 100 +- shared-lib/flowy-sync/src/errors.rs | 102 +- shared-lib/flowy-sync/src/ext.rs | 114 +- shared-lib/flowy-sync/src/lib.rs | 456 +++-- shared-lib/flowy-sync/src/util.rs | 50 +- shared-lib/folder-model/src/app_rev.rs | 40 +- shared-lib/folder-model/src/folder.rs | 8 +- shared-lib/folder-model/src/folder_rev.rs | 102 +- shared-lib/folder-model/src/macros.rs | 70 +- shared-lib/folder-model/src/trash_rev.rs | 196 +- shared-lib/folder-model/src/user_default.rs | 90 +- shared-lib/folder-model/src/view_rev.rs | 88 +- shared-lib/folder-model/src/workspace_rev.rs | 18 +- shared-lib/grid-model/src/filter_rev.rs | 12 +- shared-lib/grid-model/src/grid_block.rs | 88 +- shared-lib/grid-model/src/grid_rev.rs | 253 +-- shared-lib/grid-model/src/grid_setting_rev.rs | 219 +- shared-lib/grid-model/src/grid_view.rs | 100 +- shared-lib/grid-model/src/group_rev.rs | 225 +- shared-lib/grid-model/src/sort_rev.rs | 36 +- shared-lib/lib-infra/src/future.rs | 62 +- shared-lib/lib-infra/src/ref_map.rs | 109 +- shared-lib/lib-infra/src/retry/future.rs | 245 +-- .../src/retry/strategy/exponential_backoff.rs | 156 +- .../src/retry/strategy/fixed_interval.rs | 38 +- .../lib-infra/src/retry/strategy/jitter.rs | 2 +- shared-lib/lib-infra/src/util.rs | 49 +- .../lib-ot/src/core/attributes/attribute.rs | 455 +++-- .../src/core/attributes/attribute_serde.rs | 348 ++-- shared-lib/lib-ot/src/core/delta/builder.rs | 196 +- shared-lib/lib-ot/src/core/delta/cursor.rs | 375 ++-- shared-lib/lib-ot/src/core/delta/iterator.rs | 336 +-- .../src/core/delta/operation/operation.rs | 685 +++---- .../core/delta/operation/operation_serde.rs | 526 ++--- shared-lib/lib-ot/src/core/delta/ops.rs | 1065 +++++----- shared-lib/lib-ot/src/core/delta/ops_serde.rs | 78 +- shared-lib/lib-ot/src/core/interval.rs | 335 +-- shared-lib/lib-ot/src/core/node_tree/node.rs | 415 ++-- .../lib-ot/src/core/node_tree/node_serde.rs | 126 +- .../lib-ot/src/core/node_tree/operation.rs | 434 ++-- .../src/core/node_tree/operation_serde.rs | 64 +- shared-lib/lib-ot/src/core/node_tree/path.rs | 306 +-- .../lib-ot/src/core/node_tree/transaction.rs | 459 ++--- .../src/core/node_tree/transaction_serde.rs | 36 +- shared-lib/lib-ot/src/core/node_tree/tree.rs | 905 +++++---- .../lib-ot/src/core/node_tree/tree_serde.rs | 90 +- shared-lib/lib-ot/src/core/ot_str.rs | 466 ++--- shared-lib/lib-ot/src/errors.rs | 152 +- .../lib-ot/src/text_delta/attributes.rs | 213 +- shared-lib/lib-ot/src/text_delta/macros.rs | 40 +- .../tests/node/changeset_compose_test.rs | 328 +-- .../tests/node/operation_attribute_test.rs | 106 +- .../tests/node/operation_compose_test.rs | 188 +- .../tests/node/operation_delete_test.rs | 314 +-- .../lib-ot/tests/node/operation_delta_test.rs | 66 +- .../tests/node/operation_insert_test.rs | 923 ++++----- shared-lib/lib-ot/tests/node/script.rs | 414 ++-- shared-lib/lib-ot/tests/node/serde_test.rs | 113 +- .../tests/node/transaction_compose_test.rs | 171 +- shared-lib/lib-ot/tests/node/tree_test.rs | 1253 ++++++------ shared-lib/lib-ws/src/connect.rs | 306 +-- shared-lib/lib-ws/src/errors.rs | 108 +- shared-lib/lib-ws/src/msg.rs | 46 +- shared-lib/lib-ws/src/ws.rs | 599 +++--- shared-lib/revision-model/src/revision.rs | 152 +- shared-lib/rustfmt.toml | 28 +- shared-lib/user-model/src/errors.rs | 58 +- shared-lib/user-model/src/lib.rs | 96 +- .../user-model/src/parser/user_email.rs | 108 +- shared-lib/user-model/src/parser/user_icon.rs | 12 +- shared-lib/user-model/src/parser/user_id.rs | 18 +- shared-lib/user-model/src/parser/user_name.rs | 122 +- .../user-model/src/parser/user_password.rs | 60 +- .../user-model/src/parser/user_workspace.rs | 18 +- shared-lib/ws-model/src/ws_revision.rs | 140 +- 459 files changed, 50554 insertions(+), 46600 deletions(-) create mode 100644 frontend/appflowy_tauri/src-tauri/rustfmt.toml diff --git a/frontend/appflowy_tauri/src-tauri/build.rs b/frontend/appflowy_tauri/src-tauri/build.rs index d860e1e6a7..795b9b7c83 100644 --- a/frontend/appflowy_tauri/src-tauri/build.rs +++ b/frontend/appflowy_tauri/src-tauri/build.rs @@ -1,3 +1,3 @@ fn main() { - tauri_build::build() + tauri_build::build() } diff --git a/frontend/appflowy_tauri/src-tauri/rustfmt.toml b/frontend/appflowy_tauri/src-tauri/rustfmt.toml new file mode 100644 index 0000000000..5cb0d67ee5 --- /dev/null +++ b/frontend/appflowy_tauri/src-tauri/rustfmt.toml @@ -0,0 +1,12 @@ +# https://rust-lang.github.io/rustfmt/?version=master&search= +max_width = 100 +tab_spaces = 2 +newline_style = "Auto" +match_block_trailing_comma = true +use_field_init_shorthand = true +use_try_shorthand = true +reorder_imports = true +reorder_modules = true +remove_nested_parens = true +merge_derives = true +edition = "2021" \ No newline at end of file diff --git a/frontend/appflowy_tauri/src-tauri/src/init.rs b/frontend/appflowy_tauri/src-tauri/src/init.rs index 911ea88c1d..8ce59c187d 100644 --- a/frontend/appflowy_tauri/src-tauri/src/init.rs +++ b/frontend/appflowy_tauri/src-tauri/src/init.rs @@ -1,10 +1,10 @@ use flowy_core::{get_client_server_configuration, AppFlowyCore, AppFlowyCoreConfig}; pub fn init_flowy_core() -> AppFlowyCore { - let data_path = tauri::api::path::data_dir().unwrap(); - let path = format!("{}/AppFlowy", data_path.to_str().unwrap()); - let server_config = get_client_server_configuration().unwrap(); - let config = AppFlowyCoreConfig::new(&path, "AppFlowy".to_string(), server_config) - .log_filter("trace", vec!["appflowy_tauri".to_string()]); - AppFlowyCore::new(config) + let data_path = tauri::api::path::data_dir().unwrap(); + let path = format!("{}/AppFlowy", data_path.to_str().unwrap()); + let server_config = get_client_server_configuration().unwrap(); + let config = AppFlowyCoreConfig::new(&path, "AppFlowy".to_string(), server_config) + .log_filter("trace", vec!["appflowy_tauri".to_string()]); + AppFlowyCore::new(config) } diff --git a/frontend/appflowy_tauri/src-tauri/src/main.rs b/frontend/appflowy_tauri/src-tauri/src/main.rs index a78a669d8b..ecee588521 100644 --- a/frontend/appflowy_tauri/src-tauri/src/main.rs +++ b/frontend/appflowy_tauri/src-tauri/src/main.rs @@ -1,6 +1,6 @@ #![cfg_attr( - all(not(debug_assertions), target_os = "windows"), - windows_subsystem = "windows" + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" )] mod init; @@ -14,28 +14,28 @@ use request::*; use tauri::Manager; fn main() { - let flowy_core = init_flowy_core(); - tauri::Builder::default() - .invoke_handler(tauri::generate_handler![invoke_request]) - .manage(flowy_core) - .on_window_event(|_window_event| {}) - .on_menu_event(|_menu| {}) - .on_page_load(|window, _payload| { - let app_handler = window.app_handle(); - register_notification_sender(TSNotificationSender::new(app_handler.clone())); - // tauri::async_runtime::spawn(async move {}); - window.listen_global(AF_EVENT, move |event| { - on_event(app_handler.clone(), event); - }); - }) - .setup(|app| { - #[cfg(debug_assertions)] - { - let window = app.get_window("main").unwrap(); - window.open_devtools(); - } - Ok(()) - }) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + let flowy_core = init_flowy_core(); + tauri::Builder::default() + .invoke_handler(tauri::generate_handler![invoke_request]) + .manage(flowy_core) + .on_window_event(|_window_event| {}) + .on_menu_event(|_menu| {}) + .on_page_load(|window, _payload| { + let app_handler = window.app_handle(); + register_notification_sender(TSNotificationSender::new(app_handler.clone())); + // tauri::async_runtime::spawn(async move {}); + window.listen_global(AF_EVENT, move |event| { + on_event(app_handler.clone(), event); + }); + }) + .setup(|app| { + #[cfg(debug_assertions)] + { + let window = app.get_window("main").unwrap(); + window.open_devtools(); + } + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); } diff --git a/frontend/appflowy_tauri/src-tauri/src/notification.rs b/frontend/appflowy_tauri/src-tauri/src/notification.rs index 2a0bff6424..b42541edec 100644 --- a/frontend/appflowy_tauri/src-tauri/src/notification.rs +++ b/frontend/appflowy_tauri/src-tauri/src/notification.rs @@ -12,23 +12,24 @@ pub fn on_event(app_handler: AppHandle, event: Event) {} #[allow(dead_code)] pub fn send_notification(app_handler: AppHandle, payload: P) { - app_handler.emit_all(AF_NOTIFICATION, payload).unwrap(); + app_handler.emit_all(AF_NOTIFICATION, payload).unwrap(); } pub struct TSNotificationSender { - handler: AppHandle, + handler: AppHandle, } impl TSNotificationSender { - pub fn new(handler: AppHandle) -> Self { - Self { handler } - } + pub fn new(handler: AppHandle) -> Self { + Self { handler } + } } impl NotificationSender for TSNotificationSender { - fn send_subject(&self, subject: SubscribeObject) -> Result<(), String> { - self.handler - .emit_all(AF_NOTIFICATION, subject) - .map_err(|e| format!("{:?}", e)) - } + fn send_subject(&self, subject: SubscribeObject) -> Result<(), String> { + self + .handler + .emit_all(AF_NOTIFICATION, subject) + .map_err(|e| format!("{:?}", e)) + } } diff --git a/frontend/appflowy_tauri/src-tauri/src/request.rs b/frontend/appflowy_tauri/src-tauri/src/request.rs index 40386cac11..0e00342782 100644 --- a/frontend/appflowy_tauri/src-tauri/src/request.rs +++ b/frontend/appflowy_tauri/src-tauri/src/request.rs @@ -1,46 +1,46 @@ use flowy_core::AppFlowyCore; use lib_dispatch::prelude::{ - AFPluginDispatcher, AFPluginEventResponse, AFPluginRequest, StatusCode, + AFPluginDispatcher, AFPluginEventResponse, AFPluginRequest, StatusCode, }; use tauri::{AppHandle, Manager, State, Wry}; #[derive(Clone, Debug, serde::Deserialize)] pub struct AFTauriRequest { - ty: String, - payload: Vec, + ty: String, + payload: Vec, } impl std::convert::From for AFPluginRequest { - fn from(event: AFTauriRequest) -> Self { - AFPluginRequest::new(event.ty).payload(event.payload) - } + fn from(event: AFTauriRequest) -> Self { + AFPluginRequest::new(event.ty).payload(event.payload) + } } #[derive(Clone, serde::Serialize)] pub struct AFTauriResponse { - code: StatusCode, - payload: Vec, + code: StatusCode, + payload: Vec, } impl std::convert::From for AFTauriResponse { - fn from(response: AFPluginEventResponse) -> Self { - Self { - code: response.status_code, - payload: response.payload.to_vec(), - } + fn from(response: AFPluginEventResponse) -> Self { + Self { + code: response.status_code, + payload: response.payload.to_vec(), } + } } // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command #[tracing::instrument(level = "trace", skip(app_handler))] #[tauri::command] pub async fn invoke_request( - request: AFTauriRequest, - app_handler: AppHandle, + request: AFTauriRequest, + app_handler: AppHandle, ) -> AFTauriResponse { - let request: AFPluginRequest = request.into(); - let state: State = app_handler.state(); - let dispatcher = state.inner().dispatcher(); - let response = AFPluginDispatcher::async_send(dispatcher, request).await; - response.into() + let request: AFPluginRequest = request.into(); + let state: State = app_handler.state(); + let dispatcher = state.inner().dispatcher(); + let response = AFPluginDispatcher::async_send(dispatcher, request).await; + response.into() } diff --git a/frontend/rust-lib/dart-ffi/build.rs b/frontend/rust-lib/dart-ffi/build.rs index 90fa29ba4c..d43384c1ae 100644 --- a/frontend/rust-lib/dart-ffi/build.rs +++ b/frontend/rust-lib/dart-ffi/build.rs @@ -1,3 +1,3 @@ fn main() { - flowy_codegen::protobuf_file::gen(env!("CARGO_PKG_NAME")); + flowy_codegen::protobuf_file::gen(env!("CARGO_PKG_NAME")); } diff --git a/frontend/rust-lib/dart-ffi/src/c.rs b/frontend/rust-lib/dart-ffi/src/c.rs index a0fbed09dc..14a5c09637 100644 --- a/frontend/rust-lib/dart-ffi/src/c.rs +++ b/frontend/rust-lib/dart-ffi/src/c.rs @@ -2,25 +2,25 @@ use byteorder::{BigEndian, ByteOrder}; use std::mem::forget; pub fn forget_rust(buf: Vec) -> *const u8 { - let ptr = buf.as_ptr(); - forget(buf); - ptr + let ptr = buf.as_ptr(); + forget(buf); + ptr } #[allow(unused_attributes)] #[allow(dead_code)] pub fn reclaim_rust(ptr: *mut u8, length: u32) { - unsafe { - let len: usize = length as usize; - Vec::from_raw_parts(ptr, len, len); - } + unsafe { + let len: usize = length as usize; + Vec::from_raw_parts(ptr, len, len); + } } pub fn extend_front_four_bytes_into_bytes(bytes: &[u8]) -> Vec { - let mut output = Vec::with_capacity(bytes.len() + 4); - let mut marker_bytes = [0; 4]; - BigEndian::write_u32(&mut marker_bytes, bytes.len() as u32); - output.extend_from_slice(&marker_bytes); - output.extend_from_slice(bytes); - output + let mut output = Vec::with_capacity(bytes.len() + 4); + let mut marker_bytes = [0; 4]; + BigEndian::write_u32(&mut marker_bytes, bytes.len() as u32); + output.extend_from_slice(&marker_bytes); + output.extend_from_slice(bytes); + output } diff --git a/frontend/rust-lib/dart-ffi/src/lib.rs b/frontend/rust-lib/dart-ffi/src/lib.rs index 25aea58ed5..c3bd609d6d 100644 --- a/frontend/rust-lib/dart-ffi/src/lib.rs +++ b/frontend/rust-lib/dart-ffi/src/lib.rs @@ -7,8 +7,8 @@ mod util; use crate::notification::DartNotificationSender; use crate::{ - c::{extend_front_four_bytes_into_bytes, forget_rust}, - model::{FFIRequest, FFIResponse}, + c::{extend_front_four_bytes_into_bytes, forget_rust}, + model::{FFIRequest, FFIResponse}, }; use flowy_core::get_client_server_configuration; use flowy_core::*; @@ -20,69 +20,74 @@ use parking_lot::RwLock; use std::{ffi::CStr, os::raw::c_char}; lazy_static! { - static ref APPFLOWY_CORE: RwLock> = RwLock::new(None); + static ref APPFLOWY_CORE: RwLock> = RwLock::new(None); } #[no_mangle] pub extern "C" fn init_sdk(path: *mut c_char) -> i64 { - let c_str: &CStr = unsafe { CStr::from_ptr(path) }; - let path: &str = c_str.to_str().unwrap(); + let c_str: &CStr = unsafe { CStr::from_ptr(path) }; + let path: &str = c_str.to_str().unwrap(); - let server_config = get_client_server_configuration().unwrap(); - let log_crates = vec!["flowy-ffi".to_string()]; - let config = AppFlowyCoreConfig::new(path, "appflowy".to_string(), server_config).log_filter("info", log_crates); - *APPFLOWY_CORE.write() = Some(AppFlowyCore::new(config)); + let server_config = get_client_server_configuration().unwrap(); + let log_crates = vec!["flowy-ffi".to_string()]; + let config = AppFlowyCoreConfig::new(path, "appflowy".to_string(), server_config) + .log_filter("info", log_crates); + *APPFLOWY_CORE.write() = Some(AppFlowyCore::new(config)); - 0 + 0 } #[no_mangle] pub extern "C" fn async_event(port: i64, input: *const u8, len: usize) { - let request: AFPluginRequest = FFIRequest::from_u8_pointer(input, len).into(); - log::trace!( - "[FFI]: {} Async Event: {:?} with {} port", - &request.id, - &request.event, - port - ); + let request: AFPluginRequest = FFIRequest::from_u8_pointer(input, len).into(); + log::trace!( + "[FFI]: {} Async Event: {:?} with {} port", + &request.id, + &request.event, + port + ); - let dispatcher = match APPFLOWY_CORE.read().as_ref() { - None => { - log::error!("sdk not init yet."); - return; - } - Some(e) => e.event_dispatcher.clone(), - }; - let _ = AFPluginDispatcher::async_send_with_callback(dispatcher, request, move |resp: AFPluginEventResponse| { - log::trace!("[FFI]: Post data to dart through {} port", port); - Box::pin(post_to_flutter(resp, port)) - }); + let dispatcher = match APPFLOWY_CORE.read().as_ref() { + None => { + log::error!("sdk not init yet."); + return; + }, + Some(e) => e.event_dispatcher.clone(), + }; + let _ = AFPluginDispatcher::async_send_with_callback( + dispatcher, + request, + move |resp: AFPluginEventResponse| { + log::trace!("[FFI]: Post data to dart through {} port", port); + Box::pin(post_to_flutter(resp, port)) + }, + ); } #[no_mangle] pub extern "C" fn sync_event(input: *const u8, len: usize) -> *const u8 { - let request: AFPluginRequest = FFIRequest::from_u8_pointer(input, len).into(); - log::trace!("[FFI]: {} Sync Event: {:?}", &request.id, &request.event,); + let request: AFPluginRequest = FFIRequest::from_u8_pointer(input, len).into(); + log::trace!("[FFI]: {} Sync Event: {:?}", &request.id, &request.event,); - let dispatcher = match APPFLOWY_CORE.read().as_ref() { - None => { - log::error!("sdk not init yet."); - return forget_rust(Vec::default()); - } - Some(e) => e.event_dispatcher.clone(), - }; - let _response = AFPluginDispatcher::sync_send(dispatcher, request); + let dispatcher = match APPFLOWY_CORE.read().as_ref() { + None => { + log::error!("sdk not init yet."); + return forget_rust(Vec::default()); + }, + Some(e) => e.event_dispatcher.clone(), + }; + let _response = AFPluginDispatcher::sync_send(dispatcher, request); - // FFIResponse { } - let response_bytes = vec![]; - let result = extend_front_four_bytes_into_bytes(&response_bytes); - forget_rust(result) + // FFIResponse { } + let response_bytes = vec![]; + let result = extend_front_four_bytes_into_bytes(&response_bytes); + forget_rust(result) } #[no_mangle] pub extern "C" fn set_stream_port(port: i64) -> i32 { - register_notification_sender(DartNotificationSender::new(port)); - 0 + register_notification_sender(DartNotificationSender::new(port)); + 0 } #[inline(never)] @@ -91,39 +96,39 @@ pub extern "C" fn link_me_please() {} #[inline(always)] async fn post_to_flutter(response: AFPluginEventResponse, port: i64) { - let isolate = allo_isolate::Isolate::new(port); - match isolate - .catch_unwind(async { - let ffi_resp = FFIResponse::from(response); - ffi_resp.into_bytes().unwrap().to_vec() - }) - .await - { - Ok(_success) => { - log::trace!("[FFI]: Post data to dart success"); - } - Err(e) => { - if let Some(msg) = e.downcast_ref::<&str>() { - log::error!("[FFI]: {:?}", msg); - } else { - log::error!("[FFI]: allo_isolate post panic"); - } - } - } + let isolate = allo_isolate::Isolate::new(port); + match isolate + .catch_unwind(async { + let ffi_resp = FFIResponse::from(response); + ffi_resp.into_bytes().unwrap().to_vec() + }) + .await + { + Ok(_success) => { + log::trace!("[FFI]: Post data to dart success"); + }, + Err(e) => { + if let Some(msg) = e.downcast_ref::<&str>() { + log::error!("[FFI]: {:?}", msg); + } else { + log::error!("[FFI]: allo_isolate post panic"); + } + }, + } } #[no_mangle] pub extern "C" fn backend_log(level: i64, data: *const c_char) { - let c_str = unsafe { CStr::from_ptr(data) }; - let log_str = c_str.to_str().unwrap(); + let c_str = unsafe { CStr::from_ptr(data) }; + let log_str = c_str.to_str().unwrap(); - // Don't change the mapping relation between number and level - match level { - 0 => tracing::info!("{}", log_str), - 1 => tracing::debug!("{}", log_str), - 2 => tracing::trace!("{}", log_str), - 3 => tracing::warn!("{}", log_str), - 4 => tracing::error!("{}", log_str), - _ => (), - } + // Don't change the mapping relation between number and level + match level { + 0 => tracing::info!("{}", log_str), + 1 => tracing::debug!("{}", log_str), + 2 => tracing::trace!("{}", log_str), + 3 => tracing::warn!("{}", log_str), + 4 => tracing::error!("{}", log_str), + _ => (), + } } diff --git a/frontend/rust-lib/dart-ffi/src/model/ffi_request.rs b/frontend/rust-lib/dart-ffi/src/model/ffi_request.rs index e6c4e30d88..77bfc1e434 100644 --- a/frontend/rust-lib/dart-ffi/src/model/ffi_request.rs +++ b/frontend/rust-lib/dart-ffi/src/model/ffi_request.rs @@ -5,24 +5,24 @@ use std::convert::TryFrom; #[derive(Default, ProtoBuf)] pub struct FFIRequest { - #[pb(index = 1)] - pub(crate) event: String, + #[pb(index = 1)] + pub(crate) event: String, - #[pb(index = 2)] - pub(crate) payload: Vec, + #[pb(index = 2)] + pub(crate) payload: Vec, } impl FFIRequest { - pub fn from_u8_pointer(pointer: *const u8, len: usize) -> Self { - let buffer = unsafe { std::slice::from_raw_parts(pointer, len) }.to_vec(); - let bytes = Bytes::from(buffer); - let request: FFIRequest = FFIRequest::try_from(bytes).unwrap(); - request - } + pub fn from_u8_pointer(pointer: *const u8, len: usize) -> Self { + let buffer = unsafe { std::slice::from_raw_parts(pointer, len) }.to_vec(); + let bytes = Bytes::from(buffer); + let request: FFIRequest = FFIRequest::try_from(bytes).unwrap(); + request + } } impl std::convert::From for AFPluginRequest { - fn from(ffi_request: FFIRequest) -> Self { - AFPluginRequest::new(ffi_request.event).payload(ffi_request.payload) - } + fn from(ffi_request: FFIRequest) -> Self { + AFPluginRequest::new(ffi_request.event).payload(ffi_request.payload) + } } diff --git a/frontend/rust-lib/dart-ffi/src/model/ffi_response.rs b/frontend/rust-lib/dart-ffi/src/model/ffi_response.rs index 62a6d47751..7a96df0b19 100644 --- a/frontend/rust-lib/dart-ffi/src/model/ffi_response.rs +++ b/frontend/rust-lib/dart-ffi/src/model/ffi_response.rs @@ -3,43 +3,43 @@ use lib_dispatch::prelude::{AFPluginEventResponse, Payload, StatusCode}; #[derive(ProtoBuf_Enum, Clone, Copy)] pub enum FFIStatusCode { - Ok = 0, - Err = 1, - Internal = 2, + Ok = 0, + Err = 1, + Internal = 2, } impl std::default::Default for FFIStatusCode { - fn default() -> FFIStatusCode { - FFIStatusCode::Ok - } + fn default() -> FFIStatusCode { + FFIStatusCode::Ok + } } #[derive(ProtoBuf, Default)] pub struct FFIResponse { - #[pb(index = 1)] - payload: Vec, + #[pb(index = 1)] + payload: Vec, - #[pb(index = 2)] - code: FFIStatusCode, + #[pb(index = 2)] + code: FFIStatusCode, } impl std::convert::From for FFIResponse { - fn from(resp: AFPluginEventResponse) -> Self { - let payload = match resp.payload { - Payload::Bytes(bytes) => bytes.to_vec(), - Payload::None => vec![], - }; + fn from(resp: AFPluginEventResponse) -> Self { + let payload = match resp.payload { + Payload::Bytes(bytes) => bytes.to_vec(), + Payload::None => vec![], + }; - let code = match resp.status_code { - StatusCode::Ok => FFIStatusCode::Ok, - StatusCode::Err => FFIStatusCode::Err, - }; + let code = match resp.status_code { + StatusCode::Ok => FFIStatusCode::Ok, + StatusCode::Err => FFIStatusCode::Err, + }; - // let msg = match resp.error { - // None => "".to_owned(), - // Some(e) => format!("{:?}", e), - // }; + // let msg = match resp.error { + // None => "".to_owned(), + // Some(e) => format!("{:?}", e), + // }; - FFIResponse { payload, code } - } + FFIResponse { payload, code } + } } diff --git a/frontend/rust-lib/dart-ffi/src/notification/sender.rs b/frontend/rust-lib/dart-ffi/src/notification/sender.rs index a240c9c1ec..1dcada5b54 100644 --- a/frontend/rust-lib/dart-ffi/src/notification/sender.rs +++ b/frontend/rust-lib/dart-ffi/src/notification/sender.rs @@ -5,21 +5,21 @@ use flowy_notification::NotificationSender; use std::convert::TryInto; pub struct DartNotificationSender { - isolate: Isolate, + isolate: Isolate, } impl DartNotificationSender { - pub fn new(port: i64) -> Self { - Self { - isolate: Isolate::new(port), - } + pub fn new(port: i64) -> Self { + Self { + isolate: Isolate::new(port), } + } } impl NotificationSender for DartNotificationSender { - fn send_subject(&self, subject: SubscribeObject) -> Result<(), String> { - let bytes: Bytes = subject.try_into().unwrap(); - self.isolate.post(bytes.to_vec()); - Ok(()) - } + fn send_subject(&self, subject: SubscribeObject) -> Result<(), String> { + let bytes: Bytes = subject.try_into().unwrap(); + self.isolate.post(bytes.to_vec()); + Ok(()) + } } diff --git a/frontend/rust-lib/flowy-ast/src/ast.rs b/frontend/rust-lib/flowy-ast/src/ast.rs index 5f344d1597..ed2d49d1e7 100644 --- a/frontend/rust-lib/flowy-ast/src/ast.rs +++ b/frontend/rust-lib/flowy-ast/src/ast.rs @@ -4,277 +4,296 @@ use crate::event_attrs::EventEnumAttrs; use crate::node_attrs::NodeStructAttrs; -use crate::{is_recognizable_field, ty_ext::*, ASTResult, PBAttrsContainer, PBStructAttrs, NODE_TYPE}; +use crate::{ + is_recognizable_field, ty_ext::*, ASTResult, PBAttrsContainer, PBStructAttrs, NODE_TYPE, +}; use proc_macro2::Ident; use syn::Meta::NameValue; use syn::{self, punctuated::Punctuated}; pub struct ASTContainer<'a> { - /// The struct or enum name (without generics). - pub ident: syn::Ident, + /// The struct or enum name (without generics). + pub ident: syn::Ident, - pub node_type: Option, - /// Attributes on the structure. - pub pb_attrs: PBAttrsContainer, - /// The contents of the struct or enum. - pub data: ASTData<'a>, + pub node_type: Option, + /// Attributes on the structure. + pub pb_attrs: PBAttrsContainer, + /// The contents of the struct or enum. + pub data: ASTData<'a>, } impl<'a> ASTContainer<'a> { - pub fn from_ast(ast_result: &ASTResult, ast: &'a syn::DeriveInput) -> Option> { - let attrs = PBAttrsContainer::from_ast(ast_result, ast); - // syn::DeriveInput - // 1. syn::DataUnion - // 2. syn::DataStruct - // 3. syn::DataEnum - let data = match &ast.data { - syn::Data::Struct(data) => { - // https://docs.rs/syn/1.0.48/syn/struct.DataStruct.html - let (style, fields) = struct_from_ast(ast_result, &data.fields); - ASTData::Struct(style, fields) - } - syn::Data::Union(_) => { - ast_result.error_spanned_by(ast, "Does not support derive for unions"); - return None; - } - syn::Data::Enum(data) => { - // https://docs.rs/syn/1.0.48/syn/struct.DataEnum.html - ASTData::Enum(enum_from_ast(ast_result, &ast.ident, &data.variants, &ast.attrs)) - } - }; + pub fn from_ast(ast_result: &ASTResult, ast: &'a syn::DeriveInput) -> Option> { + let attrs = PBAttrsContainer::from_ast(ast_result, ast); + // syn::DeriveInput + // 1. syn::DataUnion + // 2. syn::DataStruct + // 3. syn::DataEnum + let data = match &ast.data { + syn::Data::Struct(data) => { + // https://docs.rs/syn/1.0.48/syn/struct.DataStruct.html + let (style, fields) = struct_from_ast(ast_result, &data.fields); + ASTData::Struct(style, fields) + }, + syn::Data::Union(_) => { + ast_result.error_spanned_by(ast, "Does not support derive for unions"); + return None; + }, + syn::Data::Enum(data) => { + // https://docs.rs/syn/1.0.48/syn/struct.DataEnum.html + ASTData::Enum(enum_from_ast( + ast_result, + &ast.ident, + &data.variants, + &ast.attrs, + )) + }, + }; - let ident = ast.ident.clone(); - let node_type = get_node_type(ast_result, &ident, &ast.attrs); - let item = ASTContainer { - ident, - pb_attrs: attrs, - node_type, - data, - }; - Some(item) - } + let ident = ast.ident.clone(); + let node_type = get_node_type(ast_result, &ident, &ast.attrs); + let item = ASTContainer { + ident, + pb_attrs: attrs, + node_type, + data, + }; + Some(item) + } } pub enum ASTData<'a> { - Struct(ASTStyle, Vec>), - Enum(Vec>), + Struct(ASTStyle, Vec>), + Enum(Vec>), } impl<'a> ASTData<'a> { - pub fn all_fields(&'a self) -> Box> + 'a> { - match self { - ASTData::Enum(variants) => Box::new(variants.iter().flat_map(|variant| variant.fields.iter())), - ASTData::Struct(_, fields) => Box::new(fields.iter()), - } + pub fn all_fields(&'a self) -> Box> + 'a> { + match self { + ASTData::Enum(variants) => { + Box::new(variants.iter().flat_map(|variant| variant.fields.iter())) + }, + ASTData::Struct(_, fields) => Box::new(fields.iter()), } + } - pub fn all_variants(&'a self) -> Box + 'a> { - match self { - ASTData::Enum(variants) => { - let iter = variants.iter().map(|variant| &variant.attrs); - Box::new(iter) - } - ASTData::Struct(_, fields) => { - let iter = fields.iter().flat_map(|_| None); - Box::new(iter) - } - } + pub fn all_variants(&'a self) -> Box + 'a> { + match self { + ASTData::Enum(variants) => { + let iter = variants.iter().map(|variant| &variant.attrs); + Box::new(iter) + }, + ASTData::Struct(_, fields) => { + let iter = fields.iter().flat_map(|_| None); + Box::new(iter) + }, } + } - pub fn all_idents(&'a self) -> Box + 'a> { - match self { - ASTData::Enum(variants) => Box::new(variants.iter().map(|v| &v.ident)), - ASTData::Struct(_, fields) => { - let iter = fields.iter().flat_map(|f| match &f.member { - syn::Member::Named(ident) => Some(ident), - _ => None, - }); - Box::new(iter) - } - } + pub fn all_idents(&'a self) -> Box + 'a> { + match self { + ASTData::Enum(variants) => Box::new(variants.iter().map(|v| &v.ident)), + ASTData::Struct(_, fields) => { + let iter = fields.iter().flat_map(|f| match &f.member { + syn::Member::Named(ident) => Some(ident), + _ => None, + }); + Box::new(iter) + }, } + } } /// A variant of an enum. pub struct ASTEnumVariant<'a> { - pub ident: syn::Ident, - pub attrs: EventEnumAttrs, - pub style: ASTStyle, - pub fields: Vec>, - pub original: &'a syn::Variant, + pub ident: syn::Ident, + pub attrs: EventEnumAttrs, + pub style: ASTStyle, + pub fields: Vec>, + pub original: &'a syn::Variant, } impl<'a> ASTEnumVariant<'a> { - pub fn name(&self) -> String { - self.ident.to_string() - } + pub fn name(&self) -> String { + self.ident.to_string() + } } pub enum BracketCategory { - Other, - Opt, - Vec, - Map((String, String)), + Other, + Opt, + Vec, + Map((String, String)), } pub struct ASTField<'a> { - pub member: syn::Member, - pub pb_attrs: PBStructAttrs, - pub node_attrs: NodeStructAttrs, - pub ty: &'a syn::Type, - pub original: &'a syn::Field, - // If the field is Vec, then the bracket_ty will be Vec - pub bracket_ty: Option, - // If the field is Vec, then the bracket_inner_ty will be String - pub bracket_inner_ty: Option, - pub bracket_category: Option, + pub member: syn::Member, + pub pb_attrs: PBStructAttrs, + pub node_attrs: NodeStructAttrs, + pub ty: &'a syn::Type, + pub original: &'a syn::Field, + // If the field is Vec, then the bracket_ty will be Vec + pub bracket_ty: Option, + // If the field is Vec, then the bracket_inner_ty will be String + pub bracket_inner_ty: Option, + pub bracket_category: Option, } impl<'a> ASTField<'a> { - pub fn new(cx: &ASTResult, field: &'a syn::Field, index: usize) -> Result { - let mut bracket_inner_ty = None; - let mut bracket_ty = None; - let mut bracket_category = Some(BracketCategory::Other); - match parse_ty(cx, &field.ty) { - Ok(Some(inner)) => { - match inner.primitive_ty { - PrimitiveTy::Map(map_info) => { - bracket_category = Some(BracketCategory::Map((map_info.key.clone(), map_info.value))) - } - PrimitiveTy::Vec => { - bracket_category = Some(BracketCategory::Vec); - } - PrimitiveTy::Opt => { - bracket_category = Some(BracketCategory::Opt); - } - PrimitiveTy::Other => { - bracket_category = Some(BracketCategory::Other); - } - } - - match *inner.bracket_ty_info { - Some(bracketed_inner_ty) => { - bracket_inner_ty = Some(bracketed_inner_ty.ident.clone()); - bracket_ty = Some(inner.ident.clone()); - } - None => { - bracket_ty = Some(inner.ident.clone()); - } - } - } - Ok(None) => { - let msg = format!("Fail to get the ty inner type: {:?}", field); - return Err(msg); - } - Err(e) => { - eprintln!("ASTField parser failed: {:?} with error: {}", field, e); - return Err(e); - } + pub fn new(cx: &ASTResult, field: &'a syn::Field, index: usize) -> Result { + let mut bracket_inner_ty = None; + let mut bracket_ty = None; + let mut bracket_category = Some(BracketCategory::Other); + match parse_ty(cx, &field.ty) { + Ok(Some(inner)) => { + match inner.primitive_ty { + PrimitiveTy::Map(map_info) => { + bracket_category = Some(BracketCategory::Map((map_info.key.clone(), map_info.value))) + }, + PrimitiveTy::Vec => { + bracket_category = Some(BracketCategory::Vec); + }, + PrimitiveTy::Opt => { + bracket_category = Some(BracketCategory::Opt); + }, + PrimitiveTy::Other => { + bracket_category = Some(BracketCategory::Other); + }, } - Ok(ASTField { - member: match &field.ident { - Some(ident) => syn::Member::Named(ident.clone()), - None => syn::Member::Unnamed(index.into()), - }, - pb_attrs: PBStructAttrs::from_ast(cx, index, field), - node_attrs: NodeStructAttrs::from_ast(cx, index, field), - ty: &field.ty, - original: field, - bracket_ty, - bracket_inner_ty, - bracket_category, - }) + match *inner.bracket_ty_info { + Some(bracketed_inner_ty) => { + bracket_inner_ty = Some(bracketed_inner_ty.ident.clone()); + bracket_ty = Some(inner.ident.clone()); + }, + None => { + bracket_ty = Some(inner.ident.clone()); + }, + } + }, + Ok(None) => { + let msg = format!("Fail to get the ty inner type: {:?}", field); + return Err(msg); + }, + Err(e) => { + eprintln!("ASTField parser failed: {:?} with error: {}", field, e); + return Err(e); + }, } - pub fn ty_as_str(&self) -> String { - match self.bracket_inner_ty { - Some(ref ty) => ty.to_string(), - None => self.bracket_ty.as_ref().unwrap().clone().to_string(), - } - } + Ok(ASTField { + member: match &field.ident { + Some(ident) => syn::Member::Named(ident.clone()), + None => syn::Member::Unnamed(index.into()), + }, + pb_attrs: PBStructAttrs::from_ast(cx, index, field), + node_attrs: NodeStructAttrs::from_ast(cx, index, field), + ty: &field.ty, + original: field, + bracket_ty, + bracket_inner_ty, + bracket_category, + }) + } - pub fn name(&self) -> Option { - if let syn::Member::Named(ident) = &self.member { - Some(ident.clone()) - } else { - None - } + pub fn ty_as_str(&self) -> String { + match self.bracket_inner_ty { + Some(ref ty) => ty.to_string(), + None => self.bracket_ty.as_ref().unwrap().clone().to_string(), } + } + + pub fn name(&self) -> Option { + if let syn::Member::Named(ident) = &self.member { + Some(ident.clone()) + } else { + None + } + } } #[derive(Copy, Clone)] pub enum ASTStyle { - Struct, - /// Many unnamed fields. - Tuple, - /// One unnamed field. - NewType, - /// No fields. - Unit, + Struct, + /// Many unnamed fields. + Tuple, + /// One unnamed field. + NewType, + /// No fields. + Unit, } -pub fn struct_from_ast<'a>(cx: &ASTResult, fields: &'a syn::Fields) -> (ASTStyle, Vec>) { - match fields { - syn::Fields::Named(fields) => (ASTStyle::Struct, fields_from_ast(cx, &fields.named)), - syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { - (ASTStyle::NewType, fields_from_ast(cx, &fields.unnamed)) - } - syn::Fields::Unnamed(fields) => (ASTStyle::Tuple, fields_from_ast(cx, &fields.unnamed)), - syn::Fields::Unit => (ASTStyle::Unit, Vec::new()), - } +pub fn struct_from_ast<'a>( + cx: &ASTResult, + fields: &'a syn::Fields, +) -> (ASTStyle, Vec>) { + match fields { + syn::Fields::Named(fields) => (ASTStyle::Struct, fields_from_ast(cx, &fields.named)), + syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { + (ASTStyle::NewType, fields_from_ast(cx, &fields.unnamed)) + }, + syn::Fields::Unnamed(fields) => (ASTStyle::Tuple, fields_from_ast(cx, &fields.unnamed)), + syn::Fields::Unit => (ASTStyle::Unit, Vec::new()), + } } pub fn enum_from_ast<'a>( - cx: &ASTResult, - ident: &syn::Ident, - variants: &'a Punctuated, - enum_attrs: &[syn::Attribute], + cx: &ASTResult, + ident: &syn::Ident, + variants: &'a Punctuated, + enum_attrs: &[syn::Attribute], ) -> Vec> { - variants - .iter() - .flat_map(|variant| { - let attrs = EventEnumAttrs::from_ast(cx, ident, variant, enum_attrs); - let (style, fields) = struct_from_ast(cx, &variant.fields); - Some(ASTEnumVariant { - ident: variant.ident.clone(), - attrs, - style, - fields, - original: variant, - }) - }) - .collect() + variants + .iter() + .flat_map(|variant| { + let attrs = EventEnumAttrs::from_ast(cx, ident, variant, enum_attrs); + let (style, fields) = struct_from_ast(cx, &variant.fields); + Some(ASTEnumVariant { + ident: variant.ident.clone(), + attrs, + style, + fields, + original: variant, + }) + }) + .collect() } -fn fields_from_ast<'a>(cx: &ASTResult, fields: &'a Punctuated) -> Vec> { - fields - .iter() - .enumerate() - .flat_map(|(index, field)| { - if is_recognizable_field(field) { - ASTField::new(cx, field, index).ok() - } else { - None - } - }) - .collect() +fn fields_from_ast<'a>( + cx: &ASTResult, + fields: &'a Punctuated, +) -> Vec> { + fields + .iter() + .enumerate() + .flat_map(|(index, field)| { + if is_recognizable_field(field) { + ASTField::new(cx, field, index).ok() + } else { + None + } + }) + .collect() } -fn get_node_type(ast_result: &ASTResult, struct_name: &Ident, attrs: &[syn::Attribute]) -> Option { - let mut node_type = None; - attrs - .iter() - .filter(|attr| attr.path.segments.iter().any(|s| s.ident == NODE_TYPE)) - .for_each(|attr| { - if let Ok(NameValue(named_value)) = attr.parse_meta() { - if node_type.is_some() { - ast_result.error_spanned_by(struct_name, "Duplicate node type definition"); - } - if let syn::Lit::Str(s) = named_value.lit { - node_type = Some(s.value()); - } - } - }); - node_type +fn get_node_type( + ast_result: &ASTResult, + struct_name: &Ident, + attrs: &[syn::Attribute], +) -> Option { + let mut node_type = None; + attrs + .iter() + .filter(|attr| attr.path.segments.iter().any(|s| s.ident == NODE_TYPE)) + .for_each(|attr| { + if let Ok(NameValue(named_value)) = attr.parse_meta() { + if node_type.is_some() { + ast_result.error_spanned_by(struct_name, "Duplicate node type definition"); + } + if let syn::Lit::Str(s) = named_value.lit { + node_type = Some(s.value()); + } + } + }); + node_type } diff --git a/frontend/rust-lib/flowy-ast/src/ctxt.rs b/frontend/rust-lib/flowy-ast/src/ctxt.rs index 2c98a07f0b..39940e0be1 100644 --- a/frontend/rust-lib/flowy-ast/src/ctxt.rs +++ b/frontend/rust-lib/flowy-ast/src/ctxt.rs @@ -3,41 +3,42 @@ use std::{cell::RefCell, fmt::Display, thread}; #[derive(Default)] pub struct ASTResult { - errors: RefCell>>, + errors: RefCell>>, } impl ASTResult { - pub fn new() -> Self { - ASTResult { - errors: RefCell::new(Some(Vec::new())), - } + pub fn new() -> Self { + ASTResult { + errors: RefCell::new(Some(Vec::new())), } + } - pub fn error_spanned_by(&self, obj: A, msg: T) { - self.errors - .borrow_mut() - .as_mut() - .unwrap() - .push(syn::Error::new_spanned(obj.into_token_stream(), msg)); - } + pub fn error_spanned_by(&self, obj: A, msg: T) { + self + .errors + .borrow_mut() + .as_mut() + .unwrap() + .push(syn::Error::new_spanned(obj.into_token_stream(), msg)); + } - pub fn syn_error(&self, err: syn::Error) { - self.errors.borrow_mut().as_mut().unwrap().push(err); - } + pub fn syn_error(&self, err: syn::Error) { + self.errors.borrow_mut().as_mut().unwrap().push(err); + } - pub fn check(self) -> Result<(), Vec> { - let errors = self.errors.borrow_mut().take().unwrap(); - match errors.len() { - 0 => Ok(()), - _ => Err(errors), - } + pub fn check(self) -> Result<(), Vec> { + let errors = self.errors.borrow_mut().take().unwrap(); + match errors.len() { + 0 => Ok(()), + _ => Err(errors), } + } } impl Drop for ASTResult { - fn drop(&mut self) { - if !thread::panicking() && self.errors.borrow().is_some() { - panic!("forgot to check for errors"); - } + fn drop(&mut self) { + if !thread::panicking() && self.errors.borrow().is_some() { + panic!("forgot to check for errors"); } + } } diff --git a/frontend/rust-lib/flowy-ast/src/event_attrs.rs b/frontend/rust-lib/flowy-ast/src/event_attrs.rs index 8c5739dd19..afa2b5a955 100644 --- a/frontend/rust-lib/flowy-ast/src/event_attrs.rs +++ b/frontend/rust-lib/flowy-ast/src/event_attrs.rs @@ -1,145 +1,150 @@ use crate::{get_event_meta_items, parse_lit_str, symbol::*, ASTResult}; use syn::{ - self, - Meta::{NameValue, Path}, - NestedMeta::{Lit, Meta}, + self, + Meta::{NameValue, Path}, + NestedMeta::{Lit, Meta}, }; #[derive(Debug, Clone)] pub struct EventAttrs { - input: Option, - output: Option, - error_ty: Option, - pub ignore: bool, + input: Option, + output: Option, + error_ty: Option, + pub ignore: bool, } #[derive(Debug, Clone)] pub struct EventEnumAttrs { - pub enum_name: String, - pub enum_item_name: String, - pub value: String, - pub event_attrs: EventAttrs, + pub enum_name: String, + pub enum_item_name: String, + pub value: String, + pub event_attrs: EventAttrs, } impl EventEnumAttrs { - pub fn from_ast( - ast_result: &ASTResult, - ident: &syn::Ident, - variant: &syn::Variant, - enum_attrs: &[syn::Attribute], - ) -> Self { - let enum_item_name = variant.ident.to_string(); - let enum_name = ident.to_string(); - let mut value = String::new(); - if variant.discriminant.is_some() { - if let syn::Expr::Lit(ref expr_list) = variant.discriminant.as_ref().unwrap().1 { - let lit_int = if let syn::Lit::Int(ref int_value) = expr_list.lit { - int_value - } else { - unimplemented!() - }; - value = lit_int.base10_digits().to_string(); - } - } - let event_attrs = get_event_attrs_from(ast_result, &variant.attrs, enum_attrs); - EventEnumAttrs { - enum_name, - enum_item_name, - value, - event_attrs, - } + pub fn from_ast( + ast_result: &ASTResult, + ident: &syn::Ident, + variant: &syn::Variant, + enum_attrs: &[syn::Attribute], + ) -> Self { + let enum_item_name = variant.ident.to_string(); + let enum_name = ident.to_string(); + let mut value = String::new(); + if variant.discriminant.is_some() { + if let syn::Expr::Lit(ref expr_list) = variant.discriminant.as_ref().unwrap().1 { + let lit_int = if let syn::Lit::Int(ref int_value) = expr_list.lit { + int_value + } else { + unimplemented!() + }; + value = lit_int.base10_digits().to_string(); + } } + let event_attrs = get_event_attrs_from(ast_result, &variant.attrs, enum_attrs); + EventEnumAttrs { + enum_name, + enum_item_name, + value, + event_attrs, + } + } - pub fn event_input(&self) -> Option { - self.event_attrs.input.clone() - } + pub fn event_input(&self) -> Option { + self.event_attrs.input.clone() + } - pub fn event_output(&self) -> Option { - self.event_attrs.output.clone() - } + pub fn event_output(&self) -> Option { + self.event_attrs.output.clone() + } - pub fn event_error(&self) -> String { - self.event_attrs.error_ty.as_ref().unwrap().clone() - } + pub fn event_error(&self) -> String { + self.event_attrs.error_ty.as_ref().unwrap().clone() + } } fn get_event_attrs_from( - ast_result: &ASTResult, - variant_attrs: &[syn::Attribute], - enum_attrs: &[syn::Attribute], + ast_result: &ASTResult, + variant_attrs: &[syn::Attribute], + enum_attrs: &[syn::Attribute], ) -> EventAttrs { - let mut event_attrs = EventAttrs { - input: None, - output: None, - error_ty: None, - ignore: false, - }; + let mut event_attrs = EventAttrs { + input: None, + output: None, + error_ty: None, + ignore: false, + }; - enum_attrs - .iter() - .filter(|attr| attr.path.segments.iter().any(|s| s.ident == EVENT_ERR)) - .for_each(|attr| { - if let Ok(NameValue(named_value)) = attr.parse_meta() { - if let syn::Lit::Str(s) = named_value.lit { - event_attrs.error_ty = Some(s.value()); - } else { - eprintln!("❌ {} should not be empty", EVENT_ERR); - } - } else { - eprintln!("❌ Can not find any {} on attr: {:#?}", EVENT_ERR, attr); - } - }); - - let mut extract_event_attr = |attr: &syn::Attribute, meta_item: &syn::NestedMeta| match &meta_item { - Meta(NameValue(name_value)) => { - if name_value.path == EVENT_INPUT { - if let syn::Lit::Str(s) = &name_value.lit { - let input_type = parse_lit_str(s) - .map_err(|_| { - ast_result - .error_spanned_by(s, format!("failed to parse request deserializer {:?}", s.value())) - }) - .unwrap(); - event_attrs.input = Some(input_type); - } - } - - if name_value.path == EVENT_OUTPUT { - if let syn::Lit::Str(s) = &name_value.lit { - let output_type = parse_lit_str(s) - .map_err(|_| { - ast_result - .error_spanned_by(s, format!("failed to parse response deserializer {:?}", s.value())) - }) - .unwrap(); - event_attrs.output = Some(output_type); - } - } + enum_attrs + .iter() + .filter(|attr| attr.path.segments.iter().any(|s| s.ident == EVENT_ERR)) + .for_each(|attr| { + if let Ok(NameValue(named_value)) = attr.parse_meta() { + if let syn::Lit::Str(s) = named_value.lit { + event_attrs.error_ty = Some(s.value()); + } else { + eprintln!("❌ {} should not be empty", EVENT_ERR); } - Meta(Path(word)) => { - if word == EVENT_IGNORE && attr.path == EVENT { - event_attrs.ignore = true; - } + } else { + eprintln!("❌ Can not find any {} on attr: {:#?}", EVENT_ERR, attr); + } + }); + + let mut extract_event_attr = |attr: &syn::Attribute, meta_item: &syn::NestedMeta| match &meta_item + { + Meta(NameValue(name_value)) => { + if name_value.path == EVENT_INPUT { + if let syn::Lit::Str(s) = &name_value.lit { + let input_type = parse_lit_str(s) + .map_err(|_| { + ast_result.error_spanned_by( + s, + format!("failed to parse request deserializer {:?}", s.value()), + ) + }) + .unwrap(); + event_attrs.input = Some(input_type); } - Lit(s) => ast_result.error_spanned_by(s, "unexpected attribute"), - _ => ast_result.error_spanned_by(meta_item, "unexpected attribute"), - }; + } - let attr_meta_items_info = variant_attrs - .iter() - .flat_map(|attr| match get_event_meta_items(ast_result, attr) { - Ok(items) => Some((attr, items)), - Err(_) => None, - }) - .collect::)>>(); + if name_value.path == EVENT_OUTPUT { + if let syn::Lit::Str(s) = &name_value.lit { + let output_type = parse_lit_str(s) + .map_err(|_| { + ast_result.error_spanned_by( + s, + format!("failed to parse response deserializer {:?}", s.value()), + ) + }) + .unwrap(); + event_attrs.output = Some(output_type); + } + } + }, + Meta(Path(word)) => { + if word == EVENT_IGNORE && attr.path == EVENT { + event_attrs.ignore = true; + } + }, + Lit(s) => ast_result.error_spanned_by(s, "unexpected attribute"), + _ => ast_result.error_spanned_by(meta_item, "unexpected attribute"), + }; - for (attr, nested_metas) in attr_meta_items_info { - nested_metas - .iter() - .for_each(|meta_item| extract_event_attr(attr, meta_item)) - } + let attr_meta_items_info = variant_attrs + .iter() + .flat_map(|attr| match get_event_meta_items(ast_result, attr) { + Ok(items) => Some((attr, items)), + Err(_) => None, + }) + .collect::)>>(); - // eprintln!("😁{:#?}", event_attrs); - event_attrs + for (attr, nested_metas) in attr_meta_items_info { + nested_metas + .iter() + .for_each(|meta_item| extract_event_attr(attr, meta_item)) + } + + // eprintln!("😁{:#?}", event_attrs); + event_attrs } diff --git a/frontend/rust-lib/flowy-ast/src/node_attrs.rs b/frontend/rust-lib/flowy-ast/src/node_attrs.rs index 7f2c062aca..1ffdd1c407 100644 --- a/frontend/rust-lib/flowy-ast/src/node_attrs.rs +++ b/frontend/rust-lib/flowy-ast/src/node_attrs.rs @@ -1,99 +1,106 @@ use crate::{get_node_meta_items, parse_lit_into_expr_path, symbol::*, ASTAttr, ASTResult}; use quote::ToTokens; use syn::{ - self, LitStr, - Meta::NameValue, - NestedMeta::{Lit, Meta}, + self, LitStr, + Meta::NameValue, + NestedMeta::{Lit, Meta}, }; pub struct NodeStructAttrs { - pub rename: Option, - pub has_child: bool, - pub child_name: Option, - pub child_index: Option, - pub get_node_value_with: Option, - pub set_node_value_with: Option, - pub with_children: Option, + pub rename: Option, + pub has_child: bool, + pub child_name: Option, + pub child_index: Option, + pub get_node_value_with: Option, + pub set_node_value_with: Option, + pub with_children: Option, } impl NodeStructAttrs { - /// Extract out the `#[node(...)]` attributes from a struct field. - pub fn from_ast(ast_result: &ASTResult, _index: usize, field: &syn::Field) -> Self { - let mut rename = ASTAttr::none(ast_result, RENAME_NODE); - let mut child_name = ASTAttr::none(ast_result, CHILD_NODE_NAME); - let mut child_index = ASTAttr::none(ast_result, CHILD_NODE_INDEX); - let mut get_node_value_with = ASTAttr::none(ast_result, GET_NODE_VALUE_WITH); - let mut set_node_value_with = ASTAttr::none(ast_result, SET_NODE_VALUE_WITH); - let mut with_children = ASTAttr::none(ast_result, WITH_CHILDREN); + /// Extract out the `#[node(...)]` attributes from a struct field. + pub fn from_ast(ast_result: &ASTResult, _index: usize, field: &syn::Field) -> Self { + let mut rename = ASTAttr::none(ast_result, RENAME_NODE); + let mut child_name = ASTAttr::none(ast_result, CHILD_NODE_NAME); + let mut child_index = ASTAttr::none(ast_result, CHILD_NODE_INDEX); + let mut get_node_value_with = ASTAttr::none(ast_result, GET_NODE_VALUE_WITH); + let mut set_node_value_with = ASTAttr::none(ast_result, SET_NODE_VALUE_WITH); + let mut with_children = ASTAttr::none(ast_result, WITH_CHILDREN); - for meta_item in field - .attrs - .iter() - .flat_map(|attr| get_node_meta_items(ast_result, attr)) - .flatten() - { - match &meta_item { - // Parse '#[node(rename = x)]' - Meta(NameValue(m)) if m.path == RENAME_NODE => { - if let syn::Lit::Str(lit) = &m.lit { - rename.set(&m.path, lit.clone()); - } - } + for meta_item in field + .attrs + .iter() + .flat_map(|attr| get_node_meta_items(ast_result, attr)) + .flatten() + { + match &meta_item { + // Parse '#[node(rename = x)]' + Meta(NameValue(m)) if m.path == RENAME_NODE => { + if let syn::Lit::Str(lit) = &m.lit { + rename.set(&m.path, lit.clone()); + } + }, - // Parse '#[node(child_name = x)]' - Meta(NameValue(m)) if m.path == CHILD_NODE_NAME => { - if let syn::Lit::Str(lit) = &m.lit { - child_name.set(&m.path, lit.clone()); - } - } + // Parse '#[node(child_name = x)]' + Meta(NameValue(m)) if m.path == CHILD_NODE_NAME => { + if let syn::Lit::Str(lit) = &m.lit { + child_name.set(&m.path, lit.clone()); + } + }, - // Parse '#[node(child_index = x)]' - Meta(NameValue(m)) if m.path == CHILD_NODE_INDEX => { - if let syn::Lit::Int(lit) = &m.lit { - child_index.set(&m.path, lit.clone()); - } - } + // Parse '#[node(child_index = x)]' + Meta(NameValue(m)) if m.path == CHILD_NODE_INDEX => { + if let syn::Lit::Int(lit) = &m.lit { + child_index.set(&m.path, lit.clone()); + } + }, - // Parse `#[node(get_node_value_with = "...")]` - Meta(NameValue(m)) if m.path == GET_NODE_VALUE_WITH => { - if let Ok(path) = parse_lit_into_expr_path(ast_result, GET_NODE_VALUE_WITH, &m.lit) { - get_node_value_with.set(&m.path, path); - } - } + // Parse `#[node(get_node_value_with = "...")]` + Meta(NameValue(m)) if m.path == GET_NODE_VALUE_WITH => { + if let Ok(path) = parse_lit_into_expr_path(ast_result, GET_NODE_VALUE_WITH, &m.lit) { + get_node_value_with.set(&m.path, path); + } + }, - // Parse `#[node(set_node_value_with= "...")]` - Meta(NameValue(m)) if m.path == SET_NODE_VALUE_WITH => { - if let Ok(path) = parse_lit_into_expr_path(ast_result, SET_NODE_VALUE_WITH, &m.lit) { - set_node_value_with.set(&m.path, path); - } - } + // Parse `#[node(set_node_value_with= "...")]` + Meta(NameValue(m)) if m.path == SET_NODE_VALUE_WITH => { + if let Ok(path) = parse_lit_into_expr_path(ast_result, SET_NODE_VALUE_WITH, &m.lit) { + set_node_value_with.set(&m.path, path); + } + }, - // Parse `#[node(with_children= "...")]` - Meta(NameValue(m)) if m.path == WITH_CHILDREN => { - if let Ok(path) = parse_lit_into_expr_path(ast_result, WITH_CHILDREN, &m.lit) { - with_children.set(&m.path, path); - } - } + // Parse `#[node(with_children= "...")]` + Meta(NameValue(m)) if m.path == WITH_CHILDREN => { + if let Ok(path) = parse_lit_into_expr_path(ast_result, WITH_CHILDREN, &m.lit) { + with_children.set(&m.path, path); + } + }, - Meta(meta_item) => { - let path = meta_item.path().into_token_stream().to_string().replace(' ', ""); - ast_result.error_spanned_by(meta_item.path(), format!("unknown node field attribute `{}`", path)); - } + Meta(meta_item) => { + let path = meta_item + .path() + .into_token_stream() + .to_string() + .replace(' ', ""); + ast_result.error_spanned_by( + meta_item.path(), + format!("unknown node field attribute `{}`", path), + ); + }, - Lit(lit) => { - ast_result.error_spanned_by(lit, "unexpected literal in field attribute"); - } - } - } - let child_name = child_name.get(); - NodeStructAttrs { - rename: rename.get(), - child_index: child_index.get(), - has_child: child_name.is_some(), - child_name, - get_node_value_with: get_node_value_with.get(), - set_node_value_with: set_node_value_with.get(), - with_children: with_children.get(), - } + Lit(lit) => { + ast_result.error_spanned_by(lit, "unexpected literal in field attribute"); + }, + } } + let child_name = child_name.get(); + NodeStructAttrs { + rename: rename.get(), + child_index: child_index.get(), + has_child: child_name.is_some(), + child_name, + get_node_value_with: get_node_value_with.get(), + set_node_value_with: set_node_value_with.get(), + with_children: with_children.get(), + } + } } diff --git a/frontend/rust-lib/flowy-ast/src/pb_attrs.rs b/frontend/rust-lib/flowy-ast/src/pb_attrs.rs index 7ad84149fb..3a907a01e2 100644 --- a/frontend/rust-lib/flowy-ast/src/pb_attrs.rs +++ b/frontend/rust-lib/flowy-ast/src/pb_attrs.rs @@ -4,441 +4,486 @@ use crate::{symbol::*, ASTResult}; use proc_macro2::{Group, Span, TokenStream, TokenTree}; use quote::ToTokens; use syn::{ - self, - parse::{self, Parse}, - Meta::{List, NameValue, Path}, - NestedMeta::{Lit, Meta}, + self, + parse::{self, Parse}, + Meta::{List, NameValue, Path}, + NestedMeta::{Lit, Meta}, }; #[allow(dead_code)] pub struct PBAttrsContainer { - name: String, - pb_struct_type: Option, - pb_enum_type: Option, + name: String, + pb_struct_type: Option, + pb_enum_type: Option, } impl PBAttrsContainer { - /// Extract out the `#[pb(...)]` attributes from an item. - pub fn from_ast(ast_result: &ASTResult, item: &syn::DeriveInput) -> Self { - let mut pb_struct_type = ASTAttr::none(ast_result, PB_STRUCT); - let mut pb_enum_type = ASTAttr::none(ast_result, PB_ENUM); - for meta_item in item - .attrs - .iter() - .flat_map(|attr| get_pb_meta_items(ast_result, attr)) - .flatten() - { - match &meta_item { - // Parse `#[pb(struct = "Type")] - Meta(NameValue(m)) if m.path == PB_STRUCT => { - if let Ok(into_ty) = parse_lit_into_ty(ast_result, PB_STRUCT, &m.lit) { - pb_struct_type.set_opt(&m.path, Some(into_ty)); - } - } + /// Extract out the `#[pb(...)]` attributes from an item. + pub fn from_ast(ast_result: &ASTResult, item: &syn::DeriveInput) -> Self { + let mut pb_struct_type = ASTAttr::none(ast_result, PB_STRUCT); + let mut pb_enum_type = ASTAttr::none(ast_result, PB_ENUM); + for meta_item in item + .attrs + .iter() + .flat_map(|attr| get_pb_meta_items(ast_result, attr)) + .flatten() + { + match &meta_item { + // Parse `#[pb(struct = "Type")] + Meta(NameValue(m)) if m.path == PB_STRUCT => { + if let Ok(into_ty) = parse_lit_into_ty(ast_result, PB_STRUCT, &m.lit) { + pb_struct_type.set_opt(&m.path, Some(into_ty)); + } + }, - // Parse `#[pb(enum = "Type")] - Meta(NameValue(m)) if m.path == PB_ENUM => { - if let Ok(into_ty) = parse_lit_into_ty(ast_result, PB_ENUM, &m.lit) { - pb_enum_type.set_opt(&m.path, Some(into_ty)); - } - } + // Parse `#[pb(enum = "Type")] + Meta(NameValue(m)) if m.path == PB_ENUM => { + if let Ok(into_ty) = parse_lit_into_ty(ast_result, PB_ENUM, &m.lit) { + pb_enum_type.set_opt(&m.path, Some(into_ty)); + } + }, - Meta(meta_item) => { - let path = meta_item.path().into_token_stream().to_string().replace(' ', ""); - ast_result.error_spanned_by(meta_item.path(), format!("unknown container attribute `{}`", path)); - } + Meta(meta_item) => { + let path = meta_item + .path() + .into_token_stream() + .to_string() + .replace(' ', ""); + ast_result.error_spanned_by( + meta_item.path(), + format!("unknown container attribute `{}`", path), + ); + }, - Lit(lit) => { - ast_result.error_spanned_by(lit, "unexpected literal in container attribute"); - } - } - } - match &item.data { - syn::Data::Struct(_) => { - pb_struct_type.set_if_none(default_pb_type(&ast_result, &item.ident)); - } - syn::Data::Enum(_) => { - pb_enum_type.set_if_none(default_pb_type(&ast_result, &item.ident)); - } - _ => {} - } - - PBAttrsContainer { - name: item.ident.to_string(), - pb_struct_type: pb_struct_type.get(), - pb_enum_type: pb_enum_type.get(), - } + Lit(lit) => { + ast_result.error_spanned_by(lit, "unexpected literal in container attribute"); + }, + } + } + match &item.data { + syn::Data::Struct(_) => { + pb_struct_type.set_if_none(default_pb_type(&ast_result, &item.ident)); + }, + syn::Data::Enum(_) => { + pb_enum_type.set_if_none(default_pb_type(&ast_result, &item.ident)); + }, + _ => {}, } - pub fn pb_struct_type(&self) -> Option<&syn::Type> { - self.pb_struct_type.as_ref() + PBAttrsContainer { + name: item.ident.to_string(), + pb_struct_type: pb_struct_type.get(), + pb_enum_type: pb_enum_type.get(), } + } - pub fn pb_enum_type(&self) -> Option<&syn::Type> { - self.pb_enum_type.as_ref() - } + pub fn pb_struct_type(&self) -> Option<&syn::Type> { + self.pb_struct_type.as_ref() + } + + pub fn pb_enum_type(&self) -> Option<&syn::Type> { + self.pb_enum_type.as_ref() + } } pub struct ASTAttr<'c, T> { - ast_result: &'c ASTResult, - name: Symbol, - tokens: TokenStream, - value: Option, + ast_result: &'c ASTResult, + name: Symbol, + tokens: TokenStream, + value: Option, } impl<'c, T> ASTAttr<'c, T> { - pub(crate) fn none(ast_result: &'c ASTResult, name: Symbol) -> Self { - ASTAttr { - ast_result, - name, - tokens: TokenStream::new(), - value: None, - } + pub(crate) fn none(ast_result: &'c ASTResult, name: Symbol) -> Self { + ASTAttr { + ast_result, + name, + tokens: TokenStream::new(), + value: None, } + } - pub(crate) fn set(&mut self, obj: A, value: T) { - let tokens = obj.into_token_stream(); + pub(crate) fn set(&mut self, obj: A, value: T) { + let tokens = obj.into_token_stream(); - if self.value.is_some() { - self.ast_result - .error_spanned_by(tokens, format!("duplicate attribute `{}`", self.name)); - } else { - self.tokens = tokens; - self.value = Some(value); - } + if self.value.is_some() { + self + .ast_result + .error_spanned_by(tokens, format!("duplicate attribute `{}`", self.name)); + } else { + self.tokens = tokens; + self.value = Some(value); } + } - fn set_opt(&mut self, obj: A, value: Option) { - if let Some(value) = value { - self.set(obj, value); - } + fn set_opt(&mut self, obj: A, value: Option) { + if let Some(value) = value { + self.set(obj, value); } + } - pub(crate) fn set_if_none(&mut self, value: T) { - if self.value.is_none() { - self.value = Some(value); - } + pub(crate) fn set_if_none(&mut self, value: T) { + if self.value.is_none() { + self.value = Some(value); } + } - pub(crate) fn get(self) -> Option { - self.value - } + pub(crate) fn get(self) -> Option { + self.value + } - #[allow(dead_code)] - fn get_with_tokens(self) -> Option<(TokenStream, T)> { - match self.value { - Some(v) => Some((self.tokens, v)), - None => None, - } + #[allow(dead_code)] + fn get_with_tokens(self) -> Option<(TokenStream, T)> { + match self.value { + Some(v) => Some((self.tokens, v)), + None => None, } + } } pub struct PBStructAttrs { - #[allow(dead_code)] - name: String, - pb_index: Option, - pb_one_of: bool, - skip_pb_serializing: bool, - skip_pb_deserializing: bool, - serialize_pb_with: Option, - deserialize_pb_with: Option, + #[allow(dead_code)] + name: String, + pb_index: Option, + pb_one_of: bool, + skip_pb_serializing: bool, + skip_pb_deserializing: bool, + serialize_pb_with: Option, + deserialize_pb_with: Option, } pub fn is_recognizable_field(field: &syn::Field) -> bool { - field.attrs.iter().any(|attr| is_recognizable_attribute(attr)) + field + .attrs + .iter() + .any(|attr| is_recognizable_attribute(attr)) } impl PBStructAttrs { - /// Extract out the `#[pb(...)]` attributes from a struct field. - pub fn from_ast(ast_result: &ASTResult, index: usize, field: &syn::Field) -> Self { - let mut pb_index = ASTAttr::none(ast_result, PB_INDEX); - let mut pb_one_of = BoolAttr::none(ast_result, PB_ONE_OF); - let mut serialize_pb_with = ASTAttr::none(ast_result, SERIALIZE_PB_WITH); - let mut skip_pb_serializing = BoolAttr::none(ast_result, SKIP_PB_SERIALIZING); - let mut deserialize_pb_with = ASTAttr::none(ast_result, DESERIALIZE_PB_WITH); - let mut skip_pb_deserializing = BoolAttr::none(ast_result, SKIP_PB_DESERIALIZING); + /// Extract out the `#[pb(...)]` attributes from a struct field. + pub fn from_ast(ast_result: &ASTResult, index: usize, field: &syn::Field) -> Self { + let mut pb_index = ASTAttr::none(ast_result, PB_INDEX); + let mut pb_one_of = BoolAttr::none(ast_result, PB_ONE_OF); + let mut serialize_pb_with = ASTAttr::none(ast_result, SERIALIZE_PB_WITH); + let mut skip_pb_serializing = BoolAttr::none(ast_result, SKIP_PB_SERIALIZING); + let mut deserialize_pb_with = ASTAttr::none(ast_result, DESERIALIZE_PB_WITH); + let mut skip_pb_deserializing = BoolAttr::none(ast_result, SKIP_PB_DESERIALIZING); - let ident = match &field.ident { - Some(ident) => ident.to_string(), - None => index.to_string(), - }; + let ident = match &field.ident { + Some(ident) => ident.to_string(), + None => index.to_string(), + }; - for meta_item in field - .attrs - .iter() - .flat_map(|attr| get_pb_meta_items(ast_result, attr)) - .flatten() - { - match &meta_item { - // Parse `#[pb(skip)]` - Meta(Path(word)) if word == SKIP => { - skip_pb_serializing.set_true(word); - skip_pb_deserializing.set_true(word); - } + for meta_item in field + .attrs + .iter() + .flat_map(|attr| get_pb_meta_items(ast_result, attr)) + .flatten() + { + match &meta_item { + // Parse `#[pb(skip)]` + Meta(Path(word)) if word == SKIP => { + skip_pb_serializing.set_true(word); + skip_pb_deserializing.set_true(word); + }, - // Parse '#[pb(index = x)]' - Meta(NameValue(m)) if m.path == PB_INDEX => { - if let syn::Lit::Int(lit) = &m.lit { - pb_index.set(&m.path, lit.clone()); - } - } + // Parse '#[pb(index = x)]' + Meta(NameValue(m)) if m.path == PB_INDEX => { + if let syn::Lit::Int(lit) = &m.lit { + pb_index.set(&m.path, lit.clone()); + } + }, - // Parse `#[pb(one_of)]` - Meta(Path(path)) if path == PB_ONE_OF => { - pb_one_of.set_true(path); - } + // Parse `#[pb(one_of)]` + Meta(Path(path)) if path == PB_ONE_OF => { + pb_one_of.set_true(path); + }, - // Parse `#[pb(serialize_pb_with = "...")]` - Meta(NameValue(m)) if m.path == SERIALIZE_PB_WITH => { - if let Ok(path) = parse_lit_into_expr_path(ast_result, SERIALIZE_PB_WITH, &m.lit) { - serialize_pb_with.set(&m.path, path); - } - } + // Parse `#[pb(serialize_pb_with = "...")]` + Meta(NameValue(m)) if m.path == SERIALIZE_PB_WITH => { + if let Ok(path) = parse_lit_into_expr_path(ast_result, SERIALIZE_PB_WITH, &m.lit) { + serialize_pb_with.set(&m.path, path); + } + }, - // Parse `#[pb(deserialize_pb_with = "...")]` - Meta(NameValue(m)) if m.path == DESERIALIZE_PB_WITH => { - if let Ok(path) = parse_lit_into_expr_path(ast_result, DESERIALIZE_PB_WITH, &m.lit) { - deserialize_pb_with.set(&m.path, path); - } - } + // Parse `#[pb(deserialize_pb_with = "...")]` + Meta(NameValue(m)) if m.path == DESERIALIZE_PB_WITH => { + if let Ok(path) = parse_lit_into_expr_path(ast_result, DESERIALIZE_PB_WITH, &m.lit) { + deserialize_pb_with.set(&m.path, path); + } + }, - Meta(meta_item) => { - let path = meta_item.path().into_token_stream().to_string().replace(' ', ""); - ast_result.error_spanned_by(meta_item.path(), format!("unknown pb field attribute `{}`", path)); - } + Meta(meta_item) => { + let path = meta_item + .path() + .into_token_stream() + .to_string() + .replace(' ', ""); + ast_result.error_spanned_by( + meta_item.path(), + format!("unknown pb field attribute `{}`", path), + ); + }, - Lit(lit) => { - ast_result.error_spanned_by(lit, "unexpected literal in field attribute"); - } - } - } - - PBStructAttrs { - name: ident, - pb_index: pb_index.get(), - pb_one_of: pb_one_of.get(), - skip_pb_serializing: skip_pb_serializing.get(), - skip_pb_deserializing: skip_pb_deserializing.get(), - serialize_pb_with: serialize_pb_with.get(), - deserialize_pb_with: deserialize_pb_with.get(), - } + Lit(lit) => { + ast_result.error_spanned_by(lit, "unexpected literal in field attribute"); + }, + } } - #[allow(dead_code)] - pub fn pb_index(&self) -> Option { - self.pb_index.as_ref().map(|lit| lit.base10_digits().to_string()) + PBStructAttrs { + name: ident, + pb_index: pb_index.get(), + pb_one_of: pb_one_of.get(), + skip_pb_serializing: skip_pb_serializing.get(), + skip_pb_deserializing: skip_pb_deserializing.get(), + serialize_pb_with: serialize_pb_with.get(), + deserialize_pb_with: deserialize_pb_with.get(), } + } - pub fn is_one_of(&self) -> bool { - self.pb_one_of - } + #[allow(dead_code)] + pub fn pb_index(&self) -> Option { + self + .pb_index + .as_ref() + .map(|lit| lit.base10_digits().to_string()) + } - pub fn serialize_pb_with(&self) -> Option<&syn::ExprPath> { - self.serialize_pb_with.as_ref() - } + pub fn is_one_of(&self) -> bool { + self.pb_one_of + } - pub fn deserialize_pb_with(&self) -> Option<&syn::ExprPath> { - self.deserialize_pb_with.as_ref() - } + pub fn serialize_pb_with(&self) -> Option<&syn::ExprPath> { + self.serialize_pb_with.as_ref() + } - pub fn skip_pb_serializing(&self) -> bool { - self.skip_pb_serializing - } + pub fn deserialize_pb_with(&self) -> Option<&syn::ExprPath> { + self.deserialize_pb_with.as_ref() + } - pub fn skip_pb_deserializing(&self) -> bool { - self.skip_pb_deserializing - } + pub fn skip_pb_serializing(&self) -> bool { + self.skip_pb_serializing + } + + pub fn skip_pb_deserializing(&self) -> bool { + self.skip_pb_deserializing + } } pub enum Default { - /// Field must always be specified because it does not have a default. - None, - /// The default is given by `std::default::Default::default()`. - Default, - /// The default is given by this function. - Path(syn::ExprPath), + /// Field must always be specified because it does not have a default. + None, + /// The default is given by `std::default::Default::default()`. + Default, + /// The default is given by this function. + Path(syn::ExprPath), } pub fn is_recognizable_attribute(attr: &syn::Attribute) -> bool { - attr.path == PB_ATTRS || attr.path == EVENT || attr.path == NODE_ATTRS || attr.path == NODES_ATTRS + attr.path == PB_ATTRS || attr.path == EVENT || attr.path == NODE_ATTRS || attr.path == NODES_ATTRS } -pub fn get_pb_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result, ()> { - // Only handle the attribute that we have defined - if attr.path != PB_ATTRS { - return Ok(vec![]); - } +pub fn get_pb_meta_items( + cx: &ASTResult, + attr: &syn::Attribute, +) -> Result, ()> { + // Only handle the attribute that we have defined + if attr.path != PB_ATTRS { + return Ok(vec![]); + } - // http://strymon.systems.ethz.ch/typename/syn/enum.Meta.html - match attr.parse_meta() { - Ok(List(meta)) => Ok(meta.nested.into_iter().collect()), - Ok(other) => { - cx.error_spanned_by(other, "expected #[pb(...)]"); - Err(()) - } - Err(err) => { - cx.error_spanned_by(attr, "attribute must be str, e.g. #[pb(xx = \"xxx\")]"); - cx.syn_error(err); - Err(()) - } - } + // http://strymon.systems.ethz.ch/typename/syn/enum.Meta.html + match attr.parse_meta() { + Ok(List(meta)) => Ok(meta.nested.into_iter().collect()), + Ok(other) => { + cx.error_spanned_by(other, "expected #[pb(...)]"); + Err(()) + }, + Err(err) => { + cx.error_spanned_by(attr, "attribute must be str, e.g. #[pb(xx = \"xxx\")]"); + cx.syn_error(err); + Err(()) + }, + } } -pub fn get_node_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result, ()> { - // Only handle the attribute that we have defined - if attr.path != NODE_ATTRS && attr.path != NODES_ATTRS { - return Ok(vec![]); - } +pub fn get_node_meta_items( + cx: &ASTResult, + attr: &syn::Attribute, +) -> Result, ()> { + // Only handle the attribute that we have defined + if attr.path != NODE_ATTRS && attr.path != NODES_ATTRS { + return Ok(vec![]); + } - // http://strymon.systems.ethz.ch/typename/syn/enum.Meta.html - match attr.parse_meta() { - Ok(List(meta)) => Ok(meta.nested.into_iter().collect()), - Ok(_) => Ok(vec![]), - Err(err) => { - cx.error_spanned_by(attr, "attribute must be str, e.g. #[node(xx = \"xxx\")]"); - cx.syn_error(err); - Err(()) - } - } + // http://strymon.systems.ethz.ch/typename/syn/enum.Meta.html + match attr.parse_meta() { + Ok(List(meta)) => Ok(meta.nested.into_iter().collect()), + Ok(_) => Ok(vec![]), + Err(err) => { + cx.error_spanned_by(attr, "attribute must be str, e.g. #[node(xx = \"xxx\")]"); + cx.syn_error(err); + Err(()) + }, + } } -pub fn get_event_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result, ()> { - // Only handle the attribute that we have defined - if attr.path != EVENT { - return Ok(vec![]); - } +pub fn get_event_meta_items( + cx: &ASTResult, + attr: &syn::Attribute, +) -> Result, ()> { + // Only handle the attribute that we have defined + if attr.path != EVENT { + return Ok(vec![]); + } - // http://strymon.systems.ethz.ch/typename/syn/enum.Meta.html - match attr.parse_meta() { - Ok(List(meta)) => Ok(meta.nested.into_iter().collect()), - Ok(other) => { - cx.error_spanned_by(other, "expected #[event(...)]"); - Err(()) - } - Err(err) => { - cx.error_spanned_by(attr, "attribute must be str, e.g. #[event(xx = \"xxx\")]"); - cx.syn_error(err); - Err(()) - } - } + // http://strymon.systems.ethz.ch/typename/syn/enum.Meta.html + match attr.parse_meta() { + Ok(List(meta)) => Ok(meta.nested.into_iter().collect()), + Ok(other) => { + cx.error_spanned_by(other, "expected #[event(...)]"); + Err(()) + }, + Err(err) => { + cx.error_spanned_by(attr, "attribute must be str, e.g. #[event(xx = \"xxx\")]"); + cx.syn_error(err); + Err(()) + }, + } } pub fn parse_lit_into_expr_path( - ast_result: &ASTResult, - attr_name: Symbol, - lit: &syn::Lit, + ast_result: &ASTResult, + attr_name: Symbol, + lit: &syn::Lit, ) -> Result { - let string = get_lit_str(ast_result, attr_name, lit)?; - parse_lit_str(string) - .map_err(|_| ast_result.error_spanned_by(lit, format!("failed to parse path: {:?}", string.value()))) + let string = get_lit_str(ast_result, attr_name, lit)?; + parse_lit_str(string).map_err(|_| { + ast_result.error_spanned_by(lit, format!("failed to parse path: {:?}", string.value())) + }) } -fn get_lit_str<'a>(ast_result: &ASTResult, attr_name: Symbol, lit: &'a syn::Lit) -> Result<&'a syn::LitStr, ()> { - if let syn::Lit::Str(lit) = lit { - Ok(lit) - } else { - ast_result.error_spanned_by( - lit, - format!( - "expected pb {} attribute to be a string: `{} = \"...\"`", - attr_name, attr_name - ), - ); - Err(()) - } +fn get_lit_str<'a>( + ast_result: &ASTResult, + attr_name: Symbol, + lit: &'a syn::Lit, +) -> Result<&'a syn::LitStr, ()> { + if let syn::Lit::Str(lit) = lit { + Ok(lit) + } else { + ast_result.error_spanned_by( + lit, + format!( + "expected pb {} attribute to be a string: `{} = \"...\"`", + attr_name, attr_name + ), + ); + Err(()) + } } -fn parse_lit_into_ty(ast_result: &ASTResult, attr_name: Symbol, lit: &syn::Lit) -> Result { - let string = get_lit_str(ast_result, attr_name, lit)?; +fn parse_lit_into_ty( + ast_result: &ASTResult, + attr_name: Symbol, + lit: &syn::Lit, +) -> Result { + let string = get_lit_str(ast_result, attr_name, lit)?; - parse_lit_str(string).map_err(|_| { - ast_result.error_spanned_by( - lit, - format!("failed to parse type: {} = {:?}", attr_name, string.value()), - ) - }) + parse_lit_str(string).map_err(|_| { + ast_result.error_spanned_by( + lit, + format!("failed to parse type: {} = {:?}", attr_name, string.value()), + ) + }) } pub fn parse_lit_str(s: &syn::LitStr) -> parse::Result where - T: Parse, + T: Parse, { - let tokens = spanned_tokens(s)?; - syn::parse2(tokens) + let tokens = spanned_tokens(s)?; + syn::parse2(tokens) } fn spanned_tokens(s: &syn::LitStr) -> parse::Result { - let stream = syn::parse_str(&s.value())?; - Ok(respan_token_stream(stream, s.span())) + let stream = syn::parse_str(&s.value())?; + Ok(respan_token_stream(stream, s.span())) } fn respan_token_stream(stream: TokenStream, span: Span) -> TokenStream { - stream.into_iter().map(|token| respan_token_tree(token, span)).collect() + stream + .into_iter() + .map(|token| respan_token_tree(token, span)) + .collect() } fn respan_token_tree(mut token: TokenTree, span: Span) -> TokenTree { - if let TokenTree::Group(g) = &mut token { - *g = Group::new(g.delimiter(), respan_token_stream(g.stream(), span)); - } - token.set_span(span); - token + if let TokenTree::Group(g) = &mut token { + *g = Group::new(g.delimiter(), respan_token_stream(g.stream(), span)); + } + token.set_span(span); + token } fn default_pb_type(ast_result: &ASTResult, ident: &syn::Ident) -> syn::Type { - let take_ident = ident.to_string(); - let lit_str = syn::LitStr::new(&take_ident, ident.span()); - if let Ok(tokens) = spanned_tokens(&lit_str) { - if let Ok(pb_struct_ty) = syn::parse2(tokens) { - return pb_struct_ty; - } + let take_ident = ident.to_string(); + let lit_str = syn::LitStr::new(&take_ident, ident.span()); + if let Ok(tokens) = spanned_tokens(&lit_str) { + if let Ok(pb_struct_ty) = syn::parse2(tokens) { + return pb_struct_ty; } - ast_result.error_spanned_by(ident, format!("❌ Can't find {} protobuf struct", take_ident)); - panic!() + } + ast_result.error_spanned_by( + ident, + format!("❌ Can't find {} protobuf struct", take_ident), + ); + panic!() } #[allow(dead_code)] pub fn is_option(ty: &syn::Type) -> bool { - let path = match ungroup(ty) { - syn::Type::Path(ty) => &ty.path, - _ => { - return false; - } - }; - let seg = match path.segments.last() { - Some(seg) => seg, - None => { - return false; - } - }; - let args = match &seg.arguments { - syn::PathArguments::AngleBracketed(bracketed) => &bracketed.args, - _ => { - return false; - } - }; - seg.ident == "Option" && args.len() == 1 + let path = match ungroup(ty) { + syn::Type::Path(ty) => &ty.path, + _ => { + return false; + }, + }; + let seg = match path.segments.last() { + Some(seg) => seg, + None => { + return false; + }, + }; + let args = match &seg.arguments { + syn::PathArguments::AngleBracketed(bracketed) => &bracketed.args, + _ => { + return false; + }, + }; + seg.ident == "Option" && args.len() == 1 } #[allow(dead_code)] pub fn ungroup(mut ty: &syn::Type) -> &syn::Type { - while let syn::Type::Group(group) = ty { - ty = &group.elem; - } - ty + while let syn::Type::Group(group) = ty { + ty = &group.elem; + } + ty } struct BoolAttr<'c>(ASTAttr<'c, ()>); impl<'c> BoolAttr<'c> { - fn none(ast_result: &'c ASTResult, name: Symbol) -> Self { - BoolAttr(ASTAttr::none(ast_result, name)) - } + fn none(ast_result: &'c ASTResult, name: Symbol) -> Self { + BoolAttr(ASTAttr::none(ast_result, name)) + } - fn set_true(&mut self, obj: A) { - self.0.set(obj, ()); - } + fn set_true(&mut self, obj: A) { + self.0.set(obj, ()); + } - fn get(&self) -> bool { - self.0.value.is_some() - } + fn get(&self) -> bool { + self.0.value.is_some() + } } diff --git a/frontend/rust-lib/flowy-ast/src/symbol.rs b/frontend/rust-lib/flowy-ast/src/symbol.rs index b0d1bd9ada..60e994d212 100644 --- a/frontend/rust-lib/flowy-ast/src/symbol.rs +++ b/frontend/rust-lib/flowy-ast/src/symbol.rs @@ -48,31 +48,31 @@ pub const GET_MUT_VEC_ELEMENT_WITH: Symbol = Symbol("get_mut_element_with"); pub const WITH_CHILDREN: Symbol = Symbol("with_children"); impl PartialEq for Ident { - fn eq(&self, word: &Symbol) -> bool { - self == word.0 - } + fn eq(&self, word: &Symbol) -> bool { + self == word.0 + } } impl<'a> PartialEq for &'a Ident { - fn eq(&self, word: &Symbol) -> bool { - *self == word.0 - } + fn eq(&self, word: &Symbol) -> bool { + *self == word.0 + } } impl PartialEq for Path { - fn eq(&self, word: &Symbol) -> bool { - self.is_ident(word.0) - } + fn eq(&self, word: &Symbol) -> bool { + self.is_ident(word.0) + } } impl<'a> PartialEq for &'a Path { - fn eq(&self, word: &Symbol) -> bool { - self.is_ident(word.0) - } + fn eq(&self, word: &Symbol) -> bool { + self.is_ident(word.0) + } } impl Display for Symbol { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str(self.0) - } + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(self.0) + } } diff --git a/frontend/rust-lib/flowy-ast/src/ty_ext.rs b/frontend/rust-lib/flowy-ast/src/ty_ext.rs index 6da5054ebb..0606491d0a 100644 --- a/frontend/rust-lib/flowy-ast/src/ty_ext.rs +++ b/frontend/rust-lib/flowy-ast/src/ty_ext.rs @@ -3,151 +3,154 @@ use syn::{self, AngleBracketedGenericArguments, PathSegment}; #[derive(Eq, PartialEq, Debug)] pub enum PrimitiveTy { - Map(MapInfo), - Vec, - Opt, - Other, + Map(MapInfo), + Vec, + Opt, + Other, } #[derive(Debug)] pub struct TyInfo<'a> { - pub ident: &'a syn::Ident, - pub ty: &'a syn::Type, - pub primitive_ty: PrimitiveTy, - pub bracket_ty_info: Box>>, + pub ident: &'a syn::Ident, + pub ty: &'a syn::Type, + pub primitive_ty: PrimitiveTy, + pub bracket_ty_info: Box>>, } #[derive(Debug, Eq, PartialEq)] pub struct MapInfo { - pub key: String, - pub value: String, + pub key: String, + pub value: String, } impl MapInfo { - fn new(key: String, value: String) -> Self { - MapInfo { key, value } - } + fn new(key: String, value: String) -> Self { + MapInfo { key, value } + } } impl<'a> TyInfo<'a> { - #[allow(dead_code)] - pub fn bracketed_ident(&'a self) -> &'a syn::Ident { - match self.bracket_ty_info.as_ref() { - Some(b_ty) => b_ty.ident, - None => { - panic!() - } - } + #[allow(dead_code)] + pub fn bracketed_ident(&'a self) -> &'a syn::Ident { + match self.bracket_ty_info.as_ref() { + Some(b_ty) => b_ty.ident, + None => { + panic!() + }, } + } } -pub fn parse_ty<'a>(ast_result: &ASTResult, ty: &'a syn::Type) -> Result>, String> { - // Type -> TypePath -> Path -> PathSegment -> PathArguments -> - // AngleBracketedGenericArguments -> GenericArgument -> Type. - if let syn::Type::Path(ref p) = ty { - if p.path.segments.len() != 1 { - return Ok(None); - } - - let seg = match p.path.segments.last() { - Some(seg) => seg, - None => return Ok(None), - }; - - let _is_option = seg.ident == "Option"; - - return if let syn::PathArguments::AngleBracketed(ref bracketed) = seg.arguments { - match seg.ident.to_string().as_ref() { - "HashMap" => generate_hashmap_ty_info(ast_result, ty, seg, bracketed), - "Vec" => generate_vec_ty_info(ast_result, seg, bracketed), - "Option" => generate_option_ty_info(ast_result, ty, seg, bracketed), - _ => { - let msg = format!("Unsupported type: {}", seg.ident); - ast_result.error_spanned_by(&seg.ident, &msg); - return Err(msg); - } - } - } else { - return Ok(Some(TyInfo { - ident: &seg.ident, - ty, - primitive_ty: PrimitiveTy::Other, - bracket_ty_info: Box::new(None), - })); - }; +pub fn parse_ty<'a>( + ast_result: &ASTResult, + ty: &'a syn::Type, +) -> Result>, String> { + // Type -> TypePath -> Path -> PathSegment -> PathArguments -> + // AngleBracketedGenericArguments -> GenericArgument -> Type. + if let syn::Type::Path(ref p) = ty { + if p.path.segments.len() != 1 { + return Ok(None); } - Err("Unsupported inner type, get inner type fail".to_string()) + + let seg = match p.path.segments.last() { + Some(seg) => seg, + None => return Ok(None), + }; + + let _is_option = seg.ident == "Option"; + + return if let syn::PathArguments::AngleBracketed(ref bracketed) = seg.arguments { + match seg.ident.to_string().as_ref() { + "HashMap" => generate_hashmap_ty_info(ast_result, ty, seg, bracketed), + "Vec" => generate_vec_ty_info(ast_result, seg, bracketed), + "Option" => generate_option_ty_info(ast_result, ty, seg, bracketed), + _ => { + let msg = format!("Unsupported type: {}", seg.ident); + ast_result.error_spanned_by(&seg.ident, &msg); + return Err(msg); + }, + } + } else { + return Ok(Some(TyInfo { + ident: &seg.ident, + ty, + primitive_ty: PrimitiveTy::Other, + bracket_ty_info: Box::new(None), + })); + }; + } + Err("Unsupported inner type, get inner type fail".to_string()) } fn parse_bracketed(bracketed: &AngleBracketedGenericArguments) -> Vec<&syn::Type> { - bracketed - .args - .iter() - .flat_map(|arg| { - if let syn::GenericArgument::Type(ref ty_in_bracket) = arg { - Some(ty_in_bracket) - } else { - None - } - }) - .collect::>() + bracketed + .args + .iter() + .flat_map(|arg| { + if let syn::GenericArgument::Type(ref ty_in_bracket) = arg { + Some(ty_in_bracket) + } else { + None + } + }) + .collect::>() } pub fn generate_hashmap_ty_info<'a>( - ast_result: &ASTResult, - ty: &'a syn::Type, - path_segment: &'a PathSegment, - bracketed: &'a AngleBracketedGenericArguments, + ast_result: &ASTResult, + ty: &'a syn::Type, + path_segment: &'a PathSegment, + bracketed: &'a AngleBracketedGenericArguments, ) -> Result>, String> { - // The args of map must greater than 2 - if bracketed.args.len() != 2 { - return Ok(None); - } - let types = parse_bracketed(bracketed); - let key = parse_ty(ast_result, types[0])?.unwrap().ident.to_string(); - let value = parse_ty(ast_result, types[1])?.unwrap().ident.to_string(); - let bracket_ty_info = Box::new(parse_ty(ast_result, types[1])?); - Ok(Some(TyInfo { - ident: &path_segment.ident, - ty, - primitive_ty: PrimitiveTy::Map(MapInfo::new(key, value)), - bracket_ty_info, - })) + // The args of map must greater than 2 + if bracketed.args.len() != 2 { + return Ok(None); + } + let types = parse_bracketed(bracketed); + let key = parse_ty(ast_result, types[0])?.unwrap().ident.to_string(); + let value = parse_ty(ast_result, types[1])?.unwrap().ident.to_string(); + let bracket_ty_info = Box::new(parse_ty(ast_result, types[1])?); + Ok(Some(TyInfo { + ident: &path_segment.ident, + ty, + primitive_ty: PrimitiveTy::Map(MapInfo::new(key, value)), + bracket_ty_info, + })) } fn generate_option_ty_info<'a>( - ast_result: &ASTResult, - ty: &'a syn::Type, - path_segment: &'a PathSegment, - bracketed: &'a AngleBracketedGenericArguments, + ast_result: &ASTResult, + ty: &'a syn::Type, + path_segment: &'a PathSegment, + bracketed: &'a AngleBracketedGenericArguments, ) -> Result>, String> { - assert_eq!(path_segment.ident.to_string(), "Option".to_string()); - let types = parse_bracketed(bracketed); - let bracket_ty_info = Box::new(parse_ty(ast_result, types[0])?); - Ok(Some(TyInfo { - ident: &path_segment.ident, - ty, - primitive_ty: PrimitiveTy::Opt, - bracket_ty_info, - })) + assert_eq!(path_segment.ident.to_string(), "Option".to_string()); + let types = parse_bracketed(bracketed); + let bracket_ty_info = Box::new(parse_ty(ast_result, types[0])?); + Ok(Some(TyInfo { + ident: &path_segment.ident, + ty, + primitive_ty: PrimitiveTy::Opt, + bracket_ty_info, + })) } fn generate_vec_ty_info<'a>( - ast_result: &ASTResult, - path_segment: &'a PathSegment, - bracketed: &'a AngleBracketedGenericArguments, + ast_result: &ASTResult, + path_segment: &'a PathSegment, + bracketed: &'a AngleBracketedGenericArguments, ) -> Result>, String> { - if bracketed.args.len() != 1 { - return Ok(None); - } - if let syn::GenericArgument::Type(ref bracketed_type) = bracketed.args.first().unwrap() { - let bracketed_ty_info = Box::new(parse_ty(ast_result, bracketed_type)?); - return Ok(Some(TyInfo { - ident: &path_segment.ident, - ty: bracketed_type, - primitive_ty: PrimitiveTy::Vec, - bracket_ty_info: bracketed_ty_info, - })); - } - Ok(None) + if bracketed.args.len() != 1 { + return Ok(None); + } + if let syn::GenericArgument::Type(ref bracketed_type) = bracketed.args.first().unwrap() { + let bracketed_ty_info = Box::new(parse_ty(ast_result, bracketed_type)?); + return Ok(Some(TyInfo { + ident: &path_segment.ident, + ty: bracketed_type, + primitive_ty: PrimitiveTy::Vec, + bracket_ty_info: bracketed_ty_info, + })); + } + Ok(None) } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_database/block_revision_pad.rs b/frontend/rust-lib/flowy-client-sync/src/client_database/block_revision_pad.rs index 75e3a2b89b..ea08675831 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_database/block_revision_pad.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_database/block_revision_pad.rs @@ -1,7 +1,9 @@ use crate::errors::{SyncError, SyncResult}; use crate::util::cal_diff; use flowy_sync::util::make_operations_from_revisions; -use grid_model::{gen_block_id, gen_row_id, CellRevision, DatabaseBlockRevision, RowChangeset, RowRevision}; +use grid_model::{ + gen_block_id, gen_row_id, CellRevision, DatabaseBlockRevision, RowChangeset, RowRevision, +}; use lib_infra::util::md5; use lib_ot::core::{DeltaBuilder, DeltaOperations, EmptyAttributes, OperationTransform}; use revision_model::Revision; @@ -14,425 +16,463 @@ pub type GridBlockOperationsBuilder = DeltaBuilder; #[derive(Debug, Clone)] pub struct GridBlockRevisionPad { - block: DatabaseBlockRevision, - operations: GridBlockOperations, + block: DatabaseBlockRevision, + operations: GridBlockOperations, } impl std::ops::Deref for GridBlockRevisionPad { - type Target = DatabaseBlockRevision; + type Target = DatabaseBlockRevision; - fn deref(&self) -> &Self::Target { - &self.block - } + fn deref(&self) -> &Self::Target { + &self.block + } } impl GridBlockRevisionPad { - pub fn duplicate_data(&self, duplicated_block_id: &str) -> DatabaseBlockRevision { - let duplicated_rows = self - .block - .rows + pub fn duplicate_data(&self, duplicated_block_id: &str) -> DatabaseBlockRevision { + let duplicated_rows = self + .block + .rows + .iter() + .map(|row| { + let mut duplicated_row = row.as_ref().clone(); + duplicated_row.id = gen_row_id(); + duplicated_row.block_id = duplicated_block_id.to_string(); + Arc::new(duplicated_row) + }) + .collect::>>(); + DatabaseBlockRevision { + block_id: duplicated_block_id.to_string(), + rows: duplicated_rows, + } + } + + pub fn from_operations(operations: GridBlockOperations) -> SyncResult { + let s = operations.content()?; + let revision: DatabaseBlockRevision = serde_json::from_str(&s).map_err(|e| { + let msg = format!("Deserialize operations to GridBlockRevision failed: {}", e); + tracing::error!("{}", s); + SyncError::internal().context(msg) + })?; + Ok(Self { + block: revision, + operations, + }) + } + + pub fn from_revisions(_grid_id: &str, revisions: Vec) -> SyncResult { + let operations: GridBlockOperations = make_operations_from_revisions(revisions)?; + Self::from_operations(operations) + } + + #[tracing::instrument(level = "trace", skip(self, row), err)] + pub fn add_row_rev( + &mut self, + row: RowRevision, + start_row_id: Option, + ) -> SyncResult> { + self.modify(|rows| { + if let Some(start_row_id) = start_row_id { + if !start_row_id.is_empty() { + if let Some(index) = rows.iter().position(|row| row.id == start_row_id) { + rows.insert(index + 1, Arc::new(row)); + return Ok(Some(())); + } + } + } + + rows.push(Arc::new(row)); + Ok(Some(())) + }) + } + + pub fn delete_rows( + &mut self, + row_ids: Vec>, + ) -> SyncResult> { + self.modify(|rows| { + rows.retain(|row| !row_ids.contains(&Cow::Borrowed(&row.id))); + Ok(Some(())) + }) + } + + pub fn get_row_rev(&self, row_id: &str) -> Option<(usize, Arc)> { + for (index, row) in self.block.rows.iter().enumerate() { + if row.id == row_id { + return Some((index, row.clone())); + } + } + None + } + + pub fn get_row_revs( + &self, + row_ids: Option>>, + ) -> SyncResult>> + where + T: AsRef + ToOwned + ?Sized, + { + match row_ids { + None => Ok(self.block.rows.clone()), + Some(row_ids) => { + let row_map = self + .block + .rows + .iter() + .map(|row| (row.id.as_str(), row.clone())) + .collect::>>(); + + Ok( + row_ids .iter() - .map(|row| { - let mut duplicated_row = row.as_ref().clone(); - duplicated_row.id = gen_row_id(); - duplicated_row.block_id = duplicated_block_id.to_string(); - Arc::new(duplicated_row) + .flat_map(|row_id| { + let row_id = row_id.as_ref().as_ref(); + match row_map.get(row_id) { + None => { + tracing::error!("Can't find the row with id: {}", row_id); + None + }, + Some(row) => Some(row.clone()), + } }) - .collect::>>(); - DatabaseBlockRevision { - block_id: duplicated_block_id.to_string(), - rows: duplicated_rows, + .collect::>(), + ) + }, + } + } + + pub fn get_cell_revs( + &self, + field_id: &str, + row_ids: Option>>, + ) -> SyncResult> { + let rows = self.get_row_revs(row_ids)?; + let cell_revs = rows + .iter() + .flat_map(|row| { + let cell_rev = row.cells.get(field_id)?; + Some(cell_rev.clone()) + }) + .collect::>(); + Ok(cell_revs) + } + + pub fn number_of_rows(&self) -> i32 { + self.block.rows.len() as i32 + } + + pub fn index_of_row(&self, row_id: &str) -> Option { + self.block.rows.iter().position(|row| row.id == row_id) + } + + pub fn update_row( + &mut self, + changeset: RowChangeset, + ) -> SyncResult> { + let row_id = changeset.row_id.clone(); + self.modify_row(&row_id, |row| { + let mut is_changed = None; + if let Some(height) = changeset.height { + row.height = height; + is_changed = Some(()); + } + + if let Some(visibility) = changeset.visibility { + row.visibility = visibility; + is_changed = Some(()); + } + + if !changeset.cell_by_field_id.is_empty() { + is_changed = Some(()); + changeset + .cell_by_field_id + .into_iter() + .for_each(|(field_id, cell)| { + row.cells.insert(field_id, cell); + }) + } + + Ok(is_changed) + }) + } + + pub fn move_row( + &mut self, + row_id: &str, + from: usize, + to: usize, + ) -> SyncResult> { + self.modify(|row_revs| { + if let Some(position) = row_revs.iter().position(|row_rev| row_rev.id == row_id) { + debug_assert_eq!(from, position); + let row_rev = row_revs.remove(position); + if to > row_revs.len() { + Err(SyncError::out_of_bound()) + } else { + row_revs.insert(to, row_rev); + Ok(Some(())) } - } + } else { + Ok(None) + } + }) + } - pub fn from_operations(operations: GridBlockOperations) -> SyncResult { - let s = operations.content()?; - let revision: DatabaseBlockRevision = serde_json::from_str(&s).map_err(|e| { - let msg = format!("Deserialize operations to GridBlockRevision failed: {}", e); - tracing::error!("{}", s); - SyncError::internal().context(msg) - })?; - Ok(Self { - block: revision, - operations, - }) - } - - pub fn from_revisions(_grid_id: &str, revisions: Vec) -> SyncResult { - let operations: GridBlockOperations = make_operations_from_revisions(revisions)?; - Self::from_operations(operations) - } - - #[tracing::instrument(level = "trace", skip(self, row), err)] - pub fn add_row_rev( - &mut self, - row: RowRevision, - start_row_id: Option, - ) -> SyncResult> { - self.modify(|rows| { - if let Some(start_row_id) = start_row_id { - if !start_row_id.is_empty() { - if let Some(index) = rows.iter().position(|row| row.id == start_row_id) { - rows.insert(index + 1, Arc::new(row)); - return Ok(Some(())); - } - } - } - - rows.push(Arc::new(row)); - Ok(Some(())) - }) - } - - pub fn delete_rows(&mut self, row_ids: Vec>) -> SyncResult> { - self.modify(|rows| { - rows.retain(|row| !row_ids.contains(&Cow::Borrowed(&row.id))); - Ok(Some(())) - }) - } - - pub fn get_row_rev(&self, row_id: &str) -> Option<(usize, Arc)> { - for (index, row) in self.block.rows.iter().enumerate() { - if row.id == row_id { - return Some((index, row.clone())); - } + pub fn modify(&mut self, f: F) -> SyncResult> + where + F: for<'a> FnOnce(&'a mut Vec>) -> SyncResult>, + { + let cloned_self = self.clone(); + match f(&mut self.block.rows)? { + None => Ok(None), + Some(_) => { + let old = cloned_self.revision_json()?; + let new = self.revision_json()?; + match cal_diff::(old, new) { + None => Ok(None), + Some(operations) => { + tracing::trace!( + "[GridBlockRevision] Composing operations {}", + operations.json_str() + ); + self.operations = self.operations.compose(&operations)?; + Ok(Some(GridBlockRevisionChangeset { + operations, + md5: md5(&self.operations.json_bytes()), + })) + }, } - None + }, } + } - pub fn get_row_revs(&self, row_ids: Option>>) -> SyncResult>> - where - T: AsRef + ToOwned + ?Sized, - { - match row_ids { - None => Ok(self.block.rows.clone()), - Some(row_ids) => { - let row_map = self - .block - .rows - .iter() - .map(|row| (row.id.as_str(), row.clone())) - .collect::>>(); + fn modify_row(&mut self, row_id: &str, f: F) -> SyncResult> + where + F: FnOnce(&mut RowRevision) -> SyncResult>, + { + self.modify(|rows| { + if let Some(row_rev) = rows.iter_mut().find(|row_rev| row_id == row_rev.id) { + f(Arc::make_mut(row_rev)) + } else { + tracing::warn!("[BlockMetaPad]: Can't find any row with id: {}", row_id); + Ok(None) + } + }) + } - Ok(row_ids - .iter() - .flat_map(|row_id| { - let row_id = row_id.as_ref().as_ref(); - match row_map.get(row_id) { - None => { - tracing::error!("Can't find the row with id: {}", row_id); - None - } - Some(row) => Some(row.clone()), - } - }) - .collect::>()) - } - } - } + pub fn revision_json(&self) -> SyncResult { + serde_json::to_string(&self.block) + .map_err(|e| SyncError::internal().context(format!("serial block to json failed: {}", e))) + } - pub fn get_cell_revs( - &self, - field_id: &str, - row_ids: Option>>, - ) -> SyncResult> { - let rows = self.get_row_revs(row_ids)?; - let cell_revs = rows - .iter() - .flat_map(|row| { - let cell_rev = row.cells.get(field_id)?; - Some(cell_rev.clone()) - }) - .collect::>(); - Ok(cell_revs) - } - - pub fn number_of_rows(&self) -> i32 { - self.block.rows.len() as i32 - } - - pub fn index_of_row(&self, row_id: &str) -> Option { - self.block.rows.iter().position(|row| row.id == row_id) - } - - pub fn update_row(&mut self, changeset: RowChangeset) -> SyncResult> { - let row_id = changeset.row_id.clone(); - self.modify_row(&row_id, |row| { - let mut is_changed = None; - if let Some(height) = changeset.height { - row.height = height; - is_changed = Some(()); - } - - if let Some(visibility) = changeset.visibility { - row.visibility = visibility; - is_changed = Some(()); - } - - if !changeset.cell_by_field_id.is_empty() { - is_changed = Some(()); - changeset.cell_by_field_id.into_iter().for_each(|(field_id, cell)| { - row.cells.insert(field_id, cell); - }) - } - - Ok(is_changed) - }) - } - - pub fn move_row(&mut self, row_id: &str, from: usize, to: usize) -> SyncResult> { - self.modify(|row_revs| { - if let Some(position) = row_revs.iter().position(|row_rev| row_rev.id == row_id) { - debug_assert_eq!(from, position); - let row_rev = row_revs.remove(position); - if to > row_revs.len() { - Err(SyncError::out_of_bound()) - } else { - row_revs.insert(to, row_rev); - Ok(Some(())) - } - } else { - Ok(None) - } - }) - } - - pub fn modify(&mut self, f: F) -> SyncResult> - where - F: for<'a> FnOnce(&'a mut Vec>) -> SyncResult>, - { - let cloned_self = self.clone(); - match f(&mut self.block.rows)? { - None => Ok(None), - Some(_) => { - let old = cloned_self.revision_json()?; - let new = self.revision_json()?; - match cal_diff::(old, new) { - None => Ok(None), - Some(operations) => { - tracing::trace!("[GridBlockRevision] Composing operations {}", operations.json_str()); - self.operations = self.operations.compose(&operations)?; - Ok(Some(GridBlockRevisionChangeset { - operations, - md5: md5(&self.operations.json_bytes()), - })) - } - } - } - } - } - - fn modify_row(&mut self, row_id: &str, f: F) -> SyncResult> - where - F: FnOnce(&mut RowRevision) -> SyncResult>, - { - self.modify(|rows| { - if let Some(row_rev) = rows.iter_mut().find(|row_rev| row_id == row_rev.id) { - f(Arc::make_mut(row_rev)) - } else { - tracing::warn!("[BlockMetaPad]: Can't find any row with id: {}", row_id); - Ok(None) - } - }) - } - - pub fn revision_json(&self) -> SyncResult { - serde_json::to_string(&self.block) - .map_err(|e| SyncError::internal().context(format!("serial block to json failed: {}", e))) - } - - pub fn operations_json_str(&self) -> String { - self.operations.json_str() - } + pub fn operations_json_str(&self) -> String { + self.operations.json_str() + } } pub struct GridBlockRevisionChangeset { - pub operations: GridBlockOperations, - /// md5: the md5 of the grid after applying the change. - pub md5: String, + pub operations: GridBlockOperations, + /// md5: the md5 of the grid after applying the change. + pub md5: String, } pub fn make_database_block_operations(block_rev: &DatabaseBlockRevision) -> GridBlockOperations { - let json = serde_json::to_string(&block_rev).unwrap(); - GridBlockOperationsBuilder::new().insert(&json).build() + let json = serde_json::to_string(&block_rev).unwrap(); + GridBlockOperationsBuilder::new().insert(&json).build() } -pub fn make_grid_block_revisions(_user_id: &str, grid_block_meta_data: &DatabaseBlockRevision) -> Vec { - let operations = make_database_block_operations(grid_block_meta_data); - let bytes = operations.json_bytes(); - let revision = Revision::initial_revision(&grid_block_meta_data.block_id, bytes); - vec![revision] +pub fn make_grid_block_revisions( + _user_id: &str, + grid_block_meta_data: &DatabaseBlockRevision, +) -> Vec { + let operations = make_database_block_operations(grid_block_meta_data); + let bytes = operations.json_bytes(); + let revision = Revision::initial_revision(&grid_block_meta_data.block_id, bytes); + vec![revision] } impl std::default::Default for GridBlockRevisionPad { - fn default() -> Self { - let block_revision = DatabaseBlockRevision { - block_id: gen_block_id(), - rows: vec![], - }; + fn default() -> Self { + let block_revision = DatabaseBlockRevision { + block_id: gen_block_id(), + rows: vec![], + }; - let operations = make_database_block_operations(&block_revision); - GridBlockRevisionPad { - block: block_revision, - operations, - } + let operations = make_database_block_operations(&block_revision); + GridBlockRevisionPad { + block: block_revision, + operations, } + } } #[cfg(test)] mod tests { - use crate::client_database::{GridBlockOperations, GridBlockRevisionPad}; - use grid_model::{RowChangeset, RowRevision}; + use crate::client_database::{GridBlockOperations, GridBlockRevisionPad}; + use grid_model::{RowChangeset, RowRevision}; - use std::borrow::Cow; + use std::borrow::Cow; - #[test] - fn block_meta_add_row() { - let mut pad = test_pad(); - let row = RowRevision { - id: "1".to_string(), - block_id: pad.block_id.clone(), - cells: Default::default(), - height: 0, - visibility: false, - }; + #[test] + fn block_meta_add_row() { + let mut pad = test_pad(); + let row = RowRevision { + id: "1".to_string(), + block_id: pad.block_id.clone(), + cells: Default::default(), + height: 0, + visibility: false, + }; - let change = pad.add_row_rev(row.clone(), None).unwrap().unwrap(); - assert_eq!(pad.rows.first().unwrap().as_ref(), &row); - assert_eq!( - change.operations.json_str(), - r#"[{"retain":24},{"insert":"{\"id\":\"1\",\"block_id\":\"1\",\"cells\":[],\"height\":0,\"visibility\":false}"},{"retain":2}]"# - ); + let change = pad.add_row_rev(row.clone(), None).unwrap().unwrap(); + assert_eq!(pad.rows.first().unwrap().as_ref(), &row); + assert_eq!( + change.operations.json_str(), + r#"[{"retain":24},{"insert":"{\"id\":\"1\",\"block_id\":\"1\",\"cells\":[],\"height\":0,\"visibility\":false}"},{"retain":2}]"# + ); + } + + #[test] + fn block_meta_insert_row() { + let mut pad = test_pad(); + let row_1 = test_row_rev("1", &pad); + let row_2 = test_row_rev("2", &pad); + let row_3 = test_row_rev("3", &pad); + + let change = pad.add_row_rev(row_1.clone(), None).unwrap().unwrap(); + assert_eq!( + change.operations.json_str(), + r#"[{"retain":24},{"insert":"{\"id\":\"1\",\"block_id\":\"1\",\"cells\":[],\"height\":0,\"visibility\":false}"},{"retain":2}]"# + ); + + let change = pad.add_row_rev(row_2.clone(), None).unwrap().unwrap(); + assert_eq!( + change.operations.json_str(), + r#"[{"retain":90},{"insert":",{\"id\":\"2\",\"block_id\":\"1\",\"cells\":[],\"height\":0,\"visibility\":false}"},{"retain":2}]"# + ); + + let change = pad + .add_row_rev(row_3.clone(), Some("2".to_string())) + .unwrap() + .unwrap(); + assert_eq!( + change.operations.json_str(), + r#"[{"retain":157},{"insert":",{\"id\":\"3\",\"block_id\":\"1\",\"cells\":[],\"height\":0,\"visibility\":false}"},{"retain":2}]"# + ); + + assert_eq!(*pad.rows[0], row_1); + assert_eq!(*pad.rows[1], row_2); + assert_eq!(*pad.rows[2], row_3); + } + + fn test_row_rev(id: &str, pad: &GridBlockRevisionPad) -> RowRevision { + RowRevision { + id: id.to_string(), + block_id: pad.block_id.clone(), + cells: Default::default(), + height: 0, + visibility: false, } + } - #[test] - fn block_meta_insert_row() { - let mut pad = test_pad(); - let row_1 = test_row_rev("1", &pad); - let row_2 = test_row_rev("2", &pad); - let row_3 = test_row_rev("3", &pad); + #[test] + fn block_meta_insert_row2() { + let mut pad = test_pad(); + let row_1 = test_row_rev("1", &pad); + let row_2 = test_row_rev("2", &pad); + let row_3 = test_row_rev("3", &pad); - let change = pad.add_row_rev(row_1.clone(), None).unwrap().unwrap(); - assert_eq!( - change.operations.json_str(), - r#"[{"retain":24},{"insert":"{\"id\":\"1\",\"block_id\":\"1\",\"cells\":[],\"height\":0,\"visibility\":false}"},{"retain":2}]"# - ); + let _ = pad.add_row_rev(row_1.clone(), None).unwrap().unwrap(); + let _ = pad.add_row_rev(row_2.clone(), None).unwrap().unwrap(); + let _ = pad + .add_row_rev(row_3.clone(), Some("1".to_string())) + .unwrap() + .unwrap(); - let change = pad.add_row_rev(row_2.clone(), None).unwrap().unwrap(); - assert_eq!( - change.operations.json_str(), - r#"[{"retain":90},{"insert":",{\"id\":\"2\",\"block_id\":\"1\",\"cells\":[],\"height\":0,\"visibility\":false}"},{"retain":2}]"# - ); + assert_eq!(*pad.rows[0], row_1); + assert_eq!(*pad.rows[1], row_3); + assert_eq!(*pad.rows[2], row_2); + } - let change = pad.add_row_rev(row_3.clone(), Some("2".to_string())).unwrap().unwrap(); - assert_eq!( - change.operations.json_str(), - r#"[{"retain":157},{"insert":",{\"id\":\"3\",\"block_id\":\"1\",\"cells\":[],\"height\":0,\"visibility\":false}"},{"retain":2}]"# - ); + #[test] + fn block_meta_insert_row3() { + let mut pad = test_pad(); + let row_1 = test_row_rev("1", &pad); + let row_2 = test_row_rev("2", &pad); + let row_3 = test_row_rev("3", &pad); - assert_eq!(*pad.rows[0], row_1); - assert_eq!(*pad.rows[1], row_2); - assert_eq!(*pad.rows[2], row_3); - } + let _ = pad.add_row_rev(row_1.clone(), None).unwrap().unwrap(); + let _ = pad.add_row_rev(row_2.clone(), None).unwrap().unwrap(); + let _ = pad + .add_row_rev(row_3.clone(), Some("".to_string())) + .unwrap() + .unwrap(); - fn test_row_rev(id: &str, pad: &GridBlockRevisionPad) -> RowRevision { - RowRevision { - id: id.to_string(), - block_id: pad.block_id.clone(), - cells: Default::default(), - height: 0, - visibility: false, - } - } + assert_eq!(*pad.rows[0], row_1); + assert_eq!(*pad.rows[1], row_2); + assert_eq!(*pad.rows[2], row_3); + } - #[test] - fn block_meta_insert_row2() { - let mut pad = test_pad(); - let row_1 = test_row_rev("1", &pad); - let row_2 = test_row_rev("2", &pad); - let row_3 = test_row_rev("3", &pad); + #[test] + fn block_meta_delete_row() { + let mut pad = test_pad(); + let pre_json_str = pad.operations_json_str(); + let row = RowRevision { + id: "1".to_string(), + block_id: pad.block_id.clone(), + cells: Default::default(), + height: 0, + visibility: false, + }; - let _ = pad.add_row_rev(row_1.clone(), None).unwrap().unwrap(); - let _ = pad.add_row_rev(row_2.clone(), None).unwrap().unwrap(); - let _ = pad.add_row_rev(row_3.clone(), Some("1".to_string())).unwrap().unwrap(); + let _ = pad.add_row_rev(row.clone(), None).unwrap().unwrap(); + let change = pad + .delete_rows(vec![Cow::Borrowed(&row.id)]) + .unwrap() + .unwrap(); + assert_eq!( + change.operations.json_str(), + r#"[{"retain":24},{"delete":66},{"retain":2}]"# + ); - assert_eq!(*pad.rows[0], row_1); - assert_eq!(*pad.rows[1], row_3); - assert_eq!(*pad.rows[2], row_2); - } + assert_eq!(pad.operations_json_str(), pre_json_str); + } - #[test] - fn block_meta_insert_row3() { - let mut pad = test_pad(); - let row_1 = test_row_rev("1", &pad); - let row_2 = test_row_rev("2", &pad); - let row_3 = test_row_rev("3", &pad); + #[test] + fn block_meta_update_row() { + let mut pad = test_pad(); + let row = RowRevision { + id: "1".to_string(), + block_id: pad.block_id.clone(), + cells: Default::default(), + height: 0, + visibility: false, + }; - let _ = pad.add_row_rev(row_1.clone(), None).unwrap().unwrap(); - let _ = pad.add_row_rev(row_2.clone(), None).unwrap().unwrap(); - let _ = pad.add_row_rev(row_3.clone(), Some("".to_string())).unwrap().unwrap(); + let changeset = RowChangeset { + row_id: row.id.clone(), + height: Some(100), + visibility: Some(true), + cell_by_field_id: Default::default(), + }; - assert_eq!(*pad.rows[0], row_1); - assert_eq!(*pad.rows[1], row_2); - assert_eq!(*pad.rows[2], row_3); - } + let _ = pad.add_row_rev(row, None).unwrap().unwrap(); + let change = pad.update_row(changeset).unwrap().unwrap(); - #[test] - fn block_meta_delete_row() { - let mut pad = test_pad(); - let pre_json_str = pad.operations_json_str(); - let row = RowRevision { - id: "1".to_string(), - block_id: pad.block_id.clone(), - cells: Default::default(), - height: 0, - visibility: false, - }; + assert_eq!( + change.operations.json_str(), + r#"[{"retain":69},{"insert":"10"},{"retain":15},{"insert":"tru"},{"delete":4},{"retain":4}]"# + ); - let _ = pad.add_row_rev(row.clone(), None).unwrap().unwrap(); - let change = pad.delete_rows(vec![Cow::Borrowed(&row.id)]).unwrap().unwrap(); - assert_eq!( - change.operations.json_str(), - r#"[{"retain":24},{"delete":66},{"retain":2}]"# - ); + assert_eq!( + pad.revision_json().unwrap(), + r#"{"block_id":"1","rows":[{"id":"1","block_id":"1","cells":[],"height":100,"visibility":true}]}"# + ); + } - assert_eq!(pad.operations_json_str(), pre_json_str); - } - - #[test] - fn block_meta_update_row() { - let mut pad = test_pad(); - let row = RowRevision { - id: "1".to_string(), - block_id: pad.block_id.clone(), - cells: Default::default(), - height: 0, - visibility: false, - }; - - let changeset = RowChangeset { - row_id: row.id.clone(), - height: Some(100), - visibility: Some(true), - cell_by_field_id: Default::default(), - }; - - let _ = pad.add_row_rev(row, None).unwrap().unwrap(); - let change = pad.update_row(changeset).unwrap().unwrap(); - - assert_eq!( - change.operations.json_str(), - r#"[{"retain":69},{"insert":"10"},{"retain":15},{"insert":"tru"},{"delete":4},{"retain":4}]"# - ); - - assert_eq!( - pad.revision_json().unwrap(), - r#"{"block_id":"1","rows":[{"id":"1","block_id":"1","cells":[],"height":100,"visibility":true}]}"# - ); - } - - fn test_pad() -> GridBlockRevisionPad { - let operations = GridBlockOperations::from_json(r#"[{"insert":"{\"block_id\":\"1\",\"rows\":[]}"}]"#).unwrap(); - GridBlockRevisionPad::from_operations(operations).unwrap() - } + fn test_pad() -> GridBlockRevisionPad { + let operations = + GridBlockOperations::from_json(r#"[{"insert":"{\"block_id\":\"1\",\"rows\":[]}"}]"#).unwrap(); + GridBlockRevisionPad::from_operations(operations).unwrap() + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_database/database_builder.rs b/frontend/rust-lib/flowy-client-sync/src/client_database/database_builder.rs index 8ef88ff2aa..99b08e9de0 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_database/database_builder.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_database/database_builder.rs @@ -1,70 +1,75 @@ use crate::errors::{SyncError, SyncResult}; -use grid_model::{BuildDatabaseContext, DatabaseBlockRevision, FieldRevision, GridBlockMetaRevision, RowRevision}; +use grid_model::{ + BuildDatabaseContext, DatabaseBlockRevision, FieldRevision, GridBlockMetaRevision, RowRevision, +}; use std::sync::Arc; pub struct DatabaseBuilder { - build_context: BuildDatabaseContext, + build_context: BuildDatabaseContext, } impl std::default::Default for DatabaseBuilder { - fn default() -> Self { - let mut build_context = BuildDatabaseContext::new(); + fn default() -> Self { + let mut build_context = BuildDatabaseContext::new(); - let block_meta = GridBlockMetaRevision::new(); - let block_meta_data = DatabaseBlockRevision { - block_id: block_meta.block_id.clone(), - rows: vec![], - }; + let block_meta = GridBlockMetaRevision::new(); + let block_meta_data = DatabaseBlockRevision { + block_id: block_meta.block_id.clone(), + rows: vec![], + }; - build_context.block_metas.push(block_meta); - build_context.blocks.push(block_meta_data); + build_context.block_metas.push(block_meta); + build_context.blocks.push(block_meta_data); - DatabaseBuilder { build_context } - } + DatabaseBuilder { build_context } + } } impl DatabaseBuilder { - pub fn new() -> Self { - Self::default() - } - pub fn add_field(&mut self, field: FieldRevision) { - self.build_context.field_revs.push(Arc::new(field)); - } + pub fn new() -> Self { + Self::default() + } + pub fn add_field(&mut self, field: FieldRevision) { + self.build_context.field_revs.push(Arc::new(field)); + } - pub fn add_row(&mut self, row_rev: RowRevision) { - let block_meta_rev = self.build_context.block_metas.first_mut().unwrap(); - let block_rev = self.build_context.blocks.first_mut().unwrap(); - block_rev.rows.push(Arc::new(row_rev)); - block_meta_rev.row_count += 1; - } + pub fn add_row(&mut self, row_rev: RowRevision) { + let block_meta_rev = self.build_context.block_metas.first_mut().unwrap(); + let block_rev = self.build_context.blocks.first_mut().unwrap(); + block_rev.rows.push(Arc::new(row_rev)); + block_meta_rev.row_count += 1; + } - pub fn add_empty_row(&mut self) { - let row = RowRevision::new(self.block_id()); - self.add_row(row); - } + pub fn add_empty_row(&mut self) { + let row = RowRevision::new(self.block_id()); + self.add_row(row); + } - pub fn field_revs(&self) -> &Vec> { - &self.build_context.field_revs - } + pub fn field_revs(&self) -> &Vec> { + &self.build_context.field_revs + } - pub fn block_id(&self) -> &str { - &self.build_context.block_metas.first().unwrap().block_id - } + pub fn block_id(&self) -> &str { + &self.build_context.block_metas.first().unwrap().block_id + } - pub fn build(self) -> BuildDatabaseContext { - self.build_context - } + pub fn build(self) -> BuildDatabaseContext { + self.build_context + } } #[allow(dead_code)] fn check_rows(fields: &[FieldRevision], rows: &[RowRevision]) -> SyncResult<()> { - let field_ids = fields.iter().map(|field| &field.id).collect::>(); - for row in rows { - let cell_field_ids = row.cells.keys().into_iter().collect::>(); - if cell_field_ids != field_ids { - let msg = format!("{:?} contains invalid cells", row); - return Err(SyncError::internal().context(msg)); - } + let field_ids = fields + .iter() + .map(|field| &field.id) + .collect::>(); + for row in rows { + let cell_field_ids = row.cells.keys().into_iter().collect::>(); + if cell_field_ids != field_ids { + let msg = format!("{:?} contains invalid cells", row); + return Err(SyncError::internal().context(msg)); } - Ok(()) + } + Ok(()) } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_database/database_revision_pad.rs b/frontend/rust-lib/flowy-client-sync/src/client_database/database_revision_pad.rs index 38d41f697b..d4785caaca 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_database/database_revision_pad.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_database/database_revision_pad.rs @@ -2,8 +2,8 @@ use crate::errors::{internal_sync_error, SyncError, SyncResult}; use crate::util::cal_diff; use flowy_sync::util::make_operations_from_revisions; use grid_model::{ - gen_block_id, gen_grid_id, DatabaseRevision, FieldRevision, FieldTypeRevision, GridBlockMetaRevision, - GridBlockMetaRevisionChangeset, + gen_block_id, gen_grid_id, DatabaseRevision, FieldRevision, FieldTypeRevision, + GridBlockMetaRevision, GridBlockMetaRevisionChangeset, }; use lib_infra::util::md5; use lib_infra::util::move_vec_element; @@ -17,264 +17,301 @@ pub type DatabaseOperationsBuilder = DeltaOperationBuilder; #[derive(Clone)] pub struct DatabaseRevisionPad { - grid_rev: Arc, - operations: DatabaseOperations, + grid_rev: Arc, + operations: DatabaseOperations, } pub trait JsonDeserializer { - fn deserialize(&self, type_option_data: Vec) -> SyncResult; + fn deserialize(&self, type_option_data: Vec) -> SyncResult; } impl DatabaseRevisionPad { - pub fn grid_id(&self) -> String { - self.grid_rev.grid_id.clone() - } - pub async fn duplicate_grid_block_meta(&self) -> (Vec, Vec) { - let fields = self - .grid_rev - .fields - .iter() - .map(|field_rev| field_rev.as_ref().clone()) - .collect(); + pub fn grid_id(&self) -> String { + self.grid_rev.grid_id.clone() + } + pub async fn duplicate_grid_block_meta( + &self, + ) -> (Vec, Vec) { + let fields = self + .grid_rev + .fields + .iter() + .map(|field_rev| field_rev.as_ref().clone()) + .collect(); - let blocks = self - .grid_rev - .blocks - .iter() - .map(|block| { - let mut duplicated_block = (**block).clone(); - duplicated_block.block_id = gen_block_id(); - duplicated_block - }) - .collect::>(); + let blocks = self + .grid_rev + .blocks + .iter() + .map(|block| { + let mut duplicated_block = (**block).clone(); + duplicated_block.block_id = gen_block_id(); + duplicated_block + }) + .collect::>(); - (fields, blocks) - } + (fields, blocks) + } - pub fn from_operations(operations: DatabaseOperations) -> SyncResult { - let content = operations.content()?; - let grid: DatabaseRevision = serde_json::from_str(&content).map_err(|e| { - let msg = format!("Deserialize operations to grid failed: {}", e); - tracing::error!("{}", msg); - SyncError::internal().context(msg) - })?; + pub fn from_operations(operations: DatabaseOperations) -> SyncResult { + let content = operations.content()?; + let grid: DatabaseRevision = serde_json::from_str(&content).map_err(|e| { + let msg = format!("Deserialize operations to grid failed: {}", e); + tracing::error!("{}", msg); + SyncError::internal().context(msg) + })?; - Ok(Self { - grid_rev: Arc::new(grid), - operations, - }) - } + Ok(Self { + grid_rev: Arc::new(grid), + operations, + }) + } - pub fn from_revisions(revisions: Vec) -> SyncResult { - let operations: DatabaseOperations = make_operations_from_revisions(revisions)?; - Self::from_operations(operations) - } + pub fn from_revisions(revisions: Vec) -> SyncResult { + let operations: DatabaseOperations = make_operations_from_revisions(revisions)?; + Self::from_operations(operations) + } - #[tracing::instrument(level = "debug", skip_all, err)] - pub fn create_field_rev( - &mut self, - new_field_rev: FieldRevision, - start_field_id: Option, - ) -> SyncResult> { - self.modify_grid(|grid_meta| { - // Check if the field exists or not - if grid_meta - .fields - .iter() - .any(|field_rev| field_rev.id == new_field_rev.id) - { - tracing::error!("Duplicate grid field"); - return Ok(None); - } + #[tracing::instrument(level = "debug", skip_all, err)] + pub fn create_field_rev( + &mut self, + new_field_rev: FieldRevision, + start_field_id: Option, + ) -> SyncResult> { + self.modify_grid(|grid_meta| { + // Check if the field exists or not + if grid_meta + .fields + .iter() + .any(|field_rev| field_rev.id == new_field_rev.id) + { + tracing::error!("Duplicate grid field"); + return Ok(None); + } - let insert_index = match start_field_id { - None => None, - Some(start_field_id) => grid_meta.fields.iter().position(|field| field.id == start_field_id), - }; - let new_field_rev = Arc::new(new_field_rev); - match insert_index { - None => grid_meta.fields.push(new_field_rev), - Some(index) => grid_meta.fields.insert(index, new_field_rev), - } + let insert_index = match start_field_id { + None => None, + Some(start_field_id) => grid_meta + .fields + .iter() + .position(|field| field.id == start_field_id), + }; + let new_field_rev = Arc::new(new_field_rev); + match insert_index { + None => grid_meta.fields.push(new_field_rev), + Some(index) => grid_meta.fields.insert(index, new_field_rev), + } + Ok(Some(())) + }) + } + + pub fn delete_field_rev( + &mut self, + field_id: &str, + ) -> SyncResult> { + self.modify_grid(|grid_meta| { + match grid_meta + .fields + .iter() + .position(|field| field.id == field_id) + { + None => Ok(None), + Some(index) => { + if grid_meta.fields[index].is_primary { + Err(SyncError::can_not_delete_primary_field()) + } else { + grid_meta.fields.remove(index); Ok(Some(())) - }) - } + } + }, + } + }) + } - pub fn delete_field_rev(&mut self, field_id: &str) -> SyncResult> { - self.modify_grid( - |grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) { - None => Ok(None), - Some(index) => { - if grid_meta.fields[index].is_primary { - Err(SyncError::can_not_delete_primary_field()) - } else { - grid_meta.fields.remove(index); - Ok(Some(())) - } - } - }, - ) - } - - pub fn duplicate_field_rev( - &mut self, - field_id: &str, - duplicated_field_id: &str, - ) -> SyncResult> { - self.modify_grid( - |grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) { - None => Ok(None), - Some(index) => { - let mut duplicate_field_rev = grid_meta.fields[index].as_ref().clone(); - duplicate_field_rev.id = duplicated_field_id.to_string(); - duplicate_field_rev.name = format!("{} (copy)", duplicate_field_rev.name); - grid_meta.fields.insert(index + 1, Arc::new(duplicate_field_rev)); - Ok(Some(())) - } - }, - ) - } - - /// Modifies the current field type of the [FieldTypeRevision] - /// - /// # Arguments - /// - /// * `field_id`: the id of the field - /// * `field_type`: the new field type of the field - /// * `make_default_type_option`: create the field type's type-option data - /// * `type_option_transform`: create the field type's type-option data - /// - /// - pub fn switch_to_field( - &mut self, - field_id: &str, - new_field_type: T, - make_default_type_option: DT, - type_option_transform: TT, - ) -> SyncResult> - where - DT: FnOnce() -> String, - TT: FnOnce(FieldTypeRevision, Option, String) -> String, - T: Into, - { - let new_field_type = new_field_type.into(); - self.modify_grid(|grid_meta| { - match grid_meta.fields.iter_mut().find(|field_rev| field_rev.id == field_id) { - None => { - tracing::warn!("Can not find the field with id: {}", field_id); - Ok(None) - } - Some(field_rev) => { - let mut_field_rev = Arc::make_mut(field_rev); - let old_field_type_rev = mut_field_rev.ty; - let old_field_type_option = mut_field_rev - .get_type_option_str(mut_field_rev.ty) - .map(|value| value.to_owned()); - match mut_field_rev.get_type_option_str(new_field_type) { - Some(new_field_type_option) => { - let transformed_type_option = type_option_transform( - old_field_type_rev, - old_field_type_option, - new_field_type_option.to_owned(), - ); - mut_field_rev.insert_type_option_str(&new_field_type, transformed_type_option); - } - None => { - // If the type-option data isn't exist before, creating the default type-option data. - let new_field_type_option = make_default_type_option(); - let transformed_type_option = - type_option_transform(old_field_type_rev, old_field_type_option, new_field_type_option); - mut_field_rev.insert_type_option_str(&new_field_type, transformed_type_option); - } - } - - mut_field_rev.ty = new_field_type; - Ok(Some(())) - } - } - }) - } - - pub fn replace_field_rev( - &mut self, - field_rev: Arc, - ) -> SyncResult> { - self.modify_grid( - |grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_rev.id) { - None => Ok(None), - Some(index) => { - grid_meta.fields.remove(index); - grid_meta.fields.insert(index, field_rev); - Ok(Some(())) - } - }, - ) - } - - pub fn move_field( - &mut self, - field_id: &str, - from_index: usize, - to_index: usize, - ) -> SyncResult> { - self.modify_grid(|grid_meta| { - match move_vec_element( - &mut grid_meta.fields, - |field| field.id == field_id, - from_index, - to_index, - ) - .map_err(internal_sync_error)? - { - true => Ok(Some(())), - false => Ok(None), - } - }) - } - - pub fn contain_field(&self, field_id: &str) -> bool { - self.grid_rev.fields.iter().any(|field| field.id == field_id) - } - - pub fn get_field_rev(&self, field_id: &str) -> Option<(usize, &Arc)> { - self.grid_rev + pub fn duplicate_field_rev( + &mut self, + field_id: &str, + duplicated_field_id: &str, + ) -> SyncResult> { + self.modify_grid(|grid_meta| { + match grid_meta + .fields + .iter() + .position(|field| field.id == field_id) + { + None => Ok(None), + Some(index) => { + let mut duplicate_field_rev = grid_meta.fields[index].as_ref().clone(); + duplicate_field_rev.id = duplicated_field_id.to_string(); + duplicate_field_rev.name = format!("{} (copy)", duplicate_field_rev.name); + grid_meta .fields - .iter() - .enumerate() - .find(|(_, field)| field.id == field_id) + .insert(index + 1, Arc::new(duplicate_field_rev)); + Ok(Some(())) + }, + } + }) + } + + /// Modifies the current field type of the [FieldTypeRevision] + /// + /// # Arguments + /// + /// * `field_id`: the id of the field + /// * `field_type`: the new field type of the field + /// * `make_default_type_option`: create the field type's type-option data + /// * `type_option_transform`: create the field type's type-option data + /// + /// + pub fn switch_to_field( + &mut self, + field_id: &str, + new_field_type: T, + make_default_type_option: DT, + type_option_transform: TT, + ) -> SyncResult> + where + DT: FnOnce() -> String, + TT: FnOnce(FieldTypeRevision, Option, String) -> String, + T: Into, + { + let new_field_type = new_field_type.into(); + self.modify_grid(|grid_meta| { + match grid_meta + .fields + .iter_mut() + .find(|field_rev| field_rev.id == field_id) + { + None => { + tracing::warn!("Can not find the field with id: {}", field_id); + Ok(None) + }, + Some(field_rev) => { + let mut_field_rev = Arc::make_mut(field_rev); + let old_field_type_rev = mut_field_rev.ty; + let old_field_type_option = mut_field_rev + .get_type_option_str(mut_field_rev.ty) + .map(|value| value.to_owned()); + match mut_field_rev.get_type_option_str(new_field_type) { + Some(new_field_type_option) => { + let transformed_type_option = type_option_transform( + old_field_type_rev, + old_field_type_option, + new_field_type_option.to_owned(), + ); + mut_field_rev.insert_type_option_str(&new_field_type, transformed_type_option); + }, + None => { + // If the type-option data isn't exist before, creating the default type-option data. + let new_field_type_option = make_default_type_option(); + let transformed_type_option = type_option_transform( + old_field_type_rev, + old_field_type_option, + new_field_type_option, + ); + mut_field_rev.insert_type_option_str(&new_field_type, transformed_type_option); + }, + } + + mut_field_rev.ty = new_field_type; + Ok(Some(())) + }, + } + }) + } + + pub fn replace_field_rev( + &mut self, + field_rev: Arc, + ) -> SyncResult> { + self.modify_grid(|grid_meta| { + match grid_meta + .fields + .iter() + .position(|field| field.id == field_rev.id) + { + None => Ok(None), + Some(index) => { + grid_meta.fields.remove(index); + grid_meta.fields.insert(index, field_rev); + Ok(Some(())) + }, + } + }) + } + + pub fn move_field( + &mut self, + field_id: &str, + from_index: usize, + to_index: usize, + ) -> SyncResult> { + self.modify_grid(|grid_meta| { + match move_vec_element( + &mut grid_meta.fields, + |field| field.id == field_id, + from_index, + to_index, + ) + .map_err(internal_sync_error)? + { + true => Ok(Some(())), + false => Ok(None), + } + }) + } + + pub fn contain_field(&self, field_id: &str) -> bool { + self + .grid_rev + .fields + .iter() + .any(|field| field.id == field_id) + } + + pub fn get_field_rev(&self, field_id: &str) -> Option<(usize, &Arc)> { + self + .grid_rev + .fields + .iter() + .enumerate() + .find(|(_, field)| field.id == field_id) + } + + pub fn get_field_revs( + &self, + field_ids: Option>, + ) -> SyncResult>> { + match field_ids { + None => Ok(self.grid_rev.fields.clone()), + Some(field_ids) => { + let field_by_field_id = self + .grid_rev + .fields + .iter() + .map(|field| (&field.id, field)) + .collect::>>(); + + let fields = field_ids + .iter() + .flat_map(|field_id| match field_by_field_id.get(&field_id) { + None => { + tracing::error!("Can't find the field with id: {}", field_id); + None + }, + Some(field) => Some((*field).clone()), + }) + .collect::>>(); + Ok(fields) + }, } + } - pub fn get_field_revs(&self, field_ids: Option>) -> SyncResult>> { - match field_ids { - None => Ok(self.grid_rev.fields.clone()), - Some(field_ids) => { - let field_by_field_id = self - .grid_rev - .fields - .iter() - .map(|field| (&field.id, field)) - .collect::>>(); - - let fields = field_ids - .iter() - .flat_map(|field_id| match field_by_field_id.get(&field_id) { - None => { - tracing::error!("Can't find the field with id: {}", field_id); - None - } - Some(field) => Some((*field).clone()), - }) - .collect::>>(); - Ok(fields) - } - } - } - - pub fn create_block_meta_rev( - &mut self, - block: GridBlockMetaRevision, - ) -> SyncResult> { - self.modify_grid(|grid_meta| { + pub fn create_block_meta_rev( + &mut self, + block: GridBlockMetaRevision, + ) -> SyncResult> { + self.modify_grid(|grid_meta| { if grid_meta.blocks.iter().any(|b| b.block_id == block.block_id) { tracing::warn!("Duplicate grid block"); Ok(None) @@ -294,142 +331,158 @@ impl DatabaseRevisionPad { Ok(Some(())) } }) - } + } - pub fn get_block_meta_revs(&self) -> Vec> { - self.grid_rev.blocks.clone() - } + pub fn get_block_meta_revs(&self) -> Vec> { + self.grid_rev.blocks.clone() + } - pub fn update_block_rev( - &mut self, - changeset: GridBlockMetaRevisionChangeset, - ) -> SyncResult> { - let block_id = changeset.block_id.clone(); - self.modify_block(&block_id, |block| { - let mut is_changed = None; + pub fn update_block_rev( + &mut self, + changeset: GridBlockMetaRevisionChangeset, + ) -> SyncResult> { + let block_id = changeset.block_id.clone(); + self.modify_block(&block_id, |block| { + let mut is_changed = None; - if let Some(row_count) = changeset.row_count { - block.row_count = row_count; - is_changed = Some(()); - } + if let Some(row_count) = changeset.row_count { + block.row_count = row_count; + is_changed = Some(()); + } - if let Some(start_row_index) = changeset.start_row_index { - block.start_row_index = start_row_index; - is_changed = Some(()); - } + if let Some(start_row_index) = changeset.start_row_index { + block.start_row_index = start_row_index; + is_changed = Some(()); + } - Ok(is_changed) - }) - } + Ok(is_changed) + }) + } - pub fn database_md5(&self) -> String { - md5(&self.operations.json_bytes()) - } + pub fn database_md5(&self) -> String { + md5(&self.operations.json_bytes()) + } - pub fn operations_json_str(&self) -> String { - self.operations.json_str() - } + pub fn operations_json_str(&self) -> String { + self.operations.json_str() + } - pub fn get_fields(&self) -> &[Arc] { - &self.grid_rev.fields - } + pub fn get_fields(&self) -> &[Arc] { + &self.grid_rev.fields + } - fn modify_grid(&mut self, f: F) -> SyncResult> - where - F: FnOnce(&mut DatabaseRevision) -> SyncResult>, - { - let cloned_grid = self.grid_rev.clone(); - match f(Arc::make_mut(&mut self.grid_rev))? { - None => Ok(None), - Some(_) => { - let old = make_database_rev_json_str(&cloned_grid)?; - let new = self.json_str()?; - match cal_diff::(old, new) { - None => Ok(None), - Some(operations) => { - self.operations = self.operations.compose(&operations)?; - Ok(Some(DatabaseRevisionChangeset { - operations, - md5: self.database_md5(), - })) - } - } - } + fn modify_grid(&mut self, f: F) -> SyncResult> + where + F: FnOnce(&mut DatabaseRevision) -> SyncResult>, + { + let cloned_grid = self.grid_rev.clone(); + match f(Arc::make_mut(&mut self.grid_rev))? { + None => Ok(None), + Some(_) => { + let old = make_database_rev_json_str(&cloned_grid)?; + let new = self.json_str()?; + match cal_diff::(old, new) { + None => Ok(None), + Some(operations) => { + self.operations = self.operations.compose(&operations)?; + Ok(Some(DatabaseRevisionChangeset { + operations, + md5: self.database_md5(), + })) + }, } + }, } + } - fn modify_block(&mut self, block_id: &str, f: F) -> SyncResult> - where - F: FnOnce(&mut GridBlockMetaRevision) -> SyncResult>, - { - self.modify_grid( - |grid_rev| match grid_rev.blocks.iter().position(|block| block.block_id == block_id) { - None => { - tracing::warn!("[GridMetaPad]: Can't find any block with id: {}", block_id); - Ok(None) - } - Some(index) => { - let block_rev = Arc::make_mut(&mut grid_rev.blocks[index]); - f(block_rev) - } - }, - ) - } + fn modify_block( + &mut self, + block_id: &str, + f: F, + ) -> SyncResult> + where + F: FnOnce(&mut GridBlockMetaRevision) -> SyncResult>, + { + self.modify_grid(|grid_rev| { + match grid_rev + .blocks + .iter() + .position(|block| block.block_id == block_id) + { + None => { + tracing::warn!("[GridMetaPad]: Can't find any block with id: {}", block_id); + Ok(None) + }, + Some(index) => { + let block_rev = Arc::make_mut(&mut grid_rev.blocks[index]); + f(block_rev) + }, + } + }) + } - pub fn modify_field(&mut self, field_id: &str, f: F) -> SyncResult> - where - F: FnOnce(&mut FieldRevision) -> SyncResult>, - { - self.modify_grid( - |grid_rev| match grid_rev.fields.iter().position(|field| field.id == field_id) { - None => { - tracing::warn!("[GridMetaPad]: Can't find any field with id: {}", field_id); - Ok(None) - } - Some(index) => { - let mut_field_rev = Arc::make_mut(&mut grid_rev.fields[index]); - f(mut_field_rev) - } - }, - ) - } + pub fn modify_field( + &mut self, + field_id: &str, + f: F, + ) -> SyncResult> + where + F: FnOnce(&mut FieldRevision) -> SyncResult>, + { + self.modify_grid(|grid_rev| { + match grid_rev + .fields + .iter() + .position(|field| field.id == field_id) + { + None => { + tracing::warn!("[GridMetaPad]: Can't find any field with id: {}", field_id); + Ok(None) + }, + Some(index) => { + let mut_field_rev = Arc::make_mut(&mut grid_rev.fields[index]); + f(mut_field_rev) + }, + } + }) + } - pub fn json_str(&self) -> SyncResult { - make_database_rev_json_str(&self.grid_rev) - } + pub fn json_str(&self) -> SyncResult { + make_database_rev_json_str(&self.grid_rev) + } } pub fn make_database_rev_json_str(grid_revision: &DatabaseRevision) -> SyncResult { - let json = serde_json::to_string(grid_revision) - .map_err(|err| internal_sync_error(format!("Serialize grid to json str failed. {:?}", err)))?; - Ok(json) + let json = serde_json::to_string(grid_revision) + .map_err(|err| internal_sync_error(format!("Serialize grid to json str failed. {:?}", err)))?; + Ok(json) } pub struct DatabaseRevisionChangeset { - pub operations: DatabaseOperations, - /// md5: the md5 of the grid after applying the change. - pub md5: String, + pub operations: DatabaseOperations, + /// md5: the md5 of the grid after applying the change. + pub md5: String, } pub fn make_database_operations(grid_rev: &DatabaseRevision) -> DatabaseOperations { - let json = serde_json::to_string(&grid_rev).unwrap(); - DatabaseOperationsBuilder::new().insert(&json).build() + let json = serde_json::to_string(&grid_rev).unwrap(); + DatabaseOperationsBuilder::new().insert(&json).build() } pub fn make_database_revisions(_user_id: &str, grid_rev: &DatabaseRevision) -> Vec { - let operations = make_database_operations(grid_rev); - let bytes = operations.json_bytes(); - let revision = Revision::initial_revision(&grid_rev.grid_id, bytes); - vec![revision] + let operations = make_database_operations(grid_rev); + let bytes = operations.json_bytes(); + let revision = Revision::initial_revision(&grid_rev.grid_id, bytes); + vec![revision] } impl std::default::Default for DatabaseRevisionPad { - fn default() -> Self { - let grid = DatabaseRevision::new(&gen_grid_id()); - let operations = make_database_operations(&grid); - DatabaseRevisionPad { - grid_rev: Arc::new(grid), - operations, - } + fn default() -> Self { + let grid = DatabaseRevision::new(&gen_grid_id()); + let operations = make_database_operations(&grid); + DatabaseRevisionPad { + grid_rev: Arc::new(grid), + operations, } + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_database/view_revision_pad.rs b/frontend/rust-lib/flowy-client-sync/src/client_database/view_revision_pad.rs index 3913b431a6..4f57d8fe04 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_database/view_revision_pad.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_database/view_revision_pad.rs @@ -2,8 +2,8 @@ use crate::errors::{internal_sync_error, SyncError, SyncResult}; use crate::util::cal_diff; use flowy_sync::util::make_operations_from_revisions; use grid_model::{ - DatabaseViewRevision, FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision, LayoutRevision, - SortRevision, + DatabaseViewRevision, FieldRevision, FieldTypeRevision, FilterRevision, + GroupConfigurationRevision, LayoutRevision, SortRevision, }; use lib_infra::util::md5; use lib_ot::core::{DeltaBuilder, DeltaOperations, EmptyAttributes, OperationTransform}; @@ -15,305 +15,334 @@ pub type GridViewOperationsBuilder = DeltaBuilder; #[derive(Debug, Clone)] pub struct GridViewRevisionPad { - view: Arc, - operations: GridViewOperations, + view: Arc, + operations: GridViewOperations, } impl std::ops::Deref for GridViewRevisionPad { - type Target = DatabaseViewRevision; + type Target = DatabaseViewRevision; - fn deref(&self) -> &Self::Target { - &self.view - } + fn deref(&self) -> &Self::Target { + &self.view + } } impl GridViewRevisionPad { - // For the moment, the view_id is equal to grid_id. The grid_id represents the database id. - // A database can be referenced by multiple views. - pub fn new(grid_id: String, view_id: String, layout: LayoutRevision) -> Self { - let view = Arc::new(DatabaseViewRevision::new(grid_id, view_id, layout)); - let json = serde_json::to_string(&view).unwrap(); - let operations = GridViewOperationsBuilder::new().insert(&json).build(); - Self { view, operations } - } + // For the moment, the view_id is equal to grid_id. The grid_id represents the database id. + // A database can be referenced by multiple views. + pub fn new(grid_id: String, view_id: String, layout: LayoutRevision) -> Self { + let view = Arc::new(DatabaseViewRevision::new(grid_id, view_id, layout)); + let json = serde_json::to_string(&view).unwrap(); + let operations = GridViewOperationsBuilder::new().insert(&json).build(); + Self { view, operations } + } - pub fn from_operations(view_id: &str, operations: GridViewOperations) -> SyncResult { - if operations.is_empty() { - return Ok(GridViewRevisionPad::new( - view_id.to_owned(), - view_id.to_owned(), - LayoutRevision::Grid, - )); - } - let s = operations.content()?; - let view: DatabaseViewRevision = serde_json::from_str(&s).map_err(|e| { - let msg = format!("Deserialize operations to GridViewRevision failed: {}", e); - tracing::error!("parsing json: {}", s); - SyncError::internal().context(msg) - })?; - Ok(Self { - view: Arc::new(view), - operations, - }) - } - - pub fn from_revisions(view_id: &str, revisions: Vec) -> SyncResult { - let operations: GridViewOperations = make_operations_from_revisions(revisions)?; - Self::from_operations(view_id, operations) - } - - pub fn get_groups_by_field_revs(&self, field_revs: &[Arc]) -> Vec> { - self.groups.get_objects_by_field_revs(field_revs) - } - - pub fn get_all_groups(&self) -> Vec> { - self.groups.get_all_objects() - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub fn insert_or_update_group_configuration( - &mut self, - field_id: &str, - field_type: &FieldTypeRevision, - group_configuration_rev: GroupConfigurationRevision, - ) -> SyncResult> { - self.modify(|view| { - // Only save one group - view.groups.clear(); - view.groups.add_object(field_id, field_type, group_configuration_rev); - Ok(Some(())) - }) - } - - #[tracing::instrument(level = "trace", skip_all)] - pub fn contains_group(&self, field_id: &str, field_type: &FieldTypeRevision) -> bool { - self.view.groups.get_objects(field_id, field_type).is_some() - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub fn with_mut_group( - &mut self, - field_id: &str, - field_type: &FieldTypeRevision, - configuration_id: &str, - mut_configuration_fn: F, - ) -> SyncResult> { - self.modify(|view| match view.groups.get_mut_objects(field_id, field_type) { - None => Ok(None), - Some(configurations_revs) => { - for configuration_rev in configurations_revs { - if configuration_rev.id == configuration_id { - mut_configuration_fn(Arc::make_mut(configuration_rev)); - return Ok(Some(())); - } - } - Ok(None) - } - }) - } - - pub fn delete_group( - &mut self, - group_id: &str, - field_id: &str, - field_type: &FieldTypeRevision, - ) -> SyncResult> { - self.modify(|view| { - if let Some(groups) = view.groups.get_mut_objects(field_id, field_type) { - groups.retain(|group| group.id != group_id); - Ok(Some(())) - } else { - Ok(None) - } - }) - } - - pub fn get_all_sorts(&self, _field_revs: &[Arc]) -> Vec> { - self.sorts.get_all_objects() - } - - /// For the moment, a field type only have one filter. - pub fn get_sorts(&self, field_id: &str, field_type_rev: &FieldTypeRevision) -> Vec> { - self.sorts.get_objects(field_id, field_type_rev).unwrap_or_default() - } - - pub fn get_sort( - &self, - field_id: &str, - field_type_rev: &FieldTypeRevision, - sort_id: &str, - ) -> Option> { - self.sorts - .get_object(field_id, field_type_rev, |sort| sort.id == sort_id) - } - - pub fn insert_sort( - &mut self, - field_id: &str, - sort_rev: SortRevision, - ) -> SyncResult> { - self.modify(|view| { - let field_type = sort_rev.field_type; - view.sorts.add_object(field_id, &field_type, sort_rev); - Ok(Some(())) - }) - } - - pub fn update_sort( - &mut self, - field_id: &str, - sort_rev: SortRevision, - ) -> SyncResult> { - self.modify(|view| { - if let Some(sort) = view - .sorts - .get_mut_object(field_id, &sort_rev.field_type, |sort| sort.id == sort_rev.id) - { - let sort = Arc::make_mut(sort); - sort.condition = sort_rev.condition; - Ok(Some(())) - } else { - Ok(None) - } - }) - } - - pub fn delete_sort>( - &mut self, - sort_id: &str, - field_id: &str, - field_type: T, - ) -> SyncResult> { - let field_type = field_type.into(); - self.modify(|view| { - if let Some(sorts) = view.sorts.get_mut_objects(field_id, &field_type) { - sorts.retain(|sort| sort.id != sort_id); - Ok(Some(())) - } else { - Ok(None) - } - }) - } - - pub fn delete_all_sorts(&mut self) -> SyncResult> { - self.modify(|view| { - view.sorts.clear(); - Ok(Some(())) - }) - } - - pub fn get_all_filters(&self, field_revs: &[Arc]) -> Vec> { - self.filters.get_objects_by_field_revs(field_revs) - } - - /// For the moment, a field type only have one filter. - pub fn get_filters(&self, field_id: &str, field_type_rev: &FieldTypeRevision) -> Vec> { - self.filters.get_objects(field_id, field_type_rev).unwrap_or_default() - } - - pub fn get_filter( - &self, - field_id: &str, - field_type_rev: &FieldTypeRevision, - filter_id: &str, - ) -> Option> { - self.filters - .get_object(field_id, field_type_rev, |filter| filter.id == filter_id) - } - - pub fn insert_filter( - &mut self, - field_id: &str, - filter_rev: FilterRevision, - ) -> SyncResult> { - self.modify(|view| { - let field_type = filter_rev.field_type; - view.filters.add_object(field_id, &field_type, filter_rev); - Ok(Some(())) - }) - } - - pub fn update_filter( - &mut self, - field_id: &str, - filter_rev: FilterRevision, - ) -> SyncResult> { - self.modify(|view| { - if let Some(filter) = view - .filters - .get_mut_object(field_id, &filter_rev.field_type, |filter| filter.id == filter_rev.id) - { - let filter = Arc::make_mut(filter); - filter.condition = filter_rev.condition; - filter.content = filter_rev.content; - Ok(Some(())) - } else { - Ok(None) - } - }) - } - - pub fn delete_filter>( - &mut self, - filter_id: &str, - field_id: &str, - field_type: T, - ) -> SyncResult> { - let field_type = field_type.into(); - self.modify(|view| { - if let Some(filters) = view.filters.get_mut_objects(field_id, &field_type) { - filters.retain(|filter| filter.id != filter_id); - Ok(Some(())) - } else { - Ok(None) - } - }) - } - - pub fn json_str(&self) -> SyncResult { - make_grid_view_rev_json_str(&self.view) - } - - pub fn layout(&self) -> LayoutRevision { - self.layout.clone() - } - - fn modify(&mut self, f: F) -> SyncResult> - where - F: FnOnce(&mut DatabaseViewRevision) -> SyncResult>, - { - let cloned_view = self.view.clone(); - match f(Arc::make_mut(&mut self.view))? { - None => Ok(None), - Some(_) => { - let old = make_grid_view_rev_json_str(&cloned_view)?; - let new = self.json_str()?; - match cal_diff::(old, new) { - None => Ok(None), - Some(operations) => { - self.operations = self.operations.compose(&operations)?; - let md5 = md5(&self.operations.json_bytes()); - Ok(Some(GridViewRevisionChangeset { operations, md5 })) - } - } - } + pub fn from_operations(view_id: &str, operations: GridViewOperations) -> SyncResult { + if operations.is_empty() { + return Ok(GridViewRevisionPad::new( + view_id.to_owned(), + view_id.to_owned(), + LayoutRevision::Grid, + )); + } + let s = operations.content()?; + let view: DatabaseViewRevision = serde_json::from_str(&s).map_err(|e| { + let msg = format!("Deserialize operations to GridViewRevision failed: {}", e); + tracing::error!("parsing json: {}", s); + SyncError::internal().context(msg) + })?; + Ok(Self { + view: Arc::new(view), + operations, + }) + } + + pub fn from_revisions(view_id: &str, revisions: Vec) -> SyncResult { + let operations: GridViewOperations = make_operations_from_revisions(revisions)?; + Self::from_operations(view_id, operations) + } + + pub fn get_groups_by_field_revs( + &self, + field_revs: &[Arc], + ) -> Vec> { + self.groups.get_objects_by_field_revs(field_revs) + } + + pub fn get_all_groups(&self) -> Vec> { + self.groups.get_all_objects() + } + + #[tracing::instrument(level = "trace", skip_all, err)] + pub fn insert_or_update_group_configuration( + &mut self, + field_id: &str, + field_type: &FieldTypeRevision, + group_configuration_rev: GroupConfigurationRevision, + ) -> SyncResult> { + self.modify(|view| { + // Only save one group + view.groups.clear(); + view + .groups + .add_object(field_id, field_type, group_configuration_rev); + Ok(Some(())) + }) + } + + #[tracing::instrument(level = "trace", skip_all)] + pub fn contains_group(&self, field_id: &str, field_type: &FieldTypeRevision) -> bool { + self.view.groups.get_objects(field_id, field_type).is_some() + } + + #[tracing::instrument(level = "trace", skip_all, err)] + pub fn with_mut_group( + &mut self, + field_id: &str, + field_type: &FieldTypeRevision, + configuration_id: &str, + mut_configuration_fn: F, + ) -> SyncResult> { + self.modify( + |view| match view.groups.get_mut_objects(field_id, field_type) { + None => Ok(None), + Some(configurations_revs) => { + for configuration_rev in configurations_revs { + if configuration_rev.id == configuration_id { + mut_configuration_fn(Arc::make_mut(configuration_rev)); + return Ok(Some(())); + } + } + Ok(None) + }, + }, + ) + } + + pub fn delete_group( + &mut self, + group_id: &str, + field_id: &str, + field_type: &FieldTypeRevision, + ) -> SyncResult> { + self.modify(|view| { + if let Some(groups) = view.groups.get_mut_objects(field_id, field_type) { + groups.retain(|group| group.id != group_id); + Ok(Some(())) + } else { + Ok(None) + } + }) + } + + pub fn get_all_sorts(&self, _field_revs: &[Arc]) -> Vec> { + self.sorts.get_all_objects() + } + + /// For the moment, a field type only have one filter. + pub fn get_sorts( + &self, + field_id: &str, + field_type_rev: &FieldTypeRevision, + ) -> Vec> { + self + .sorts + .get_objects(field_id, field_type_rev) + .unwrap_or_default() + } + + pub fn get_sort( + &self, + field_id: &str, + field_type_rev: &FieldTypeRevision, + sort_id: &str, + ) -> Option> { + self + .sorts + .get_object(field_id, field_type_rev, |sort| sort.id == sort_id) + } + + pub fn insert_sort( + &mut self, + field_id: &str, + sort_rev: SortRevision, + ) -> SyncResult> { + self.modify(|view| { + let field_type = sort_rev.field_type; + view.sorts.add_object(field_id, &field_type, sort_rev); + Ok(Some(())) + }) + } + + pub fn update_sort( + &mut self, + field_id: &str, + sort_rev: SortRevision, + ) -> SyncResult> { + self.modify(|view| { + if let Some(sort) = view + .sorts + .get_mut_object(field_id, &sort_rev.field_type, |sort| { + sort.id == sort_rev.id + }) + { + let sort = Arc::make_mut(sort); + sort.condition = sort_rev.condition; + Ok(Some(())) + } else { + Ok(None) + } + }) + } + + pub fn delete_sort>( + &mut self, + sort_id: &str, + field_id: &str, + field_type: T, + ) -> SyncResult> { + let field_type = field_type.into(); + self.modify(|view| { + if let Some(sorts) = view.sorts.get_mut_objects(field_id, &field_type) { + sorts.retain(|sort| sort.id != sort_id); + Ok(Some(())) + } else { + Ok(None) + } + }) + } + + pub fn delete_all_sorts(&mut self) -> SyncResult> { + self.modify(|view| { + view.sorts.clear(); + Ok(Some(())) + }) + } + + pub fn get_all_filters(&self, field_revs: &[Arc]) -> Vec> { + self.filters.get_objects_by_field_revs(field_revs) + } + + /// For the moment, a field type only have one filter. + pub fn get_filters( + &self, + field_id: &str, + field_type_rev: &FieldTypeRevision, + ) -> Vec> { + self + .filters + .get_objects(field_id, field_type_rev) + .unwrap_or_default() + } + + pub fn get_filter( + &self, + field_id: &str, + field_type_rev: &FieldTypeRevision, + filter_id: &str, + ) -> Option> { + self + .filters + .get_object(field_id, field_type_rev, |filter| filter.id == filter_id) + } + + pub fn insert_filter( + &mut self, + field_id: &str, + filter_rev: FilterRevision, + ) -> SyncResult> { + self.modify(|view| { + let field_type = filter_rev.field_type; + view.filters.add_object(field_id, &field_type, filter_rev); + Ok(Some(())) + }) + } + + pub fn update_filter( + &mut self, + field_id: &str, + filter_rev: FilterRevision, + ) -> SyncResult> { + self.modify(|view| { + if let Some(filter) = + view + .filters + .get_mut_object(field_id, &filter_rev.field_type, |filter| { + filter.id == filter_rev.id + }) + { + let filter = Arc::make_mut(filter); + filter.condition = filter_rev.condition; + filter.content = filter_rev.content; + Ok(Some(())) + } else { + Ok(None) + } + }) + } + + pub fn delete_filter>( + &mut self, + filter_id: &str, + field_id: &str, + field_type: T, + ) -> SyncResult> { + let field_type = field_type.into(); + self.modify(|view| { + if let Some(filters) = view.filters.get_mut_objects(field_id, &field_type) { + filters.retain(|filter| filter.id != filter_id); + Ok(Some(())) + } else { + Ok(None) + } + }) + } + + pub fn json_str(&self) -> SyncResult { + make_grid_view_rev_json_str(&self.view) + } + + pub fn layout(&self) -> LayoutRevision { + self.layout.clone() + } + + fn modify(&mut self, f: F) -> SyncResult> + where + F: FnOnce(&mut DatabaseViewRevision) -> SyncResult>, + { + let cloned_view = self.view.clone(); + match f(Arc::make_mut(&mut self.view))? { + None => Ok(None), + Some(_) => { + let old = make_grid_view_rev_json_str(&cloned_view)?; + let new = self.json_str()?; + match cal_diff::(old, new) { + None => Ok(None), + Some(operations) => { + self.operations = self.operations.compose(&operations)?; + let md5 = md5(&self.operations.json_bytes()); + Ok(Some(GridViewRevisionChangeset { operations, md5 })) + }, } + }, } + } } #[derive(Debug)] pub struct GridViewRevisionChangeset { - pub operations: GridViewOperations, - pub md5: String, + pub operations: GridViewOperations, + pub md5: String, } pub fn make_grid_view_rev_json_str(grid_revision: &DatabaseViewRevision) -> SyncResult { - let json = serde_json::to_string(grid_revision) - .map_err(|err| internal_sync_error(format!("Serialize grid view to json str failed. {:?}", err)))?; - Ok(json) + let json = serde_json::to_string(grid_revision).map_err(|err| { + internal_sync_error(format!("Serialize grid view to json str failed. {:?}", err)) + })?; + Ok(json) } pub fn make_grid_view_operations(grid_view: &DatabaseViewRevision) -> GridViewOperations { - let json = serde_json::to_string(grid_view).unwrap(); - GridViewOperationsBuilder::new().insert(&json).build() + let json = serde_json::to_string(grid_view).unwrap(); + GridViewOperationsBuilder::new().insert(&json).build() } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/document_pad.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/document_pad.rs index efd8c2e926..c6d460e2b9 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/document_pad.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/document_pad.rs @@ -1,9 +1,9 @@ use crate::{ - client_document::{ - history::{History, UndoResult}, - view::{ViewExtensions, RECORD_THRESHOLD}, - }, - errors::SyncError, + client_document::{ + history::{History, UndoResult}, + view::{ViewExtensions, RECORD_THRESHOLD}, + }, + errors::SyncError, }; use bytes::Bytes; use lib_infra::util::md5; @@ -12,227 +12,252 @@ use lib_ot::{core::*, text_delta::DeltaTextOperations}; use tokio::sync::mpsc; pub trait InitialDocument { - fn json_str() -> String; + fn json_str() -> String; } pub struct EmptyDocument(); impl InitialDocument for EmptyDocument { - fn json_str() -> String { - DeltaTextOperations::default().json_str() - } + fn json_str() -> String { + DeltaTextOperations::default().json_str() + } } pub struct NewlineDocument(); impl InitialDocument for NewlineDocument { - fn json_str() -> String { - initial_delta_document_content() - } + fn json_str() -> String { + initial_delta_document_content() + } } pub fn initial_delta_document_content() -> String { - DeltaTextOperationBuilder::new().insert("\n").build().json_str() + DeltaTextOperationBuilder::new() + .insert("\n") + .build() + .json_str() } pub struct ClientDocument { - operations: DeltaTextOperations, - history: History, - view: ViewExtensions, - last_edit_time: usize, - notify: Option>, + operations: DeltaTextOperations, + history: History, + view: ViewExtensions, + last_edit_time: usize, + notify: Option>, } impl ClientDocument { - pub fn new() -> Self { - let content = C::json_str(); - Self::from_json(&content).unwrap() + pub fn new() -> Self { + let content = C::json_str(); + Self::from_json(&content).unwrap() + } + + pub fn from_operations(operations: DeltaTextOperations) -> Self { + ClientDocument { + operations, + history: History::new(), + view: ViewExtensions::new(), + last_edit_time: 0, + notify: None, + } + } + + pub fn from_json(json: &str) -> Result { + let operations = DeltaTextOperations::from_json(json)?; + Ok(Self::from_operations(operations)) + } + + pub fn get_operations_json(&self) -> String { + self.operations.json_str() + } + + pub fn to_bytes(&self) -> Bytes { + self.operations.json_bytes() + } + + pub fn to_content(&self) -> String { + self.operations.content().unwrap() + } + + pub fn get_operations(&self) -> &DeltaTextOperations { + &self.operations + } + + pub fn document_md5(&self) -> String { + let bytes = self.to_bytes(); + md5(&bytes) + } + + pub fn set_notify(&mut self, notify: mpsc::UnboundedSender<()>) { + self.notify = Some(notify); + } + + pub fn set_operations(&mut self, operations: DeltaTextOperations) { + tracing::trace!("document: {}", operations.json_str()); + self.operations = operations; + + match &self.notify { + None => {}, + Some(notify) => { + let _ = notify.send(()); + }, + } + } + + pub fn compose_operations(&mut self, operations: DeltaTextOperations) -> Result<(), SyncError> { + tracing::trace!( + "{} compose {}", + &self.operations.json_str(), + operations.json_str() + ); + let composed_operations = self.operations.compose(&operations)?; + let mut undo_operations = operations.invert(&self.operations); + + let now = chrono::Utc::now().timestamp_millis() as usize; + if now - self.last_edit_time < RECORD_THRESHOLD { + if let Some(last_operation) = self.history.undo() { + tracing::trace!("compose previous change"); + tracing::trace!("current = {}", undo_operations); + tracing::trace!("previous = {}", last_operation); + undo_operations = undo_operations.compose(&last_operation)?; + } + } else { + self.last_edit_time = now; } - pub fn from_operations(operations: DeltaTextOperations) -> Self { - ClientDocument { - operations, - history: History::new(), - view: ViewExtensions::new(), - last_edit_time: 0, - notify: None, - } + if !undo_operations.is_empty() { + tracing::trace!("add history operations: {}", undo_operations); + self.history.record(undo_operations); } - pub fn from_json(json: &str) -> Result { - let operations = DeltaTextOperations::from_json(json)?; - Ok(Self::from_operations(operations)) - } - - pub fn get_operations_json(&self) -> String { - self.operations.json_str() - } - - pub fn to_bytes(&self) -> Bytes { - self.operations.json_bytes() - } - - pub fn to_content(&self) -> String { - self.operations.content().unwrap() - } - - pub fn get_operations(&self) -> &DeltaTextOperations { - &self.operations - } - - pub fn document_md5(&self) -> String { - let bytes = self.to_bytes(); - md5(&bytes) - } - - pub fn set_notify(&mut self, notify: mpsc::UnboundedSender<()>) { - self.notify = Some(notify); - } - - pub fn set_operations(&mut self, operations: DeltaTextOperations) { - tracing::trace!("document: {}", operations.json_str()); - self.operations = operations; - - match &self.notify { - None => {} - Some(notify) => { - let _ = notify.send(()); - } - } - } - - pub fn compose_operations(&mut self, operations: DeltaTextOperations) -> Result<(), SyncError> { - tracing::trace!("{} compose {}", &self.operations.json_str(), operations.json_str()); - let composed_operations = self.operations.compose(&operations)?; - let mut undo_operations = operations.invert(&self.operations); - - let now = chrono::Utc::now().timestamp_millis() as usize; - if now - self.last_edit_time < RECORD_THRESHOLD { - if let Some(last_operation) = self.history.undo() { - tracing::trace!("compose previous change"); - tracing::trace!("current = {}", undo_operations); - tracing::trace!("previous = {}", last_operation); - undo_operations = undo_operations.compose(&last_operation)?; - } - } else { - self.last_edit_time = now; - } - - if !undo_operations.is_empty() { - tracing::trace!("add history operations: {}", undo_operations); - self.history.record(undo_operations); - } - - self.set_operations(composed_operations); - Ok(()) - } - - pub fn insert(&mut self, index: usize, data: T) -> Result { - let text = data.to_string(); - let interval = Interval::new(index, index); - validate_interval(&self.operations, &interval)?; - let operations = self.view.insert(&self.operations, &text, interval)?; - self.compose_operations(operations.clone())?; - Ok(operations) - } - - pub fn delete(&mut self, interval: Interval) -> Result { - validate_interval(&self.operations, &interval)?; - debug_assert!(!interval.is_empty()); - let operations = self.view.delete(&self.operations, interval)?; - if !operations.is_empty() { - self.compose_operations(operations.clone())?; - } - Ok(operations) - } - - pub fn format(&mut self, interval: Interval, attribute: AttributeEntry) -> Result { - validate_interval(&self.operations, &interval)?; - tracing::trace!("format {} with {:?}", interval, attribute); - let operations = self.view.format(&self.operations, attribute, interval).unwrap(); - self.compose_operations(operations.clone())?; - Ok(operations) - } - - pub fn replace(&mut self, interval: Interval, data: T) -> Result { - validate_interval(&self.operations, &interval)?; - let mut operations = DeltaTextOperations::default(); - let text = data.to_string(); - if !text.is_empty() { - operations = self.view.insert(&self.operations, &text, interval)?; - self.compose_operations(operations.clone())?; - } - - if !interval.is_empty() { - let delete = self.delete(interval)?; - operations = operations.compose(&delete)?; - } - - Ok(operations) - } - - pub fn can_undo(&self) -> bool { - self.history.can_undo() - } - - pub fn can_redo(&self) -> bool { - self.history.can_redo() - } - - pub fn undo(&mut self) -> Result { - match self.history.undo() { - None => Err(SyncError::undo().context("Undo stack is empty")), - Some(undo_operations) => { - let (new_operations, inverted_operations) = self.invert(&undo_operations)?; - self.set_operations(new_operations); - self.history.add_redo(inverted_operations); - Ok(UndoResult { - operations: undo_operations, - }) - } - } - } - - pub fn redo(&mut self) -> Result { - match self.history.redo() { - None => Err(SyncError::redo()), - Some(redo_operations) => { - let (new_operations, inverted_operations) = self.invert(&redo_operations)?; - self.set_operations(new_operations); - self.history.add_undo(inverted_operations); - Ok(UndoResult { - operations: redo_operations, - }) - } - } - } - - pub fn is_empty(&self) -> bool { - // The document is empty if its text is equal to the initial text. - self.operations.json_str() == NewlineDocument::json_str() - } -} - -impl ClientDocument { - fn invert( - &self, - operations: &DeltaTextOperations, - ) -> Result<(DeltaTextOperations, DeltaTextOperations), SyncError> { - // c = a.compose(b) - // d = b.invert(a) - // a = c.compose(d) - let new_operations = self.operations.compose(operations)?; - let inverted_operations = operations.invert(&self.operations); - Ok((new_operations, inverted_operations)) - } -} - -fn validate_interval(operations: &DeltaTextOperations, interval: &Interval) -> Result<(), SyncError> { - if operations.utf16_target_len < interval.end { - tracing::error!( - "{:?} out of bounds. should 0..{}", - interval, - operations.utf16_target_len - ); - return Err(SyncError::out_of_bound()); - } + self.set_operations(composed_operations); Ok(()) + } + + pub fn insert( + &mut self, + index: usize, + data: T, + ) -> Result { + let text = data.to_string(); + let interval = Interval::new(index, index); + validate_interval(&self.operations, &interval)?; + let operations = self.view.insert(&self.operations, &text, interval)?; + self.compose_operations(operations.clone())?; + Ok(operations) + } + + pub fn delete(&mut self, interval: Interval) -> Result { + validate_interval(&self.operations, &interval)?; + debug_assert!(!interval.is_empty()); + let operations = self.view.delete(&self.operations, interval)?; + if !operations.is_empty() { + self.compose_operations(operations.clone())?; + } + Ok(operations) + } + + pub fn format( + &mut self, + interval: Interval, + attribute: AttributeEntry, + ) -> Result { + validate_interval(&self.operations, &interval)?; + tracing::trace!("format {} with {:?}", interval, attribute); + let operations = self + .view + .format(&self.operations, attribute, interval) + .unwrap(); + self.compose_operations(operations.clone())?; + Ok(operations) + } + + pub fn replace( + &mut self, + interval: Interval, + data: T, + ) -> Result { + validate_interval(&self.operations, &interval)?; + let mut operations = DeltaTextOperations::default(); + let text = data.to_string(); + if !text.is_empty() { + operations = self.view.insert(&self.operations, &text, interval)?; + self.compose_operations(operations.clone())?; + } + + if !interval.is_empty() { + let delete = self.delete(interval)?; + operations = operations.compose(&delete)?; + } + + Ok(operations) + } + + pub fn can_undo(&self) -> bool { + self.history.can_undo() + } + + pub fn can_redo(&self) -> bool { + self.history.can_redo() + } + + pub fn undo(&mut self) -> Result { + match self.history.undo() { + None => Err(SyncError::undo().context("Undo stack is empty")), + Some(undo_operations) => { + let (new_operations, inverted_operations) = self.invert(&undo_operations)?; + self.set_operations(new_operations); + self.history.add_redo(inverted_operations); + Ok(UndoResult { + operations: undo_operations, + }) + }, + } + } + + pub fn redo(&mut self) -> Result { + match self.history.redo() { + None => Err(SyncError::redo()), + Some(redo_operations) => { + let (new_operations, inverted_operations) = self.invert(&redo_operations)?; + self.set_operations(new_operations); + self.history.add_undo(inverted_operations); + Ok(UndoResult { + operations: redo_operations, + }) + }, + } + } + + pub fn is_empty(&self) -> bool { + // The document is empty if its text is equal to the initial text. + self.operations.json_str() == NewlineDocument::json_str() + } +} + +impl ClientDocument { + fn invert( + &self, + operations: &DeltaTextOperations, + ) -> Result<(DeltaTextOperations, DeltaTextOperations), SyncError> { + // c = a.compose(b) + // d = b.invert(a) + // a = c.compose(d) + let new_operations = self.operations.compose(operations)?; + let inverted_operations = operations.invert(&self.operations); + Ok((new_operations, inverted_operations)) + } +} + +fn validate_interval( + operations: &DeltaTextOperations, + interval: &Interval, +) -> Result<(), SyncError> { + if operations.utf16_target_len < interval.end { + tracing::error!( + "{:?} out of bounds. should 0..{}", + interval, + operations.utf16_target_len + ); + return Err(SyncError::out_of_bound()); + } + Ok(()) } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/default_delete.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/default_delete.rs index 208786a61d..8f4f6e3872 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/default_delete.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/default_delete.rs @@ -1,21 +1,21 @@ use crate::client_document::DeleteExt; use lib_ot::{ - core::{DeltaOperationBuilder, Interval}, - text_delta::DeltaTextOperations, + core::{DeltaOperationBuilder, Interval}, + text_delta::DeltaTextOperations, }; pub struct DefaultDelete {} impl DeleteExt for DefaultDelete { - fn ext_name(&self) -> &str { - "DefaultDelete" - } + fn ext_name(&self) -> &str { + "DefaultDelete" + } - fn apply(&self, _delta: &DeltaTextOperations, interval: Interval) -> Option { - Some( - DeltaOperationBuilder::new() - .retain(interval.start) - .delete(interval.size()) - .build(), - ) - } + fn apply(&self, _delta: &DeltaTextOperations, interval: Interval) -> Option { + Some( + DeltaOperationBuilder::new() + .retain(interval.start) + .delete(interval.size()) + .build(), + ) + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/preserve_line_format_merge.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/preserve_line_format_merge.rs index 4511a4a48f..e414a3f49b 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/preserve_line_format_merge.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/preserve_line_format_merge.rs @@ -1,62 +1,65 @@ use crate::{client_document::DeleteExt, util::is_newline}; use lib_ot::{ - core::{DeltaOperationBuilder, Interval, OperationAttributes, OperationIterator, Utf16CodeUnitMetric, NEW_LINE}, - text_delta::{empty_attributes, DeltaTextOperations}, + core::{ + DeltaOperationBuilder, Interval, OperationAttributes, OperationIterator, Utf16CodeUnitMetric, + NEW_LINE, + }, + text_delta::{empty_attributes, DeltaTextOperations}, }; pub struct PreserveLineFormatOnMerge {} impl DeleteExt for PreserveLineFormatOnMerge { - fn ext_name(&self) -> &str { - "PreserveLineFormatOnMerge" + fn ext_name(&self) -> &str { + "PreserveLineFormatOnMerge" + } + + fn apply(&self, delta: &DeltaTextOperations, interval: Interval) -> Option { + if interval.is_empty() { + return None; } - fn apply(&self, delta: &DeltaTextOperations, interval: Interval) -> Option { - if interval.is_empty() { - return None; - } + // seek to the interval start pos. e.g. You backspace enter pos + let mut iter = OperationIterator::from_offset(delta, interval.start); - // seek to the interval start pos. e.g. You backspace enter pos - let mut iter = OperationIterator::from_offset(delta, interval.start); - - // op will be the "\n" - let newline_op = iter.next_op_with_len(1)?; - if !is_newline(newline_op.get_data()) { - return None; - } - - iter.seek::(interval.size() - 1); - let mut new_delta = DeltaOperationBuilder::new() - .retain(interval.start) - .delete(interval.size()) - .build(); - - while iter.has_next() { - match iter.next() { - None => tracing::error!("op must be not None when has_next() return true"), - Some(op) => { - // - match op.get_data().find(NEW_LINE) { - None => { - new_delta.retain(op.len(), empty_attributes()); - continue; - } - Some(line_break) => { - let mut attributes = op.get_attributes(); - attributes.remove_all_value(); - - if newline_op.has_attribute() { - attributes.extend(newline_op.get_attributes()); - } - - new_delta.retain(line_break, empty_attributes()); - new_delta.retain(1, attributes); - break; - } - } - } - } - } - - Some(new_delta) + // op will be the "\n" + let newline_op = iter.next_op_with_len(1)?; + if !is_newline(newline_op.get_data()) { + return None; } + + iter.seek::(interval.size() - 1); + let mut new_delta = DeltaOperationBuilder::new() + .retain(interval.start) + .delete(interval.size()) + .build(); + + while iter.has_next() { + match iter.next() { + None => tracing::error!("op must be not None when has_next() return true"), + Some(op) => { + // + match op.get_data().find(NEW_LINE) { + None => { + new_delta.retain(op.len(), empty_attributes()); + continue; + }, + Some(line_break) => { + let mut attributes = op.get_attributes(); + attributes.remove_all_value(); + + if newline_op.has_attribute() { + attributes.extend(newline_op.get_attributes()); + } + + new_delta.retain(line_break, empty_attributes()); + new_delta.retain(1, attributes); + break; + }, + } + }, + } + } + + Some(new_delta) + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/resolve_block_format.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/resolve_block_format.rs index 680db108f4..6e2dfa0984 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/resolve_block_format.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/resolve_block_format.rs @@ -1,61 +1,63 @@ use lib_ot::core::AttributeEntry; use lib_ot::text_delta::is_block; use lib_ot::{ - core::{DeltaOperationBuilder, Interval, OperationIterator}, - text_delta::{empty_attributes, AttributeScope, DeltaTextOperations}, + core::{DeltaOperationBuilder, Interval, OperationIterator}, + text_delta::{empty_attributes, AttributeScope, DeltaTextOperations}, }; use crate::{ - client_document::{extensions::helper::line_break, FormatExt}, - util::find_newline, + client_document::{extensions::helper::line_break, FormatExt}, + util::find_newline, }; pub struct ResolveBlockFormat {} impl FormatExt for ResolveBlockFormat { - fn ext_name(&self) -> &str { - "ResolveBlockFormat" + fn ext_name(&self) -> &str { + "ResolveBlockFormat" + } + + fn apply( + &self, + delta: &DeltaTextOperations, + interval: Interval, + attribute: &AttributeEntry, + ) -> Option { + if !is_block(&attribute.key) { + return None; } - fn apply( - &self, - delta: &DeltaTextOperations, - interval: Interval, - attribute: &AttributeEntry, - ) -> Option { - if !is_block(&attribute.key) { - return None; - } + let mut new_delta = DeltaOperationBuilder::new().retain(interval.start).build(); + let mut iter = OperationIterator::from_offset(delta, interval.start); + let mut start = 0; + let end = interval.size(); + while start < end && iter.has_next() { + let next_op = iter.next_op_with_len(end - start).unwrap(); + match find_newline(next_op.get_data()) { + None => new_delta.retain(next_op.len(), empty_attributes()), + Some(_) => { + let tmp_delta = line_break(&next_op, attribute, AttributeScope::Block); + new_delta.extend(tmp_delta); + }, + } - let mut new_delta = DeltaOperationBuilder::new().retain(interval.start).build(); - let mut iter = OperationIterator::from_offset(delta, interval.start); - let mut start = 0; - let end = interval.size(); - while start < end && iter.has_next() { - let next_op = iter.next_op_with_len(end - start).unwrap(); - match find_newline(next_op.get_data()) { - None => new_delta.retain(next_op.len(), empty_attributes()), - Some(_) => { - let tmp_delta = line_break(&next_op, attribute, AttributeScope::Block); - new_delta.extend(tmp_delta); - } - } - - start += next_op.len(); - } - - while iter.has_next() { - let op = iter.next_op().expect("Unexpected None, iter.has_next() must return op"); - - match find_newline(op.get_data()) { - None => new_delta.retain(op.len(), empty_attributes()), - Some(line_break) => { - new_delta.retain(line_break, empty_attributes()); - new_delta.retain(1, attribute.clone().into()); - break; - } - } - } - - Some(new_delta) + start += next_op.len(); } + + while iter.has_next() { + let op = iter + .next_op() + .expect("Unexpected None, iter.has_next() must return op"); + + match find_newline(op.get_data()) { + None => new_delta.retain(op.len(), empty_attributes()), + Some(line_break) => { + new_delta.retain(line_break, empty_attributes()); + new_delta.retain(1, attribute.clone().into()); + break; + }, + } + } + + Some(new_delta) + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/resolve_inline_format.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/resolve_inline_format.rs index 6a3c0d23fa..eb8a4055b6 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/resolve_inline_format.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/resolve_inline_format.rs @@ -1,48 +1,48 @@ use lib_ot::core::AttributeEntry; use lib_ot::text_delta::is_inline; use lib_ot::{ - core::{DeltaOperationBuilder, Interval, OperationIterator}, - text_delta::{AttributeScope, DeltaTextOperations}, + core::{DeltaOperationBuilder, Interval, OperationIterator}, + text_delta::{AttributeScope, DeltaTextOperations}, }; use crate::{ - client_document::{extensions::helper::line_break, FormatExt}, - util::find_newline, + client_document::{extensions::helper::line_break, FormatExt}, + util::find_newline, }; pub struct ResolveInlineFormat {} impl FormatExt for ResolveInlineFormat { - fn ext_name(&self) -> &str { - "ResolveInlineFormat" + fn ext_name(&self) -> &str { + "ResolveInlineFormat" + } + + fn apply( + &self, + delta: &DeltaTextOperations, + interval: Interval, + attribute: &AttributeEntry, + ) -> Option { + if !is_inline(&attribute.key) { + return None; + } + let mut new_delta = DeltaOperationBuilder::new().retain(interval.start).build(); + let mut iter = OperationIterator::from_offset(delta, interval.start); + let mut start = 0; + let end = interval.size(); + + while start < end && iter.has_next() { + let next_op = iter.next_op_with_len(end - start).unwrap(); + match find_newline(next_op.get_data()) { + None => new_delta.retain(next_op.len(), attribute.clone().into()), + Some(_) => { + let tmp_delta = line_break(&next_op, attribute, AttributeScope::Inline); + new_delta.extend(tmp_delta); + }, + } + + start += next_op.len(); } - fn apply( - &self, - delta: &DeltaTextOperations, - interval: Interval, - attribute: &AttributeEntry, - ) -> Option { - if !is_inline(&attribute.key) { - return None; - } - let mut new_delta = DeltaOperationBuilder::new().retain(interval.start).build(); - let mut iter = OperationIterator::from_offset(delta, interval.start); - let mut start = 0; - let end = interval.size(); - - while start < end && iter.has_next() { - let next_op = iter.next_op_with_len(end - start).unwrap(); - match find_newline(next_op.get_data()) { - None => new_delta.retain(next_op.len(), attribute.clone().into()), - Some(_) => { - let tmp_delta = line_break(&next_op, attribute, AttributeScope::Inline); - new_delta.extend(tmp_delta); - } - } - - start += next_op.len(); - } - - Some(new_delta) - } + Some(new_delta) + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/helper.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/helper.rs index a1c571bc4b..4d9a4473f6 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/helper.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/helper.rs @@ -1,42 +1,44 @@ use crate::util::find_newline; use lib_ot::core::AttributeEntry; -use lib_ot::text_delta::{empty_attributes, AttributeScope, DeltaTextOperation, DeltaTextOperations}; +use lib_ot::text_delta::{ + empty_attributes, AttributeScope, DeltaTextOperation, DeltaTextOperations, +}; pub(crate) fn line_break( - op: &DeltaTextOperation, - attribute: &AttributeEntry, - scope: AttributeScope, + op: &DeltaTextOperation, + attribute: &AttributeEntry, + scope: AttributeScope, ) -> DeltaTextOperations { - let mut new_delta = DeltaTextOperations::new(); - let mut start = 0; - let end = op.len(); - let mut s = op.get_data(); + let mut new_delta = DeltaTextOperations::new(); + let mut start = 0; + let end = op.len(); + let mut s = op.get_data(); - while let Some(line_break) = find_newline(s) { - match scope { - AttributeScope::Inline => { - new_delta.retain(line_break - start, attribute.clone().into()); - new_delta.retain(1, empty_attributes()); - } - AttributeScope::Block => { - new_delta.retain(line_break - start, empty_attributes()); - new_delta.retain(1, attribute.clone().into()); - } - _ => { - tracing::error!("Unsupported parser line break for {:?}", scope); - } - } - - start = line_break + 1; - s = &s[start..s.len()]; + while let Some(line_break) = find_newline(s) { + match scope { + AttributeScope::Inline => { + new_delta.retain(line_break - start, attribute.clone().into()); + new_delta.retain(1, empty_attributes()); + }, + AttributeScope::Block => { + new_delta.retain(line_break - start, empty_attributes()); + new_delta.retain(1, attribute.clone().into()); + }, + _ => { + tracing::error!("Unsupported parser line break for {:?}", scope); + }, } - if start < end { - match scope { - AttributeScope::Inline => new_delta.retain(end - start, attribute.clone().into()), - AttributeScope::Block => new_delta.retain(end - start, empty_attributes()), - _ => tracing::error!("Unsupported parser line break for {:?}", scope), - } + start = line_break + 1; + s = &s[start..s.len()]; + } + + if start < end { + match scope { + AttributeScope::Inline => new_delta.retain(end - start, attribute.clone().into()), + AttributeScope::Block => new_delta.retain(end - start, empty_attributes()), + _ => tracing::error!("Unsupported parser line break for {:?}", scope), } - new_delta + } + new_delta } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/auto_exit_block.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/auto_exit_block.rs index 31f40545f9..14416cd5cf 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/auto_exit_block.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/auto_exit_block.rs @@ -5,56 +5,56 @@ use lib_ot::text_delta::{attributes_except_header, BuildInTextAttributeKey, Delt pub struct AutoExitBlock {} impl InsertExt for AutoExitBlock { - fn ext_name(&self) -> &str { - "AutoExitBlock" + fn ext_name(&self) -> &str { + "AutoExitBlock" + } + + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option { + // Auto exit block will be triggered by enter two new lines + if !is_newline(text) { + return None; } - fn apply( - &self, - delta: &DeltaTextOperations, - replace_len: usize, - text: &str, - index: usize, - ) -> Option { - // Auto exit block will be triggered by enter two new lines - if !is_newline(text) { - return None; - } - - if !is_empty_line_at_index(delta, index) { - return None; - } - - let mut iter = OperationIterator::from_offset(delta, index); - let next = iter.next_op()?; - let mut attributes = next.get_attributes(); - - let block_attributes = attributes_except_header(&next); - if block_attributes.is_empty() { - return None; - } - - if next.len() > 1 { - return None; - } - - match iter.next_op_with_newline() { - None => {} - Some((newline_op, _)) => { - let newline_attributes = attributes_except_header(&newline_op); - if block_attributes == newline_attributes { - return None; - } - } - } - - attributes.retain_values(&[BuildInTextAttributeKey::Header.as_ref()]); - - Some( - DeltaOperationBuilder::new() - .retain(index + replace_len) - .retain_with_attributes(1, attributes) - .build(), - ) + if !is_empty_line_at_index(delta, index) { + return None; } + + let mut iter = OperationIterator::from_offset(delta, index); + let next = iter.next_op()?; + let mut attributes = next.get_attributes(); + + let block_attributes = attributes_except_header(&next); + if block_attributes.is_empty() { + return None; + } + + if next.len() > 1 { + return None; + } + + match iter.next_op_with_newline() { + None => {}, + Some((newline_op, _)) => { + let newline_attributes = attributes_except_header(&newline_op); + if block_attributes == newline_attributes { + return None; + } + }, + } + + attributes.retain_values(&[BuildInTextAttributeKey::Header.as_ref()]); + + Some( + DeltaOperationBuilder::new() + .retain(index + replace_len) + .retain_with_attributes(1, attributes) + .build(), + ) + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/auto_format.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/auto_format.rs index e62fcff287..d5f416caa9 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/auto_format.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/auto_format.rs @@ -1,94 +1,94 @@ use crate::{client_document::InsertExt, util::is_whitespace}; use lib_ot::core::AttributeHashMap; use lib_ot::{ - core::{count_utf16_code_units, DeltaOperationBuilder, OperationIterator}, - text_delta::{empty_attributes, BuildInTextAttribute, DeltaTextOperations}, + core::{count_utf16_code_units, DeltaOperationBuilder, OperationIterator}, + text_delta::{empty_attributes, BuildInTextAttribute, DeltaTextOperations}, }; use std::cmp::min; use url::Url; pub struct AutoFormatExt {} impl InsertExt for AutoFormatExt { - fn ext_name(&self) -> &str { - "AutoFormatExt" + fn ext_name(&self) -> &str { + "AutoFormatExt" + } + + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option { + // enter whitespace to trigger auto format + if !is_whitespace(text) { + return None; } + let mut iter = OperationIterator::new(delta); + if let Some(prev) = iter.next_op_with_len(index) { + match AutoFormat::parse(prev.get_data()) { + None => {}, + Some(formatter) => { + let mut new_attributes = prev.get_attributes(); - fn apply( - &self, - delta: &DeltaTextOperations, - replace_len: usize, - text: &str, - index: usize, - ) -> Option { - // enter whitespace to trigger auto format - if !is_whitespace(text) { - return None; - } - let mut iter = OperationIterator::new(delta); - if let Some(prev) = iter.next_op_with_len(index) { - match AutoFormat::parse(prev.get_data()) { - None => {} - Some(formatter) => { - let mut new_attributes = prev.get_attributes(); + // format_len should not greater than index. The url crate will add "/" to the + // end of input string that causes the format_len greater than the input string + let format_len = min(index, formatter.format_len()); - // format_len should not greater than index. The url crate will add "/" to the - // end of input string that causes the format_len greater than the input string - let format_len = min(index, formatter.format_len()); - - let format_attributes = formatter.to_attributes(); - format_attributes.iter().for_each(|(k, v)| { - if !new_attributes.contains_key(k) { - new_attributes.insert(k.clone(), v.clone()); - } - }); - - let next_attributes = match iter.next_op() { - None => empty_attributes(), - Some(op) => op.get_attributes(), - }; - - return Some( - DeltaOperationBuilder::new() - .retain(index + replace_len - min(index, format_len)) - .retain_with_attributes(format_len, format_attributes) - .insert_with_attributes(text, next_attributes) - .build(), - ); - } + let format_attributes = formatter.to_attributes(); + format_attributes.iter().for_each(|(k, v)| { + if !new_attributes.contains_key(k) { + new_attributes.insert(k.clone(), v.clone()); } - } + }); - None + let next_attributes = match iter.next_op() { + None => empty_attributes(), + Some(op) => op.get_attributes(), + }; + + return Some( + DeltaOperationBuilder::new() + .retain(index + replace_len - min(index, format_len)) + .retain_with_attributes(format_len, format_attributes) + .insert_with_attributes(text, next_attributes) + .build(), + ); + }, + } } + + None + } } pub enum AutoFormatter { - Url(Url), + Url(Url), } impl AutoFormatter { - pub fn to_attributes(&self) -> AttributeHashMap { - match self { - AutoFormatter::Url(url) => BuildInTextAttribute::Link(url.as_str()).into(), - } + pub fn to_attributes(&self) -> AttributeHashMap { + match self { + AutoFormatter::Url(url) => BuildInTextAttribute::Link(url.as_str()).into(), } + } - pub fn format_len(&self) -> usize { - let s = match self { - AutoFormatter::Url(url) => url.to_string(), - }; + pub fn format_len(&self) -> usize { + let s = match self { + AutoFormatter::Url(url) => url.to_string(), + }; - count_utf16_code_units(&s) - } + count_utf16_code_units(&s) + } } pub struct AutoFormat {} impl AutoFormat { - fn parse(s: &str) -> Option { - if let Ok(url) = Url::parse(s) { - return Some(AutoFormatter::Url(url)); - } - - None + fn parse(s: &str) -> Option { + if let Ok(url) = Url::parse(s) { + return Some(AutoFormatter::Url(url)); } + + None + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/default_insert.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/default_insert.rs index 78248b6877..62b5fcc066 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/default_insert.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/default_insert.rs @@ -1,50 +1,50 @@ use crate::client_document::InsertExt; use lib_ot::core::AttributeHashMap; use lib_ot::{ - core::{DeltaOperationBuilder, OperationAttributes, OperationIterator, NEW_LINE}, - text_delta::{BuildInTextAttributeKey, DeltaTextOperations}, + core::{DeltaOperationBuilder, OperationAttributes, OperationIterator, NEW_LINE}, + text_delta::{BuildInTextAttributeKey, DeltaTextOperations}, }; pub struct DefaultInsertAttribute {} impl InsertExt for DefaultInsertAttribute { - fn ext_name(&self) -> &str { - "DefaultInsertAttribute" + fn ext_name(&self) -> &str { + "DefaultInsertAttribute" + } + + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option { + let iter = OperationIterator::new(delta); + let mut attributes = AttributeHashMap::new(); + + // Enable each line split by "\n" remains the block attributes. for example: + // insert "\n" to "123456" at index 3 + // + // [{"insert":"123"},{"insert":"\n","attributes":{"header":1}}, + // {"insert":"456"},{"insert":"\n","attributes":{"header":1}}] + if text.ends_with(NEW_LINE) { + match iter.last() { + None => {}, + Some(op) => { + if op + .get_attributes() + .contains_key(BuildInTextAttributeKey::Header.as_ref()) + { + attributes.extend(op.get_attributes()); + } + }, + } } - fn apply( - &self, - delta: &DeltaTextOperations, - replace_len: usize, - text: &str, - index: usize, - ) -> Option { - let iter = OperationIterator::new(delta); - let mut attributes = AttributeHashMap::new(); - - // Enable each line split by "\n" remains the block attributes. for example: - // insert "\n" to "123456" at index 3 - // - // [{"insert":"123"},{"insert":"\n","attributes":{"header":1}}, - // {"insert":"456"},{"insert":"\n","attributes":{"header":1}}] - if text.ends_with(NEW_LINE) { - match iter.last() { - None => {} - Some(op) => { - if op - .get_attributes() - .contains_key(BuildInTextAttributeKey::Header.as_ref()) - { - attributes.extend(op.get_attributes()); - } - } - } - } - - Some( - DeltaOperationBuilder::new() - .retain(index + replace_len) - .insert_with_attributes(text, attributes) - .build(), - ) - } + Some( + DeltaOperationBuilder::new() + .retain(index + replace_len) + .insert_with_attributes(text, attributes) + .build(), + ) + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/mod.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/mod.rs index 3ec97f37d8..c703bd5dc5 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/mod.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/mod.rs @@ -16,34 +16,34 @@ mod reset_format_on_new_line; pub struct InsertEmbedsExt {} impl InsertExt for InsertEmbedsExt { - fn ext_name(&self) -> &str { - "InsertEmbedsExt" - } + fn ext_name(&self) -> &str { + "InsertEmbedsExt" + } - fn apply( - &self, - _delta: &DeltaTextOperations, - _replace_len: usize, - _text: &str, - _index: usize, - ) -> Option { - None - } + fn apply( + &self, + _delta: &DeltaTextOperations, + _replace_len: usize, + _text: &str, + _index: usize, + ) -> Option { + None + } } pub struct ForceNewlineForInsertsAroundEmbedExt {} impl InsertExt for ForceNewlineForInsertsAroundEmbedExt { - fn ext_name(&self) -> &str { - "ForceNewlineForInsertsAroundEmbedExt" - } + fn ext_name(&self) -> &str { + "ForceNewlineForInsertsAroundEmbedExt" + } - fn apply( - &self, - _delta: &DeltaTextOperations, - _replace_len: usize, - _text: &str, - _index: usize, - ) -> Option { - None - } + fn apply( + &self, + _delta: &DeltaTextOperations, + _replace_len: usize, + _text: &str, + _index: usize, + ) -> Option { + None + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/preserve_block_format.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/preserve_block_format.rs index 46859cdb1f..54a7cd24e3 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/preserve_block_format.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/preserve_block_format.rs @@ -1,68 +1,72 @@ use crate::{client_document::InsertExt, util::is_newline}; use lib_ot::core::AttributeHashMap; use lib_ot::{ - core::{DeltaOperationBuilder, OperationIterator, NEW_LINE}, - text_delta::{attributes_except_header, empty_attributes, BuildInTextAttributeKey, DeltaTextOperations}, + core::{DeltaOperationBuilder, OperationIterator, NEW_LINE}, + text_delta::{ + attributes_except_header, empty_attributes, BuildInTextAttributeKey, DeltaTextOperations, + }, }; pub struct PreserveBlockFormatOnInsert {} impl InsertExt for PreserveBlockFormatOnInsert { - fn ext_name(&self) -> &str { - "PreserveBlockFormatOnInsert" + fn ext_name(&self) -> &str { + "PreserveBlockFormatOnInsert" + } + + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option { + if !is_newline(text) { + return None; } - fn apply( - &self, - delta: &DeltaTextOperations, - replace_len: usize, - text: &str, - index: usize, - ) -> Option { - if !is_newline(text) { - return None; + let mut iter = OperationIterator::from_offset(delta, index); + match iter.next_op_with_newline() { + None => {}, + Some((newline_op, offset)) => { + let newline_attributes = newline_op.get_attributes(); + let block_attributes = attributes_except_header(&newline_op); + if block_attributes.is_empty() { + return None; } - let mut iter = OperationIterator::from_offset(delta, index); - match iter.next_op_with_newline() { - None => {} - Some((newline_op, offset)) => { - let newline_attributes = newline_op.get_attributes(); - let block_attributes = attributes_except_header(&newline_op); - if block_attributes.is_empty() { - return None; - } - - let mut reset_attribute = AttributeHashMap::new(); - if newline_attributes.contains_key(BuildInTextAttributeKey::Header.as_ref()) { - reset_attribute.insert(BuildInTextAttributeKey::Header, 1); - } - - let lines: Vec<_> = text.split(NEW_LINE).collect(); - let mut new_delta = DeltaOperationBuilder::new().retain(index + replace_len).build(); - lines.iter().enumerate().for_each(|(i, line)| { - if !line.is_empty() { - new_delta.insert(line, empty_attributes()); - } - - if i == 0 { - new_delta.insert(NEW_LINE, newline_attributes.clone()); - } else if i < lines.len() - 1 { - new_delta.insert(NEW_LINE, block_attributes.clone()); - } else { - // do nothing - } - }); - if !reset_attribute.is_empty() { - new_delta.retain(offset, empty_attributes()); - let len = newline_op.get_data().find(NEW_LINE).unwrap(); - new_delta.retain(len, empty_attributes()); - new_delta.retain(1, reset_attribute); - } - - return Some(new_delta); - } + let mut reset_attribute = AttributeHashMap::new(); + if newline_attributes.contains_key(BuildInTextAttributeKey::Header.as_ref()) { + reset_attribute.insert(BuildInTextAttributeKey::Header, 1); } - None + let lines: Vec<_> = text.split(NEW_LINE).collect(); + let mut new_delta = DeltaOperationBuilder::new() + .retain(index + replace_len) + .build(); + lines.iter().enumerate().for_each(|(i, line)| { + if !line.is_empty() { + new_delta.insert(line, empty_attributes()); + } + + if i == 0 { + new_delta.insert(NEW_LINE, newline_attributes.clone()); + } else if i < lines.len() - 1 { + new_delta.insert(NEW_LINE, block_attributes.clone()); + } else { + // do nothing + } + }); + if !reset_attribute.is_empty() { + new_delta.retain(offset, empty_attributes()); + let len = newline_op.get_data().find(NEW_LINE).unwrap(); + new_delta.retain(len, empty_attributes()); + new_delta.retain(1, reset_attribute); + } + + return Some(new_delta); + }, } + + None + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/preserve_inline_format.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/preserve_inline_format.rs index d7f238a21e..c6f4738d63 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/preserve_inline_format.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/preserve_inline_format.rs @@ -1,109 +1,109 @@ use crate::{ - client_document::InsertExt, - util::{contain_newline, is_newline}, + client_document::InsertExt, + util::{contain_newline, is_newline}, }; use lib_ot::{ - core::{DeltaOperationBuilder, OpNewline, OperationIterator, NEW_LINE}, - text_delta::{empty_attributes, BuildInTextAttributeKey, DeltaTextOperations}, + core::{DeltaOperationBuilder, OpNewline, OperationIterator, NEW_LINE}, + text_delta::{empty_attributes, BuildInTextAttributeKey, DeltaTextOperations}, }; pub struct PreserveInlineFormat {} impl InsertExt for PreserveInlineFormat { - fn ext_name(&self) -> &str { - "PreserveInlineFormat" + fn ext_name(&self) -> &str { + "PreserveInlineFormat" + } + + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option { + if contain_newline(text) { + return None; } - fn apply( - &self, - delta: &DeltaTextOperations, - replace_len: usize, - text: &str, - index: usize, - ) -> Option { - if contain_newline(text) { - return None; - } - - let mut iter = OperationIterator::new(delta); - let prev = iter.next_op_with_len(index)?; - if OpNewline::parse(&prev).is_contain() { - return None; - } - - let mut attributes = prev.get_attributes(); - if attributes.is_empty() || !attributes.contains_key(BuildInTextAttributeKey::Link.as_ref()) { - return Some( - DeltaOperationBuilder::new() - .retain(index + replace_len) - .insert_with_attributes(text, attributes) - .build(), - ); - } - - let next = iter.next_op(); - match &next { - None => attributes = empty_attributes(), - Some(next) => { - if OpNewline::parse(next).is_equal() { - attributes = empty_attributes(); - } - } - } - - let new_delta = DeltaOperationBuilder::new() - .retain(index + replace_len) - .insert_with_attributes(text, attributes) - .build(); - - Some(new_delta) + let mut iter = OperationIterator::new(delta); + let prev = iter.next_op_with_len(index)?; + if OpNewline::parse(&prev).is_contain() { + return None; } + + let mut attributes = prev.get_attributes(); + if attributes.is_empty() || !attributes.contains_key(BuildInTextAttributeKey::Link.as_ref()) { + return Some( + DeltaOperationBuilder::new() + .retain(index + replace_len) + .insert_with_attributes(text, attributes) + .build(), + ); + } + + let next = iter.next_op(); + match &next { + None => attributes = empty_attributes(), + Some(next) => { + if OpNewline::parse(next).is_equal() { + attributes = empty_attributes(); + } + }, + } + + let new_delta = DeltaOperationBuilder::new() + .retain(index + replace_len) + .insert_with_attributes(text, attributes) + .build(); + + Some(new_delta) + } } pub struct PreserveLineFormatOnSplit {} impl InsertExt for PreserveLineFormatOnSplit { - fn ext_name(&self) -> &str { - "PreserveLineFormatOnSplit" + fn ext_name(&self) -> &str { + "PreserveLineFormatOnSplit" + } + + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option { + if !is_newline(text) { + return None; } - fn apply( - &self, - delta: &DeltaTextOperations, - replace_len: usize, - text: &str, - index: usize, - ) -> Option { - if !is_newline(text) { - return None; - } - - let mut iter = OperationIterator::new(delta); - let prev = iter.next_op_with_len(index)?; - if OpNewline::parse(&prev).is_end() { - return None; - } - - let next = iter.next_op()?; - let newline_status = OpNewline::parse(&next); - if newline_status.is_end() { - return None; - } - - let mut new_delta = DeltaTextOperations::new(); - new_delta.retain(index + replace_len, empty_attributes()); - - if newline_status.is_contain() { - debug_assert!(!next.has_attribute()); - new_delta.insert(NEW_LINE, empty_attributes()); - return Some(new_delta); - } - - match iter.next_op_with_newline() { - None => {} - Some((newline_op, _)) => { - new_delta.insert(NEW_LINE, newline_op.get_attributes()); - } - } - - Some(new_delta) + let mut iter = OperationIterator::new(delta); + let prev = iter.next_op_with_len(index)?; + if OpNewline::parse(&prev).is_end() { + return None; } + + let next = iter.next_op()?; + let newline_status = OpNewline::parse(&next); + if newline_status.is_end() { + return None; + } + + let mut new_delta = DeltaTextOperations::new(); + new_delta.retain(index + replace_len, empty_attributes()); + + if newline_status.is_contain() { + debug_assert!(!next.has_attribute()); + new_delta.insert(NEW_LINE, empty_attributes()); + return Some(new_delta); + } + + match iter.next_op_with_newline() { + None => {}, + Some((newline_op, _)) => { + new_delta.insert(NEW_LINE, newline_op.get_attributes()); + }, + } + + Some(new_delta) + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/reset_format_on_new_line.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/reset_format_on_new_line.rs index 067e373212..ecd5593e1e 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/reset_format_on_new_line.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/reset_format_on_new_line.rs @@ -1,50 +1,50 @@ use crate::{client_document::InsertExt, util::is_newline}; use lib_ot::core::AttributeHashMap; use lib_ot::{ - core::{DeltaOperationBuilder, OperationIterator, Utf16CodeUnitMetric, NEW_LINE}, - text_delta::{BuildInTextAttributeKey, DeltaTextOperations}, + core::{DeltaOperationBuilder, OperationIterator, Utf16CodeUnitMetric, NEW_LINE}, + text_delta::{BuildInTextAttributeKey, DeltaTextOperations}, }; pub struct ResetLineFormatOnNewLine {} impl InsertExt for ResetLineFormatOnNewLine { - fn ext_name(&self) -> &str { - "ResetLineFormatOnNewLine" + fn ext_name(&self) -> &str { + "ResetLineFormatOnNewLine" + } + + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option { + if !is_newline(text) { + return None; } - fn apply( - &self, - delta: &DeltaTextOperations, - replace_len: usize, - text: &str, - index: usize, - ) -> Option { - if !is_newline(text) { - return None; - } - - let mut iter = OperationIterator::new(delta); - iter.seek::(index); - let next_op = iter.next_op()?; - if !next_op.get_data().starts_with(NEW_LINE) { - return None; - } - - let mut reset_attribute = AttributeHashMap::new(); - if next_op - .get_attributes() - .contains_key(BuildInTextAttributeKey::Header.as_ref()) - { - reset_attribute.remove_value(BuildInTextAttributeKey::Header); - } - - let len = index + replace_len; - Some( - DeltaOperationBuilder::new() - .retain(len) - .insert_with_attributes(NEW_LINE, next_op.get_attributes()) - .retain_with_attributes(1, reset_attribute) - .trim() - .build(), - ) + let mut iter = OperationIterator::new(delta); + iter.seek::(index); + let next_op = iter.next_op()?; + if !next_op.get_data().starts_with(NEW_LINE) { + return None; } + + let mut reset_attribute = AttributeHashMap::new(); + if next_op + .get_attributes() + .contains_key(BuildInTextAttributeKey::Header.as_ref()) + { + reset_attribute.remove_value(BuildInTextAttributeKey::Header); + } + + let len = index + replace_len; + Some( + DeltaOperationBuilder::new() + .retain(len) + .insert_with_attributes(NEW_LINE, next_op.get_attributes()) + .retain_with_attributes(1, reset_attribute) + .trim() + .build(), + ) + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/mod.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/mod.rs index 6cfc1f48ac..8a1ef23403 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/mod.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/mod.rs @@ -14,27 +14,27 @@ pub type FormatExtension = Box; pub type DeleteExtension = Box; pub trait InsertExt { - fn ext_name(&self) -> &str; - fn apply( - &self, - delta: &DeltaTextOperations, - replace_len: usize, - text: &str, - index: usize, - ) -> Option; + fn ext_name(&self) -> &str; + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option; } pub trait FormatExt { - fn ext_name(&self) -> &str; - fn apply( - &self, - delta: &DeltaTextOperations, - interval: Interval, - attribute: &AttributeEntry, - ) -> Option; + fn ext_name(&self) -> &str; + fn apply( + &self, + delta: &DeltaTextOperations, + interval: Interval, + attribute: &AttributeEntry, + ) -> Option; } pub trait DeleteExt { - fn ext_name(&self) -> &str; - fn apply(&self, delta: &DeltaTextOperations, interval: Interval) -> Option; + fn ext_name(&self) -> &str; + fn apply(&self, delta: &DeltaTextOperations, interval: Interval) -> Option; } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/history.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/history.rs index 07930f3973..c668125b22 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/history.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/history.rs @@ -4,77 +4,77 @@ const MAX_UNDOES: usize = 20; #[derive(Debug, Clone)] pub struct UndoResult { - pub operations: DeltaTextOperations, + pub operations: DeltaTextOperations, } #[derive(Debug, Clone)] pub struct History { - #[allow(dead_code)] - cur_undo: usize, - undoes: Vec, - redoes: Vec, - capacity: usize, + #[allow(dead_code)] + cur_undo: usize, + undoes: Vec, + redoes: Vec, + capacity: usize, } impl std::default::Default for History { - fn default() -> Self { - History { - cur_undo: 1, - undoes: Vec::new(), - redoes: Vec::new(), - capacity: MAX_UNDOES, - } + fn default() -> Self { + History { + cur_undo: 1, + undoes: Vec::new(), + redoes: Vec::new(), + capacity: MAX_UNDOES, } + } } impl History { - pub fn new() -> Self { - History::default() + pub fn new() -> Self { + History::default() + } + + pub fn can_undo(&self) -> bool { + !self.undoes.is_empty() + } + + pub fn can_redo(&self) -> bool { + !self.redoes.is_empty() + } + + pub fn add_undo(&mut self, delta: DeltaTextOperations) { + self.undoes.push(delta); + } + + pub fn add_redo(&mut self, delta: DeltaTextOperations) { + self.redoes.push(delta); + } + + pub fn record(&mut self, delta: DeltaTextOperations) { + if delta.ops.is_empty() { + return; } - pub fn can_undo(&self) -> bool { - !self.undoes.is_empty() + self.redoes.clear(); + self.add_undo(delta); + + if self.undoes.len() > self.capacity { + self.undoes.remove(0); + } + } + + pub fn undo(&mut self) -> Option { + if !self.can_undo() { + return None; + } + let delta = self.undoes.pop().unwrap(); + Some(delta) + } + + pub fn redo(&mut self) -> Option { + if !self.can_redo() { + return None; } - pub fn can_redo(&self) -> bool { - !self.redoes.is_empty() - } - - pub fn add_undo(&mut self, delta: DeltaTextOperations) { - self.undoes.push(delta); - } - - pub fn add_redo(&mut self, delta: DeltaTextOperations) { - self.redoes.push(delta); - } - - pub fn record(&mut self, delta: DeltaTextOperations) { - if delta.ops.is_empty() { - return; - } - - self.redoes.clear(); - self.add_undo(delta); - - if self.undoes.len() > self.capacity { - self.undoes.remove(0); - } - } - - pub fn undo(&mut self) -> Option { - if !self.can_undo() { - return None; - } - let delta = self.undoes.pop().unwrap(); - Some(delta) - } - - pub fn redo(&mut self) -> Option { - if !self.can_redo() { - return None; - } - - let delta = self.redoes.pop().unwrap(); - Some(delta) - } + let delta = self.redoes.pop().unwrap(); + Some(delta) + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/view.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/view.rs index e009f1ae45..e1804c9eff 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/view.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_document/view.rs @@ -1,116 +1,119 @@ use crate::client_document::*; use lib_ot::core::AttributeEntry; use lib_ot::{ - core::{trim, Interval}, - errors::{ErrorBuilder, OTError, OTErrorCode}, - text_delta::DeltaTextOperations, + core::{trim, Interval}, + errors::{ErrorBuilder, OTError, OTErrorCode}, + text_delta::DeltaTextOperations, }; pub const RECORD_THRESHOLD: usize = 400; // in milliseconds pub struct ViewExtensions { - insert_exts: Vec, - format_exts: Vec, - delete_exts: Vec, + insert_exts: Vec, + format_exts: Vec, + delete_exts: Vec, } impl ViewExtensions { - pub(crate) fn new() -> Self { - Self { - insert_exts: construct_insert_exts(), - format_exts: construct_format_exts(), - delete_exts: construct_delete_exts(), - } + pub(crate) fn new() -> Self { + Self { + insert_exts: construct_insert_exts(), + format_exts: construct_format_exts(), + delete_exts: construct_delete_exts(), + } + } + + pub(crate) fn insert( + &self, + operations: &DeltaTextOperations, + text: &str, + interval: Interval, + ) -> Result { + let mut new_operations = None; + for ext in &self.insert_exts { + if let Some(mut operations) = ext.apply(operations, interval.size(), text, interval.start) { + trim(&mut operations); + tracing::trace!("[{}] applied, delta: {}", ext.ext_name(), operations); + new_operations = Some(operations); + break; + } } - pub(crate) fn insert( - &self, - operations: &DeltaTextOperations, - text: &str, - interval: Interval, - ) -> Result { - let mut new_operations = None; - for ext in &self.insert_exts { - if let Some(mut operations) = ext.apply(operations, interval.size(), text, interval.start) { - trim(&mut operations); - tracing::trace!("[{}] applied, delta: {}", ext.ext_name(), operations); - new_operations = Some(operations); - break; - } - } + match new_operations { + None => Err(ErrorBuilder::new(OTErrorCode::ApplyInsertFail).build()), + Some(new_operations) => Ok(new_operations), + } + } - match new_operations { - None => Err(ErrorBuilder::new(OTErrorCode::ApplyInsertFail).build()), - Some(new_operations) => Ok(new_operations), - } + pub(crate) fn delete( + &self, + delta: &DeltaTextOperations, + interval: Interval, + ) -> Result { + let mut new_delta = None; + for ext in &self.delete_exts { + if let Some(mut delta) = ext.apply(delta, interval) { + trim(&mut delta); + tracing::trace!("[{}] applied, delta: {}", ext.ext_name(), delta); + new_delta = Some(delta); + break; + } } - pub(crate) fn delete( - &self, - delta: &DeltaTextOperations, - interval: Interval, - ) -> Result { - let mut new_delta = None; - for ext in &self.delete_exts { - if let Some(mut delta) = ext.apply(delta, interval) { - trim(&mut delta); - tracing::trace!("[{}] applied, delta: {}", ext.ext_name(), delta); - new_delta = Some(delta); - break; - } - } + match new_delta { + None => Err(ErrorBuilder::new(OTErrorCode::ApplyDeleteFail).build()), + Some(new_delta) => Ok(new_delta), + } + } - match new_delta { - None => Err(ErrorBuilder::new(OTErrorCode::ApplyDeleteFail).build()), - Some(new_delta) => Ok(new_delta), - } + pub(crate) fn format( + &self, + operations: &DeltaTextOperations, + attribute: AttributeEntry, + interval: Interval, + ) -> Result { + let mut new_operations = None; + for ext in &self.format_exts { + if let Some(mut operations) = ext.apply(operations, interval, &attribute) { + trim(&mut operations); + tracing::trace!("[{}] applied, delta: {}", ext.ext_name(), operations); + new_operations = Some(operations); + break; + } } - pub(crate) fn format( - &self, - operations: &DeltaTextOperations, - attribute: AttributeEntry, - interval: Interval, - ) -> Result { - let mut new_operations = None; - for ext in &self.format_exts { - if let Some(mut operations) = ext.apply(operations, interval, &attribute) { - trim(&mut operations); - tracing::trace!("[{}] applied, delta: {}", ext.ext_name(), operations); - new_operations = Some(operations); - break; - } - } - - match new_operations { - None => Err(ErrorBuilder::new(OTErrorCode::ApplyFormatFail).build()), - Some(new_operations) => Ok(new_operations), - } + match new_operations { + None => Err(ErrorBuilder::new(OTErrorCode::ApplyFormatFail).build()), + Some(new_operations) => Ok(new_operations), } + } } fn construct_insert_exts() -> Vec { - vec![ - Box::new(InsertEmbedsExt {}), - Box::new(ForceNewlineForInsertsAroundEmbedExt {}), - Box::new(AutoExitBlock {}), - Box::new(PreserveBlockFormatOnInsert {}), - Box::new(PreserveLineFormatOnSplit {}), - Box::new(ResetLineFormatOnNewLine {}), - Box::new(AutoFormatExt {}), - Box::new(PreserveInlineFormat {}), - Box::new(DefaultInsertAttribute {}), - ] + vec![ + Box::new(InsertEmbedsExt {}), + Box::new(ForceNewlineForInsertsAroundEmbedExt {}), + Box::new(AutoExitBlock {}), + Box::new(PreserveBlockFormatOnInsert {}), + Box::new(PreserveLineFormatOnSplit {}), + Box::new(ResetLineFormatOnNewLine {}), + Box::new(AutoFormatExt {}), + Box::new(PreserveInlineFormat {}), + Box::new(DefaultInsertAttribute {}), + ] } fn construct_format_exts() -> Vec { - vec![ - // Box::new(FormatLinkAtCaretPositionExt {}), - Box::new(ResolveBlockFormat {}), - Box::new(ResolveInlineFormat {}), - ] + vec![ + // Box::new(FormatLinkAtCaretPositionExt {}), + Box::new(ResolveBlockFormat {}), + Box::new(ResolveInlineFormat {}), + ] } fn construct_delete_exts() -> Vec { - vec![Box::new(PreserveLineFormatOnMerge {}), Box::new(DefaultDelete {})] + vec![ + Box::new(PreserveLineFormatOnMerge {}), + Box::new(DefaultDelete {}), + ] } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_folder/builder.rs b/frontend/rust-lib/flowy-client-sync/src/client_folder/builder.rs index 4cb97a696d..606cb92eb2 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_folder/builder.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_folder/builder.rs @@ -1,7 +1,7 @@ use crate::client_folder::FolderOperations; use crate::{ - client_folder::{default_folder_operations, FolderPad}, - errors::SyncResult, + client_folder::{default_folder_operations, FolderPad}, + errors::SyncResult, }; use flowy_sync::util::make_operations_from_revisions; use folder_model::{TrashRevision, WorkspaceRevision}; @@ -10,40 +10,40 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] pub(crate) struct FolderPadBuilder { - workspaces: Vec, - trash: Vec, + workspaces: Vec, + trash: Vec, } impl FolderPadBuilder { - pub(crate) fn new() -> Self { - Self { - workspaces: vec![], - trash: vec![], - } + pub(crate) fn new() -> Self { + Self { + workspaces: vec![], + trash: vec![], } + } - #[allow(dead_code)] - pub(crate) fn with_workspace(mut self, workspaces: Vec) -> Self { - self.workspaces = workspaces; - self - } + #[allow(dead_code)] + pub(crate) fn with_workspace(mut self, workspaces: Vec) -> Self { + self.workspaces = workspaces; + self + } - #[allow(dead_code)] - pub(crate) fn with_trash(mut self, trash: Vec) -> Self { - self.trash = trash; - self - } + #[allow(dead_code)] + pub(crate) fn with_trash(mut self, trash: Vec) -> Self { + self.trash = trash; + self + } - pub(crate) fn build_with_revisions(self, revisions: Vec) -> SyncResult { - let mut operations: FolderOperations = make_operations_from_revisions(revisions)?; - if operations.is_empty() { - operations = default_folder_operations(); - } - FolderPad::from_operations(operations) + pub(crate) fn build_with_revisions(self, revisions: Vec) -> SyncResult { + let mut operations: FolderOperations = make_operations_from_revisions(revisions)?; + if operations.is_empty() { + operations = default_folder_operations(); } + FolderPad::from_operations(operations) + } - #[allow(dead_code)] - pub(crate) fn build(self) -> SyncResult { - FolderPad::new(self.workspaces, self.trash) - } + #[allow(dead_code)] + pub(crate) fn build(self) -> SyncResult { + FolderPad::new(self.workspaces, self.trash) + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_folder/folder_node.rs b/frontend/rust-lib/flowy-client-sync/src/client_folder/folder_node.rs index 7083c5b17c..891f7d2e02 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_folder/folder_node.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_folder/folder_node.rs @@ -10,133 +10,138 @@ use std::sync::Arc; pub type AtomicNodeTree = RwLock; pub struct FolderNodePad { - pub tree: Arc, - pub node_id: NodeId, - pub workspaces: WorkspaceList, - pub trash: TrashList, + pub tree: Arc, + pub node_id: NodeId, + pub workspaces: WorkspaceList, + pub trash: TrashList, } #[derive(Clone, Node)] #[node_type = "workspaces"] pub struct WorkspaceList { - pub tree: Arc, - pub node_id: Option, + pub tree: Arc, + pub node_id: Option, - #[node(child_name = "workspace")] - inner: Vec, + #[node(child_name = "workspace")] + inner: Vec, } impl std::ops::Deref for WorkspaceList { - type Target = Vec; + type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.inner - } + fn deref(&self) -> &Self::Target { + &self.inner + } } impl std::ops::DerefMut for WorkspaceList { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } #[derive(Clone, Node)] #[node_type = "trash"] pub struct TrashList { - pub tree: Arc, - pub node_id: Option, + pub tree: Arc, + pub node_id: Option, - #[node(child_name = "trash")] - inner: Vec, + #[node(child_name = "trash")] + inner: Vec, } impl FolderNodePad { - pub fn new() -> Self { - Self::default() - } + pub fn new() -> Self { + Self::default() + } - pub fn get_workspace(&self, workspace_id: &str) -> Option<&WorkspaceNode> { - self.workspaces.iter().find(|workspace| workspace.id == workspace_id) - } + pub fn get_workspace(&self, workspace_id: &str) -> Option<&WorkspaceNode> { + self + .workspaces + .iter() + .find(|workspace| workspace.id == workspace_id) + } - pub fn get_mut_workspace(&mut self, workspace_id: &str) -> Option<&mut WorkspaceNode> { - self.workspaces - .iter_mut() - .find(|workspace| workspace.id == workspace_id) - } + pub fn get_mut_workspace(&mut self, workspace_id: &str) -> Option<&mut WorkspaceNode> { + self + .workspaces + .iter_mut() + .find(|workspace| workspace.id == workspace_id) + } - pub fn add_workspace(&mut self, mut workspace: WorkspaceNode) { - let path = workspaces_path().clone_with(self.workspaces.len()); - let op = NodeOperation::Insert { - path: path.clone(), - nodes: vec![workspace.to_node_data()], - }; - self.tree.write().apply_op(op).unwrap(); + pub fn add_workspace(&mut self, mut workspace: WorkspaceNode) { + let path = workspaces_path().clone_with(self.workspaces.len()); + let op = NodeOperation::Insert { + path: path.clone(), + nodes: vec![workspace.to_node_data()], + }; + self.tree.write().apply_op(op).unwrap(); - let node_id = self.tree.read().node_id_at_path(path).unwrap(); - workspace.node_id = Some(node_id); - self.workspaces.push(workspace); - } + let node_id = self.tree.read().node_id_at_path(path).unwrap(); + workspace.node_id = Some(node_id); + self.workspaces.push(workspace); + } - pub fn to_json(&self, pretty: bool) -> SyncResult { - self.tree - .read() - .to_json(pretty) - .map_err(|e| SyncError::serde().context(e)) - } + pub fn to_json(&self, pretty: bool) -> SyncResult { + self + .tree + .read() + .to_json(pretty) + .map_err(|e| SyncError::serde().context(e)) + } } impl std::default::Default for FolderNodePad { - fn default() -> Self { - let tree = Arc::new(RwLock::new(NodeTree::default())); + fn default() -> Self { + let tree = Arc::new(RwLock::new(NodeTree::default())); - // Workspace - let mut workspaces = WorkspaceList { - tree: tree.clone(), - node_id: None, - inner: vec![], - }; - let workspace_node = workspaces.to_node_data(); + // Workspace + let mut workspaces = WorkspaceList { + tree: tree.clone(), + node_id: None, + inner: vec![], + }; + let workspace_node = workspaces.to_node_data(); - // Trash - let mut trash = TrashList { - tree: tree.clone(), - node_id: None, - inner: vec![], - }; - let trash_node = trash.to_node_data(); + // Trash + let mut trash = TrashList { + tree: tree.clone(), + node_id: None, + inner: vec![], + }; + let trash_node = trash.to_node_data(); - let folder_node = NodeDataBuilder::new("folder") - .add_node_data(workspace_node) - .add_node_data(trash_node) - .build(); + let folder_node = NodeDataBuilder::new("folder") + .add_node_data(workspace_node) + .add_node_data(trash_node) + .build(); - let operation = NodeOperation::Insert { - path: folder_path(), - nodes: vec![folder_node], - }; - tree.write().apply_op(operation).unwrap(); - let node_id = tree.read().node_id_at_path(folder_path()).unwrap(); - workspaces.node_id = Some(tree.read().node_id_at_path(workspaces_path()).unwrap()); - trash.node_id = Some(tree.read().node_id_at_path(trash_path()).unwrap()); + let operation = NodeOperation::Insert { + path: folder_path(), + nodes: vec![folder_node], + }; + tree.write().apply_op(operation).unwrap(); + let node_id = tree.read().node_id_at_path(folder_path()).unwrap(); + workspaces.node_id = Some(tree.read().node_id_at_path(workspaces_path()).unwrap()); + trash.node_id = Some(tree.read().node_id_at_path(trash_path()).unwrap()); - Self { - tree, - node_id, - workspaces, - trash, - } + Self { + tree, + node_id, + workspaces, + trash, } + } } fn folder_path() -> Path { - vec![0].into() + vec![0].into() } fn workspaces_path() -> Path { - folder_path().clone_with(0) + folder_path().clone_with(0) } fn trash_path() -> Path { - folder_path().clone_with(1) + folder_path().clone_with(1) } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_folder/folder_pad.rs b/frontend/rust-lib/flowy-client-sync/src/client_folder/folder_pad.rs index bd7dd5f2dc..3c4eae04dd 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_folder/folder_pad.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_folder/folder_pad.rs @@ -1,8 +1,8 @@ use crate::errors::internal_sync_error; use crate::util::cal_diff; use crate::{ - client_folder::builder::FolderPadBuilder, - errors::{SyncError, SyncResult}, + client_folder::builder::FolderPadBuilder, + errors::{SyncError, SyncResult}, }; use folder_model::{AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision}; use lib_infra::util::md5; @@ -17,531 +17,595 @@ pub type FolderOperationsBuilder = DeltaOperationBuilder; #[derive(Debug, Clone, Eq, PartialEq)] pub struct FolderPad { - folder_rev: FolderRevision, - operations: FolderOperations, + folder_rev: FolderRevision, + operations: FolderOperations, } impl FolderPad { - pub fn new(workspaces: Vec, trash: Vec) -> SyncResult { - let folder_rev = FolderRevision { - workspaces: workspaces.into_iter().map(Arc::new).collect(), - trash: trash.into_iter().map(Arc::new).collect(), - }; - Self::from_folder_rev(folder_rev) + pub fn new(workspaces: Vec, trash: Vec) -> SyncResult { + let folder_rev = FolderRevision { + workspaces: workspaces.into_iter().map(Arc::new).collect(), + trash: trash.into_iter().map(Arc::new).collect(), + }; + Self::from_folder_rev(folder_rev) + } + + pub fn from_folder_rev(folder_rev: FolderRevision) -> SyncResult { + let json = serde_json::to_string(&folder_rev).map_err(|e| { + SyncError::internal().context(format!("Serialize to folder json str failed: {}", e)) + })?; + let operations = FolderOperationsBuilder::new().insert(&json).build(); + + Ok(Self { + folder_rev, + operations, + }) + } + + pub fn from_revisions(revisions: Vec) -> SyncResult { + FolderPadBuilder::new().build_with_revisions(revisions) + } + + pub fn from_operations(operations: FolderOperations) -> SyncResult { + let content = operations.content()?; + let mut deserializer = serde_json::Deserializer::from_reader(content.as_bytes()); + + let folder_rev = FolderRevision::deserialize(&mut deserializer).map_err(|e| { + tracing::error!("Deserialize folder from {} failed", content); + SyncError::internal().context(format!("Deserialize operations to folder failed: {}", e)) + })?; + + Ok(Self { + folder_rev, + operations, + }) + } + + pub fn get_operations(&self) -> &FolderOperations { + &self.operations + } + + pub fn reset_folder(&mut self, operations: FolderOperations) -> SyncResult { + let folder = FolderPad::from_operations(operations)?; + self.folder_rev = folder.folder_rev; + self.operations = folder.operations; + + Ok(self.folder_md5()) + } + + pub fn compose_remote_operations(&mut self, operations: FolderOperations) -> SyncResult { + let composed_operations = self.operations.compose(&operations)?; + self.reset_folder(composed_operations) + } + + pub fn is_empty(&self) -> bool { + self.folder_rev.workspaces.is_empty() && self.folder_rev.trash.is_empty() + } + + #[tracing::instrument(level = "trace", skip(self, workspace_rev), fields(workspace_name=%workspace_rev.name), err)] + pub fn create_workspace( + &mut self, + workspace_rev: WorkspaceRevision, + ) -> SyncResult> { + let workspace = Arc::new(workspace_rev); + if self.folder_rev.workspaces.contains(&workspace) { + tracing::warn!("[RootFolder]: Duplicate workspace"); + return Ok(None); } - pub fn from_folder_rev(folder_rev: FolderRevision) -> SyncResult { - let json = serde_json::to_string(&folder_rev) - .map_err(|e| SyncError::internal().context(format!("Serialize to folder json str failed: {}", e)))?; - let operations = FolderOperationsBuilder::new().insert(&json).build(); + self.modify_workspaces(move |workspaces| { + workspaces.push(workspace); + Ok(Some(())) + }) + } - Ok(Self { folder_rev, operations }) - } + pub fn update_workspace( + &mut self, + workspace_id: &str, + name: Option, + desc: Option, + ) -> SyncResult> { + self.with_workspace(workspace_id, |workspace| { + if let Some(name) = name { + workspace.name = name; + } - pub fn from_revisions(revisions: Vec) -> SyncResult { - FolderPadBuilder::new().build_with_revisions(revisions) - } + if let Some(desc) = desc { + workspace.desc = desc; + } + Ok(Some(())) + }) + } - pub fn from_operations(operations: FolderOperations) -> SyncResult { - let content = operations.content()?; - let mut deserializer = serde_json::Deserializer::from_reader(content.as_bytes()); - - let folder_rev = FolderRevision::deserialize(&mut deserializer).map_err(|e| { - tracing::error!("Deserialize folder from {} failed", content); - SyncError::internal().context(format!("Deserialize operations to folder failed: {}", e)) - })?; - - Ok(Self { folder_rev, operations }) - } - - pub fn get_operations(&self) -> &FolderOperations { - &self.operations - } - - pub fn reset_folder(&mut self, operations: FolderOperations) -> SyncResult { - let folder = FolderPad::from_operations(operations)?; - self.folder_rev = folder.folder_rev; - self.operations = folder.operations; - - Ok(self.folder_md5()) - } - - pub fn compose_remote_operations(&mut self, operations: FolderOperations) -> SyncResult { - let composed_operations = self.operations.compose(&operations)?; - self.reset_folder(composed_operations) - } - - pub fn is_empty(&self) -> bool { - self.folder_rev.workspaces.is_empty() && self.folder_rev.trash.is_empty() - } - - #[tracing::instrument(level = "trace", skip(self, workspace_rev), fields(workspace_name=%workspace_rev.name), err)] - pub fn create_workspace(&mut self, workspace_rev: WorkspaceRevision) -> SyncResult> { - let workspace = Arc::new(workspace_rev); - if self.folder_rev.workspaces.contains(&workspace) { - tracing::warn!("[RootFolder]: Duplicate workspace"); - return Ok(None); + pub fn read_workspaces( + &self, + workspace_id: Option, + ) -> SyncResult> { + match workspace_id { + None => { + let workspaces = self + .folder_rev + .workspaces + .iter() + .map(|workspace| workspace.as_ref().clone()) + .collect::>(); + Ok(workspaces) + }, + Some(workspace_id) => { + if let Some(workspace) = self + .folder_rev + .workspaces + .iter() + .find(|workspace| workspace.id == workspace_id) + { + Ok(vec![workspace.as_ref().clone()]) + } else { + Err( + SyncError::record_not_found() + .context(format!("Can't find workspace with id {}", workspace_id)), + ) } - - self.modify_workspaces(move |workspaces| { - workspaces.push(workspace); - Ok(Some(())) - }) + }, } + } - pub fn update_workspace( - &mut self, - workspace_id: &str, - name: Option, - desc: Option, - ) -> SyncResult> { - self.with_workspace(workspace_id, |workspace| { - if let Some(name) = name { - workspace.name = name; - } + #[tracing::instrument(level = "trace", skip(self), err)] + pub fn delete_workspace(&mut self, workspace_id: &str) -> SyncResult> { + self.modify_workspaces(|workspaces| { + workspaces.retain(|w| w.id != workspace_id); + Ok(Some(())) + }) + } - if let Some(desc) = desc { - workspace.desc = desc; - } - Ok(Some(())) - }) + #[tracing::instrument(level = "trace", skip(self), fields(app_name=%app_rev.name), err)] + pub fn create_app(&mut self, app_rev: AppRevision) -> SyncResult> { + let workspace_id = app_rev.workspace_id.clone(); + self.with_workspace(&workspace_id, move |workspace| { + if workspace.apps.contains(&app_rev) { + tracing::warn!("[RootFolder]: Duplicate app"); + return Ok(None); + } + workspace.apps.push(app_rev); + Ok(Some(())) + }) + } + + pub fn read_app(&self, app_id: &str) -> SyncResult { + for workspace in &self.folder_rev.workspaces { + if let Some(app) = workspace.apps.iter().find(|app| app.id == app_id) { + return Ok(app.clone()); + } } + Err(SyncError::record_not_found().context(format!("Can't find app with id {}", app_id))) + } - pub fn read_workspaces(&self, workspace_id: Option) -> SyncResult> { - match workspace_id { - None => { - let workspaces = self - .folder_rev - .workspaces - .iter() - .map(|workspace| workspace.as_ref().clone()) - .collect::>(); - Ok(workspaces) - } - Some(workspace_id) => { - if let Some(workspace) = self - .folder_rev - .workspaces - .iter() - .find(|workspace| workspace.id == workspace_id) - { - Ok(vec![workspace.as_ref().clone()]) - } else { - Err(SyncError::record_not_found().context(format!("Can't find workspace with id {}", workspace_id))) - } - } + pub fn update_app( + &mut self, + app_id: &str, + name: Option, + desc: Option, + ) -> SyncResult> { + self.with_app(app_id, move |app| { + if let Some(name) = name { + app.name = name; + } + + if let Some(desc) = desc { + app.desc = desc; + } + Ok(Some(())) + }) + } + + #[tracing::instrument(level = "trace", skip(self), err)] + pub fn delete_app(&mut self, app_id: &str) -> SyncResult> { + let app = self.read_app(app_id)?; + self.with_workspace(&app.workspace_id, |workspace| { + workspace.apps.retain(|app| app.id != app_id); + Ok(Some(())) + }) + } + + #[tracing::instrument(level = "trace", skip(self), err)] + pub fn move_app( + &mut self, + app_id: &str, + from: usize, + to: usize, + ) -> SyncResult> { + let app = self.read_app(app_id)?; + self.with_workspace(&app.workspace_id, |workspace| { + match move_vec_element(&mut workspace.apps, |app| app.id == app_id, from, to) + .map_err(internal_sync_error)? + { + true => Ok(Some(())), + false => Ok(None), + } + }) + } + + #[tracing::instrument(level = "trace", skip(self), fields(view_name=%view_rev.name), err)] + pub fn create_view(&mut self, view_rev: ViewRevision) -> SyncResult> { + let app_id = view_rev.app_id.clone(); + self.with_app(&app_id, move |app| { + if app.belongings.contains(&view_rev) { + tracing::warn!("[RootFolder]: Duplicate view"); + return Ok(None); + } + app.belongings.push(view_rev); + Ok(Some(())) + }) + } + + pub fn read_view(&self, view_id: &str) -> SyncResult { + for workspace in &self.folder_rev.workspaces { + for app in &(*workspace.apps) { + if let Some(view) = app.belongings.iter().find(|b| b.id == view_id) { + return Ok(view.clone()); } + } } + Err(SyncError::record_not_found().context(format!("Can't find view with id {}", view_id))) + } - #[tracing::instrument(level = "trace", skip(self), err)] - pub fn delete_workspace(&mut self, workspace_id: &str) -> SyncResult> { - self.modify_workspaces(|workspaces| { - workspaces.retain(|w| w.id != workspace_id); - Ok(Some(())) - }) - } - - #[tracing::instrument(level = "trace", skip(self), fields(app_name=%app_rev.name), err)] - pub fn create_app(&mut self, app_rev: AppRevision) -> SyncResult> { - let workspace_id = app_rev.workspace_id.clone(); - self.with_workspace(&workspace_id, move |workspace| { - if workspace.apps.contains(&app_rev) { - tracing::warn!("[RootFolder]: Duplicate app"); - return Ok(None); - } - workspace.apps.push(app_rev); - Ok(Some(())) - }) - } - - pub fn read_app(&self, app_id: &str) -> SyncResult { - for workspace in &self.folder_rev.workspaces { - if let Some(app) = workspace.apps.iter().find(|app| app.id == app_id) { - return Ok(app.clone()); - } + pub fn read_views(&self, belong_to_id: &str) -> SyncResult> { + for workspace in &self.folder_rev.workspaces { + for app in &(*workspace.apps) { + if app.id == belong_to_id { + return Ok(app.belongings.to_vec()); } - Err(SyncError::record_not_found().context(format!("Can't find app with id {}", app_id))) + } } + Ok(vec![]) + } - pub fn update_app( - &mut self, - app_id: &str, - name: Option, - desc: Option, - ) -> SyncResult> { - self.with_app(app_id, move |app| { - if let Some(name) = name { - app.name = name; - } + pub fn update_view( + &mut self, + view_id: &str, + name: Option, + desc: Option, + modified_time: i64, + ) -> SyncResult> { + let view = self.read_view(view_id)?; + self.with_view(&view.app_id, view_id, |view| { + if let Some(name) = name { + view.name = name; + } - if let Some(desc) = desc { - app.desc = desc; - } - Ok(Some(())) + if let Some(desc) = desc { + view.desc = desc; + } + + view.modified_time = modified_time; + Ok(Some(())) + }) + } + + #[tracing::instrument(level = "trace", skip(self), err)] + pub fn delete_view( + &mut self, + app_id: &str, + view_id: &str, + ) -> SyncResult> { + self.with_app(app_id, |app| { + app.belongings.retain(|view| view.id != view_id); + Ok(Some(())) + }) + } + + #[tracing::instrument(level = "trace", skip(self), err)] + pub fn move_view( + &mut self, + view_id: &str, + from: usize, + to: usize, + ) -> SyncResult> { + let view = self.read_view(view_id)?; + self.with_app(&view.app_id, |app| { + match move_vec_element(&mut app.belongings, |view| view.id == view_id, from, to) + .map_err(internal_sync_error)? + { + true => Ok(Some(())), + false => Ok(None), + } + }) + } + + pub fn create_trash(&mut self, trash: Vec) -> SyncResult> { + self.with_trash(|original_trash| { + let mut new_trash = trash + .into_iter() + .flat_map(|new_trash| { + if original_trash + .iter() + .any(|old_trash| old_trash.id == new_trash.id) + { + None + } else { + Some(Arc::new(new_trash)) + } }) - } + .collect::>>(); + if new_trash.is_empty() { + Ok(None) + } else { + original_trash.append(&mut new_trash); + Ok(Some(())) + } + }) + } - #[tracing::instrument(level = "trace", skip(self), err)] - pub fn delete_app(&mut self, app_id: &str) -> SyncResult> { - let app = self.read_app(app_id)?; - self.with_workspace(&app.workspace_id, |workspace| { - workspace.apps.retain(|app| app.id != app_id); - Ok(Some(())) - }) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub fn move_app(&mut self, app_id: &str, from: usize, to: usize) -> SyncResult> { - let app = self.read_app(app_id)?; - self.with_workspace(&app.workspace_id, |workspace| { - match move_vec_element(&mut workspace.apps, |app| app.id == app_id, from, to) - .map_err(internal_sync_error)? - { - true => Ok(Some(())), - false => Ok(None), - } - }) - } - - #[tracing::instrument(level = "trace", skip(self), fields(view_name=%view_rev.name), err)] - pub fn create_view(&mut self, view_rev: ViewRevision) -> SyncResult> { - let app_id = view_rev.app_id.clone(); - self.with_app(&app_id, move |app| { - if app.belongings.contains(&view_rev) { - tracing::warn!("[RootFolder]: Duplicate view"); - return Ok(None); - } - app.belongings.push(view_rev); - Ok(Some(())) - }) - } - - pub fn read_view(&self, view_id: &str) -> SyncResult { - for workspace in &self.folder_rev.workspaces { - for app in &(*workspace.apps) { - if let Some(view) = app.belongings.iter().find(|b| b.id == view_id) { - return Ok(view.clone()); - } - } + pub fn read_trash(&self, trash_id: Option) -> SyncResult> { + match trash_id { + None => { + // Removes the duplicate items if exist + let mut trash_items = Vec::::with_capacity(self.folder_rev.trash.len()); + for trash_item in self.folder_rev.trash.iter() { + if !trash_items.iter().any(|item| item.id == trash_item.id) { + trash_items.push(trash_item.as_ref().clone()); + } } - Err(SyncError::record_not_found().context(format!("Can't find view with id {}", view_id))) + Ok(trash_items) + }, + Some(trash_id) => match self.folder_rev.trash.iter().find(|t| t.id == trash_id) { + Some(trash) => Ok(vec![trash.as_ref().clone()]), + None => Ok(vec![]), + }, } + } - pub fn read_views(&self, belong_to_id: &str) -> SyncResult> { - for workspace in &self.folder_rev.workspaces { - for app in &(*workspace.apps) { - if app.id == belong_to_id { - return Ok(app.belongings.to_vec()); - } - } - } - Ok(vec![]) + pub fn delete_trash( + &mut self, + trash_ids: Option>, + ) -> SyncResult> { + match trash_ids { + None => self.with_trash(|trash| { + trash.clear(); + Ok(Some(())) + }), + Some(trash_ids) => self.with_trash(|trash| { + trash.retain(|t| !trash_ids.contains(&t.id)); + Ok(Some(())) + }), } + } - pub fn update_view( - &mut self, - view_id: &str, - name: Option, - desc: Option, - modified_time: i64, - ) -> SyncResult> { - let view = self.read_view(view_id)?; - self.with_view(&view.app_id, view_id, |view| { - if let Some(name) = name { - view.name = name; - } + pub fn folder_md5(&self) -> String { + md5(&self.operations.json_bytes()) + } - if let Some(desc) = desc { - view.desc = desc; - } - - view.modified_time = modified_time; - Ok(Some(())) - }) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub fn delete_view(&mut self, app_id: &str, view_id: &str) -> SyncResult> { - self.with_app(app_id, |app| { - app.belongings.retain(|view| view.id != view_id); - Ok(Some(())) - }) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub fn move_view(&mut self, view_id: &str, from: usize, to: usize) -> SyncResult> { - let view = self.read_view(view_id)?; - self.with_app(&view.app_id, |app| { - match move_vec_element(&mut app.belongings, |view| view.id == view_id, from, to) - .map_err(internal_sync_error)? - { - true => Ok(Some(())), - false => Ok(None), - } - }) - } - - pub fn create_trash(&mut self, trash: Vec) -> SyncResult> { - self.with_trash(|original_trash| { - let mut new_trash = trash - .into_iter() - .flat_map(|new_trash| { - if original_trash.iter().any(|old_trash| old_trash.id == new_trash.id) { - None - } else { - Some(Arc::new(new_trash)) - } - }) - .collect::>>(); - if new_trash.is_empty() { - Ok(None) - } else { - original_trash.append(&mut new_trash); - Ok(Some(())) - } - }) - } - - pub fn read_trash(&self, trash_id: Option) -> SyncResult> { - match trash_id { - None => { - // Removes the duplicate items if exist - let mut trash_items = Vec::::with_capacity(self.folder_rev.trash.len()); - for trash_item in self.folder_rev.trash.iter() { - if !trash_items.iter().any(|item| item.id == trash_item.id) { - trash_items.push(trash_item.as_ref().clone()); - } - } - Ok(trash_items) - } - Some(trash_id) => match self.folder_rev.trash.iter().find(|t| t.id == trash_id) { - Some(trash) => Ok(vec![trash.as_ref().clone()]), - None => Ok(vec![]), - }, - } - } - - pub fn delete_trash(&mut self, trash_ids: Option>) -> SyncResult> { - match trash_ids { - None => self.with_trash(|trash| { - trash.clear(); - Ok(Some(())) - }), - Some(trash_ids) => self.with_trash(|trash| { - trash.retain(|t| !trash_ids.contains(&t.id)); - Ok(Some(())) - }), - } - } - - pub fn folder_md5(&self) -> String { - md5(&self.operations.json_bytes()) - } - - pub fn to_json(&self) -> SyncResult { - make_folder_rev_json_str(&self.folder_rev) - } + pub fn to_json(&self) -> SyncResult { + make_folder_rev_json_str(&self.folder_rev) + } } pub fn make_folder_rev_json_str(folder_rev: &FolderRevision) -> SyncResult { - let json = serde_json::to_string(folder_rev) - .map_err(|err| internal_sync_error(format!("Serialize folder to json str failed. {:?}", err)))?; - Ok(json) + let json = serde_json::to_string(folder_rev).map_err(|err| { + internal_sync_error(format!("Serialize folder to json str failed. {:?}", err)) + })?; + Ok(json) } impl FolderPad { - fn modify_workspaces(&mut self, f: F) -> SyncResult> - where - F: FnOnce(&mut Vec>) -> SyncResult>, - { - let cloned_self = self.clone(); - match f(&mut self.folder_rev.workspaces)? { - None => Ok(None), - Some(_) => { - let old = cloned_self.to_json()?; - let new = self.to_json()?; - match cal_diff::(old, new) { - None => Ok(None), - Some(operations) => { - self.operations = self.operations.compose(&operations)?; - Ok(Some(FolderChangeset { - operations, - md5: self.folder_md5(), - })) - } - } - } + fn modify_workspaces(&mut self, f: F) -> SyncResult> + where + F: FnOnce(&mut Vec>) -> SyncResult>, + { + let cloned_self = self.clone(); + match f(&mut self.folder_rev.workspaces)? { + None => Ok(None), + Some(_) => { + let old = cloned_self.to_json()?; + let new = self.to_json()?; + match cal_diff::(old, new) { + None => Ok(None), + Some(operations) => { + self.operations = self.operations.compose(&operations)?; + Ok(Some(FolderChangeset { + operations, + md5: self.folder_md5(), + })) + }, } + }, } + } - fn with_workspace(&mut self, workspace_id: &str, f: F) -> SyncResult> - where - F: FnOnce(&mut WorkspaceRevision) -> SyncResult>, - { - self.modify_workspaces(|workspaces| { - if let Some(workspace) = workspaces.iter_mut().find(|workspace| workspace_id == workspace.id) { - f(Arc::make_mut(workspace)) - } else { - tracing::warn!("[FolderPad]: Can't find any workspace with id: {}", workspace_id); - Ok(None) - } - }) - } + fn with_workspace(&mut self, workspace_id: &str, f: F) -> SyncResult> + where + F: FnOnce(&mut WorkspaceRevision) -> SyncResult>, + { + self.modify_workspaces(|workspaces| { + if let Some(workspace) = workspaces + .iter_mut() + .find(|workspace| workspace_id == workspace.id) + { + f(Arc::make_mut(workspace)) + } else { + tracing::warn!( + "[FolderPad]: Can't find any workspace with id: {}", + workspace_id + ); + Ok(None) + } + }) + } - fn with_trash(&mut self, f: F) -> SyncResult> - where - F: FnOnce(&mut Vec>) -> SyncResult>, - { - let cloned_self = self.clone(); - match f(&mut self.folder_rev.trash)? { - None => Ok(None), - Some(_) => { - let old = cloned_self.to_json()?; - let new = self.to_json()?; - match cal_diff::(old, new) { - None => Ok(None), - Some(operations) => { - self.operations = self.operations.compose(&operations)?; - Ok(Some(FolderChangeset { - operations, - md5: self.folder_md5(), - })) - } - } - } + fn with_trash(&mut self, f: F) -> SyncResult> + where + F: FnOnce(&mut Vec>) -> SyncResult>, + { + let cloned_self = self.clone(); + match f(&mut self.folder_rev.trash)? { + None => Ok(None), + Some(_) => { + let old = cloned_self.to_json()?; + let new = self.to_json()?; + match cal_diff::(old, new) { + None => Ok(None), + Some(operations) => { + self.operations = self.operations.compose(&operations)?; + Ok(Some(FolderChangeset { + operations, + md5: self.folder_md5(), + })) + }, } + }, } + } - fn with_app(&mut self, app_id: &str, f: F) -> SyncResult> - where - F: FnOnce(&mut AppRevision) -> SyncResult>, + fn with_app(&mut self, app_id: &str, f: F) -> SyncResult> + where + F: FnOnce(&mut AppRevision) -> SyncResult>, + { + let workspace_id = match self + .folder_rev + .workspaces + .iter() + .find(|workspace| workspace.apps.iter().any(|app| app.id == app_id)) { - let workspace_id = match self - .folder_rev - .workspaces - .iter() - .find(|workspace| workspace.apps.iter().any(|app| app.id == app_id)) - { - None => { - tracing::warn!("[FolderPad]: Can't find any app with id: {}", app_id); - return Ok(None); - } - Some(workspace) => workspace.id.clone(), - }; + None => { + tracing::warn!("[FolderPad]: Can't find any app with id: {}", app_id); + return Ok(None); + }, + Some(workspace) => workspace.id.clone(), + }; - self.with_workspace(&workspace_id, |workspace| { - // It's ok to unwrap because we get the workspace from the app_id. - f(workspace.apps.iter_mut().find(|app| app_id == app.id).unwrap()) - }) - } + self.with_workspace(&workspace_id, |workspace| { + // It's ok to unwrap because we get the workspace from the app_id. + f(workspace + .apps + .iter_mut() + .find(|app| app_id == app.id) + .unwrap()) + }) + } - fn with_view(&mut self, belong_to_id: &str, view_id: &str, f: F) -> SyncResult> - where - F: FnOnce(&mut ViewRevision) -> SyncResult>, - { - self.with_app(belong_to_id, |app| { - match app.belongings.iter_mut().find(|view| view_id == view.id) { - None => { - tracing::warn!("[FolderPad]: Can't find any view with id: {}", view_id); - Ok(None) - } - Some(view) => f(view), - } - }) - } + fn with_view( + &mut self, + belong_to_id: &str, + view_id: &str, + f: F, + ) -> SyncResult> + where + F: FnOnce(&mut ViewRevision) -> SyncResult>, + { + self.with_app(belong_to_id, |app| { + match app.belongings.iter_mut().find(|view| view_id == view.id) { + None => { + tracing::warn!("[FolderPad]: Can't find any view with id: {}", view_id); + Ok(None) + }, + Some(view) => f(view), + } + }) + } } pub fn default_folder_operations() -> FolderOperations { - FolderOperationsBuilder::new() - .insert(r#"{"workspaces":[],"trash":[]}"#) - .build() + FolderOperationsBuilder::new() + .insert(r#"{"workspaces":[],"trash":[]}"#) + .build() } pub fn initial_folder_operations(folder_pad: &FolderPad) -> SyncResult { - let json = folder_pad.to_json()?; - let operations = FolderOperationsBuilder::new().insert(&json).build(); - Ok(operations) + let json = folder_pad.to_json()?; + let operations = FolderOperationsBuilder::new().insert(&json).build(); + Ok(operations) } pub struct FolderChangeset { - pub operations: FolderOperations, - /// md5: the md5 of the FolderPad's operations after applying the change. - pub md5: String, + pub operations: FolderOperations, + /// md5: the md5 of the FolderPad's operations after applying the change. + pub md5: String, } #[cfg(test)] mod tests { - #![allow(clippy::all)] - use crate::client_folder::folder_pad::FolderPad; - use crate::client_folder::{FolderOperations, FolderOperationsBuilder}; - use chrono::Utc; - use folder_model::{AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision}; - use lib_ot::core::OperationTransform; - use serde::Deserialize; + #![allow(clippy::all)] + use crate::client_folder::folder_pad::FolderPad; + use crate::client_folder::{FolderOperations, FolderOperationsBuilder}; + use chrono::Utc; + use folder_model::{AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision}; + use lib_ot::core::OperationTransform; + use serde::Deserialize; - #[test] - fn folder_add_workspace() { - let (mut folder, initial_operations, _) = test_folder(); + #[test] + fn folder_add_workspace() { + let (mut folder, initial_operations, _) = test_folder(); - let _time = Utc::now(); - let mut workspace_1 = WorkspaceRevision::default(); - workspace_1.name = "My first workspace".to_owned(); - let operations_1 = folder.create_workspace(workspace_1).unwrap().unwrap().operations; + let _time = Utc::now(); + let mut workspace_1 = WorkspaceRevision::default(); + workspace_1.name = "My first workspace".to_owned(); + let operations_1 = folder + .create_workspace(workspace_1) + .unwrap() + .unwrap() + .operations; - let mut workspace_2 = WorkspaceRevision::default(); - workspace_2.name = "My second workspace".to_owned(); - let operations_2 = folder.create_workspace(workspace_2).unwrap().unwrap().operations; + let mut workspace_2 = WorkspaceRevision::default(); + workspace_2.name = "My second workspace".to_owned(); + let operations_2 = folder + .create_workspace(workspace_2) + .unwrap() + .unwrap() + .operations; - let folder_from_operations = make_folder_from_operations(initial_operations, vec![operations_1, operations_2]); - assert_eq!(folder, folder_from_operations); + let folder_from_operations = + make_folder_from_operations(initial_operations, vec![operations_1, operations_2]); + assert_eq!(folder, folder_from_operations); + } + + #[test] + fn folder_deserialize_invalid_json_test() { + for json in vec![ + // No timestamp + r#"{"workspaces":[{"id":"1","name":"first workspace","desc":"","apps":[]}],"trash":[]}"#, + // Trailing characters + r#"{"workspaces":[{"id":"1","name":"first workspace","desc":"","apps":[]}],"trash":[]}123"#, + ] { + let mut deserializer = serde_json::Deserializer::from_reader(json.as_bytes()); + let folder_rev = FolderRevision::deserialize(&mut deserializer).unwrap(); + assert_eq!( + folder_rev.workspaces.first().as_ref().unwrap().name, + "first workspace" + ); } + } - #[test] - fn folder_deserialize_invalid_json_test() { - for json in vec![ - // No timestamp - r#"{"workspaces":[{"id":"1","name":"first workspace","desc":"","apps":[]}],"trash":[]}"#, - // Trailing characters - r#"{"workspaces":[{"id":"1","name":"first workspace","desc":"","apps":[]}],"trash":[]}123"#, - ] { - let mut deserializer = serde_json::Deserializer::from_reader(json.as_bytes()); - let folder_rev = FolderRevision::deserialize(&mut deserializer).unwrap(); - assert_eq!(folder_rev.workspaces.first().as_ref().unwrap().name, "first workspace"); - } - } + #[test] + fn folder_update_workspace() { + let (mut folder, initial_operation, workspace) = test_folder(); + assert_folder_equal( + &folder, + &make_folder_from_operations(initial_operation.clone(), vec![]), + r#"{"workspaces":[{"id":"1","name":"😁 my first workspace","desc":"","apps":[],"modified_time":0,"create_time":0}],"trash":[]}"#, + ); - #[test] - fn folder_update_workspace() { - let (mut folder, initial_operation, workspace) = test_folder(); - assert_folder_equal( - &folder, - &make_folder_from_operations(initial_operation.clone(), vec![]), - r#"{"workspaces":[{"id":"1","name":"😁 my first workspace","desc":"","apps":[],"modified_time":0,"create_time":0}],"trash":[]}"#, - ); + let operations = folder + .update_workspace(&workspace.id, Some("☺️ rename workspace️".to_string()), None) + .unwrap() + .unwrap() + .operations; - let operations = folder - .update_workspace(&workspace.id, Some("☺️ rename workspace️".to_string()), None) - .unwrap() - .unwrap() - .operations; + let folder_from_operations = make_folder_from_operations(initial_operation, vec![operations]); + assert_folder_equal( + &folder, + &folder_from_operations, + r#"{"workspaces":[{"id":"1","name":"☺️ rename workspace️","desc":"","apps":[],"modified_time":0,"create_time":0}],"trash":[]}"#, + ); + } - let folder_from_operations = make_folder_from_operations(initial_operation, vec![operations]); - assert_folder_equal( - &folder, - &folder_from_operations, - r#"{"workspaces":[{"id":"1","name":"☺️ rename workspace️","desc":"","apps":[],"modified_time":0,"create_time":0}],"trash":[]}"#, - ); - } - - #[test] - fn folder_add_app() { - let (folder, initial_operations, _app) = test_app_folder(); - let folder_from_operations = make_folder_from_operations(initial_operations, vec![]); - assert_eq!(folder, folder_from_operations); - assert_folder_equal( - &folder, - &folder_from_operations, - r#"{ + #[test] + fn folder_add_app() { + let (folder, initial_operations, _app) = test_app_folder(); + let folder_from_operations = make_folder_from_operations(initial_operations, vec![]); + assert_eq!(folder, folder_from_operations); + assert_folder_equal( + &folder, + &folder_from_operations, + r#"{ "workspaces": [ { "id": "1", @@ -565,23 +629,23 @@ mod tests { ], "trash": [] }"#, - ); - } + ); + } - #[test] - fn folder_update_app() { - let (mut folder, initial_operations, app) = test_app_folder(); - let operations = folder - .update_app(&app.id, Some("🤪 rename app".to_owned()), None) - .unwrap() - .unwrap() - .operations; + #[test] + fn folder_update_app() { + let (mut folder, initial_operations, app) = test_app_folder(); + let operations = folder + .update_app(&app.id, Some("🤪 rename app".to_owned()), None) + .unwrap() + .unwrap() + .operations; - let new_folder = make_folder_from_operations(initial_operations, vec![operations]); - assert_folder_equal( - &folder, - &new_folder, - r#"{ + let new_folder = make_folder_from_operations(initial_operations, vec![operations]); + assert_folder_equal( + &folder, + &new_folder, + r#"{ "workspaces": [ { "id": "1", @@ -605,18 +669,18 @@ mod tests { ], "trash": [] }"#, - ); - } + ); + } - #[test] - fn folder_delete_app() { - let (mut folder, initial_operations, app) = test_app_folder(); - let operations = folder.delete_app(&app.id).unwrap().unwrap().operations; - let new_folder = make_folder_from_operations(initial_operations, vec![operations]); - assert_folder_equal( - &folder, - &new_folder, - r#"{ + #[test] + fn folder_delete_app() { + let (mut folder, initial_operations, app) = test_app_folder(); + let operations = folder.delete_app(&app.id).unwrap().unwrap().operations; + let new_folder = make_folder_from_operations(initial_operations, vec![operations]); + assert_folder_equal( + &folder, + &new_folder, + r#"{ "workspaces": [ { "id": "1", @@ -629,16 +693,16 @@ mod tests { ], "trash": [] }"#, - ); - } + ); + } - #[test] - fn folder_add_view() { - let (folder, initial_operations, _view) = test_view_folder(); - assert_folder_equal( - &folder, - &make_folder_from_operations(initial_operations, vec![]), - r#" + #[test] + fn folder_add_view() { + let (folder, initial_operations, _view) = test_view_folder(); + assert_folder_equal( + &folder, + &make_folder_from_operations(initial_operations, vec![]), + r#" { "workspaces": [ { @@ -675,23 +739,23 @@ mod tests { ], "trash": [] }"#, - ); - } + ); + } - #[test] - fn folder_update_view() { - let (mut folder, initial_operations, view) = test_view_folder(); - let operations = folder - .update_view(&view.id, Some("😦 rename view".to_owned()), None, 123) - .unwrap() - .unwrap() - .operations; + #[test] + fn folder_update_view() { + let (mut folder, initial_operations, view) = test_view_folder(); + let operations = folder + .update_view(&view.id, Some("😦 rename view".to_owned()), None, 123) + .unwrap() + .unwrap() + .operations; - let new_folder = make_folder_from_operations(initial_operations, vec![operations]); - assert_folder_equal( - &folder, - &new_folder, - r#"{ + let new_folder = make_folder_from_operations(initial_operations, vec![operations]); + assert_folder_equal( + &folder, + &new_folder, + r#"{ "workspaces": [ { "id": "1", @@ -727,19 +791,23 @@ mod tests { ], "trash": [] }"#, - ); - } + ); + } - #[test] - fn folder_delete_view() { - let (mut folder, initial_operations, view) = test_view_folder(); - let operations = folder.delete_view(&view.app_id, &view.id).unwrap().unwrap().operations; + #[test] + fn folder_delete_view() { + let (mut folder, initial_operations, view) = test_view_folder(); + let operations = folder + .delete_view(&view.app_id, &view.id) + .unwrap() + .unwrap() + .operations; - let new_folder = make_folder_from_operations(initial_operations, vec![operations]); - assert_folder_equal( - &folder, - &new_folder, - r#"{ + let new_folder = make_folder_from_operations(initial_operations, vec![operations]); + assert_folder_equal( + &folder, + &new_folder, + r#"{ "workspaces": [ { "id": "1", @@ -763,16 +831,16 @@ mod tests { ], "trash": [] }"#, - ); - } + ); + } - #[test] - fn folder_add_trash() { - let (folder, initial_operations, _trash) = test_trash(); - assert_folder_equal( - &folder, - &make_folder_from_operations(initial_operations, vec![]), - r#"{ + #[test] + fn folder_add_trash() { + let (folder, initial_operations, _trash) = test_trash(); + assert_folder_equal( + &folder, + &make_folder_from_operations(initial_operations, vec![]), + r#"{ "workspaces": [], "trash": [ { @@ -785,117 +853,133 @@ mod tests { ] } "#, - ); - } + ); + } - #[test] - fn folder_delete_trash() { - let (mut folder, initial_operations, trash) = test_trash(); - let operations = folder.delete_trash(Some(vec![trash.id])).unwrap().unwrap().operations; - assert_folder_equal( - &folder, - &make_folder_from_operations(initial_operations, vec![operations]), - r#"{ + #[test] + fn folder_delete_trash() { + let (mut folder, initial_operations, trash) = test_trash(); + let operations = folder + .delete_trash(Some(vec![trash.id])) + .unwrap() + .unwrap() + .operations; + assert_folder_equal( + &folder, + &make_folder_from_operations(initial_operations, vec![operations]), + r#"{ "workspaces": [], "trash": [] } "#, - ); + ); + } + + fn test_folder() -> (FolderPad, FolderOperations, WorkspaceRevision) { + let folder_rev = FolderRevision::default(); + let folder_json = serde_json::to_string(&folder_rev).unwrap(); + let mut operations = FolderOperationsBuilder::new().insert(&folder_json).build(); + + let mut workspace_rev = WorkspaceRevision::default(); + workspace_rev.name = "😁 my first workspace".to_owned(); + workspace_rev.id = "1".to_owned(); + + let mut folder = FolderPad::from_folder_rev(folder_rev).unwrap(); + + operations = operations + .compose( + &folder + .create_workspace(workspace_rev.clone()) + .unwrap() + .unwrap() + .operations, + ) + .unwrap(); + + (folder, operations, workspace_rev) + } + + fn test_app_folder() -> (FolderPad, FolderOperations, AppRevision) { + let (mut folder_rev, mut initial_operations, workspace) = test_folder(); + let mut app_rev = AppRevision::default(); + app_rev.workspace_id = workspace.id; + app_rev.name = "😁 my first app".to_owned(); + + initial_operations = initial_operations + .compose( + &folder_rev + .create_app(app_rev.clone()) + .unwrap() + .unwrap() + .operations, + ) + .unwrap(); + + (folder_rev, initial_operations, app_rev) + } + + fn test_view_folder() -> (FolderPad, FolderOperations, ViewRevision) { + let (mut folder, mut initial_operations, app) = test_app_folder(); + let mut view_rev = ViewRevision::default(); + view_rev.app_id = app.id.clone(); + view_rev.name = "🎃 my first view".to_owned(); + + initial_operations = initial_operations + .compose( + &folder + .create_view(view_rev.clone()) + .unwrap() + .unwrap() + .operations, + ) + .unwrap(); + + (folder, initial_operations, view_rev) + } + + fn test_trash() -> (FolderPad, FolderOperations, TrashRevision) { + let folder_rev = FolderRevision::default(); + let folder_json = serde_json::to_string(&folder_rev).unwrap(); + let mut operations = FolderOperationsBuilder::new().insert(&folder_json).build(); + + let mut trash_rev = TrashRevision::default(); + trash_rev.name = "🚽 my first trash".to_owned(); + trash_rev.id = "1".to_owned(); + let mut folder = FolderPad::from_folder_rev(folder_rev).unwrap(); + operations = operations + .compose( + &folder + .create_trash(vec![trash_rev.clone().into()]) + .unwrap() + .unwrap() + .operations, + ) + .unwrap(); + + (folder, operations, trash_rev) + } + + fn make_folder_from_operations( + mut initial_operation: FolderOperations, + operations: Vec, + ) -> FolderPad { + for operation in operations { + initial_operation = initial_operation.compose(&operation).unwrap(); } + FolderPad::from_operations(initial_operation).unwrap() + } - fn test_folder() -> (FolderPad, FolderOperations, WorkspaceRevision) { - let folder_rev = FolderRevision::default(); - let folder_json = serde_json::to_string(&folder_rev).unwrap(); - let mut operations = FolderOperationsBuilder::new().insert(&folder_json).build(); + fn assert_folder_equal(old: &FolderPad, new: &FolderPad, expected: &str) { + assert_eq!(old, new); - let mut workspace_rev = WorkspaceRevision::default(); - workspace_rev.name = "😁 my first workspace".to_owned(); - workspace_rev.id = "1".to_owned(); + let json1 = old.to_json().unwrap(); + let json2 = new.to_json().unwrap(); - let mut folder = FolderPad::from_folder_rev(folder_rev).unwrap(); + // format the json str + let folder_rev: FolderRevision = serde_json::from_str(expected).unwrap(); + let expected = serde_json::to_string(&folder_rev).unwrap(); - operations = operations - .compose( - &folder - .create_workspace(workspace_rev.clone()) - .unwrap() - .unwrap() - .operations, - ) - .unwrap(); - - (folder, operations, workspace_rev) - } - - fn test_app_folder() -> (FolderPad, FolderOperations, AppRevision) { - let (mut folder_rev, mut initial_operations, workspace) = test_folder(); - let mut app_rev = AppRevision::default(); - app_rev.workspace_id = workspace.id; - app_rev.name = "😁 my first app".to_owned(); - - initial_operations = initial_operations - .compose(&folder_rev.create_app(app_rev.clone()).unwrap().unwrap().operations) - .unwrap(); - - (folder_rev, initial_operations, app_rev) - } - - fn test_view_folder() -> (FolderPad, FolderOperations, ViewRevision) { - let (mut folder, mut initial_operations, app) = test_app_folder(); - let mut view_rev = ViewRevision::default(); - view_rev.app_id = app.id.clone(); - view_rev.name = "🎃 my first view".to_owned(); - - initial_operations = initial_operations - .compose(&folder.create_view(view_rev.clone()).unwrap().unwrap().operations) - .unwrap(); - - (folder, initial_operations, view_rev) - } - - fn test_trash() -> (FolderPad, FolderOperations, TrashRevision) { - let folder_rev = FolderRevision::default(); - let folder_json = serde_json::to_string(&folder_rev).unwrap(); - let mut operations = FolderOperationsBuilder::new().insert(&folder_json).build(); - - let mut trash_rev = TrashRevision::default(); - trash_rev.name = "🚽 my first trash".to_owned(); - trash_rev.id = "1".to_owned(); - let mut folder = FolderPad::from_folder_rev(folder_rev).unwrap(); - operations = operations - .compose( - &folder - .create_trash(vec![trash_rev.clone().into()]) - .unwrap() - .unwrap() - .operations, - ) - .unwrap(); - - (folder, operations, trash_rev) - } - - fn make_folder_from_operations( - mut initial_operation: FolderOperations, - operations: Vec, - ) -> FolderPad { - for operation in operations { - initial_operation = initial_operation.compose(&operation).unwrap(); - } - FolderPad::from_operations(initial_operation).unwrap() - } - - fn assert_folder_equal(old: &FolderPad, new: &FolderPad, expected: &str) { - assert_eq!(old, new); - - let json1 = old.to_json().unwrap(); - let json2 = new.to_json().unwrap(); - - // format the json str - let folder_rev: FolderRevision = serde_json::from_str(expected).unwrap(); - let expected = serde_json::to_string(&folder_rev).unwrap(); - - assert_eq!(json1, expected); - assert_eq!(json1, json2); - } + assert_eq!(json1, expected); + assert_eq!(json1, json2); + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_folder/trash_node.rs b/frontend/rust-lib/flowy-client-sync/src/client_folder/trash_node.rs index 0edda5cc1a..da6caa118d 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_folder/trash_node.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_folder/trash_node.rs @@ -7,14 +7,14 @@ use std::sync::Arc; #[derive(Clone, Node)] #[node_type = "trash"] pub struct TrashNode { - pub tree: Arc, - pub node_id: Option, + pub tree: Arc, + pub node_id: Option, - #[node(get_value_with = "get_attributes_str_value")] - #[node(set_value_with = "set_attributes_str_value")] - pub id: String, + #[node(get_value_with = "get_attributes_str_value")] + #[node(set_value_with = "set_attributes_str_value")] + pub id: String, - #[node(get_value_with = "get_attributes_str_value")] - #[node(set_value_with = "set_attributes_str_value")] - pub name: String, + #[node(get_value_with = "get_attributes_str_value")] + #[node(set_value_with = "set_attributes_str_value")] + pub name: String, } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_folder/util.rs b/frontend/rust-lib/flowy-client-sync/src/client_folder/util.rs index bb0e136b1c..d45eab2a16 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_folder/util.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_folder/util.rs @@ -3,52 +3,70 @@ use crate::errors::SyncResult; use lib_ot::core::{AttributeHashMap, AttributeValue, Changeset, NodeId, NodeOperation}; use std::sync::Arc; -pub fn get_attributes_str_value(tree: Arc, node_id: &NodeId, key: &str) -> Option { - tree.read() - .get_node(*node_id) - .and_then(|node| node.attributes.get(key).cloned()) - .and_then(|value| value.str_value()) +pub fn get_attributes_str_value( + tree: Arc, + node_id: &NodeId, + key: &str, +) -> Option { + tree + .read() + .get_node(*node_id) + .and_then(|node| node.attributes.get(key).cloned()) + .and_then(|value| value.str_value()) } pub fn set_attributes_str_value( - tree: Arc, - node_id: &NodeId, - key: &str, - value: String, + tree: Arc, + node_id: &NodeId, + key: &str, + value: String, ) -> SyncResult<()> { - let old_attributes = match get_attributes(tree.clone(), node_id) { - None => AttributeHashMap::new(), - Some(attributes) => attributes, - }; - let mut new_attributes = old_attributes.clone(); - new_attributes.insert(key, value); - let path = tree.read().path_from_node_id(*node_id); - let update_operation = NodeOperation::Update { - path, - changeset: Changeset::Attributes { - new: new_attributes, - old: old_attributes, - }, - }; - tree.write().apply_op(update_operation)?; - Ok(()) + let old_attributes = match get_attributes(tree.clone(), node_id) { + None => AttributeHashMap::new(), + Some(attributes) => attributes, + }; + let mut new_attributes = old_attributes.clone(); + new_attributes.insert(key, value); + let path = tree.read().path_from_node_id(*node_id); + let update_operation = NodeOperation::Update { + path, + changeset: Changeset::Attributes { + new: new_attributes, + old: old_attributes, + }, + }; + tree.write().apply_op(update_operation)?; + Ok(()) } #[allow(dead_code)] -pub fn get_attributes_int_value(tree: Arc, node_id: &NodeId, key: &str) -> Option { - tree.read() - .get_node(*node_id) - .and_then(|node| node.attributes.get(key).cloned()) - .and_then(|value| value.int_value()) +pub fn get_attributes_int_value( + tree: Arc, + node_id: &NodeId, + key: &str, +) -> Option { + tree + .read() + .get_node(*node_id) + .and_then(|node| node.attributes.get(key).cloned()) + .and_then(|value| value.int_value()) } pub fn get_attributes(tree: Arc, node_id: &NodeId) -> Option { - tree.read().get_node(*node_id).map(|node| node.attributes.clone()) + tree + .read() + .get_node(*node_id) + .map(|node| node.attributes.clone()) } #[allow(dead_code)] -pub fn get_attributes_value(tree: Arc, node_id: &NodeId, key: &str) -> Option { - tree.read() - .get_node(*node_id) - .and_then(|node| node.attributes.get(key).cloned()) +pub fn get_attributes_value( + tree: Arc, + node_id: &NodeId, + key: &str, +) -> Option { + tree + .read() + .get_node(*node_id) + .and_then(|node| node.attributes.get(key).cloned()) } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_folder/workspace_node.rs b/frontend/rust-lib/flowy-client-sync/src/client_folder/workspace_node.rs index a74c9bf4ed..ff07b5f8e2 100644 --- a/frontend/rust-lib/flowy-client-sync/src/client_folder/workspace_node.rs +++ b/frontend/rust-lib/flowy-client-sync/src/client_folder/workspace_node.rs @@ -7,55 +7,55 @@ use std::sync::Arc; #[derive(Clone, Node)] #[node_type = "workspace"] pub struct WorkspaceNode { - pub tree: Arc, - pub node_id: Option, + pub tree: Arc, + pub node_id: Option, - #[node(get_value_with = "get_attributes_str_value")] - #[node(set_value_with = "set_attributes_str_value")] - pub id: String, + #[node(get_value_with = "get_attributes_str_value")] + #[node(set_value_with = "set_attributes_str_value")] + pub id: String, - #[node(get_value_with = "get_attributes_str_value")] - #[node(set_value_with = "set_attributes_str_value")] - pub name: String, + #[node(get_value_with = "get_attributes_str_value")] + #[node(set_value_with = "set_attributes_str_value")] + pub name: String, - #[node(child_name = "app")] - pub apps: Vec, + #[node(child_name = "app")] + pub apps: Vec, } impl WorkspaceNode { - pub fn new(tree: Arc, id: String, name: String) -> Self { - Self { - tree, - node_id: None, - id, - name, - apps: vec![], - } + pub fn new(tree: Arc, id: String, name: String) -> Self { + Self { + tree, + node_id: None, + id, + name, + apps: vec![], } + } } #[derive(Clone, Node)] #[node_type = "app"] pub struct AppNode { - pub tree: Arc, - pub node_id: Option, + pub tree: Arc, + pub node_id: Option, - #[node(get_value_with = "get_attributes_str_value")] - #[node(set_value_with = "set_attributes_str_value")] - pub id: String, + #[node(get_value_with = "get_attributes_str_value")] + #[node(set_value_with = "set_attributes_str_value")] + pub id: String, - #[node(get_value_with = "get_attributes_str_value")] - #[node(set_value_with = "set_attributes_str_value")] - pub name: String, + #[node(get_value_with = "get_attributes_str_value")] + #[node(set_value_with = "set_attributes_str_value")] + pub name: String, } impl AppNode { - pub fn new(tree: Arc, id: String, name: String) -> Self { - Self { - tree, - node_id: None, - id, - name, - } + pub fn new(tree: Arc, id: String, name: String) -> Self { + Self { + tree, + node_id: None, + id, + name, } + } } diff --git a/frontend/rust-lib/flowy-client-sync/src/lib.rs b/frontend/rust-lib/flowy-client-sync/src/lib.rs index d2a5a6adf9..7fe27011bc 100644 --- a/frontend/rust-lib/flowy-client-sync/src/lib.rs +++ b/frontend/rust-lib/flowy-client-sync/src/lib.rs @@ -2,7 +2,7 @@ pub mod client_database; pub mod client_document; pub mod client_folder; pub mod errors { - pub use flowy_sync::errors::*; + pub use flowy_sync::errors::*; } pub mod util; diff --git a/frontend/rust-lib/flowy-client-sync/src/util.rs b/frontend/rust-lib/flowy-client-sync/src/util.rs index 8b5af5bcf9..7195e44888 100644 --- a/frontend/rust-lib/flowy-client-sync/src/util.rs +++ b/frontend/rust-lib/flowy-client-sync/src/util.rs @@ -3,127 +3,127 @@ use dissimilar::Chunk; use document_model::document::DocumentInfo; use lib_ot::core::{DeltaOperationBuilder, OTString, OperationAttributes}; use lib_ot::{ - core::{DeltaOperations, OperationTransform, NEW_LINE, WHITESPACE}, - text_delta::DeltaTextOperations, + core::{DeltaOperations, OperationTransform, NEW_LINE, WHITESPACE}, + text_delta::DeltaTextOperations, }; use revision_model::Revision; use serde::de::DeserializeOwned; #[inline] pub fn find_newline(s: &str) -> Option { - s.find(NEW_LINE) + s.find(NEW_LINE) } #[inline] pub fn is_newline(s: &str) -> bool { - s == NEW_LINE + s == NEW_LINE } #[inline] pub fn is_whitespace(s: &str) -> bool { - s == WHITESPACE + s == WHITESPACE } #[inline] pub fn contain_newline(s: &str) -> bool { - s.contains(NEW_LINE) + s.contains(NEW_LINE) } pub fn recover_operation_from_revisions( - revisions: Vec, - validator: impl Fn(&DeltaOperations) -> bool, + revisions: Vec, + validator: impl Fn(&DeltaOperations) -> bool, ) -> Option<(DeltaOperations, i64)> where - T: OperationAttributes + DeserializeOwned + OperationAttributes, + T: OperationAttributes + DeserializeOwned + OperationAttributes, { - let mut new_operations = DeltaOperations::::new(); - let mut rev_id = 0; - for revision in revisions { - if let Ok(operations) = DeltaOperations::::from_bytes(revision.bytes) { - match new_operations.compose(&operations) { - Ok(composed_operations) => { - if validator(&composed_operations) { - rev_id = revision.rev_id; - new_operations = composed_operations; - } else { - break; - } - } - Err(_) => break, - } - } else { + let mut new_operations = DeltaOperations::::new(); + let mut rev_id = 0; + for revision in revisions { + if let Ok(operations) = DeltaOperations::::from_bytes(revision.bytes) { + match new_operations.compose(&operations) { + Ok(composed_operations) => { + if validator(&composed_operations) { + rev_id = revision.rev_id; + new_operations = composed_operations; + } else { break; - } - } - if new_operations.is_empty() { - None + } + }, + Err(_) => break, + } } else { - Some((new_operations, rev_id)) + break; } + } + if new_operations.is_empty() { + None + } else { + Some((new_operations, rev_id)) + } } #[inline] pub fn make_document_info_from_revisions( - doc_id: &str, - revisions: Vec, + doc_id: &str, + revisions: Vec, ) -> Result, SyncError> { - if revisions.is_empty() { - return Ok(None); + if revisions.is_empty() { + return Ok(None); + } + + let mut delta = DeltaTextOperations::new(); + let mut base_rev_id = 0; + let mut rev_id = 0; + for revision in revisions { + base_rev_id = revision.base_rev_id; + rev_id = revision.rev_id; + + if revision.bytes.is_empty() { + tracing::warn!("revision delta_data is empty"); } - let mut delta = DeltaTextOperations::new(); - let mut base_rev_id = 0; - let mut rev_id = 0; - for revision in revisions { - base_rev_id = revision.base_rev_id; - rev_id = revision.rev_id; + let new_delta = DeltaTextOperations::from_bytes(revision.bytes)?; + delta = delta.compose(&new_delta)?; + } - if revision.bytes.is_empty() { - tracing::warn!("revision delta_data is empty"); - } - - let new_delta = DeltaTextOperations::from_bytes(revision.bytes)?; - delta = delta.compose(&new_delta)?; - } - - Ok(Some(DocumentInfo { - doc_id: doc_id.to_owned(), - data: delta.json_bytes().to_vec(), - rev_id, - base_rev_id, - })) + Ok(Some(DocumentInfo { + doc_id: doc_id.to_owned(), + data: delta.json_bytes().to_vec(), + rev_id, + base_rev_id, + })) } #[inline] pub fn rev_id_from_str(s: &str) -> Result { - let rev_id = s - .to_owned() - .parse::() - .map_err(|e| SyncError::internal().context(format!("Parse rev_id from {} failed. {}", s, e)))?; - Ok(rev_id) + let rev_id = s + .to_owned() + .parse::() + .map_err(|e| SyncError::internal().context(format!("Parse rev_id from {} failed. {}", s, e)))?; + Ok(rev_id) } pub fn cal_diff(old: String, new: String) -> Option> { - let chunks = dissimilar::diff(&old, &new); - let mut delta_builder = DeltaOperationBuilder::::new(); - for chunk in &chunks { - match chunk { - Chunk::Equal(s) => { - delta_builder = delta_builder.retain(OTString::from(*s).utf16_len()); - } - Chunk::Delete(s) => { - delta_builder = delta_builder.delete(OTString::from(*s).utf16_len()); - } - Chunk::Insert(s) => { - delta_builder = delta_builder.insert(s); - } - } + let chunks = dissimilar::diff(&old, &new); + let mut delta_builder = DeltaOperationBuilder::::new(); + for chunk in &chunks { + match chunk { + Chunk::Equal(s) => { + delta_builder = delta_builder.retain(OTString::from(*s).utf16_len()); + }, + Chunk::Delete(s) => { + delta_builder = delta_builder.delete(OTString::from(*s).utf16_len()); + }, + Chunk::Insert(s) => { + delta_builder = delta_builder.insert(s); + }, } + } - let delta = delta_builder.build(); - if delta.is_empty() { - None - } else { - Some(delta) - } + let delta = delta_builder.build(); + if delta.is_empty() { + None + } else { + Some(delta) + } } diff --git a/frontend/rust-lib/flowy-client-sync/tests/client_folder/folder_test.rs b/frontend/rust-lib/flowy-client-sync/tests/client_folder/folder_test.rs index a386922319..3ac50a25c3 100644 --- a/frontend/rust-lib/flowy-client-sync/tests/client_folder/folder_test.rs +++ b/frontend/rust-lib/flowy-client-sync/tests/client_folder/folder_test.rs @@ -2,57 +2,74 @@ use flowy_client_sync::client_folder::{FolderNodePad, WorkspaceNode}; #[test] fn client_folder_create_default_folder_test() { - let folder_pad = FolderNodePad::new(); - let json = folder_pad.to_json(false).unwrap(); - assert_eq!( - json, - r#"{"type":"folder","children":[{"type":"workspaces"},{"type":"trash"}]}"# - ); + let folder_pad = FolderNodePad::new(); + let json = folder_pad.to_json(false).unwrap(); + assert_eq!( + json, + r#"{"type":"folder","children":[{"type":"workspaces"},{"type":"trash"}]}"# + ); } #[test] fn client_folder_create_default_folder_with_workspace_test() { - let mut folder_pad = FolderNodePad::new(); - let workspace = WorkspaceNode::new(folder_pad.tree.clone(), "1".to_string(), "workspace name".to_string()); - folder_pad.workspaces.add_workspace(workspace).unwrap(); - let json = folder_pad.to_json(false).unwrap(); - assert_eq!( - json, - r#"{"type":"folder","children":[{"type":"workspaces","children":[{"type":"workspace","attributes":{"id":"1","name":"workspace name"}}]},{"type":"trash"}]}"# - ); + let mut folder_pad = FolderNodePad::new(); + let workspace = WorkspaceNode::new( + folder_pad.tree.clone(), + "1".to_string(), + "workspace name".to_string(), + ); + folder_pad.workspaces.add_workspace(workspace).unwrap(); + let json = folder_pad.to_json(false).unwrap(); + assert_eq!( + json, + r#"{"type":"folder","children":[{"type":"workspaces","children":[{"type":"workspace","attributes":{"id":"1","name":"workspace name"}}]},{"type":"trash"}]}"# + ); - assert_eq!( - folder_pad.get_workspace("1").unwrap().get_name().unwrap(), - "workspace name" - ); + assert_eq!( + folder_pad.get_workspace("1").unwrap().get_name().unwrap(), + "workspace name" + ); } #[test] fn client_folder_delete_workspace_test() { - let mut folder_pad = FolderNodePad::new(); - let workspace = WorkspaceNode::new(folder_pad.tree.clone(), "1".to_string(), "workspace name".to_string()); - folder_pad.workspaces.add_workspace(workspace).unwrap(); - folder_pad.workspaces.remove_workspace("1"); - let json = folder_pad.to_json(false).unwrap(); - assert_eq!( - json, - r#"{"type":"folder","children":[{"type":"workspaces"},{"type":"trash"}]}"# - ); + let mut folder_pad = FolderNodePad::new(); + let workspace = WorkspaceNode::new( + folder_pad.tree.clone(), + "1".to_string(), + "workspace name".to_string(), + ); + folder_pad.workspaces.add_workspace(workspace).unwrap(); + folder_pad.workspaces.remove_workspace("1"); + let json = folder_pad.to_json(false).unwrap(); + assert_eq!( + json, + r#"{"type":"folder","children":[{"type":"workspaces"},{"type":"trash"}]}"# + ); } #[test] fn client_folder_update_workspace_name_test() { - let mut folder_pad = FolderNodePad::new(); - let workspace = WorkspaceNode::new(folder_pad.tree.clone(), "1".to_string(), "workspace name".to_string()); - folder_pad.workspaces.add_workspace(workspace).unwrap(); - folder_pad - .workspaces - .get_mut_workspace("1") - .unwrap() - .set_name("my first workspace".to_string()); + let mut folder_pad = FolderNodePad::new(); + let workspace = WorkspaceNode::new( + folder_pad.tree.clone(), + "1".to_string(), + "workspace name".to_string(), + ); + folder_pad.workspaces.add_workspace(workspace).unwrap(); + folder_pad + .workspaces + .get_mut_workspace("1") + .unwrap() + .set_name("my first workspace".to_string()); - assert_eq!( - folder_pad.workspaces.get_workspace("1").unwrap().get_name().unwrap(), - "my first workspace" - ); + assert_eq!( + folder_pad + .workspaces + .get_workspace("1") + .unwrap() + .get_name() + .unwrap(), + "my first workspace" + ); } diff --git a/frontend/rust-lib/flowy-client-sync/tests/client_folder/script.rs b/frontend/rust-lib/flowy-client-sync/tests/client_folder/script.rs index ab98f589af..8d54b91fe6 100644 --- a/frontend/rust-lib/flowy-client-sync/tests/client_folder/script.rs +++ b/frontend/rust-lib/flowy-client-sync/tests/client_folder/script.rs @@ -3,87 +3,115 @@ use folder_model::AppRevision; use lib_ot::core::Path; pub enum FolderNodePadScript { - CreateWorkspace { id: String, name: String }, - DeleteWorkspace { id: String }, - AssertPathOfWorkspace { id: String, expected_path: Path }, - AssertNumberOfWorkspace { expected: usize }, - CreateApp { id: String, name: String }, - DeleteApp { id: String }, - UpdateApp { id: String, name: String }, - AssertApp { id: String, expected: Option }, - AssertAppContent { id: String, name: String }, - // AssertNumberOfApps { expected: usize }, + CreateWorkspace { + id: String, + name: String, + }, + DeleteWorkspace { + id: String, + }, + AssertPathOfWorkspace { + id: String, + expected_path: Path, + }, + AssertNumberOfWorkspace { + expected: usize, + }, + CreateApp { + id: String, + name: String, + }, + DeleteApp { + id: String, + }, + UpdateApp { + id: String, + name: String, + }, + AssertApp { + id: String, + expected: Option, + }, + AssertAppContent { + id: String, + name: String, + }, + // AssertNumberOfApps { expected: usize }, } pub struct FolderNodePadTest { - folder_pad: FolderNodePad, + folder_pad: FolderNodePad, } impl FolderNodePadTest { - pub fn new() -> FolderNodePadTest { - let mut folder_pad = FolderNodePad::default(); - let workspace = WorkspaceNode::new(folder_pad.tree.clone(), "1".to_string(), "workspace name".to_string()); - folder_pad.workspaces.add_workspace(workspace).unwrap(); - Self { folder_pad } - } + pub fn new() -> FolderNodePadTest { + let mut folder_pad = FolderNodePad::default(); + let workspace = WorkspaceNode::new( + folder_pad.tree.clone(), + "1".to_string(), + "workspace name".to_string(), + ); + folder_pad.workspaces.add_workspace(workspace).unwrap(); + Self { folder_pad } + } - pub fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script); - } + pub fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script); } + } - pub fn run_script(&mut self, script: FolderNodePadScript) { - match script { - FolderNodePadScript::CreateWorkspace { id, name } => { - let workspace = WorkspaceNode::new(self.folder_pad.tree.clone(), id, name); - self.folder_pad.workspaces.add_workspace(workspace).unwrap(); - } - FolderNodePadScript::DeleteWorkspace { id } => { - self.folder_pad.workspaces.remove_workspace(id); - } - FolderNodePadScript::AssertPathOfWorkspace { id, expected_path } => { - let workspace_node: &WorkspaceNode = self.folder_pad.workspaces.get_workspace(id).unwrap(); - let node_id = workspace_node.node_id.unwrap(); - let path = self.folder_pad.tree.read().path_from_node_id(node_id); - assert_eq!(path, expected_path); - } - FolderNodePadScript::AssertNumberOfWorkspace { expected } => { - assert_eq!(self.folder_pad.workspaces.len(), expected); - } - FolderNodePadScript::CreateApp { id, name } => { - let app_node = AppNode::new(self.folder_pad.tree.clone(), id, name); - let workspace_node = self.folder_pad.get_mut_workspace("1").unwrap(); - workspace_node.add_app(app_node).unwrap(); - } - FolderNodePadScript::DeleteApp { id } => { - let workspace_node = self.folder_pad.get_mut_workspace("1").unwrap(); - workspace_node.remove_app(&id); - } - FolderNodePadScript::UpdateApp { id, name } => { - let workspace_node = self.folder_pad.get_mut_workspace("1").unwrap(); - workspace_node.get_mut_app(&id).unwrap().set_name(name); - } - FolderNodePadScript::AssertApp { id, expected } => { - let workspace_node = self.folder_pad.get_workspace("1").unwrap(); - let app = workspace_node.get_app(&id); - match expected { - None => assert!(app.is_none()), - Some(expected_app) => { - let app_node = app.unwrap(); - assert_eq!(expected_app.name, app_node.get_name().unwrap()); - assert_eq!(expected_app.id, app_node.get_id().unwrap()); - } - } - } - FolderNodePadScript::AssertAppContent { id, name } => { - let workspace_node = self.folder_pad.get_workspace("1").unwrap(); - let app = workspace_node.get_app(&id).unwrap(); - assert_eq!(app.get_name().unwrap(), name) - } // FolderNodePadScript::AssertNumberOfApps { expected } => { - // let workspace_node = self.folder_pad.get_workspace("1").unwrap(); - // assert_eq!(workspace_node.apps.len(), expected); - // } + pub fn run_script(&mut self, script: FolderNodePadScript) { + match script { + FolderNodePadScript::CreateWorkspace { id, name } => { + let workspace = WorkspaceNode::new(self.folder_pad.tree.clone(), id, name); + self.folder_pad.workspaces.add_workspace(workspace).unwrap(); + }, + FolderNodePadScript::DeleteWorkspace { id } => { + self.folder_pad.workspaces.remove_workspace(id); + }, + FolderNodePadScript::AssertPathOfWorkspace { id, expected_path } => { + let workspace_node: &WorkspaceNode = self.folder_pad.workspaces.get_workspace(id).unwrap(); + let node_id = workspace_node.node_id.unwrap(); + let path = self.folder_pad.tree.read().path_from_node_id(node_id); + assert_eq!(path, expected_path); + }, + FolderNodePadScript::AssertNumberOfWorkspace { expected } => { + assert_eq!(self.folder_pad.workspaces.len(), expected); + }, + FolderNodePadScript::CreateApp { id, name } => { + let app_node = AppNode::new(self.folder_pad.tree.clone(), id, name); + let workspace_node = self.folder_pad.get_mut_workspace("1").unwrap(); + workspace_node.add_app(app_node).unwrap(); + }, + FolderNodePadScript::DeleteApp { id } => { + let workspace_node = self.folder_pad.get_mut_workspace("1").unwrap(); + workspace_node.remove_app(&id); + }, + FolderNodePadScript::UpdateApp { id, name } => { + let workspace_node = self.folder_pad.get_mut_workspace("1").unwrap(); + workspace_node.get_mut_app(&id).unwrap().set_name(name); + }, + FolderNodePadScript::AssertApp { id, expected } => { + let workspace_node = self.folder_pad.get_workspace("1").unwrap(); + let app = workspace_node.get_app(&id); + match expected { + None => assert!(app.is_none()), + Some(expected_app) => { + let app_node = app.unwrap(); + assert_eq!(expected_app.name, app_node.get_name().unwrap()); + assert_eq!(expected_app.id, app_node.get_id().unwrap()); + }, } + }, + FolderNodePadScript::AssertAppContent { id, name } => { + let workspace_node = self.folder_pad.get_workspace("1").unwrap(); + let app = workspace_node.get_app(&id).unwrap(); + assert_eq!(app.get_name().unwrap(), name) + }, // FolderNodePadScript::AssertNumberOfApps { expected } => { + // let workspace_node = self.folder_pad.get_workspace("1").unwrap(); + // assert_eq!(workspace_node.apps.len(), expected); + // } } + } } diff --git a/frontend/rust-lib/flowy-client-sync/tests/client_folder/workspace_test.rs b/frontend/rust-lib/flowy-client-sync/tests/client_folder/workspace_test.rs index 1208fcc1ca..bd54528191 100644 --- a/frontend/rust-lib/flowy-client-sync/tests/client_folder/workspace_test.rs +++ b/frontend/rust-lib/flowy-client-sync/tests/client_folder/workspace_test.rs @@ -3,84 +3,88 @@ use crate::client_folder::script::FolderNodePadTest; #[test] fn client_folder_create_multi_workspaces_test() { - let mut test = FolderNodePadTest::new(); - test.run_scripts(vec![ - AssertPathOfWorkspace { - id: "1".to_string(), - expected_path: vec![0, 0, 0].into(), - }, - CreateWorkspace { - id: "a".to_string(), - name: "workspace a".to_string(), - }, - AssertPathOfWorkspace { - id: "a".to_string(), - expected_path: vec![0, 0, 1].into(), - }, - CreateWorkspace { - id: "b".to_string(), - name: "workspace b".to_string(), - }, - AssertPathOfWorkspace { - id: "b".to_string(), - expected_path: vec![0, 0, 2].into(), - }, - AssertNumberOfWorkspace { expected: 3 }, - // The path of the workspace 'b' will be changed after deleting the 'a' workspace. - DeleteWorkspace { id: "a".to_string() }, - AssertPathOfWorkspace { - id: "b".to_string(), - expected_path: vec![0, 0, 1].into(), - }, - ]); + let mut test = FolderNodePadTest::new(); + test.run_scripts(vec![ + AssertPathOfWorkspace { + id: "1".to_string(), + expected_path: vec![0, 0, 0].into(), + }, + CreateWorkspace { + id: "a".to_string(), + name: "workspace a".to_string(), + }, + AssertPathOfWorkspace { + id: "a".to_string(), + expected_path: vec![0, 0, 1].into(), + }, + CreateWorkspace { + id: "b".to_string(), + name: "workspace b".to_string(), + }, + AssertPathOfWorkspace { + id: "b".to_string(), + expected_path: vec![0, 0, 2].into(), + }, + AssertNumberOfWorkspace { expected: 3 }, + // The path of the workspace 'b' will be changed after deleting the 'a' workspace. + DeleteWorkspace { + id: "a".to_string(), + }, + AssertPathOfWorkspace { + id: "b".to_string(), + expected_path: vec![0, 0, 1].into(), + }, + ]); } #[test] fn client_folder_create_app_test() { - let mut test = FolderNodePadTest::new(); - test.run_scripts(vec![ - CreateApp { - id: "1".to_string(), - name: "my first app".to_string(), - }, - AssertAppContent { - id: "1".to_string(), - name: "my first app".to_string(), - }, - ]); + let mut test = FolderNodePadTest::new(); + test.run_scripts(vec![ + CreateApp { + id: "1".to_string(), + name: "my first app".to_string(), + }, + AssertAppContent { + id: "1".to_string(), + name: "my first app".to_string(), + }, + ]); } #[test] fn client_folder_delete_app_test() { - let mut test = FolderNodePadTest::new(); - test.run_scripts(vec![ - CreateApp { - id: "1".to_string(), - name: "my first app".to_string(), - }, - DeleteApp { id: "1".to_string() }, - AssertApp { - id: "1".to_string(), - expected: None, - }, - ]); + let mut test = FolderNodePadTest::new(); + test.run_scripts(vec![ + CreateApp { + id: "1".to_string(), + name: "my first app".to_string(), + }, + DeleteApp { + id: "1".to_string(), + }, + AssertApp { + id: "1".to_string(), + expected: None, + }, + ]); } #[test] fn client_folder_update_app_test() { - let mut test = FolderNodePadTest::new(); - test.run_scripts(vec![ - CreateApp { - id: "1".to_string(), - name: "my first app".to_string(), - }, - UpdateApp { - id: "1".to_string(), - name: "TODO".to_string(), - }, - AssertAppContent { - id: "1".to_string(), - name: "TODO".to_string(), - }, - ]); + let mut test = FolderNodePadTest::new(); + test.run_scripts(vec![ + CreateApp { + id: "1".to_string(), + name: "my first app".to_string(), + }, + UpdateApp { + id: "1".to_string(), + name: "TODO".to_string(), + }, + AssertAppContent { + id: "1".to_string(), + name: "TODO".to_string(), + }, + ]); } diff --git a/frontend/rust-lib/flowy-codegen/src/ast.rs b/frontend/rust-lib/flowy-codegen/src/ast.rs index 1f46e700e8..90df48ea70 100644 --- a/frontend/rust-lib/flowy-codegen/src/ast.rs +++ b/frontend/rust-lib/flowy-codegen/src/ast.rs @@ -3,39 +3,39 @@ use quote::format_ident; #[allow(dead_code)] pub struct EventASTContext { - pub event: syn::Ident, - pub event_ty: syn::Ident, - pub event_request_struct: syn::Ident, - pub event_input: Option, - pub event_output: Option, - pub event_error: String, + pub event: syn::Ident, + pub event_ty: syn::Ident, + pub event_request_struct: syn::Ident, + pub event_input: Option, + pub event_output: Option, + pub event_error: String, } impl EventASTContext { - #[allow(dead_code)] - pub fn from(enum_attrs: &EventEnumAttrs) -> EventASTContext { - let command_name = enum_attrs.enum_item_name.clone(); - if command_name.is_empty() { - panic!("Invalid command name: {}", enum_attrs.enum_item_name); - } - - let event = format_ident!("{}", &command_name); - let splits = command_name.split('_').collect::>(); - - let event_ty = format_ident!("{}", enum_attrs.enum_name); - let event_request_struct = format_ident!("{}Event", &splits.join("")); - - let event_input = enum_attrs.event_input(); - let event_output = enum_attrs.event_output(); - let event_error = enum_attrs.event_error(); - - EventASTContext { - event, - event_ty, - event_request_struct, - event_input, - event_output, - event_error, - } + #[allow(dead_code)] + pub fn from(enum_attrs: &EventEnumAttrs) -> EventASTContext { + let command_name = enum_attrs.enum_item_name.clone(); + if command_name.is_empty() { + panic!("Invalid command name: {}", enum_attrs.enum_item_name); } + + let event = format_ident!("{}", &command_name); + let splits = command_name.split('_').collect::>(); + + let event_ty = format_ident!("{}", enum_attrs.enum_name); + let event_request_struct = format_ident!("{}Event", &splits.join("")); + + let event_input = enum_attrs.event_input(); + let event_output = enum_attrs.event_output(); + let event_error = enum_attrs.event_error(); + + EventASTContext { + event, + event_ty, + event_request_struct, + event_input, + event_output, + event_error, + } + } } diff --git a/frontend/rust-lib/flowy-codegen/src/dart_event/dart_event.rs b/frontend/rust-lib/flowy-codegen/src/dart_event/dart_event.rs index 3149773246..3a0a69240b 100644 --- a/frontend/rust-lib/flowy-codegen/src/dart_event/dart_event.rs +++ b/frontend/rust-lib/flowy-codegen/src/dart_event/dart_event.rs @@ -10,63 +10,71 @@ use syn::Item; use walkdir::WalkDir; pub fn gen(crate_name: &str) { - if std::env::var("CARGO_MAKE_WORKING_DIRECTORY").is_err() { - log::warn!("CARGO_MAKE_WORKING_DIRECTORY was not set, skip generate dart pb"); - return; - } + if std::env::var("CARGO_MAKE_WORKING_DIRECTORY").is_err() { + log::warn!("CARGO_MAKE_WORKING_DIRECTORY was not set, skip generate dart pb"); + return; + } - if std::env::var("FLUTTER_FLOWY_SDK_PATH").is_err() { - log::warn!("FLUTTER_FLOWY_SDK_PATH was not set, skip generate dart pb"); - return; - } + if std::env::var("FLUTTER_FLOWY_SDK_PATH").is_err() { + log::warn!("FLUTTER_FLOWY_SDK_PATH was not set, skip generate dart pb"); + return; + } - let crate_path = std::fs::canonicalize(".").unwrap().as_path().display().to_string(); - let event_crates = parse_dart_event_files(vec![crate_path]); - let event_ast = event_crates.iter().flat_map(parse_event_crate).collect::>(); - - let event_render_ctx = ast_to_event_render_ctx(event_ast.as_ref()); - let mut render_result = DART_IMPORTED.to_owned(); - for (index, render_ctx) in event_render_ctx.into_iter().enumerate() { - let mut event_template = EventTemplate::new(); - - if let Some(content) = event_template.render(render_ctx, index) { - render_result.push_str(content.as_ref()) - } - } - - let dart_event_folder: PathBuf = [ - &std::env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap(), - &std::env::var("FLUTTER_FLOWY_SDK_PATH").unwrap(), - "lib", - "dispatch", - "dart_event", - crate_name, - ] + let crate_path = std::fs::canonicalize(".") + .unwrap() + .as_path() + .display() + .to_string(); + let event_crates = parse_dart_event_files(vec![crate_path]); + let event_ast = event_crates .iter() - .collect(); + .flat_map(parse_event_crate) + .collect::>(); - if !dart_event_folder.as_path().exists() { - std::fs::create_dir_all(dart_event_folder.as_path()).unwrap(); + let event_render_ctx = ast_to_event_render_ctx(event_ast.as_ref()); + let mut render_result = DART_IMPORTED.to_owned(); + for (index, render_ctx) in event_render_ctx.into_iter().enumerate() { + let mut event_template = EventTemplate::new(); + + if let Some(content) = event_template.render(render_ctx, index) { + render_result.push_str(content.as_ref()) } + } - let dart_event_file_path = path_string_with_component(&dart_event_folder, vec!["dart_event.dart"]); - println!("cargo:rerun-if-changed={}", dart_event_file_path); + let dart_event_folder: PathBuf = [ + &std::env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap(), + &std::env::var("FLUTTER_FLOWY_SDK_PATH").unwrap(), + "lib", + "dispatch", + "dart_event", + crate_name, + ] + .iter() + .collect(); - match std::fs::OpenOptions::new() - .create(true) - .write(true) - .append(false) - .truncate(true) - .open(&dart_event_file_path) - { - Ok(ref mut file) => { - file.write_all(render_result.as_bytes()).unwrap(); - File::flush(file).unwrap(); - } - Err(err) => { - panic!("Failed to open file: {}, {:?}", dart_event_file_path, err); - } - } + if !dart_event_folder.as_path().exists() { + std::fs::create_dir_all(dart_event_folder.as_path()).unwrap(); + } + + let dart_event_file_path = + path_string_with_component(&dart_event_folder, vec!["dart_event.dart"]); + println!("cargo:rerun-if-changed={}", dart_event_file_path); + + match std::fs::OpenOptions::new() + .create(true) + .write(true) + .append(false) + .truncate(true) + .open(&dart_event_file_path) + { + Ok(ref mut file) => { + file.write_all(render_result.as_bytes()).unwrap(); + File::flush(file).unwrap(); + }, + Err(err) => { + panic!("Failed to open file: {}, {:?}", dart_event_file_path, err); + }, + } } const DART_IMPORTED: &str = r#" @@ -76,90 +84,93 @@ part of '../../dispatch.dart'; #[derive(Debug)] pub struct DartEventCrate { - crate_path: PathBuf, - event_files: Vec, + crate_path: PathBuf, + event_files: Vec, } impl DartEventCrate { - pub fn from_config(config: &CrateConfig) -> Self { - DartEventCrate { - crate_path: config.crate_path.clone(), - event_files: config.flowy_config.event_files.clone(), - } + pub fn from_config(config: &CrateConfig) -> Self { + DartEventCrate { + crate_path: config.crate_path.clone(), + event_files: config.flowy_config.event_files.clone(), } + } } pub fn parse_dart_event_files(crate_paths: Vec) -> Vec { - let mut dart_event_crates: Vec = vec![]; - crate_paths.iter().for_each(|path| { - let crates = WalkDir::new(path) - .into_iter() - .filter_entry(|e| !is_hidden(e)) - .filter_map(|e| e.ok()) - .filter(is_crate_dir) - .flat_map(|e| parse_crate_config_from(&e)) - .map(|crate_config| DartEventCrate::from_config(&crate_config)) - .collect::>(); - dart_event_crates.extend(crates); - }); - dart_event_crates + let mut dart_event_crates: Vec = vec![]; + crate_paths.iter().for_each(|path| { + let crates = WalkDir::new(path) + .into_iter() + .filter_entry(|e| !is_hidden(e)) + .filter_map(|e| e.ok()) + .filter(is_crate_dir) + .flat_map(|e| parse_crate_config_from(&e)) + .map(|crate_config| DartEventCrate::from_config(&crate_config)) + .collect::>(); + dart_event_crates.extend(crates); + }); + dart_event_crates } pub fn parse_event_crate(event_crate: &DartEventCrate) -> Vec { - event_crate - .event_files - .iter() - .flat_map(|event_file| { - let file_path = path_string_with_component(&event_crate.crate_path, vec![event_file.as_str()]); + event_crate + .event_files + .iter() + .flat_map(|event_file| { + let file_path = + path_string_with_component(&event_crate.crate_path, vec![event_file.as_str()]); - let file_content = read_file(file_path.as_ref()).unwrap(); - let ast = syn::parse_file(file_content.as_ref()).expect("Unable to parse file"); - ast.items - .iter() - .flat_map(|item| match item { - Item::Enum(item_enum) => { - let ast_result = ASTResult::new(); - let attrs = flowy_ast::enum_from_ast( - &ast_result, - &item_enum.ident, - &item_enum.variants, - &item_enum.attrs, - ); - ast_result.check().unwrap(); - attrs - .iter() - .filter(|attr| !attr.attrs.event_attrs.ignore) - .enumerate() - .map(|(_index, variant)| EventASTContext::from(&variant.attrs)) - .collect::>() - } - _ => vec![], - }) - .collect::>() + let file_content = read_file(file_path.as_ref()).unwrap(); + let ast = syn::parse_file(file_content.as_ref()).expect("Unable to parse file"); + ast + .items + .iter() + .flat_map(|item| match item { + Item::Enum(item_enum) => { + let ast_result = ASTResult::new(); + let attrs = flowy_ast::enum_from_ast( + &ast_result, + &item_enum.ident, + &item_enum.variants, + &item_enum.attrs, + ); + ast_result.check().unwrap(); + attrs + .iter() + .filter(|attr| !attr.attrs.event_attrs.ignore) + .enumerate() + .map(|(_index, variant)| EventASTContext::from(&variant.attrs)) + .collect::>() + }, + _ => vec![], }) - .collect::>() + .collect::>() + }) + .collect::>() } pub fn ast_to_event_render_ctx(ast: &[EventASTContext]) -> Vec { - ast.iter() - .map(|event_ast| { - let input_deserializer = event_ast - .event_input - .as_ref() - .map(|event_input| event_input.get_ident().unwrap().to_string()); + ast + .iter() + .map(|event_ast| { + let input_deserializer = event_ast + .event_input + .as_ref() + .map(|event_input| event_input.get_ident().unwrap().to_string()); - let output_deserializer = event_ast - .event_output - .as_ref() - .map(|event_output| event_output.get_ident().unwrap().to_string()); + let output_deserializer = event_ast + .event_output + .as_ref() + .map(|event_output| event_output.get_ident().unwrap().to_string()); - EventRenderContext { - input_deserializer, - output_deserializer, - error_deserializer: event_ast.event_error.clone(), - event: event_ast.event.to_string(), - event_ty: event_ast.event_ty.to_string(), - } - }) - .collect::>() + EventRenderContext { + input_deserializer, + output_deserializer, + error_deserializer: event_ast.event_error.clone(), + event: event_ast.event.to_string(), + event_ty: event_ast.event_ty.to_string(), + } + }) + .collect::>() } diff --git a/frontend/rust-lib/flowy-codegen/src/dart_event/event_template.rs b/frontend/rust-lib/flowy-codegen/src/dart_event/event_template.rs index 8da5c910cd..82ca535578 100644 --- a/frontend/rust-lib/flowy-codegen/src/dart_event/event_template.rs +++ b/frontend/rust-lib/flowy-codegen/src/dart_event/event_template.rs @@ -2,60 +2,64 @@ use crate::util::get_tera; use tera::Context; pub struct EventTemplate { - tera_context: Context, + tera_context: Context, } pub struct EventRenderContext { - pub input_deserializer: Option, - pub output_deserializer: Option, - pub error_deserializer: String, - pub event: String, - pub event_ty: String, + pub input_deserializer: Option, + pub output_deserializer: Option, + pub error_deserializer: String, + pub event: String, + pub event_ty: String, } #[allow(dead_code)] impl EventTemplate { - pub fn new() -> Self { - EventTemplate { - tera_context: Context::new(), - } + pub fn new() -> Self { + EventTemplate { + tera_context: Context::new(), + } + } + + pub fn render(&mut self, ctx: EventRenderContext, index: usize) -> Option { + self.tera_context.insert("index", &index); + let dart_class_name = format!("{}{}", ctx.event_ty, ctx.event); + let event = format!("{}.{}", ctx.event_ty, ctx.event); + self.tera_context.insert("event_class", &dart_class_name); + self.tera_context.insert("event", &event); + + self + .tera_context + .insert("has_input", &ctx.input_deserializer.is_some()); + match ctx.input_deserializer { + None => self.tera_context.insert("input_deserializer", "Unit"), + Some(ref input) => self.tera_context.insert("input_deserializer", input), } - pub fn render(&mut self, ctx: EventRenderContext, index: usize) -> Option { - self.tera_context.insert("index", &index); - let dart_class_name = format!("{}{}", ctx.event_ty, ctx.event); - let event = format!("{}.{}", ctx.event_ty, ctx.event); - self.tera_context.insert("event_class", &dart_class_name); - self.tera_context.insert("event", &event); + // eprintln!( + // "😁 {:?} / {:?}", + // &ctx.input_deserializer, &ctx.output_deserializer + // ); - self.tera_context.insert("has_input", &ctx.input_deserializer.is_some()); - match ctx.input_deserializer { - None => self.tera_context.insert("input_deserializer", "Unit"), - Some(ref input) => self.tera_context.insert("input_deserializer", input), - } + let has_output = ctx.output_deserializer.is_some(); + self.tera_context.insert("has_output", &has_output); - // eprintln!( - // "😁 {:?} / {:?}", - // &ctx.input_deserializer, &ctx.output_deserializer - // ); - - let has_output = ctx.output_deserializer.is_some(); - self.tera_context.insert("has_output", &has_output); - - match ctx.output_deserializer { - None => self.tera_context.insert("output_deserializer", "Unit"), - Some(ref output) => self.tera_context.insert("output_deserializer", output), - } - - self.tera_context.insert("error_deserializer", &ctx.error_deserializer); - - let tera = get_tera("dart_event"); - match tera.render("event_template.tera", &self.tera_context) { - Ok(r) => Some(r), - Err(e) => { - log::error!("{:?}", e); - None - } - } + match ctx.output_deserializer { + None => self.tera_context.insert("output_deserializer", "Unit"), + Some(ref output) => self.tera_context.insert("output_deserializer", output), } + + self + .tera_context + .insert("error_deserializer", &ctx.error_deserializer); + + let tera = get_tera("dart_event"); + match tera.render("event_template.tera", &self.tera_context) { + Ok(r) => Some(r), + Err(e) => { + log::error!("{:?}", e); + None + }, + } + } } diff --git a/frontend/rust-lib/flowy-codegen/src/flowy_toml.rs b/frontend/rust-lib/flowy-codegen/src/flowy_toml.rs index c7a07bed73..558374f44c 100644 --- a/frontend/rust-lib/flowy-codegen/src/flowy_toml.rs +++ b/frontend/rust-lib/flowy-codegen/src/flowy_toml.rs @@ -3,57 +3,62 @@ use std::path::{Path, PathBuf}; #[derive(serde::Deserialize, Clone, Debug)] pub struct FlowyConfig { - #[serde(default)] - pub event_files: Vec, + #[serde(default)] + pub event_files: Vec, - // Collect AST from the file or directory specified by proto_input to generate the proto files. - #[serde(default)] - pub proto_input: Vec, + // Collect AST from the file or directory specified by proto_input to generate the proto files. + #[serde(default)] + pub proto_input: Vec, - // Output path for the generated proto files. The default value is default_proto_output() - #[serde(default = "default_proto_output")] - pub proto_output: String, + // Output path for the generated proto files. The default value is default_proto_output() + #[serde(default = "default_proto_output")] + pub proto_output: String, - // Create a crate that stores the generated protobuf Rust structures. The default value is default_protobuf_crate() - #[serde(default = "default_protobuf_crate")] - pub protobuf_crate_path: String, + // Create a crate that stores the generated protobuf Rust structures. The default value is default_protobuf_crate() + #[serde(default = "default_protobuf_crate")] + pub protobuf_crate_path: String, } fn default_proto_output() -> String { - "resources/proto".to_owned() + "resources/proto".to_owned() } fn default_protobuf_crate() -> String { - "src/protobuf".to_owned() + "src/protobuf".to_owned() } impl FlowyConfig { - pub fn from_toml_file(path: &Path) -> Self { - let content = fs::read_to_string(path).unwrap(); - let config: FlowyConfig = toml::from_str(content.as_ref()).unwrap(); - config - } + pub fn from_toml_file(path: &Path) -> Self { + let content = fs::read_to_string(path).unwrap(); + let config: FlowyConfig = toml::from_str(content.as_ref()).unwrap(); + config + } } pub struct CrateConfig { - pub crate_path: PathBuf, - pub crate_folder: String, - pub flowy_config: FlowyConfig, + pub crate_path: PathBuf, + pub crate_folder: String, + pub flowy_config: FlowyConfig, } pub fn parse_crate_config_from(entry: &walkdir::DirEntry) -> Option { - let mut config_path = entry.path().parent().unwrap().to_path_buf(); - config_path.push("Flowy.toml"); - if !config_path.as_path().exists() { - return None; - } - let crate_path = entry.path().parent().unwrap().to_path_buf(); - let flowy_config = FlowyConfig::from_toml_file(config_path.as_path()); - let crate_folder = crate_path.file_stem().unwrap().to_str().unwrap().to_string(); + let mut config_path = entry.path().parent().unwrap().to_path_buf(); + config_path.push("Flowy.toml"); + if !config_path.as_path().exists() { + return None; + } + let crate_path = entry.path().parent().unwrap().to_path_buf(); + let flowy_config = FlowyConfig::from_toml_file(config_path.as_path()); + let crate_folder = crate_path + .file_stem() + .unwrap() + .to_str() + .unwrap() + .to_string(); - Some(CrateConfig { - crate_path, - crate_folder, - flowy_config, - }) + Some(CrateConfig { + crate_path, + crate_folder, + flowy_config, + }) } diff --git a/frontend/rust-lib/flowy-codegen/src/lib.rs b/frontend/rust-lib/flowy-codegen/src/lib.rs index 770ed27b21..4d4df0067a 100644 --- a/frontend/rust-lib/flowy-codegen/src/lib.rs +++ b/frontend/rust-lib/flowy-codegen/src/lib.rs @@ -16,6 +16,6 @@ pub mod util; #[derive(serde::Serialize, serde::Deserialize)] pub struct ProtoCache { - pub structs: Vec, - pub enums: Vec, + pub structs: Vec, + pub enums: Vec, } diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/ast.rs b/frontend/rust-lib/flowy-codegen/src/protobuf_file/ast.rs index fbe0f21b7c..337498f0cd 100644 --- a/frontend/rust-lib/flowy-codegen/src/protobuf_file/ast.rs +++ b/frontend/rust-lib/flowy-codegen/src/protobuf_file/ast.rs @@ -14,151 +14,161 @@ use syn::Item; use walkdir::WalkDir; pub fn parse_protobuf_context_from(crate_paths: Vec) -> Vec { - let crate_infos = parse_crate_info_from_path(crate_paths); - crate_infos - .into_iter() - .map(|crate_info| { - let proto_output_path = crate_info.proto_output_path(); - let files = crate_info - .proto_input_paths() - .iter() - .flat_map(|proto_crate_path| parse_files_protobuf(proto_crate_path, &proto_output_path)) - .collect::>(); + let crate_infos = parse_crate_info_from_path(crate_paths); + crate_infos + .into_iter() + .map(|crate_info| { + let proto_output_path = crate_info.proto_output_path(); + let files = crate_info + .proto_input_paths() + .iter() + .flat_map(|proto_crate_path| parse_files_protobuf(proto_crate_path, &proto_output_path)) + .collect::>(); - ProtobufCrateContext::from_crate_info(crate_info, files) - }) - .collect::>() + ProtobufCrateContext::from_crate_info(crate_info, files) + }) + .collect::>() } fn parse_files_protobuf(proto_crate_path: &Path, proto_output_path: &Path) -> Vec { - let mut gen_proto_vec: Vec = vec![]; - // file_stem https://doc.rust-lang.org/std/path/struct.Path.html#method.file_stem - for (path, file_name) in WalkDir::new(proto_crate_path) - .into_iter() - .filter_entry(|e| !is_hidden(e)) - .filter_map(|e| e.ok()) - .filter(|e| !e.file_type().is_dir()) - .map(|e| { - let path = e.path().to_str().unwrap().to_string(); - let file_name = e.path().file_stem().unwrap().to_str().unwrap().to_string(); - (path, file_name) - }) - { - if file_name == "mod" { - continue; - } - - // https://docs.rs/syn/1.0.54/syn/struct.File.html - let ast = syn::parse_file(read_file(&path).unwrap().as_ref()) - .unwrap_or_else(|_| panic!("Unable to parse file at {}", path)); - let structs = get_ast_structs(&ast); - let proto_file = format!("{}.proto", &file_name); - let proto_file_path = path_string_with_component(proto_output_path, vec![&proto_file]); - let proto_syntax = find_proto_syntax(proto_file_path.as_ref()); - - let mut proto_content = String::new(); - - // The types that are not defined in the current file. - let mut ref_types: Vec = vec![]; - structs.iter().for_each(|s| { - let mut struct_template = StructTemplate::new(); - struct_template.set_message_struct_name(&s.name); - - s.fields - .iter() - .filter(|field| field.pb_attrs.pb_index().is_some()) - .for_each(|field| { - ref_types.push(field.ty_as_str()); - struct_template.set_field(field); - }); - - let s = struct_template.render().unwrap(); - - proto_content.push_str(s.as_ref()); - proto_content.push('\n'); - }); - - let enums = get_ast_enums(&ast); - enums.iter().for_each(|e| { - let mut enum_template = EnumTemplate::new(); - enum_template.set_message_enum(e); - let s = enum_template.render().unwrap(); - proto_content.push_str(s.as_ref()); - ref_types.push(e.name.clone()); - - proto_content.push('\n'); - }); - - if !enums.is_empty() || !structs.is_empty() { - let structs: Vec = structs.iter().map(|s| s.name.clone()).collect(); - let enums: Vec = enums.iter().map(|e| e.name.clone()).collect(); - ref_types.retain(|s| !structs.contains(s)); - ref_types.retain(|s| !enums.contains(s)); - - let info = ProtoFile { - file_path: path.clone(), - file_name: file_name.clone(), - ref_types, - structs, - enums, - syntax: proto_syntax, - content: proto_content, - }; - gen_proto_vec.push(info); - } + let mut gen_proto_vec: Vec = vec![]; + // file_stem https://doc.rust-lang.org/std/path/struct.Path.html#method.file_stem + for (path, file_name) in WalkDir::new(proto_crate_path) + .into_iter() + .filter_entry(|e| !is_hidden(e)) + .filter_map(|e| e.ok()) + .filter(|e| !e.file_type().is_dir()) + .map(|e| { + let path = e.path().to_str().unwrap().to_string(); + let file_name = e.path().file_stem().unwrap().to_str().unwrap().to_string(); + (path, file_name) + }) + { + if file_name == "mod" { + continue; } - gen_proto_vec + // https://docs.rs/syn/1.0.54/syn/struct.File.html + let ast = syn::parse_file(read_file(&path).unwrap().as_ref()) + .unwrap_or_else(|_| panic!("Unable to parse file at {}", path)); + let structs = get_ast_structs(&ast); + let proto_file = format!("{}.proto", &file_name); + let proto_file_path = path_string_with_component(proto_output_path, vec![&proto_file]); + let proto_syntax = find_proto_syntax(proto_file_path.as_ref()); + + let mut proto_content = String::new(); + + // The types that are not defined in the current file. + let mut ref_types: Vec = vec![]; + structs.iter().for_each(|s| { + let mut struct_template = StructTemplate::new(); + struct_template.set_message_struct_name(&s.name); + + s.fields + .iter() + .filter(|field| field.pb_attrs.pb_index().is_some()) + .for_each(|field| { + ref_types.push(field.ty_as_str()); + struct_template.set_field(field); + }); + + let s = struct_template.render().unwrap(); + + proto_content.push_str(s.as_ref()); + proto_content.push('\n'); + }); + + let enums = get_ast_enums(&ast); + enums.iter().for_each(|e| { + let mut enum_template = EnumTemplate::new(); + enum_template.set_message_enum(e); + let s = enum_template.render().unwrap(); + proto_content.push_str(s.as_ref()); + ref_types.push(e.name.clone()); + + proto_content.push('\n'); + }); + + if !enums.is_empty() || !structs.is_empty() { + let structs: Vec = structs.iter().map(|s| s.name.clone()).collect(); + let enums: Vec = enums.iter().map(|e| e.name.clone()).collect(); + ref_types.retain(|s| !structs.contains(s)); + ref_types.retain(|s| !enums.contains(s)); + + let info = ProtoFile { + file_path: path.clone(), + file_name: file_name.clone(), + ref_types, + structs, + enums, + syntax: proto_syntax, + content: proto_content, + }; + gen_proto_vec.push(info); + } + } + + gen_proto_vec } pub fn get_ast_structs(ast: &syn::File) -> Vec { - // let mut content = format!("{:#?}", &ast); - // let mut file = File::create("./foo.txt").unwrap(); - // file.write_all(content.as_bytes()).unwrap(); - let ast_result = ASTResult::new(); - let mut proto_structs: Vec = vec![]; - ast.items.iter().for_each(|item| { - if let Item::Struct(item_struct) = item { - let (_, fields) = struct_from_ast(&ast_result, &item_struct.fields); + // let mut content = format!("{:#?}", &ast); + // let mut file = File::create("./foo.txt").unwrap(); + // file.write_all(content.as_bytes()).unwrap(); + let ast_result = ASTResult::new(); + let mut proto_structs: Vec = vec![]; + ast.items.iter().for_each(|item| { + if let Item::Struct(item_struct) = item { + let (_, fields) = struct_from_ast(&ast_result, &item_struct.fields); - if fields.iter().filter(|f| f.pb_attrs.pb_index().is_some()).count() > 0 { - proto_structs.push(Struct { - name: item_struct.ident.to_string(), - fields, - }); - } - } - }); - ast_result.check().unwrap(); - proto_structs + if fields + .iter() + .filter(|f| f.pb_attrs.pb_index().is_some()) + .count() + > 0 + { + proto_structs.push(Struct { + name: item_struct.ident.to_string(), + fields, + }); + } + } + }); + ast_result.check().unwrap(); + proto_structs } pub fn get_ast_enums(ast: &syn::File) -> Vec { - let mut flowy_enums: Vec = vec![]; - let ast_result = ASTResult::new(); + let mut flowy_enums: Vec = vec![]; + let ast_result = ASTResult::new(); - ast.items.iter().for_each(|item| { - // https://docs.rs/syn/1.0.54/syn/enum.Item.html - if let Item::Enum(item_enum) = item { - let attrs = flowy_ast::enum_from_ast(&ast_result, &item_enum.ident, &item_enum.variants, &ast.attrs); - flowy_enums.push(FlowyEnum { - name: item_enum.ident.to_string(), - attrs, - }); - } - }); - ast_result.check().unwrap(); - flowy_enums + ast.items.iter().for_each(|item| { + // https://docs.rs/syn/1.0.54/syn/enum.Item.html + if let Item::Enum(item_enum) = item { + let attrs = flowy_ast::enum_from_ast( + &ast_result, + &item_enum.ident, + &item_enum.variants, + &ast.attrs, + ); + flowy_enums.push(FlowyEnum { + name: item_enum.ident.to_string(), + attrs, + }); + } + }); + ast_result.check().unwrap(); + flowy_enums } pub struct FlowyEnum<'a> { - pub name: String, - pub attrs: Vec>, + pub name: String, + pub attrs: Vec>, } pub struct Struct<'a> { - pub name: String, - pub fields: Vec>, + pub name: String, + pub fields: Vec>, } lazy_static! { @@ -167,27 +177,27 @@ lazy_static! { } fn find_proto_syntax(path: &str) -> String { - if !Path::new(path).exists() { - return String::from("syntax = \"proto3\";\n"); + if !Path::new(path).exists() { + return String::from("syntax = \"proto3\";\n"); + } + + let mut result = String::new(); + let mut file = File::open(path).unwrap(); + let mut content = String::new(); + file.read_to_string(&mut content).unwrap(); + + content.lines().for_each(|line| { + ////Result>> + if let Ok(Some(m)) = SYNTAX_REGEX.find(line) { + result.push_str(m.as_str()); } - let mut result = String::new(); - let mut file = File::open(path).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); + // if let Ok(Some(m)) = IMPORT_REGEX.find(line) { + // result.push_str(m.as_str()); + // result.push('\n'); + // } + }); - content.lines().for_each(|line| { - ////Result>> - if let Ok(Some(m)) = SYNTAX_REGEX.find(line) { - result.push_str(m.as_str()); - } - - // if let Ok(Some(m)) = IMPORT_REGEX.find(line) { - // result.push_str(m.as_str()); - // result.push('\n'); - // } - }); - - result.push('\n'); - result + result.push('\n'); + result } diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/mod.rs b/frontend/rust-lib/flowy-codegen/src/protobuf_file/mod.rs index 046687b3b0..a3c98d3f88 100644 --- a/frontend/rust-lib/flowy-codegen/src/protobuf_file/mod.rs +++ b/frontend/rust-lib/flowy-codegen/src/protobuf_file/mod.rs @@ -18,255 +18,274 @@ use std::process::Command; use walkdir::WalkDir; pub fn gen(crate_name: &str) { - let crate_path = std::fs::canonicalize(".").unwrap().as_path().display().to_string(); + let crate_path = std::fs::canonicalize(".") + .unwrap() + .as_path() + .display() + .to_string(); - // 1. generate the proto files to proto_file_dir - #[cfg(feature = "proto_gen")] - let proto_crates = gen_proto_files(crate_name, &crate_path); + // 1. generate the proto files to proto_file_dir + #[cfg(feature = "proto_gen")] + let proto_crates = gen_proto_files(crate_name, &crate_path); - for proto_crate in proto_crates { - let mut proto_file_paths = vec![]; - let mut file_names = vec![]; - let proto_file_output_path = proto_crate.proto_output_path().to_str().unwrap().to_string(); - let protobuf_output_path = proto_crate.protobuf_crate_path().to_str().unwrap().to_string(); + for proto_crate in proto_crates { + let mut proto_file_paths = vec![]; + let mut file_names = vec![]; + let proto_file_output_path = proto_crate + .proto_output_path() + .to_str() + .unwrap() + .to_string(); + let protobuf_output_path = proto_crate + .protobuf_crate_path() + .to_str() + .unwrap() + .to_string(); - for (path, file_name) in WalkDir::new(&proto_file_output_path) - .into_iter() - .filter_map(|e| e.ok()) - .map(|e| { - let path = e.path().to_str().unwrap().to_string(); - let file_name = e.path().file_stem().unwrap().to_str().unwrap().to_string(); - (path, file_name) - }) - { - if path.ends_with(".proto") { - // https://stackoverflow.com/questions/49077147/how-can-i-force-build-rs-to-run-again-without-cleaning-my-whole-project - println!("cargo:rerun-if-changed={}", path); - proto_file_paths.push(path); - file_names.push(file_name); - } - } - let protoc_bin_path = protoc_bin_vendored::protoc_bin_path().unwrap(); - - // 2. generate the protobuf files(Dart) - #[cfg(feature = "dart")] - generate_dart_protobuf_files( - crate_name, - &proto_file_output_path, - &proto_file_paths, - &file_names, - &protoc_bin_path, - ); - - #[cfg(feature = "ts")] - generate_ts_protobuf_files( - crate_name, - &proto_file_output_path, - &proto_file_paths, - &file_names, - &protoc_bin_path, - ); - - // 3. generate the protobuf files(Rust) - generate_rust_protobuf_files( - &protoc_bin_path, - &proto_file_paths, - &proto_file_output_path, - &protobuf_output_path, - ); + for (path, file_name) in WalkDir::new(&proto_file_output_path) + .into_iter() + .filter_map(|e| e.ok()) + .map(|e| { + let path = e.path().to_str().unwrap().to_string(); + let file_name = e.path().file_stem().unwrap().to_str().unwrap().to_string(); + (path, file_name) + }) + { + if path.ends_with(".proto") { + // https://stackoverflow.com/questions/49077147/how-can-i-force-build-rs-to-run-again-without-cleaning-my-whole-project + println!("cargo:rerun-if-changed={}", path); + proto_file_paths.push(path); + file_names.push(file_name); + } } + let protoc_bin_path = protoc_bin_vendored::protoc_bin_path().unwrap(); + + // 2. generate the protobuf files(Dart) + #[cfg(feature = "dart")] + generate_dart_protobuf_files( + crate_name, + &proto_file_output_path, + &proto_file_paths, + &file_names, + &protoc_bin_path, + ); + + #[cfg(feature = "ts")] + generate_ts_protobuf_files( + crate_name, + &proto_file_output_path, + &proto_file_paths, + &file_names, + &protoc_bin_path, + ); + + // 3. generate the protobuf files(Rust) + generate_rust_protobuf_files( + &protoc_bin_path, + &proto_file_paths, + &proto_file_output_path, + &protobuf_output_path, + ); + } } fn generate_rust_protobuf_files( - protoc_bin_path: &Path, - proto_file_paths: &[String], - proto_file_output_path: &str, - protobuf_output_path: &str, + protoc_bin_path: &Path, + proto_file_paths: &[String], + proto_file_output_path: &str, + protobuf_output_path: &str, ) { - protoc_rust::Codegen::new() - .out_dir(protobuf_output_path) - .protoc_path(protoc_bin_path) - .inputs(proto_file_paths) - .include(proto_file_output_path) - .run() - .expect("Running rust protoc failed."); + protoc_rust::Codegen::new() + .out_dir(protobuf_output_path) + .protoc_path(protoc_bin_path) + .inputs(proto_file_paths) + .include(proto_file_output_path) + .run() + .expect("Running rust protoc failed."); } #[cfg(feature = "ts")] fn generate_ts_protobuf_files( - name: &str, - proto_file_output_path: &str, - paths: &[String], - file_names: &Vec, - protoc_bin_path: &Path, + name: &str, + proto_file_output_path: &str, + paths: &[String], + file_names: &Vec, + protoc_bin_path: &Path, ) { - let root = std::env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap_or("../../".to_string()); - let tauri_backend_service_path = - std::env::var("TAURI_BACKEND_SERVICE_PATH").unwrap_or("appflowy_tauri/src/services/backend".to_string()); + let root = std::env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap_or("../../".to_string()); + let tauri_backend_service_path = std::env::var("TAURI_BACKEND_SERVICE_PATH") + .unwrap_or("appflowy_tauri/src/services/backend".to_string()); - let mut output = PathBuf::new(); - output.push(root); - output.push(tauri_backend_service_path); - output.push("classes"); - output.push(name); + let mut output = PathBuf::new(); + output.push(root); + output.push(tauri_backend_service_path); + output.push("classes"); + output.push(name); - if !output.as_path().exists() { - std::fs::create_dir_all(&output).unwrap(); - } - let protoc_bin_path = protoc_bin_path.to_str().unwrap().to_owned(); - paths.iter().for_each(|path| { - let result = cmd_lib::run_cmd! { - ${protoc_bin_path} --ts_out=${output} --proto_path=${proto_file_output_path} ${path} - }; + if !output.as_path().exists() { + std::fs::create_dir_all(&output).unwrap(); + } + let protoc_bin_path = protoc_bin_path.to_str().unwrap().to_owned(); + paths.iter().for_each(|path| { + let result = cmd_lib::run_cmd! { + ${protoc_bin_path} --ts_out=${output} --proto_path=${proto_file_output_path} ${path} + }; - if result.is_err() { - panic!("Generate dart pb file failed with: {}, {:?}", path, result) - }; - }); + if result.is_err() { + panic!("Generate dart pb file failed with: {}, {:?}", path, result) + }; + }); - let ts_index = path_string_with_component(&output, vec!["index.ts"]); - match std::fs::OpenOptions::new() - .create(true) - .write(true) - .append(false) - .truncate(true) - .open(&ts_index) - { - Ok(ref mut file) => { - let mut export = String::new(); - export.push_str("// Auto-generated, do not edit \n"); - for file_name in file_names { - let c = format!("export * from \"./{}\";\n", file_name); - export.push_str(c.as_ref()); - } + let ts_index = path_string_with_component(&output, vec!["index.ts"]); + match std::fs::OpenOptions::new() + .create(true) + .write(true) + .append(false) + .truncate(true) + .open(&ts_index) + { + Ok(ref mut file) => { + let mut export = String::new(); + export.push_str("// Auto-generated, do not edit \n"); + for file_name in file_names { + let c = format!("export * from \"./{}\";\n", file_name); + export.push_str(c.as_ref()); + } - file.write_all(export.as_bytes()).unwrap(); - File::flush(file).unwrap(); - } - Err(err) => { - panic!("Failed to open file: {}", err); - } - } + file.write_all(export.as_bytes()).unwrap(); + File::flush(file).unwrap(); + }, + Err(err) => { + panic!("Failed to open file: {}", err); + }, + } } #[cfg(feature = "dart")] fn generate_dart_protobuf_files( - name: &str, - proto_file_output_path: &str, - paths: &[String], - file_names: &Vec, - protoc_bin_path: &Path, + name: &str, + proto_file_output_path: &str, + paths: &[String], + file_names: &Vec, + protoc_bin_path: &Path, ) { - if std::env::var("CARGO_MAKE_WORKING_DIRECTORY").is_err() { - log::error!("CARGO_MAKE_WORKING_DIRECTORY was not set, skip generate dart pb"); - return; - } + if std::env::var("CARGO_MAKE_WORKING_DIRECTORY").is_err() { + log::error!("CARGO_MAKE_WORKING_DIRECTORY was not set, skip generate dart pb"); + return; + } - if std::env::var("FLUTTER_FLOWY_SDK_PATH").is_err() { - log::error!("FLUTTER_FLOWY_SDK_PATH was not set, skip generate dart pb"); - return; - } + if std::env::var("FLUTTER_FLOWY_SDK_PATH").is_err() { + log::error!("FLUTTER_FLOWY_SDK_PATH was not set, skip generate dart pb"); + return; + } - let mut output = PathBuf::new(); - output.push(std::env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap()); - output.push(std::env::var("FLUTTER_FLOWY_SDK_PATH").unwrap()); - output.push("lib"); - output.push("protobuf"); - output.push(name); + let mut output = PathBuf::new(); + output.push(std::env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap()); + output.push(std::env::var("FLUTTER_FLOWY_SDK_PATH").unwrap()); + output.push("lib"); + output.push("protobuf"); + output.push(name); - if !output.as_path().exists() { - std::fs::create_dir_all(&output).unwrap(); - } - check_pb_dart_plugin(); - let protoc_bin_path = protoc_bin_path.to_str().unwrap().to_owned(); - paths.iter().for_each(|path| { - let result = cmd_lib::run_cmd! { - ${protoc_bin_path} --dart_out=${output} --proto_path=${proto_file_output_path} ${path} - }; + if !output.as_path().exists() { + std::fs::create_dir_all(&output).unwrap(); + } + check_pb_dart_plugin(); + let protoc_bin_path = protoc_bin_path.to_str().unwrap().to_owned(); + paths.iter().for_each(|path| { + let result = cmd_lib::run_cmd! { + ${protoc_bin_path} --dart_out=${output} --proto_path=${proto_file_output_path} ${path} + }; - if result.is_err() { - panic!("Generate dart pb file failed with: {}, {:?}", path, result) - }; - }); + if result.is_err() { + panic!("Generate dart pb file failed with: {}, {:?}", path, result) + }; + }); - let protobuf_dart = path_string_with_component(&output, vec!["protobuf.dart"]); + let protobuf_dart = path_string_with_component(&output, vec!["protobuf.dart"]); - match std::fs::OpenOptions::new() - .create(true) - .write(true) - .append(false) - .truncate(true) - .open(&protobuf_dart) - { - Ok(ref mut file) => { - let mut export = String::new(); - export.push_str("// Auto-generated, do not edit \n"); - for file_name in file_names { - let c = format!("export './{}.pb.dart';\n", file_name); - export.push_str(c.as_ref()); - } + match std::fs::OpenOptions::new() + .create(true) + .write(true) + .append(false) + .truncate(true) + .open(&protobuf_dart) + { + Ok(ref mut file) => { + let mut export = String::new(); + export.push_str("// Auto-generated, do not edit \n"); + for file_name in file_names { + let c = format!("export './{}.pb.dart';\n", file_name); + export.push_str(c.as_ref()); + } - file.write_all(export.as_bytes()).unwrap(); - File::flush(file).unwrap(); - } - Err(err) => { - panic!("Failed to open file: {}", err); - } - } + file.write_all(export.as_bytes()).unwrap(); + File::flush(file).unwrap(); + }, + Err(err) => { + panic!("Failed to open file: {}", err); + }, + } } pub fn check_pb_dart_plugin() { - if cfg!(target_os = "windows") { - //Command::new("cmd") - // .arg("/C") - // .arg(cmd) - // .status() - // .expect("failed to execute process"); - //panic!("{}", format!("\n❌ The protoc-gen-dart was not installed correctly.")) - } else { - let exit_result = Command::new("sh") - .arg("-c") - .arg("command -v protoc-gen-dart") - .status() - .expect("failed to execute process"); + if cfg!(target_os = "windows") { + //Command::new("cmd") + // .arg("/C") + // .arg(cmd) + // .status() + // .expect("failed to execute process"); + //panic!("{}", format!("\n❌ The protoc-gen-dart was not installed correctly.")) + } else { + let exit_result = Command::new("sh") + .arg("-c") + .arg("command -v protoc-gen-dart") + .status() + .expect("failed to execute process"); - if !exit_result.success() { - let mut msg = "\n❌ Can't find protoc-gen-dart in $PATH:\n".to_string(); - let output = Command::new("sh").arg("-c").arg("echo $PATH").output(); - let paths = String::from_utf8(output.unwrap().stdout) - .unwrap() - .split(':') - .map(|s| s.to_string()) - .collect::>(); + if !exit_result.success() { + let mut msg = "\n❌ Can't find protoc-gen-dart in $PATH:\n".to_string(); + let output = Command::new("sh").arg("-c").arg("echo $PATH").output(); + let paths = String::from_utf8(output.unwrap().stdout) + .unwrap() + .split(':') + .map(|s| s.to_string()) + .collect::>(); - paths.iter().for_each(|s| msg.push_str(&format!("{}\n", s))); + paths.iter().for_each(|s| msg.push_str(&format!("{}\n", s))); - if let Ok(output) = Command::new("sh").arg("-c").arg("which protoc-gen-dart").output() { - msg.push_str(&format!( - "Installed protoc-gen-dart path: {:?}\n", - String::from_utf8(output.stdout).unwrap() - )); - } + if let Ok(output) = Command::new("sh") + .arg("-c") + .arg("which protoc-gen-dart") + .output() + { + msg.push_str(&format!( + "Installed protoc-gen-dart path: {:?}\n", + String::from_utf8(output.stdout).unwrap() + )); + } - msg.push_str("✅ You can fix that by adding:"); - msg.push_str("\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n"); - msg.push_str("to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)"); - panic!("{}", msg) - } + msg.push_str("✅ You can fix that by adding:"); + msg.push_str("\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n"); + msg.push_str("to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)"); + panic!("{}", msg) } + } } #[cfg(feature = "proto_gen")] fn gen_proto_files(crate_name: &str, crate_path: &str) -> Vec { - let crate_context = ProtoGenerator::gen(crate_name, crate_path); - let proto_crates = crate_context - .iter() - .map(|info| info.protobuf_crate.clone()) - .collect::>(); + let crate_context = ProtoGenerator::gen(crate_name, crate_path); + let proto_crates = crate_context + .iter() + .map(|info| info.protobuf_crate.clone()) + .collect::>(); - crate_context.into_iter().flat_map(|info| info.files).for_each(|file| { - println!("cargo:rerun-if-changed={}", file.file_path); + crate_context + .into_iter() + .flat_map(|info| info.files) + .for_each(|file| { + println!("cargo:rerun-if-changed={}", file.file_path); }); - proto_crates + proto_crates } diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/proto_gen.rs b/frontend/rust-lib/flowy-codegen/src/protobuf_file/proto_gen.rs index 2594d7d0ae..ff2431348f 100644 --- a/frontend/rust-lib/flowy-codegen/src/protobuf_file/proto_gen.rs +++ b/frontend/rust-lib/flowy-codegen/src/protobuf_file/proto_gen.rs @@ -14,148 +14,158 @@ use std::{fs::OpenOptions, io::Write}; pub struct ProtoGenerator(); impl ProtoGenerator { - pub fn gen(crate_name: &str, crate_path: &str) -> Vec { - let crate_contexts = parse_protobuf_context_from(vec![crate_path.to_owned()]); - write_proto_files(&crate_contexts); - write_rust_crate_mod_file(&crate_contexts); + pub fn gen(crate_name: &str, crate_path: &str) -> Vec { + let crate_contexts = parse_protobuf_context_from(vec![crate_path.to_owned()]); + write_proto_files(&crate_contexts); + write_rust_crate_mod_file(&crate_contexts); - let proto_cache = ProtoCache::from_crate_contexts(&crate_contexts); - let proto_cache_str = serde_json::to_string(&proto_cache).unwrap(); + let proto_cache = ProtoCache::from_crate_contexts(&crate_contexts); + let proto_cache_str = serde_json::to_string(&proto_cache).unwrap(); - let crate_cache_dir = path_buf_with_component(&cache_dir(), vec![crate_name]); - if !crate_cache_dir.as_path().exists() { - std::fs::create_dir_all(&crate_cache_dir).unwrap(); - } - - let protobuf_cache_path = path_string_with_component(&crate_cache_dir, vec!["proto_cache"]); - - match std::fs::OpenOptions::new() - .create(true) - .write(true) - .append(false) - .truncate(true) - .open(&protobuf_cache_path) - { - Ok(ref mut file) => { - file.write_all(proto_cache_str.as_bytes()).unwrap(); - File::flush(file).unwrap(); - } - Err(_err) => { - panic!("Failed to open file: {}", protobuf_cache_path); - } - } - - crate_contexts + let crate_cache_dir = path_buf_with_component(&cache_dir(), vec![crate_name]); + if !crate_cache_dir.as_path().exists() { + std::fs::create_dir_all(&crate_cache_dir).unwrap(); } + + let protobuf_cache_path = path_string_with_component(&crate_cache_dir, vec!["proto_cache"]); + + match std::fs::OpenOptions::new() + .create(true) + .write(true) + .append(false) + .truncate(true) + .open(&protobuf_cache_path) + { + Ok(ref mut file) => { + file.write_all(proto_cache_str.as_bytes()).unwrap(); + File::flush(file).unwrap(); + }, + Err(_err) => { + panic!("Failed to open file: {}", protobuf_cache_path); + }, + } + + crate_contexts + } } fn write_proto_files(crate_contexts: &[ProtobufCrateContext]) { - let file_path_content_map = crate_contexts + let file_path_content_map = crate_contexts + .iter() + .flat_map(|ctx| { + ctx + .files .iter() - .flat_map(|ctx| { - ctx.files - .iter() - .map(|file| { - ( - file.file_path.clone(), - ProtoFileSymbol { - file_name: file.file_name.clone(), - symbols: file.symbols(), - }, - ) - }) - .collect::>() + .map(|file| { + ( + file.file_path.clone(), + ProtoFileSymbol { + file_name: file.file_name.clone(), + symbols: file.symbols(), + }, + ) }) - .collect::>(); + .collect::>() + }) + .collect::>(); - for context in crate_contexts { - let dir = context.protobuf_crate.proto_output_path(); - context.files.iter().for_each(|file| { - // syntax - let mut file_content = file.syntax.clone(); + for context in crate_contexts { + let dir = context.protobuf_crate.proto_output_path(); + context.files.iter().for_each(|file| { + // syntax + let mut file_content = file.syntax.clone(); - // import - file_content.push_str(&gen_import_content(file, &file_path_content_map)); + // import + file_content.push_str(&gen_import_content(file, &file_path_content_map)); - // content - file_content.push_str(&file.content); + // content + file_content.push_str(&file.content); - let proto_file = format!("{}.proto", &file.file_name); - let proto_file_path = path_string_with_component(&dir, vec![&proto_file]); - save_content_to_file_with_diff_prompt(&file_content, proto_file_path.as_ref()); - }); - } + let proto_file = format!("{}.proto", &file.file_name); + let proto_file_path = path_string_with_component(&dir, vec![&proto_file]); + save_content_to_file_with_diff_prompt(&file_content, proto_file_path.as_ref()); + }); + } } -fn gen_import_content(current_file: &ProtoFile, file_path_symbols_map: &HashMap) -> String { - let mut import_files: Vec = vec![]; - file_path_symbols_map - .iter() - .for_each(|(file_path, proto_file_symbols)| { - if file_path != ¤t_file.file_path { - current_file.ref_types.iter().for_each(|ref_type| { - if proto_file_symbols.symbols.contains(ref_type) { - let import_file = format!("import \"{}.proto\";", proto_file_symbols.file_name); - if !import_files.contains(&import_file) { - import_files.push(import_file); - } - } - }); +fn gen_import_content( + current_file: &ProtoFile, + file_path_symbols_map: &HashMap, +) -> String { + let mut import_files: Vec = vec![]; + file_path_symbols_map + .iter() + .for_each(|(file_path, proto_file_symbols)| { + if file_path != ¤t_file.file_path { + current_file.ref_types.iter().for_each(|ref_type| { + if proto_file_symbols.symbols.contains(ref_type) { + let import_file = format!("import \"{}.proto\";", proto_file_symbols.file_name); + if !import_files.contains(&import_file) { + import_files.push(import_file); } + } }); - if import_files.len() == 1 { - format!("{}\n", import_files.pop().unwrap()) - } else { - import_files.join("\n") - } + } + }); + if import_files.len() == 1 { + format!("{}\n", import_files.pop().unwrap()) + } else { + import_files.join("\n") + } } struct ProtoFileSymbol { - file_name: String, - symbols: Vec, + file_name: String, + symbols: Vec, } fn write_rust_crate_mod_file(crate_contexts: &[ProtobufCrateContext]) { - for context in crate_contexts { - let mod_path = context.protobuf_crate.proto_model_mod_file(); - match OpenOptions::new() - .create(true) - .write(true) - .append(false) - .truncate(true) - .open(&mod_path) - { - Ok(ref mut file) => { - let mut mod_file_content = String::new(); + for context in crate_contexts { + let mod_path = context.protobuf_crate.proto_model_mod_file(); + match OpenOptions::new() + .create(true) + .write(true) + .append(false) + .truncate(true) + .open(&mod_path) + { + Ok(ref mut file) => { + let mut mod_file_content = String::new(); - mod_file_content.push_str("#![cfg_attr(rustfmt, rustfmt::skip)]\n"); - mod_file_content.push_str("// Auto-generated, do not edit\n"); - walk_dir( - context.protobuf_crate.proto_output_path(), - |e| !e.file_type().is_dir(), - |_, name| { - let c = format!("\nmod {};\npub use {}::*;\n", &name, &name); - mod_file_content.push_str(c.as_ref()); - }, - ); - file.write_all(mod_file_content.as_bytes()).unwrap(); - } - Err(err) => { - panic!("Failed to open file: {}", err); - } - } + mod_file_content.push_str("#![cfg_attr(rustfmt, rustfmt::skip)]\n"); + mod_file_content.push_str("// Auto-generated, do not edit\n"); + walk_dir( + context.protobuf_crate.proto_output_path(), + |e| !e.file_type().is_dir(), + |_, name| { + let c = format!("\nmod {};\npub use {}::*;\n", &name, &name); + mod_file_content.push_str(c.as_ref()); + }, + ); + file.write_all(mod_file_content.as_bytes()).unwrap(); + }, + Err(err) => { + panic!("Failed to open file: {}", err); + }, } + } } impl ProtoCache { - fn from_crate_contexts(crate_contexts: &[ProtobufCrateContext]) -> Self { - let proto_files = crate_contexts - .iter() - .flat_map(|crate_info| &crate_info.files) - .collect::>(); + fn from_crate_contexts(crate_contexts: &[ProtobufCrateContext]) -> Self { + let proto_files = crate_contexts + .iter() + .flat_map(|crate_info| &crate_info.files) + .collect::>(); - let structs: Vec = proto_files.iter().flat_map(|info| info.structs.clone()).collect(); - let enums: Vec = proto_files.iter().flat_map(|info| info.enums.clone()).collect(); - Self { structs, enums } - } + let structs: Vec = proto_files + .iter() + .flat_map(|info| info.structs.clone()) + .collect(); + let enums: Vec = proto_files + .iter() + .flat_map(|info| info.enums.clone()) + .collect(); + Self { structs, enums } + } } diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/proto_info.rs b/frontend/rust-lib/flowy-codegen/src/protobuf_file/proto_info.rs index f071fe0461..bffd5c51b1 100644 --- a/frontend/rust-lib/flowy-codegen/src/protobuf_file/proto_info.rs +++ b/frontend/rust-lib/flowy-codegen/src/protobuf_file/proto_info.rs @@ -9,135 +9,140 @@ use walkdir::WalkDir; #[derive(Debug)] pub struct ProtobufCrateContext { - pub files: Vec, - pub protobuf_crate: ProtobufCrate, + pub files: Vec, + pub protobuf_crate: ProtobufCrate, } impl ProtobufCrateContext { - pub fn from_crate_info(inner: ProtobufCrate, files: Vec) -> Self { - Self { - files, - protobuf_crate: inner, - } + pub fn from_crate_info(inner: ProtobufCrate, files: Vec) -> Self { + Self { + files, + protobuf_crate: inner, } + } - pub fn create_crate_mod_file(&self) { - // mod model; - // pub use model::*; - let mod_file_path = path_string_with_component(&self.protobuf_crate.protobuf_crate_path(), vec!["mod.rs"]); - let mut content = "#![cfg_attr(rustfmt, rustfmt::skip)]\n".to_owned(); - content.push_str("// Auto-generated, do not edit\n"); - content.push_str("mod model;\npub use model::*;"); - match OpenOptions::new() - .create(true) - .write(true) - .append(false) - .truncate(true) - .open(&mod_file_path) - { - Ok(ref mut file) => { - file.write_all(content.as_bytes()).unwrap(); - } - Err(err) => { - panic!("Failed to open protobuf mod file: {}", err); - } - } + pub fn create_crate_mod_file(&self) { + // mod model; + // pub use model::*; + let mod_file_path = + path_string_with_component(&self.protobuf_crate.protobuf_crate_path(), vec!["mod.rs"]); + let mut content = "#![cfg_attr(rustfmt, rustfmt::skip)]\n".to_owned(); + content.push_str("// Auto-generated, do not edit\n"); + content.push_str("mod model;\npub use model::*;"); + match OpenOptions::new() + .create(true) + .write(true) + .append(false) + .truncate(true) + .open(&mod_file_path) + { + Ok(ref mut file) => { + file.write_all(content.as_bytes()).unwrap(); + }, + Err(err) => { + panic!("Failed to open protobuf mod file: {}", err); + }, } + } - #[allow(dead_code)] - pub fn flutter_mod_dir(&self, root: &str) -> String { - let crate_module_dir = format!("{}/{}", root, self.protobuf_crate.crate_folder); - crate_module_dir - } + #[allow(dead_code)] + pub fn flutter_mod_dir(&self, root: &str) -> String { + let crate_module_dir = format!("{}/{}", root, self.protobuf_crate.crate_folder); + crate_module_dir + } - #[allow(dead_code)] - pub fn flutter_mod_file(&self, root: &str) -> String { - let crate_module_dir = format!("{}/{}/protobuf.dart", root, self.protobuf_crate.crate_folder); - crate_module_dir - } + #[allow(dead_code)] + pub fn flutter_mod_file(&self, root: &str) -> String { + let crate_module_dir = format!( + "{}/{}/protobuf.dart", + root, self.protobuf_crate.crate_folder + ); + crate_module_dir + } } #[derive(Clone, Debug)] pub struct ProtobufCrate { - pub crate_folder: String, - pub crate_path: PathBuf, - flowy_config: FlowyConfig, + pub crate_folder: String, + pub crate_path: PathBuf, + flowy_config: FlowyConfig, } impl ProtobufCrate { - pub fn from_config(config: CrateConfig) -> Self { - ProtobufCrate { - crate_path: config.crate_path, - crate_folder: config.crate_folder, - flowy_config: config.flowy_config, - } + pub fn from_config(config: CrateConfig) -> Self { + ProtobufCrate { + crate_path: config.crate_path, + crate_folder: config.crate_folder, + flowy_config: config.flowy_config, } + } - // Return the file paths for each rust file that used to generate the proto file. - pub fn proto_input_paths(&self) -> Vec { - self.flowy_config - .proto_input - .iter() - .map(|name| path_buf_with_component(&self.crate_path, vec![name])) - .collect::>() - } + // Return the file paths for each rust file that used to generate the proto file. + pub fn proto_input_paths(&self) -> Vec { + self + .flowy_config + .proto_input + .iter() + .map(|name| path_buf_with_component(&self.crate_path, vec![name])) + .collect::>() + } - // The protobuf_crate_path is used to store the generated protobuf Rust structures. - pub fn protobuf_crate_path(&self) -> PathBuf { - let crate_path = PathBuf::from(&self.flowy_config.protobuf_crate_path); - create_dir_if_not_exist(&crate_path); - crate_path - } + // The protobuf_crate_path is used to store the generated protobuf Rust structures. + pub fn protobuf_crate_path(&self) -> PathBuf { + let crate_path = PathBuf::from(&self.flowy_config.protobuf_crate_path); + create_dir_if_not_exist(&crate_path); + crate_path + } - // The proto_output_path is used to store the proto files - pub fn proto_output_path(&self) -> PathBuf { - let output_dir = PathBuf::from(&self.flowy_config.proto_output); - create_dir_if_not_exist(&output_dir); - output_dir - } + // The proto_output_path is used to store the proto files + pub fn proto_output_path(&self) -> PathBuf { + let output_dir = PathBuf::from(&self.flowy_config.proto_output); + create_dir_if_not_exist(&output_dir); + output_dir + } - pub fn proto_model_mod_file(&self) -> String { - path_string_with_component(&self.protobuf_crate_path(), vec!["mod.rs"]) - } + pub fn proto_model_mod_file(&self) -> String { + path_string_with_component(&self.protobuf_crate_path(), vec!["mod.rs"]) + } } #[derive(Debug)] pub struct ProtoFile { - pub file_path: String, - pub file_name: String, - pub structs: Vec, - // store the type of current file using - pub ref_types: Vec, + pub file_path: String, + pub file_name: String, + pub structs: Vec, + // store the type of current file using + pub ref_types: Vec, - pub enums: Vec, - // proto syntax. "proto3" or "proto2" - pub syntax: String, + pub enums: Vec, + // proto syntax. "proto3" or "proto2" + pub syntax: String, - // proto message content - pub content: String, + // proto message content + pub content: String, } impl ProtoFile { - pub fn symbols(&self) -> Vec { - let mut symbols = self.structs.clone(); - let mut enum_symbols = self.enums.clone(); - symbols.append(&mut enum_symbols); - symbols - } + pub fn symbols(&self) -> Vec { + let mut symbols = self.structs.clone(); + let mut enum_symbols = self.enums.clone(); + symbols.append(&mut enum_symbols); + symbols + } } pub fn parse_crate_info_from_path(roots: Vec) -> Vec { - let mut protobuf_crates: Vec = vec![]; - roots.iter().for_each(|root| { - let crates = WalkDir::new(root) - .into_iter() - .filter_entry(|e| !is_hidden(e)) - .filter_map(|e| e.ok()) - .filter(is_crate_dir) - .flat_map(|e| parse_crate_config_from(&e)) - .map(ProtobufCrate::from_config) - .collect::>(); - protobuf_crates.extend(crates); - }); - protobuf_crates + let mut protobuf_crates: Vec = vec![]; + roots.iter().for_each(|root| { + let crates = WalkDir::new(root) + .into_iter() + .filter_entry(|e| !is_hidden(e)) + .filter_map(|e| e.ok()) + .filter(is_crate_dir) + .flat_map(|e| parse_crate_config_from(&e)) + .map(ProtobufCrate::from_config) + .collect::>(); + protobuf_crates.extend(crates); + }); + protobuf_crates } diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/derive_meta/derive_meta.rs b/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/derive_meta/derive_meta.rs index 1babe1ced6..b0a494e553 100644 --- a/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/derive_meta/derive_meta.rs +++ b/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/derive_meta/derive_meta.rs @@ -3,33 +3,33 @@ use itertools::Itertools; use tera::Context; pub struct ProtobufDeriveMeta { - context: Context, - structs: Vec, - enums: Vec, + context: Context, + structs: Vec, + enums: Vec, } #[allow(dead_code)] impl ProtobufDeriveMeta { - pub fn new(structs: Vec, enums: Vec) -> Self { - let enums: Vec<_> = enums.into_iter().unique().collect(); - ProtobufDeriveMeta { - context: Context::new(), - structs, - enums, - } + pub fn new(structs: Vec, enums: Vec) -> Self { + let enums: Vec<_> = enums.into_iter().unique().collect(); + ProtobufDeriveMeta { + context: Context::new(), + structs, + enums, } + } - pub fn render(&mut self) -> Option { - self.context.insert("names", &self.structs); - self.context.insert("enums", &self.enums); + pub fn render(&mut self) -> Option { + self.context.insert("names", &self.structs); + self.context.insert("enums", &self.enums); - let tera = get_tera("protobuf_file/template/derive_meta"); - match tera.render("derive_meta.tera", &self.context) { - Ok(r) => Some(r), - Err(e) => { - log::error!("{:?}", e); - None - } - } + let tera = get_tera("protobuf_file/template/derive_meta"); + match tera.render("derive_meta.tera", &self.context) { + Ok(r) => Some(r), + Err(e) => { + log::error!("{:?}", e); + None + }, } + } } diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/enum_template.rs b/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/enum_template.rs index 9e390c1f5c..4080878709 100644 --- a/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/enum_template.rs +++ b/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/enum_template.rs @@ -3,36 +3,38 @@ use crate::util::get_tera; use tera::Context; pub struct EnumTemplate { - context: Context, - items: Vec, + context: Context, + items: Vec, } #[allow(dead_code)] impl EnumTemplate { - pub fn new() -> Self { - EnumTemplate { - context: Context::new(), - items: vec![], - } + pub fn new() -> Self { + EnumTemplate { + context: Context::new(), + items: vec![], } + } - pub fn set_message_enum(&mut self, flowy_enum: &FlowyEnum) { - self.context.insert("enum_name", &flowy_enum.name); - flowy_enum.attrs.iter().for_each(|item| { - self.items - .push(format!("{} = {};", item.attrs.enum_item_name, item.attrs.value)) - }) - } + pub fn set_message_enum(&mut self, flowy_enum: &FlowyEnum) { + self.context.insert("enum_name", &flowy_enum.name); + flowy_enum.attrs.iter().for_each(|item| { + self.items.push(format!( + "{} = {};", + item.attrs.enum_item_name, item.attrs.value + )) + }) + } - pub fn render(&mut self) -> Option { - self.context.insert("items", &self.items); - let tera = get_tera("protobuf_file/template/proto_file"); - match tera.render("enum.tera", &self.context) { - Ok(r) => Some(r), - Err(e) => { - log::error!("{:?}", e); - None - } - } + pub fn render(&mut self) -> Option { + self.context.insert("items", &self.items); + let tera = get_tera("protobuf_file/template/proto_file"); + match tera.render("enum.tera", &self.context) { + Ok(r) => Some(r), + Err(e) => { + log::error!("{:?}", e); + None + }, } + } } diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct_template.rs b/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct_template.rs index 105c669403..06295b3138 100644 --- a/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct_template.rs +++ b/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct_template.rs @@ -16,91 +16,95 @@ pub static RUST_TYPE_MAP: phf::Map<&'static str, &'static str> = phf_map! { }; pub struct StructTemplate { - context: Context, - fields: Vec, + context: Context, + fields: Vec, } #[allow(dead_code)] impl StructTemplate { - pub fn new() -> Self { - StructTemplate { - context: Context::new(), - fields: vec![], - } + pub fn new() -> Self { + StructTemplate { + context: Context::new(), + fields: vec![], + } + } + + pub fn set_message_struct_name(&mut self, name: &str) { + self.context.insert("struct_name", name); + } + + pub fn set_field(&mut self, field: &ASTField) { + // {{ field_type }} {{ field_name }} = {{index}}; + let name = field.name().unwrap().to_string(); + let index = field.pb_attrs.pb_index().unwrap(); + + let ty: &str = &field.ty_as_str(); + let mut mapped_ty: &str = ty; + + if RUST_TYPE_MAP.contains_key(ty) { + mapped_ty = RUST_TYPE_MAP[ty]; } - pub fn set_message_struct_name(&mut self, name: &str) { - self.context.insert("struct_name", name); + if let Some(ref category) = field.bracket_category { + match category { + BracketCategory::Opt => match &field.bracket_inner_ty { + None => {}, + Some(inner_ty) => match inner_ty.to_string().as_str() { + //TODO: support hashmap or something else wrapped by Option + "Vec" => { + self.fields.push(format!( + "oneof one_of_{} {{ bytes {} = {}; }};", + name, name, index + )); + }, + _ => { + self.fields.push(format!( + "oneof one_of_{} {{ {} {} = {}; }};", + name, mapped_ty, name, index + )); + }, + }, + }, + BracketCategory::Map((k, v)) => { + let key: &str = k; + let value: &str = v; + self.fields.push(format!( + // map attrs = 1; + "map<{}, {}> {} = {};", + RUST_TYPE_MAP.get(key).unwrap_or(&key), + RUST_TYPE_MAP.get(value).unwrap_or(&value), + name, + index + )); + }, + BracketCategory::Vec => { + let bracket_ty: &str = &field.bracket_ty.as_ref().unwrap().to_string(); + // Vec + if mapped_ty == "u8" && bracket_ty == "Vec" { + self.fields.push(format!("bytes {} = {};", name, index)) + } else { + self.fields.push(format!( + "{} {} {} = {};", + RUST_TYPE_MAP[bracket_ty], mapped_ty, name, index + )) + } + }, + BracketCategory::Other => self + .fields + .push(format!("{} {} = {};", mapped_ty, name, index)), + } } + } - pub fn set_field(&mut self, field: &ASTField) { - // {{ field_type }} {{ field_name }} = {{index}}; - let name = field.name().unwrap().to_string(); - let index = field.pb_attrs.pb_index().unwrap(); - - let ty: &str = &field.ty_as_str(); - let mut mapped_ty: &str = ty; - - if RUST_TYPE_MAP.contains_key(ty) { - mapped_ty = RUST_TYPE_MAP[ty]; - } - - if let Some(ref category) = field.bracket_category { - match category { - BracketCategory::Opt => match &field.bracket_inner_ty { - None => {} - Some(inner_ty) => match inner_ty.to_string().as_str() { - //TODO: support hashmap or something else wrapped by Option - "Vec" => { - self.fields - .push(format!("oneof one_of_{} {{ bytes {} = {}; }};", name, name, index)); - } - _ => { - self.fields.push(format!( - "oneof one_of_{} {{ {} {} = {}; }};", - name, mapped_ty, name, index - )); - } - }, - }, - BracketCategory::Map((k, v)) => { - let key: &str = k; - let value: &str = v; - self.fields.push(format!( - // map attrs = 1; - "map<{}, {}> {} = {};", - RUST_TYPE_MAP.get(key).unwrap_or(&key), - RUST_TYPE_MAP.get(value).unwrap_or(&value), - name, - index - )); - } - BracketCategory::Vec => { - let bracket_ty: &str = &field.bracket_ty.as_ref().unwrap().to_string(); - // Vec - if mapped_ty == "u8" && bracket_ty == "Vec" { - self.fields.push(format!("bytes {} = {};", name, index)) - } else { - self.fields.push(format!( - "{} {} {} = {};", - RUST_TYPE_MAP[bracket_ty], mapped_ty, name, index - )) - } - } - BracketCategory::Other => self.fields.push(format!("{} {} = {};", mapped_ty, name, index)), - } - } - } - - pub fn render(&mut self) -> Option { - self.context.insert("fields", &self.fields); - let tera = get_tera("protobuf_file/template/proto_file"); - match tera.render("struct.tera", &self.context) { - Ok(r) => Some(r), - Err(e) => { - log::error!("{:?}", e); - None - } - } + pub fn render(&mut self) -> Option { + self.context.insert("fields", &self.fields); + let tera = get_tera("protobuf_file/template/proto_file"); + match tera.render("struct.tera", &self.context) { + Ok(r) => Some(r), + Err(e) => { + log::error!("{:?}", e); + None + }, } + } } diff --git a/frontend/rust-lib/flowy-codegen/src/ts_event/event_template.rs b/frontend/rust-lib/flowy-codegen/src/ts_event/event_template.rs index a3b2043ed8..4c24c670e7 100644 --- a/frontend/rust-lib/flowy-codegen/src/ts_event/event_template.rs +++ b/frontend/rust-lib/flowy-codegen/src/ts_event/event_template.rs @@ -2,64 +2,69 @@ use crate::util::get_tera; use tera::Context; pub struct EventTemplate { - tera_context: Context, + tera_context: Context, } pub struct EventRenderContext { - pub input_deserializer: Option, - pub output_deserializer: Option, - pub error_deserializer: String, - pub event: String, - pub event_ty: String, - pub prefix: String, + pub input_deserializer: Option, + pub output_deserializer: Option, + pub error_deserializer: String, + pub event: String, + pub event_ty: String, + pub prefix: String, } #[allow(dead_code)] impl EventTemplate { - pub fn new() -> Self { - EventTemplate { - tera_context: Context::new(), - } + pub fn new() -> Self { + EventTemplate { + tera_context: Context::new(), + } + } + + pub fn render(&mut self, ctx: EventRenderContext, index: usize) -> Option { + self.tera_context.insert("index", &index); + let event_func_name = format!("{}{}", ctx.event_ty, ctx.event); + self + .tera_context + .insert("event_func_name", &event_func_name); + self + .tera_context + .insert("event_name", &format!("{}.{}", ctx.prefix, ctx.event_ty)); + self.tera_context.insert("event", &ctx.event); + + self + .tera_context + .insert("has_input", &ctx.input_deserializer.is_some()); + match ctx.input_deserializer { + None => {}, + Some(ref input) => self + .tera_context + .insert("input_deserializer", &format!("{}.{}", ctx.prefix, input)), } - pub fn render(&mut self, ctx: EventRenderContext, index: usize) -> Option { - self.tera_context.insert("index", &index); - let event_func_name = format!("{}{}", ctx.event_ty, ctx.event); - self.tera_context.insert("event_func_name", &event_func_name); - self.tera_context - .insert("event_name", &format!("{}.{}", ctx.prefix, ctx.event_ty)); - self.tera_context.insert("event", &ctx.event); + let has_output = ctx.output_deserializer.is_some(); + self.tera_context.insert("has_output", &has_output); - self.tera_context.insert("has_input", &ctx.input_deserializer.is_some()); - match ctx.input_deserializer { - None => {} - Some(ref input) => self - .tera_context - .insert("input_deserializer", &format!("{}.{}", ctx.prefix, input)), - } - - let has_output = ctx.output_deserializer.is_some(); - self.tera_context.insert("has_output", &has_output); - - match ctx.output_deserializer { - None => self.tera_context.insert("output_deserializer", "void"), - Some(ref output) => self - .tera_context - .insert("output_deserializer", &format!("{}.{}", ctx.prefix, output)), - } - - self.tera_context.insert( - "error_deserializer", - &format!("{}.{}", ctx.prefix, ctx.error_deserializer), - ); - - let tera = get_tera("ts_event"); - match tera.render("event_template.tera", &self.tera_context) { - Ok(r) => Some(r), - Err(e) => { - log::error!("{:?}", e); - None - } - } + match ctx.output_deserializer { + None => self.tera_context.insert("output_deserializer", "void"), + Some(ref output) => self + .tera_context + .insert("output_deserializer", &format!("{}.{}", ctx.prefix, output)), } + + self.tera_context.insert( + "error_deserializer", + &format!("{}.{}", ctx.prefix, ctx.error_deserializer), + ); + + let tera = get_tera("ts_event"); + match tera.render("event_template.tera", &self.tera_context) { + Ok(r) => Some(r), + Err(e) => { + log::error!("{:?}", e); + None + }, + } + } } diff --git a/frontend/rust-lib/flowy-codegen/src/ts_event/mod.rs b/frontend/rust-lib/flowy-codegen/src/ts_event/mod.rs index 017f045e62..57bdf6f0b0 100644 --- a/frontend/rust-lib/flowy-codegen/src/ts_event/mod.rs +++ b/frontend/rust-lib/flowy-codegen/src/ts_event/mod.rs @@ -13,175 +13,187 @@ use syn::Item; use walkdir::WalkDir; pub fn gen(crate_name: &str) { - let root = std::env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap_or("../../".to_string()); - let tauri_backend_service_path = - std::env::var("TAURI_BACKEND_SERVICE_PATH").unwrap_or("appflowy_tauri/src/services/backend".to_string()); + let root = std::env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap_or("../../".to_string()); + let tauri_backend_service_path = std::env::var("TAURI_BACKEND_SERVICE_PATH") + .unwrap_or("appflowy_tauri/src/services/backend".to_string()); - let crate_path = std::fs::canonicalize(".").unwrap().as_path().display().to_string(); - let event_crates = parse_ts_event_files(vec![crate_path]); - let event_ast = event_crates.iter().flat_map(parse_event_crate).collect::>(); + let crate_path = std::fs::canonicalize(".") + .unwrap() + .as_path() + .display() + .to_string(); + let event_crates = parse_ts_event_files(vec![crate_path]); + let event_ast = event_crates + .iter() + .flat_map(parse_event_crate) + .collect::>(); - let event_render_ctx = ast_to_event_render_ctx(event_ast.as_ref()); - let mut render_result = TS_HEADER.to_string(); - for (index, render_ctx) in event_render_ctx.into_iter().enumerate() { - let mut event_template = EventTemplate::new(); + let event_render_ctx = ast_to_event_render_ctx(event_ast.as_ref()); + let mut render_result = TS_HEADER.to_string(); + for (index, render_ctx) in event_render_ctx.into_iter().enumerate() { + let mut event_template = EventTemplate::new(); - if let Some(content) = event_template.render(render_ctx, index) { - render_result.push_str(content.as_ref()) - } + if let Some(content) = event_template.render(render_ctx, index) { + render_result.push_str(content.as_ref()) } - render_result.push_str(TS_FOOTER); + } + render_result.push_str(TS_FOOTER); - let ts_event_folder: PathBuf = [&root, &tauri_backend_service_path, "events", crate_name] - .iter() - .collect(); - if !ts_event_folder.as_path().exists() { - std::fs::create_dir_all(ts_event_folder.as_path()).unwrap(); - } + let ts_event_folder: PathBuf = [&root, &tauri_backend_service_path, "events", crate_name] + .iter() + .collect(); + if !ts_event_folder.as_path().exists() { + std::fs::create_dir_all(ts_event_folder.as_path()).unwrap(); + } - let event_file = "event"; - let event_file_ext = "ts"; - let ts_event_file_path = - path_string_with_component(&ts_event_folder, vec![&format!("{}.{}", event_file, event_file_ext)]); - println!("cargo:rerun-if-changed={}", ts_event_file_path); + let event_file = "event"; + let event_file_ext = "ts"; + let ts_event_file_path = path_string_with_component( + &ts_event_folder, + vec![&format!("{}.{}", event_file, event_file_ext)], + ); + println!("cargo:rerun-if-changed={}", ts_event_file_path); - match std::fs::OpenOptions::new() - .create(true) - .write(true) - .append(false) - .truncate(true) - .open(&ts_event_file_path) - { - Ok(ref mut file) => { - file.write_all(render_result.as_bytes()).unwrap(); - File::flush(file).unwrap(); - } - Err(err) => { - panic!("Failed to open file: {}, {:?}", ts_event_file_path, err); - } - } + match std::fs::OpenOptions::new() + .create(true) + .write(true) + .append(false) + .truncate(true) + .open(&ts_event_file_path) + { + Ok(ref mut file) => { + file.write_all(render_result.as_bytes()).unwrap(); + File::flush(file).unwrap(); + }, + Err(err) => { + panic!("Failed to open file: {}, {:?}", ts_event_file_path, err); + }, + } - let ts_index = path_string_with_component(&ts_event_folder, vec!["index.ts"]); - match std::fs::OpenOptions::new() - .create(true) - .write(true) - .append(false) - .truncate(true) - .open(&ts_index) - { - Ok(ref mut file) => { - let mut export = String::new(); - export.push_str("// Auto-generated, do not edit \n"); - export.push_str(&format!("export * from '../../classes/{}';\n", crate_name)); - export.push_str(&format!("export * from './{}';\n", event_file)); - file.write_all(export.as_bytes()).unwrap(); - File::flush(file).unwrap(); - } - Err(err) => { - panic!("Failed to open file: {}", err); - } - } + let ts_index = path_string_with_component(&ts_event_folder, vec!["index.ts"]); + match std::fs::OpenOptions::new() + .create(true) + .write(true) + .append(false) + .truncate(true) + .open(&ts_index) + { + Ok(ref mut file) => { + let mut export = String::new(); + export.push_str("// Auto-generated, do not edit \n"); + export.push_str(&format!("export * from '../../classes/{}';\n", crate_name)); + export.push_str(&format!("export * from './{}';\n", event_file)); + file.write_all(export.as_bytes()).unwrap(); + File::flush(file).unwrap(); + }, + Err(err) => { + panic!("Failed to open file: {}", err); + }, + } } #[derive(Debug)] pub struct TsEventCrate { - crate_path: PathBuf, - event_files: Vec, + crate_path: PathBuf, + event_files: Vec, } impl TsEventCrate { - pub fn from_config(config: &CrateConfig) -> Self { - TsEventCrate { - crate_path: config.crate_path.clone(), - event_files: config.flowy_config.event_files.clone(), - } + pub fn from_config(config: &CrateConfig) -> Self { + TsEventCrate { + crate_path: config.crate_path.clone(), + event_files: config.flowy_config.event_files.clone(), } + } } pub fn parse_ts_event_files(crate_paths: Vec) -> Vec { - let mut ts_event_crates: Vec = vec![]; - crate_paths.iter().for_each(|path| { - let crates = WalkDir::new(path) - .into_iter() - .filter_entry(|e| !is_hidden(e)) - .filter_map(|e| e.ok()) - .filter(is_crate_dir) - .flat_map(|e| parse_crate_config_from(&e)) - .map(|crate_config| TsEventCrate::from_config(&crate_config)) - .collect::>(); - ts_event_crates.extend(crates); - }); - ts_event_crates + let mut ts_event_crates: Vec = vec![]; + crate_paths.iter().for_each(|path| { + let crates = WalkDir::new(path) + .into_iter() + .filter_entry(|e| !is_hidden(e)) + .filter_map(|e| e.ok()) + .filter(is_crate_dir) + .flat_map(|e| parse_crate_config_from(&e)) + .map(|crate_config| TsEventCrate::from_config(&crate_config)) + .collect::>(); + ts_event_crates.extend(crates); + }); + ts_event_crates } pub fn parse_event_crate(event_crate: &TsEventCrate) -> Vec { - event_crate - .event_files - .iter() - .flat_map(|event_file| { - let file_path = path_string_with_component(&event_crate.crate_path, vec![event_file.as_str()]); + event_crate + .event_files + .iter() + .flat_map(|event_file| { + let file_path = + path_string_with_component(&event_crate.crate_path, vec![event_file.as_str()]); - let file_content = read_file(file_path.as_ref()).unwrap(); - let ast = syn::parse_file(file_content.as_ref()).expect("Unable to parse file"); - ast.items - .iter() - .flat_map(|item| match item { - Item::Enum(item_enum) => { - let ast_result = ASTResult::new(); - let attrs = flowy_ast::enum_from_ast( - &ast_result, - &item_enum.ident, - &item_enum.variants, - &item_enum.attrs, - ); - ast_result.check().unwrap(); - attrs - .iter() - .filter(|attr| !attr.attrs.event_attrs.ignore) - .enumerate() - .map(|(_index, variant)| EventASTContext::from(&variant.attrs)) - .collect::>() - } - _ => vec![], - }) - .collect::>() + let file_content = read_file(file_path.as_ref()).unwrap(); + let ast = syn::parse_file(file_content.as_ref()).expect("Unable to parse file"); + ast + .items + .iter() + .flat_map(|item| match item { + Item::Enum(item_enum) => { + let ast_result = ASTResult::new(); + let attrs = flowy_ast::enum_from_ast( + &ast_result, + &item_enum.ident, + &item_enum.variants, + &item_enum.attrs, + ); + ast_result.check().unwrap(); + attrs + .iter() + .filter(|attr| !attr.attrs.event_attrs.ignore) + .enumerate() + .map(|(_index, variant)| EventASTContext::from(&variant.attrs)) + .collect::>() + }, + _ => vec![], }) - .collect::>() + .collect::>() + }) + .collect::>() } pub fn ast_to_event_render_ctx(ast: &[EventASTContext]) -> Vec { - let mut import_objects = HashSet::new(); - ast.iter().for_each(|event_ast| { - if let Some(input) = event_ast.event_input.as_ref() { - import_objects.insert(input.get_ident().unwrap().to_string()); - } - if let Some(output) = event_ast.event_output.as_ref() { - import_objects.insert(output.get_ident().unwrap().to_string()); - } - }); + let mut import_objects = HashSet::new(); + ast.iter().for_each(|event_ast| { + if let Some(input) = event_ast.event_input.as_ref() { + import_objects.insert(input.get_ident().unwrap().to_string()); + } + if let Some(output) = event_ast.event_output.as_ref() { + import_objects.insert(output.get_ident().unwrap().to_string()); + } + }); - ast.iter() - .map(|event_ast| { - let input_deserializer = event_ast - .event_input - .as_ref() - .map(|event_input| event_input.get_ident().unwrap().to_string()); + ast + .iter() + .map(|event_ast| { + let input_deserializer = event_ast + .event_input + .as_ref() + .map(|event_input| event_input.get_ident().unwrap().to_string()); - let output_deserializer = event_ast - .event_output - .as_ref() - .map(|event_output| event_output.get_ident().unwrap().to_string()); + let output_deserializer = event_ast + .event_output + .as_ref() + .map(|event_output| event_output.get_ident().unwrap().to_string()); - EventRenderContext { - input_deserializer, - output_deserializer, - error_deserializer: event_ast.event_error.to_string(), - event: event_ast.event.to_string(), - event_ty: event_ast.event_ty.to_string(), - prefix: "pb".to_string(), - } - }) - .collect::>() + EventRenderContext { + input_deserializer, + output_deserializer, + error_deserializer: event_ast.event_error.to_string(), + event: event_ast.event.to_string(), + event_ty: event_ast.event_ty.to_string(), + prefix: "pb".to_string(), + } + }) + .collect::>() } const TS_HEADER: &str = r#" diff --git a/frontend/rust-lib/flowy-codegen/src/util.rs b/frontend/rust-lib/flowy-codegen/src/util.rs index c3b57eab38..9fa1ecd2b9 100644 --- a/frontend/rust-lib/flowy-codegen/src/util.rs +++ b/frontend/rust-lib/flowy-codegen/src/util.rs @@ -3,172 +3,188 @@ use similar::{ChangeTag, TextDiff}; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{ - fs::{File, OpenOptions}, - io::{Read, Write}, + fs::{File, OpenOptions}, + io::{Read, Write}, }; use tera::Tera; use walkdir::WalkDir; pub fn read_file(path: &str) -> Option { - let mut file = File::open(path).unwrap_or_else(|_| panic!("Unable to open file at {}", path)); - let mut content = String::new(); - match file.read_to_string(&mut content) { - Ok(_) => Some(content), - Err(e) => { - log::error!("{}, with error: {:?}", path, e); - Some("".to_string()) - } - } + let mut file = File::open(path).unwrap_or_else(|_| panic!("Unable to open file at {}", path)); + let mut content = String::new(); + match file.read_to_string(&mut content) { + Ok(_) => Some(content), + Err(e) => { + log::error!("{}, with error: {:?}", path, e); + Some("".to_string()) + }, + } } pub fn save_content_to_file_with_diff_prompt(content: &str, output_file: &str) { - if Path::new(output_file).exists() { - let old_content = read_file(output_file).unwrap(); - let new_content = content.to_owned(); - let write_to_file = || match OpenOptions::new() - .create(true) - .write(true) - .append(false) - .truncate(true) - .open(output_file) - { - Ok(ref mut file) => { - file.write_all(new_content.as_bytes()).unwrap(); - } - Err(err) => { - panic!("Failed to open log file: {}", err); - } - }; - if new_content != old_content { - print_diff(old_content, new_content.clone()); - write_to_file() - } - } else { - match OpenOptions::new().create(true).write(true).open(output_file) { - Ok(ref mut file) => file.write_all(content.as_bytes()).unwrap(), - Err(err) => panic!("Open or create to {} fail: {}", output_file, err), - } + if Path::new(output_file).exists() { + let old_content = read_file(output_file).unwrap(); + let new_content = content.to_owned(); + let write_to_file = || match OpenOptions::new() + .create(true) + .write(true) + .append(false) + .truncate(true) + .open(output_file) + { + Ok(ref mut file) => { + file.write_all(new_content.as_bytes()).unwrap(); + }, + Err(err) => { + panic!("Failed to open log file: {}", err); + }, + }; + if new_content != old_content { + print_diff(old_content, new_content.clone()); + write_to_file() } + } else { + match OpenOptions::new() + .create(true) + .write(true) + .open(output_file) + { + Ok(ref mut file) => file.write_all(content.as_bytes()).unwrap(), + Err(err) => panic!("Open or create to {} fail: {}", output_file, err), + } + } } pub fn print_diff(old_content: String, new_content: String) { - let diff = TextDiff::from_lines(&old_content, &new_content); - for op in diff.ops() { - for change in diff.iter_changes(op) { - let (sign, style) = match change.tag() { - ChangeTag::Delete => ("-", Style::new().red()), - ChangeTag::Insert => ("+", Style::new().green()), - ChangeTag::Equal => (" ", Style::new()), - }; + let diff = TextDiff::from_lines(&old_content, &new_content); + for op in diff.ops() { + for change in diff.iter_changes(op) { + let (sign, style) = match change.tag() { + ChangeTag::Delete => ("-", Style::new().red()), + ChangeTag::Insert => ("+", Style::new().green()), + ChangeTag::Equal => (" ", Style::new()), + }; - match change.tag() { - ChangeTag::Delete => { - print!("{}{}", style.apply_to(sign).bold(), style.apply_to(change)); - } - ChangeTag::Insert => { - print!("{}{}", style.apply_to(sign).bold(), style.apply_to(change)); - } - ChangeTag::Equal => {} - }; - } - println!("---------------------------------------------------"); + match change.tag() { + ChangeTag::Delete => { + print!("{}{}", style.apply_to(sign).bold(), style.apply_to(change)); + }, + ChangeTag::Insert => { + print!("{}{}", style.apply_to(sign).bold(), style.apply_to(change)); + }, + ChangeTag::Equal => {}, + }; } + println!("---------------------------------------------------"); + } } #[allow(dead_code)] pub fn is_crate_dir(e: &walkdir::DirEntry) -> bool { - let cargo = e.path().file_stem().unwrap().to_str().unwrap().to_string(); - cargo == *"Cargo" + let cargo = e.path().file_stem().unwrap().to_str().unwrap().to_string(); + cargo == *"Cargo" } #[allow(dead_code)] pub fn is_proto_file(e: &walkdir::DirEntry) -> bool { - if e.path().extension().is_none() { - return false; - } - let ext = e.path().extension().unwrap().to_str().unwrap().to_string(); - ext == *"proto" + if e.path().extension().is_none() { + return false; + } + let ext = e.path().extension().unwrap().to_str().unwrap().to_string(); + ext == *"proto" } pub fn is_hidden(entry: &walkdir::DirEntry) -> bool { - entry.file_name().to_str().map(|s| s.starts_with('.')).unwrap_or(false) + entry + .file_name() + .to_str() + .map(|s| s.starts_with('.')) + .unwrap_or(false) } pub fn create_dir_if_not_exist(dir: &Path) { - if !dir.exists() { - std::fs::create_dir_all(dir).unwrap(); - } + if !dir.exists() { + std::fs::create_dir_all(dir).unwrap(); + } } pub fn path_string_with_component(path: &Path, components: Vec<&str>) -> String { - path_buf_with_component(path, components).to_str().unwrap().to_string() + path_buf_with_component(path, components) + .to_str() + .unwrap() + .to_string() } #[allow(dead_code)] pub fn path_buf_with_component(path: &Path, components: Vec<&str>) -> PathBuf { - let mut path_buf = path.to_path_buf(); - for component in components { - path_buf.push(component); - } - path_buf + let mut path_buf = path.to_path_buf(); + for component in components { + path_buf.push(component); + } + path_buf } #[allow(dead_code)] pub fn walk_dir, F1, F2>(dir: P, filter: F2, mut path_and_name: F1) where - F1: FnMut(String, String), - F2: Fn(&walkdir::DirEntry) -> bool, + F1: FnMut(String, String), + F2: Fn(&walkdir::DirEntry) -> bool, { - for (path, name) in WalkDir::new(dir) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| filter(e)) - .map(|e| { - ( - e.path().to_str().unwrap().to_string(), - e.path().file_stem().unwrap().to_str().unwrap().to_string(), - ) - }) - { - path_and_name(path, name); - } + for (path, name) in WalkDir::new(dir) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| filter(e)) + .map(|e| { + ( + e.path().to_str().unwrap().to_string(), + e.path().file_stem().unwrap().to_str().unwrap().to_string(), + ) + }) + { + path_and_name(path, name); + } } #[allow(dead_code)] pub fn suffix_relative_to_path(path: &str, base: &str) -> String { - let base = Path::new(base); - let path = Path::new(path); - path.strip_prefix(base).unwrap().to_str().unwrap().to_owned() + let base = Path::new(base); + let path = Path::new(path); + path + .strip_prefix(base) + .unwrap() + .to_str() + .unwrap() + .to_owned() } pub fn get_tera(directory: &str) -> Tera { - let mut root = format!("{}/src/", env!("CARGO_MANIFEST_DIR")); - root.push_str(directory); + let mut root = format!("{}/src/", env!("CARGO_MANIFEST_DIR")); + root.push_str(directory); - let root_absolute_path = match std::fs::canonicalize(&root) { - Ok(p) => p.as_path().display().to_string(), - Err(e) => { - panic!("❌ Canonicalize file path {} failed {:?}", root, e); - } - }; + let root_absolute_path = match std::fs::canonicalize(&root) { + Ok(p) => p.as_path().display().to_string(), + Err(e) => { + panic!("❌ Canonicalize file path {} failed {:?}", root, e); + }, + }; - let mut template_path = format!("{}/**/*.tera", root_absolute_path); - if cfg!(windows) { - // remove "\\?\" prefix on windows - template_path = format!("{}/**/*.tera", &root_absolute_path[4..]); - } + let mut template_path = format!("{}/**/*.tera", root_absolute_path); + if cfg!(windows) { + // remove "\\?\" prefix on windows + template_path = format!("{}/**/*.tera", &root_absolute_path[4..]); + } - match Tera::new(template_path.as_ref()) { - Ok(t) => t, - Err(e) => { - log::error!("Parsing error(s): {}", e); - ::std::process::exit(1); - } - } + match Tera::new(template_path.as_ref()) { + Ok(t) => t, + Err(e) => { + log::error!("Parsing error(s): {}", e); + ::std::process::exit(1); + }, + } } pub fn cache_dir() -> PathBuf { - let mut path_buf = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap(); - path_buf.push(".cache"); - path_buf + let mut path_buf = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap(); + path_buf.push(".cache"); + path_buf } diff --git a/frontend/rust-lib/flowy-core/src/deps_resolve/document_deps.rs b/frontend/rust-lib/flowy-core/src/deps_resolve/document_deps.rs index 1ac74c00ff..b08b99769e 100644 --- a/frontend/rust-lib/flowy-core/src/deps_resolve/document_deps.rs +++ b/frontend/rust-lib/flowy-core/src/deps_resolve/document_deps.rs @@ -1,8 +1,8 @@ use bytes::Bytes; use flowy_client_ws::FlowyWebSocketConnect; use flowy_document::{ - errors::{internal_error, FlowyError}, - DocumentCloudService, DocumentConfig, DocumentDatabase, DocumentManager, DocumentUser, + errors::{internal_error, FlowyError}, + DocumentCloudService, DocumentConfig, DocumentDatabase, DocumentManager, DocumentUser, }; use flowy_net::ClientServerConfiguration; use flowy_net::{http_server::document::DocumentCloudServiceImpl, local_server::LocalServer}; @@ -17,98 +17,101 @@ use ws_model::ws_revision::ClientRevisionWSData; pub struct DocumentDepsResolver(); impl DocumentDepsResolver { - pub fn resolve( - local_server: Option>, - ws_conn: Arc, - user_session: Arc, - server_config: &ClientServerConfiguration, - document_config: &DocumentConfig, - ) -> Arc { - let user = Arc::new(BlockUserImpl(user_session.clone())); - let rev_web_socket = Arc::new(DocumentRevisionWebSocket(ws_conn.clone())); - let cloud_service: Arc = match local_server { - None => Arc::new(DocumentCloudServiceImpl::new(server_config.clone())), - Some(local_server) => local_server, - }; - let database = Arc::new(DocumentDatabaseImpl(user_session)); + pub fn resolve( + local_server: Option>, + ws_conn: Arc, + user_session: Arc, + server_config: &ClientServerConfiguration, + document_config: &DocumentConfig, + ) -> Arc { + let user = Arc::new(BlockUserImpl(user_session.clone())); + let rev_web_socket = Arc::new(DocumentRevisionWebSocket(ws_conn.clone())); + let cloud_service: Arc = match local_server { + None => Arc::new(DocumentCloudServiceImpl::new(server_config.clone())), + Some(local_server) => local_server, + }; + let database = Arc::new(DocumentDatabaseImpl(user_session)); - let manager = Arc::new(DocumentManager::new( - cloud_service, - user, - database, - rev_web_socket, - document_config.clone(), - )); - let receiver = Arc::new(DocumentWSMessageReceiverImpl(manager.clone())); - ws_conn.add_ws_message_receiver(receiver).unwrap(); + let manager = Arc::new(DocumentManager::new( + cloud_service, + user, + database, + rev_web_socket, + document_config.clone(), + )); + let receiver = Arc::new(DocumentWSMessageReceiverImpl(manager.clone())); + ws_conn.add_ws_message_receiver(receiver).unwrap(); - manager - } + manager + } } struct BlockUserImpl(Arc); impl DocumentUser for BlockUserImpl { - fn user_dir(&self) -> Result { - let dir = self.0.user_dir().map_err(|e| FlowyError::unauthorized().context(e))?; + fn user_dir(&self) -> Result { + let dir = self + .0 + .user_dir() + .map_err(|e| FlowyError::unauthorized().context(e))?; - let doc_dir = format!("{}/document", dir); - if !Path::new(&doc_dir).exists() { - std::fs::create_dir_all(&doc_dir)?; - } - Ok(doc_dir) + let doc_dir = format!("{}/document", dir); + if !Path::new(&doc_dir).exists() { + std::fs::create_dir_all(&doc_dir)?; } + Ok(doc_dir) + } - fn user_id(&self) -> Result { - self.0.user_id() - } + fn user_id(&self) -> Result { + self.0.user_id() + } - fn token(&self) -> Result { - self.0.token() - } + fn token(&self) -> Result { + self.0.token() + } } struct DocumentDatabaseImpl(Arc); impl DocumentDatabase for DocumentDatabaseImpl { - fn db_pool(&self) -> Result, FlowyError> { - self.0.db_pool() - } + fn db_pool(&self) -> Result, FlowyError> { + self.0.db_pool() + } } struct DocumentRevisionWebSocket(Arc); impl RevisionWebSocket for DocumentRevisionWebSocket { - fn send(&self, data: ClientRevisionWSData) -> BoxResultFuture<(), FlowyError> { - let bytes: Bytes = data.try_into().unwrap(); - let msg = WebSocketRawMessage { - channel: WSChannel::Document, - data: bytes.to_vec(), - }; - let ws_conn = self.0.clone(); - Box::pin(async move { - match ws_conn.web_socket().await? { - None => {} - Some(sender) => { - sender.send(msg).map_err(internal_error)?; - } - } - Ok(()) - }) - } + fn send(&self, data: ClientRevisionWSData) -> BoxResultFuture<(), FlowyError> { + let bytes: Bytes = data.try_into().unwrap(); + let msg = WebSocketRawMessage { + channel: WSChannel::Document, + data: bytes.to_vec(), + }; + let ws_conn = self.0.clone(); + Box::pin(async move { + match ws_conn.web_socket().await? { + None => {}, + Some(sender) => { + sender.send(msg).map_err(internal_error)?; + }, + } + Ok(()) + }) + } - fn subscribe_state_changed(&self) -> BoxFuture { - let ws_conn = self.0.clone(); - Box::pin(async move { ws_conn.subscribe_websocket_state().await }) - } + fn subscribe_state_changed(&self) -> BoxFuture { + let ws_conn = self.0.clone(); + Box::pin(async move { ws_conn.subscribe_websocket_state().await }) + } } struct DocumentWSMessageReceiverImpl(Arc); impl WSMessageReceiver for DocumentWSMessageReceiverImpl { - fn source(&self) -> WSChannel { - WSChannel::Document - } - fn receive_message(&self, msg: WebSocketRawMessage) { - let handler = self.0.clone(); - tokio::spawn(async move { - handler.receive_ws_data(Bytes::from(msg.data)).await; - }); - } + fn source(&self) -> WSChannel { + WSChannel::Document + } + fn receive_message(&self, msg: WebSocketRawMessage) { + let handler = self.0.clone(); + tokio::spawn(async move { + handler.receive_ws_data(Bytes::from(msg.data)).await; + }); + } } diff --git a/frontend/rust-lib/flowy-core/src/deps_resolve/folder_deps.rs b/frontend/rust-lib/flowy-core/src/deps_resolve/folder_deps.rs index e4bfa670a5..931063d971 100644 --- a/frontend/rust-lib/flowy-core/src/deps_resolve/folder_deps.rs +++ b/frontend/rust-lib/flowy-core/src/deps_resolve/folder_deps.rs @@ -10,9 +10,9 @@ use flowy_document::DocumentManager; use flowy_folder::entities::{ViewDataFormatPB, ViewLayoutTypePB, ViewPB}; use flowy_folder::manager::{ViewDataProcessor, ViewDataProcessorMap}; use flowy_folder::{ - errors::{internal_error, FlowyError}, - event_map::{FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser}, - manager::FolderManager, + errors::{internal_error, FlowyError}, + event_map::{FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser}, + manager::FolderManager, }; use flowy_net::ClientServerConfiguration; use flowy_net::{http_server::folder::FolderHttpCloudService, local_server::LocalServer}; @@ -30,294 +30,320 @@ use ws_model::ws_revision::ClientRevisionWSData; pub struct FolderDepsResolver(); impl FolderDepsResolver { - pub async fn resolve( - local_server: Option>, - user_session: Arc, - server_config: &ClientServerConfiguration, - ws_conn: &Arc, - text_block_manager: &Arc, - grid_manager: &Arc, - ) -> Arc { - let user: Arc = Arc::new(WorkspaceUserImpl(user_session.clone())); - let database: Arc = Arc::new(WorkspaceDatabaseImpl(user_session)); - let web_socket = Arc::new(FolderRevisionWebSocket(ws_conn.clone())); - let cloud_service: Arc = match local_server { - None => Arc::new(FolderHttpCloudService::new(server_config.clone())), - Some(local_server) => local_server, - }; + pub async fn resolve( + local_server: Option>, + user_session: Arc, + server_config: &ClientServerConfiguration, + ws_conn: &Arc, + text_block_manager: &Arc, + grid_manager: &Arc, + ) -> Arc { + let user: Arc = Arc::new(WorkspaceUserImpl(user_session.clone())); + let database: Arc = Arc::new(WorkspaceDatabaseImpl(user_session)); + let web_socket = Arc::new(FolderRevisionWebSocket(ws_conn.clone())); + let cloud_service: Arc = match local_server { + None => Arc::new(FolderHttpCloudService::new(server_config.clone())), + Some(local_server) => local_server, + }; - let view_data_processor = make_view_data_processor(text_block_manager.clone(), grid_manager.clone()); - let folder_manager = - Arc::new(FolderManager::new(user.clone(), cloud_service, database, view_data_processor, web_socket).await); + let view_data_processor = + make_view_data_processor(text_block_manager.clone(), grid_manager.clone()); + let folder_manager = Arc::new( + FolderManager::new( + user.clone(), + cloud_service, + database, + view_data_processor, + web_socket, + ) + .await, + ); - if let (Ok(user_id), Ok(token)) = (user.user_id(), user.token()) { - match folder_manager.initialize(&user_id, &token).await { - Ok(_) => {} - Err(e) => tracing::error!("Initialize folder manager failed: {}", e), - } - } - - let receiver = Arc::new(FolderWSMessageReceiverImpl(folder_manager.clone())); - ws_conn.add_ws_message_receiver(receiver).unwrap(); - folder_manager + if let (Ok(user_id), Ok(token)) = (user.user_id(), user.token()) { + match folder_manager.initialize(&user_id, &token).await { + Ok(_) => {}, + Err(e) => tracing::error!("Initialize folder manager failed: {}", e), + } } + + let receiver = Arc::new(FolderWSMessageReceiverImpl(folder_manager.clone())); + ws_conn.add_ws_message_receiver(receiver).unwrap(); + folder_manager + } } fn make_view_data_processor( - document_manager: Arc, - grid_manager: Arc, + document_manager: Arc, + grid_manager: Arc, ) -> ViewDataProcessorMap { - let mut map: HashMap> = HashMap::new(); + let mut map: HashMap> = HashMap::new(); - let document_processor = Arc::new(DocumentViewDataProcessor(document_manager)); - document_processor.data_types().into_iter().for_each(|data_type| { - map.insert(data_type, document_processor.clone()); + let document_processor = Arc::new(DocumentViewDataProcessor(document_manager)); + document_processor + .data_types() + .into_iter() + .for_each(|data_type| { + map.insert(data_type, document_processor.clone()); }); - let grid_data_impl = Arc::new(GridViewDataProcessor(grid_manager)); - grid_data_impl.data_types().into_iter().for_each(|data_type| { - map.insert(data_type, grid_data_impl.clone()); + let grid_data_impl = Arc::new(GridViewDataProcessor(grid_manager)); + grid_data_impl + .data_types() + .into_iter() + .for_each(|data_type| { + map.insert(data_type, grid_data_impl.clone()); }); - Arc::new(map) + Arc::new(map) } struct WorkspaceDatabaseImpl(Arc); impl WorkspaceDatabase for WorkspaceDatabaseImpl { - fn db_pool(&self) -> Result, FlowyError> { - self.0.db_pool().map_err(|e| FlowyError::internal().context(e)) - } + fn db_pool(&self) -> Result, FlowyError> { + self + .0 + .db_pool() + .map_err(|e| FlowyError::internal().context(e)) + } } struct WorkspaceUserImpl(Arc); impl WorkspaceUser for WorkspaceUserImpl { - fn user_id(&self) -> Result { - self.0.user_id().map_err(|e| FlowyError::internal().context(e)) - } + fn user_id(&self) -> Result { + self + .0 + .user_id() + .map_err(|e| FlowyError::internal().context(e)) + } - fn token(&self) -> Result { - self.0.token().map_err(|e| FlowyError::internal().context(e)) - } + fn token(&self) -> Result { + self + .0 + .token() + .map_err(|e| FlowyError::internal().context(e)) + } } struct FolderRevisionWebSocket(Arc); impl RevisionWebSocket for FolderRevisionWebSocket { - fn send(&self, data: ClientRevisionWSData) -> BoxResultFuture<(), FlowyError> { - let bytes: Bytes = data.try_into().unwrap(); - let msg = WebSocketRawMessage { - channel: WSChannel::Folder, - data: bytes.to_vec(), - }; + fn send(&self, data: ClientRevisionWSData) -> BoxResultFuture<(), FlowyError> { + let bytes: Bytes = data.try_into().unwrap(); + let msg = WebSocketRawMessage { + channel: WSChannel::Folder, + data: bytes.to_vec(), + }; - let ws_conn = self.0.clone(); - Box::pin(async move { - match ws_conn.web_socket().await? { - None => {} - Some(sender) => { - sender.send(msg).map_err(internal_error)?; - } - } - Ok(()) - }) - } + let ws_conn = self.0.clone(); + Box::pin(async move { + match ws_conn.web_socket().await? { + None => {}, + Some(sender) => { + sender.send(msg).map_err(internal_error)?; + }, + } + Ok(()) + }) + } - fn subscribe_state_changed(&self) -> BoxFuture { - let ws_conn = self.0.clone(); - Box::pin(async move { ws_conn.subscribe_websocket_state().await }) - } + fn subscribe_state_changed(&self) -> BoxFuture { + let ws_conn = self.0.clone(); + Box::pin(async move { ws_conn.subscribe_websocket_state().await }) + } } struct FolderWSMessageReceiverImpl(Arc); impl WSMessageReceiver for FolderWSMessageReceiverImpl { - fn source(&self) -> WSChannel { - WSChannel::Folder - } - fn receive_message(&self, msg: WebSocketRawMessage) { - let handler = self.0.clone(); - tokio::spawn(async move { - handler.did_receive_ws_data(Bytes::from(msg.data)).await; - }); - } + fn source(&self) -> WSChannel { + WSChannel::Folder + } + fn receive_message(&self, msg: WebSocketRawMessage) { + let handler = self.0.clone(); + tokio::spawn(async move { + handler.did_receive_ws_data(Bytes::from(msg.data)).await; + }); + } } struct DocumentViewDataProcessor(Arc); impl ViewDataProcessor for DocumentViewDataProcessor { - fn create_view( - &self, - _user_id: &str, - view_id: &str, - layout: ViewLayoutTypePB, - view_data: Bytes, - ) -> FutureResult<(), FlowyError> { - // Only accept Document type - debug_assert_eq!(layout, ViewLayoutTypePB::Document); - let view_data = match String::from_utf8(view_data.to_vec()) { - Ok(content) => match make_transaction_from_document_content(&content) { - Ok(transaction) => transaction.to_bytes().unwrap_or(vec![]), - Err(_) => vec![], - }, - Err(_) => vec![], - }; + fn create_view( + &self, + _user_id: &str, + view_id: &str, + layout: ViewLayoutTypePB, + view_data: Bytes, + ) -> FutureResult<(), FlowyError> { + // Only accept Document type + debug_assert_eq!(layout, ViewLayoutTypePB::Document); + let view_data = match String::from_utf8(view_data.to_vec()) { + Ok(content) => match make_transaction_from_document_content(&content) { + Ok(transaction) => transaction.to_bytes().unwrap_or(vec![]), + Err(_) => vec![], + }, + Err(_) => vec![], + }; - let revision = Revision::initial_revision(view_id, Bytes::from(view_data)); - let view_id = view_id.to_string(); - let manager = self.0.clone(); + let revision = Revision::initial_revision(view_id, Bytes::from(view_data)); + let view_id = view_id.to_string(); + let manager = self.0.clone(); - FutureResult::new(async move { - manager.create_document(view_id, vec![revision]).await?; - Ok(()) - }) - } + FutureResult::new(async move { + manager.create_document(view_id, vec![revision]).await?; + Ok(()) + }) + } - fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> { - let manager = self.0.clone(); - let view_id = view_id.to_string(); - FutureResult::new(async move { - manager.close_document_editor(view_id).await?; - Ok(()) - }) - } + fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> { + let manager = self.0.clone(); + let view_id = view_id.to_string(); + FutureResult::new(async move { + manager.close_document_editor(view_id).await?; + Ok(()) + }) + } - fn get_view_data(&self, view: &ViewPB) -> FutureResult { - let view_id = view.id.clone(); - let manager = self.0.clone(); - FutureResult::new(async move { - let editor = manager.open_document_editor(view_id).await?; - let document_data = Bytes::from(editor.duplicate().await?); - Ok(document_data) - }) - } + fn get_view_data(&self, view: &ViewPB) -> FutureResult { + let view_id = view.id.clone(); + let manager = self.0.clone(); + FutureResult::new(async move { + let editor = manager.open_document_editor(view_id).await?; + let document_data = Bytes::from(editor.duplicate().await?); + Ok(document_data) + }) + } - fn create_default_view( - &self, - user_id: &str, - view_id: &str, - layout: ViewLayoutTypePB, - _data_format: ViewDataFormatPB, - ) -> FutureResult { - debug_assert_eq!(layout, ViewLayoutTypePB::Document); - let _user_id = user_id.to_string(); - let view_id = view_id.to_string(); - let manager = self.0.clone(); - let document_content = self.0.initial_document_content(); - FutureResult::new(async move { - let delta_data = Bytes::from(document_content); - let revision = Revision::initial_revision(&view_id, delta_data.clone()); - manager.create_document(view_id, vec![revision]).await?; - Ok(delta_data) - }) - } + fn create_default_view( + &self, + user_id: &str, + view_id: &str, + layout: ViewLayoutTypePB, + _data_format: ViewDataFormatPB, + ) -> FutureResult { + debug_assert_eq!(layout, ViewLayoutTypePB::Document); + let _user_id = user_id.to_string(); + let view_id = view_id.to_string(); + let manager = self.0.clone(); + let document_content = self.0.initial_document_content(); + FutureResult::new(async move { + let delta_data = Bytes::from(document_content); + let revision = Revision::initial_revision(&view_id, delta_data.clone()); + manager.create_document(view_id, vec![revision]).await?; + Ok(delta_data) + }) + } - fn create_view_with_data( - &self, - _user_id: &str, - _view_id: &str, - data: Vec, - layout: ViewLayoutTypePB, - ) -> FutureResult { - debug_assert_eq!(layout, ViewLayoutTypePB::Document); - FutureResult::new(async move { Ok(Bytes::from(data)) }) - } + fn create_view_with_data( + &self, + _user_id: &str, + _view_id: &str, + data: Vec, + layout: ViewLayoutTypePB, + ) -> FutureResult { + debug_assert_eq!(layout, ViewLayoutTypePB::Document); + FutureResult::new(async move { Ok(Bytes::from(data)) }) + } - fn data_types(&self) -> Vec { - vec![ViewDataFormatPB::DeltaFormat, ViewDataFormatPB::NodeFormat] - } + fn data_types(&self) -> Vec { + vec![ViewDataFormatPB::DeltaFormat, ViewDataFormatPB::NodeFormat] + } } struct GridViewDataProcessor(Arc); impl ViewDataProcessor for GridViewDataProcessor { - fn create_view( - &self, - _user_id: &str, - view_id: &str, - _layout: ViewLayoutTypePB, - delta_data: Bytes, - ) -> FutureResult<(), FlowyError> { - let revision = Revision::initial_revision(view_id, delta_data); - let view_id = view_id.to_string(); - let grid_manager = self.0.clone(); - FutureResult::new(async move { - grid_manager.create_database(view_id, vec![revision]).await?; - Ok(()) - }) - } + fn create_view( + &self, + _user_id: &str, + view_id: &str, + _layout: ViewLayoutTypePB, + delta_data: Bytes, + ) -> FutureResult<(), FlowyError> { + let revision = Revision::initial_revision(view_id, delta_data); + let view_id = view_id.to_string(); + let grid_manager = self.0.clone(); + FutureResult::new(async move { + grid_manager + .create_database(view_id, vec![revision]) + .await?; + Ok(()) + }) + } - fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> { - let grid_manager = self.0.clone(); - let view_id = view_id.to_string(); - FutureResult::new(async move { - grid_manager.close_database(view_id).await?; - Ok(()) - }) - } + fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> { + let grid_manager = self.0.clone(); + let view_id = view_id.to_string(); + FutureResult::new(async move { + grid_manager.close_database(view_id).await?; + Ok(()) + }) + } - fn get_view_data(&self, view: &ViewPB) -> FutureResult { - let grid_manager = self.0.clone(); - let view_id = view.id.clone(); - FutureResult::new(async move { - let editor = grid_manager.open_database(view_id).await?; - let delta_bytes = editor.duplicate_grid().await?; - Ok(delta_bytes.into()) - }) - } + fn get_view_data(&self, view: &ViewPB) -> FutureResult { + let grid_manager = self.0.clone(); + let view_id = view.id.clone(); + FutureResult::new(async move { + let editor = grid_manager.open_database(view_id).await?; + let delta_bytes = editor.duplicate_grid().await?; + Ok(delta_bytes.into()) + }) + } - fn create_default_view( - &self, - user_id: &str, - view_id: &str, - layout: ViewLayoutTypePB, - data_format: ViewDataFormatPB, - ) -> FutureResult { - debug_assert_eq!(data_format, ViewDataFormatPB::DatabaseFormat); - let (build_context, layout) = match layout { - ViewLayoutTypePB::Grid => (make_default_grid(), LayoutTypePB::Grid), - ViewLayoutTypePB::Board => (make_default_board(), LayoutTypePB::Board), - ViewLayoutTypePB::Calendar => (make_default_calendar(), LayoutTypePB::Calendar), - ViewLayoutTypePB::Document => { - return FutureResult::new(async move { - Err(FlowyError::internal().context(format!("Can't handle {:?} layout type", layout))) - }); - } - }; + fn create_default_view( + &self, + user_id: &str, + view_id: &str, + layout: ViewLayoutTypePB, + data_format: ViewDataFormatPB, + ) -> FutureResult { + debug_assert_eq!(data_format, ViewDataFormatPB::DatabaseFormat); + let (build_context, layout) = match layout { + ViewLayoutTypePB::Grid => (make_default_grid(), LayoutTypePB::Grid), + ViewLayoutTypePB::Board => (make_default_board(), LayoutTypePB::Board), + ViewLayoutTypePB::Calendar => (make_default_calendar(), LayoutTypePB::Calendar), + ViewLayoutTypePB::Document => { + return FutureResult::new(async move { + Err(FlowyError::internal().context(format!("Can't handle {:?} layout type", layout))) + }); + }, + }; - let user_id = user_id.to_string(); - let view_id = view_id.to_string(); - let grid_manager = self.0.clone(); - FutureResult::new(async move { - make_database_view_data(&user_id, &view_id, layout, grid_manager, build_context).await - }) - } + let user_id = user_id.to_string(); + let view_id = view_id.to_string(); + let grid_manager = self.0.clone(); + FutureResult::new(async move { + make_database_view_data(&user_id, &view_id, layout, grid_manager, build_context).await + }) + } - fn create_view_with_data( - &self, - user_id: &str, - view_id: &str, - data: Vec, - layout: ViewLayoutTypePB, - ) -> FutureResult { - let user_id = user_id.to_string(); - let view_id = view_id.to_string(); - let grid_manager = self.0.clone(); + fn create_view_with_data( + &self, + user_id: &str, + view_id: &str, + data: Vec, + layout: ViewLayoutTypePB, + ) -> FutureResult { + let user_id = user_id.to_string(); + let view_id = view_id.to_string(); + let grid_manager = self.0.clone(); - let layout = match layout { - ViewLayoutTypePB::Grid => LayoutTypePB::Grid, - ViewLayoutTypePB::Board => LayoutTypePB::Board, - ViewLayoutTypePB::Calendar => LayoutTypePB::Calendar, - ViewLayoutTypePB::Document => { - return FutureResult::new(async move { - Err(FlowyError::internal().context(format!("Can't handle {:?} layout type", layout))) - }); - } - }; + let layout = match layout { + ViewLayoutTypePB::Grid => LayoutTypePB::Grid, + ViewLayoutTypePB::Board => LayoutTypePB::Board, + ViewLayoutTypePB::Calendar => LayoutTypePB::Calendar, + ViewLayoutTypePB::Document => { + return FutureResult::new(async move { + Err(FlowyError::internal().context(format!("Can't handle {:?} layout type", layout))) + }); + }, + }; - FutureResult::new(async move { - let bytes = Bytes::from(data); - let build_context = BuildDatabaseContext::try_from(bytes)?; - make_database_view_data(&user_id, &view_id, layout, grid_manager, build_context).await - }) - } + FutureResult::new(async move { + let bytes = Bytes::from(data); + let build_context = BuildDatabaseContext::try_from(bytes)?; + make_database_view_data(&user_id, &view_id, layout, grid_manager, build_context).await + }) + } - fn data_types(&self) -> Vec { - vec![ViewDataFormatPB::DatabaseFormat] - } + fn data_types(&self) -> Vec { + vec![ViewDataFormatPB::DatabaseFormat] + } } diff --git a/frontend/rust-lib/flowy-core/src/deps_resolve/grid_deps.rs b/frontend/rust-lib/flowy-core/src/deps_resolve/grid_deps.rs index 5ff063b046..a2f2ff118f 100644 --- a/frontend/rust-lib/flowy-core/src/deps_resolve/grid_deps.rs +++ b/frontend/rust-lib/flowy-core/src/deps_resolve/grid_deps.rs @@ -18,76 +18,81 @@ use ws_model::ws_revision::ClientRevisionWSData; pub struct GridDepsResolver(); impl GridDepsResolver { - pub async fn resolve( - ws_conn: Arc, - user_session: Arc, - task_scheduler: Arc>, - ) -> Arc { - let user = Arc::new(GridUserImpl(user_session.clone())); - let rev_web_socket = Arc::new(GridRevisionWebSocket(ws_conn)); - let grid_manager = Arc::new(DatabaseManager::new( - user.clone(), - rev_web_socket, - task_scheduler, - Arc::new(GridDatabaseImpl(user_session)), - )); + pub async fn resolve( + ws_conn: Arc, + user_session: Arc, + task_scheduler: Arc>, + ) -> Arc { + let user = Arc::new(GridUserImpl(user_session.clone())); + let rev_web_socket = Arc::new(GridRevisionWebSocket(ws_conn)); + let grid_manager = Arc::new(DatabaseManager::new( + user.clone(), + rev_web_socket, + task_scheduler, + Arc::new(GridDatabaseImpl(user_session)), + )); - if let (Ok(user_id), Ok(token)) = (user.user_id(), user.token()) { - match grid_manager.initialize(&user_id, &token).await { - Ok(_) => {} - Err(e) => tracing::error!("Initialize grid manager failed: {}", e), - } - } - - grid_manager + if let (Ok(user_id), Ok(token)) = (user.user_id(), user.token()) { + match grid_manager.initialize(&user_id, &token).await { + Ok(_) => {}, + Err(e) => tracing::error!("Initialize grid manager failed: {}", e), + } } + + grid_manager + } } struct GridDatabaseImpl(Arc); impl GridDatabase for GridDatabaseImpl { - fn db_pool(&self) -> Result, FlowyError> { - self.0.db_pool().map_err(|e| FlowyError::internal().context(e)) - } + fn db_pool(&self) -> Result, FlowyError> { + self + .0 + .db_pool() + .map_err(|e| FlowyError::internal().context(e)) + } } struct GridUserImpl(Arc); impl DatabaseUser for GridUserImpl { - fn user_id(&self) -> Result { - self.0.user_id() - } + fn user_id(&self) -> Result { + self.0.user_id() + } - fn token(&self) -> Result { - self.0.token() - } + fn token(&self) -> Result { + self.0.token() + } - fn db_pool(&self) -> Result, FlowyError> { - self.0.db_pool() - } + fn db_pool(&self) -> Result, FlowyError> { + self.0.db_pool() + } } struct GridRevisionWebSocket(Arc); impl RevisionWebSocket for GridRevisionWebSocket { - fn send(&self, data: ClientRevisionWSData) -> BoxResultFuture<(), FlowyError> { - let bytes: Bytes = data.try_into().unwrap(); - let msg = WebSocketRawMessage { - channel: WSChannel::Database, - data: bytes.to_vec(), - }; + fn send(&self, data: ClientRevisionWSData) -> BoxResultFuture<(), FlowyError> { + let bytes: Bytes = data.try_into().unwrap(); + let msg = WebSocketRawMessage { + channel: WSChannel::Database, + data: bytes.to_vec(), + }; - let ws_conn = self.0.clone(); - Box::pin(async move { - match ws_conn.web_socket().await? { - None => {} - Some(sender) => { - sender.send(msg).map_err(|e| FlowyError::internal().context(e))?; - } - } - Ok(()) - }) - } + let ws_conn = self.0.clone(); + Box::pin(async move { + match ws_conn.web_socket().await? { + None => {}, + Some(sender) => { + sender + .send(msg) + .map_err(|e| FlowyError::internal().context(e))?; + }, + } + Ok(()) + }) + } - fn subscribe_state_changed(&self) -> BoxFuture { - let ws_conn = self.0.clone(); - Box::pin(async move { ws_conn.subscribe_websocket_state().await }) - } + fn subscribe_state_changed(&self) -> BoxFuture { + let ws_conn = self.0.clone(); + Box::pin(async move { ws_conn.subscribe_websocket_state().await }) + } } diff --git a/frontend/rust-lib/flowy-core/src/deps_resolve/user_deps.rs b/frontend/rust-lib/flowy-core/src/deps_resolve/user_deps.rs index 7115c5a2bb..e4a82ee75c 100644 --- a/frontend/rust-lib/flowy-core/src/deps_resolve/user_deps.rs +++ b/frontend/rust-lib/flowy-core/src/deps_resolve/user_deps.rs @@ -6,13 +6,13 @@ use std::sync::Arc; pub struct UserDepsResolver(); impl UserDepsResolver { - pub fn resolve( - local_server: &Option>, - server_config: &ClientServerConfiguration, - ) -> Arc { - match local_server.clone() { - None => Arc::new(UserHttpCloudService::new(server_config)), - Some(local_server) => local_server, - } + pub fn resolve( + local_server: &Option>, + server_config: &ClientServerConfiguration, + ) -> Arc { + match local_server.clone() { + None => Arc::new(UserHttpCloudService::new(server_config)), + Some(local_server) => local_server, } + } } diff --git a/frontend/rust-lib/flowy-core/src/lib.rs b/frontend/rust-lib/flowy-core/src/lib.rs index 063b67bff9..cde5a8cc65 100644 --- a/frontend/rust-lib/flowy-core/src/lib.rs +++ b/frontend/rust-lib/flowy-core/src/lib.rs @@ -22,11 +22,11 @@ use module::make_plugins; pub use module::*; use std::time::Duration; use std::{ - fmt, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, + fmt, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, }; use tokio::sync::{broadcast, RwLock}; use user_model::UserProfile; @@ -35,316 +35,331 @@ static INIT_LOG: AtomicBool = AtomicBool::new(false); #[derive(Clone)] pub struct AppFlowyCoreConfig { - /// Different `AppFlowyCoreConfig` instance should have different name - name: String, - /// Panics if the `root` path is not existing - storage_path: String, - log_filter: String, - server_config: ClientServerConfiguration, - pub document: DocumentConfig, + /// Different `AppFlowyCoreConfig` instance should have different name + name: String, + /// Panics if the `root` path is not existing + storage_path: String, + log_filter: String, + server_config: ClientServerConfiguration, + pub document: DocumentConfig, } impl fmt::Debug for AppFlowyCoreConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("AppFlowyCoreConfig") - .field("storage_path", &self.storage_path) - .field("server-config", &self.server_config) - .field("document-config", &self.document) - .finish() - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AppFlowyCoreConfig") + .field("storage_path", &self.storage_path) + .field("server-config", &self.server_config) + .field("document-config", &self.document) + .finish() + } } impl AppFlowyCoreConfig { - pub fn new(root: &str, name: String, server_config: ClientServerConfiguration) -> Self { - AppFlowyCoreConfig { - name, - storage_path: root.to_owned(), - log_filter: create_log_filter("info".to_owned(), vec![]), - server_config, - document: DocumentConfig::default(), - } + pub fn new(root: &str, name: String, server_config: ClientServerConfiguration) -> Self { + AppFlowyCoreConfig { + name, + storage_path: root.to_owned(), + log_filter: create_log_filter("info".to_owned(), vec![]), + server_config, + document: DocumentConfig::default(), } + } - pub fn with_document_version(mut self, version: DocumentVersionPB) -> Self { - self.document.version = version; - self - } + pub fn with_document_version(mut self, version: DocumentVersionPB) -> Self { + self.document.version = version; + self + } - pub fn log_filter(mut self, level: &str, with_crates: Vec) -> Self { - self.log_filter = create_log_filter(level.to_owned(), with_crates); - self - } + pub fn log_filter(mut self, level: &str, with_crates: Vec) -> Self { + self.log_filter = create_log_filter(level.to_owned(), with_crates); + self + } } fn create_log_filter(level: String, with_crates: Vec) -> String { - let level = std::env::var("RUST_LOG").unwrap_or(level); - let mut filters = with_crates - .into_iter() - .map(|crate_name| format!("{}={}", crate_name, level)) - .collect::>(); - filters.push(format!("flowy_core={}", level)); - filters.push(format!("flowy_folder={}", level)); - filters.push(format!("flowy_user={}", level)); - filters.push(format!("flowy_document={}", level)); - filters.push(format!("flowy_database={}", level)); - filters.push(format!("flowy_sync={}", "info")); - filters.push(format!("flowy_client_sync={}", "info")); - filters.push(format!("flowy_notification={}", "info")); - filters.push(format!("lib_ot={}", level)); - filters.push(format!("lib_ws={}", level)); - filters.push(format!("lib_infra={}", level)); - filters.push(format!("flowy_sync={}", level)); - filters.push(format!("flowy_revision={}", level)); - filters.push(format!("flowy_revision_persistence={}", level)); - filters.push(format!("flowy_task={}", level)); - // filters.push(format!("lib_dispatch={}", level)); + let level = std::env::var("RUST_LOG").unwrap_or(level); + let mut filters = with_crates + .into_iter() + .map(|crate_name| format!("{}={}", crate_name, level)) + .collect::>(); + filters.push(format!("flowy_core={}", level)); + filters.push(format!("flowy_folder={}", level)); + filters.push(format!("flowy_user={}", level)); + filters.push(format!("flowy_document={}", level)); + filters.push(format!("flowy_database={}", level)); + filters.push(format!("flowy_sync={}", "info")); + filters.push(format!("flowy_client_sync={}", "info")); + filters.push(format!("flowy_notification={}", "info")); + filters.push(format!("lib_ot={}", level)); + filters.push(format!("lib_ws={}", level)); + filters.push(format!("lib_infra={}", level)); + filters.push(format!("flowy_sync={}", level)); + filters.push(format!("flowy_revision={}", level)); + filters.push(format!("flowy_revision_persistence={}", level)); + filters.push(format!("flowy_task={}", level)); + // filters.push(format!("lib_dispatch={}", level)); - filters.push(format!("dart_ffi={}", "info")); - filters.push(format!("flowy_sqlite={}", "info")); - filters.push(format!("flowy_net={}", "info")); - filters.join(",") + filters.push(format!("dart_ffi={}", "info")); + filters.push(format!("flowy_sqlite={}", "info")); + filters.push(format!("flowy_net={}", "info")); + filters.join(",") } #[derive(Clone)] pub struct AppFlowyCore { - #[allow(dead_code)] - pub config: AppFlowyCoreConfig, - pub user_session: Arc, - pub document_manager: Arc, - pub folder_manager: Arc, - pub grid_manager: Arc, - pub event_dispatcher: Arc, - pub ws_conn: Arc, - pub local_server: Option>, - pub task_dispatcher: Arc>, + #[allow(dead_code)] + pub config: AppFlowyCoreConfig, + pub user_session: Arc, + pub document_manager: Arc, + pub folder_manager: Arc, + pub grid_manager: Arc, + pub event_dispatcher: Arc, + pub ws_conn: Arc, + pub local_server: Option>, + pub task_dispatcher: Arc>, } impl AppFlowyCore { - pub fn new(config: AppFlowyCoreConfig) -> Self { - init_log(&config); - init_kv(&config.storage_path); - tracing::debug!("🔥 {:?}", config); - let runtime = tokio_default_runtime().unwrap(); - let task_scheduler = TaskDispatcher::new(Duration::from_secs(2)); - let task_dispatcher = Arc::new(RwLock::new(task_scheduler)); - runtime.spawn(TaskRunner::run(task_dispatcher.clone())); + pub fn new(config: AppFlowyCoreConfig) -> Self { + init_log(&config); + init_kv(&config.storage_path); + tracing::debug!("🔥 {:?}", config); + let runtime = tokio_default_runtime().unwrap(); + let task_scheduler = TaskDispatcher::new(Duration::from_secs(2)); + let task_dispatcher = Arc::new(RwLock::new(task_scheduler)); + runtime.spawn(TaskRunner::run(task_dispatcher.clone())); - let (local_server, ws_conn) = mk_local_server(&config.server_config); - let (user_session, document_manager, folder_manager, local_server, grid_manager) = runtime.block_on(async { - let user_session = mk_user_session(&config, &local_server, &config.server_config); - let document_manager = DocumentDepsResolver::resolve( - local_server.clone(), - ws_conn.clone(), - user_session.clone(), - &config.server_config, - &config.document, - ); + let (local_server, ws_conn) = mk_local_server(&config.server_config); + let (user_session, document_manager, folder_manager, local_server, grid_manager) = runtime + .block_on(async { + let user_session = mk_user_session(&config, &local_server, &config.server_config); + let document_manager = DocumentDepsResolver::resolve( + local_server.clone(), + ws_conn.clone(), + user_session.clone(), + &config.server_config, + &config.document, + ); - let grid_manager = - GridDepsResolver::resolve(ws_conn.clone(), user_session.clone(), task_dispatcher.clone()).await; + let grid_manager = GridDepsResolver::resolve( + ws_conn.clone(), + user_session.clone(), + task_dispatcher.clone(), + ) + .await; - let folder_manager = FolderDepsResolver::resolve( - local_server.clone(), - user_session.clone(), - &config.server_config, - &ws_conn, - &document_manager, - &grid_manager, - ) - .await; + let folder_manager = FolderDepsResolver::resolve( + local_server.clone(), + user_session.clone(), + &config.server_config, + &ws_conn, + &document_manager, + &grid_manager, + ) + .await; - if let Some(local_server) = local_server.as_ref() { - local_server.run(); - } - ws_conn.init().await; - ( - user_session, - document_manager, - folder_manager, - local_server, - grid_manager, - ) - }); - - let user_status_listener = UserStatusListener { - document_manager: document_manager.clone(), - folder_manager: folder_manager.clone(), - grid_manager: grid_manager.clone(), - ws_conn: ws_conn.clone(), - config: config.clone(), - }; - let user_status_callback = UserStatusCallbackImpl { - listener: Arc::new(user_status_listener), - }; - let cloned_user_session = user_session.clone(); - runtime.block_on(async move { - cloned_user_session.clone().init(user_status_callback).await; - }); - - let event_dispatcher = Arc::new(AFPluginDispatcher::construct(runtime, || { - make_plugins( - &ws_conn, - &folder_manager, - &grid_manager, - &user_session, - &document_manager, - ) - })); - _start_listening(&event_dispatcher, &ws_conn, &folder_manager); - - Self { - config, - user_session, - document_manager, - folder_manager, - grid_manager, - event_dispatcher, - ws_conn, - local_server, - task_dispatcher, + if let Some(local_server) = local_server.as_ref() { + local_server.run(); } - } + ws_conn.init().await; + ( + user_session, + document_manager, + folder_manager, + local_server, + grid_manager, + ) + }); - pub fn dispatcher(&self) -> Arc { - self.event_dispatcher.clone() + let user_status_listener = UserStatusListener { + document_manager: document_manager.clone(), + folder_manager: folder_manager.clone(), + grid_manager: grid_manager.clone(), + ws_conn: ws_conn.clone(), + config: config.clone(), + }; + let user_status_callback = UserStatusCallbackImpl { + listener: Arc::new(user_status_listener), + }; + let cloned_user_session = user_session.clone(); + runtime.block_on(async move { + cloned_user_session.clone().init(user_status_callback).await; + }); + + let event_dispatcher = Arc::new(AFPluginDispatcher::construct(runtime, || { + make_plugins( + &ws_conn, + &folder_manager, + &grid_manager, + &user_session, + &document_manager, + ) + })); + _start_listening(&event_dispatcher, &ws_conn, &folder_manager); + + Self { + config, + user_session, + document_manager, + folder_manager, + grid_manager, + event_dispatcher, + ws_conn, + local_server, + task_dispatcher, } + } + + pub fn dispatcher(&self) -> Arc { + self.event_dispatcher.clone() + } } fn _start_listening( - event_dispatcher: &AFPluginDispatcher, - ws_conn: &Arc, - folder_manager: &Arc, + event_dispatcher: &AFPluginDispatcher, + ws_conn: &Arc, + folder_manager: &Arc, ) { - let subscribe_network_type = ws_conn.subscribe_network_ty(); - let folder_manager = folder_manager.clone(); - let cloned_folder_manager = folder_manager; - let ws_conn = ws_conn.clone(); + let subscribe_network_type = ws_conn.subscribe_network_ty(); + let folder_manager = folder_manager.clone(); + let cloned_folder_manager = folder_manager; + let ws_conn = ws_conn.clone(); - event_dispatcher.spawn(async move { - listen_on_websocket(ws_conn.clone()); - }); + event_dispatcher.spawn(async move { + listen_on_websocket(ws_conn.clone()); + }); - event_dispatcher.spawn(async move { - _listen_network_status(subscribe_network_type, cloned_folder_manager).await; - }); + event_dispatcher.spawn(async move { + _listen_network_status(subscribe_network_type, cloned_folder_manager).await; + }); } fn mk_local_server( - server_config: &ClientServerConfiguration, + server_config: &ClientServerConfiguration, ) -> (Option>, Arc) { - let ws_addr = server_config.ws_addr(); - if cfg!(feature = "http_sync") { - let ws_conn = Arc::new(FlowyWebSocketConnect::new(ws_addr)); - (None, ws_conn) - } else { - let context = flowy_net::local_server::build_server(server_config); - let local_ws = Arc::new(context.local_ws); - let ws_conn = Arc::new(FlowyWebSocketConnect::from_local(ws_addr, local_ws)); - (Some(Arc::new(context.local_server)), ws_conn) - } + let ws_addr = server_config.ws_addr(); + if cfg!(feature = "http_sync") { + let ws_conn = Arc::new(FlowyWebSocketConnect::new(ws_addr)); + (None, ws_conn) + } else { + let context = flowy_net::local_server::build_server(server_config); + let local_ws = Arc::new(context.local_ws); + let ws_conn = Arc::new(FlowyWebSocketConnect::from_local(ws_addr, local_ws)); + (Some(Arc::new(context.local_server)), ws_conn) + } } -async fn _listen_network_status(mut subscribe: broadcast::Receiver, _core: Arc) { - while let Ok(_new_type) = subscribe.recv().await { - // core.network_state_changed(new_type); - } +async fn _listen_network_status( + mut subscribe: broadcast::Receiver, + _core: Arc, +) { + while let Ok(_new_type) = subscribe.recv().await { + // core.network_state_changed(new_type); + } } fn init_kv(root: &str) { - match flowy_sqlite::kv::KV::init(root) { - Ok(_) => {} - Err(e) => tracing::error!("Init kv store failed: {}", e), - } + match flowy_sqlite::kv::KV::init(root) { + Ok(_) => {}, + Err(e) => tracing::error!("Init kv store failed: {}", e), + } } fn init_log(config: &AppFlowyCoreConfig) { - if !INIT_LOG.load(Ordering::SeqCst) { - INIT_LOG.store(true, Ordering::SeqCst); + if !INIT_LOG.load(Ordering::SeqCst) { + INIT_LOG.store(true, Ordering::SeqCst); - let _ = lib_log::Builder::new("AppFlowy-Client", &config.storage_path) - .env_filter(&config.log_filter) - .build(); - } + let _ = lib_log::Builder::new("AppFlowy-Client", &config.storage_path) + .env_filter(&config.log_filter) + .build(); + } } fn mk_user_session( - config: &AppFlowyCoreConfig, - local_server: &Option>, - server_config: &ClientServerConfiguration, + config: &AppFlowyCoreConfig, + local_server: &Option>, + server_config: &ClientServerConfiguration, ) -> Arc { - let user_config = UserSessionConfig::new(&config.name, &config.storage_path); - let cloud_service = UserDepsResolver::resolve(local_server, server_config); - Arc::new(UserSession::new(user_config, cloud_service)) + let user_config = UserSessionConfig::new(&config.name, &config.storage_path); + let cloud_service = UserDepsResolver::resolve(local_server, server_config); + Arc::new(UserSession::new(user_config, cloud_service)) } struct UserStatusListener { - document_manager: Arc, - folder_manager: Arc, - grid_manager: Arc, - ws_conn: Arc, - config: AppFlowyCoreConfig, + document_manager: Arc, + folder_manager: Arc, + grid_manager: Arc, + ws_conn: Arc, + config: AppFlowyCoreConfig, } impl UserStatusListener { - async fn did_sign_in(&self, token: &str, user_id: &str) -> FlowyResult<()> { - self.folder_manager.initialize(user_id, token).await?; - self.document_manager.initialize(user_id).await?; - self.grid_manager.initialize(user_id, token).await?; - self.ws_conn.start(token.to_owned(), user_id.to_owned()).await?; - Ok(()) - } + async fn did_sign_in(&self, token: &str, user_id: &str) -> FlowyResult<()> { + self.folder_manager.initialize(user_id, token).await?; + self.document_manager.initialize(user_id).await?; + self.grid_manager.initialize(user_id, token).await?; + self + .ws_conn + .start(token.to_owned(), user_id.to_owned()) + .await?; + Ok(()) + } - async fn did_sign_up(&self, user_profile: &UserProfile) -> FlowyResult<()> { - let view_data_type = match self.config.document.version { - DocumentVersionPB::V0 => ViewDataFormatPB::DeltaFormat, - DocumentVersionPB::V1 => ViewDataFormatPB::NodeFormat, - }; - self.folder_manager - .initialize_with_new_user(&user_profile.id, &user_profile.token, view_data_type) - .await?; - self.document_manager - .initialize_with_new_user(&user_profile.id, &user_profile.token) - .await?; + async fn did_sign_up(&self, user_profile: &UserProfile) -> FlowyResult<()> { + let view_data_type = match self.config.document.version { + DocumentVersionPB::V0 => ViewDataFormatPB::DeltaFormat, + DocumentVersionPB::V1 => ViewDataFormatPB::NodeFormat, + }; + self + .folder_manager + .initialize_with_new_user(&user_profile.id, &user_profile.token, view_data_type) + .await?; + self + .document_manager + .initialize_with_new_user(&user_profile.id, &user_profile.token) + .await?; - self.grid_manager - .initialize_with_new_user(&user_profile.id, &user_profile.token) - .await?; + self + .grid_manager + .initialize_with_new_user(&user_profile.id, &user_profile.token) + .await?; - self.ws_conn - .start(user_profile.token.clone(), user_profile.id.clone()) - .await?; - Ok(()) - } + self + .ws_conn + .start(user_profile.token.clone(), user_profile.id.clone()) + .await?; + Ok(()) + } - async fn did_expired(&self, _token: &str, user_id: &str) -> FlowyResult<()> { - self.folder_manager.clear(user_id).await; - self.ws_conn.stop().await; - Ok(()) - } + async fn did_expired(&self, _token: &str, user_id: &str) -> FlowyResult<()> { + self.folder_manager.clear(user_id).await; + self.ws_conn.stop().await; + Ok(()) + } } struct UserStatusCallbackImpl { - listener: Arc, + listener: Arc, } impl UserStatusCallback for UserStatusCallbackImpl { - fn did_sign_in(&self, token: &str, user_id: &str) -> Fut> { - let listener = self.listener.clone(); - let token = token.to_owned(); - let user_id = user_id.to_owned(); - to_fut(async move { listener.did_sign_in(&token, &user_id).await }) - } + fn did_sign_in(&self, token: &str, user_id: &str) -> Fut> { + let listener = self.listener.clone(); + let token = token.to_owned(); + let user_id = user_id.to_owned(); + to_fut(async move { listener.did_sign_in(&token, &user_id).await }) + } - fn did_sign_up(&self, user_profile: &UserProfile) -> Fut> { - let listener = self.listener.clone(); - let user_profile = user_profile.clone(); - to_fut(async move { listener.did_sign_up(&user_profile).await }) - } + fn did_sign_up(&self, user_profile: &UserProfile) -> Fut> { + let listener = self.listener.clone(); + let user_profile = user_profile.clone(); + to_fut(async move { listener.did_sign_up(&user_profile).await }) + } - fn did_expired(&self, token: &str, user_id: &str) -> Fut> { - let listener = self.listener.clone(); - let token = token.to_owned(); - let user_id = user_id.to_owned(); - to_fut(async move { listener.did_expired(&token, &user_id).await }) - } + fn did_expired(&self, token: &str, user_id: &str) -> Fut> { + let listener = self.listener.clone(); + let token = token.to_owned(); + let user_id = user_id.to_owned(); + to_fut(async move { listener.did_expired(&token, &user_id).await }) + } } diff --git a/frontend/rust-lib/flowy-core/src/module.rs b/frontend/rust-lib/flowy-core/src/module.rs index 65543cb953..d9a3d117bc 100644 --- a/frontend/rust-lib/flowy-core/src/module.rs +++ b/frontend/rust-lib/flowy-core/src/module.rs @@ -7,16 +7,22 @@ use lib_dispatch::prelude::AFPlugin; use std::sync::Arc; pub fn make_plugins( - ws_conn: &Arc, - folder_manager: &Arc, - grid_manager: &Arc, - user_session: &Arc, - document_manager: &Arc, + ws_conn: &Arc, + folder_manager: &Arc, + grid_manager: &Arc, + user_session: &Arc, + document_manager: &Arc, ) -> Vec { - let user_plugin = flowy_user::event_map::init(user_session.clone()); - let folder_plugin = flowy_folder::event_map::init(folder_manager.clone()); - let network_plugin = flowy_net::event_map::init(ws_conn.clone()); - let grid_plugin = flowy_database::event_map::init(grid_manager.clone()); - let document_plugin = flowy_document::event_map::init(document_manager.clone()); - vec![user_plugin, folder_plugin, network_plugin, grid_plugin, document_plugin] + let user_plugin = flowy_user::event_map::init(user_session.clone()); + let folder_plugin = flowy_folder::event_map::init(folder_manager.clone()); + let network_plugin = flowy_net::event_map::init(ws_conn.clone()); + let grid_plugin = flowy_database::event_map::init(grid_manager.clone()); + let document_plugin = flowy_document::event_map::init(document_manager.clone()); + vec![ + user_plugin, + folder_plugin, + network_plugin, + grid_plugin, + document_plugin, + ] } diff --git a/frontend/rust-lib/flowy-database/build.rs b/frontend/rust-lib/flowy-database/build.rs index 508b370b87..06388d2a02 100644 --- a/frontend/rust-lib/flowy-database/build.rs +++ b/frontend/rust-lib/flowy-database/build.rs @@ -1,10 +1,10 @@ fn main() { - let crate_name = env!("CARGO_PKG_NAME"); - flowy_codegen::protobuf_file::gen(crate_name); + let crate_name = env!("CARGO_PKG_NAME"); + flowy_codegen::protobuf_file::gen(crate_name); - #[cfg(feature = "dart")] - flowy_codegen::dart_event::gen(crate_name); + #[cfg(feature = "dart")] + flowy_codegen::dart_event::gen(crate_name); - #[cfg(feature = "ts")] - flowy_codegen::ts_event::gen(crate_name); + #[cfg(feature = "ts")] + flowy_codegen::ts_event::gen(crate_name); } diff --git a/frontend/rust-lib/flowy-database/src/entities/cell_entities.rs b/frontend/rust-lib/flowy-database/src/entities/cell_entities.rs index 3fdc6d67f4..a0582ead31 100644 --- a/frontend/rust-lib/flowy-database/src/entities/cell_entities.rs +++ b/frontend/rust-lib/flowy-database/src/entities/cell_entities.rs @@ -7,166 +7,169 @@ use std::collections::HashMap; #[derive(ProtoBuf, Default)] pub struct CreateSelectOptionPayloadPB { - #[pb(index = 1)] - pub field_id: String, + #[pb(index = 1)] + pub field_id: String, - #[pb(index = 2)] - pub database_id: String, + #[pb(index = 2)] + pub database_id: String, - #[pb(index = 3)] - pub option_name: String, + #[pb(index = 3)] + pub option_name: String, } pub struct CreateSelectOptionParams { - pub field_id: String, - pub database_id: String, - pub option_name: String, + pub field_id: String, + pub database_id: String, + pub option_name: String, } impl TryInto for CreateSelectOptionPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let option_name = NotEmptyStr::parse(self.option_name).map_err(|_| ErrorCode::SelectOptionNameIsEmpty)?; - let database_id = NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; - let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; - Ok(CreateSelectOptionParams { - field_id: field_id.0, - option_name: option_name.0, - database_id: database_id.0, - }) - } + fn try_into(self) -> Result { + let option_name = + NotEmptyStr::parse(self.option_name).map_err(|_| ErrorCode::SelectOptionNameIsEmpty)?; + let database_id = + NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; + let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; + Ok(CreateSelectOptionParams { + field_id: field_id.0, + option_name: option_name.0, + database_id: database_id.0, + }) + } } #[derive(Debug, Clone, Default, ProtoBuf)] pub struct CellIdPB { - #[pb(index = 1)] - pub database_id: String, + #[pb(index = 1)] + pub database_id: String, - #[pb(index = 2)] - pub field_id: String, + #[pb(index = 2)] + pub field_id: String, - #[pb(index = 3)] - pub row_id: String, + #[pb(index = 3)] + pub row_id: String, } /// Represents as the cell identifier. It's used to locate the cell in corresponding /// view's row with the field id. pub struct CellIdParams { - pub database_id: String, - pub field_id: String, - pub row_id: String, + pub database_id: String, + pub field_id: String, + pub row_id: String, } impl TryInto for CellIdPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let database_id = NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; - let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; - let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; - Ok(CellIdParams { - database_id: database_id.0, - field_id: field_id.0, - row_id: row_id.0, - }) - } + fn try_into(self) -> Result { + let database_id = + NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; + let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; + let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; + Ok(CellIdParams { + database_id: database_id.0, + field_id: field_id.0, + row_id: row_id.0, + }) + } } /// Represents as the data of the cell. #[derive(Debug, Default, ProtoBuf)] pub struct CellPB { - #[pb(index = 1)] - pub field_id: String, + #[pb(index = 1)] + pub field_id: String, - #[pb(index = 2)] - pub row_id: String, + #[pb(index = 2)] + pub row_id: String, - /// Encoded the data using the helper struct `CellProtobufBlob`. - /// Check out the `CellProtobufBlob` for more information. - #[pb(index = 3)] - pub data: Vec, + /// Encoded the data using the helper struct `CellProtobufBlob`. + /// Check out the `CellProtobufBlob` for more information. + #[pb(index = 3)] + pub data: Vec, - /// the field_type will be None if the field with field_id is not found - #[pb(index = 4, one_of)] - pub field_type: Option, + /// the field_type will be None if the field with field_id is not found + #[pb(index = 4, one_of)] + pub field_type: Option, } impl CellPB { - pub fn new(field_id: &str, row_id: &str, field_type: FieldType, data: Vec) -> Self { - Self { - field_id: field_id.to_owned(), - row_id: row_id.to_string(), - data, - field_type: Some(field_type), - } + pub fn new(field_id: &str, row_id: &str, field_type: FieldType, data: Vec) -> Self { + Self { + field_id: field_id.to_owned(), + row_id: row_id.to_string(), + data, + field_type: Some(field_type), } + } - pub fn empty(field_id: &str, row_id: &str) -> Self { - Self { - field_id: field_id.to_owned(), - row_id: row_id.to_owned(), - data: vec![], - field_type: None, - } + pub fn empty(field_id: &str, row_id: &str) -> Self { + Self { + field_id: field_id.to_owned(), + row_id: row_id.to_owned(), + data: vec![], + field_type: None, } + } } #[derive(Debug, Default, ProtoBuf)] pub struct RepeatedCellPB { - #[pb(index = 1)] - pub items: Vec, + #[pb(index = 1)] + pub items: Vec, } impl std::ops::Deref for RepeatedCellPB { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.items - } + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.items + } } impl std::ops::DerefMut for RepeatedCellPB { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.items - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.items + } } impl std::convert::From> for RepeatedCellPB { - fn from(items: Vec) -> Self { - Self { items } - } + fn from(items: Vec) -> Self { + Self { items } + } } /// #[derive(Debug, Clone, Default, ProtoBuf)] pub struct CellChangesetPB { - #[pb(index = 1)] - pub database_id: String, + #[pb(index = 1)] + pub database_id: String, - #[pb(index = 2)] - pub row_id: String, + #[pb(index = 2)] + pub row_id: String, - #[pb(index = 3)] - pub field_id: String, + #[pb(index = 3)] + pub field_id: String, - #[pb(index = 4)] - pub type_cell_data: String, + #[pb(index = 4)] + pub type_cell_data: String, } impl std::convert::From for RowChangeset { - fn from(changeset: CellChangesetPB) -> Self { - let mut cell_by_field_id = HashMap::with_capacity(1); - let field_id = changeset.field_id; - let cell_rev = CellRevision { - type_cell_data: changeset.type_cell_data, - }; - cell_by_field_id.insert(field_id, cell_rev); + fn from(changeset: CellChangesetPB) -> Self { + let mut cell_by_field_id = HashMap::with_capacity(1); + let field_id = changeset.field_id; + let cell_rev = CellRevision { + type_cell_data: changeset.type_cell_data, + }; + cell_by_field_id.insert(field_id, cell_rev); - RowChangeset { - row_id: changeset.row_id, - height: None, - visibility: None, - cell_by_field_id, - } + RowChangeset { + row_id: changeset.row_id, + height: None, + visibility: None, + cell_by_field_id, } + } } diff --git a/frontend/rust-lib/flowy-database/src/entities/field_entities.rs b/frontend/rust-lib/flowy-database/src/entities/field_entities.rs index ef9637af9e..e48922e82c 100644 --- a/frontend/rust-lib/flowy-database/src/entities/field_entities.rs +++ b/frontend/rust-lib/flowy-database/src/entities/field_entities.rs @@ -10,372 +10,379 @@ use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter, EnumString}; /// [FieldPB] defines a Field's attributes. Such as the name, field_type, and width. etc. #[derive(Debug, Clone, Default, ProtoBuf)] pub struct FieldPB { - #[pb(index = 1)] - pub id: String, + #[pb(index = 1)] + pub id: String, - #[pb(index = 2)] - pub name: String, + #[pb(index = 2)] + pub name: String, - #[pb(index = 3)] - pub desc: String, + #[pb(index = 3)] + pub desc: String, - #[pb(index = 4)] - pub field_type: FieldType, + #[pb(index = 4)] + pub field_type: FieldType, - #[pb(index = 5)] - pub frozen: bool, + #[pb(index = 5)] + pub frozen: bool, - #[pb(index = 6)] - pub visibility: bool, + #[pb(index = 6)] + pub visibility: bool, - #[pb(index = 7)] - pub width: i32, + #[pb(index = 7)] + pub width: i32, - #[pb(index = 8)] - pub is_primary: bool, + #[pb(index = 8)] + pub is_primary: bool, } impl std::convert::From for FieldPB { - fn from(field_rev: FieldRevision) -> Self { - Self { - id: field_rev.id, - name: field_rev.name, - desc: field_rev.desc, - field_type: field_rev.ty.into(), - frozen: field_rev.frozen, - visibility: field_rev.visibility, - width: field_rev.width, - is_primary: field_rev.is_primary, - } + fn from(field_rev: FieldRevision) -> Self { + Self { + id: field_rev.id, + name: field_rev.name, + desc: field_rev.desc, + field_type: field_rev.ty.into(), + frozen: field_rev.frozen, + visibility: field_rev.visibility, + width: field_rev.width, + is_primary: field_rev.is_primary, } + } } impl std::convert::From> for FieldPB { - fn from(field_rev: Arc) -> Self { - let field_rev = field_rev.as_ref().clone(); - FieldPB::from(field_rev) - } + fn from(field_rev: Arc) -> Self { + let field_rev = field_rev.as_ref().clone(); + FieldPB::from(field_rev) + } } /// [FieldIdPB] id of the [Field] #[derive(Debug, Clone, Default, ProtoBuf)] pub struct FieldIdPB { - #[pb(index = 1)] - pub field_id: String, + #[pb(index = 1)] + pub field_id: String, } impl std::convert::From<&str> for FieldIdPB { - fn from(s: &str) -> Self { - FieldIdPB { field_id: s.to_owned() } + fn from(s: &str) -> Self { + FieldIdPB { + field_id: s.to_owned(), } + } } impl std::convert::From for FieldIdPB { - fn from(s: String) -> Self { - FieldIdPB { field_id: s } - } + fn from(s: String) -> Self { + FieldIdPB { field_id: s } + } } impl std::convert::From<&Arc> for FieldIdPB { - fn from(field_rev: &Arc) -> Self { - Self { - field_id: field_rev.id.clone(), - } + fn from(field_rev: &Arc) -> Self { + Self { + field_id: field_rev.id.clone(), } + } } #[derive(Debug, Clone, Default, ProtoBuf)] pub struct DatabaseFieldChangesetPB { - #[pb(index = 1)] - pub database_id: String, + #[pb(index = 1)] + pub database_id: String, - #[pb(index = 2)] - pub inserted_fields: Vec, + #[pb(index = 2)] + pub inserted_fields: Vec, - #[pb(index = 3)] - pub deleted_fields: Vec, + #[pb(index = 3)] + pub deleted_fields: Vec, - #[pb(index = 4)] - pub updated_fields: Vec, + #[pb(index = 4)] + pub updated_fields: Vec, } impl DatabaseFieldChangesetPB { - pub fn insert(database_id: &str, inserted_fields: Vec) -> Self { - Self { - database_id: database_id.to_owned(), - inserted_fields, - deleted_fields: vec![], - updated_fields: vec![], - } + pub fn insert(database_id: &str, inserted_fields: Vec) -> Self { + Self { + database_id: database_id.to_owned(), + inserted_fields, + deleted_fields: vec![], + updated_fields: vec![], } + } - pub fn delete(database_id: &str, deleted_fields: Vec) -> Self { - Self { - database_id: database_id.to_string(), - inserted_fields: vec![], - deleted_fields, - updated_fields: vec![], - } + pub fn delete(database_id: &str, deleted_fields: Vec) -> Self { + Self { + database_id: database_id.to_string(), + inserted_fields: vec![], + deleted_fields, + updated_fields: vec![], } + } - pub fn update(database_id: &str, updated_fields: Vec) -> Self { - Self { - database_id: database_id.to_string(), - inserted_fields: vec![], - deleted_fields: vec![], - updated_fields, - } + pub fn update(database_id: &str, updated_fields: Vec) -> Self { + Self { + database_id: database_id.to_string(), + inserted_fields: vec![], + deleted_fields: vec![], + updated_fields, } + } } #[derive(Debug, Clone, Default, ProtoBuf)] pub struct IndexFieldPB { - #[pb(index = 1)] - pub field: FieldPB, + #[pb(index = 1)] + pub field: FieldPB, - #[pb(index = 2)] - pub index: i32, + #[pb(index = 2)] + pub index: i32, } impl IndexFieldPB { - pub fn from_field_rev(field_rev: &Arc, index: usize) -> Self { - Self { - field: FieldPB::from(field_rev.as_ref().clone()), - index: index as i32, - } + pub fn from_field_rev(field_rev: &Arc, index: usize) -> Self { + Self { + field: FieldPB::from(field_rev.as_ref().clone()), + index: index as i32, } + } } #[derive(Debug, Default, ProtoBuf)] pub struct CreateFieldPayloadPB { - #[pb(index = 1)] - pub database_id: String, + #[pb(index = 1)] + pub database_id: String, - #[pb(index = 2)] - pub field_type: FieldType, + #[pb(index = 2)] + pub field_type: FieldType, - #[pb(index = 3, one_of)] - pub type_option_data: Option>, + #[pb(index = 3, one_of)] + pub type_option_data: Option>, } #[derive(Clone)] pub struct CreateFieldParams { - pub database_id: String, - pub field_type: FieldType, - pub type_option_data: Option>, + pub database_id: String, + pub field_type: FieldType, + pub type_option_data: Option>, } impl TryInto for CreateFieldPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let database_id = NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; - Ok(CreateFieldParams { - database_id: database_id.0, - field_type: self.field_type, - type_option_data: self.type_option_data, - }) - } + fn try_into(self) -> Result { + let database_id = + NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; + Ok(CreateFieldParams { + database_id: database_id.0, + field_type: self.field_type, + type_option_data: self.type_option_data, + }) + } } #[derive(Debug, Default, ProtoBuf)] pub struct UpdateFieldTypePayloadPB { - #[pb(index = 1)] - pub database_id: String, + #[pb(index = 1)] + pub database_id: String, - #[pb(index = 2)] - pub field_id: String, + #[pb(index = 2)] + pub field_id: String, - #[pb(index = 3)] - pub field_type: FieldType, + #[pb(index = 3)] + pub field_type: FieldType, - #[pb(index = 4)] - pub create_if_not_exist: bool, + #[pb(index = 4)] + pub create_if_not_exist: bool, } pub struct EditFieldParams { - pub database_id: String, - pub field_id: String, - pub field_type: FieldType, + pub database_id: String, + pub field_id: String, + pub field_type: FieldType, } impl TryInto for UpdateFieldTypePayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let database_id = NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; - let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; - Ok(EditFieldParams { - database_id: database_id.0, - field_id: field_id.0, - field_type: self.field_type, - }) - } + fn try_into(self) -> Result { + let database_id = + NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; + let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; + Ok(EditFieldParams { + database_id: database_id.0, + field_id: field_id.0, + field_type: self.field_type, + }) + } } #[derive(Debug, Default, ProtoBuf)] pub struct TypeOptionPathPB { - #[pb(index = 1)] - pub database_id: String, + #[pb(index = 1)] + pub database_id: String, - #[pb(index = 2)] - pub field_id: String, + #[pb(index = 2)] + pub field_id: String, - #[pb(index = 3)] - pub field_type: FieldType, + #[pb(index = 3)] + pub field_type: FieldType, } pub struct TypeOptionPathParams { - pub database_id: String, - pub field_id: String, - pub field_type: FieldType, + pub database_id: String, + pub field_id: String, + pub field_type: FieldType, } impl TryInto for TypeOptionPathPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let database_id = NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; - let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; - Ok(TypeOptionPathParams { - database_id: database_id.0, - field_id: field_id.0, - field_type: self.field_type, - }) - } + fn try_into(self) -> Result { + let database_id = + NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; + let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; + Ok(TypeOptionPathParams { + database_id: database_id.0, + field_id: field_id.0, + field_type: self.field_type, + }) + } } #[derive(Debug, Default, ProtoBuf)] pub struct TypeOptionPB { - #[pb(index = 1)] - pub database_id: String, + #[pb(index = 1)] + pub database_id: String, - #[pb(index = 2)] - pub field: FieldPB, + #[pb(index = 2)] + pub field: FieldPB, - #[pb(index = 3)] - pub type_option_data: Vec, + #[pb(index = 3)] + pub type_option_data: Vec, } /// Collection of the [FieldPB] #[derive(Debug, Default, ProtoBuf)] pub struct RepeatedFieldPB { - #[pb(index = 1)] - pub items: Vec, + #[pb(index = 1)] + pub items: Vec, } impl std::ops::Deref for RepeatedFieldPB { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.items - } + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.items + } } impl std::ops::DerefMut for RepeatedFieldPB { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.items - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.items + } } impl std::convert::From> for RepeatedFieldPB { - fn from(items: Vec) -> Self { - Self { items } - } + fn from(items: Vec) -> Self { + Self { items } + } } #[derive(Debug, Clone, Default, ProtoBuf)] pub struct RepeatedFieldIdPB { - #[pb(index = 1)] - pub items: Vec, + #[pb(index = 1)] + pub items: Vec, } impl std::ops::Deref for RepeatedFieldIdPB { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.items - } + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.items + } } impl std::convert::From> for RepeatedFieldIdPB { - fn from(items: Vec) -> Self { - RepeatedFieldIdPB { items } - } + fn from(items: Vec) -> Self { + RepeatedFieldIdPB { items } + } } impl std::convert::From for RepeatedFieldIdPB { - fn from(s: String) -> Self { - RepeatedFieldIdPB { - items: vec![FieldIdPB::from(s)], - } + fn from(s: String) -> Self { + RepeatedFieldIdPB { + items: vec![FieldIdPB::from(s)], } + } } /// [TypeOptionChangesetPB] is used to update the type-option data. #[derive(ProtoBuf, Default)] pub struct TypeOptionChangesetPB { - #[pb(index = 1)] - pub database_id: String, + #[pb(index = 1)] + pub database_id: String, - #[pb(index = 2)] - pub field_id: String, + #[pb(index = 2)] + pub field_id: String, - /// Check out [TypeOptionPB] for more details. - #[pb(index = 3)] - pub type_option_data: Vec, + /// Check out [TypeOptionPB] for more details. + #[pb(index = 3)] + pub type_option_data: Vec, } #[derive(Clone)] pub struct TypeOptionChangesetParams { - pub database_id: String, - pub field_id: String, - pub type_option_data: Vec, + pub database_id: String, + pub field_id: String, + pub type_option_data: Vec, } impl TryInto for TypeOptionChangesetPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let database_id = NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; - let _ = NotEmptyStr::parse(self.field_id.clone()).map_err(|_| ErrorCode::FieldIdIsEmpty)?; + fn try_into(self) -> Result { + let database_id = + NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; + let _ = NotEmptyStr::parse(self.field_id.clone()).map_err(|_| ErrorCode::FieldIdIsEmpty)?; - Ok(TypeOptionChangesetParams { - database_id: database_id.0, - field_id: self.field_id, - type_option_data: self.type_option_data, - }) - } + Ok(TypeOptionChangesetParams { + database_id: database_id.0, + field_id: self.field_id, + type_option_data: self.type_option_data, + }) + } } #[derive(ProtoBuf, Default)] pub struct GetFieldPayloadPB { - #[pb(index = 1)] - pub database_id: String, + #[pb(index = 1)] + pub database_id: String, - #[pb(index = 2, one_of)] - pub field_ids: Option, + #[pb(index = 2, one_of)] + pub field_ids: Option, } pub struct GetFieldParams { - pub database_id: String, - pub field_ids: Option>, + pub database_id: String, + pub field_ids: Option>, } impl TryInto for GetFieldPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let database_id = NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; - let field_ids = self.field_ids.map(|repeated| { - repeated - .items - .into_iter() - .map(|item| item.field_id) - .collect::>() - }); + fn try_into(self) -> Result { + let database_id = + NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; + let field_ids = self.field_ids.map(|repeated| { + repeated + .items + .into_iter() + .map(|item| item.field_id) + .collect::>() + }); - Ok(GetFieldParams { - database_id: database_id.0, - field_ids, - }) - } + Ok(GetFieldParams { + database_id: database_id.0, + field_ids, + }) + } } /// [FieldChangesetPB] is used to modify the corresponding field. It defines which properties of @@ -386,78 +393,79 @@ impl TryInto for GetFieldPayloadPB { /// #[derive(Debug, Clone, Default, ProtoBuf)] pub struct FieldChangesetPB { - #[pb(index = 1)] - pub field_id: String, + #[pb(index = 1)] + pub field_id: String, - #[pb(index = 2)] - pub database_id: String, + #[pb(index = 2)] + pub database_id: String, - #[pb(index = 3, one_of)] - pub name: Option, + #[pb(index = 3, one_of)] + pub name: Option, - #[pb(index = 4, one_of)] - pub desc: Option, + #[pb(index = 4, one_of)] + pub desc: Option, - #[pb(index = 5, one_of)] - pub field_type: Option, + #[pb(index = 5, one_of)] + pub field_type: Option, - #[pb(index = 6, one_of)] - pub frozen: Option, + #[pb(index = 6, one_of)] + pub frozen: Option, - #[pb(index = 7, one_of)] - pub visibility: Option, + #[pb(index = 7, one_of)] + pub visibility: Option, - #[pb(index = 8, one_of)] - pub width: Option, - // #[pb(index = 9, one_of)] - // pub type_option_data: Option>, + #[pb(index = 8, one_of)] + pub width: Option, + // #[pb(index = 9, one_of)] + // pub type_option_data: Option>, } impl TryInto for FieldChangesetPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let database_id = NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; - let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; - let field_type = self.field_type.map(FieldTypeRevision::from); - // if let Some(type_option_data) = self.type_option_data.as_ref() { - // if type_option_data.is_empty() { - // return Err(ErrorCode::TypeOptionDataIsEmpty); - // } - // } + fn try_into(self) -> Result { + let database_id = + NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; + let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; + let field_type = self.field_type.map(FieldTypeRevision::from); + // if let Some(type_option_data) = self.type_option_data.as_ref() { + // if type_option_data.is_empty() { + // return Err(ErrorCode::TypeOptionDataIsEmpty); + // } + // } - Ok(FieldChangesetParams { - field_id: field_id.0, - database_id: database_id.0, - name: self.name, - desc: self.desc, - field_type, - frozen: self.frozen, - visibility: self.visibility, - width: self.width, - // type_option_data: self.type_option_data, - }) - } + Ok(FieldChangesetParams { + field_id: field_id.0, + database_id: database_id.0, + name: self.name, + desc: self.desc, + field_type, + frozen: self.frozen, + visibility: self.visibility, + width: self.width, + // type_option_data: self.type_option_data, + }) + } } #[derive(Debug, Clone, Default)] pub struct FieldChangesetParams { - pub field_id: String, + pub field_id: String, - pub database_id: String, + pub database_id: String, - pub name: Option, + pub name: Option, - pub desc: Option, + pub desc: Option, - pub field_type: Option, + pub field_type: Option, - pub frozen: Option, + pub frozen: Option, - pub visibility: Option, + pub visibility: Option, - pub width: Option, - // pub type_option_data: Option>, + pub width: Option, + // pub type_option_data: Option>, } /// Certain field types have user-defined options such as color, date format, number format, /// or a list of values for a multi-select list. These options are defined within a specialization @@ -469,29 +477,29 @@ pub struct FieldChangesetParams { /// The order of the enum can't be changed. If you want to add a new type, /// it would be better to append it to the end of the list. #[derive( - Debug, - Clone, - PartialEq, - Hash, - Eq, - ProtoBuf_Enum, - EnumCountMacro, - EnumString, - EnumIter, - Display, - Serialize_repr, - Deserialize_repr, + Debug, + Clone, + PartialEq, + Hash, + Eq, + ProtoBuf_Enum, + EnumCountMacro, + EnumString, + EnumIter, + Display, + Serialize_repr, + Deserialize_repr, )] #[repr(u8)] pub enum FieldType { - RichText = 0, - Number = 1, - DateTime = 2, - SingleSelect = 3, - MultiSelect = 4, - Checkbox = 5, - URL = 6, - Checklist = 7, + RichText = 0, + Number = 1, + DateTime = 2, + SingleSelect = 3, + MultiSelect = 4, + Checkbox = 5, + URL = 6, + Checklist = 7, } pub const RICH_TEXT_FIELD: FieldType = FieldType::RichText; @@ -504,166 +512,168 @@ pub const URL_FIELD: FieldType = FieldType::URL; pub const CHECKLIST_FIELD: FieldType = FieldType::Checklist; impl std::default::Default for FieldType { - fn default() -> Self { - FieldType::RichText - } + fn default() -> Self { + FieldType::RichText + } } impl AsRef for FieldType { - fn as_ref(&self) -> &FieldType { - self - } + fn as_ref(&self) -> &FieldType { + self + } } impl From<&FieldType> for FieldType { - fn from(field_type: &FieldType) -> Self { - field_type.clone() - } + fn from(field_type: &FieldType) -> Self { + field_type.clone() + } } impl FieldType { - pub fn type_id(&self) -> String { - (self.clone() as u8).to_string() - } + pub fn type_id(&self) -> String { + (self.clone() as u8).to_string() + } - pub fn default_cell_width(&self) -> i32 { - match self { - FieldType::DateTime => 180, - _ => 150, - } + pub fn default_cell_width(&self) -> i32 { + match self { + FieldType::DateTime => 180, + _ => 150, } + } - pub fn is_number(&self) -> bool { - self == &NUMBER_FIELD - } + pub fn is_number(&self) -> bool { + self == &NUMBER_FIELD + } - pub fn is_text(&self) -> bool { - self == &RICH_TEXT_FIELD - } + pub fn is_text(&self) -> bool { + self == &RICH_TEXT_FIELD + } - pub fn is_checkbox(&self) -> bool { - self == &CHECKBOX_FIELD - } + pub fn is_checkbox(&self) -> bool { + self == &CHECKBOX_FIELD + } - pub fn is_date(&self) -> bool { - self == &DATE_FIELD - } + pub fn is_date(&self) -> bool { + self == &DATE_FIELD + } - pub fn is_single_select(&self) -> bool { - self == &SINGLE_SELECT_FIELD - } + pub fn is_single_select(&self) -> bool { + self == &SINGLE_SELECT_FIELD + } - pub fn is_multi_select(&self) -> bool { - self == &MULTI_SELECT_FIELD - } + pub fn is_multi_select(&self) -> bool { + self == &MULTI_SELECT_FIELD + } - pub fn is_url(&self) -> bool { - self == &URL_FIELD - } + pub fn is_url(&self) -> bool { + self == &URL_FIELD + } - pub fn is_select_option(&self) -> bool { - self == &MULTI_SELECT_FIELD || self == &SINGLE_SELECT_FIELD - } + pub fn is_select_option(&self) -> bool { + self == &MULTI_SELECT_FIELD || self == &SINGLE_SELECT_FIELD + } - pub fn is_check_list(&self) -> bool { - self == &CHECKLIST_FIELD - } + pub fn is_check_list(&self) -> bool { + self == &CHECKLIST_FIELD + } - pub fn can_be_group(&self) -> bool { - self.is_select_option() || self.is_checkbox() - } + pub fn can_be_group(&self) -> bool { + self.is_select_option() || self.is_checkbox() + } } impl std::convert::From<&FieldType> for FieldTypeRevision { - fn from(ty: &FieldType) -> Self { - ty.clone() as u8 - } + fn from(ty: &FieldType) -> Self { + ty.clone() as u8 + } } impl std::convert::From for FieldTypeRevision { - fn from(ty: FieldType) -> Self { - ty as u8 - } + fn from(ty: FieldType) -> Self { + ty as u8 + } } impl std::convert::From<&FieldTypeRevision> for FieldType { - fn from(ty: &FieldTypeRevision) -> Self { - FieldType::from(*ty) - } + fn from(ty: &FieldTypeRevision) -> Self { + FieldType::from(*ty) + } } impl std::convert::From for FieldType { - fn from(ty: FieldTypeRevision) -> Self { - match ty { - 0 => FieldType::RichText, - 1 => FieldType::Number, - 2 => FieldType::DateTime, - 3 => FieldType::SingleSelect, - 4 => FieldType::MultiSelect, - 5 => FieldType::Checkbox, - 6 => FieldType::URL, - 7 => FieldType::Checklist, - _ => { - tracing::error!("Can't convert FieldTypeRevision: {} to FieldType", ty); - FieldType::RichText - } - } + fn from(ty: FieldTypeRevision) -> Self { + match ty { + 0 => FieldType::RichText, + 1 => FieldType::Number, + 2 => FieldType::DateTime, + 3 => FieldType::SingleSelect, + 4 => FieldType::MultiSelect, + 5 => FieldType::Checkbox, + 6 => FieldType::URL, + 7 => FieldType::Checklist, + _ => { + tracing::error!("Can't convert FieldTypeRevision: {} to FieldType", ty); + FieldType::RichText + }, } + } } #[derive(Debug, Clone, Default, ProtoBuf)] pub struct DuplicateFieldPayloadPB { - #[pb(index = 1)] - pub field_id: String, + #[pb(index = 1)] + pub field_id: String, - #[pb(index = 2)] - pub database_id: String, + #[pb(index = 2)] + pub database_id: String, } #[derive(Debug, Clone, Default, ProtoBuf)] pub struct GridFieldIdentifierPayloadPB { - #[pb(index = 1)] - pub field_id: String, + #[pb(index = 1)] + pub field_id: String, - #[pb(index = 2)] - pub database_id: String, + #[pb(index = 2)] + pub database_id: String, } impl TryInto for DuplicateFieldPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let database_id = NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; - let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; - Ok(FieldIdParams { - database_id: database_id.0, - field_id: field_id.0, - }) - } + fn try_into(self) -> Result { + let database_id = + NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; + let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; + Ok(FieldIdParams { + database_id: database_id.0, + field_id: field_id.0, + }) + } } #[derive(Debug, Clone, Default, ProtoBuf)] pub struct DeleteFieldPayloadPB { - #[pb(index = 1)] - pub field_id: String, + #[pb(index = 1)] + pub field_id: String, - #[pb(index = 2)] - pub database_id: String, + #[pb(index = 2)] + pub database_id: String, } impl TryInto for DeleteFieldPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let database_id = NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; - let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; - Ok(FieldIdParams { - database_id: database_id.0, - field_id: field_id.0, - }) - } + fn try_into(self) -> Result { + let database_id = + NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; + let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; + Ok(FieldIdParams { + database_id: database_id.0, + field_id: field_id.0, + }) + } } pub struct FieldIdParams { - pub field_id: String, - pub database_id: String, + pub field_id: String, + pub database_id: String, } diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/checkbox_filter.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/checkbox_filter.rs index 2bf847ffb0..7cc133e32a 100644 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/checkbox_filter.rs +++ b/frontend/rust-lib/flowy-database/src/entities/filter_entities/checkbox_filter.rs @@ -5,58 +5,58 @@ use grid_model::FilterRevision; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct CheckboxFilterPB { - #[pb(index = 1)] - pub condition: CheckboxFilterConditionPB, + #[pb(index = 1)] + pub condition: CheckboxFilterConditionPB, } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] pub enum CheckboxFilterConditionPB { - IsChecked = 0, - IsUnChecked = 1, + IsChecked = 0, + IsUnChecked = 1, } impl std::convert::From for u32 { - fn from(value: CheckboxFilterConditionPB) -> Self { - value as u32 - } + fn from(value: CheckboxFilterConditionPB) -> Self { + value as u32 + } } impl std::default::Default for CheckboxFilterConditionPB { - fn default() -> Self { - CheckboxFilterConditionPB::IsChecked - } + fn default() -> Self { + CheckboxFilterConditionPB::IsChecked + } } impl std::convert::TryFrom for CheckboxFilterConditionPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(CheckboxFilterConditionPB::IsChecked), - 1 => Ok(CheckboxFilterConditionPB::IsUnChecked), - _ => Err(ErrorCode::InvalidData), - } + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(CheckboxFilterConditionPB::IsChecked), + 1 => Ok(CheckboxFilterConditionPB::IsUnChecked), + _ => Err(ErrorCode::InvalidData), } + } } impl FromFilterString for CheckboxFilterPB { - fn from_filter_rev(filter_rev: &FilterRevision) -> Self - where - Self: Sized, - { - CheckboxFilterPB { - condition: CheckboxFilterConditionPB::try_from(filter_rev.condition) - .unwrap_or(CheckboxFilterConditionPB::IsChecked), - } + fn from_filter_rev(filter_rev: &FilterRevision) -> Self + where + Self: Sized, + { + CheckboxFilterPB { + condition: CheckboxFilterConditionPB::try_from(filter_rev.condition) + .unwrap_or(CheckboxFilterConditionPB::IsChecked), } + } } impl std::convert::From<&FilterRevision> for CheckboxFilterPB { - fn from(rev: &FilterRevision) -> Self { - CheckboxFilterPB { - condition: CheckboxFilterConditionPB::try_from(rev.condition) - .unwrap_or(CheckboxFilterConditionPB::IsChecked), - } + fn from(rev: &FilterRevision) -> Self { + CheckboxFilterPB { + condition: CheckboxFilterConditionPB::try_from(rev.condition) + .unwrap_or(CheckboxFilterConditionPB::IsChecked), } + } } diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/checklist_filter.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/checklist_filter.rs index 1e7fe95500..b823b7919f 100644 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/checklist_filter.rs +++ b/frontend/rust-lib/flowy-database/src/entities/filter_entities/checklist_filter.rs @@ -5,58 +5,58 @@ use grid_model::FilterRevision; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct ChecklistFilterPB { - #[pb(index = 1)] - pub condition: ChecklistFilterConditionPB, + #[pb(index = 1)] + pub condition: ChecklistFilterConditionPB, } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] pub enum ChecklistFilterConditionPB { - IsComplete = 0, - IsIncomplete = 1, + IsComplete = 0, + IsIncomplete = 1, } impl std::convert::From for u32 { - fn from(value: ChecklistFilterConditionPB) -> Self { - value as u32 - } + fn from(value: ChecklistFilterConditionPB) -> Self { + value as u32 + } } impl std::default::Default for ChecklistFilterConditionPB { - fn default() -> Self { - ChecklistFilterConditionPB::IsIncomplete - } + fn default() -> Self { + ChecklistFilterConditionPB::IsIncomplete + } } impl std::convert::TryFrom for ChecklistFilterConditionPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(ChecklistFilterConditionPB::IsComplete), - 1 => Ok(ChecklistFilterConditionPB::IsIncomplete), - _ => Err(ErrorCode::InvalidData), - } + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(ChecklistFilterConditionPB::IsComplete), + 1 => Ok(ChecklistFilterConditionPB::IsIncomplete), + _ => Err(ErrorCode::InvalidData), } + } } impl FromFilterString for ChecklistFilterPB { - fn from_filter_rev(filter_rev: &FilterRevision) -> Self - where - Self: Sized, - { - ChecklistFilterPB { - condition: ChecklistFilterConditionPB::try_from(filter_rev.condition) - .unwrap_or(ChecklistFilterConditionPB::IsIncomplete), - } + fn from_filter_rev(filter_rev: &FilterRevision) -> Self + where + Self: Sized, + { + ChecklistFilterPB { + condition: ChecklistFilterConditionPB::try_from(filter_rev.condition) + .unwrap_or(ChecklistFilterConditionPB::IsIncomplete), } + } } impl std::convert::From<&FilterRevision> for ChecklistFilterPB { - fn from(rev: &FilterRevision) -> Self { - ChecklistFilterPB { - condition: ChecklistFilterConditionPB::try_from(rev.condition) - .unwrap_or(ChecklistFilterConditionPB::IsIncomplete), - } + fn from(rev: &FilterRevision) -> Self { + ChecklistFilterPB { + condition: ChecklistFilterConditionPB::try_from(rev.condition) + .unwrap_or(ChecklistFilterConditionPB::IsIncomplete), } + } } diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/date_filter.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/date_filter.rs index 950dfb1f1c..c6ad0a7f4a 100644 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/date_filter.rs +++ b/frontend/rust-lib/flowy-database/src/entities/filter_entities/date_filter.rs @@ -7,114 +7,116 @@ use std::str::FromStr; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct DateFilterPB { - #[pb(index = 1)] - pub condition: DateFilterConditionPB, + #[pb(index = 1)] + pub condition: DateFilterConditionPB, - #[pb(index = 2, one_of)] - pub start: Option, + #[pb(index = 2, one_of)] + pub start: Option, - #[pb(index = 3, one_of)] - pub end: Option, + #[pb(index = 3, one_of)] + pub end: Option, - #[pb(index = 4, one_of)] - pub timestamp: Option, + #[pb(index = 4, one_of)] + pub timestamp: Option, } #[derive(Deserialize, Serialize, Default, Clone, Debug)] pub struct DateFilterContentPB { - pub start: Option, - pub end: Option, - pub timestamp: Option, + pub start: Option, + pub end: Option, + pub timestamp: Option, } impl ToString for DateFilterContentPB { - fn to_string(&self) -> String { - serde_json::to_string(self).unwrap() - } + fn to_string(&self) -> String { + serde_json::to_string(self).unwrap() + } } impl FromStr for DateFilterContentPB { - type Err = serde_json::Error; + type Err = serde_json::Error; - fn from_str(s: &str) -> Result { - serde_json::from_str(s) - } + fn from_str(s: &str) -> Result { + serde_json::from_str(s) + } } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] pub enum DateFilterConditionPB { - DateIs = 0, - DateBefore = 1, - DateAfter = 2, - DateOnOrBefore = 3, - DateOnOrAfter = 4, - DateWithIn = 5, - DateIsEmpty = 6, - DateIsNotEmpty = 7, + DateIs = 0, + DateBefore = 1, + DateAfter = 2, + DateOnOrBefore = 3, + DateOnOrAfter = 4, + DateWithIn = 5, + DateIsEmpty = 6, + DateIsNotEmpty = 7, } impl std::convert::From for u32 { - fn from(value: DateFilterConditionPB) -> Self { - value as u32 - } + fn from(value: DateFilterConditionPB) -> Self { + value as u32 + } } impl std::default::Default for DateFilterConditionPB { - fn default() -> Self { - DateFilterConditionPB::DateIs - } + fn default() -> Self { + DateFilterConditionPB::DateIs + } } impl std::convert::TryFrom for DateFilterConditionPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(DateFilterConditionPB::DateIs), - 1 => Ok(DateFilterConditionPB::DateBefore), - 2 => Ok(DateFilterConditionPB::DateAfter), - 3 => Ok(DateFilterConditionPB::DateOnOrBefore), - 4 => Ok(DateFilterConditionPB::DateOnOrAfter), - 5 => Ok(DateFilterConditionPB::DateWithIn), - 6 => Ok(DateFilterConditionPB::DateIsEmpty), - _ => Err(ErrorCode::InvalidData), - } + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(DateFilterConditionPB::DateIs), + 1 => Ok(DateFilterConditionPB::DateBefore), + 2 => Ok(DateFilterConditionPB::DateAfter), + 3 => Ok(DateFilterConditionPB::DateOnOrBefore), + 4 => Ok(DateFilterConditionPB::DateOnOrAfter), + 5 => Ok(DateFilterConditionPB::DateWithIn), + 6 => Ok(DateFilterConditionPB::DateIsEmpty), + _ => Err(ErrorCode::InvalidData), } + } } impl FromFilterString for DateFilterPB { - fn from_filter_rev(filter_rev: &FilterRevision) -> Self - where - Self: Sized, - { - let condition = DateFilterConditionPB::try_from(filter_rev.condition).unwrap_or(DateFilterConditionPB::DateIs); - let mut filter = DateFilterPB { - condition, - ..Default::default() - }; + fn from_filter_rev(filter_rev: &FilterRevision) -> Self + where + Self: Sized, + { + let condition = DateFilterConditionPB::try_from(filter_rev.condition) + .unwrap_or(DateFilterConditionPB::DateIs); + let mut filter = DateFilterPB { + condition, + ..Default::default() + }; - if let Ok(content) = DateFilterContentPB::from_str(&filter_rev.content) { - filter.start = content.start; - filter.end = content.end; - filter.timestamp = content.timestamp; - }; + if let Ok(content) = DateFilterContentPB::from_str(&filter_rev.content) { + filter.start = content.start; + filter.end = content.end; + filter.timestamp = content.timestamp; + }; - filter - } + filter + } } impl std::convert::From<&FilterRevision> for DateFilterPB { - fn from(rev: &FilterRevision) -> Self { - let condition = DateFilterConditionPB::try_from(rev.condition).unwrap_or(DateFilterConditionPB::DateIs); - let mut filter = DateFilterPB { - condition, - ..Default::default() - }; + fn from(rev: &FilterRevision) -> Self { + let condition = + DateFilterConditionPB::try_from(rev.condition).unwrap_or(DateFilterConditionPB::DateIs); + let mut filter = DateFilterPB { + condition, + ..Default::default() + }; - if let Ok(content) = DateFilterContentPB::from_str(&rev.content) { - filter.start = content.start; - filter.end = content.end; - filter.timestamp = content.timestamp; - }; + if let Ok(content) = DateFilterContentPB::from_str(&rev.content) { + filter.start = content.start; + filter.end = content.end; + filter.timestamp = content.timestamp; + }; - filter - } + filter + } } diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/filter_changeset.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/filter_changeset.rs index 09345fbabe..05a0fbd4ea 100644 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/filter_changeset.rs +++ b/frontend/rust-lib/flowy-database/src/entities/filter_entities/filter_changeset.rs @@ -3,52 +3,52 @@ use flowy_derive::ProtoBuf; #[derive(Debug, Default, ProtoBuf)] pub struct FilterChangesetNotificationPB { - #[pb(index = 1)] - pub view_id: String, + #[pb(index = 1)] + pub view_id: String, - #[pb(index = 2)] - pub insert_filters: Vec, + #[pb(index = 2)] + pub insert_filters: Vec, - #[pb(index = 3)] - pub delete_filters: Vec, + #[pb(index = 3)] + pub delete_filters: Vec, - #[pb(index = 4)] - pub update_filters: Vec, + #[pb(index = 4)] + pub update_filters: Vec, } #[derive(Debug, Default, ProtoBuf)] pub struct UpdatedFilter { - #[pb(index = 1)] - pub filter_id: String, + #[pb(index = 1)] + pub filter_id: String, - #[pb(index = 2, one_of)] - pub filter: Option, + #[pb(index = 2, one_of)] + pub filter: Option, } impl FilterChangesetNotificationPB { - pub fn from_insert(view_id: &str, filters: Vec) -> Self { - Self { - view_id: view_id.to_string(), - insert_filters: filters, - delete_filters: Default::default(), - update_filters: Default::default(), - } + pub fn from_insert(view_id: &str, filters: Vec) -> Self { + Self { + view_id: view_id.to_string(), + insert_filters: filters, + delete_filters: Default::default(), + update_filters: Default::default(), } - pub fn from_delete(view_id: &str, filters: Vec) -> Self { - Self { - view_id: view_id.to_string(), - insert_filters: Default::default(), - delete_filters: filters, - update_filters: Default::default(), - } + } + pub fn from_delete(view_id: &str, filters: Vec) -> Self { + Self { + view_id: view_id.to_string(), + insert_filters: Default::default(), + delete_filters: filters, + update_filters: Default::default(), } + } - pub fn from_update(view_id: &str, filters: Vec) -> Self { - Self { - view_id: view_id.to_string(), - insert_filters: Default::default(), - delete_filters: Default::default(), - update_filters: filters, - } + pub fn from_update(view_id: &str, filters: Vec) -> Self { + Self { + view_id: view_id.to_string(), + insert_filters: Default::default(), + delete_filters: Default::default(), + update_filters: filters, } + } } diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/number_filter.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/number_filter.rs index 357a2600da..cea30b70ea 100644 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/number_filter.rs +++ b/frontend/rust-lib/flowy-database/src/entities/filter_entities/number_filter.rs @@ -5,72 +5,73 @@ use grid_model::FilterRevision; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct NumberFilterPB { - #[pb(index = 1)] - pub condition: NumberFilterConditionPB, + #[pb(index = 1)] + pub condition: NumberFilterConditionPB, - #[pb(index = 2)] - pub content: String, + #[pb(index = 2)] + pub content: String, } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] pub enum NumberFilterConditionPB { - Equal = 0, - NotEqual = 1, - GreaterThan = 2, - LessThan = 3, - GreaterThanOrEqualTo = 4, - LessThanOrEqualTo = 5, - NumberIsEmpty = 6, - NumberIsNotEmpty = 7, + Equal = 0, + NotEqual = 1, + GreaterThan = 2, + LessThan = 3, + GreaterThanOrEqualTo = 4, + LessThanOrEqualTo = 5, + NumberIsEmpty = 6, + NumberIsNotEmpty = 7, } impl std::default::Default for NumberFilterConditionPB { - fn default() -> Self { - NumberFilterConditionPB::Equal - } + fn default() -> Self { + NumberFilterConditionPB::Equal + } } impl std::convert::From for u32 { - fn from(value: NumberFilterConditionPB) -> Self { - value as u32 - } + fn from(value: NumberFilterConditionPB) -> Self { + value as u32 + } } impl std::convert::TryFrom for NumberFilterConditionPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_from(n: u8) -> Result { - match n { - 0 => Ok(NumberFilterConditionPB::Equal), - 1 => Ok(NumberFilterConditionPB::NotEqual), - 2 => Ok(NumberFilterConditionPB::GreaterThan), - 3 => Ok(NumberFilterConditionPB::LessThan), - 4 => Ok(NumberFilterConditionPB::GreaterThanOrEqualTo), - 5 => Ok(NumberFilterConditionPB::LessThanOrEqualTo), - 6 => Ok(NumberFilterConditionPB::NumberIsEmpty), - 7 => Ok(NumberFilterConditionPB::NumberIsNotEmpty), - _ => Err(ErrorCode::InvalidData), - } + fn try_from(n: u8) -> Result { + match n { + 0 => Ok(NumberFilterConditionPB::Equal), + 1 => Ok(NumberFilterConditionPB::NotEqual), + 2 => Ok(NumberFilterConditionPB::GreaterThan), + 3 => Ok(NumberFilterConditionPB::LessThan), + 4 => Ok(NumberFilterConditionPB::GreaterThanOrEqualTo), + 5 => Ok(NumberFilterConditionPB::LessThanOrEqualTo), + 6 => Ok(NumberFilterConditionPB::NumberIsEmpty), + 7 => Ok(NumberFilterConditionPB::NumberIsNotEmpty), + _ => Err(ErrorCode::InvalidData), } + } } impl FromFilterString for NumberFilterPB { - fn from_filter_rev(filter_rev: &FilterRevision) -> Self - where - Self: Sized, - { - NumberFilterPB { - condition: NumberFilterConditionPB::try_from(filter_rev.condition) - .unwrap_or(NumberFilterConditionPB::Equal), - content: filter_rev.content.clone(), - } + fn from_filter_rev(filter_rev: &FilterRevision) -> Self + where + Self: Sized, + { + NumberFilterPB { + condition: NumberFilterConditionPB::try_from(filter_rev.condition) + .unwrap_or(NumberFilterConditionPB::Equal), + content: filter_rev.content.clone(), } + } } impl std::convert::From<&FilterRevision> for NumberFilterPB { - fn from(rev: &FilterRevision) -> Self { - NumberFilterPB { - condition: NumberFilterConditionPB::try_from(rev.condition).unwrap_or(NumberFilterConditionPB::Equal), - content: rev.content.clone(), - } + fn from(rev: &FilterRevision) -> Self { + NumberFilterPB { + condition: NumberFilterConditionPB::try_from(rev.condition) + .unwrap_or(NumberFilterConditionPB::Equal), + content: rev.content.clone(), } + } } diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/select_option_filter.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/select_option_filter.rs index 88838dcbb2..d4e58c0552 100644 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/select_option_filter.rs +++ b/frontend/rust-lib/flowy-database/src/entities/filter_entities/select_option_filter.rs @@ -6,67 +6,68 @@ use grid_model::FilterRevision; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct SelectOptionFilterPB { - #[pb(index = 1)] - pub condition: SelectOptionConditionPB, + #[pb(index = 1)] + pub condition: SelectOptionConditionPB, - #[pb(index = 2)] - pub option_ids: Vec, + #[pb(index = 2)] + pub option_ids: Vec, } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] pub enum SelectOptionConditionPB { - OptionIs = 0, - OptionIsNot = 1, - OptionIsEmpty = 2, - OptionIsNotEmpty = 3, + OptionIs = 0, + OptionIsNot = 1, + OptionIsEmpty = 2, + OptionIsNotEmpty = 3, } impl std::convert::From for u32 { - fn from(value: SelectOptionConditionPB) -> Self { - value as u32 - } + fn from(value: SelectOptionConditionPB) -> Self { + value as u32 + } } impl std::default::Default for SelectOptionConditionPB { - fn default() -> Self { - SelectOptionConditionPB::OptionIs - } + fn default() -> Self { + SelectOptionConditionPB::OptionIs + } } impl std::convert::TryFrom for SelectOptionConditionPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(SelectOptionConditionPB::OptionIs), - 1 => Ok(SelectOptionConditionPB::OptionIsNot), - 2 => Ok(SelectOptionConditionPB::OptionIsEmpty), - 3 => Ok(SelectOptionConditionPB::OptionIsNotEmpty), - _ => Err(ErrorCode::InvalidData), - } + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(SelectOptionConditionPB::OptionIs), + 1 => Ok(SelectOptionConditionPB::OptionIsNot), + 2 => Ok(SelectOptionConditionPB::OptionIsEmpty), + 3 => Ok(SelectOptionConditionPB::OptionIsNotEmpty), + _ => Err(ErrorCode::InvalidData), } + } } impl FromFilterString for SelectOptionFilterPB { - fn from_filter_rev(filter_rev: &FilterRevision) -> Self - where - Self: Sized, - { - let ids = SelectOptionIds::from(filter_rev.content.clone()); - SelectOptionFilterPB { - condition: SelectOptionConditionPB::try_from(filter_rev.condition) - .unwrap_or(SelectOptionConditionPB::OptionIs), - option_ids: ids.into_inner(), - } + fn from_filter_rev(filter_rev: &FilterRevision) -> Self + where + Self: Sized, + { + let ids = SelectOptionIds::from(filter_rev.content.clone()); + SelectOptionFilterPB { + condition: SelectOptionConditionPB::try_from(filter_rev.condition) + .unwrap_or(SelectOptionConditionPB::OptionIs), + option_ids: ids.into_inner(), } + } } impl std::convert::From<&FilterRevision> for SelectOptionFilterPB { - fn from(rev: &FilterRevision) -> Self { - let ids = SelectOptionIds::from(rev.content.clone()); - SelectOptionFilterPB { - condition: SelectOptionConditionPB::try_from(rev.condition).unwrap_or(SelectOptionConditionPB::OptionIs), - option_ids: ids.into_inner(), - } + fn from(rev: &FilterRevision) -> Self { + let ids = SelectOptionIds::from(rev.content.clone()); + SelectOptionFilterPB { + condition: SelectOptionConditionPB::try_from(rev.condition) + .unwrap_or(SelectOptionConditionPB::OptionIs), + option_ids: ids.into_inner(), } + } } diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/text_filter.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/text_filter.rs index c5755734ef..483c1e61ce 100644 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/text_filter.rs +++ b/frontend/rust-lib/flowy-database/src/entities/filter_entities/text_filter.rs @@ -5,73 +5,75 @@ use grid_model::FilterRevision; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct TextFilterPB { - #[pb(index = 1)] - pub condition: TextFilterConditionPB, + #[pb(index = 1)] + pub condition: TextFilterConditionPB, - #[pb(index = 2)] - pub content: String, + #[pb(index = 2)] + pub content: String, } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] pub enum TextFilterConditionPB { - Is = 0, - IsNot = 1, - Contains = 2, - DoesNotContain = 3, - StartsWith = 4, - EndsWith = 5, - TextIsEmpty = 6, - TextIsNotEmpty = 7, + Is = 0, + IsNot = 1, + Contains = 2, + DoesNotContain = 3, + StartsWith = 4, + EndsWith = 5, + TextIsEmpty = 6, + TextIsNotEmpty = 7, } impl std::convert::From for u32 { - fn from(value: TextFilterConditionPB) -> Self { - value as u32 - } + fn from(value: TextFilterConditionPB) -> Self { + value as u32 + } } impl std::default::Default for TextFilterConditionPB { - fn default() -> Self { - TextFilterConditionPB::Is - } + fn default() -> Self { + TextFilterConditionPB::Is + } } impl std::convert::TryFrom for TextFilterConditionPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(TextFilterConditionPB::Is), - 1 => Ok(TextFilterConditionPB::IsNot), - 2 => Ok(TextFilterConditionPB::Contains), - 3 => Ok(TextFilterConditionPB::DoesNotContain), - 4 => Ok(TextFilterConditionPB::StartsWith), - 5 => Ok(TextFilterConditionPB::EndsWith), - 6 => Ok(TextFilterConditionPB::TextIsEmpty), - 7 => Ok(TextFilterConditionPB::TextIsNotEmpty), - _ => Err(ErrorCode::InvalidData), - } + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(TextFilterConditionPB::Is), + 1 => Ok(TextFilterConditionPB::IsNot), + 2 => Ok(TextFilterConditionPB::Contains), + 3 => Ok(TextFilterConditionPB::DoesNotContain), + 4 => Ok(TextFilterConditionPB::StartsWith), + 5 => Ok(TextFilterConditionPB::EndsWith), + 6 => Ok(TextFilterConditionPB::TextIsEmpty), + 7 => Ok(TextFilterConditionPB::TextIsNotEmpty), + _ => Err(ErrorCode::InvalidData), } + } } impl FromFilterString for TextFilterPB { - fn from_filter_rev(filter_rev: &FilterRevision) -> Self - where - Self: Sized, - { - TextFilterPB { - condition: TextFilterConditionPB::try_from(filter_rev.condition).unwrap_or(TextFilterConditionPB::Is), - content: filter_rev.content.clone(), - } + fn from_filter_rev(filter_rev: &FilterRevision) -> Self + where + Self: Sized, + { + TextFilterPB { + condition: TextFilterConditionPB::try_from(filter_rev.condition) + .unwrap_or(TextFilterConditionPB::Is), + content: filter_rev.content.clone(), } + } } impl std::convert::From<&FilterRevision> for TextFilterPB { - fn from(rev: &FilterRevision) -> Self { - TextFilterPB { - condition: TextFilterConditionPB::try_from(rev.condition).unwrap_or(TextFilterConditionPB::Is), - content: rev.content.clone(), - } + fn from(rev: &FilterRevision) -> Self { + TextFilterPB { + condition: TextFilterConditionPB::try_from(rev.condition) + .unwrap_or(TextFilterConditionPB::Is), + content: rev.content.clone(), } + } } diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/util.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/util.rs index 65e765a40d..e1f8119fdb 100644 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/util.rs +++ b/frontend/rust-lib/flowy-database/src/entities/filter_entities/util.rs @@ -1,7 +1,7 @@ use crate::entities::parser::NotEmptyStr; use crate::entities::{ - CheckboxFilterPB, ChecklistFilterPB, DateFilterContentPB, DateFilterPB, FieldType, NumberFilterPB, - SelectOptionFilterPB, TextFilterPB, + CheckboxFilterPB, ChecklistFilterPB, DateFilterContentPB, DateFilterPB, FieldType, + NumberFilterPB, SelectOptionFilterPB, TextFilterPB, }; use crate::services::field::SelectOptionIds; use crate::services::filter::FilterType; @@ -14,217 +14,221 @@ use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct FilterPB { - #[pb(index = 1)] - pub id: String, + #[pb(index = 1)] + pub id: String, - #[pb(index = 2)] - pub field_id: String, + #[pb(index = 2)] + pub field_id: String, - #[pb(index = 3)] - pub field_type: FieldType, + #[pb(index = 3)] + pub field_type: FieldType, - #[pb(index = 4)] - pub data: Vec, + #[pb(index = 4)] + pub data: Vec, } impl std::convert::From<&FilterRevision> for FilterPB { - fn from(rev: &FilterRevision) -> Self { - let field_type: FieldType = rev.field_type.into(); - let bytes: Bytes = match field_type { - FieldType::RichText => TextFilterPB::from(rev).try_into().unwrap(), - FieldType::Number => NumberFilterPB::from(rev).try_into().unwrap(), - FieldType::DateTime => DateFilterPB::from(rev).try_into().unwrap(), - FieldType::SingleSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(), - FieldType::MultiSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(), - FieldType::Checklist => ChecklistFilterPB::from(rev).try_into().unwrap(), - FieldType::Checkbox => CheckboxFilterPB::from(rev).try_into().unwrap(), - FieldType::URL => TextFilterPB::from(rev).try_into().unwrap(), - }; - Self { - id: rev.id.clone(), - field_id: rev.field_id.clone(), - field_type: rev.field_type.into(), - data: bytes.to_vec(), - } + fn from(rev: &FilterRevision) -> Self { + let field_type: FieldType = rev.field_type.into(); + let bytes: Bytes = match field_type { + FieldType::RichText => TextFilterPB::from(rev).try_into().unwrap(), + FieldType::Number => NumberFilterPB::from(rev).try_into().unwrap(), + FieldType::DateTime => DateFilterPB::from(rev).try_into().unwrap(), + FieldType::SingleSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(), + FieldType::MultiSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(), + FieldType::Checklist => ChecklistFilterPB::from(rev).try_into().unwrap(), + FieldType::Checkbox => CheckboxFilterPB::from(rev).try_into().unwrap(), + FieldType::URL => TextFilterPB::from(rev).try_into().unwrap(), + }; + Self { + id: rev.id.clone(), + field_id: rev.field_id.clone(), + field_type: rev.field_type.into(), + data: bytes.to_vec(), } + } } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct RepeatedFilterPB { - #[pb(index = 1)] - pub items: Vec, + #[pb(index = 1)] + pub items: Vec, } impl std::convert::From>> for RepeatedFilterPB { - fn from(revs: Vec>) -> Self { - RepeatedFilterPB { - items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(), - } + fn from(revs: Vec>) -> Self { + RepeatedFilterPB { + items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(), } + } } impl std::convert::From> for RepeatedFilterPB { - fn from(items: Vec) -> Self { - Self { items } - } + fn from(items: Vec) -> Self { + Self { items } + } } #[derive(ProtoBuf, Debug, Default, Clone)] pub struct DeleteFilterPayloadPB { - #[pb(index = 1)] - pub field_id: String, + #[pb(index = 1)] + pub field_id: String, - #[pb(index = 2)] - pub field_type: FieldType, + #[pb(index = 2)] + pub field_type: FieldType, - #[pb(index = 3)] - pub filter_id: String, + #[pb(index = 3)] + pub filter_id: String, - #[pb(index = 4)] - pub view_id: String, + #[pb(index = 4)] + pub view_id: String, } impl TryInto for DeleteFilterPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id) - .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? - .0; - let field_id = NotEmptyStr::parse(self.field_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; + fn try_into(self) -> Result { + let view_id = NotEmptyStr::parse(self.view_id) + .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? + .0; + let field_id = NotEmptyStr::parse(self.field_id) + .map_err(|_| ErrorCode::FieldIdIsEmpty)? + .0; - let filter_id = NotEmptyStr::parse(self.filter_id) - .map_err(|_| ErrorCode::UnexpectedEmptyString)? - .0; + let filter_id = NotEmptyStr::parse(self.filter_id) + .map_err(|_| ErrorCode::UnexpectedEmptyString)? + .0; - let filter_type = FilterType { - field_id, - field_type: self.field_type, - }; + let filter_type = FilterType { + field_id, + field_type: self.field_type, + }; - Ok(DeleteFilterParams { - view_id, - filter_id, - filter_type, - }) - } + Ok(DeleteFilterParams { + view_id, + filter_id, + filter_type, + }) + } } #[derive(Debug)] pub struct DeleteFilterParams { - pub view_id: String, - pub filter_type: FilterType, - pub filter_id: String, + pub view_id: String, + pub filter_type: FilterType, + pub filter_id: String, } #[derive(ProtoBuf, Debug, Default, Clone)] pub struct AlterFilterPayloadPB { - #[pb(index = 1)] - pub field_id: String, + #[pb(index = 1)] + pub field_id: String, - #[pb(index = 2)] - pub field_type: FieldType, + #[pb(index = 2)] + pub field_type: FieldType, - /// Create a new filter if the filter_id is None - #[pb(index = 3, one_of)] - pub filter_id: Option, + /// Create a new filter if the filter_id is None + #[pb(index = 3, one_of)] + pub filter_id: Option, - #[pb(index = 4)] - pub data: Vec, + #[pb(index = 4)] + pub data: Vec, - #[pb(index = 5)] - pub view_id: String, + #[pb(index = 5)] + pub view_id: String, } impl AlterFilterPayloadPB { - #[allow(dead_code)] - pub fn new>( - view_id: &str, - field_rev: &FieldRevision, - data: T, - ) -> Self { - let data = data.try_into().unwrap_or_else(|_| Bytes::new()); - Self { - view_id: view_id.to_owned(), - field_id: field_rev.id.clone(), - field_type: field_rev.ty.into(), - filter_id: None, - data: data.to_vec(), - } + #[allow(dead_code)] + pub fn new>( + view_id: &str, + field_rev: &FieldRevision, + data: T, + ) -> Self { + let data = data.try_into().unwrap_or_else(|_| Bytes::new()); + Self { + view_id: view_id.to_owned(), + field_id: field_rev.id.clone(), + field_type: field_rev.ty.into(), + filter_id: None, + data: data.to_vec(), } + } } impl TryInto for AlterFilterPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id) - .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? - .0; + fn try_into(self) -> Result { + let view_id = NotEmptyStr::parse(self.view_id) + .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? + .0; - let field_id = NotEmptyStr::parse(self.field_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; - let filter_id = match self.filter_id { - None => None, - Some(filter_id) => Some(NotEmptyStr::parse(filter_id).map_err(|_| ErrorCode::FilterIdIsEmpty)?.0), - }; - let condition; - let mut content = "".to_string(); - let bytes: &[u8] = self.data.as_ref(); + let field_id = NotEmptyStr::parse(self.field_id) + .map_err(|_| ErrorCode::FieldIdIsEmpty)? + .0; + let filter_id = match self.filter_id { + None => None, + Some(filter_id) => Some( + NotEmptyStr::parse(filter_id) + .map_err(|_| ErrorCode::FilterIdIsEmpty)? + .0, + ), + }; + let condition; + let mut content = "".to_string(); + let bytes: &[u8] = self.data.as_ref(); - match self.field_type { - FieldType::RichText | FieldType::URL => { - let filter = TextFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; - condition = filter.condition as u8; - content = filter.content; - } - FieldType::Checkbox => { - let filter = CheckboxFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; - condition = filter.condition as u8; - } - FieldType::Number => { - let filter = NumberFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; - condition = filter.condition as u8; - content = filter.content; - } - FieldType::DateTime => { - let filter = DateFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; - condition = filter.condition as u8; - content = DateFilterContentPB { - start: filter.start, - end: filter.end, - timestamp: filter.timestamp, - } - .to_string(); - } - FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => { - let filter = SelectOptionFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; - condition = filter.condition as u8; - content = SelectOptionIds::from(filter.option_ids).to_string(); - } + match self.field_type { + FieldType::RichText | FieldType::URL => { + let filter = TextFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; + condition = filter.condition as u8; + content = filter.content; + }, + FieldType::Checkbox => { + let filter = CheckboxFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; + condition = filter.condition as u8; + }, + FieldType::Number => { + let filter = NumberFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; + condition = filter.condition as u8; + content = filter.content; + }, + FieldType::DateTime => { + let filter = DateFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; + condition = filter.condition as u8; + content = DateFilterContentPB { + start: filter.start, + end: filter.end, + timestamp: filter.timestamp, } - - Ok(AlterFilterParams { - view_id, - field_id, - filter_id, - field_type: self.field_type.into(), - condition, - content, - }) + .to_string(); + }, + FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => { + let filter = SelectOptionFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; + condition = filter.condition as u8; + content = SelectOptionIds::from(filter.option_ids).to_string(); + }, } + + Ok(AlterFilterParams { + view_id, + field_id, + filter_id, + field_type: self.field_type.into(), + condition, + content, + }) + } } #[derive(Debug)] pub struct AlterFilterParams { - pub view_id: String, - pub field_id: String, - /// Create a new filter if the filter_id is None - pub filter_id: Option, - pub field_type: FieldTypeRevision, - pub condition: u8, - pub content: String, + pub view_id: String, + pub field_id: String, + /// Create a new filter if the filter_id is None + pub filter_id: Option, + pub field_type: FieldTypeRevision, + pub condition: u8, + pub content: String, } diff --git a/frontend/rust-lib/flowy-database/src/entities/grid_entities.rs b/frontend/rust-lib/flowy-database/src/entities/grid_entities.rs index 9baf6b7357..09e5ac8667 100644 --- a/frontend/rust-lib/flowy-database/src/entities/grid_entities.rs +++ b/frontend/rust-lib/flowy-database/src/entities/grid_entities.rs @@ -6,145 +6,150 @@ use flowy_error::ErrorCode; /// [DatabasePB] describes how many fields and blocks the grid has #[derive(Debug, Clone, Default, ProtoBuf)] pub struct DatabasePB { - #[pb(index = 1)] - pub id: String, + #[pb(index = 1)] + pub id: String, - #[pb(index = 2)] - pub fields: Vec, + #[pb(index = 2)] + pub fields: Vec, - #[pb(index = 3)] - pub rows: Vec, + #[pb(index = 3)] + pub rows: Vec, } #[derive(ProtoBuf, Default)] pub struct CreateDatabasePayloadPB { - #[pb(index = 1)] - pub name: String, + #[pb(index = 1)] + pub name: String, } #[derive(Clone, ProtoBuf, Default, Debug)] pub struct DatabaseIdPB { - #[pb(index = 1)] - pub value: String, + #[pb(index = 1)] + pub value: String, } impl AsRef for DatabaseIdPB { - fn as_ref(&self) -> &str { - &self.value - } + fn as_ref(&self) -> &str { + &self.value + } } #[derive(Debug, Clone, Default, ProtoBuf)] pub struct MoveFieldPayloadPB { - #[pb(index = 1)] - pub view_id: String, + #[pb(index = 1)] + pub view_id: String, - #[pb(index = 2)] - pub field_id: String, + #[pb(index = 2)] + pub field_id: String, - #[pb(index = 3)] - pub from_index: i32, + #[pb(index = 3)] + pub from_index: i32, - #[pb(index = 4)] - pub to_index: i32, + #[pb(index = 4)] + pub to_index: i32, } #[derive(Clone)] pub struct MoveFieldParams { - pub view_id: String, - pub field_id: String, - pub from_index: i32, - pub to_index: i32, + pub view_id: String, + pub field_id: String, + pub from_index: i32, + pub to_index: i32, } impl TryInto for MoveFieldPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?; - let item_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::InvalidData)?; - Ok(MoveFieldParams { - view_id: view_id.0, - field_id: item_id.0, - from_index: self.from_index, - to_index: self.to_index, - }) - } + fn try_into(self) -> Result { + let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?; + let item_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::InvalidData)?; + Ok(MoveFieldParams { + view_id: view_id.0, + field_id: item_id.0, + from_index: self.from_index, + to_index: self.to_index, + }) + } } #[derive(Debug, Clone, Default, ProtoBuf)] pub struct MoveRowPayloadPB { - #[pb(index = 1)] - pub view_id: String, + #[pb(index = 1)] + pub view_id: String, - #[pb(index = 2)] - pub from_row_id: String, + #[pb(index = 2)] + pub from_row_id: String, - #[pb(index = 4)] - pub to_row_id: String, + #[pb(index = 4)] + pub to_row_id: String, } pub struct MoveRowParams { - pub view_id: String, - pub from_row_id: String, - pub to_row_id: String, + pub view_id: String, + pub from_row_id: String, + pub to_row_id: String, } impl TryInto for MoveRowPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?; - let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; - let to_row_id = NotEmptyStr::parse(self.to_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; + fn try_into(self) -> Result { + let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?; + let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; + let to_row_id = NotEmptyStr::parse(self.to_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; - Ok(MoveRowParams { - view_id: view_id.0, - from_row_id: from_row_id.0, - to_row_id: to_row_id.0, - }) - } + Ok(MoveRowParams { + view_id: view_id.0, + from_row_id: from_row_id.0, + to_row_id: to_row_id.0, + }) + } } #[derive(Debug, Clone, Default, ProtoBuf)] pub struct MoveGroupRowPayloadPB { - #[pb(index = 1)] - pub view_id: String, + #[pb(index = 1)] + pub view_id: String, - #[pb(index = 2)] - pub from_row_id: String, + #[pb(index = 2)] + pub from_row_id: String, - #[pb(index = 3)] - pub to_group_id: String, + #[pb(index = 3)] + pub to_group_id: String, - #[pb(index = 4, one_of)] - pub to_row_id: Option, + #[pb(index = 4, one_of)] + pub to_row_id: Option, } pub struct MoveGroupRowParams { - pub view_id: String, - pub from_row_id: String, - pub to_group_id: String, - pub to_row_id: Option, + pub view_id: String, + pub from_row_id: String, + pub to_group_id: String, + pub to_row_id: Option, } impl TryInto for MoveGroupRowPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?; - let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; - let to_group_id = NotEmptyStr::parse(self.to_group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?; + fn try_into(self) -> Result { + let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?; + let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; + let to_group_id = + NotEmptyStr::parse(self.to_group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?; - let to_row_id = match self.to_row_id { - None => None, - Some(to_row_id) => Some(NotEmptyStr::parse(to_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?.0), - }; + let to_row_id = match self.to_row_id { + None => None, + Some(to_row_id) => Some( + NotEmptyStr::parse(to_row_id) + .map_err(|_| ErrorCode::RowIdIsEmpty)? + .0, + ), + }; - Ok(MoveGroupRowParams { - view_id: view_id.0, - from_row_id: from_row_id.0, - to_group_id: to_group_id.0, - to_row_id, - }) - } + Ok(MoveGroupRowParams { + view_id: view_id.0, + from_row_id: from_row_id.0, + to_group_id: to_group_id.0, + to_row_id, + }) + } } diff --git a/frontend/rust-lib/flowy-database/src/entities/group_entities/configuration.rs b/frontend/rust-lib/flowy-database/src/entities/group_entities/configuration.rs index 28ab331798..13dfcc6069 100644 --- a/frontend/rust-lib/flowy-database/src/entities/group_entities/configuration.rs +++ b/frontend/rust-lib/flowy-database/src/entities/group_entities/configuration.rs @@ -3,81 +3,83 @@ use grid_model::{GroupRevision, SelectOptionGroupConfigurationRevision}; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct UrlGroupConfigurationPB { - #[pb(index = 1)] - hide_empty: bool, + #[pb(index = 1)] + hide_empty: bool, } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct TextGroupConfigurationPB { - #[pb(index = 1)] - hide_empty: bool, + #[pb(index = 1)] + hide_empty: bool, } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct SelectOptionGroupConfigurationPB { - #[pb(index = 1)] - hide_empty: bool, + #[pb(index = 1)] + hide_empty: bool, } -impl std::convert::From for SelectOptionGroupConfigurationPB { - fn from(rev: SelectOptionGroupConfigurationRevision) -> Self { - Self { - hide_empty: rev.hide_empty, - } +impl std::convert::From + for SelectOptionGroupConfigurationPB +{ + fn from(rev: SelectOptionGroupConfigurationRevision) -> Self { + Self { + hide_empty: rev.hide_empty, } + } } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct GroupRecordPB { - #[pb(index = 1)] - group_id: String, + #[pb(index = 1)] + group_id: String, - #[pb(index = 2)] - visible: bool, + #[pb(index = 2)] + visible: bool, } impl std::convert::From for GroupRecordPB { - fn from(rev: GroupRevision) -> Self { - Self { - group_id: rev.id, - visible: rev.visible, - } + fn from(rev: GroupRevision) -> Self { + Self { + group_id: rev.id, + visible: rev.visible, } + } } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct NumberGroupConfigurationPB { - #[pb(index = 1)] - hide_empty: bool, + #[pb(index = 1)] + hide_empty: bool, } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct DateGroupConfigurationPB { - #[pb(index = 1)] - pub condition: DateCondition, + #[pb(index = 1)] + pub condition: DateCondition, - #[pb(index = 2)] - hide_empty: bool, + #[pb(index = 2)] + hide_empty: bool, } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] pub enum DateCondition { - Relative = 0, - Day = 1, - Week = 2, - Month = 3, - Year = 4, + Relative = 0, + Day = 1, + Week = 2, + Month = 3, + Year = 4, } impl std::default::Default for DateCondition { - fn default() -> Self { - DateCondition::Relative - } + fn default() -> Self { + DateCondition::Relative + } } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct CheckboxGroupConfigurationPB { - #[pb(index = 1)] - pub(crate) hide_empty: bool, + #[pb(index = 1)] + pub(crate) hide_empty: bool, } diff --git a/frontend/rust-lib/flowy-database/src/entities/group_entities/group.rs b/frontend/rust-lib/flowy-database/src/entities/group_entities/group.rs index 76c33bf7a7..cff0eafc2c 100644 --- a/frontend/rust-lib/flowy-database/src/entities/group_entities/group.rs +++ b/frontend/rust-lib/flowy-database/src/entities/group_entities/group.rs @@ -9,188 +9,193 @@ use std::sync::Arc; #[derive(ProtoBuf, Debug, Default, Clone)] pub struct CreateBoardCardPayloadPB { - #[pb(index = 1)] - pub database_id: String, + #[pb(index = 1)] + pub database_id: String, - #[pb(index = 2)] - pub group_id: String, + #[pb(index = 2)] + pub group_id: String, - #[pb(index = 3, one_of)] - pub start_row_id: Option, + #[pb(index = 3, one_of)] + pub start_row_id: Option, } impl TryInto for CreateBoardCardPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let database_id = NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; - let group_id = NotEmptyStr::parse(self.group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?; - let start_row_id = match self.start_row_id { - None => None, - Some(start_row_id) => Some(NotEmptyStr::parse(start_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?.0), - }; - Ok(CreateRowParams { - database_id: database_id.0, - start_row_id, - group_id: Some(group_id.0), - layout: LayoutTypePB::Board, - }) - } + fn try_into(self) -> Result { + let database_id = + NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; + let group_id = NotEmptyStr::parse(self.group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?; + let start_row_id = match self.start_row_id { + None => None, + Some(start_row_id) => Some( + NotEmptyStr::parse(start_row_id) + .map_err(|_| ErrorCode::RowIdIsEmpty)? + .0, + ), + }; + Ok(CreateRowParams { + database_id: database_id.0, + start_row_id, + group_id: Some(group_id.0), + layout: LayoutTypePB::Board, + }) + } } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct GroupConfigurationPB { - #[pb(index = 1)] - pub id: String, + #[pb(index = 1)] + pub id: String, - #[pb(index = 2)] - pub field_id: String, + #[pb(index = 2)] + pub field_id: String, } impl std::convert::From<&GroupConfigurationRevision> for GroupConfigurationPB { - fn from(rev: &GroupConfigurationRevision) -> Self { - GroupConfigurationPB { - id: rev.id.clone(), - field_id: rev.field_id.clone(), - } + fn from(rev: &GroupConfigurationRevision) -> Self { + GroupConfigurationPB { + id: rev.id.clone(), + field_id: rev.field_id.clone(), } + } } #[derive(ProtoBuf, Debug, Default, Clone)] pub struct RepeatedGroupPB { - #[pb(index = 1)] - pub items: Vec, + #[pb(index = 1)] + pub items: Vec, } impl std::ops::Deref for RepeatedGroupPB { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.items - } + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.items + } } impl std::ops::DerefMut for RepeatedGroupPB { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.items - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.items + } } #[derive(ProtoBuf, Debug, Default, Clone)] pub struct GroupPB { - #[pb(index = 1)] - pub field_id: String, + #[pb(index = 1)] + pub field_id: String, - #[pb(index = 2)] - pub group_id: String, + #[pb(index = 2)] + pub group_id: String, - #[pb(index = 3)] - pub desc: String, + #[pb(index = 3)] + pub desc: String, - #[pb(index = 4)] - pub rows: Vec, + #[pb(index = 4)] + pub rows: Vec, - #[pb(index = 5)] - pub is_default: bool, + #[pb(index = 5)] + pub is_default: bool, - #[pb(index = 6)] - pub is_visible: bool, + #[pb(index = 6)] + pub is_visible: bool, } impl std::convert::From for GroupPB { - fn from(group: Group) -> Self { - Self { - field_id: group.field_id, - group_id: group.id, - desc: group.name, - rows: group.rows, - is_default: group.is_default, - is_visible: group.is_visible, - } + fn from(group: Group) -> Self { + Self { + field_id: group.field_id, + group_id: group.id, + desc: group.name, + rows: group.rows, + is_default: group.is_default, + is_visible: group.is_visible, } + } } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct RepeatedGroupConfigurationPB { - #[pb(index = 1)] - pub items: Vec, + #[pb(index = 1)] + pub items: Vec, } impl std::convert::From> for RepeatedGroupConfigurationPB { - fn from(items: Vec) -> Self { - Self { items } - } + fn from(items: Vec) -> Self { + Self { items } + } } impl std::convert::From>> for RepeatedGroupConfigurationPB { - fn from(revs: Vec>) -> Self { - RepeatedGroupConfigurationPB { - items: revs.iter().map(|rev| rev.as_ref().into()).collect(), - } + fn from(revs: Vec>) -> Self { + RepeatedGroupConfigurationPB { + items: revs.iter().map(|rev| rev.as_ref().into()).collect(), } + } } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct InsertGroupPayloadPB { - #[pb(index = 1)] - pub field_id: String, + #[pb(index = 1)] + pub field_id: String, - #[pb(index = 2)] - pub field_type: FieldType, + #[pb(index = 2)] + pub field_type: FieldType, } impl TryInto for InsertGroupPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let field_id = NotEmptyStr::parse(self.field_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; + fn try_into(self) -> Result { + let field_id = NotEmptyStr::parse(self.field_id) + .map_err(|_| ErrorCode::FieldIdIsEmpty)? + .0; - Ok(InsertGroupParams { - field_id, - field_type_rev: self.field_type.into(), - }) - } + Ok(InsertGroupParams { + field_id, + field_type_rev: self.field_type.into(), + }) + } } pub struct InsertGroupParams { - pub field_id: String, - pub field_type_rev: FieldTypeRevision, + pub field_id: String, + pub field_type_rev: FieldTypeRevision, } #[derive(ProtoBuf, Debug, Default, Clone)] pub struct DeleteGroupPayloadPB { - #[pb(index = 1)] - pub field_id: String, + #[pb(index = 1)] + pub field_id: String, - #[pb(index = 2)] - pub group_id: String, + #[pb(index = 2)] + pub group_id: String, - #[pb(index = 3)] - pub field_type: FieldType, + #[pb(index = 3)] + pub field_type: FieldType, } impl TryInto for DeleteGroupPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let field_id = NotEmptyStr::parse(self.field_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; - let group_id = NotEmptyStr::parse(self.group_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; + fn try_into(self) -> Result { + let field_id = NotEmptyStr::parse(self.field_id) + .map_err(|_| ErrorCode::FieldIdIsEmpty)? + .0; + let group_id = NotEmptyStr::parse(self.group_id) + .map_err(|_| ErrorCode::FieldIdIsEmpty)? + .0; - Ok(DeleteGroupParams { - field_id, - field_type_rev: self.field_type.into(), - group_id, - }) - } + Ok(DeleteGroupParams { + field_id, + field_type_rev: self.field_type.into(), + group_id, + }) + } } pub struct DeleteGroupParams { - pub field_id: String, - pub group_id: String, - pub field_type_rev: FieldTypeRevision, + pub field_id: String, + pub group_id: String, + pub field_type_rev: FieldTypeRevision, } diff --git a/frontend/rust-lib/flowy-database/src/entities/group_entities/group_changeset.rs b/frontend/rust-lib/flowy-database/src/entities/group_entities/group_changeset.rs index 60af3f8af5..1fe5385fb8 100644 --- a/frontend/rust-lib/flowy-database/src/entities/group_entities/group_changeset.rs +++ b/frontend/rust-lib/flowy-database/src/entities/group_entities/group_changeset.rs @@ -6,158 +6,158 @@ use std::fmt::Formatter; #[derive(Debug, Default, ProtoBuf)] pub struct GroupRowsNotificationPB { - #[pb(index = 1)] - pub group_id: String, + #[pb(index = 1)] + pub group_id: String, - #[pb(index = 2, one_of)] - pub group_name: Option, + #[pb(index = 2, one_of)] + pub group_name: Option, - #[pb(index = 3)] - pub inserted_rows: Vec, + #[pb(index = 3)] + pub inserted_rows: Vec, - #[pb(index = 4)] - pub deleted_rows: Vec, + #[pb(index = 4)] + pub deleted_rows: Vec, - #[pb(index = 5)] - pub updated_rows: Vec, + #[pb(index = 5)] + pub updated_rows: Vec, } impl std::fmt::Display for GroupRowsNotificationPB { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - for inserted_row in &self.inserted_rows { - f.write_fmt(format_args!( - "Insert: {} row at {:?}", - inserted_row.row.id, inserted_row.index - ))?; - } - - for deleted_row in &self.deleted_rows { - f.write_fmt(format_args!("Delete: {} row", deleted_row))?; - } - - Ok(()) + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for inserted_row in &self.inserted_rows { + f.write_fmt(format_args!( + "Insert: {} row at {:?}", + inserted_row.row.id, inserted_row.index + ))?; } + + for deleted_row in &self.deleted_rows { + f.write_fmt(format_args!("Delete: {} row", deleted_row))?; + } + + Ok(()) + } } impl GroupRowsNotificationPB { - pub fn is_empty(&self) -> bool { - self.group_name.is_none() - && self.inserted_rows.is_empty() - && self.deleted_rows.is_empty() - && self.updated_rows.is_empty() - } + pub fn is_empty(&self) -> bool { + self.group_name.is_none() + && self.inserted_rows.is_empty() + && self.deleted_rows.is_empty() + && self.updated_rows.is_empty() + } - pub fn new(group_id: String) -> Self { - Self { - group_id, - ..Default::default() - } + pub fn new(group_id: String) -> Self { + Self { + group_id, + ..Default::default() } + } - pub fn name(group_id: String, name: &str) -> Self { - Self { - group_id, - group_name: Some(name.to_owned()), - ..Default::default() - } + pub fn name(group_id: String, name: &str) -> Self { + Self { + group_id, + group_name: Some(name.to_owned()), + ..Default::default() } + } - pub fn insert(group_id: String, inserted_rows: Vec) -> Self { - Self { - group_id, - inserted_rows, - ..Default::default() - } + pub fn insert(group_id: String, inserted_rows: Vec) -> Self { + Self { + group_id, + inserted_rows, + ..Default::default() } + } - pub fn delete(group_id: String, deleted_rows: Vec) -> Self { - Self { - group_id, - deleted_rows, - ..Default::default() - } + pub fn delete(group_id: String, deleted_rows: Vec) -> Self { + Self { + group_id, + deleted_rows, + ..Default::default() } + } - pub fn update(group_id: String, updated_rows: Vec) -> Self { - Self { - group_id, - updated_rows, - ..Default::default() - } + pub fn update(group_id: String, updated_rows: Vec) -> Self { + Self { + group_id, + updated_rows, + ..Default::default() } + } } #[derive(Debug, Default, ProtoBuf)] pub struct MoveGroupPayloadPB { - #[pb(index = 1)] - pub view_id: String, + #[pb(index = 1)] + pub view_id: String, - #[pb(index = 2)] - pub from_group_id: String, + #[pb(index = 2)] + pub from_group_id: String, - #[pb(index = 3)] - pub to_group_id: String, + #[pb(index = 3)] + pub to_group_id: String, } #[derive(Debug)] pub struct MoveGroupParams { - pub view_id: String, - pub from_group_id: String, - pub to_group_id: String, + pub view_id: String, + pub from_group_id: String, + pub to_group_id: String, } impl TryInto for MoveGroupPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id) - .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? - .0; - let from_group_id = NotEmptyStr::parse(self.from_group_id) - .map_err(|_| ErrorCode::GroupIdIsEmpty)? - .0; - let to_group_id = NotEmptyStr::parse(self.to_group_id) - .map_err(|_| ErrorCode::GroupIdIsEmpty)? - .0; - Ok(MoveGroupParams { - view_id, - from_group_id, - to_group_id, - }) - } + fn try_into(self) -> Result { + let view_id = NotEmptyStr::parse(self.view_id) + .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? + .0; + let from_group_id = NotEmptyStr::parse(self.from_group_id) + .map_err(|_| ErrorCode::GroupIdIsEmpty)? + .0; + let to_group_id = NotEmptyStr::parse(self.to_group_id) + .map_err(|_| ErrorCode::GroupIdIsEmpty)? + .0; + Ok(MoveGroupParams { + view_id, + from_group_id, + to_group_id, + }) + } } #[derive(Debug, Default, ProtoBuf)] pub struct GroupChangesetPB { - #[pb(index = 1)] - pub view_id: String, + #[pb(index = 1)] + pub view_id: String, - #[pb(index = 2)] - pub inserted_groups: Vec, + #[pb(index = 2)] + pub inserted_groups: Vec, - #[pb(index = 3)] - pub initial_groups: Vec, + #[pb(index = 3)] + pub initial_groups: Vec, - #[pb(index = 4)] - pub deleted_groups: Vec, + #[pb(index = 4)] + pub deleted_groups: Vec, - #[pb(index = 5)] - pub update_groups: Vec, + #[pb(index = 5)] + pub update_groups: Vec, } impl GroupChangesetPB { - pub fn is_empty(&self) -> bool { - self.initial_groups.is_empty() - && self.inserted_groups.is_empty() - && self.deleted_groups.is_empty() - && self.update_groups.is_empty() - } + pub fn is_empty(&self) -> bool { + self.initial_groups.is_empty() + && self.inserted_groups.is_empty() + && self.deleted_groups.is_empty() + && self.update_groups.is_empty() + } } #[derive(Debug, Default, ProtoBuf)] pub struct InsertedGroupPB { - #[pb(index = 1)] - pub group: GroupPB, + #[pb(index = 1)] + pub group: GroupPB, - #[pb(index = 2)] - pub index: i32, + #[pb(index = 2)] + pub index: i32, } diff --git a/frontend/rust-lib/flowy-database/src/entities/parser.rs b/frontend/rust-lib/flowy-database/src/entities/parser.rs index 175ff3fd49..edad3ee6b8 100644 --- a/frontend/rust-lib/flowy-database/src/entities/parser.rs +++ b/frontend/rust-lib/flowy-database/src/entities/parser.rs @@ -2,16 +2,16 @@ pub struct NotEmptyStr(pub String); impl NotEmptyStr { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err("Input string is empty".to_owned()); - } - Ok(Self(s)) + pub fn parse(s: String) -> Result { + if s.trim().is_empty() { + return Err("Input string is empty".to_owned()); } + Ok(Self(s)) + } } impl AsRef for NotEmptyStr { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } diff --git a/frontend/rust-lib/flowy-database/src/entities/row_entities.rs b/frontend/rust-lib/flowy-database/src/entities/row_entities.rs index d7702a63ad..e3f43396e0 100644 --- a/frontend/rust-lib/flowy-database/src/entities/row_entities.rs +++ b/frontend/rust-lib/flowy-database/src/entities/row_entities.rs @@ -8,196 +8,198 @@ use std::sync::Arc; /// [RowPB] Describes a row. Has the id of the parent Block. Has the metadata of the row. #[derive(Debug, Default, Clone, ProtoBuf, Eq, PartialEq)] pub struct RowPB { - #[pb(index = 1)] - pub block_id: String, + #[pb(index = 1)] + pub block_id: String, - #[pb(index = 2)] - pub id: String, + #[pb(index = 2)] + pub id: String, - #[pb(index = 3)] - pub height: i32, + #[pb(index = 3)] + pub height: i32, } impl RowPB { - pub fn row_id(&self) -> &str { - &self.id - } + pub fn row_id(&self) -> &str { + &self.id + } - pub fn block_id(&self) -> &str { - &self.block_id - } + pub fn block_id(&self) -> &str { + &self.block_id + } } impl std::convert::From<&RowRevision> for RowPB { - fn from(rev: &RowRevision) -> Self { - Self { - block_id: rev.block_id.clone(), - id: rev.id.clone(), - height: rev.height, - } + fn from(rev: &RowRevision) -> Self { + Self { + block_id: rev.block_id.clone(), + id: rev.id.clone(), + height: rev.height, } + } } impl std::convert::From<&mut RowRevision> for RowPB { - fn from(rev: &mut RowRevision) -> Self { - Self { - block_id: rev.block_id.clone(), - id: rev.id.clone(), - height: rev.height, - } + fn from(rev: &mut RowRevision) -> Self { + Self { + block_id: rev.block_id.clone(), + id: rev.id.clone(), + height: rev.height, } + } } impl std::convert::From<&Arc> for RowPB { - fn from(rev: &Arc) -> Self { - Self { - block_id: rev.block_id.clone(), - id: rev.id.clone(), - height: rev.height, - } + fn from(rev: &Arc) -> Self { + Self { + block_id: rev.block_id.clone(), + id: rev.id.clone(), + height: rev.height, } + } } #[derive(Debug, Default, ProtoBuf)] pub struct OptionalRowPB { - #[pb(index = 1, one_of)] - pub row: Option, + #[pb(index = 1, one_of)] + pub row: Option, } #[derive(Debug, Default, ProtoBuf)] pub struct RepeatedRowPB { - #[pb(index = 1)] - pub items: Vec, + #[pb(index = 1)] + pub items: Vec, } impl std::convert::From> for RepeatedRowPB { - fn from(items: Vec) -> Self { - Self { items } - } + fn from(items: Vec) -> Self { + Self { items } + } } #[derive(Debug, Clone, Default, ProtoBuf)] pub struct InsertedRowPB { - #[pb(index = 1)] - pub row: RowPB, + #[pb(index = 1)] + pub row: RowPB, - #[pb(index = 2, one_of)] - pub index: Option, + #[pb(index = 2, one_of)] + pub index: Option, - #[pb(index = 3)] - pub is_new: bool, + #[pb(index = 3)] + pub is_new: bool, } impl InsertedRowPB { - pub fn new(row: RowPB) -> Self { - Self { - row, - index: None, - is_new: false, - } + pub fn new(row: RowPB) -> Self { + Self { + row, + index: None, + is_new: false, } + } - pub fn with_index(row: RowPB, index: i32) -> Self { - Self { - row, - index: Some(index), - is_new: false, - } + pub fn with_index(row: RowPB, index: i32) -> Self { + Self { + row, + index: Some(index), + is_new: false, } + } } impl std::convert::From for InsertedRowPB { - fn from(row: RowPB) -> Self { - Self { - row, - index: None, - is_new: false, - } + fn from(row: RowPB) -> Self { + Self { + row, + index: None, + is_new: false, } + } } impl std::convert::From<&RowRevision> for InsertedRowPB { - fn from(row: &RowRevision) -> Self { - let row_order = RowPB::from(row); - Self::from(row_order) - } + fn from(row: &RowRevision) -> Self { + let row_order = RowPB::from(row); + Self::from(row_order) + } } #[derive(Debug, Clone, Default, ProtoBuf)] pub struct UpdatedRowPB { - #[pb(index = 1)] - pub row: RowPB, + #[pb(index = 1)] + pub row: RowPB, - // represents as the cells that were updated in this row. - #[pb(index = 2)] - pub field_ids: Vec, + // represents as the cells that were updated in this row. + #[pb(index = 2)] + pub field_ids: Vec, } #[derive(Debug, Default, Clone, ProtoBuf)] pub struct RowIdPB { - #[pb(index = 1)] - pub database_id: String, + #[pb(index = 1)] + pub database_id: String, - #[pb(index = 2)] - pub row_id: String, + #[pb(index = 2)] + pub row_id: String, } pub struct RowIdParams { - pub database_id: String, - pub row_id: String, + pub database_id: String, + pub row_id: String, } impl TryInto for RowIdPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let database_id = NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; - let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; + fn try_into(self) -> Result { + let database_id = + NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; + let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; - Ok(RowIdParams { - database_id: database_id.0, - row_id: row_id.0, - }) - } + Ok(RowIdParams { + database_id: database_id.0, + row_id: row_id.0, + }) + } } #[derive(Debug, Default, Clone, ProtoBuf)] pub struct BlockRowIdPB { - #[pb(index = 1)] - pub block_id: String, + #[pb(index = 1)] + pub block_id: String, - #[pb(index = 2)] - pub row_id: String, + #[pb(index = 2)] + pub row_id: String, } #[derive(ProtoBuf, Default)] pub struct CreateRowPayloadPB { - #[pb(index = 1)] - pub database_id: String, + #[pb(index = 1)] + pub database_id: String, - #[pb(index = 2, one_of)] - pub start_row_id: Option, + #[pb(index = 2, one_of)] + pub start_row_id: Option, } #[derive(Default)] pub struct CreateRowParams { - pub database_id: String, - pub start_row_id: Option, - pub group_id: Option, - pub layout: LayoutTypePB, + pub database_id: String, + pub start_row_id: Option, + pub group_id: Option, + pub layout: LayoutTypePB, } impl TryInto for CreateRowPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let database_id = NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; + fn try_into(self) -> Result { + let database_id = + NotEmptyStr::parse(self.database_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?; - Ok(CreateRowParams { - database_id: database_id.0, - start_row_id: self.start_row_id, - group_id: None, - layout: LayoutTypePB::Grid, - }) - } + Ok(CreateRowParams { + database_id: database_id.0, + start_row_id: self.start_row_id, + group_id: None, + layout: LayoutTypePB::Grid, + }) + } } diff --git a/frontend/rust-lib/flowy-database/src/entities/setting_entities.rs b/frontend/rust-lib/flowy-database/src/entities/setting_entities.rs index c1adba6846..ddd9467700 100644 --- a/frontend/rust-lib/flowy-database/src/entities/setting_entities.rs +++ b/frontend/rust-lib/flowy-database/src/entities/setting_entities.rs @@ -1,8 +1,9 @@ use crate::entities::parser::NotEmptyStr; use crate::entities::{ - AlterFilterParams, AlterFilterPayloadPB, AlterSortParams, AlterSortPayloadPB, DeleteFilterParams, - DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB, DeleteSortParams, DeleteSortPayloadPB, - InsertGroupParams, InsertGroupPayloadPB, RepeatedFilterPB, RepeatedGroupConfigurationPB, RepeatedSortPB, + AlterFilterParams, AlterFilterPayloadPB, AlterSortParams, AlterSortPayloadPB, DeleteFilterParams, + DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB, DeleteSortParams, + DeleteSortPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedFilterPB, + RepeatedGroupConfigurationPB, RepeatedSortPB, }; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; @@ -14,164 +15,164 @@ use strum_macros::EnumIter; /// [DatabaseViewSettingPB] defines the setting options for the grid. Such as the filter, group, and sort. #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct DatabaseViewSettingPB { - #[pb(index = 1)] - pub support_layouts: Vec, + #[pb(index = 1)] + pub support_layouts: Vec, - #[pb(index = 2)] - pub current_layout: LayoutTypePB, + #[pb(index = 2)] + pub current_layout: LayoutTypePB, - #[pb(index = 3)] - pub filters: RepeatedFilterPB, + #[pb(index = 3)] + pub filters: RepeatedFilterPB, - #[pb(index = 4)] - pub group_configurations: RepeatedGroupConfigurationPB, + #[pb(index = 4)] + pub group_configurations: RepeatedGroupConfigurationPB, - #[pb(index = 5)] - pub sorts: RepeatedSortPB, + #[pb(index = 5)] + pub sorts: RepeatedSortPB, } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct ViewLayoutPB { - #[pb(index = 1)] - ty: LayoutTypePB, + #[pb(index = 1)] + ty: LayoutTypePB, } impl ViewLayoutPB { - pub fn all() -> Vec { - let mut layouts = vec![]; - for layout_ty in LayoutTypePB::iter() { - layouts.push(ViewLayoutPB { ty: layout_ty }) - } - - layouts + pub fn all() -> Vec { + let mut layouts = vec![]; + for layout_ty in LayoutTypePB::iter() { + layouts.push(ViewLayoutPB { ty: layout_ty }) } + + layouts + } } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum, EnumIter)] #[repr(u8)] pub enum LayoutTypePB { - Grid = 0, - Board = 1, - Calendar = 2, + Grid = 0, + Board = 1, + Calendar = 2, } impl std::default::Default for LayoutTypePB { - fn default() -> Self { - LayoutTypePB::Grid - } + fn default() -> Self { + LayoutTypePB::Grid + } } impl std::convert::From for LayoutTypePB { - fn from(rev: LayoutRevision) -> Self { - match rev { - LayoutRevision::Grid => LayoutTypePB::Grid, - LayoutRevision::Board => LayoutTypePB::Board, - LayoutRevision::Calendar => LayoutTypePB::Calendar, - } + fn from(rev: LayoutRevision) -> Self { + match rev { + LayoutRevision::Grid => LayoutTypePB::Grid, + LayoutRevision::Board => LayoutTypePB::Board, + LayoutRevision::Calendar => LayoutTypePB::Calendar, } + } } impl std::convert::From for LayoutRevision { - fn from(layout: LayoutTypePB) -> Self { - match layout { - LayoutTypePB::Grid => LayoutRevision::Grid, - LayoutTypePB::Board => LayoutRevision::Board, - LayoutTypePB::Calendar => LayoutRevision::Calendar, - } + fn from(layout: LayoutTypePB) -> Self { + match layout { + LayoutTypePB::Grid => LayoutRevision::Grid, + LayoutTypePB::Board => LayoutRevision::Board, + LayoutTypePB::Calendar => LayoutRevision::Calendar, } + } } #[derive(Default, ProtoBuf)] pub struct DatabaseSettingChangesetPB { - #[pb(index = 1)] - pub database_id: String, + #[pb(index = 1)] + pub database_id: String, - #[pb(index = 2)] - pub layout_type: LayoutTypePB, + #[pb(index = 2)] + pub layout_type: LayoutTypePB, - #[pb(index = 3, one_of)] - pub alter_filter: Option, + #[pb(index = 3, one_of)] + pub alter_filter: Option, - #[pb(index = 4, one_of)] - pub delete_filter: Option, + #[pb(index = 4, one_of)] + pub delete_filter: Option, - #[pb(index = 5, one_of)] - pub insert_group: Option, + #[pb(index = 5, one_of)] + pub insert_group: Option, - #[pb(index = 6, one_of)] - pub delete_group: Option, + #[pb(index = 6, one_of)] + pub delete_group: Option, - #[pb(index = 7, one_of)] - pub alter_sort: Option, + #[pb(index = 7, one_of)] + pub alter_sort: Option, - #[pb(index = 8, one_of)] - pub delete_sort: Option, + #[pb(index = 8, one_of)] + pub delete_sort: Option, } impl TryInto for DatabaseSettingChangesetPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let database_id = NotEmptyStr::parse(self.database_id) - .map_err(|_| ErrorCode::ViewIdInvalid)? - .0; + fn try_into(self) -> Result { + let database_id = NotEmptyStr::parse(self.database_id) + .map_err(|_| ErrorCode::ViewIdInvalid)? + .0; - let insert_filter = match self.alter_filter { - None => None, - Some(payload) => Some(payload.try_into()?), - }; + let insert_filter = match self.alter_filter { + None => None, + Some(payload) => Some(payload.try_into()?), + }; - let delete_filter = match self.delete_filter { - None => None, - Some(payload) => Some(payload.try_into()?), - }; + let delete_filter = match self.delete_filter { + None => None, + Some(payload) => Some(payload.try_into()?), + }; - let insert_group = match self.insert_group { - Some(payload) => Some(payload.try_into()?), - None => None, - }; + let insert_group = match self.insert_group { + Some(payload) => Some(payload.try_into()?), + None => None, + }; - let delete_group = match self.delete_group { - Some(payload) => Some(payload.try_into()?), - None => None, - }; + let delete_group = match self.delete_group { + Some(payload) => Some(payload.try_into()?), + None => None, + }; - let alert_sort = match self.alter_sort { - None => None, - Some(payload) => Some(payload.try_into()?), - }; + let alert_sort = match self.alter_sort { + None => None, + Some(payload) => Some(payload.try_into()?), + }; - let delete_sort = match self.delete_sort { - None => None, - Some(payload) => Some(payload.try_into()?), - }; + let delete_sort = match self.delete_sort { + None => None, + Some(payload) => Some(payload.try_into()?), + }; - Ok(DatabaseSettingChangesetParams { - database_id, - layout_type: self.layout_type.into(), - insert_filter, - delete_filter, - insert_group, - delete_group, - alert_sort, - delete_sort, - }) - } + Ok(DatabaseSettingChangesetParams { + database_id, + layout_type: self.layout_type.into(), + insert_filter, + delete_filter, + insert_group, + delete_group, + alert_sort, + delete_sort, + }) + } } pub struct DatabaseSettingChangesetParams { - pub database_id: String, - pub layout_type: LayoutRevision, - pub insert_filter: Option, - pub delete_filter: Option, - pub insert_group: Option, - pub delete_group: Option, - pub alert_sort: Option, - pub delete_sort: Option, + pub database_id: String, + pub layout_type: LayoutRevision, + pub insert_filter: Option, + pub delete_filter: Option, + pub insert_group: Option, + pub delete_group: Option, + pub alert_sort: Option, + pub delete_sort: Option, } impl DatabaseSettingChangesetParams { - pub fn is_filter_changed(&self) -> bool { - self.insert_filter.is_some() || self.delete_filter.is_some() - } + pub fn is_filter_changed(&self) -> bool { + self.insert_filter.is_some() || self.delete_filter.is_some() + } } diff --git a/frontend/rust-lib/flowy-database/src/entities/sort_entities.rs b/frontend/rust-lib/flowy-database/src/entities/sort_entities.rs index 8b2c3664fa..f0204c9385 100644 --- a/frontend/rust-lib/flowy-database/src/entities/sort_entities.rs +++ b/frontend/rust-lib/flowy-database/src/entities/sort_entities.rs @@ -9,227 +9,231 @@ use grid_model::{FieldTypeRevision, SortCondition, SortRevision}; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct SortPB { - #[pb(index = 1)] - pub id: String, + #[pb(index = 1)] + pub id: String, - #[pb(index = 2)] - pub field_id: String, + #[pb(index = 2)] + pub field_id: String, - #[pb(index = 3)] - pub field_type: FieldType, + #[pb(index = 3)] + pub field_type: FieldType, - #[pb(index = 4)] - pub condition: SortConditionPB, + #[pb(index = 4)] + pub condition: SortConditionPB, } impl std::convert::From<&SortRevision> for SortPB { - fn from(sort_rev: &SortRevision) -> Self { - Self { - id: sort_rev.id.clone(), - field_id: sort_rev.field_id.clone(), - field_type: sort_rev.field_type.into(), - condition: sort_rev.condition.clone().into(), - } + fn from(sort_rev: &SortRevision) -> Self { + Self { + id: sort_rev.id.clone(), + field_id: sort_rev.field_id.clone(), + field_type: sort_rev.field_type.into(), + condition: sort_rev.condition.clone().into(), } + } } #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct RepeatedSortPB { - #[pb(index = 1)] - pub items: Vec, + #[pb(index = 1)] + pub items: Vec, } impl std::convert::From>> for RepeatedSortPB { - fn from(revs: Vec>) -> Self { - RepeatedSortPB { - items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(), - } + fn from(revs: Vec>) -> Self { + RepeatedSortPB { + items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(), } + } } impl std::convert::From> for RepeatedSortPB { - fn from(items: Vec) -> Self { - Self { items } - } + fn from(items: Vec) -> Self { + Self { items } + } } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] pub enum SortConditionPB { - Ascending = 0, - Descending = 1, + Ascending = 0, + Descending = 1, } impl std::default::Default for SortConditionPB { - fn default() -> Self { - Self::Ascending - } + fn default() -> Self { + Self::Ascending + } } impl std::convert::From for SortConditionPB { - fn from(condition: SortCondition) -> Self { - match condition { - SortCondition::Ascending => SortConditionPB::Ascending, - SortCondition::Descending => SortConditionPB::Descending, - } + fn from(condition: SortCondition) -> Self { + match condition { + SortCondition::Ascending => SortConditionPB::Ascending, + SortCondition::Descending => SortConditionPB::Descending, } + } } #[derive(ProtoBuf, Debug, Default, Clone)] pub struct AlterSortPayloadPB { - #[pb(index = 1)] - pub view_id: String, + #[pb(index = 1)] + pub view_id: String, - #[pb(index = 2)] - pub field_id: String, + #[pb(index = 2)] + pub field_id: String, - #[pb(index = 3)] - pub field_type: FieldType, + #[pb(index = 3)] + pub field_type: FieldType, - /// Create a new sort if the sort_id is None - #[pb(index = 4, one_of)] - pub sort_id: Option, + /// Create a new sort if the sort_id is None + #[pb(index = 4, one_of)] + pub sort_id: Option, - #[pb(index = 5)] - pub condition: SortConditionPB, + #[pb(index = 5)] + pub condition: SortConditionPB, } impl TryInto for AlterSortPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id) - .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? - .0; + fn try_into(self) -> Result { + let view_id = NotEmptyStr::parse(self.view_id) + .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? + .0; - let field_id = NotEmptyStr::parse(self.field_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; + let field_id = NotEmptyStr::parse(self.field_id) + .map_err(|_| ErrorCode::FieldIdIsEmpty)? + .0; - let sort_id = match self.sort_id { - None => None, - Some(sort_id) => Some(NotEmptyStr::parse(sort_id).map_err(|_| ErrorCode::SortIdIsEmpty)?.0), - }; + let sort_id = match self.sort_id { + None => None, + Some(sort_id) => Some( + NotEmptyStr::parse(sort_id) + .map_err(|_| ErrorCode::SortIdIsEmpty)? + .0, + ), + }; - Ok(AlterSortParams { - view_id, - field_id, - sort_id, - field_type: self.field_type.into(), - condition: self.condition as u8, - }) - } + Ok(AlterSortParams { + view_id, + field_id, + sort_id, + field_type: self.field_type.into(), + condition: self.condition as u8, + }) + } } #[derive(Debug)] pub struct AlterSortParams { - pub view_id: String, - pub field_id: String, - /// Create a new sort if the sort is None - pub sort_id: Option, - pub field_type: FieldTypeRevision, - pub condition: u8, + pub view_id: String, + pub field_id: String, + /// Create a new sort if the sort is None + pub sort_id: Option, + pub field_type: FieldTypeRevision, + pub condition: u8, } #[derive(ProtoBuf, Debug, Default, Clone)] pub struct DeleteSortPayloadPB { - #[pb(index = 1)] - pub view_id: String, + #[pb(index = 1)] + pub view_id: String, - #[pb(index = 2)] - pub field_id: String, + #[pb(index = 2)] + pub field_id: String, - #[pb(index = 3)] - pub field_type: FieldType, + #[pb(index = 3)] + pub field_type: FieldType, - #[pb(index = 4)] - pub sort_id: String, + #[pb(index = 4)] + pub sort_id: String, } impl TryInto for DeleteSortPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id) - .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? - .0; - let field_id = NotEmptyStr::parse(self.field_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; + fn try_into(self) -> Result { + let view_id = NotEmptyStr::parse(self.view_id) + .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? + .0; + let field_id = NotEmptyStr::parse(self.field_id) + .map_err(|_| ErrorCode::FieldIdIsEmpty)? + .0; - let sort_id = NotEmptyStr::parse(self.sort_id) - .map_err(|_| ErrorCode::UnexpectedEmptyString)? - .0; + let sort_id = NotEmptyStr::parse(self.sort_id) + .map_err(|_| ErrorCode::UnexpectedEmptyString)? + .0; - let sort_type = SortType { - field_id, - field_type: self.field_type, - }; + let sort_type = SortType { + field_id, + field_type: self.field_type, + }; - Ok(DeleteSortParams { - view_id, - sort_type, - sort_id, - }) - } + Ok(DeleteSortParams { + view_id, + sort_type, + sort_id, + }) + } } #[derive(Debug, Clone)] pub struct DeleteSortParams { - pub view_id: String, - pub sort_type: SortType, - pub sort_id: String, + pub view_id: String, + pub sort_type: SortType, + pub sort_id: String, } #[derive(Debug, Default, ProtoBuf)] pub struct SortChangesetNotificationPB { - #[pb(index = 1)] - pub view_id: String, + #[pb(index = 1)] + pub view_id: String, - #[pb(index = 2)] - pub insert_sorts: Vec, + #[pb(index = 2)] + pub insert_sorts: Vec, - #[pb(index = 3)] - pub delete_sorts: Vec, + #[pb(index = 3)] + pub delete_sorts: Vec, - #[pb(index = 4)] - pub update_sorts: Vec, + #[pb(index = 4)] + pub update_sorts: Vec, } impl SortChangesetNotificationPB { - pub fn new(view_id: String) -> Self { - Self { - view_id, - insert_sorts: vec![], - delete_sorts: vec![], - update_sorts: vec![], - } + pub fn new(view_id: String) -> Self { + Self { + view_id, + insert_sorts: vec![], + delete_sorts: vec![], + update_sorts: vec![], } + } - pub fn extend(&mut self, other: SortChangesetNotificationPB) { - self.insert_sorts.extend(other.insert_sorts); - self.delete_sorts.extend(other.delete_sorts); - self.update_sorts.extend(other.update_sorts); - } + pub fn extend(&mut self, other: SortChangesetNotificationPB) { + self.insert_sorts.extend(other.insert_sorts); + self.delete_sorts.extend(other.delete_sorts); + self.update_sorts.extend(other.update_sorts); + } - pub fn is_empty(&self) -> bool { - self.insert_sorts.is_empty() && self.delete_sorts.is_empty() && self.update_sorts.is_empty() - } + pub fn is_empty(&self) -> bool { + self.insert_sorts.is_empty() && self.delete_sorts.is_empty() && self.update_sorts.is_empty() + } } #[derive(Debug, Default, ProtoBuf)] pub struct ReorderAllRowsPB { - #[pb(index = 1)] - pub row_orders: Vec, + #[pb(index = 1)] + pub row_orders: Vec, } #[derive(Debug, Default, ProtoBuf)] pub struct ReorderSingleRowPB { - #[pb(index = 1)] - pub row_id: String, + #[pb(index = 1)] + pub row_id: String, - #[pb(index = 2)] - pub old_index: i32, + #[pb(index = 2)] + pub old_index: i32, - #[pb(index = 3)] - pub new_index: i32, + #[pb(index = 3)] + pub new_index: i32, } diff --git a/frontend/rust-lib/flowy-database/src/entities/view_entities.rs b/frontend/rust-lib/flowy-database/src/entities/view_entities.rs index 1e1170dc56..6a95519613 100644 --- a/frontend/rust-lib/flowy-database/src/entities/view_entities.rs +++ b/frontend/rust-lib/flowy-database/src/entities/view_entities.rs @@ -3,62 +3,66 @@ use flowy_derive::ProtoBuf; #[derive(Debug, Default, Clone, ProtoBuf)] pub struct ViewRowsVisibilityChangesetPB { - #[pb(index = 1)] - pub view_id: String, + #[pb(index = 1)] + pub view_id: String, - #[pb(index = 5)] - pub visible_rows: Vec, + #[pb(index = 5)] + pub visible_rows: Vec, - #[pb(index = 6)] - pub invisible_rows: Vec, + #[pb(index = 6)] + pub invisible_rows: Vec, } #[derive(Debug, Default, Clone, ProtoBuf)] pub struct ViewRowsChangesetPB { - #[pb(index = 1)] - pub view_id: String, + #[pb(index = 1)] + pub view_id: String, - #[pb(index = 2)] - pub inserted_rows: Vec, + #[pb(index = 2)] + pub inserted_rows: Vec, - #[pb(index = 3)] - pub deleted_rows: Vec, + #[pb(index = 3)] + pub deleted_rows: Vec, - #[pb(index = 4)] - pub updated_rows: Vec, + #[pb(index = 4)] + pub updated_rows: Vec, } impl ViewRowsChangesetPB { - pub fn from_insert(view_id: String, inserted_rows: Vec) -> Self { - Self { - view_id, - inserted_rows, - ..Default::default() - } + pub fn from_insert(view_id: String, inserted_rows: Vec) -> Self { + Self { + view_id, + inserted_rows, + ..Default::default() } + } - pub fn from_delete(view_id: String, deleted_rows: Vec) -> Self { - Self { - view_id, - deleted_rows, - ..Default::default() - } + pub fn from_delete(view_id: String, deleted_rows: Vec) -> Self { + Self { + view_id, + deleted_rows, + ..Default::default() } + } - pub fn from_update(view_id: String, updated_rows: Vec) -> Self { - Self { - view_id, - updated_rows, - ..Default::default() - } + pub fn from_update(view_id: String, updated_rows: Vec) -> Self { + Self { + view_id, + updated_rows, + ..Default::default() } + } - pub fn from_move(view_id: String, deleted_rows: Vec, inserted_rows: Vec) -> Self { - Self { - view_id, - inserted_rows, - deleted_rows, - ..Default::default() - } + pub fn from_move( + view_id: String, + deleted_rows: Vec, + inserted_rows: Vec, + ) -> Self { + Self { + view_id, + inserted_rows, + deleted_rows, + ..Default::default() } + } } diff --git a/frontend/rust-lib/flowy-database/src/event_handler.rs b/frontend/rust-lib/flowy-database/src/event_handler.rs index 385d324a0a..4708dd442e 100644 --- a/frontend/rust-lib/flowy-database/src/event_handler.rs +++ b/frontend/rust-lib/flowy-database/src/event_handler.rs @@ -2,10 +2,10 @@ use crate::entities::*; use crate::manager::DatabaseManager; use crate::services::cell::{FromCellString, ToCellChangesetString, TypeCellData}; use crate::services::field::{ - default_type_option_builder_from_type, select_type_option_from_field_rev, type_option_builder_from_json_str, - DateCellChangeset, DateChangesetPB, SelectOptionCellChangeset, SelectOptionCellChangesetPB, - SelectOptionCellChangesetParams, SelectOptionCellDataPB, SelectOptionChangeset, SelectOptionChangesetPB, - SelectOptionIds, SelectOptionPB, + default_type_option_builder_from_type, select_type_option_from_field_rev, + type_option_builder_from_json_str, DateCellChangeset, DateChangesetPB, SelectOptionCellChangeset, + SelectOptionCellChangesetPB, SelectOptionCellChangesetParams, SelectOptionCellDataPB, + SelectOptionChangeset, SelectOptionChangesetPB, SelectOptionIds, SelectOptionPB, }; use crate::services::row::make_row_from_row_rev; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; @@ -15,528 +15,562 @@ use std::sync::Arc; #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_database_data_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let database_id: DatabaseIdPB = data.into_inner(); - let editor = manager.open_database(database_id.as_ref()).await?; - let database = editor.get_database(database_id.as_ref()).await?; - data_result(database) + let database_id: DatabaseIdPB = data.into_inner(); + let editor = manager.open_database(database_id.as_ref()).await?; + let database = editor.get_database(database_id.as_ref()).await?; + data_result(database) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_database_setting_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let database_id: DatabaseIdPB = data.into_inner(); - let editor = manager.open_database(database_id).await?; - let database_setting = editor.get_setting().await?; - data_result(database_setting) + let database_id: DatabaseIdPB = data.into_inner(); + let editor = manager.open_database(database_id).await?; + let database_setting = editor.get_setting().await?; + data_result(database_setting) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn update_database_setting_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let params: DatabaseSettingChangesetParams = data.into_inner().try_into()?; + let params: DatabaseSettingChangesetParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.database_id).await?; - if let Some(insert_params) = params.insert_group { - editor.insert_group(insert_params).await?; - } + let editor = manager.get_database_editor(¶ms.database_id).await?; + if let Some(insert_params) = params.insert_group { + editor.insert_group(insert_params).await?; + } - if let Some(delete_params) = params.delete_group { - editor.delete_group(delete_params).await?; - } + if let Some(delete_params) = params.delete_group { + editor.delete_group(delete_params).await?; + } - if let Some(alter_filter) = params.insert_filter { - editor.create_or_update_filter(alter_filter).await?; - } + if let Some(alter_filter) = params.insert_filter { + editor.create_or_update_filter(alter_filter).await?; + } - if let Some(delete_filter) = params.delete_filter { - editor.delete_filter(delete_filter).await?; - } + if let Some(delete_filter) = params.delete_filter { + editor.delete_filter(delete_filter).await?; + } - if let Some(alter_sort) = params.alert_sort { - let _ = editor.create_or_update_sort(alter_sort).await?; - } - if let Some(delete_sort) = params.delete_sort { - editor.delete_sort(delete_sort).await?; - } - Ok(()) + if let Some(alter_sort) = params.alert_sort { + let _ = editor.create_or_update_sort(alter_sort).await?; + } + if let Some(delete_sort) = params.delete_sort { + editor.delete_sort(delete_sort).await?; + } + Ok(()) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_all_filters_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let database_id: DatabaseIdPB = data.into_inner(); - let editor = manager.open_database(database_id).await?; - let filters = RepeatedFilterPB { - items: editor.get_all_filters().await?, - }; - data_result(filters) + let database_id: DatabaseIdPB = data.into_inner(); + let editor = manager.open_database(database_id).await?; + let filters = RepeatedFilterPB { + items: editor.get_all_filters().await?, + }; + data_result(filters) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_all_sorts_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let database_id: DatabaseIdPB = data.into_inner(); - let editor = manager.open_database(database_id.as_ref()).await?; - let sorts = RepeatedSortPB { - items: editor.get_all_sorts(database_id.as_ref()).await?, - }; - data_result(sorts) + let database_id: DatabaseIdPB = data.into_inner(); + let editor = manager.open_database(database_id.as_ref()).await?; + let sorts = RepeatedSortPB { + items: editor.get_all_sorts(database_id.as_ref()).await?, + }; + data_result(sorts) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn delete_all_sorts_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let database_id: DatabaseIdPB = data.into_inner(); - let editor = manager.open_database(database_id.as_ref()).await?; - editor.delete_all_sorts(database_id.as_ref()).await?; - Ok(()) + let database_id: DatabaseIdPB = data.into_inner(); + let editor = manager.open_database(database_id.as_ref()).await?; + editor.delete_all_sorts(database_id.as_ref()).await?; + Ok(()) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_fields_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let params: GetFieldParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.database_id).await?; - let field_revs = editor.get_field_revs(params.field_ids).await?; - let repeated_field: RepeatedFieldPB = field_revs.into_iter().map(FieldPB::from).collect::>().into(); - data_result(repeated_field) + let params: GetFieldParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(¶ms.database_id).await?; + let field_revs = editor.get_field_revs(params.field_ids).await?; + let repeated_field: RepeatedFieldPB = field_revs + .into_iter() + .map(FieldPB::from) + .collect::>() + .into(); + data_result(repeated_field) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn update_field_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let changeset: FieldChangesetParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(&changeset.database_id).await?; - editor.update_field(changeset).await?; - Ok(()) + let changeset: FieldChangesetParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(&changeset.database_id).await?; + editor.update_field(changeset).await?; + Ok(()) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn update_field_type_option_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let params: TypeOptionChangesetParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.database_id).await?; - let old_field_rev = editor.get_field_rev(¶ms.field_id).await; - editor - .update_field_type_option( - ¶ms.database_id, - ¶ms.field_id, - params.type_option_data, - old_field_rev, - ) - .await?; - Ok(()) + let params: TypeOptionChangesetParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(¶ms.database_id).await?; + let old_field_rev = editor.get_field_rev(¶ms.field_id).await; + editor + .update_field_type_option( + ¶ms.database_id, + ¶ms.field_id, + params.type_option_data, + old_field_rev, + ) + .await?; + Ok(()) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn delete_field_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let params: FieldIdParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.database_id).await?; - editor.delete_field(¶ms.field_id).await?; - Ok(()) + let params: FieldIdParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(¶ms.database_id).await?; + editor.delete_field(¶ms.field_id).await?; + Ok(()) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn switch_to_field_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let params: EditFieldParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.database_id).await?; - let old_field_rev = editor.get_field_rev(¶ms.field_id).await; - editor - .switch_to_field_type(¶ms.field_id, ¶ms.field_type) - .await?; + let params: EditFieldParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(¶ms.database_id).await?; + let old_field_rev = editor.get_field_rev(¶ms.field_id).await; + editor + .switch_to_field_type(¶ms.field_id, ¶ms.field_type) + .await?; - // Get the field_rev with field_id, if it doesn't exist, we create the default FieldRevision from the FieldType. - let new_field_rev = editor - .get_field_rev(¶ms.field_id) - .await - .unwrap_or(Arc::new(editor.next_field_rev(¶ms.field_type).await?)); + // Get the field_rev with field_id, if it doesn't exist, we create the default FieldRevision from the FieldType. + let new_field_rev = editor + .get_field_rev(¶ms.field_id) + .await + .unwrap_or(Arc::new(editor.next_field_rev(¶ms.field_type).await?)); - // Update the type-option data after the field type has been changed - let type_option_data = get_type_option_data(&new_field_rev, ¶ms.field_type).await?; - editor - .update_field_type_option(¶ms.database_id, &new_field_rev.id, type_option_data, old_field_rev) - .await?; + // Update the type-option data after the field type has been changed + let type_option_data = get_type_option_data(&new_field_rev, ¶ms.field_type).await?; + editor + .update_field_type_option( + ¶ms.database_id, + &new_field_rev.id, + type_option_data, + old_field_rev, + ) + .await?; - Ok(()) + Ok(()) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn duplicate_field_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let params: FieldIdParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.database_id).await?; - editor.duplicate_field(¶ms.field_id).await?; - Ok(()) + let params: FieldIdParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(¶ms.database_id).await?; + editor.duplicate_field(¶ms.field_id).await?; + Ok(()) } /// Return the FieldTypeOptionData if the Field exists otherwise return record not found error. #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_field_type_option_data_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let params: TypeOptionPathParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.database_id).await?; - match editor.get_field_rev(¶ms.field_id).await { - None => Err(FlowyError::record_not_found()), - Some(field_rev) => { - let field_type = field_rev.ty.into(); - let type_option_data = get_type_option_data(&field_rev, &field_type).await?; - let data = TypeOptionPB { - database_id: params.database_id, - field: field_rev.into(), - type_option_data, - }; - data_result(data) - } - } + let params: TypeOptionPathParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(¶ms.database_id).await?; + match editor.get_field_rev(¶ms.field_id).await { + None => Err(FlowyError::record_not_found()), + Some(field_rev) => { + let field_type = field_rev.ty.into(); + let type_option_data = get_type_option_data(&field_rev, &field_type).await?; + let data = TypeOptionPB { + database_id: params.database_id, + field: field_rev.into(), + type_option_data, + }; + data_result(data) + }, + } } /// Create FieldMeta and save it. Return the FieldTypeOptionData. #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn create_field_type_option_data_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let params: CreateFieldParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.database_id).await?; - let field_rev = editor - .create_new_field_rev_with_type_option(¶ms.field_type, params.type_option_data) - .await?; - let field_type: FieldType = field_rev.ty.into(); - let type_option_data = get_type_option_data(&field_rev, &field_type).await?; + let params: CreateFieldParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(¶ms.database_id).await?; + let field_rev = editor + .create_new_field_rev_with_type_option(¶ms.field_type, params.type_option_data) + .await?; + let field_type: FieldType = field_rev.ty.into(); + let type_option_data = get_type_option_data(&field_rev, &field_type).await?; - data_result(TypeOptionPB { - database_id: params.database_id, - field: field_rev.into(), - type_option_data, - }) + data_result(TypeOptionPB { + database_id: params.database_id, + field: field_rev.into(), + type_option_data, + }) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn move_field_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let params: MoveFieldParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.view_id).await?; - editor.move_field(params).await?; - Ok(()) + let params: MoveFieldParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(¶ms.view_id).await?; + editor.move_field(params).await?; + Ok(()) } /// The [FieldRevision] contains multiple data, each of them belongs to a specific FieldType. -async fn get_type_option_data(field_rev: &FieldRevision, field_type: &FieldType) -> FlowyResult> { - let s = field_rev - .get_type_option_str(field_type) - .map(|value| value.to_owned()) - .unwrap_or_else(|| { - default_type_option_builder_from_type(field_type) - .serializer() - .json_str() - }); - let field_type: FieldType = field_rev.ty.into(); - let builder = type_option_builder_from_json_str(&s, &field_type); - let type_option_data = builder.serializer().protobuf_bytes().to_vec(); +async fn get_type_option_data( + field_rev: &FieldRevision, + field_type: &FieldType, +) -> FlowyResult> { + let s = field_rev + .get_type_option_str(field_type) + .map(|value| value.to_owned()) + .unwrap_or_else(|| { + default_type_option_builder_from_type(field_type) + .serializer() + .json_str() + }); + let field_type: FieldType = field_rev.ty.into(); + let builder = type_option_builder_from_json_str(&s, &field_type); + let type_option_data = builder.serializer().protobuf_bytes().to_vec(); - Ok(type_option_data) + Ok(type_option_data) } // #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn get_row_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let params: RowIdParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.database_id).await?; - let row = editor.get_row_rev(¶ms.row_id).await?.map(make_row_from_row_rev); + let params: RowIdParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(¶ms.database_id).await?; + let row = editor + .get_row_rev(¶ms.row_id) + .await? + .map(make_row_from_row_rev); - data_result(OptionalRowPB { row }) + data_result(OptionalRowPB { row }) } #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn delete_row_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let params: RowIdParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.database_id).await?; - editor.delete_row(¶ms.row_id).await?; - Ok(()) + let params: RowIdParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(¶ms.database_id).await?; + editor.delete_row(¶ms.row_id).await?; + Ok(()) } #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn duplicate_row_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let params: RowIdParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.database_id).await?; - editor.duplicate_row(¶ms.row_id).await?; - Ok(()) + let params: RowIdParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(¶ms.database_id).await?; + editor.duplicate_row(¶ms.row_id).await?; + Ok(()) } #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn move_row_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let params: MoveRowParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.view_id).await?; - editor.move_row(params).await?; - Ok(()) + let params: MoveRowParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(¶ms.view_id).await?; + editor.move_row(params).await?; + Ok(()) } #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn create_table_row_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let params: CreateRowParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(params.database_id.as_ref()).await?; - let row = editor.create_row(params).await?; - data_result(row) + let params: CreateRowParams = data.into_inner().try_into()?; + let editor = manager + .get_database_editor(params.database_id.as_ref()) + .await?; + let row = editor.create_row(params).await?; + data_result(row) } #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn get_cell_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let params: CellIdParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.database_id).await?; - match editor.get_cell(¶ms).await { - None => data_result(CellPB::empty(¶ms.field_id, ¶ms.row_id)), - Some(cell) => data_result(cell), - } + let params: CellIdParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(¶ms.database_id).await?; + match editor.get_cell(¶ms).await { + None => data_result(CellPB::empty(¶ms.field_id, ¶ms.row_id)), + Some(cell) => data_result(cell), + } } #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_cell_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let changeset: CellChangesetPB = data.into_inner(); - let editor = manager.get_database_editor(&changeset.database_id).await?; - editor - .update_cell_with_changeset(&changeset.row_id, &changeset.field_id, changeset.type_cell_data) - .await?; - Ok(()) + let changeset: CellChangesetPB = data.into_inner(); + let editor = manager.get_database_editor(&changeset.database_id).await?; + editor + .update_cell_with_changeset( + &changeset.row_id, + &changeset.field_id, + changeset.type_cell_data, + ) + .await?; + Ok(()) } #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn new_select_option_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let params: CreateSelectOptionParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.database_id).await?; - match editor.get_field_rev(¶ms.field_id).await { - None => Err(ErrorCode::InvalidData.into()), - Some(field_rev) => { - let type_option = select_type_option_from_field_rev(&field_rev)?; - let select_option = type_option.create_option(¶ms.option_name); - data_result(select_option) - } - } + let params: CreateSelectOptionParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(¶ms.database_id).await?; + match editor.get_field_rev(¶ms.field_id).await { + None => Err(ErrorCode::InvalidData.into()), + Some(field_rev) => { + let type_option = select_type_option_from_field_rev(&field_rev)?; + let select_option = type_option.create_option(¶ms.option_name); + data_result(select_option) + }, + } } #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_select_option_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let changeset: SelectOptionChangeset = data.into_inner().try_into()?; - let editor = manager.get_database_editor(&changeset.cell_path.database_id).await?; - let field_id = changeset.cell_path.field_id.clone(); - let (tx, rx) = tokio::sync::oneshot::channel(); - editor - .modify_field_rev(&field_id, |field_rev| { - let mut type_option = select_type_option_from_field_rev(field_rev)?; - let mut cell_changeset_str = None; - let mut is_changed = None; + let changeset: SelectOptionChangeset = data.into_inner().try_into()?; + let editor = manager + .get_database_editor(&changeset.cell_path.database_id) + .await?; + let field_id = changeset.cell_path.field_id.clone(); + let (tx, rx) = tokio::sync::oneshot::channel(); + editor + .modify_field_rev(&field_id, |field_rev| { + let mut type_option = select_type_option_from_field_rev(field_rev)?; + let mut cell_changeset_str = None; + let mut is_changed = None; - for option in changeset.insert_options { - cell_changeset_str = - Some(SelectOptionCellChangeset::from_insert_option_id(&option.id).to_cell_changeset_str()); - type_option.insert_option(option); - is_changed = Some(()); - } + for option in changeset.insert_options { + cell_changeset_str = Some( + SelectOptionCellChangeset::from_insert_option_id(&option.id).to_cell_changeset_str(), + ); + type_option.insert_option(option); + is_changed = Some(()); + } - for option in changeset.update_options { - type_option.insert_option(option); - is_changed = Some(()); - } + for option in changeset.update_options { + type_option.insert_option(option); + is_changed = Some(()); + } - for option in changeset.delete_options { - cell_changeset_str = - Some(SelectOptionCellChangeset::from_delete_option_id(&option.id).to_cell_changeset_str()); - type_option.delete_option(option); - is_changed = Some(()); - } + for option in changeset.delete_options { + cell_changeset_str = Some( + SelectOptionCellChangeset::from_delete_option_id(&option.id).to_cell_changeset_str(), + ); + type_option.delete_option(option); + is_changed = Some(()); + } - if is_changed.is_some() { - field_rev.insert_type_option(&*type_option); - } - let _ = tx.send(cell_changeset_str); - Ok(is_changed) - }) - .await?; + if is_changed.is_some() { + field_rev.insert_type_option(&*type_option); + } + let _ = tx.send(cell_changeset_str); + Ok(is_changed) + }) + .await?; - if let Ok(Some(cell_changeset_str)) = rx.await { - match editor - .update_cell_with_changeset( - &changeset.cell_path.row_id, - &changeset.cell_path.field_id, - cell_changeset_str, - ) - .await - { - Ok(_) => {} - Err(e) => tracing::error!("{}", e), - } + if let Ok(Some(cell_changeset_str)) = rx.await { + match editor + .update_cell_with_changeset( + &changeset.cell_path.row_id, + &changeset.cell_path.field_id, + cell_changeset_str, + ) + .await + { + Ok(_) => {}, + Err(e) => tracing::error!("{}", e), } - Ok(()) + } + Ok(()) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_select_option_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let params: CellIdParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.database_id).await?; - match editor.get_field_rev(¶ms.field_id).await { - None => { - tracing::error!("Can't find the select option field with id: {}", params.field_id); - data_result(SelectOptionCellDataPB::default()) - } - Some(field_rev) => { - // - let cell_rev = editor.get_cell_rev(¶ms.row_id, ¶ms.field_id).await?; - let type_option = select_type_option_from_field_rev(&field_rev)?; - let type_cell_data: TypeCellData = match cell_rev { - None => TypeCellData { - cell_str: "".to_string(), - field_type: field_rev.ty.into(), - }, - Some(cell_rev) => cell_rev.try_into()?, - }; - let ids = SelectOptionIds::from_cell_str(&type_cell_data.cell_str)?; - let selected_options = type_option.get_selected_options(ids); - data_result(selected_options) - } - } + let params: CellIdParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(¶ms.database_id).await?; + match editor.get_field_rev(¶ms.field_id).await { + None => { + tracing::error!( + "Can't find the select option field with id: {}", + params.field_id + ); + data_result(SelectOptionCellDataPB::default()) + }, + Some(field_rev) => { + // + let cell_rev = editor + .get_cell_rev(¶ms.row_id, ¶ms.field_id) + .await?; + let type_option = select_type_option_from_field_rev(&field_rev)?; + let type_cell_data: TypeCellData = match cell_rev { + None => TypeCellData { + cell_str: "".to_string(), + field_type: field_rev.ty.into(), + }, + Some(cell_rev) => cell_rev.try_into()?, + }; + let ids = SelectOptionIds::from_cell_str(&type_cell_data.cell_str)?; + let selected_options = type_option.get_selected_options(ids); + data_result(selected_options) + }, + } } #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_select_option_cell_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let params: SelectOptionCellChangesetParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.cell_identifier.database_id).await?; - let changeset = SelectOptionCellChangeset { - insert_option_ids: params.insert_option_ids, - delete_option_ids: params.delete_option_ids, - }; + let params: SelectOptionCellChangesetParams = data.into_inner().try_into()?; + let editor = manager + .get_database_editor(¶ms.cell_identifier.database_id) + .await?; + let changeset = SelectOptionCellChangeset { + insert_option_ids: params.insert_option_ids, + delete_option_ids: params.delete_option_ids, + }; - editor - .update_cell_with_changeset( - ¶ms.cell_identifier.row_id, - ¶ms.cell_identifier.field_id, - changeset, - ) - .await?; - Ok(()) + editor + .update_cell_with_changeset( + ¶ms.cell_identifier.row_id, + ¶ms.cell_identifier.field_id, + changeset, + ) + .await?; + Ok(()) } #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_date_cell_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let data = data.into_inner(); - let cell_path: CellIdParams = data.cell_path.try_into()?; - let cell_changeset = DateCellChangeset { - date: data.date, - time: data.time, - is_utc: data.is_utc, - }; + let data = data.into_inner(); + let cell_path: CellIdParams = data.cell_path.try_into()?; + let cell_changeset = DateCellChangeset { + date: data.date, + time: data.time, + is_utc: data.is_utc, + }; - let editor = manager.get_database_editor(&cell_path.database_id).await?; - editor - .update_cell(cell_path.row_id, cell_path.field_id, cell_changeset) - .await?; - Ok(()) + let editor = manager.get_database_editor(&cell_path.database_id).await?; + editor + .update_cell(cell_path.row_id, cell_path.field_id, cell_changeset) + .await?; + Ok(()) } #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn get_groups_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let params: DatabaseIdPB = data.into_inner(); - let editor = manager.get_database_editor(¶ms.value).await?; - let group = editor.load_groups().await?; - data_result(group) + let params: DatabaseIdPB = data.into_inner(); + let editor = manager.get_database_editor(¶ms.value).await?; + let group = editor.load_groups().await?; + data_result(group) } #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn create_board_card_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let params: CreateRowParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(params.database_id.as_ref()).await?; - let row = editor.create_row(params).await?; - data_result(row) + let params: CreateRowParams = data.into_inner().try_into()?; + let editor = manager + .get_database_editor(params.database_id.as_ref()) + .await?; + let row = editor.create_row(params).await?; + data_result(row) } #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn move_group_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> FlowyResult<()> { - let params: MoveGroupParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(params.view_id.as_ref()).await?; - editor.move_group(params).await?; - Ok(()) + let params: MoveGroupParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(params.view_id.as_ref()).await?; + editor.move_group(params).await?; + Ok(()) } #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn move_group_row_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> FlowyResult<()> { - let params: MoveGroupRowParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(params.view_id.as_ref()).await?; - editor.move_group_row(params).await?; - Ok(()) + let params: MoveGroupRowParams = data.into_inner().try_into()?; + let editor = manager.get_database_editor(params.view_id.as_ref()).await?; + editor.move_group_row(params).await?; + Ok(()) } diff --git a/frontend/rust-lib/flowy-database/src/event_map.rs b/frontend/rust-lib/flowy-database/src/event_map.rs index 26da43ee94..07db6891fc 100644 --- a/frontend/rust-lib/flowy-database/src/event_map.rs +++ b/frontend/rust-lib/flowy-database/src/event_map.rs @@ -6,8 +6,10 @@ use std::sync::Arc; use strum_macros::Display; pub fn init(database_manager: Arc) -> AFPlugin { - let mut plugin = AFPlugin::new().name(env!("CARGO_PKG_NAME")).state(database_manager); - plugin = plugin + let mut plugin = AFPlugin::new() + .name(env!("CARGO_PKG_NAME")) + .state(database_manager); + plugin = plugin .event(DatabaseEvent::GetDatabase, get_database_data_handler) // .event(GridEvent::GetGridBlocks, get_grid_blocks_handler) .event(DatabaseEvent::GetDatabaseSetting, get_database_setting_handler) @@ -47,7 +49,7 @@ pub fn init(database_manager: Arc) -> AFPlugin { .event(DatabaseEvent::MoveGroupRow, move_group_row_handler) .event(DatabaseEvent::GetGroup, get_groups_handler); - plugin + plugin } /// [DatabaseEvent] defines events that are used to interact with the Grid. You could check [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/backend/protobuf) @@ -55,176 +57,176 @@ pub fn init(database_manager: Arc) -> AFPlugin { #[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)] #[event_err = "FlowyError"] pub enum DatabaseEvent { - /// [GetDatabase] event is used to get the [DatabasePB] - /// - /// The event handler accepts a [DatabaseIdPB] and returns a [DatabasePB] if there are no errors. - #[event(input = "DatabaseIdPB", output = "DatabasePB")] - GetDatabase = 0, + /// [GetDatabase] event is used to get the [DatabasePB] + /// + /// The event handler accepts a [DatabaseIdPB] and returns a [DatabasePB] if there are no errors. + #[event(input = "DatabaseIdPB", output = "DatabasePB")] + GetDatabase = 0, - /// [GetDatabaseSetting] event is used to get the database's settings. - /// - /// The event handler accepts [DatabaseIdPB] and return [DatabaseViewSettingPB] - /// if there is no errors. - #[event(input = "DatabaseIdPB", output = "DatabaseViewSettingPB")] - GetDatabaseSetting = 2, + /// [GetDatabaseSetting] event is used to get the database's settings. + /// + /// The event handler accepts [DatabaseIdPB] and return [DatabaseViewSettingPB] + /// if there is no errors. + #[event(input = "DatabaseIdPB", output = "DatabaseViewSettingPB")] + GetDatabaseSetting = 2, - /// [UpdateDatabaseSetting] event is used to update the database's settings. - /// - /// The event handler accepts [DatabaseSettingChangesetPB] and return errors if failed to modify the grid's settings. - #[event(input = "DatabaseSettingChangesetPB")] - UpdateDatabaseSetting = 3, + /// [UpdateDatabaseSetting] event is used to update the database's settings. + /// + /// The event handler accepts [DatabaseSettingChangesetPB] and return errors if failed to modify the grid's settings. + #[event(input = "DatabaseSettingChangesetPB")] + UpdateDatabaseSetting = 3, - #[event(input = "DatabaseIdPB", output = "RepeatedFilterPB")] - GetAllFilters = 4, + #[event(input = "DatabaseIdPB", output = "RepeatedFilterPB")] + GetAllFilters = 4, - #[event(input = "DatabaseIdPB", output = "RepeatedSortPB")] - GetAllSorts = 5, + #[event(input = "DatabaseIdPB", output = "RepeatedSortPB")] + GetAllSorts = 5, - #[event(input = "DatabaseIdPB")] - DeleteAllSorts = 6, + #[event(input = "DatabaseIdPB")] + DeleteAllSorts = 6, - /// [GetFields] event is used to get the database's settings. - /// - /// The event handler accepts a [GetFieldPayloadPB] and returns a [RepeatedFieldPB] - /// if there are no errors. - #[event(input = "GetFieldPayloadPB", output = "RepeatedFieldPB")] - GetFields = 10, + /// [GetFields] event is used to get the database's settings. + /// + /// The event handler accepts a [GetFieldPayloadPB] and returns a [RepeatedFieldPB] + /// if there are no errors. + #[event(input = "GetFieldPayloadPB", output = "RepeatedFieldPB")] + GetFields = 10, - /// [UpdateField] event is used to update a field's attributes. - /// - /// The event handler accepts a [FieldChangesetPB] and returns errors if failed to modify the - /// field. - #[event(input = "FieldChangesetPB")] - UpdateField = 11, + /// [UpdateField] event is used to update a field's attributes. + /// + /// The event handler accepts a [FieldChangesetPB] and returns errors if failed to modify the + /// field. + #[event(input = "FieldChangesetPB")] + UpdateField = 11, - /// [UpdateFieldTypeOption] event is used to update the field's type-option data. Certain field - /// types have user-defined options such as color, date format, number format, or a list of values - /// for a multi-select list. These options are defined within a specialization of the - /// FieldTypeOption class. - /// - /// Check out [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid#fieldtype) - /// for more information. - /// - /// The event handler accepts a [TypeOptionChangesetPB] and returns errors if failed to modify the - /// field. - #[event(input = "TypeOptionChangesetPB")] - UpdateFieldTypeOption = 12, + /// [UpdateFieldTypeOption] event is used to update the field's type-option data. Certain field + /// types have user-defined options such as color, date format, number format, or a list of values + /// for a multi-select list. These options are defined within a specialization of the + /// FieldTypeOption class. + /// + /// Check out [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid#fieldtype) + /// for more information. + /// + /// The event handler accepts a [TypeOptionChangesetPB] and returns errors if failed to modify the + /// field. + #[event(input = "TypeOptionChangesetPB")] + UpdateFieldTypeOption = 12, - /// [DeleteField] event is used to delete a Field. [DeleteFieldPayloadPB] is the context that - /// is used to delete the field from the Database. - #[event(input = "DeleteFieldPayloadPB")] - DeleteField = 14, + /// [DeleteField] event is used to delete a Field. [DeleteFieldPayloadPB] is the context that + /// is used to delete the field from the Database. + #[event(input = "DeleteFieldPayloadPB")] + DeleteField = 14, - /// [UpdateFieldType] event is used to update the current Field's type. - /// It will insert a new FieldTypeOptionData if the new FieldType doesn't exist before, otherwise - /// reuse the existing FieldTypeOptionData. You could check the [DatabaseRevisionPad] for more details. - #[event(input = "UpdateFieldTypePayloadPB")] - UpdateFieldType = 20, + /// [UpdateFieldType] event is used to update the current Field's type. + /// It will insert a new FieldTypeOptionData if the new FieldType doesn't exist before, otherwise + /// reuse the existing FieldTypeOptionData. You could check the [DatabaseRevisionPad] for more details. + #[event(input = "UpdateFieldTypePayloadPB")] + UpdateFieldType = 20, - /// [DuplicateField] event is used to duplicate a Field. The duplicated field data is kind of - /// deep copy of the target field. The passed in [DuplicateFieldPayloadPB] is the context that is - /// used to duplicate the field. - /// - /// Return errors if failed to duplicate the field. - /// - #[event(input = "DuplicateFieldPayloadPB")] - DuplicateField = 21, + /// [DuplicateField] event is used to duplicate a Field. The duplicated field data is kind of + /// deep copy of the target field. The passed in [DuplicateFieldPayloadPB] is the context that is + /// used to duplicate the field. + /// + /// Return errors if failed to duplicate the field. + /// + #[event(input = "DuplicateFieldPayloadPB")] + DuplicateField = 21, - /// [MoveItem] event is used to move an item. For the moment, Item has two types defined in - /// [MoveItemTypePB]. - #[event(input = "MoveFieldPayloadPB")] - MoveField = 22, + /// [MoveItem] event is used to move an item. For the moment, Item has two types defined in + /// [MoveItemTypePB]. + #[event(input = "MoveFieldPayloadPB")] + MoveField = 22, - /// [TypeOptionPathPB] event is used to get the FieldTypeOption data for a specific field type. - /// - /// Check out the [TypeOptionPB] for more details. If the [FieldTypeOptionData] does exist - /// for the target type, the [TypeOptionBuilder] will create the default data for that type. - /// - /// Return the [TypeOptionPB] if there are no errors. - #[event(input = "TypeOptionPathPB", output = "TypeOptionPB")] - GetTypeOption = 23, + /// [TypeOptionPathPB] event is used to get the FieldTypeOption data for a specific field type. + /// + /// Check out the [TypeOptionPB] for more details. If the [FieldTypeOptionData] does exist + /// for the target type, the [TypeOptionBuilder] will create the default data for that type. + /// + /// Return the [TypeOptionPB] if there are no errors. + #[event(input = "TypeOptionPathPB", output = "TypeOptionPB")] + GetTypeOption = 23, - /// [CreateTypeOption] event is used to create a new FieldTypeOptionData. - #[event(input = "CreateFieldPayloadPB", output = "TypeOptionPB")] - CreateTypeOption = 24, + /// [CreateTypeOption] event is used to create a new FieldTypeOptionData. + #[event(input = "CreateFieldPayloadPB", output = "TypeOptionPB")] + CreateTypeOption = 24, - /// [CreateSelectOption] event is used to create a new select option. Returns a [SelectOptionPB] if - /// there are no errors. - #[event(input = "CreateSelectOptionPayloadPB", output = "SelectOptionPB")] - CreateSelectOption = 30, + /// [CreateSelectOption] event is used to create a new select option. Returns a [SelectOptionPB] if + /// there are no errors. + #[event(input = "CreateSelectOptionPayloadPB", output = "SelectOptionPB")] + CreateSelectOption = 30, - /// [GetSelectOptionCellData] event is used to get the select option data for cell editing. - /// [CellIdPB] locate which cell data that will be read from. The return value, [SelectOptionCellDataPB] - /// contains the available options and the currently selected options. - #[event(input = "CellIdPB", output = "SelectOptionCellDataPB")] - GetSelectOptionCellData = 31, + /// [GetSelectOptionCellData] event is used to get the select option data for cell editing. + /// [CellIdPB] locate which cell data that will be read from. The return value, [SelectOptionCellDataPB] + /// contains the available options and the currently selected options. + #[event(input = "CellIdPB", output = "SelectOptionCellDataPB")] + GetSelectOptionCellData = 31, - /// [UpdateSelectOption] event is used to update a FieldTypeOptionData whose field_type is - /// FieldType::SingleSelect or FieldType::MultiSelect. - /// - /// This event may trigger the DatabaseNotification::DidUpdateCell event. - /// For example, DatabaseNotification::DidUpdateCell will be triggered if the [SelectOptionChangesetPB] - /// carries a change that updates the name of the option. - #[event(input = "SelectOptionChangesetPB")] - UpdateSelectOption = 32, + /// [UpdateSelectOption] event is used to update a FieldTypeOptionData whose field_type is + /// FieldType::SingleSelect or FieldType::MultiSelect. + /// + /// This event may trigger the DatabaseNotification::DidUpdateCell event. + /// For example, DatabaseNotification::DidUpdateCell will be triggered if the [SelectOptionChangesetPB] + /// carries a change that updates the name of the option. + #[event(input = "SelectOptionChangesetPB")] + UpdateSelectOption = 32, - #[event(input = "CreateRowPayloadPB", output = "RowPB")] - CreateRow = 50, + #[event(input = "CreateRowPayloadPB", output = "RowPB")] + CreateRow = 50, - /// [GetRow] event is used to get the row data,[RowPB]. [OptionalRowPB] is a wrapper that enables - /// to return a nullable row data. - #[event(input = "RowIdPB", output = "OptionalRowPB")] - GetRow = 51, + /// [GetRow] event is used to get the row data,[RowPB]. [OptionalRowPB] is a wrapper that enables + /// to return a nullable row data. + #[event(input = "RowIdPB", output = "OptionalRowPB")] + GetRow = 51, - #[event(input = "RowIdPB")] - DeleteRow = 52, + #[event(input = "RowIdPB")] + DeleteRow = 52, - #[event(input = "RowIdPB")] - DuplicateRow = 53, + #[event(input = "RowIdPB")] + DuplicateRow = 53, - #[event(input = "MoveRowPayloadPB")] - MoveRow = 54, + #[event(input = "MoveRowPayloadPB")] + MoveRow = 54, - #[event(input = "CellIdPB", output = "CellPB")] - GetCell = 70, + #[event(input = "CellIdPB", output = "CellPB")] + GetCell = 70, - /// [UpdateCell] event is used to update the cell content. The passed in data, [CellChangesetPB], - /// carries the changes that will be applied to the cell content by calling `update_cell` function. - /// - /// The 'content' property of the [CellChangesetPB] is a String type. It can be used directly if the - /// cell uses string data. For example, the TextCell or NumberCell. - /// - /// But,it can be treated as a generic type, because we can use [serde] to deserialize the string - /// into a specific data type. For the moment, the 'content' will be deserialized to a concrete type - /// when the FieldType is SingleSelect, DateTime, and MultiSelect. Please see - /// the [UpdateSelectOptionCell] and [UpdateDateCell] events for more details. - #[event(input = "CellChangesetPB")] - UpdateCell = 71, + /// [UpdateCell] event is used to update the cell content. The passed in data, [CellChangesetPB], + /// carries the changes that will be applied to the cell content by calling `update_cell` function. + /// + /// The 'content' property of the [CellChangesetPB] is a String type. It can be used directly if the + /// cell uses string data. For example, the TextCell or NumberCell. + /// + /// But,it can be treated as a generic type, because we can use [serde] to deserialize the string + /// into a specific data type. For the moment, the 'content' will be deserialized to a concrete type + /// when the FieldType is SingleSelect, DateTime, and MultiSelect. Please see + /// the [UpdateSelectOptionCell] and [UpdateDateCell] events for more details. + #[event(input = "CellChangesetPB")] + UpdateCell = 71, - /// [UpdateSelectOptionCell] event is used to update a select option cell's data. [SelectOptionCellChangesetPB] - /// contains options that will be deleted or inserted. It can be cast to [CellChangesetPB] that - /// will be used by the `update_cell` function. - #[event(input = "SelectOptionCellChangesetPB")] - UpdateSelectOptionCell = 72, + /// [UpdateSelectOptionCell] event is used to update a select option cell's data. [SelectOptionCellChangesetPB] + /// contains options that will be deleted or inserted. It can be cast to [CellChangesetPB] that + /// will be used by the `update_cell` function. + #[event(input = "SelectOptionCellChangesetPB")] + UpdateSelectOptionCell = 72, - /// [UpdateDateCell] event is used to update a date cell's data. [DateChangesetPB] - /// contains the date and the time string. It can be cast to [CellChangesetPB] that - /// will be used by the `update_cell` function. - #[event(input = "DateChangesetPB")] - UpdateDateCell = 80, + /// [UpdateDateCell] event is used to update a date cell's data. [DateChangesetPB] + /// contains the date and the time string. It can be cast to [CellChangesetPB] that + /// will be used by the `update_cell` function. + #[event(input = "DateChangesetPB")] + UpdateDateCell = 80, - #[event(input = "DatabaseIdPB", output = "RepeatedGroupPB")] - GetGroup = 100, + #[event(input = "DatabaseIdPB", output = "RepeatedGroupPB")] + GetGroup = 100, - #[event(input = "CreateBoardCardPayloadPB", output = "RowPB")] - CreateBoardCard = 110, + #[event(input = "CreateBoardCardPayloadPB", output = "RowPB")] + CreateBoardCard = 110, - #[event(input = "MoveGroupPayloadPB")] - MoveGroup = 111, + #[event(input = "MoveGroupPayloadPB")] + MoveGroup = 111, - #[event(input = "MoveGroupRowPayloadPB")] - MoveGroupRow = 112, + #[event(input = "MoveGroupRowPayloadPB")] + MoveGroupRow = 112, - #[event(input = "MoveGroupRowPayloadPB")] - GroupByField = 113, + #[event(input = "MoveGroupRowPayloadPB")] + GroupByField = 113, } diff --git a/frontend/rust-lib/flowy-database/src/macros.rs b/frontend/rust-lib/flowy-database/src/macros.rs index 72018725a7..18afe49544 100644 --- a/frontend/rust-lib/flowy-database/src/macros.rs +++ b/frontend/rust-lib/flowy-database/src/macros.rs @@ -1,92 +1,92 @@ #[macro_export] macro_rules! impl_into_box_type_option_builder { - ($target: ident) => { - impl std::convert::From<$target> for BoxTypeOptionBuilder { - fn from(target: $target) -> BoxTypeOptionBuilder { - Box::new(target) - } - } - }; + ($target: ident) => { + impl std::convert::From<$target> for BoxTypeOptionBuilder { + fn from(target: $target) -> BoxTypeOptionBuilder { + Box::new(target) + } + } + }; } macro_rules! impl_builder_from_json_str_and_from_bytes { - ($target: ident,$type_option: ident) => { - impl $target { - pub fn from_protobuf_bytes(bytes: Bytes) -> $target { - let type_option = $type_option::from_protobuf_bytes(bytes); - $target(type_option) - } + ($target: ident,$type_option: ident) => { + impl $target { + pub fn from_protobuf_bytes(bytes: Bytes) -> $target { + let type_option = $type_option::from_protobuf_bytes(bytes); + $target(type_option) + } - pub fn from_json_str(s: &str) -> $target { - let type_option = $type_option::from_json_str(s); - $target(type_option) - } - } - }; + pub fn from_json_str(s: &str) -> $target { + let type_option = $type_option::from_json_str(s); + $target(type_option) + } + } + }; } #[macro_export] macro_rules! impl_type_option { - ($target: ident, $field_type:expr) => { - impl std::convert::From<&FieldRevision> for $target { - fn from(field_rev: &FieldRevision) -> $target { - match field_rev.get_type_option::<$target>($field_type.into()) { - None => $target::default(), - Some(target) => target, - } - } + ($target: ident, $field_type:expr) => { + impl std::convert::From<&FieldRevision> for $target { + fn from(field_rev: &FieldRevision) -> $target { + match field_rev.get_type_option::<$target>($field_type.into()) { + None => $target::default(), + Some(target) => target, } + } + } - impl std::convert::From<&std::sync::Arc> for $target { - fn from(field_rev: &std::sync::Arc) -> $target { - match field_rev.get_type_option::<$target>($field_type.into()) { - None => $target::default(), - Some(target) => target, - } - } + impl std::convert::From<&std::sync::Arc> for $target { + fn from(field_rev: &std::sync::Arc) -> $target { + match field_rev.get_type_option::<$target>($field_type.into()) { + None => $target::default(), + Some(target) => target, } + } + } - impl std::convert::From<$target> for String { - fn from(type_option: $target) -> String { - type_option.json_str() - } + impl std::convert::From<$target> for String { + fn from(type_option: $target) -> String { + type_option.json_str() + } + } + + impl TypeOptionDataSerializer for $target { + fn json_str(&self) -> String { + match serde_json::to_string(&self) { + Ok(s) => s, + Err(e) => { + tracing::error!("Field type data serialize to json fail, error: {:?}", e); + serde_json::to_string(&$target::default()).unwrap() + }, } + } - impl TypeOptionDataSerializer for $target { - fn json_str(&self) -> String { - match serde_json::to_string(&self) { - Ok(s) => s, - Err(e) => { - tracing::error!("Field type data serialize to json fail, error: {:?}", e); - serde_json::to_string(&$target::default()).unwrap() - } - } - } + fn protobuf_bytes(&self) -> Bytes { + self.clone().try_into().unwrap() + } + } - fn protobuf_bytes(&self) -> Bytes { - self.clone().try_into().unwrap() - } + impl TypeOptionDataDeserializer for $target { + fn from_json_str(s: &str) -> $target { + match serde_json::from_str(s) { + Ok(obj) => obj, + Err(err) => { + tracing::error!( + "{} type option deserialize from {} failed, {:?}", + stringify!($target), + s, + err + ); + $target::default() + }, } + } - impl TypeOptionDataDeserializer for $target { - fn from_json_str(s: &str) -> $target { - match serde_json::from_str(s) { - Ok(obj) => obj, - Err(err) => { - tracing::error!( - "{} type option deserialize from {} failed, {:?}", - stringify!($target), - s, - err - ); - $target::default() - } - } - } - - fn from_protobuf_bytes(bytes: Bytes) -> $target { - $target::try_from(bytes).unwrap_or($target::default()) - } - } - }; + fn from_protobuf_bytes(bytes: Bytes) -> $target { + $target::try_from(bytes).unwrap_or($target::default()) + } + } + }; } diff --git a/frontend/rust-lib/flowy-database/src/manager.rs b/frontend/rust-lib/flowy-database/src/manager.rs index 842ea7721d..a4b7d1dba0 100644 --- a/frontend/rust-lib/flowy-database/src/manager.rs +++ b/frontend/rust-lib/flowy-database/src/manager.rs @@ -1,21 +1,23 @@ use crate::entities::LayoutTypePB; use crate::services::grid_editor::{ - DatabaseRevisionEditor, GridRevisionCloudService, GridRevisionMergeable, GridRevisionSerde, + DatabaseRevisionEditor, GridRevisionCloudService, GridRevisionMergeable, GridRevisionSerde, }; use crate::services::persistence::block_index::BlockIndexCache; use crate::services::persistence::kv::DatabaseKVPersistence; use crate::services::persistence::migration::DatabaseMigration; use crate::services::persistence::rev_sqlite::{ - SQLiteDatabaseRevisionPersistence, SQLiteDatabaseRevisionSnapshotPersistence, + SQLiteDatabaseRevisionPersistence, SQLiteDatabaseRevisionSnapshotPersistence, }; use crate::services::persistence::GridDatabase; use crate::services::view_editor::make_database_view_rev_manager; use bytes::Bytes; use flowy_client_sync::client_database::{ - make_database_block_operations, make_database_operations, make_grid_view_operations, + make_database_block_operations, make_database_operations, make_grid_view_operations, }; use flowy_error::{FlowyError, FlowyResult}; -use flowy_revision::{RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, RevisionWebSocket}; +use flowy_revision::{ + RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, RevisionWebSocket, +}; use flowy_sqlite::ConnectionPool; use grid_model::{BuildDatabaseContext, DatabaseRevision, DatabaseViewRevision}; use lib_infra::async_trait::async_trait; @@ -28,228 +30,264 @@ use std::sync::Arc; use tokio::sync::RwLock; pub trait DatabaseUser: Send + Sync { - fn user_id(&self) -> Result; - fn token(&self) -> Result; - fn db_pool(&self) -> Result, FlowyError>; + fn user_id(&self) -> Result; + fn token(&self) -> Result; + fn db_pool(&self) -> Result, FlowyError>; } pub struct DatabaseManager { - database_editors: RwLock>>, - database_user: Arc, - block_index_cache: Arc, - #[allow(dead_code)] - kv_persistence: Arc, - task_scheduler: Arc>, - migration: DatabaseMigration, + database_editors: RwLock>>, + database_user: Arc, + block_index_cache: Arc, + #[allow(dead_code)] + kv_persistence: Arc, + task_scheduler: Arc>, + migration: DatabaseMigration, } impl DatabaseManager { - pub fn new( - grid_user: Arc, - _rev_web_socket: Arc, - task_scheduler: Arc>, - database: Arc, - ) -> Self { - let grid_editors = RwLock::new(RefCountHashMap::new()); - let kv_persistence = Arc::new(DatabaseKVPersistence::new(database.clone())); - let block_index_cache = Arc::new(BlockIndexCache::new(database.clone())); - let migration = DatabaseMigration::new(grid_user.clone(), database); - Self { - database_editors: grid_editors, - database_user: grid_user, - kv_persistence, - block_index_cache, - task_scheduler, - migration, - } + pub fn new( + grid_user: Arc, + _rev_web_socket: Arc, + task_scheduler: Arc>, + database: Arc, + ) -> Self { + let grid_editors = RwLock::new(RefCountHashMap::new()); + let kv_persistence = Arc::new(DatabaseKVPersistence::new(database.clone())); + let block_index_cache = Arc::new(BlockIndexCache::new(database.clone())); + let migration = DatabaseMigration::new(grid_user.clone(), database); + Self { + database_editors: grid_editors, + database_user: grid_user, + kv_persistence, + block_index_cache, + task_scheduler, + migration, + } + } + + pub async fn initialize_with_new_user(&self, _user_id: &str, _token: &str) -> FlowyResult<()> { + Ok(()) + } + + pub async fn initialize(&self, _user_id: &str, _token: &str) -> FlowyResult<()> { + Ok(()) + } + + #[tracing::instrument(level = "debug", skip_all, err)] + pub async fn create_database>( + &self, + database_id: T, + revisions: Vec, + ) -> FlowyResult<()> { + let database_id = database_id.as_ref(); + let db_pool = self.database_user.db_pool()?; + let rev_manager = self.make_database_rev_manager(database_id, db_pool)?; + rev_manager.reset_object(revisions).await?; + + Ok(()) + } + + #[tracing::instrument(level = "debug", skip_all, err)] + async fn create_database_view>( + &self, + view_id: T, + revisions: Vec, + ) -> FlowyResult<()> { + let view_id = view_id.as_ref(); + let rev_manager = make_database_view_rev_manager(&self.database_user, view_id).await?; + rev_manager.reset_object(revisions).await?; + Ok(()) + } + + #[tracing::instrument(level = "debug", skip_all, err)] + pub async fn create_database_block>( + &self, + block_id: T, + revisions: Vec, + ) -> FlowyResult<()> { + let block_id = block_id.as_ref(); + let rev_manager = make_database_block_rev_manager(&self.database_user, block_id)?; + rev_manager.reset_object(revisions).await?; + Ok(()) + } + + pub async fn open_database>( + &self, + database_id: T, + ) -> FlowyResult> { + let database_id = database_id.as_ref(); + let _ = self.migration.run_v1_migration(database_id).await; + self.get_or_create_database_editor(database_id).await + } + + #[tracing::instrument(level = "debug", skip_all, fields(database_id), err)] + pub async fn close_database>(&self, database_id: T) -> FlowyResult<()> { + let database_id = database_id.as_ref(); + tracing::Span::current().record("database_id", database_id); + self + .database_editors + .write() + .await + .remove(database_id) + .await; + Ok(()) + } + + // #[tracing::instrument(level = "debug", skip(self), err)] + pub async fn get_database_editor( + &self, + database_id: &str, + ) -> FlowyResult> { + let read_guard = self.database_editors.read().await; + let editor = read_guard.get(database_id); + match editor { + None => { + // Drop the read_guard ASAP in case of the following read/write lock + drop(read_guard); + self.open_database(database_id).await + }, + Some(editor) => Ok(editor), + } + } + + async fn get_or_create_database_editor( + &self, + database_id: &str, + ) -> FlowyResult> { + if let Some(editor) = self.database_editors.read().await.get(database_id) { + return Ok(editor); } - pub async fn initialize_with_new_user(&self, _user_id: &str, _token: &str) -> FlowyResult<()> { - Ok(()) - } + let mut database_editors = self.database_editors.write().await; + let db_pool = self.database_user.db_pool()?; + let editor = self.make_database_rev_editor(database_id, db_pool).await?; + tracing::trace!("Open database: {}", database_id); + database_editors.insert(database_id.to_string(), editor.clone()); + Ok(editor) + } - pub async fn initialize(&self, _user_id: &str, _token: &str) -> FlowyResult<()> { - Ok(()) - } + #[tracing::instrument(level = "trace", skip(self, pool), err)] + async fn make_database_rev_editor( + &self, + database_id: &str, + pool: Arc, + ) -> Result, FlowyError> { + let user = self.database_user.clone(); + let token = user.token()?; + let cloud = Arc::new(GridRevisionCloudService::new(token)); + let mut rev_manager = self.make_database_rev_manager(database_id, pool.clone())?; + let database_pad = Arc::new(RwLock::new( + rev_manager + .initialize::(Some(cloud)) + .await?, + )); + let database_editor = DatabaseRevisionEditor::new( + database_id, + user, + database_pad, + rev_manager, + self.block_index_cache.clone(), + self.task_scheduler.clone(), + ) + .await?; + Ok(database_editor) + } - #[tracing::instrument(level = "debug", skip_all, err)] - pub async fn create_database>(&self, database_id: T, revisions: Vec) -> FlowyResult<()> { - let database_id = database_id.as_ref(); - let db_pool = self.database_user.db_pool()?; - let rev_manager = self.make_database_rev_manager(database_id, db_pool)?; - rev_manager.reset_object(revisions).await?; + #[tracing::instrument(level = "trace", skip(self, pool), err)] + pub fn make_database_rev_manager( + &self, + database_id: &str, + pool: Arc, + ) -> FlowyResult>> { + let user_id = self.database_user.user_id()?; - Ok(()) - } + // Create revision persistence + let disk_cache = SQLiteDatabaseRevisionPersistence::new(&user_id, pool.clone()); + let configuration = RevisionPersistenceConfiguration::new(6, false); + let rev_persistence = + RevisionPersistence::new(&user_id, database_id, disk_cache, configuration); - #[tracing::instrument(level = "debug", skip_all, err)] - async fn create_database_view>(&self, view_id: T, revisions: Vec) -> FlowyResult<()> { - let view_id = view_id.as_ref(); - let rev_manager = make_database_view_rev_manager(&self.database_user, view_id).await?; - rev_manager.reset_object(revisions).await?; - Ok(()) - } + // Create snapshot persistence + let snapshot_object_id = format!("grid:{}", database_id); + let snapshot_persistence = + SQLiteDatabaseRevisionSnapshotPersistence::new(&snapshot_object_id, pool); - #[tracing::instrument(level = "debug", skip_all, err)] - pub async fn create_database_block>(&self, block_id: T, revisions: Vec) -> FlowyResult<()> { - let block_id = block_id.as_ref(); - let rev_manager = make_database_block_rev_manager(&self.database_user, block_id)?; - rev_manager.reset_object(revisions).await?; - Ok(()) - } - - pub async fn open_database>(&self, database_id: T) -> FlowyResult> { - let database_id = database_id.as_ref(); - let _ = self.migration.run_v1_migration(database_id).await; - self.get_or_create_database_editor(database_id).await - } - - #[tracing::instrument(level = "debug", skip_all, fields(database_id), err)] - pub async fn close_database>(&self, database_id: T) -> FlowyResult<()> { - let database_id = database_id.as_ref(); - tracing::Span::current().record("database_id", database_id); - self.database_editors.write().await.remove(database_id).await; - Ok(()) - } - - // #[tracing::instrument(level = "debug", skip(self), err)] - pub async fn get_database_editor(&self, database_id: &str) -> FlowyResult> { - let read_guard = self.database_editors.read().await; - let editor = read_guard.get(database_id); - match editor { - None => { - // Drop the read_guard ASAP in case of the following read/write lock - drop(read_guard); - self.open_database(database_id).await - } - Some(editor) => Ok(editor), - } - } - - async fn get_or_create_database_editor(&self, database_id: &str) -> FlowyResult> { - if let Some(editor) = self.database_editors.read().await.get(database_id) { - return Ok(editor); - } - - let mut database_editors = self.database_editors.write().await; - let db_pool = self.database_user.db_pool()?; - let editor = self.make_database_rev_editor(database_id, db_pool).await?; - tracing::trace!("Open database: {}", database_id); - database_editors.insert(database_id.to_string(), editor.clone()); - Ok(editor) - } - - #[tracing::instrument(level = "trace", skip(self, pool), err)] - async fn make_database_rev_editor( - &self, - database_id: &str, - pool: Arc, - ) -> Result, FlowyError> { - let user = self.database_user.clone(); - let token = user.token()?; - let cloud = Arc::new(GridRevisionCloudService::new(token)); - let mut rev_manager = self.make_database_rev_manager(database_id, pool.clone())?; - let database_pad = Arc::new(RwLock::new( - rev_manager.initialize::(Some(cloud)).await?, - )); - let database_editor = DatabaseRevisionEditor::new( - database_id, - user, - database_pad, - rev_manager, - self.block_index_cache.clone(), - self.task_scheduler.clone(), - ) - .await?; - Ok(database_editor) - } - - #[tracing::instrument(level = "trace", skip(self, pool), err)] - pub fn make_database_rev_manager( - &self, - database_id: &str, - pool: Arc, - ) -> FlowyResult>> { - let user_id = self.database_user.user_id()?; - - // Create revision persistence - let disk_cache = SQLiteDatabaseRevisionPersistence::new(&user_id, pool.clone()); - let configuration = RevisionPersistenceConfiguration::new(6, false); - let rev_persistence = RevisionPersistence::new(&user_id, database_id, disk_cache, configuration); - - // Create snapshot persistence - let snapshot_object_id = format!("grid:{}", database_id); - let snapshot_persistence = SQLiteDatabaseRevisionSnapshotPersistence::new(&snapshot_object_id, pool); - - let rev_compress = GridRevisionMergeable(); - let rev_manager = RevisionManager::new( - &user_id, - database_id, - rev_persistence, - rev_compress, - snapshot_persistence, - ); - Ok(rev_manager) - } + let rev_compress = GridRevisionMergeable(); + let rev_manager = RevisionManager::new( + &user_id, + database_id, + rev_persistence, + rev_compress, + snapshot_persistence, + ); + Ok(rev_manager) + } } pub async fn make_database_view_data( - _user_id: &str, - view_id: &str, - layout: LayoutTypePB, - database_manager: Arc, - build_context: BuildDatabaseContext, + _user_id: &str, + view_id: &str, + layout: LayoutTypePB, + database_manager: Arc, + build_context: BuildDatabaseContext, ) -> FlowyResult { - let BuildDatabaseContext { - field_revs, - block_metas, - blocks, - grid_view_revision_data, - } = build_context; + let BuildDatabaseContext { + field_revs, + block_metas, + blocks, + grid_view_revision_data, + } = build_context; - for block_meta_data in &blocks { - let block_id = &block_meta_data.block_id; - // Indexing the block's rows - block_meta_data.rows.iter().for_each(|row| { - let _ = database_manager.block_index_cache.insert(&row.block_id, &row.id); - }); + for block_meta_data in &blocks { + let block_id = &block_meta_data.block_id; + // Indexing the block's rows + block_meta_data.rows.iter().for_each(|row| { + let _ = database_manager + .block_index_cache + .insert(&row.block_id, &row.id); + }); - // Create grid's block - let grid_block_delta = make_database_block_operations(block_meta_data); - let block_delta_data = grid_block_delta.json_bytes(); - let revision = Revision::initial_revision(block_id, block_delta_data); - database_manager - .create_database_block(&block_id, vec![revision]) - .await?; - } + // Create grid's block + let grid_block_delta = make_database_block_operations(block_meta_data); + let block_delta_data = grid_block_delta.json_bytes(); + let revision = Revision::initial_revision(block_id, block_delta_data); + database_manager + .create_database_block(&block_id, vec![revision]) + .await?; + } - // Will replace the grid_id with the value returned by the gen_grid_id() - let grid_id = view_id.to_owned(); - let grid_rev = DatabaseRevision::from_build_context(&grid_id, field_revs, block_metas); + // Will replace the grid_id with the value returned by the gen_grid_id() + let grid_id = view_id.to_owned(); + let grid_rev = DatabaseRevision::from_build_context(&grid_id, field_revs, block_metas); - // Create grid - let grid_rev_delta = make_database_operations(&grid_rev); - let grid_rev_delta_bytes = grid_rev_delta.json_bytes(); - let revision = Revision::initial_revision(&grid_id, grid_rev_delta_bytes.clone()); - database_manager.create_database(&grid_id, vec![revision]).await?; + // Create grid + let grid_rev_delta = make_database_operations(&grid_rev); + let grid_rev_delta_bytes = grid_rev_delta.json_bytes(); + let revision = Revision::initial_revision(&grid_id, grid_rev_delta_bytes.clone()); + database_manager + .create_database(&grid_id, vec![revision]) + .await?; - // Create grid view - let grid_view = if grid_view_revision_data.is_empty() { - DatabaseViewRevision::new(grid_id, view_id.to_owned(), layout.into()) - } else { - DatabaseViewRevision::from_json(grid_view_revision_data)? - }; - let grid_view_delta = make_grid_view_operations(&grid_view); - let grid_view_delta_bytes = grid_view_delta.json_bytes(); - let revision = Revision::initial_revision(view_id, grid_view_delta_bytes); - database_manager.create_database_view(view_id, vec![revision]).await?; + // Create grid view + let grid_view = if grid_view_revision_data.is_empty() { + DatabaseViewRevision::new(grid_id, view_id.to_owned(), layout.into()) + } else { + DatabaseViewRevision::from_json(grid_view_revision_data)? + }; + let grid_view_delta = make_grid_view_operations(&grid_view); + let grid_view_delta_bytes = grid_view_delta.json_bytes(); + let revision = Revision::initial_revision(view_id, grid_view_delta_bytes); + database_manager + .create_database_view(view_id, vec![revision]) + .await?; - Ok(grid_rev_delta_bytes) + Ok(grid_rev_delta_bytes) } #[async_trait] impl RefCountValue for DatabaseRevisionEditor { - async fn did_remove(&self) { - self.close().await; - } + async fn did_remove(&self) { + self.close().await; + } } diff --git a/frontend/rust-lib/flowy-database/src/notification.rs b/frontend/rust-lib/flowy-database/src/notification.rs index 7f870d45af..b4f56ba14e 100644 --- a/frontend/rust-lib/flowy-database/src/notification.rs +++ b/frontend/rust-lib/flowy-database/src/notification.rs @@ -4,48 +4,48 @@ const OBSERVABLE_CATEGORY: &str = "Grid"; #[derive(ProtoBuf_Enum, Debug)] pub enum DatabaseNotification { - Unknown = 0, - /// Trigger after inserting/deleting/updating a row - DidUpdateViewRows = 20, - /// Trigger when the visibility of the row was changed. For example, updating the filter will trigger the notification - DidUpdateViewRowsVisibility = 21, - /// Trigger after inserting/deleting/updating a field - DidUpdateFields = 22, - /// Trigger after editing a cell - DidUpdateCell = 40, - /// Trigger after editing a field properties including rename,update type option, etc - DidUpdateField = 50, - /// Trigger after the number of groups is changed - DidUpdateGroups = 60, - /// Trigger after inserting/deleting/updating/moving a row - DidUpdateGroupRow = 61, - /// Trigger when setting a new grouping field - DidGroupByField = 62, - /// Trigger after inserting/deleting/updating a filter - DidUpdateFilter = 63, - /// Trigger after inserting/deleting/updating a sort - DidUpdateSort = 64, - /// Trigger after the sort configurations are changed - DidReorderRows = 65, - /// Trigger after editing the row that hit the sort rule - DidReorderSingleRow = 66, - /// Trigger when the settings of the database are changed - DidUpdateSettings = 70, + Unknown = 0, + /// Trigger after inserting/deleting/updating a row + DidUpdateViewRows = 20, + /// Trigger when the visibility of the row was changed. For example, updating the filter will trigger the notification + DidUpdateViewRowsVisibility = 21, + /// Trigger after inserting/deleting/updating a field + DidUpdateFields = 22, + /// Trigger after editing a cell + DidUpdateCell = 40, + /// Trigger after editing a field properties including rename,update type option, etc + DidUpdateField = 50, + /// Trigger after the number of groups is changed + DidUpdateGroups = 60, + /// Trigger after inserting/deleting/updating/moving a row + DidUpdateGroupRow = 61, + /// Trigger when setting a new grouping field + DidGroupByField = 62, + /// Trigger after inserting/deleting/updating a filter + DidUpdateFilter = 63, + /// Trigger after inserting/deleting/updating a sort + DidUpdateSort = 64, + /// Trigger after the sort configurations are changed + DidReorderRows = 65, + /// Trigger after editing the row that hit the sort rule + DidReorderSingleRow = 66, + /// Trigger when the settings of the database are changed + DidUpdateSettings = 70, } impl std::default::Default for DatabaseNotification { - fn default() -> Self { - DatabaseNotification::Unknown - } + fn default() -> Self { + DatabaseNotification::Unknown + } } impl std::convert::From for i32 { - fn from(notification: DatabaseNotification) -> Self { - notification as i32 - } + fn from(notification: DatabaseNotification) -> Self { + notification as i32 + } } #[tracing::instrument(level = "trace")] pub fn send_notification(id: &str, ty: DatabaseNotification) -> NotificationBuilder { - NotificationBuilder::new(id, ty, OBSERVABLE_CATEGORY) + NotificationBuilder::new(id, ty, OBSERVABLE_CATEGORY) } diff --git a/frontend/rust-lib/flowy-database/src/services/block_editor.rs b/frontend/rust-lib/flowy-database/src/services/block_editor.rs index fec73a4535..63bc428b67 100644 --- a/frontend/rust-lib/flowy-database/src/services/block_editor.rs +++ b/frontend/rust-lib/flowy-database/src/services/block_editor.rs @@ -4,7 +4,8 @@ use flowy_client_sync::client_database::{GridBlockRevisionChangeset, GridBlockRe use flowy_client_sync::make_operations_from_revisions; use flowy_error::{FlowyError, FlowyResult}; use flowy_revision::{ - RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, RevisionObjectSerializer, + RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, + RevisionObjectSerializer, }; use flowy_sqlite::ConnectionPool; use grid_model::{CellRevision, DatabaseBlockRevision, RowChangeset, RowRevision}; @@ -17,201 +18,218 @@ use std::sync::Arc; use tokio::sync::RwLock; pub struct DatabaseBlockRevisionEditor { - #[allow(dead_code)] - user_id: String, - pub block_id: String, - pad: Arc>, - rev_manager: Arc>>, + #[allow(dead_code)] + user_id: String, + pub block_id: String, + pad: Arc>, + rev_manager: Arc>>, } impl DatabaseBlockRevisionEditor { - pub async fn new( - user_id: &str, - token: &str, - block_id: &str, - mut rev_manager: RevisionManager>, - ) -> FlowyResult { - let cloud = Arc::new(GridBlockRevisionCloudService { - token: token.to_owned(), - }); - let block_revision_pad = rev_manager - .initialize::(Some(cloud)) - .await?; - let pad = Arc::new(RwLock::new(block_revision_pad)); - let rev_manager = Arc::new(rev_manager); - let user_id = user_id.to_owned(); - let block_id = block_id.to_owned(); - Ok(Self { - user_id, - block_id, - pad, - rev_manager, - }) - } + pub async fn new( + user_id: &str, + token: &str, + block_id: &str, + mut rev_manager: RevisionManager>, + ) -> FlowyResult { + let cloud = Arc::new(GridBlockRevisionCloudService { + token: token.to_owned(), + }); + let block_revision_pad = rev_manager + .initialize::(Some(cloud)) + .await?; + let pad = Arc::new(RwLock::new(block_revision_pad)); + let rev_manager = Arc::new(rev_manager); + let user_id = user_id.to_owned(); + let block_id = block_id.to_owned(); + Ok(Self { + user_id, + block_id, + pad, + rev_manager, + }) + } - pub async fn close(&self) { - self.rev_manager.generate_snapshot().await; - self.rev_manager.close().await; - } + pub async fn close(&self) { + self.rev_manager.generate_snapshot().await; + self.rev_manager.close().await; + } - pub async fn duplicate_block(&self, duplicated_block_id: &str) -> DatabaseBlockRevision { - self.pad.read().await.duplicate_data(duplicated_block_id) - } + pub async fn duplicate_block(&self, duplicated_block_id: &str) -> DatabaseBlockRevision { + self.pad.read().await.duplicate_data(duplicated_block_id) + } - /// Create a row after the the with prev_row_id. If prev_row_id is None, the row will be appended to the list - pub(crate) async fn create_row( - &self, - row: RowRevision, - prev_row_id: Option, - ) -> FlowyResult<(i32, Option)> { - let mut row_count = 0; - let mut row_index = None; - self.modify(|block_pad| { - if let Some(start_row_id) = prev_row_id.as_ref() { - match block_pad.index_of_row(start_row_id) { - None => {} - Some(index) => row_index = Some(index as i32 + 1), - } - } - - let change = block_pad.add_row_rev(row, prev_row_id)?; - row_count = block_pad.number_of_rows(); - - if row_index.is_none() { - row_index = Some(row_count - 1); - } - Ok(change) - }) - .await?; - - Ok((row_count, row_index)) - } - - pub async fn delete_rows(&self, ids: Vec>) -> FlowyResult { - let mut row_count = 0; - self.modify(|block_pad| { - let changeset = block_pad.delete_rows(ids)?; - row_count = block_pad.number_of_rows(); - Ok(changeset) - }) - .await?; - Ok(row_count) - } - - pub async fn update_row(&self, changeset: RowChangeset) -> FlowyResult<()> { - self.modify(|block_pad| Ok(block_pad.update_row(changeset)?)).await?; - Ok(()) - } - - pub async fn move_row(&self, row_id: &str, from: usize, to: usize) -> FlowyResult<()> { - self.modify(|block_pad| Ok(block_pad.move_row(row_id, from, to)?)) - .await?; - Ok(()) - } - - pub async fn index_of_row(&self, row_id: &str) -> Option { - self.pad.read().await.index_of_row(row_id) - } - - pub async fn number_of_rows(&self) -> i32 { - self.pad.read().await.rows.len() as i32 - } - - pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult)>> { - if let Ok(pad) = self.pad.try_read() { - Ok(pad.get_row_rev(row_id)) - } else { - tracing::error!("Required grid block read lock failed, retrying"); - let retry = GetRowDataRetryAction { - row_id: row_id.to_owned(), - pad: self.pad.clone(), - }; - match spawn_retry(3, 300, retry).await { - Ok(value) => Ok(value), - Err(err) => { - tracing::error!("Read row revision failed with: {}", err); - Ok(None) - } - } + /// Create a row after the the with prev_row_id. If prev_row_id is None, the row will be appended to the list + pub(crate) async fn create_row( + &self, + row: RowRevision, + prev_row_id: Option, + ) -> FlowyResult<(i32, Option)> { + let mut row_count = 0; + let mut row_index = None; + self + .modify(|block_pad| { + if let Some(start_row_id) = prev_row_id.as_ref() { + match block_pad.index_of_row(start_row_id) { + None => {}, + Some(index) => row_index = Some(index as i32 + 1), + } } - } - pub async fn get_row_revs(&self, row_ids: Option>>) -> FlowyResult>> - where - T: AsRef + ToOwned + ?Sized, - { - let row_revs = self.pad.read().await.get_row_revs(row_ids)?; - Ok(row_revs) - } + let change = block_pad.add_row_rev(row, prev_row_id)?; + row_count = block_pad.number_of_rows(); - pub async fn get_cell_revs( - &self, - field_id: &str, - row_ids: Option>>, - ) -> FlowyResult> { - let cell_revs = self.pad.read().await.get_cell_revs(field_id, row_ids)?; - Ok(cell_revs) - } - - async fn modify(&self, f: F) -> FlowyResult<()> - where - F: for<'a> FnOnce(&'a mut GridBlockRevisionPad) -> FlowyResult>, - { - let mut write_guard = self.pad.write().await; - let changeset = f(&mut write_guard)?; - match changeset { - None => {} - Some(changeset) => { - self.apply_change(changeset).await?; - } + if row_index.is_none() { + row_index = Some(row_count - 1); } - Ok(()) - } + Ok(change) + }) + .await?; - async fn apply_change(&self, change: GridBlockRevisionChangeset) -> FlowyResult<()> { - let GridBlockRevisionChangeset { operations: delta, md5 } = change; - let data = delta.json_bytes(); - let _ = self.rev_manager.add_local_revision(data, md5).await?; - Ok(()) + Ok((row_count, row_index)) + } + + pub async fn delete_rows(&self, ids: Vec>) -> FlowyResult { + let mut row_count = 0; + self + .modify(|block_pad| { + let changeset = block_pad.delete_rows(ids)?; + row_count = block_pad.number_of_rows(); + Ok(changeset) + }) + .await?; + Ok(row_count) + } + + pub async fn update_row(&self, changeset: RowChangeset) -> FlowyResult<()> { + self + .modify(|block_pad| Ok(block_pad.update_row(changeset)?)) + .await?; + Ok(()) + } + + pub async fn move_row(&self, row_id: &str, from: usize, to: usize) -> FlowyResult<()> { + self + .modify(|block_pad| Ok(block_pad.move_row(row_id, from, to)?)) + .await?; + Ok(()) + } + + pub async fn index_of_row(&self, row_id: &str) -> Option { + self.pad.read().await.index_of_row(row_id) + } + + pub async fn number_of_rows(&self) -> i32 { + self.pad.read().await.rows.len() as i32 + } + + pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult)>> { + if let Ok(pad) = self.pad.try_read() { + Ok(pad.get_row_rev(row_id)) + } else { + tracing::error!("Required grid block read lock failed, retrying"); + let retry = GetRowDataRetryAction { + row_id: row_id.to_owned(), + pad: self.pad.clone(), + }; + match spawn_retry(3, 300, retry).await { + Ok(value) => Ok(value), + Err(err) => { + tracing::error!("Read row revision failed with: {}", err); + Ok(None) + }, + } } + } + + pub async fn get_row_revs( + &self, + row_ids: Option>>, + ) -> FlowyResult>> + where + T: AsRef + ToOwned + ?Sized, + { + let row_revs = self.pad.read().await.get_row_revs(row_ids)?; + Ok(row_revs) + } + + pub async fn get_cell_revs( + &self, + field_id: &str, + row_ids: Option>>, + ) -> FlowyResult> { + let cell_revs = self.pad.read().await.get_cell_revs(field_id, row_ids)?; + Ok(cell_revs) + } + + async fn modify(&self, f: F) -> FlowyResult<()> + where + F: for<'a> FnOnce( + &'a mut GridBlockRevisionPad, + ) -> FlowyResult>, + { + let mut write_guard = self.pad.write().await; + let changeset = f(&mut write_guard)?; + match changeset { + None => {}, + Some(changeset) => { + self.apply_change(changeset).await?; + }, + } + Ok(()) + } + + async fn apply_change(&self, change: GridBlockRevisionChangeset) -> FlowyResult<()> { + let GridBlockRevisionChangeset { + operations: delta, + md5, + } = change; + let data = delta.json_bytes(); + let _ = self.rev_manager.add_local_revision(data, md5).await?; + Ok(()) + } } struct GridBlockRevisionCloudService { - #[allow(dead_code)] - token: String, + #[allow(dead_code)] + token: String, } impl RevisionCloudService for GridBlockRevisionCloudService { - #[tracing::instrument(level = "trace", skip(self))] - fn fetch_object(&self, _user_id: &str, _object_id: &str) -> FutureResult, FlowyError> { - FutureResult::new(async move { Ok(vec![]) }) - } + #[tracing::instrument(level = "trace", skip(self))] + fn fetch_object( + &self, + _user_id: &str, + _object_id: &str, + ) -> FutureResult, FlowyError> { + FutureResult::new(async move { Ok(vec![]) }) + } } struct DatabaseBlockRevisionSerde(); impl RevisionObjectDeserializer for DatabaseBlockRevisionSerde { - type Output = GridBlockRevisionPad; + type Output = GridBlockRevisionPad; - fn deserialize_revisions(object_id: &str, revisions: Vec) -> FlowyResult { - let pad = GridBlockRevisionPad::from_revisions(object_id, revisions)?; - Ok(pad) - } + fn deserialize_revisions(object_id: &str, revisions: Vec) -> FlowyResult { + let pad = GridBlockRevisionPad::from_revisions(object_id, revisions)?; + Ok(pad) + } - fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { - None - } + fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { + None + } } impl RevisionObjectSerializer for DatabaseBlockRevisionSerde { - fn combine_revisions(revisions: Vec) -> FlowyResult { - let operations = make_operations_from_revisions::(revisions)?; - Ok(operations.json_bytes()) - } + fn combine_revisions(revisions: Vec) -> FlowyResult { + let operations = make_operations_from_revisions::(revisions)?; + Ok(operations.json_bytes()) + } } pub struct GridBlockRevisionMergeable(); impl RevisionMergeable for GridBlockRevisionMergeable { - fn combine_revisions(&self, revisions: Vec) -> FlowyResult { - DatabaseBlockRevisionSerde::combine_revisions(revisions) - } + fn combine_revisions(&self, revisions: Vec) -> FlowyResult { + DatabaseBlockRevisionSerde::combine_revisions(revisions) + } } diff --git a/frontend/rust-lib/flowy-database/src/services/block_manager.rs b/frontend/rust-lib/flowy-database/src/services/block_manager.rs index 53c559d4a8..a42c1b0f5b 100644 --- a/frontend/rust-lib/flowy-database/src/services/block_manager.rs +++ b/frontend/rust-lib/flowy-database/src/services/block_manager.rs @@ -4,14 +4,16 @@ use crate::notification::{send_notification, DatabaseNotification}; use crate::services::block_editor::{DatabaseBlockRevisionEditor, GridBlockRevisionMergeable}; use crate::services::persistence::block_index::BlockIndexCache; use crate::services::persistence::rev_sqlite::{ - SQLiteDatabaseBlockRevisionPersistence, SQLiteDatabaseRevisionSnapshotPersistence, + SQLiteDatabaseBlockRevisionPersistence, SQLiteDatabaseRevisionSnapshotPersistence, }; use crate::services::row::{make_row_from_row_rev, DatabaseBlockRow, DatabaseBlockRowRevision}; use dashmap::DashMap; use flowy_error::FlowyResult; use flowy_revision::{RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration}; use flowy_sqlite::ConnectionPool; -use grid_model::{GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowChangeset, RowRevision}; +use grid_model::{ + GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowChangeset, RowRevision, +}; use std::borrow::Cow; use std::collections::HashMap; use std::sync::Arc; @@ -19,301 +21,335 @@ use tokio::sync::broadcast; #[derive(Debug, Clone)] pub enum DatabaseBlockEvent { - InsertRow { - block_id: String, - row: InsertedRowPB, - }, - UpdateRow { - block_id: String, - row: UpdatedRowPB, - }, - DeleteRow { - block_id: String, - row_id: String, - }, - Move { - block_id: String, - deleted_row_id: String, - inserted_row: InsertedRowPB, - }, + InsertRow { + block_id: String, + row: InsertedRowPB, + }, + UpdateRow { + block_id: String, + row: UpdatedRowPB, + }, + DeleteRow { + block_id: String, + row_id: String, + }, + Move { + block_id: String, + deleted_row_id: String, + inserted_row: InsertedRowPB, + }, } type BlockId = String; pub(crate) struct DatabaseBlockManager { - user: Arc, - persistence: Arc, - block_editors: DashMap>, - event_notifier: broadcast::Sender, + user: Arc, + persistence: Arc, + block_editors: DashMap>, + event_notifier: broadcast::Sender, } impl DatabaseBlockManager { - pub(crate) async fn new( - user: &Arc, - block_meta_revs: Vec>, - persistence: Arc, - event_notifier: broadcast::Sender, - ) -> FlowyResult { - let block_editors = make_block_editors(user, block_meta_revs).await?; - let user = user.clone(); - let manager = Self { - user, - block_editors, - persistence, - event_notifier, - }; - Ok(manager) - } + pub(crate) async fn new( + user: &Arc, + block_meta_revs: Vec>, + persistence: Arc, + event_notifier: broadcast::Sender, + ) -> FlowyResult { + let block_editors = make_block_editors(user, block_meta_revs).await?; + let user = user.clone(); + let manager = Self { + user, + block_editors, + persistence, + event_notifier, + }; + Ok(manager) + } - pub async fn close(&self) { - for block_editor in self.block_editors.iter() { - block_editor.close().await; - } + pub async fn close(&self) { + for block_editor in self.block_editors.iter() { + block_editor.close().await; } + } - // #[tracing::instrument(level = "trace", skip(self))] - pub(crate) async fn get_block_editor(&self, block_id: &str) -> FlowyResult> { - debug_assert!(!block_id.is_empty()); - match self.block_editors.get(block_id) { - None => { - tracing::error!("This is a fatal error, block with id:{} is not exist", block_id); - let editor = Arc::new(make_database_block_editor(&self.user, block_id).await?); - self.block_editors.insert(block_id.to_owned(), editor.clone()); - Ok(editor) - } - Some(editor) => Ok(editor.clone()), - } + // #[tracing::instrument(level = "trace", skip(self))] + pub(crate) async fn get_block_editor( + &self, + block_id: &str, + ) -> FlowyResult> { + debug_assert!(!block_id.is_empty()); + match self.block_editors.get(block_id) { + None => { + tracing::error!( + "This is a fatal error, block with id:{} is not exist", + block_id + ); + let editor = Arc::new(make_database_block_editor(&self.user, block_id).await?); + self + .block_editors + .insert(block_id.to_owned(), editor.clone()); + Ok(editor) + }, + Some(editor) => Ok(editor.clone()), } + } - pub(crate) async fn get_editor_from_row_id(&self, row_id: &str) -> FlowyResult> { - let block_id = self.persistence.get_block_id(row_id)?; - self.get_block_editor(&block_id).await - } + pub(crate) async fn get_editor_from_row_id( + &self, + row_id: &str, + ) -> FlowyResult> { + let block_id = self.persistence.get_block_id(row_id)?; + self.get_block_editor(&block_id).await + } - #[tracing::instrument(level = "trace", skip(self, start_row_id), err)] - pub(crate) async fn create_row(&self, row_rev: RowRevision, start_row_id: Option) -> FlowyResult { - let block_id = row_rev.block_id.clone(); + #[tracing::instrument(level = "trace", skip(self, start_row_id), err)] + pub(crate) async fn create_row( + &self, + row_rev: RowRevision, + start_row_id: Option, + ) -> FlowyResult { + let block_id = row_rev.block_id.clone(); + self.persistence.insert(&row_rev.block_id, &row_rev.id)?; + let editor = self.get_block_editor(&row_rev.block_id).await?; + + let mut row = InsertedRowPB::from(&row_rev); + let (number_of_rows, index) = editor.create_row(row_rev, start_row_id).await?; + row.index = index; + + let _ = self + .event_notifier + .send(DatabaseBlockEvent::InsertRow { block_id, row }); + Ok(number_of_rows) + } + + pub(crate) async fn insert_row( + &self, + rows_by_block_id: HashMap>, + ) -> FlowyResult> { + let mut changesets = vec![]; + for (block_id, row_revs) in rows_by_block_id { + let editor = self.get_block_editor(&block_id).await?; + for row_rev in row_revs { self.persistence.insert(&row_rev.block_id, &row_rev.id)?; - let editor = self.get_block_editor(&row_rev.block_id).await?; - let mut row = InsertedRowPB::from(&row_rev); - let (number_of_rows, index) = editor.create_row(row_rev, start_row_id).await?; - row.index = index; - - let _ = self - .event_notifier - .send(DatabaseBlockEvent::InsertRow { block_id, row }); - Ok(number_of_rows) + row.index = editor.create_row(row_rev, None).await?.1; + let _ = self.event_notifier.send(DatabaseBlockEvent::InsertRow { + block_id: block_id.clone(), + row, + }); + } + changesets.push(GridBlockMetaRevisionChangeset::from_row_count( + block_id.clone(), + editor.number_of_rows().await, + )); } - pub(crate) async fn insert_row( - &self, - rows_by_block_id: HashMap>, - ) -> FlowyResult> { - let mut changesets = vec![]; - for (block_id, row_revs) in rows_by_block_id { - let editor = self.get_block_editor(&block_id).await?; - for row_rev in row_revs { - self.persistence.insert(&row_rev.block_id, &row_rev.id)?; - let mut row = InsertedRowPB::from(&row_rev); - row.index = editor.create_row(row_rev, None).await?.1; - let _ = self.event_notifier.send(DatabaseBlockEvent::InsertRow { - block_id: block_id.clone(), - row, - }); - } - changesets.push(GridBlockMetaRevisionChangeset::from_row_count( - block_id.clone(), - editor.number_of_rows().await, - )); - } + Ok(changesets) + } - Ok(changesets) - } - - pub async fn update_row(&self, changeset: RowChangeset) -> FlowyResult<()> { - let editor = self.get_editor_from_row_id(&changeset.row_id).await?; - editor.update_row(changeset.clone()).await?; - match editor.get_row_rev(&changeset.row_id).await? { - None => tracing::error!("Update row failed, can't find the row with id: {}", changeset.row_id), - Some((_, row_rev)) => { - let changed_field_ids = changeset.cell_by_field_id.keys().cloned().collect::>(); - let row = UpdatedRowPB { - row: make_row_from_row_rev(row_rev), - field_ids: changed_field_ids, - }; - - let _ = self.event_notifier.send(DatabaseBlockEvent::UpdateRow { - block_id: editor.block_id.clone(), - row, - }); - } - } - Ok(()) - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn delete_row(&self, row_id: &str) -> FlowyResult>> { - let row_id = row_id.to_owned(); - let block_id = self.persistence.get_block_id(&row_id)?; - let editor = self.get_block_editor(&block_id).await?; - match editor.get_row_rev(&row_id).await? { - None => Ok(None), - Some((_, row_rev)) => { - let _ = editor.delete_rows(vec![Cow::Borrowed(&row_id)]).await?; - let _ = self.event_notifier.send(DatabaseBlockEvent::DeleteRow { - block_id: editor.block_id.clone(), - row_id: row_rev.id.clone(), - }); - - Ok(Some(row_rev)) - } - } - } - - pub(crate) async fn delete_rows( - &self, - block_rows: Vec, - ) -> FlowyResult> { - let mut changesets = vec![]; - for block_row in block_rows { - let editor = self.get_block_editor(&block_row.block_id).await?; - let row_ids = block_row - .row_ids - .into_iter() - .map(Cow::Owned) - .collect::>>(); - let row_count = editor.delete_rows(row_ids).await?; - let changeset = GridBlockMetaRevisionChangeset::from_row_count(block_row.block_id, row_count); - changesets.push(changeset); - } - - Ok(changesets) - } - // This function will be moved to GridViewRevisionEditor - pub(crate) async fn move_row(&self, row_rev: Arc, from: usize, to: usize) -> FlowyResult<()> { - let editor = self.get_editor_from_row_id(&row_rev.id).await?; - editor.move_row(&row_rev.id, from, to).await?; - - let delete_row_id = row_rev.id.clone(); - let insert_row = InsertedRowPB { - index: Some(to as i32), - row: make_row_from_row_rev(row_rev), - is_new: false, + pub async fn update_row(&self, changeset: RowChangeset) -> FlowyResult<()> { + let editor = self.get_editor_from_row_id(&changeset.row_id).await?; + editor.update_row(changeset.clone()).await?; + match editor.get_row_rev(&changeset.row_id).await? { + None => tracing::error!( + "Update row failed, can't find the row with id: {}", + changeset.row_id + ), + Some((_, row_rev)) => { + let changed_field_ids = changeset + .cell_by_field_id + .keys() + .cloned() + .collect::>(); + let row = UpdatedRowPB { + row: make_row_from_row_rev(row_rev), + field_ids: changed_field_ids, }; - let _ = self.event_notifier.send(DatabaseBlockEvent::Move { - block_id: editor.block_id.clone(), - deleted_row_id: delete_row_id, - inserted_row: insert_row, + let _ = self.event_notifier.send(DatabaseBlockEvent::UpdateRow { + block_id: editor.block_id.clone(), + row, + }); + }, + } + Ok(()) + } + + #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn delete_row(&self, row_id: &str) -> FlowyResult>> { + let row_id = row_id.to_owned(); + let block_id = self.persistence.get_block_id(&row_id)?; + let editor = self.get_block_editor(&block_id).await?; + match editor.get_row_rev(&row_id).await? { + None => Ok(None), + Some((_, row_rev)) => { + let _ = editor.delete_rows(vec![Cow::Borrowed(&row_id)]).await?; + let _ = self.event_notifier.send(DatabaseBlockEvent::DeleteRow { + block_id: editor.block_id.clone(), + row_id: row_rev.id.clone(), }); - Ok(()) + Ok(Some(row_rev)) + }, + } + } + + pub(crate) async fn delete_rows( + &self, + block_rows: Vec, + ) -> FlowyResult> { + let mut changesets = vec![]; + for block_row in block_rows { + let editor = self.get_block_editor(&block_row.block_id).await?; + let row_ids = block_row + .row_ids + .into_iter() + .map(Cow::Owned) + .collect::>>(); + let row_count = editor.delete_rows(row_ids).await?; + let changeset = GridBlockMetaRevisionChangeset::from_row_count(block_row.block_id, row_count); + changesets.push(changeset); } - // This function will be moved to GridViewRevisionEditor. - pub async fn index_of_row(&self, row_id: &str) -> Option { - match self.get_editor_from_row_id(row_id).await { - Ok(editor) => editor.index_of_row(row_id).await, - Err(_) => None, - } - } + Ok(changesets) + } + // This function will be moved to GridViewRevisionEditor + pub(crate) async fn move_row( + &self, + row_rev: Arc, + from: usize, + to: usize, + ) -> FlowyResult<()> { + let editor = self.get_editor_from_row_id(&row_rev.id).await?; + editor.move_row(&row_rev.id, from, to).await?; - pub async fn update_cell(&self, changeset: CellChangesetPB) -> FlowyResult<()> { - let row_changeset: RowChangeset = changeset.clone().into(); - self.update_row(row_changeset).await?; - self.notify_did_update_cell(changeset).await?; - Ok(()) - } + let delete_row_id = row_rev.id.clone(); + let insert_row = InsertedRowPB { + index: Some(to as i32), + row: make_row_from_row_rev(row_rev), + is_new: false, + }; - pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult)>> { - let editor = self.get_editor_from_row_id(row_id).await?; - editor.get_row_rev(row_id).await - } + let _ = self.event_notifier.send(DatabaseBlockEvent::Move { + block_id: editor.block_id.clone(), + deleted_row_id: delete_row_id, + inserted_row: insert_row, + }); - #[allow(dead_code)] - pub async fn get_row_revs(&self) -> FlowyResult>> { - let mut row_revs = vec![]; + Ok(()) + } + + // This function will be moved to GridViewRevisionEditor. + pub async fn index_of_row(&self, row_id: &str) -> Option { + match self.get_editor_from_row_id(row_id).await { + Ok(editor) => editor.index_of_row(row_id).await, + Err(_) => None, + } + } + + pub async fn update_cell(&self, changeset: CellChangesetPB) -> FlowyResult<()> { + let row_changeset: RowChangeset = changeset.clone().into(); + self.update_row(row_changeset).await?; + self.notify_did_update_cell(changeset).await?; + Ok(()) + } + + pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult)>> { + let editor = self.get_editor_from_row_id(row_id).await?; + editor.get_row_rev(row_id).await + } + + #[allow(dead_code)] + pub async fn get_row_revs(&self) -> FlowyResult>> { + let mut row_revs = vec![]; + for iter in self.block_editors.iter() { + let editor = iter.value(); + row_revs.extend(editor.get_row_revs::<&str>(None).await?); + } + Ok(row_revs) + } + + pub(crate) async fn get_blocks( + &self, + block_ids: Option>, + ) -> FlowyResult> { + let mut blocks = vec![]; + match block_ids { + None => { for iter in self.block_editors.iter() { - let editor = iter.value(); - row_revs.extend(editor.get_row_revs::<&str>(None).await?); + let editor = iter.value(); + let block_id = editor.block_id.clone(); + let row_revs = editor.get_row_revs::<&str>(None).await?; + blocks.push(DatabaseBlockRowRevision { block_id, row_revs }); } - Ok(row_revs) - } - - pub(crate) async fn get_blocks( - &self, - block_ids: Option>, - ) -> FlowyResult> { - let mut blocks = vec![]; - match block_ids { - None => { - for iter in self.block_editors.iter() { - let editor = iter.value(); - let block_id = editor.block_id.clone(); - let row_revs = editor.get_row_revs::<&str>(None).await?; - blocks.push(DatabaseBlockRowRevision { block_id, row_revs }); - } - } - Some(block_ids) => { - for block_id in block_ids { - let editor = self.get_block_editor(&block_id).await?; - let row_revs = editor.get_row_revs::<&str>(None).await?; - blocks.push(DatabaseBlockRowRevision { block_id, row_revs }); - } - } + }, + Some(block_ids) => { + for block_id in block_ids { + let editor = self.get_block_editor(&block_id).await?; + let row_revs = editor.get_row_revs::<&str>(None).await?; + blocks.push(DatabaseBlockRowRevision { block_id, row_revs }); } - Ok(blocks) + }, } + Ok(blocks) + } - async fn notify_did_update_cell(&self, changeset: CellChangesetPB) -> FlowyResult<()> { - let id = format!("{}:{}", changeset.row_id, changeset.field_id); - send_notification(&id, DatabaseNotification::DidUpdateCell).send(); - Ok(()) - } + async fn notify_did_update_cell(&self, changeset: CellChangesetPB) -> FlowyResult<()> { + let id = format!("{}:{}", changeset.row_id, changeset.field_id); + send_notification(&id, DatabaseNotification::DidUpdateCell).send(); + Ok(()) + } } /// Initialize each block editor async fn make_block_editors( - user: &Arc, - block_meta_revs: Vec>, + user: &Arc, + block_meta_revs: Vec>, ) -> FlowyResult>> { - let editor_map = DashMap::new(); - for block_meta_rev in block_meta_revs { - let editor = make_database_block_editor(user, &block_meta_rev.block_id).await?; - editor_map.insert(block_meta_rev.block_id.clone(), Arc::new(editor)); - } + let editor_map = DashMap::new(); + for block_meta_rev in block_meta_revs { + let editor = make_database_block_editor(user, &block_meta_rev.block_id).await?; + editor_map.insert(block_meta_rev.block_id.clone(), Arc::new(editor)); + } - Ok(editor_map) + Ok(editor_map) } async fn make_database_block_editor( - user: &Arc, - block_id: &str, + user: &Arc, + block_id: &str, ) -> FlowyResult { - tracing::trace!("Open block:{} editor", block_id); - let token = user.token()?; - let user_id = user.user_id()?; - let rev_manager = make_database_block_rev_manager(user, block_id)?; - DatabaseBlockRevisionEditor::new(&user_id, &token, block_id, rev_manager).await + tracing::trace!("Open block:{} editor", block_id); + let token = user.token()?; + let user_id = user.user_id()?; + let rev_manager = make_database_block_rev_manager(user, block_id)?; + DatabaseBlockRevisionEditor::new(&user_id, &token, block_id, rev_manager).await } pub fn make_database_block_rev_manager( - user: &Arc, - block_id: &str, + user: &Arc, + block_id: &str, ) -> FlowyResult>> { - let user_id = user.user_id()?; + let user_id = user.user_id()?; - // Create revision persistence - let pool = user.db_pool()?; - let disk_cache = SQLiteDatabaseBlockRevisionPersistence::new(&user_id, pool.clone()); - let configuration = RevisionPersistenceConfiguration::new(4, false); - let rev_persistence = RevisionPersistence::new(&user_id, block_id, disk_cache, configuration); + // Create revision persistence + let pool = user.db_pool()?; + let disk_cache = SQLiteDatabaseBlockRevisionPersistence::new(&user_id, pool.clone()); + let configuration = RevisionPersistenceConfiguration::new(4, false); + let rev_persistence = RevisionPersistence::new(&user_id, block_id, disk_cache, configuration); - // Create snapshot persistence - let snapshot_object_id = format!("grid_block:{}", block_id); - let snapshot_persistence = SQLiteDatabaseRevisionSnapshotPersistence::new(&snapshot_object_id, pool); + // Create snapshot persistence + let snapshot_object_id = format!("grid_block:{}", block_id); + let snapshot_persistence = + SQLiteDatabaseRevisionSnapshotPersistence::new(&snapshot_object_id, pool); - let rev_compress = GridBlockRevisionMergeable(); - let rev_manager = RevisionManager::new(&user_id, block_id, rev_persistence, rev_compress, snapshot_persistence); - Ok(rev_manager) + let rev_compress = GridBlockRevisionMergeable(); + let rev_manager = RevisionManager::new( + &user_id, + block_id, + rev_persistence, + rev_compress, + snapshot_persistence, + ); + Ok(rev_manager) } diff --git a/frontend/rust-lib/flowy-database/src/services/cell/cell_data_cache.rs b/frontend/rust-lib/flowy-database/src/services/cell/cell_data_cache.rs index 8f94d193dd..7fdf5755bd 100644 --- a/frontend/rust-lib/flowy-database/src/services/cell/cell_data_cache.rs +++ b/frontend/rust-lib/flowy-database/src/services/cell/cell_data_cache.rs @@ -16,90 +16,97 @@ pub struct AnyTypeCache(HashMap); impl AnyTypeCache where - TypeValueKey: Clone + Hash + Eq, + TypeValueKey: Clone + Hash + Eq, { - pub fn new() -> Arc>> { - Arc::new(RwLock::new(AnyTypeCache(HashMap::default()))) - } + pub fn new() -> Arc>> { + Arc::new(RwLock::new(AnyTypeCache(HashMap::default()))) + } - pub fn insert(&mut self, key: &TypeValueKey, val: T) -> Option - where - T: 'static + Send + Sync, - { - self.0.insert(key.clone(), TypeValue::new(val)).and_then(downcast_owned) - } + pub fn insert(&mut self, key: &TypeValueKey, val: T) -> Option + where + T: 'static + Send + Sync, + { + self + .0 + .insert(key.clone(), TypeValue::new(val)) + .and_then(downcast_owned) + } - pub fn remove(&mut self, key: &TypeValueKey) { - self.0.remove(key); - } + pub fn remove(&mut self, key: &TypeValueKey) { + self.0.remove(key); + } - // pub fn remove>(&mut self, key: K) -> Option - // where - // T: 'static + Send + Sync, - // { - // self.0.remove(key.as_ref()).and_then(downcast_owned) - // } + // pub fn remove>(&mut self, key: K) -> Option + // where + // T: 'static + Send + Sync, + // { + // self.0.remove(key.as_ref()).and_then(downcast_owned) + // } - pub fn get(&self, key: &TypeValueKey) -> Option<&T> - where - T: 'static + Send + Sync, - { - self.0.get(key).and_then(|type_value| type_value.boxed.downcast_ref()) - } + pub fn get(&self, key: &TypeValueKey) -> Option<&T> + where + T: 'static + Send + Sync, + { + self + .0 + .get(key) + .and_then(|type_value| type_value.boxed.downcast_ref()) + } - pub fn get_mut(&mut self, key: &TypeValueKey) -> Option<&mut T> - where - T: 'static + Send + Sync, - { - self.0 - .get_mut(key) - .and_then(|type_value| type_value.boxed.downcast_mut()) - } + pub fn get_mut(&mut self, key: &TypeValueKey) -> Option<&mut T> + where + T: 'static + Send + Sync, + { + self + .0 + .get_mut(key) + .and_then(|type_value| type_value.boxed.downcast_mut()) + } - pub fn contains(&self, key: &TypeValueKey) -> bool { - self.0.contains_key(key) - } + pub fn contains(&self, key: &TypeValueKey) -> bool { + self.0.contains_key(key) + } - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } } fn downcast_owned(type_value: TypeValue) -> Option { - type_value.boxed.downcast().ok().map(|boxed| *boxed) + type_value.boxed.downcast().ok().map(|boxed| *boxed) } #[derive(Debug)] struct TypeValue { - boxed: Box, - #[allow(dead_code)] - ty: &'static str, + boxed: Box, + #[allow(dead_code)] + ty: &'static str, } impl TypeValue { - pub fn new(value: T) -> Self - where - T: Send + Sync + 'static, - { - Self { - boxed: Box::new(value), - ty: type_name::(), - } + pub fn new(value: T) -> Self + where + T: Send + Sync + 'static, + { + Self { + boxed: Box::new(value), + ty: type_name::(), } + } } impl std::ops::Deref for TypeValue { - type Target = Box; + type Target = Box; - fn deref(&self) -> &Self::Target { - &self.boxed - } + fn deref(&self) -> &Self::Target { + &self.boxed + } } impl std::ops::DerefMut for TypeValue { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.boxed - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.boxed + } } // #[cfg(test)] diff --git a/frontend/rust-lib/flowy-database/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-database/src/services/cell/cell_operation.rs index 1dad9ed3c8..156022153b 100644 --- a/frontend/rust-lib/flowy-database/src/services/cell/cell_operation.rs +++ b/frontend/rust-lib/flowy-database/src/services/cell/cell_operation.rs @@ -9,41 +9,41 @@ use std::fmt::Debug; /// Decode the opaque cell data into readable format content pub trait CellDataDecoder: TypeOption { - /// - /// Tries to decode the opaque cell string to `decoded_field_type`'s cell data. Sometimes, the `field_type` - /// of the `FieldRevision` is not equal to the `decoded_field_type`(This happened When switching - /// the field type of the `FieldRevision` to another field type). So the cell data is need to do - /// some transformation. - /// - /// For example, the current field type of the `FieldRevision` is a checkbox. When switching the field - /// type from the checkbox to single select, it will create two new options,`Yes` and `No`, if they don't exist. - /// But the data of the cell doesn't change. We can't iterate all the rows to transform the cell - /// data that can be parsed by the current field type. One approach is to transform the cell data - /// when it get read. For the moment, the cell data is a string, `Yes` or `No`. It needs to compare - /// with the option's name, if match return the id of the option. - fn decode_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> FlowyResult<::CellData>; + /// + /// Tries to decode the opaque cell string to `decoded_field_type`'s cell data. Sometimes, the `field_type` + /// of the `FieldRevision` is not equal to the `decoded_field_type`(This happened When switching + /// the field type of the `FieldRevision` to another field type). So the cell data is need to do + /// some transformation. + /// + /// For example, the current field type of the `FieldRevision` is a checkbox. When switching the field + /// type from the checkbox to single select, it will create two new options,`Yes` and `No`, if they don't exist. + /// But the data of the cell doesn't change. We can't iterate all the rows to transform the cell + /// data that can be parsed by the current field type. One approach is to transform the cell data + /// when it get read. For the moment, the cell data is a string, `Yes` or `No`. It needs to compare + /// with the option's name, if match return the id of the option. + fn decode_cell_str( + &self, + cell_str: String, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> FlowyResult<::CellData>; - /// Same as `decode_cell_data` does but Decode the cell data to readable `String` - /// For example, The string of the Multi-Select cell will be a list of the option's name - /// separated by a comma. - fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String; + /// Same as `decode_cell_data` does but Decode the cell data to readable `String` + /// For example, The string of the Multi-Select cell will be a list of the option's name + /// separated by a comma. + fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String; } pub trait CellDataChangeset: TypeOption { - /// The changeset is able to parse into the concrete data struct if `TypeOption::CellChangeset` - /// implements the `FromCellChangesetString` trait. - /// For example,the SelectOptionCellChangeset,DateCellChangeset. etc. - /// - fn apply_changeset( - &self, - changeset: ::CellChangeset, - type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)>; + /// The changeset is able to parse into the concrete data struct if `TypeOption::CellChangeset` + /// implements the `FromCellChangesetString` trait. + /// For example,the SelectOptionCellChangeset,DateCellChangeset. etc. + /// + fn apply_changeset( + &self, + changeset: ::CellChangeset, + type_cell_data: Option, + ) -> FlowyResult<(String, ::CellData)>; } /// changeset: It will be deserialized into specific data base on the FieldType. @@ -53,78 +53,90 @@ pub trait CellDataChangeset: TypeOption { /// /// cell_rev: It will be None if the cell does not contain any data. pub fn apply_cell_data_changeset>( - changeset: C, - cell_rev: Option, - field_rev: T, - cell_data_cache: Option, + changeset: C, + cell_rev: Option, + field_rev: T, + cell_data_cache: Option, ) -> Result { - let field_rev = field_rev.as_ref(); - let changeset = changeset.to_cell_changeset_str(); - let field_type: FieldType = field_rev.ty.into(); + let field_rev = field_rev.as_ref(); + let changeset = changeset.to_cell_changeset_str(); + let field_type: FieldType = field_rev.ty.into(); - let type_cell_data = cell_rev.and_then(|cell_rev| match TypeCellData::try_from(cell_rev) { - Ok(type_cell_data) => Some(type_cell_data), - Err(_) => None, - }); + let type_cell_data = cell_rev.and_then(|cell_rev| match TypeCellData::try_from(cell_rev) { + Ok(type_cell_data) => Some(type_cell_data), + Err(_) => None, + }); - let cell_str = match TypeOptionCellExt::new_with_cell_data_cache(field_rev, cell_data_cache) - .get_type_option_cell_data_handler(&field_type) - { - None => "".to_string(), - Some(handler) => handler.handle_cell_changeset(changeset, type_cell_data, field_rev)?, - }; - Ok(TypeCellData::new(cell_str, field_type).to_json()) + let cell_str = match TypeOptionCellExt::new_with_cell_data_cache(field_rev, cell_data_cache) + .get_type_option_cell_data_handler(&field_type) + { + None => "".to_string(), + Some(handler) => handler.handle_cell_changeset(changeset, type_cell_data, field_rev)?, + }; + Ok(TypeCellData::new(cell_str, field_type).to_json()) } pub fn get_type_cell_protobuf + Debug>( - data: T, - field_rev: &FieldRevision, - cell_data_cache: Option, + data: T, + field_rev: &FieldRevision, + cell_data_cache: Option, ) -> (FieldType, CellProtobufBlob) { - let to_field_type = field_rev.ty.into(); - match data.try_into() { - Ok(type_cell_data) => { - let TypeCellData { cell_str, field_type } = type_cell_data; - match try_decode_cell_str_to_cell_protobuf( - cell_str, - &field_type, - &to_field_type, - field_rev, - cell_data_cache, - ) { - Ok(cell_bytes) => (field_type, cell_bytes), - Err(e) => { - tracing::error!("Decode cell data failed, {:?}", e); - (field_type, CellProtobufBlob::default()) - } - } - } - Err(_err) => { - // It's okay to ignore this error, because it's okay that the current cell can't - // display the existing cell data. For example, the UI of the text cell will be blank if - // the type of the data of cell is Number. - (to_field_type, CellProtobufBlob::default()) - } - } + let to_field_type = field_rev.ty.into(); + match data.try_into() { + Ok(type_cell_data) => { + let TypeCellData { + cell_str, + field_type, + } = type_cell_data; + match try_decode_cell_str_to_cell_protobuf( + cell_str, + &field_type, + &to_field_type, + field_rev, + cell_data_cache, + ) { + Ok(cell_bytes) => (field_type, cell_bytes), + Err(e) => { + tracing::error!("Decode cell data failed, {:?}", e); + (field_type, CellProtobufBlob::default()) + }, + } + }, + Err(_err) => { + // It's okay to ignore this error, because it's okay that the current cell can't + // display the existing cell data. For example, the UI of the text cell will be blank if + // the type of the data of cell is Number. + (to_field_type, CellProtobufBlob::default()) + }, + } } pub fn get_type_cell_data( - data: CellData, - field_rev: &FieldRevision, - cell_data_cache: Option, + data: CellData, + field_rev: &FieldRevision, + cell_data_cache: Option, ) -> Option where - CellData: TryInto + Debug, - Output: Default + 'static, + CellData: TryInto + Debug, + Output: Default + 'static, { - let to_field_type = field_rev.ty.into(); - match data.try_into() { - Ok(type_cell_data) => { - let TypeCellData { cell_str, field_type } = type_cell_data; - try_decode_cell_str_to_cell_data(cell_str, &field_type, &to_field_type, field_rev, cell_data_cache) - } - Err(_err) => None, - } + let to_field_type = field_rev.ty.into(); + match data.try_into() { + Ok(type_cell_data) => { + let TypeCellData { + cell_str, + field_type, + } = type_cell_data; + try_decode_cell_str_to_cell_data( + cell_str, + &field_type, + &to_field_type, + field_rev, + cell_data_cache, + ) + }, + Err(_err) => None, + } } /// Decode the opaque cell data from one field type to another using the corresponding `TypeOption` @@ -145,33 +157,33 @@ where /// returns: CellBytes /// pub fn try_decode_cell_str_to_cell_protobuf( - cell_str: String, - from_field_type: &FieldType, - to_field_type: &FieldType, - field_rev: &FieldRevision, - cell_data_cache: Option, + cell_str: String, + from_field_type: &FieldType, + to_field_type: &FieldType, + field_rev: &FieldRevision, + cell_data_cache: Option, ) -> FlowyResult { - match TypeOptionCellExt::new_with_cell_data_cache(field_rev, cell_data_cache) - .get_type_option_cell_data_handler(to_field_type) - { - None => Ok(CellProtobufBlob::default()), - Some(handler) => handler.handle_cell_str(cell_str, from_field_type, field_rev), - } + match TypeOptionCellExt::new_with_cell_data_cache(field_rev, cell_data_cache) + .get_type_option_cell_data_handler(to_field_type) + { + None => Ok(CellProtobufBlob::default()), + Some(handler) => handler.handle_cell_str(cell_str, from_field_type, field_rev), + } } pub fn try_decode_cell_str_to_cell_data( - cell_str: String, - from_field_type: &FieldType, - to_field_type: &FieldType, - field_rev: &FieldRevision, - cell_data_cache: Option, + cell_str: String, + from_field_type: &FieldType, + to_field_type: &FieldType, + field_rev: &FieldRevision, + cell_data_cache: Option, ) -> Option { - let handler = TypeOptionCellExt::new_with_cell_data_cache(field_rev, cell_data_cache) - .get_type_option_cell_data_handler(to_field_type)?; - handler - .get_cell_data(cell_str, from_field_type, field_rev) - .ok()? - .unbox_or_none::() + let handler = TypeOptionCellExt::new_with_cell_data_cache(field_rev, cell_data_cache) + .get_type_option_cell_data_handler(to_field_type)?; + handler + .get_cell_data(cell_str, from_field_type, field_rev) + .ok()? + .unbox_or_none::() } /// Returns a string that represents the current field_type's cell data. /// For example, The string of the Multi-Select cell will be a list of the option's name @@ -187,123 +199,133 @@ pub fn try_decode_cell_str_to_cell_data( /// /// returns: String pub fn stringify_cell_data( - cell_str: String, - decoded_field_type: &FieldType, - field_type: &FieldType, - field_rev: &FieldRevision, + cell_str: String, + decoded_field_type: &FieldType, + field_type: &FieldType, + field_rev: &FieldRevision, ) -> String { - match TypeOptionCellExt::new_with_cell_data_cache(field_rev, None).get_type_option_cell_data_handler(field_type) { - None => "".to_string(), - Some(handler) => handler.stringify_cell_str(cell_str, decoded_field_type, field_rev), - } + match TypeOptionCellExt::new_with_cell_data_cache(field_rev, None) + .get_type_option_cell_data_handler(field_type) + { + None => "".to_string(), + Some(handler) => handler.stringify_cell_str(cell_str, decoded_field_type, field_rev), + } } pub fn insert_text_cell(s: String, field_rev: &FieldRevision) -> CellRevision { - let data = apply_cell_data_changeset(s, None, field_rev, None).unwrap(); - CellRevision::new(data) + let data = apply_cell_data_changeset(s, None, field_rev, None).unwrap(); + CellRevision::new(data) } pub fn insert_number_cell(num: i64, field_rev: &FieldRevision) -> CellRevision { - let data = apply_cell_data_changeset(num.to_string(), None, field_rev, None).unwrap(); - CellRevision::new(data) + let data = apply_cell_data_changeset(num.to_string(), None, field_rev, None).unwrap(); + CellRevision::new(data) } pub fn insert_url_cell(url: String, field_rev: &FieldRevision) -> CellRevision { - let data = apply_cell_data_changeset(url, None, field_rev, None).unwrap(); - CellRevision::new(data) + let data = apply_cell_data_changeset(url, None, field_rev, None).unwrap(); + CellRevision::new(data) } pub fn insert_checkbox_cell(is_check: bool, field_rev: &FieldRevision) -> CellRevision { - let s = if is_check { - CHECK.to_string() - } else { - UNCHECK.to_string() - }; - let data = apply_cell_data_changeset(s, None, field_rev, None).unwrap(); - CellRevision::new(data) + let s = if is_check { + CHECK.to_string() + } else { + UNCHECK.to_string() + }; + let data = apply_cell_data_changeset(s, None, field_rev, None).unwrap(); + CellRevision::new(data) } pub fn insert_date_cell(timestamp: i64, field_rev: &FieldRevision) -> CellRevision { - let cell_data = serde_json::to_string(&DateCellChangeset { - date: Some(timestamp.to_string()), - time: None, - is_utc: true, - }) - .unwrap(); - let data = apply_cell_data_changeset(cell_data, None, field_rev, None).unwrap(); - CellRevision::new(data) + let cell_data = serde_json::to_string(&DateCellChangeset { + date: Some(timestamp.to_string()), + time: None, + is_utc: true, + }) + .unwrap(); + let data = apply_cell_data_changeset(cell_data, None, field_rev, None).unwrap(); + CellRevision::new(data) } -pub fn insert_select_option_cell(option_ids: Vec, field_rev: &FieldRevision) -> CellRevision { - let changeset = SelectOptionCellChangeset::from_insert_options(option_ids).to_cell_changeset_str(); - let data = apply_cell_data_changeset(changeset, None, field_rev, None).unwrap(); - CellRevision::new(data) +pub fn insert_select_option_cell( + option_ids: Vec, + field_rev: &FieldRevision, +) -> CellRevision { + let changeset = + SelectOptionCellChangeset::from_insert_options(option_ids).to_cell_changeset_str(); + let data = apply_cell_data_changeset(changeset, None, field_rev, None).unwrap(); + CellRevision::new(data) } -pub fn delete_select_option_cell(option_ids: Vec, field_rev: &FieldRevision) -> CellRevision { - let changeset = SelectOptionCellChangeset::from_delete_options(option_ids).to_cell_changeset_str(); - let data = apply_cell_data_changeset(changeset, None, field_rev, None).unwrap(); - CellRevision::new(data) +pub fn delete_select_option_cell( + option_ids: Vec, + field_rev: &FieldRevision, +) -> CellRevision { + let changeset = + SelectOptionCellChangeset::from_delete_options(option_ids).to_cell_changeset_str(); + let data = apply_cell_data_changeset(changeset, None, field_rev, None).unwrap(); + CellRevision::new(data) } /// Deserialize the String into cell specific data type. pub trait FromCellString { - fn from_cell_str(s: &str) -> FlowyResult - where - Self: Sized; + fn from_cell_str(s: &str) -> FlowyResult + where + Self: Sized; } /// If the changeset applying to the cell is not String type, it should impl this trait. /// Deserialize the string into cell specific changeset. pub trait FromCellChangesetString { - fn from_changeset(changeset: String) -> FlowyResult - where - Self: Sized; + fn from_changeset(changeset: String) -> FlowyResult + where + Self: Sized; } impl FromCellChangesetString for String { - fn from_changeset(changeset: String) -> FlowyResult - where - Self: Sized, - { - Ok(changeset) - } + fn from_changeset(changeset: String) -> FlowyResult + where + Self: Sized, + { + Ok(changeset) + } } pub trait ToCellChangesetString: Debug { - fn to_cell_changeset_str(&self) -> String; + fn to_cell_changeset_str(&self) -> String; } impl ToCellChangesetString for String { - fn to_cell_changeset_str(&self) -> String { - self.clone() - } + fn to_cell_changeset_str(&self) -> String { + self.clone() + } } pub struct AnyCellChangeset(pub Option); impl AnyCellChangeset { - pub fn try_into_inner(self) -> FlowyResult { - match self.0 { - None => Err(ErrorCode::InvalidData.into()), - Some(data) => Ok(data), - } + pub fn try_into_inner(self) -> FlowyResult { + match self.0 { + None => Err(ErrorCode::InvalidData.into()), + Some(data) => Ok(data), } + } } impl std::convert::From for AnyCellChangeset where - T: FromCellChangesetString, + T: FromCellChangesetString, { - fn from(changeset: C) -> Self { - match T::from_changeset(changeset.to_string()) { - Ok(data) => AnyCellChangeset(Some(data)), - Err(e) => { - tracing::error!("Deserialize CellDataChangeset failed: {}", e); - AnyCellChangeset(None) - } - } + fn from(changeset: C) -> Self { + match T::from_changeset(changeset.to_string()) { + Ok(data) => AnyCellChangeset(Some(data)), + Err(e) => { + tracing::error!("Deserialize CellDataChangeset failed: {}", e); + AnyCellChangeset(None) + }, } + } } // impl std::convert::From for AnyCellChangeset { // fn from(s: String) -> Self { diff --git a/frontend/rust-lib/flowy-database/src/services/cell/type_cell_data.rs b/frontend/rust-lib/flowy-database/src/services/cell/type_cell_data.rs index db48da229a..259b10f48c 100644 --- a/frontend/rust-lib/flowy-database/src/services/cell/type_cell_data.rs +++ b/frontend/rust-lib/flowy-database/src/services/cell/type_cell_data.rs @@ -16,106 +16,109 @@ use serde::{Deserialize, Serialize}; /// #[derive(Debug, Serialize, Deserialize)] pub struct TypeCellData { - #[serde(rename = "data")] - pub cell_str: String, - pub field_type: FieldType, + #[serde(rename = "data")] + pub cell_str: String, + pub field_type: FieldType, } impl TypeCellData { - pub fn from_field_type(field_type: &FieldType) -> TypeCellData { - Self { - cell_str: "".to_string(), - field_type: field_type.clone(), - } + pub fn from_field_type(field_type: &FieldType) -> TypeCellData { + Self { + cell_str: "".to_string(), + field_type: field_type.clone(), } + } - pub fn from_json_str(s: &str) -> FlowyResult { - let type_cell_data: TypeCellData = serde_json::from_str(s).map_err(|err| { - let msg = format!("Deserialize {} to type cell data failed.{}", s, err); - FlowyError::internal().context(msg) - })?; - Ok(type_cell_data) - } + pub fn from_json_str(s: &str) -> FlowyResult { + let type_cell_data: TypeCellData = serde_json::from_str(s).map_err(|err| { + let msg = format!("Deserialize {} to type cell data failed.{}", s, err); + FlowyError::internal().context(msg) + })?; + Ok(type_cell_data) + } - pub fn into_inner(self) -> String { - self.cell_str - } + pub fn into_inner(self) -> String { + self.cell_str + } } impl std::convert::TryFrom for TypeCellData { - type Error = FlowyError; + type Error = FlowyError; - fn try_from(value: String) -> Result { - TypeCellData::from_json_str(&value) - } + fn try_from(value: String) -> Result { + TypeCellData::from_json_str(&value) + } } impl ToString for TypeCellData { - fn to_string(&self) -> String { - self.cell_str.clone() - } + fn to_string(&self) -> String { + self.cell_str.clone() + } } impl std::convert::TryFrom<&CellRevision> for TypeCellData { - type Error = FlowyError; + type Error = FlowyError; - fn try_from(value: &CellRevision) -> Result { - Self::from_json_str(&value.type_cell_data) - } + fn try_from(value: &CellRevision) -> Result { + Self::from_json_str(&value.type_cell_data) + } } impl std::convert::TryFrom for TypeCellData { - type Error = FlowyError; + type Error = FlowyError; - fn try_from(value: CellRevision) -> Result { - Self::try_from(&value) - } + fn try_from(value: CellRevision) -> Result { + Self::try_from(&value) + } } impl TypeCellData { - pub fn new(cell_str: String, field_type: FieldType) -> Self { - TypeCellData { cell_str, field_type } + pub fn new(cell_str: String, field_type: FieldType) -> Self { + TypeCellData { + cell_str, + field_type, } + } - pub fn to_json(&self) -> String { - serde_json::to_string(self).unwrap_or_else(|_| "".to_owned()) - } + pub fn to_json(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|_| "".to_owned()) + } - pub fn is_number(&self) -> bool { - self.field_type == FieldType::Number - } + pub fn is_number(&self) -> bool { + self.field_type == FieldType::Number + } - pub fn is_text(&self) -> bool { - self.field_type == FieldType::RichText - } + pub fn is_text(&self) -> bool { + self.field_type == FieldType::RichText + } - pub fn is_checkbox(&self) -> bool { - self.field_type == FieldType::Checkbox - } + pub fn is_checkbox(&self) -> bool { + self.field_type == FieldType::Checkbox + } - pub fn is_date(&self) -> bool { - self.field_type == FieldType::DateTime - } + pub fn is_date(&self) -> bool { + self.field_type == FieldType::DateTime + } - pub fn is_single_select(&self) -> bool { - self.field_type == FieldType::SingleSelect - } + pub fn is_single_select(&self) -> bool { + self.field_type == FieldType::SingleSelect + } - pub fn is_multi_select(&self) -> bool { - self.field_type == FieldType::MultiSelect - } + pub fn is_multi_select(&self) -> bool { + self.field_type == FieldType::MultiSelect + } - pub fn is_checklist(&self) -> bool { - self.field_type == FieldType::Checklist - } + pub fn is_checklist(&self) -> bool { + self.field_type == FieldType::Checklist + } - pub fn is_url(&self) -> bool { - self.field_type == FieldType::URL - } + pub fn is_url(&self) -> bool { + self.field_type == FieldType::URL + } - pub fn is_select_option(&self) -> bool { - self.field_type == FieldType::MultiSelect || self.field_type == FieldType::SingleSelect - } + pub fn is_select_option(&self) -> bool { + self.field_type == FieldType::MultiSelect || self.field_type == FieldType::SingleSelect + } } /// The data is encoded by protobuf or utf8. You should choose the corresponding decode struct to parse it. @@ -130,77 +133,77 @@ impl TypeCellData { pub struct CellProtobufBlob(pub Bytes); pub trait DecodedCellData { - type Object; - fn is_empty(&self) -> bool; + type Object; + fn is_empty(&self) -> bool; } pub trait CellProtobufBlobParser { - type Object: DecodedCellData; - fn parser(bytes: &Bytes) -> FlowyResult; + type Object: DecodedCellData; + fn parser(bytes: &Bytes) -> FlowyResult; } pub trait CellStringParser { - type Object; - fn parser_cell_str(&self, s: &str) -> Option; + type Object; + fn parser_cell_str(&self, s: &str) -> Option; } pub trait CellBytesCustomParser { - type Object; - fn parse(&self, bytes: &Bytes) -> FlowyResult; + type Object; + fn parse(&self, bytes: &Bytes) -> FlowyResult; } impl CellProtobufBlob { - pub fn new>(data: T) -> Self { - let bytes = Bytes::from(data.as_ref().to_vec()); - Self(bytes) - } + pub fn new>(data: T) -> Self { + let bytes = Bytes::from(data.as_ref().to_vec()); + Self(bytes) + } - pub fn from>(bytes: T) -> FlowyResult - where - >::Error: std::fmt::Debug, - { - let bytes = bytes.try_into().map_err(internal_error)?; - Ok(Self(bytes)) - } + pub fn from>(bytes: T) -> FlowyResult + where + >::Error: std::fmt::Debug, + { + let bytes = bytes.try_into().map_err(internal_error)?; + Ok(Self(bytes)) + } - pub fn parser

(&self) -> FlowyResult - where - P: CellProtobufBlobParser, - { - P::parser(&self.0) - } + pub fn parser

(&self) -> FlowyResult + where + P: CellProtobufBlobParser, + { + P::parser(&self.0) + } - pub fn custom_parser

(&self, parser: P) -> FlowyResult - where - P: CellBytesCustomParser, - { - parser.parse(&self.0) - } + pub fn custom_parser

(&self, parser: P) -> FlowyResult + where + P: CellBytesCustomParser, + { + parser.parse(&self.0) + } - // pub fn parse<'a, T: TryFrom<&'a [u8]>>(&'a self) -> FlowyResult - // where - // >::Error: std::fmt::Debug, - // { - // T::try_from(self.0.as_ref()).map_err(internal_error) - // } + // pub fn parse<'a, T: TryFrom<&'a [u8]>>(&'a self) -> FlowyResult + // where + // >::Error: std::fmt::Debug, + // { + // T::try_from(self.0.as_ref()).map_err(internal_error) + // } } impl ToString for CellProtobufBlob { - fn to_string(&self) -> String { - match String::from_utf8(self.0.to_vec()) { - Ok(s) => s, - Err(e) => { - tracing::error!("DecodedCellData to string failed: {:?}", e); - "".to_string() - } - } + fn to_string(&self) -> String { + match String::from_utf8(self.0.to_vec()) { + Ok(s) => s, + Err(e) => { + tracing::error!("DecodedCellData to string failed: {:?}", e); + "".to_string() + }, } + } } impl std::ops::Deref for CellProtobufBlob { - type Target = Bytes; + type Target = Bytes; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/field_builder.rs b/frontend/rust-lib/flowy-database/src/services/field/field_builder.rs index c32df6cdc9..dfa0f57d1c 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/field_builder.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/field_builder.rs @@ -6,80 +6,80 @@ use grid_model::FieldRevision; use indexmap::IndexMap; pub struct FieldBuilder { - field_rev: FieldRevision, - type_option_builder: Box, + field_rev: FieldRevision, + type_option_builder: Box, } pub type BoxTypeOptionBuilder = Box; impl FieldBuilder { - pub fn new>(type_option_builder: T) -> Self { - let type_option_builder = type_option_builder.into(); - let field_type = type_option_builder.field_type(); - let width = field_type.default_cell_width(); - let field_rev = FieldRevision::new("", "", field_type, width, false); - Self { - field_rev, - type_option_builder, - } + pub fn new>(type_option_builder: T) -> Self { + let type_option_builder = type_option_builder.into(); + let field_type = type_option_builder.field_type(); + let width = field_type.default_cell_width(); + let field_rev = FieldRevision::new("", "", field_type, width, false); + Self { + field_rev, + type_option_builder, } + } - pub fn from_field_type(field_type: &FieldType) -> Self { - let type_option_builder = default_type_option_builder_from_type(field_type); - Self::new(type_option_builder) - } + pub fn from_field_type(field_type: &FieldType) -> Self { + let type_option_builder = default_type_option_builder_from_type(field_type); + Self::new(type_option_builder) + } - pub fn from_field(field: FieldPB, type_option_builder: Box) -> Self { - let field_rev = FieldRevision { - id: field.id, - name: field.name, - desc: field.desc, - ty: field.field_type.into(), - frozen: field.frozen, - visibility: field.visibility, - width: field.width, - type_options: IndexMap::default(), - is_primary: field.is_primary, - }; - Self { - field_rev, - type_option_builder, - } + pub fn from_field(field: FieldPB, type_option_builder: Box) -> Self { + let field_rev = FieldRevision { + id: field.id, + name: field.name, + desc: field.desc, + ty: field.field_type.into(), + frozen: field.frozen, + visibility: field.visibility, + width: field.width, + type_options: IndexMap::default(), + is_primary: field.is_primary, + }; + Self { + field_rev, + type_option_builder, } + } - pub fn name(mut self, name: &str) -> Self { - self.field_rev.name = name.to_owned(); - self - } + pub fn name(mut self, name: &str) -> Self { + self.field_rev.name = name.to_owned(); + self + } - pub fn desc(mut self, desc: &str) -> Self { - self.field_rev.desc = desc.to_owned(); - self - } + pub fn desc(mut self, desc: &str) -> Self { + self.field_rev.desc = desc.to_owned(); + self + } - pub fn primary(mut self, is_primary: bool) -> Self { - self.field_rev.is_primary = is_primary; - self - } + pub fn primary(mut self, is_primary: bool) -> Self { + self.field_rev.is_primary = is_primary; + self + } - pub fn visibility(mut self, visibility: bool) -> Self { - self.field_rev.visibility = visibility; - self - } + pub fn visibility(mut self, visibility: bool) -> Self { + self.field_rev.visibility = visibility; + self + } - pub fn width(mut self, width: i32) -> Self { - self.field_rev.width = width; - self - } + pub fn width(mut self, width: i32) -> Self { + self.field_rev.width = width; + self + } - pub fn frozen(mut self, frozen: bool) -> Self { - self.field_rev.frozen = frozen; - self - } + pub fn frozen(mut self, frozen: bool) -> Self { + self.field_rev.frozen = frozen; + self + } - pub fn build(self) -> FieldRevision { - let mut field_rev = self.field_rev; - field_rev.insert_type_option(self.type_option_builder.serializer()); - field_rev - } + pub fn build(self) -> FieldRevision { + let mut field_rev = self.field_rev; + field_rev.insert_type_option(self.type_option_builder.serializer()); + field_rev + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/field_operation.rs b/frontend/rust-lib/flowy-database/src/services/field/field_operation.rs index 3001849a74..d8bc61f9fe 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/field_operation.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/field_operation.rs @@ -5,43 +5,43 @@ use grid_model::{TypeOptionDataDeserializer, TypeOptionDataSerializer}; use std::sync::Arc; pub async fn edit_field_type_option( - field_id: &str, - editor: Arc, - action: impl FnOnce(&mut T), + field_id: &str, + editor: Arc, + action: impl FnOnce(&mut T), ) -> FlowyResult<()> where - T: TypeOptionDataDeserializer + TypeOptionDataSerializer, + T: TypeOptionDataDeserializer + TypeOptionDataSerializer, { - let get_type_option = async { - let field_rev = editor.get_field_rev(field_id).await?; - field_rev.get_type_option::(field_rev.ty) - }; + let get_type_option = async { + let field_rev = editor.get_field_rev(field_id).await?; + field_rev.get_type_option::(field_rev.ty) + }; - if let Some(mut type_option) = get_type_option.await { - let old_field_rev = editor.get_field_rev(field_id).await; + if let Some(mut type_option) = get_type_option.await { + let old_field_rev = editor.get_field_rev(field_id).await; - action(&mut type_option); - let bytes = type_option.protobuf_bytes().to_vec(); - editor - .update_field_type_option(&editor.database_id, field_id, bytes, old_field_rev) - .await?; - } + action(&mut type_option); + let bytes = type_option.protobuf_bytes().to_vec(); + editor + .update_field_type_option(&editor.database_id, field_id, bytes, old_field_rev) + .await?; + } - Ok(()) + Ok(()) } pub async fn edit_single_select_type_option( - field_id: &str, - editor: Arc, - action: impl FnOnce(&mut SingleSelectTypeOptionPB), + field_id: &str, + editor: Arc, + action: impl FnOnce(&mut SingleSelectTypeOptionPB), ) -> FlowyResult<()> { - edit_field_type_option(field_id, editor, action).await + edit_field_type_option(field_id, editor, action).await } pub async fn edit_multi_select_type_option( - field_id: &str, - editor: Arc, - action: impl FnOnce(&mut MultiSelectTypeOptionPB), + field_id: &str, + editor: Arc, + action: impl FnOnce(&mut MultiSelectTypeOptionPB), ) -> FlowyResult<()> { - edit_field_type_option(field_id, editor, action).await + edit_field_type_option(field_id, editor, action).await } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_option_builder.rs b/frontend/rust-lib/flowy-database/src/services/field/type_option_builder.rs index f01209fe05..014824577f 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_option_builder.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_option_builder.rs @@ -4,51 +4,57 @@ use bytes::Bytes; use grid_model::TypeOptionDataSerializer; pub trait TypeOptionBuilder { - /// Returns the type of the type-option data - fn field_type(&self) -> FieldType; + /// Returns the type of the type-option data + fn field_type(&self) -> FieldType; - /// Returns a serializer that can be used to serialize the type-option data - fn serializer(&self) -> &dyn TypeOptionDataSerializer; + /// Returns a serializer that can be used to serialize the type-option data + fn serializer(&self) -> &dyn TypeOptionDataSerializer; } pub fn default_type_option_builder_from_type(field_type: &FieldType) -> Box { - let s: String = match field_type { - FieldType::RichText => RichTextTypeOptionPB::default().into(), - FieldType::Number => NumberTypeOptionPB::default().into(), - FieldType::DateTime => DateTypeOptionPB::default().into(), - FieldType::SingleSelect => SingleSelectTypeOptionPB::default().into(), - FieldType::MultiSelect => MultiSelectTypeOptionPB::default().into(), - FieldType::Checkbox => CheckboxTypeOptionPB::default().into(), - FieldType::URL => URLTypeOptionPB::default().into(), - FieldType::Checklist => ChecklistTypeOptionPB::default().into(), - }; + let s: String = match field_type { + FieldType::RichText => RichTextTypeOptionPB::default().into(), + FieldType::Number => NumberTypeOptionPB::default().into(), + FieldType::DateTime => DateTypeOptionPB::default().into(), + FieldType::SingleSelect => SingleSelectTypeOptionPB::default().into(), + FieldType::MultiSelect => MultiSelectTypeOptionPB::default().into(), + FieldType::Checkbox => CheckboxTypeOptionPB::default().into(), + FieldType::URL => URLTypeOptionPB::default().into(), + FieldType::Checklist => ChecklistTypeOptionPB::default().into(), + }; - type_option_builder_from_json_str(&s, field_type) + type_option_builder_from_json_str(&s, field_type) } -pub fn type_option_builder_from_json_str(s: &str, field_type: &FieldType) -> Box { - match field_type { - FieldType::RichText => Box::new(RichTextTypeOptionBuilder::from_json_str(s)), - FieldType::Number => Box::new(NumberTypeOptionBuilder::from_json_str(s)), - FieldType::DateTime => Box::new(DateTypeOptionBuilder::from_json_str(s)), - FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_json_str(s)), - FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_json_str(s)), - FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_json_str(s)), - FieldType::URL => Box::new(URLTypeOptionBuilder::from_json_str(s)), - FieldType::Checklist => Box::new(ChecklistTypeOptionBuilder::from_json_str(s)), - } +pub fn type_option_builder_from_json_str( + s: &str, + field_type: &FieldType, +) -> Box { + match field_type { + FieldType::RichText => Box::new(RichTextTypeOptionBuilder::from_json_str(s)), + FieldType::Number => Box::new(NumberTypeOptionBuilder::from_json_str(s)), + FieldType::DateTime => Box::new(DateTypeOptionBuilder::from_json_str(s)), + FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_json_str(s)), + FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_json_str(s)), + FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_json_str(s)), + FieldType::URL => Box::new(URLTypeOptionBuilder::from_json_str(s)), + FieldType::Checklist => Box::new(ChecklistTypeOptionBuilder::from_json_str(s)), + } } -pub fn type_option_builder_from_bytes>(bytes: T, field_type: &FieldType) -> Box { - let bytes = bytes.into(); - match field_type { - FieldType::RichText => Box::new(RichTextTypeOptionBuilder::from_protobuf_bytes(bytes)), - FieldType::Number => Box::new(NumberTypeOptionBuilder::from_protobuf_bytes(bytes)), - FieldType::DateTime => Box::new(DateTypeOptionBuilder::from_protobuf_bytes(bytes)), - FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_protobuf_bytes(bytes)), - FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_protobuf_bytes(bytes)), - FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_protobuf_bytes(bytes)), - FieldType::URL => Box::new(URLTypeOptionBuilder::from_protobuf_bytes(bytes)), - FieldType::Checklist => Box::new(ChecklistTypeOptionBuilder::from_protobuf_bytes(bytes)), - } +pub fn type_option_builder_from_bytes>( + bytes: T, + field_type: &FieldType, +) -> Box { + let bytes = bytes.into(); + match field_type { + FieldType::RichText => Box::new(RichTextTypeOptionBuilder::from_protobuf_bytes(bytes)), + FieldType::Number => Box::new(NumberTypeOptionBuilder::from_protobuf_bytes(bytes)), + FieldType::DateTime => Box::new(DateTypeOptionBuilder::from_protobuf_bytes(bytes)), + FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_protobuf_bytes(bytes)), + FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_protobuf_bytes(bytes)), + FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_protobuf_bytes(bytes)), + FieldType::URL => Box::new(URLTypeOptionBuilder::from_protobuf_bytes(bytes)), + FieldType::Checklist => Box::new(ChecklistTypeOptionBuilder::from_protobuf_bytes(bytes)), + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs index e98454de81..4dbb991e3e 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs @@ -2,40 +2,50 @@ use crate::entities::{CheckboxFilterConditionPB, CheckboxFilterPB}; use crate::services::field::CheckboxCellData; impl CheckboxFilterPB { - pub fn is_visible(&self, cell_data: &CheckboxCellData) -> bool { - let is_check = cell_data.is_check(); - match self.condition { - CheckboxFilterConditionPB::IsChecked => is_check, - CheckboxFilterConditionPB::IsUnChecked => !is_check, - } + pub fn is_visible(&self, cell_data: &CheckboxCellData) -> bool { + let is_check = cell_data.is_check(); + match self.condition { + CheckboxFilterConditionPB::IsChecked => is_check, + CheckboxFilterConditionPB::IsUnChecked => !is_check, } + } } #[cfg(test)] mod tests { - use crate::entities::{CheckboxFilterConditionPB, CheckboxFilterPB}; - use crate::services::field::CheckboxCellData; - use std::str::FromStr; + use crate::entities::{CheckboxFilterConditionPB, CheckboxFilterPB}; + use crate::services::field::CheckboxCellData; + use std::str::FromStr; - #[test] - fn checkbox_filter_is_check_test() { - let checkbox_filter = CheckboxFilterPB { - condition: CheckboxFilterConditionPB::IsChecked, - }; - for (value, visible) in [("true", true), ("yes", true), ("false", false), ("no", false)] { - let data = CheckboxCellData::from_str(value).unwrap(); - assert_eq!(checkbox_filter.is_visible(&data), visible); - } + #[test] + fn checkbox_filter_is_check_test() { + let checkbox_filter = CheckboxFilterPB { + condition: CheckboxFilterConditionPB::IsChecked, + }; + for (value, visible) in [ + ("true", true), + ("yes", true), + ("false", false), + ("no", false), + ] { + let data = CheckboxCellData::from_str(value).unwrap(); + assert_eq!(checkbox_filter.is_visible(&data), visible); } + } - #[test] - fn checkbox_filter_is_uncheck_test() { - let checkbox_filter = CheckboxFilterPB { - condition: CheckboxFilterConditionPB::IsUnChecked, - }; - for (value, visible) in [("false", true), ("no", true), ("true", false), ("yes", false)] { - let data = CheckboxCellData::from_str(value).unwrap(); - assert_eq!(checkbox_filter.is_visible(&data), visible); - } + #[test] + fn checkbox_filter_is_uncheck_test() { + let checkbox_filter = CheckboxFilterPB { + condition: CheckboxFilterConditionPB::IsUnChecked, + }; + for (value, visible) in [ + ("false", true), + ("no", true), + ("true", false), + ("yes", false), + ] { + let data = CheckboxCellData::from_str(value).unwrap(); + assert_eq!(checkbox_filter.is_visible(&data), visible); } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs index 1883b82098..5afa2c69b6 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs @@ -1,49 +1,49 @@ #[cfg(test)] mod tests { - use crate::entities::FieldType; - use crate::services::cell::CellDataDecoder; - use crate::services::field::type_options::checkbox_type_option::*; - use crate::services::field::FieldBuilder; + use crate::entities::FieldType; + use crate::services::cell::CellDataDecoder; + use crate::services::field::type_options::checkbox_type_option::*; + use crate::services::field::FieldBuilder; - use grid_model::FieldRevision; + use grid_model::FieldRevision; - #[test] - fn checkout_box_description_test() { - let type_option = CheckboxTypeOptionPB::default(); - let field_type = FieldType::Checkbox; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); + #[test] + fn checkout_box_description_test() { + let type_option = CheckboxTypeOptionPB::default(); + let field_type = FieldType::Checkbox; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); - // the checkout value will be checked if the value is "1", "true" or "yes" - assert_checkbox(&type_option, "1", CHECK, &field_type, &field_rev); - assert_checkbox(&type_option, "true", CHECK, &field_type, &field_rev); - assert_checkbox(&type_option, "TRUE", CHECK, &field_type, &field_rev); - assert_checkbox(&type_option, "yes", CHECK, &field_type, &field_rev); - assert_checkbox(&type_option, "YES", CHECK, &field_type, &field_rev); + // the checkout value will be checked if the value is "1", "true" or "yes" + assert_checkbox(&type_option, "1", CHECK, &field_type, &field_rev); + assert_checkbox(&type_option, "true", CHECK, &field_type, &field_rev); + assert_checkbox(&type_option, "TRUE", CHECK, &field_type, &field_rev); + assert_checkbox(&type_option, "yes", CHECK, &field_type, &field_rev); + assert_checkbox(&type_option, "YES", CHECK, &field_type, &field_rev); - // the checkout value will be uncheck if the value is "false" or "No" - assert_checkbox(&type_option, "false", UNCHECK, &field_type, &field_rev); - assert_checkbox(&type_option, "No", UNCHECK, &field_type, &field_rev); - assert_checkbox(&type_option, "NO", UNCHECK, &field_type, &field_rev); - assert_checkbox(&type_option, "0", UNCHECK, &field_type, &field_rev); + // the checkout value will be uncheck if the value is "false" or "No" + assert_checkbox(&type_option, "false", UNCHECK, &field_type, &field_rev); + assert_checkbox(&type_option, "No", UNCHECK, &field_type, &field_rev); + assert_checkbox(&type_option, "NO", UNCHECK, &field_type, &field_rev); + assert_checkbox(&type_option, "0", UNCHECK, &field_type, &field_rev); - // the checkout value will be empty if the value is letters or empty string - assert_checkbox(&type_option, "abc", "", &field_type, &field_rev); - assert_checkbox(&type_option, "", "", &field_type, &field_rev); - } + // the checkout value will be empty if the value is letters or empty string + assert_checkbox(&type_option, "abc", "", &field_type, &field_rev); + assert_checkbox(&type_option, "", "", &field_type, &field_rev); + } - fn assert_checkbox( - type_option: &CheckboxTypeOptionPB, - input_str: &str, - expected_str: &str, - field_type: &FieldType, - field_rev: &FieldRevision, - ) { - assert_eq!( - type_option - .decode_cell_str(input_str.to_owned(), field_type, field_rev) - .unwrap() - .to_string(), - expected_str.to_owned() - ); - } + fn assert_checkbox( + type_option: &CheckboxTypeOptionPB, + input_str: &str, + expected_str: &str, + field_type: &FieldType, + field_rev: &FieldRevision, + ) { + assert_eq!( + type_option + .decode_cell_str(input_str.to_owned(), field_type, field_rev) + .unwrap() + .to_string(), + expected_str.to_owned() + ); + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs index f1ea58e0ea..0bc906d3a2 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs @@ -2,8 +2,8 @@ use crate::entities::{CheckboxFilterPB, FieldType}; use crate::impl_type_option; use crate::services::cell::{CellDataChangeset, CellDataDecoder, FromCellString, TypeCellData}; use crate::services::field::{ - default_order, BoxTypeOptionBuilder, CheckboxCellData, TypeOption, TypeOptionBuilder, TypeOptionCellData, - TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, + default_order, BoxTypeOptionBuilder, CheckboxCellData, TypeOption, TypeOptionBuilder, + TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, }; use bytes::Bytes; use flowy_derive::ProtoBuf; @@ -19,127 +19,138 @@ impl_into_box_type_option_builder!(CheckboxTypeOptionBuilder); impl_builder_from_json_str_and_from_bytes!(CheckboxTypeOptionBuilder, CheckboxTypeOptionPB); impl CheckboxTypeOptionBuilder { - pub fn set_selected(mut self, is_selected: bool) -> Self { - self.0.is_selected = is_selected; - self - } + pub fn set_selected(mut self, is_selected: bool) -> Self { + self.0.is_selected = is_selected; + self + } } impl TypeOptionBuilder for CheckboxTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::Checkbox - } + fn field_type(&self) -> FieldType { + FieldType::Checkbox + } - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } + fn serializer(&self) -> &dyn TypeOptionDataSerializer { + &self.0 + } } #[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] pub struct CheckboxTypeOptionPB { - #[pb(index = 1)] - pub is_selected: bool, + #[pb(index = 1)] + pub is_selected: bool, } impl_type_option!(CheckboxTypeOptionPB, FieldType::Checkbox); impl TypeOption for CheckboxTypeOptionPB { - type CellData = CheckboxCellData; - type CellChangeset = CheckboxCellChangeset; - type CellProtobufType = CheckboxCellData; - type CellFilter = CheckboxFilterPB; + type CellData = CheckboxCellData; + type CellChangeset = CheckboxCellChangeset; + type CellProtobufType = CheckboxCellData; + type CellFilter = CheckboxFilterPB; } impl TypeOptionTransform for CheckboxTypeOptionPB { - fn transformable(&self) -> bool { - true - } + fn transformable(&self) -> bool { + true + } - fn transform_type_option(&mut self, _old_type_option_field_type: FieldType, _old_type_option_data: String) {} + fn transform_type_option( + &mut self, + _old_type_option_field_type: FieldType, + _old_type_option_data: String, + ) { + } - fn transform_type_option_cell_str( - &self, - cell_str: &str, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> Option<::CellData> { - if decoded_field_type.is_text() { - match CheckboxCellData::from_str(cell_str) { - Ok(cell_data) => Some(cell_data), - Err(_) => None, - } - } else { - None - } + fn transform_type_option_cell_str( + &self, + cell_str: &str, + decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> Option<::CellData> { + if decoded_field_type.is_text() { + match CheckboxCellData::from_str(cell_str) { + Ok(cell_data) => Some(cell_data), + Err(_) => None, + } + } else { + None } + } } impl TypeOptionCellData for CheckboxTypeOptionPB { - fn convert_to_protobuf(&self, cell_data: ::CellData) -> ::CellProtobufType { - cell_data - } + fn convert_to_protobuf( + &self, + cell_data: ::CellData, + ) -> ::CellProtobufType { + cell_data + } - fn decode_type_option_cell_str(&self, cell_str: String) -> FlowyResult<::CellData> { - CheckboxCellData::from_cell_str(&cell_str) - } + fn decode_type_option_cell_str( + &self, + cell_str: String, + ) -> FlowyResult<::CellData> { + CheckboxCellData::from_cell_str(&cell_str) + } } impl CellDataDecoder for CheckboxTypeOptionPB { - fn decode_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult<::CellData> { - if !decoded_field_type.is_checkbox() { - return Ok(Default::default()); - } - - self.decode_type_option_cell_str(cell_str) + fn decode_cell_str( + &self, + cell_str: String, + decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> FlowyResult<::CellData> { + if !decoded_field_type.is_checkbox() { + return Ok(Default::default()); } - fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { - cell_data.to_string() - } + self.decode_type_option_cell_str(cell_str) + } + + fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { + cell_data.to_string() + } } pub type CheckboxCellChangeset = String; impl CellDataChangeset for CheckboxTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - _type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - let checkbox_cell_data = CheckboxCellData::from_str(&changeset)?; - Ok((checkbox_cell_data.to_string(), checkbox_cell_data)) - } + fn apply_changeset( + &self, + changeset: ::CellChangeset, + _type_cell_data: Option, + ) -> FlowyResult<(String, ::CellData)> { + let checkbox_cell_data = CheckboxCellData::from_str(&changeset)?; + Ok((checkbox_cell_data.to_string(), checkbox_cell_data)) + } } impl TypeOptionCellDataFilter for CheckboxTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_checkbox() { - return true; - } - filter.is_visible(cell_data) + fn apply_filter( + &self, + filter: &::CellFilter, + field_type: &FieldType, + cell_data: &::CellData, + ) -> bool { + if !field_type.is_checkbox() { + return true; } + filter.is_visible(cell_data) + } } impl TypeOptionCellDataCompare for CheckboxTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - match (cell_data.is_check(), other_cell_data.is_check()) { - (true, true) => Ordering::Equal, - (true, false) => Ordering::Greater, - (false, true) => Ordering::Less, - (false, false) => default_order(), - } + fn apply_cmp( + &self, + cell_data: &::CellData, + other_cell_data: &::CellData, + ) -> Ordering { + match (cell_data.is_check(), other_cell_data.is_check()) { + (true, true) => Ordering::Equal, + (true, false) => Ordering::Greater, + (false, true) => Ordering::Less, + (false, false) => default_order(), } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs index 04c48823c0..d7de02e9ef 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs @@ -11,82 +11,82 @@ pub const UNCHECK: &str = "No"; pub struct CheckboxCellData(String); impl CheckboxCellData { - pub fn is_check(&self) -> bool { - self.0 == CHECK - } + pub fn is_check(&self) -> bool { + self.0 == CHECK + } - pub fn is_uncheck(&self) -> bool { - self.0 == UNCHECK - } + pub fn is_uncheck(&self) -> bool { + self.0 == UNCHECK + } } impl AsRef<[u8]> for CheckboxCellData { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } } impl FromStr for CheckboxCellData { - type Err = FlowyError; + type Err = FlowyError; - fn from_str(s: &str) -> Result { - let lower_case_str: &str = &s.to_lowercase(); - let val = match lower_case_str { - "1" => Some(true), - "true" => Some(true), - "yes" => Some(true), - "0" => Some(false), - "false" => Some(false), - "no" => Some(false), - _ => None, - }; + fn from_str(s: &str) -> Result { + let lower_case_str: &str = &s.to_lowercase(); + let val = match lower_case_str { + "1" => Some(true), + "true" => Some(true), + "yes" => Some(true), + "0" => Some(false), + "false" => Some(false), + "no" => Some(false), + _ => None, + }; - match val { - Some(true) => Ok(Self(CHECK.to_string())), - Some(false) => Ok(Self(UNCHECK.to_string())), - None => Ok(Self("".to_string())), - } + match val { + Some(true) => Ok(Self(CHECK.to_string())), + Some(false) => Ok(Self(UNCHECK.to_string())), + None => Ok(Self("".to_string())), } + } } impl std::convert::TryFrom for Bytes { - type Error = ProtobufError; + type Error = ProtobufError; - fn try_from(value: CheckboxCellData) -> Result { - Ok(Bytes::from(value.0)) - } + fn try_from(value: CheckboxCellData) -> Result { + Ok(Bytes::from(value.0)) + } } impl FromCellString for CheckboxCellData { - fn from_cell_str(s: &str) -> FlowyResult - where - Self: Sized, - { - Self::from_str(s) - } + fn from_cell_str(s: &str) -> FlowyResult + where + Self: Sized, + { + Self::from_str(s) + } } impl ToString for CheckboxCellData { - fn to_string(&self) -> String { - self.0.clone() - } + fn to_string(&self) -> String { + self.0.clone() + } } impl DecodedCellData for CheckboxCellData { - type Object = CheckboxCellData; + type Object = CheckboxCellData; - fn is_empty(&self) -> bool { - self.0.is_empty() - } + fn is_empty(&self) -> bool { + self.0.is_empty() + } } pub struct CheckboxCellDataParser(); impl CellProtobufBlobParser for CheckboxCellDataParser { - type Object = CheckboxCellData; - fn parser(bytes: &Bytes) -> FlowyResult { - match String::from_utf8(bytes.to_vec()) { - Ok(s) => CheckboxCellData::from_cell_str(&s), - Err(_) => Ok(CheckboxCellData("".to_string())), - } + type Object = CheckboxCellData; + fn parser(bytes: &Bytes) -> FlowyResult { + match String::from_utf8(bytes.to_vec()) { + Ok(s) => CheckboxCellData::from_cell_str(&s), + Err(_) => Ok(CheckboxCellData("".to_string())), } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_filter.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_filter.rs index 12d54b67c1..35f30b388e 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_filter.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_filter.rs @@ -2,148 +2,148 @@ use crate::entities::{DateFilterConditionPB, DateFilterPB}; use chrono::NaiveDateTime; impl DateFilterPB { - pub fn is_visible>>(&self, cell_timestamp: T) -> bool { - match cell_timestamp.into() { - None => DateFilterConditionPB::DateIsEmpty == self.condition, - Some(timestamp) => { - match self.condition { - DateFilterConditionPB::DateIsNotEmpty => { - return true; - } - DateFilterConditionPB::DateIsEmpty => { - return false; - } - _ => {} - } - - let cell_time = NaiveDateTime::from_timestamp_opt(timestamp, 0); - let cell_date = cell_time.map(|time| time.date()); - match self.timestamp { - None => { - if self.start.is_none() { - return true; - } - - if self.end.is_none() { - return true; - } - - let start_time = NaiveDateTime::from_timestamp_opt(*self.start.as_ref().unwrap(), 0); - let start_date = start_time.map(|time| time.date()); - - let end_time = NaiveDateTime::from_timestamp_opt(*self.end.as_ref().unwrap(), 0); - let end_date = end_time.map(|time| time.date()); - - cell_date >= start_date && cell_date <= end_date - } - Some(timestamp) => { - let expected_timestamp = NaiveDateTime::from_timestamp_opt(timestamp, 0); - let expected_date = expected_timestamp.map(|time| time.date()); - - // We assume that the cell_timestamp doesn't contain hours, just day. - match self.condition { - DateFilterConditionPB::DateIs => cell_date == expected_date, - DateFilterConditionPB::DateBefore => cell_date < expected_date, - DateFilterConditionPB::DateAfter => cell_date > expected_date, - DateFilterConditionPB::DateOnOrBefore => cell_date <= expected_date, - DateFilterConditionPB::DateOnOrAfter => cell_date >= expected_date, - _ => true, - } - } - } - } + pub fn is_visible>>(&self, cell_timestamp: T) -> bool { + match cell_timestamp.into() { + None => DateFilterConditionPB::DateIsEmpty == self.condition, + Some(timestamp) => { + match self.condition { + DateFilterConditionPB::DateIsNotEmpty => { + return true; + }, + DateFilterConditionPB::DateIsEmpty => { + return false; + }, + _ => {}, } + + let cell_time = NaiveDateTime::from_timestamp_opt(timestamp, 0); + let cell_date = cell_time.map(|time| time.date()); + match self.timestamp { + None => { + if self.start.is_none() { + return true; + } + + if self.end.is_none() { + return true; + } + + let start_time = NaiveDateTime::from_timestamp_opt(*self.start.as_ref().unwrap(), 0); + let start_date = start_time.map(|time| time.date()); + + let end_time = NaiveDateTime::from_timestamp_opt(*self.end.as_ref().unwrap(), 0); + let end_date = end_time.map(|time| time.date()); + + cell_date >= start_date && cell_date <= end_date + }, + Some(timestamp) => { + let expected_timestamp = NaiveDateTime::from_timestamp_opt(timestamp, 0); + let expected_date = expected_timestamp.map(|time| time.date()); + + // We assume that the cell_timestamp doesn't contain hours, just day. + match self.condition { + DateFilterConditionPB::DateIs => cell_date == expected_date, + DateFilterConditionPB::DateBefore => cell_date < expected_date, + DateFilterConditionPB::DateAfter => cell_date > expected_date, + DateFilterConditionPB::DateOnOrBefore => cell_date <= expected_date, + DateFilterConditionPB::DateOnOrAfter => cell_date >= expected_date, + _ => true, + } + }, + } + }, } + } } #[cfg(test)] mod tests { - #![allow(clippy::all)] - use crate::entities::{DateFilterConditionPB, DateFilterPB}; + #![allow(clippy::all)] + use crate::entities::{DateFilterConditionPB, DateFilterPB}; - #[test] - fn date_filter_is_test() { - let filter = DateFilterPB { - condition: DateFilterConditionPB::DateIs, - timestamp: Some(1668387885), - end: None, - start: None, - }; + #[test] + fn date_filter_is_test() { + let filter = DateFilterPB { + condition: DateFilterConditionPB::DateIs, + timestamp: Some(1668387885), + end: None, + start: None, + }; - for (val, visible) in vec![(1668387885, true), (1647251762, false)] { - assert_eq!(filter.is_visible(val as i64), visible); - } + for (val, visible) in vec![(1668387885, true), (1647251762, false)] { + assert_eq!(filter.is_visible(val as i64), visible); } - #[test] - fn date_filter_before_test() { - let filter = DateFilterPB { - condition: DateFilterConditionPB::DateBefore, - timestamp: Some(1668387885), - start: None, - end: None, - }; + } + #[test] + fn date_filter_before_test() { + let filter = DateFilterPB { + condition: DateFilterConditionPB::DateBefore, + timestamp: Some(1668387885), + start: None, + end: None, + }; - for (val, visible, msg) in vec![(1668387884, false, "1"), (1647251762, true, "2")] { - assert_eq!(filter.is_visible(val as i64), visible, "{}", msg); - } + for (val, visible, msg) in vec![(1668387884, false, "1"), (1647251762, true, "2")] { + assert_eq!(filter.is_visible(val as i64), visible, "{}", msg); } + } - #[test] - fn date_filter_before_or_on_test() { - let filter = DateFilterPB { - condition: DateFilterConditionPB::DateOnOrBefore, - timestamp: Some(1668387885), - start: None, - end: None, - }; + #[test] + fn date_filter_before_or_on_test() { + let filter = DateFilterPB { + condition: DateFilterConditionPB::DateOnOrBefore, + timestamp: Some(1668387885), + start: None, + end: None, + }; - for (val, visible) in vec![(1668387884, true), (1668387885, true)] { - assert_eq!(filter.is_visible(val as i64), visible); - } + for (val, visible) in vec![(1668387884, true), (1668387885, true)] { + assert_eq!(filter.is_visible(val as i64), visible); } - #[test] - fn date_filter_after_test() { - let filter = DateFilterPB { - condition: DateFilterConditionPB::DateAfter, - timestamp: Some(1668387885), - start: None, - end: None, - }; + } + #[test] + fn date_filter_after_test() { + let filter = DateFilterPB { + condition: DateFilterConditionPB::DateAfter, + timestamp: Some(1668387885), + start: None, + end: None, + }; - for (val, visible) in vec![(1668387888, false), (1668531885, true), (0, false)] { - assert_eq!(filter.is_visible(val as i64), visible); - } + for (val, visible) in vec![(1668387888, false), (1668531885, true), (0, false)] { + assert_eq!(filter.is_visible(val as i64), visible); } + } - #[test] - fn date_filter_within_test() { - let filter = DateFilterPB { - condition: DateFilterConditionPB::DateWithIn, - start: Some(1668272685), // 11/13 - end: Some(1668618285), // 11/17 - timestamp: None, - }; + #[test] + fn date_filter_within_test() { + let filter = DateFilterPB { + condition: DateFilterConditionPB::DateWithIn, + start: Some(1668272685), // 11/13 + end: Some(1668618285), // 11/17 + timestamp: None, + }; - for (val, visible, _msg) in vec![ - (1668272685, true, "11/13"), - (1668359085, true, "11/14"), - (1668704685, false, "11/18"), - ] { - assert_eq!(filter.is_visible(val as i64), visible); - } + for (val, visible, _msg) in vec![ + (1668272685, true, "11/13"), + (1668359085, true, "11/14"), + (1668704685, false, "11/18"), + ] { + assert_eq!(filter.is_visible(val as i64), visible); } + } - #[test] - fn date_filter_is_empty_test() { - let filter = DateFilterPB { - condition: DateFilterConditionPB::DateIsEmpty, - start: None, - end: None, - timestamp: None, - }; + #[test] + fn date_filter_is_empty_test() { + let filter = DateFilterPB { + condition: DateFilterConditionPB::DateIsEmpty, + start: None, + end: None, + timestamp: None, + }; - for (val, visible) in vec![(None, true), (Some(123), false)] { - assert_eq!(filter.is_visible(val), visible); - } + for (val, visible) in vec![(None, true), (Some(123), false)] { + assert_eq!(filter.is_visible(val), visible); } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_tests.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_tests.rs index 58bb327448..b3bea1fb69 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_tests.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_tests.rs @@ -1,176 +1,189 @@ #[cfg(test)] mod tests { - use crate::entities::FieldType; - use crate::services::cell::{CellDataChangeset, CellDataDecoder}; + use crate::entities::FieldType; + use crate::services::cell::{CellDataChangeset, CellDataDecoder}; - use crate::services::field::*; - // use crate::services::field::{DateCellChangeset, DateCellData, DateFormat, DateTypeOptionPB, TimeFormat}; - use chrono::format::strftime::StrftimeItems; - use chrono::{FixedOffset, NaiveDateTime}; - use grid_model::FieldRevision; - use strum::IntoEnumIterator; + use crate::services::field::*; + // use crate::services::field::{DateCellChangeset, DateCellData, DateFormat, DateTypeOptionPB, TimeFormat}; + use chrono::format::strftime::StrftimeItems; + use chrono::{FixedOffset, NaiveDateTime}; + use grid_model::FieldRevision; + use strum::IntoEnumIterator; - #[test] - fn date_type_option_date_format_test() { - let mut type_option = DateTypeOptionPB::default(); - let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - for date_format in DateFormat::iter() { - type_option.date_format = date_format; - match date_format { - DateFormat::Friendly => { - assert_date(&type_option, 1647251762, None, "Mar 14,2022", &field_rev); - } - DateFormat::US => { - assert_date(&type_option, 1647251762, None, "2022/03/14", &field_rev); - } - DateFormat::ISO => { - assert_date(&type_option, 1647251762, None, "2022-03-14", &field_rev); - } - DateFormat::Local => { - assert_date(&type_option, 1647251762, None, "03/14/2022", &field_rev); - } - } - } + #[test] + fn date_type_option_date_format_test() { + let mut type_option = DateTypeOptionPB::default(); + let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); + for date_format in DateFormat::iter() { + type_option.date_format = date_format; + match date_format { + DateFormat::Friendly => { + assert_date(&type_option, 1647251762, None, "Mar 14,2022", &field_rev); + }, + DateFormat::US => { + assert_date(&type_option, 1647251762, None, "2022/03/14", &field_rev); + }, + DateFormat::ISO => { + assert_date(&type_option, 1647251762, None, "2022-03-14", &field_rev); + }, + DateFormat::Local => { + assert_date(&type_option, 1647251762, None, "03/14/2022", &field_rev); + }, + } } + } - #[test] - fn date_type_option_different_time_format_test() { - let mut type_option = DateTypeOptionPB::default(); - let field_type = FieldType::DateTime; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); + #[test] + fn date_type_option_different_time_format_test() { + let mut type_option = DateTypeOptionPB::default(); + let field_type = FieldType::DateTime; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); - for time_format in TimeFormat::iter() { - type_option.time_format = time_format; - type_option.include_time = true; - match time_format { - TimeFormat::TwentyFourHour => { - assert_date(&type_option, 1653609600, None, "May 27,2022", &field_rev); - assert_date( - &type_option, - 1653609600, - Some("23:00".to_owned()), - "May 27,2022 23:00", - &field_rev, - ); - } - TimeFormat::TwelveHour => { - assert_date(&type_option, 1653609600, None, "May 27,2022", &field_rev); - assert_date( - &type_option, - 1653609600, - Some("11:23 pm".to_owned()), - "May 27,2022 11:23 PM", - &field_rev, - ); - } - } - } - } - - #[test] - fn date_type_option_invalid_date_str_test() { - let type_option = DateTypeOptionPB::default(); - let field_type = FieldType::DateTime; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_date(&type_option, "abc", None, "", &field_rev); - } - - #[test] - #[should_panic] - fn date_type_option_invalid_include_time_str_test() { - let mut type_option = DateTypeOptionPB::new(); - type_option.include_time = true; - let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - - assert_date( + for time_format in TimeFormat::iter() { + type_option.time_format = time_format; + type_option.include_time = true; + match time_format { + TimeFormat::TwentyFourHour => { + assert_date(&type_option, 1653609600, None, "May 27,2022", &field_rev); + assert_date( &type_option, 1653609600, - Some("1:".to_owned()), - "May 27,2022 01:00", + Some("23:00".to_owned()), + "May 27,2022 23:00", &field_rev, - ); - } - - #[test] - fn date_type_option_empty_include_time_str_test() { - let mut type_option = DateTypeOptionPB::new(); - type_option.include_time = true; - let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - - assert_date(&type_option, 1653609600, Some("".to_owned()), "May 27,2022", &field_rev); - } - - /// The default time format is TwentyFourHour, so the include_time_str in twelve_hours_format will cause parser error. - #[test] - #[should_panic] - fn date_type_option_twelve_hours_include_time_str_in_twenty_four_hours_format() { - let mut type_option = DateTypeOptionPB::new(); - type_option.include_time = true; - let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - - assert_date( + ); + }, + TimeFormat::TwelveHour => { + assert_date(&type_option, 1653609600, None, "May 27,2022", &field_rev); + assert_date( &type_option, 1653609600, - Some("1:00 am".to_owned()), - "May 27,2022 01:00 AM", + Some("11:23 pm".to_owned()), + "May 27,2022 11:23 PM", &field_rev, - ); + ); + }, + } } + } - #[test] - fn utc_to_native_test() { - let native_timestamp = 1647251762; - let native = NaiveDateTime::from_timestamp_opt(native_timestamp, 0).unwrap(); + #[test] + fn date_type_option_invalid_date_str_test() { + let type_option = DateTypeOptionPB::default(); + let field_type = FieldType::DateTime; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + assert_date(&type_option, "abc", None, "", &field_rev); + } - let utc = chrono::DateTime::::from_utc(native, chrono::Utc); - // utc_timestamp doesn't carry timezone - let utc_timestamp = utc.timestamp(); - assert_eq!(native_timestamp, utc_timestamp); + #[test] + #[should_panic] + fn date_type_option_invalid_include_time_str_test() { + let mut type_option = DateTypeOptionPB::new(); + type_option.include_time = true; + let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - let format = "%m/%d/%Y %I:%M %p".to_string(); - let native_time_str = format!("{}", native.format_with_items(StrftimeItems::new(&format))); - let utc_time_str = format!("{}", utc.format_with_items(StrftimeItems::new(&format))); - assert_eq!(native_time_str, utc_time_str); + assert_date( + &type_option, + 1653609600, + Some("1:".to_owned()), + "May 27,2022 01:00", + &field_rev, + ); + } - // Mon Mar 14 2022 17:56:02 GMT+0800 (China Standard Time) - let gmt_8_offset = FixedOffset::east_opt(8 * 3600).unwrap(); - let china_local = chrono::DateTime::::from_utc(native, gmt_8_offset); - let china_local_time = format!("{}", china_local.format_with_items(StrftimeItems::new(&format))); + #[test] + fn date_type_option_empty_include_time_str_test() { + let mut type_option = DateTypeOptionPB::new(); + type_option.include_time = true; + let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - assert_eq!(china_local_time, "03/14/2022 05:56 PM"); - } - - fn assert_date( - type_option: &DateTypeOptionPB, - timestamp: T, - include_time_str: Option, - expected_str: &str, - field_rev: &FieldRevision, - ) { - let changeset = DateCellChangeset { - date: Some(timestamp.to_string()), - time: include_time_str, - is_utc: false, - }; - let (cell_str, _) = type_option.apply_changeset(changeset, None).unwrap(); - - assert_eq!( - decode_cell_data(cell_str, type_option, field_rev), - expected_str.to_owned(), - ); - } - - fn decode_cell_data(cell_str: String, type_option: &DateTypeOptionPB, field_rev: &FieldRevision) -> String { - let decoded_data = type_option - .decode_cell_str(cell_str, &FieldType::DateTime, field_rev) - .unwrap(); - let decoded_data = type_option.convert_to_protobuf(decoded_data); - if type_option.include_time { - format!("{} {}", decoded_data.date, decoded_data.time) - .trim_end() - .to_owned() - } else { - decoded_data.date - } + assert_date( + &type_option, + 1653609600, + Some("".to_owned()), + "May 27,2022", + &field_rev, + ); + } + + /// The default time format is TwentyFourHour, so the include_time_str in twelve_hours_format will cause parser error. + #[test] + #[should_panic] + fn date_type_option_twelve_hours_include_time_str_in_twenty_four_hours_format() { + let mut type_option = DateTypeOptionPB::new(); + type_option.include_time = true; + let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); + + assert_date( + &type_option, + 1653609600, + Some("1:00 am".to_owned()), + "May 27,2022 01:00 AM", + &field_rev, + ); + } + + #[test] + fn utc_to_native_test() { + let native_timestamp = 1647251762; + let native = NaiveDateTime::from_timestamp_opt(native_timestamp, 0).unwrap(); + + let utc = chrono::DateTime::::from_utc(native, chrono::Utc); + // utc_timestamp doesn't carry timezone + let utc_timestamp = utc.timestamp(); + assert_eq!(native_timestamp, utc_timestamp); + + let format = "%m/%d/%Y %I:%M %p".to_string(); + let native_time_str = format!("{}", native.format_with_items(StrftimeItems::new(&format))); + let utc_time_str = format!("{}", utc.format_with_items(StrftimeItems::new(&format))); + assert_eq!(native_time_str, utc_time_str); + + // Mon Mar 14 2022 17:56:02 GMT+0800 (China Standard Time) + let gmt_8_offset = FixedOffset::east_opt(8 * 3600).unwrap(); + let china_local = chrono::DateTime::::from_utc(native, gmt_8_offset); + let china_local_time = format!( + "{}", + china_local.format_with_items(StrftimeItems::new(&format)) + ); + + assert_eq!(china_local_time, "03/14/2022 05:56 PM"); + } + + fn assert_date( + type_option: &DateTypeOptionPB, + timestamp: T, + include_time_str: Option, + expected_str: &str, + field_rev: &FieldRevision, + ) { + let changeset = DateCellChangeset { + date: Some(timestamp.to_string()), + time: include_time_str, + is_utc: false, + }; + let (cell_str, _) = type_option.apply_changeset(changeset, None).unwrap(); + + assert_eq!( + decode_cell_data(cell_str, type_option, field_rev), + expected_str.to_owned(), + ); + } + + fn decode_cell_data( + cell_str: String, + type_option: &DateTypeOptionPB, + field_rev: &FieldRevision, + ) -> String { + let decoded_data = type_option + .decode_cell_str(cell_str, &FieldType::DateTime, field_rev) + .unwrap(); + let decoded_data = type_option.convert_to_protobuf(decoded_data); + if type_option.include_time { + format!("{} {}", decoded_data.date, decoded_data.time) + .trim_end() + .to_owned() + } else { + decoded_data.date } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option.rs index 8406e32dcf..d1827ad8e9 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option.rs @@ -2,9 +2,9 @@ use crate::entities::{DateFilterPB, FieldType}; use crate::impl_type_option; use crate::services::cell::{CellDataChangeset, CellDataDecoder, FromCellString, TypeCellData}; use crate::services::field::{ - default_order, BoxTypeOptionBuilder, DateCellChangeset, DateCellData, DateCellDataPB, DateFormat, TimeFormat, - TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, - TypeOptionTransform, + default_order, BoxTypeOptionBuilder, DateCellChangeset, DateCellData, DateCellDataPB, DateFormat, + TimeFormat, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare, + TypeOptionCellDataFilter, TypeOptionTransform, }; use bytes::Bytes; use chrono::format::strftime::StrftimeItems; @@ -18,199 +18,220 @@ use std::cmp::Ordering; // Date #[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] pub struct DateTypeOptionPB { - #[pb(index = 1)] - pub date_format: DateFormat, + #[pb(index = 1)] + pub date_format: DateFormat, - #[pb(index = 2)] - pub time_format: TimeFormat, + #[pb(index = 2)] + pub time_format: TimeFormat, - #[pb(index = 3)] - pub include_time: bool, + #[pb(index = 3)] + pub include_time: bool, } impl_type_option!(DateTypeOptionPB, FieldType::DateTime); impl TypeOption for DateTypeOptionPB { - type CellData = DateCellData; - type CellChangeset = DateCellChangeset; - type CellProtobufType = DateCellDataPB; - type CellFilter = DateFilterPB; + type CellData = DateCellData; + type CellChangeset = DateCellChangeset; + type CellProtobufType = DateCellDataPB; + type CellFilter = DateFilterPB; } impl TypeOptionCellData for DateTypeOptionPB { - fn convert_to_protobuf(&self, cell_data: ::CellData) -> ::CellProtobufType { - self.today_desc_from_timestamp(cell_data) - } + fn convert_to_protobuf( + &self, + cell_data: ::CellData, + ) -> ::CellProtobufType { + self.today_desc_from_timestamp(cell_data) + } - fn decode_type_option_cell_str(&self, cell_str: String) -> FlowyResult<::CellData> { - DateCellData::from_cell_str(&cell_str) - } + fn decode_type_option_cell_str( + &self, + cell_str: String, + ) -> FlowyResult<::CellData> { + DateCellData::from_cell_str(&cell_str) + } } impl DateTypeOptionPB { - #[allow(dead_code)] - pub fn new() -> Self { - Self::default() + #[allow(dead_code)] + pub fn new() -> Self { + Self::default() + } + + fn today_desc_from_timestamp>(&self, timestamp: T) -> DateCellDataPB { + let timestamp = timestamp.into(); + let native = chrono::NaiveDateTime::from_timestamp_opt(timestamp, 0); + if native.is_none() { + return DateCellDataPB::default(); + } + let native = native.unwrap(); + if native.timestamp() == 0 { + return DateCellDataPB::default(); } - fn today_desc_from_timestamp>(&self, timestamp: T) -> DateCellDataPB { - let timestamp = timestamp.into(); - let native = chrono::NaiveDateTime::from_timestamp_opt(timestamp, 0); - if native.is_none() { - return DateCellDataPB::default(); - } - let native = native.unwrap(); - if native.timestamp() == 0 { - return DateCellDataPB::default(); - } + let time = native.time(); + let has_time = time.hour() != 0 || time.second() != 0; - let time = native.time(); - let has_time = time.hour() != 0 || time.second() != 0; + let utc = self.utc_date_time_from_native(native); + let fmt = self.date_format.format_str(); + let date = format!("{}", utc.format_with_items(StrftimeItems::new(fmt))); - let utc = self.utc_date_time_from_native(native); - let fmt = self.date_format.format_str(); - let date = format!("{}", utc.format_with_items(StrftimeItems::new(fmt))); - - let mut time = "".to_string(); - if has_time && self.include_time { - let fmt = format!("{}{}", self.date_format.format_str(), self.time_format.format_str()); - time = format!("{}", utc.format_with_items(StrftimeItems::new(&fmt))).replace(&date, ""); - } - - let timestamp = native.timestamp(); - DateCellDataPB { date, time, timestamp } + let mut time = "".to_string(); + if has_time && self.include_time { + let fmt = format!( + "{}{}", + self.date_format.format_str(), + self.time_format.format_str() + ); + time = format!("{}", utc.format_with_items(StrftimeItems::new(&fmt))).replace(&date, ""); } - fn date_fmt(&self, time: &Option) -> String { - if self.include_time { - match time.as_ref() { - None => self.date_format.format_str().to_string(), - Some(time_str) => { - if time_str.is_empty() { - self.date_format.format_str().to_string() - } else { - format!("{} {}", self.date_format.format_str(), self.time_format.format_str()) - } - } - } - } else { + let timestamp = native.timestamp(); + DateCellDataPB { + date, + time, + timestamp, + } + } + + fn date_fmt(&self, time: &Option) -> String { + if self.include_time { + match time.as_ref() { + None => self.date_format.format_str().to_string(), + Some(time_str) => { + if time_str.is_empty() { self.date_format.format_str().to_string() - } + } else { + format!( + "{} {}", + self.date_format.format_str(), + self.time_format.format_str() + ) + } + }, + } + } else { + self.date_format.format_str().to_string() + } + } + + fn timestamp_from_utc_with_time( + &self, + utc: &chrono::DateTime, + time: &Option, + ) -> FlowyResult { + if let Some(time_str) = time.as_ref() { + if !time_str.is_empty() { + let date_str = format!( + "{}{}", + utc.format_with_items(StrftimeItems::new(self.date_format.format_str())), + &time_str + ); + + return match NaiveDateTime::parse_from_str(&date_str, &self.date_fmt(time)) { + Ok(native) => { + let utc = self.utc_date_time_from_native(native); + Ok(utc.timestamp()) + }, + Err(_e) => { + let msg = format!("Parse {} failed", date_str); + Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg)) + }, + }; + } } - fn timestamp_from_utc_with_time( - &self, - utc: &chrono::DateTime, - time: &Option, - ) -> FlowyResult { - if let Some(time_str) = time.as_ref() { - if !time_str.is_empty() { - let date_str = format!( - "{}{}", - utc.format_with_items(StrftimeItems::new(self.date_format.format_str())), - &time_str - ); + Ok(utc.timestamp()) + } - return match NaiveDateTime::parse_from_str(&date_str, &self.date_fmt(time)) { - Ok(native) => { - let utc = self.utc_date_time_from_native(native); - Ok(utc.timestamp()) - } - Err(_e) => { - let msg = format!("Parse {} failed", date_str); - Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg)) - } - }; - } - } - - Ok(utc.timestamp()) - } - - fn utc_date_time_from_native(&self, naive: chrono::NaiveDateTime) -> chrono::DateTime { - chrono::DateTime::::from_utc(naive, chrono::Utc) - } + fn utc_date_time_from_native( + &self, + naive: chrono::NaiveDateTime, + ) -> chrono::DateTime { + chrono::DateTime::::from_utc(naive, chrono::Utc) + } } impl TypeOptionTransform for DateTypeOptionPB {} impl CellDataDecoder for DateTypeOptionPB { - fn decode_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult<::CellData> { - // Return default data if the type_option_cell_data is not FieldType::DateTime. - // It happens when switching from one field to another. - // For example: - // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen. - if !decoded_field_type.is_date() { - return Ok(Default::default()); - } - - self.decode_type_option_cell_str(cell_str) + fn decode_cell_str( + &self, + cell_str: String, + decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> FlowyResult<::CellData> { + // Return default data if the type_option_cell_data is not FieldType::DateTime. + // It happens when switching from one field to another. + // For example: + // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen. + if !decoded_field_type.is_date() { + return Ok(Default::default()); } - fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { - self.today_desc_from_timestamp(cell_data).date - } + self.decode_type_option_cell_str(cell_str) + } + + fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { + self.today_desc_from_timestamp(cell_data).date + } } impl CellDataChangeset for DateTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - _type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - let cell_data = match changeset.date_timestamp() { - None => 0, - Some(date_timestamp) => match (self.include_time, changeset.time) { - (true, Some(time)) => { - let time = Some(time.trim().to_uppercase()); - let native = NaiveDateTime::from_timestamp_opt(date_timestamp, 0); - if let Some(native) = native { - let utc = self.utc_date_time_from_native(native); - self.timestamp_from_utc_with_time(&utc, &time)? - } else { - date_timestamp - } - } - _ => date_timestamp, - }, - }; - let date_cell_data = DateCellData(Some(cell_data)); - Ok((date_cell_data.to_string(), date_cell_data)) - } + fn apply_changeset( + &self, + changeset: ::CellChangeset, + _type_cell_data: Option, + ) -> FlowyResult<(String, ::CellData)> { + let cell_data = match changeset.date_timestamp() { + None => 0, + Some(date_timestamp) => match (self.include_time, changeset.time) { + (true, Some(time)) => { + let time = Some(time.trim().to_uppercase()); + let native = NaiveDateTime::from_timestamp_opt(date_timestamp, 0); + if let Some(native) = native { + let utc = self.utc_date_time_from_native(native); + self.timestamp_from_utc_with_time(&utc, &time)? + } else { + date_timestamp + } + }, + _ => date_timestamp, + }, + }; + let date_cell_data = DateCellData(Some(cell_data)); + Ok((date_cell_data.to_string(), date_cell_data)) + } } impl TypeOptionCellDataFilter for DateTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_date() { - return true; - } - - filter.is_visible(cell_data.0) + fn apply_filter( + &self, + filter: &::CellFilter, + field_type: &FieldType, + cell_data: &::CellData, + ) -> bool { + if !field_type.is_date() { + return true; } + + filter.is_visible(cell_data.0) + } } impl TypeOptionCellDataCompare for DateTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - match (cell_data.0, other_cell_data.0) { - (Some(left), Some(right)) => left.cmp(&right), - (Some(_), None) => Ordering::Greater, - (None, Some(_)) => Ordering::Less, - (None, None) => default_order(), - } + fn apply_cmp( + &self, + cell_data: &::CellData, + other_cell_data: &::CellData, + ) -> Ordering { + match (cell_data.0, other_cell_data.0) { + (Some(left), Some(right)) => left.cmp(&right), + (Some(_), None) => Ordering::Greater, + (None, Some(_)) => Ordering::Less, + (None, None) => default_order(), } + } } #[derive(Default)] @@ -219,22 +240,22 @@ impl_into_box_type_option_builder!(DateTypeOptionBuilder); impl_builder_from_json_str_and_from_bytes!(DateTypeOptionBuilder, DateTypeOptionPB); impl DateTypeOptionBuilder { - pub fn date_format(mut self, date_format: DateFormat) -> Self { - self.0.date_format = date_format; - self - } + pub fn date_format(mut self, date_format: DateFormat) -> Self { + self.0.date_format = date_format; + self + } - pub fn time_format(mut self, time_format: TimeFormat) -> Self { - self.0.time_format = time_format; - self - } + pub fn time_format(mut self, time_format: TimeFormat) -> Self { + self.0.time_format = time_format; + self + } } impl TypeOptionBuilder for DateTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::DateTime - } + fn field_type(&self) -> FieldType { + FieldType::DateTime + } - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } + fn serializer(&self) -> &dyn TypeOptionDataSerializer { + &self.0 + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option_entities.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option_entities.rs index 0de9c277f0..fd11207bef 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option_entities.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option_entities.rs @@ -1,6 +1,7 @@ use crate::entities::CellIdPB; use crate::services::cell::{ - CellProtobufBlobParser, DecodedCellData, FromCellChangesetString, FromCellString, ToCellChangesetString, + CellProtobufBlobParser, DecodedCellData, FromCellChangesetString, FromCellString, + ToCellChangesetString, }; use bytes::Bytes; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; @@ -10,195 +11,197 @@ use strum_macros::EnumIter; #[derive(Clone, Debug, Default, ProtoBuf)] pub struct DateCellDataPB { - #[pb(index = 1)] - pub date: String, + #[pb(index = 1)] + pub date: String, - #[pb(index = 2)] - pub time: String, + #[pb(index = 2)] + pub time: String, - #[pb(index = 3)] - pub timestamp: i64, + #[pb(index = 3)] + pub timestamp: i64, } #[derive(Clone, Debug, Default, ProtoBuf)] pub struct DateChangesetPB { - #[pb(index = 1)] - pub cell_path: CellIdPB, + #[pb(index = 1)] + pub cell_path: CellIdPB, - #[pb(index = 2, one_of)] - pub date: Option, + #[pb(index = 2, one_of)] + pub date: Option, - #[pb(index = 3, one_of)] - pub time: Option, + #[pb(index = 3, one_of)] + pub time: Option, - #[pb(index = 4)] - pub is_utc: bool, + #[pb(index = 4)] + pub is_utc: bool, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct DateCellChangeset { - pub date: Option, - pub time: Option, - pub is_utc: bool, + pub date: Option, + pub time: Option, + pub is_utc: bool, } impl DateCellChangeset { - pub fn date_timestamp(&self) -> Option { - if let Some(date) = &self.date { - match date.parse::() { - Ok(date_timestamp) => Some(date_timestamp), - Err(_) => None, - } - } else { - None - } + pub fn date_timestamp(&self) -> Option { + if let Some(date) = &self.date { + match date.parse::() { + Ok(date_timestamp) => Some(date_timestamp), + Err(_) => None, + } + } else { + None } + } } impl FromCellChangesetString for DateCellChangeset { - fn from_changeset(changeset: String) -> FlowyResult - where - Self: Sized, - { - serde_json::from_str::(&changeset).map_err(internal_error) - } + fn from_changeset(changeset: String) -> FlowyResult + where + Self: Sized, + { + serde_json::from_str::(&changeset).map_err(internal_error) + } } impl ToCellChangesetString for DateCellChangeset { - fn to_cell_changeset_str(&self) -> String { - serde_json::to_string(self).unwrap_or_default() - } + fn to_cell_changeset_str(&self) -> String { + serde_json::to_string(self).unwrap_or_default() + } } #[derive(Default, Clone, Debug)] pub struct DateCellData(pub Option); impl std::convert::From for i64 { - fn from(timestamp: DateCellData) -> Self { - timestamp.0.unwrap_or(0) - } + fn from(timestamp: DateCellData) -> Self { + timestamp.0.unwrap_or(0) + } } impl std::convert::From for Option { - fn from(timestamp: DateCellData) -> Self { - timestamp.0 - } + fn from(timestamp: DateCellData) -> Self { + timestamp.0 + } } impl FromCellString for DateCellData { - fn from_cell_str(s: &str) -> FlowyResult - where - Self: Sized, - { - let num = s.parse::().ok(); - Ok(DateCellData(num)) - } + fn from_cell_str(s: &str) -> FlowyResult + where + Self: Sized, + { + let num = s.parse::().ok(); + Ok(DateCellData(num)) + } } impl ToString for DateCellData { - fn to_string(&self) -> String { - match self.0 { - None => "".to_string(), - Some(val) => val.to_string(), - } + fn to_string(&self) -> String { + match self.0 { + None => "".to_string(), + Some(val) => val.to_string(), } + } } #[derive(Clone, Debug, Copy, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)] pub enum DateFormat { - Local = 0, - US = 1, - ISO = 2, - Friendly = 3, + Local = 0, + US = 1, + ISO = 2, + Friendly = 3, } impl std::default::Default for DateFormat { - fn default() -> Self { - DateFormat::Friendly - } + fn default() -> Self { + DateFormat::Friendly + } } impl std::convert::From for DateFormat { - fn from(value: i32) -> Self { - match value { - 0 => DateFormat::Local, - 1 => DateFormat::US, - 2 => DateFormat::ISO, - 3 => DateFormat::Friendly, - _ => { - tracing::error!("Unsupported date format, fallback to friendly"); - DateFormat::Friendly - } - } + fn from(value: i32) -> Self { + match value { + 0 => DateFormat::Local, + 1 => DateFormat::US, + 2 => DateFormat::ISO, + 3 => DateFormat::Friendly, + _ => { + tracing::error!("Unsupported date format, fallback to friendly"); + DateFormat::Friendly + }, } + } } impl DateFormat { - pub fn value(&self) -> i32 { - *self as i32 - } - // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html - pub fn format_str(&self) -> &'static str { - match self { - DateFormat::Local => "%m/%d/%Y", - DateFormat::US => "%Y/%m/%d", - DateFormat::ISO => "%Y-%m-%d", - DateFormat::Friendly => "%b %d,%Y", - } + pub fn value(&self) -> i32 { + *self as i32 + } + // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html + pub fn format_str(&self) -> &'static str { + match self { + DateFormat::Local => "%m/%d/%Y", + DateFormat::US => "%Y/%m/%d", + DateFormat::ISO => "%Y-%m-%d", + DateFormat::Friendly => "%b %d,%Y", } + } } -#[derive(Clone, Copy, PartialEq, Eq, EnumIter, Debug, Hash, Serialize, Deserialize, ProtoBuf_Enum)] +#[derive( + Clone, Copy, PartialEq, Eq, EnumIter, Debug, Hash, Serialize, Deserialize, ProtoBuf_Enum, +)] pub enum TimeFormat { - TwelveHour = 0, - TwentyFourHour = 1, + TwelveHour = 0, + TwentyFourHour = 1, } impl std::convert::From for TimeFormat { - fn from(value: i32) -> Self { - match value { - 0 => TimeFormat::TwelveHour, - 1 => TimeFormat::TwentyFourHour, - _ => { - tracing::error!("Unsupported time format, fallback to TwentyFourHour"); - TimeFormat::TwentyFourHour - } - } + fn from(value: i32) -> Self { + match value { + 0 => TimeFormat::TwelveHour, + 1 => TimeFormat::TwentyFourHour, + _ => { + tracing::error!("Unsupported time format, fallback to TwentyFourHour"); + TimeFormat::TwentyFourHour + }, } + } } impl TimeFormat { - pub fn value(&self) -> i32 { - *self as i32 - } + pub fn value(&self) -> i32 { + *self as i32 + } - // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html - pub fn format_str(&self) -> &'static str { - match self { - TimeFormat::TwelveHour => "%I:%M %p", - TimeFormat::TwentyFourHour => "%R", - } + // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html + pub fn format_str(&self) -> &'static str { + match self { + TimeFormat::TwelveHour => "%I:%M %p", + TimeFormat::TwentyFourHour => "%R", } + } } impl std::default::Default for TimeFormat { - fn default() -> Self { - TimeFormat::TwentyFourHour - } + fn default() -> Self { + TimeFormat::TwentyFourHour + } } impl DecodedCellData for DateCellDataPB { - type Object = DateCellDataPB; + type Object = DateCellDataPB; - fn is_empty(&self) -> bool { - self.date.is_empty() - } + fn is_empty(&self) -> bool { + self.date.is_empty() + } } pub struct DateCellDataParser(); impl CellProtobufBlobParser for DateCellDataParser { - type Object = DateCellDataPB; + type Object = DateCellDataPB; - fn parser(bytes: &Bytes) -> FlowyResult { - DateCellDataPB::try_from(bytes.as_ref()).map_err(internal_error) - } + fn parser(bytes: &Bytes) -> FlowyResult { + DateCellDataPB::try_from(bytes.as_ref()).map_err(internal_error) + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/format.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/format.rs index 0a8029e869..00d0c7ad8e 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/format.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/format.rs @@ -7,56 +7,56 @@ use strum::IntoEnumIterator; use strum_macros::EnumIter; lazy_static! { - pub static ref CURRENCY_SYMBOL: Vec = NumberFormat::iter() - .map(|format| format.symbol()) - .collect::>(); - pub static ref STRIP_SYMBOL: Vec = vec![",".to_owned(), ".".to_owned()]; + pub static ref CURRENCY_SYMBOL: Vec = NumberFormat::iter() + .map(|format| format.symbol()) + .collect::>(); + pub static ref STRIP_SYMBOL: Vec = vec![",".to_owned(), ".".to_owned()]; } #[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)] pub enum NumberFormat { - Num = 0, - USD = 1, - CanadianDollar = 2, - EUR = 4, - Pound = 5, - Yen = 6, - Ruble = 7, - Rupee = 8, - Won = 9, - Yuan = 10, - Real = 11, - Lira = 12, - Rupiah = 13, - Franc = 14, - HongKongDollar = 15, - NewZealandDollar = 16, - Krona = 17, - NorwegianKrone = 18, - MexicanPeso = 19, - Rand = 20, - NewTaiwanDollar = 21, - DanishKrone = 22, - Baht = 23, - Forint = 24, - Koruna = 25, - Shekel = 26, - ChileanPeso = 27, - PhilippinePeso = 28, - Dirham = 29, - ColombianPeso = 30, - Riyal = 31, - Ringgit = 32, - Leu = 33, - ArgentinePeso = 34, - UruguayanPeso = 35, - Percent = 36, + Num = 0, + USD = 1, + CanadianDollar = 2, + EUR = 4, + Pound = 5, + Yen = 6, + Ruble = 7, + Rupee = 8, + Won = 9, + Yuan = 10, + Real = 11, + Lira = 12, + Rupiah = 13, + Franc = 14, + HongKongDollar = 15, + NewZealandDollar = 16, + Krona = 17, + NorwegianKrone = 18, + MexicanPeso = 19, + Rand = 20, + NewTaiwanDollar = 21, + DanishKrone = 22, + Baht = 23, + Forint = 24, + Koruna = 25, + Shekel = 26, + ChileanPeso = 27, + PhilippinePeso = 28, + Dirham = 29, + ColombianPeso = 30, + Riyal = 31, + Ringgit = 32, + Leu = 33, + ArgentinePeso = 34, + UruguayanPeso = 35, + Percent = 36, } impl std::default::Default for NumberFormat { - fn default() -> Self { - NumberFormat::Num - } + fn default() -> Self { + NumberFormat::Num + } } define_currency_set!( @@ -407,48 +407,48 @@ define_currency_set!( ); impl NumberFormat { - pub fn currency(&self) -> &'static number_currency::Currency { - match self { - NumberFormat::Num => number_currency::NUMBER, - NumberFormat::USD => number_currency::USD, - NumberFormat::CanadianDollar => number_currency::CANADIAN_DOLLAR, - NumberFormat::EUR => number_currency::EUR, - NumberFormat::Pound => number_currency::GIP, - NumberFormat::Yen => number_currency::CNY, - NumberFormat::Ruble => number_currency::RUB, - NumberFormat::Rupee => number_currency::INR, - NumberFormat::Won => number_currency::KRW, - NumberFormat::Yuan => number_currency::YUAN, - NumberFormat::Real => number_currency::BRL, - NumberFormat::Lira => number_currency::TRY, - NumberFormat::Rupiah => number_currency::IDR, - NumberFormat::Franc => number_currency::CHF, - NumberFormat::HongKongDollar => number_currency::HONG_KONG_DOLLAR, - NumberFormat::NewZealandDollar => number_currency::NEW_ZEALAND_DOLLAR, - NumberFormat::Krona => number_currency::SEK, - NumberFormat::NorwegianKrone => number_currency::NOK, - NumberFormat::MexicanPeso => number_currency::MEXICAN_PESO, - NumberFormat::Rand => number_currency::ZAR, - NumberFormat::NewTaiwanDollar => number_currency::NEW_TAIWAN_DOLLAR, - NumberFormat::DanishKrone => number_currency::DKK, - NumberFormat::Baht => number_currency::THB, - NumberFormat::Forint => number_currency::HUF, - NumberFormat::Koruna => number_currency::KORUNA, - NumberFormat::Shekel => number_currency::SHEKEL, - NumberFormat::ChileanPeso => number_currency::CLP, - NumberFormat::PhilippinePeso => number_currency::PHP, - NumberFormat::Dirham => number_currency::AED, - NumberFormat::ColombianPeso => number_currency::COP, - NumberFormat::Riyal => number_currency::SAR, - NumberFormat::Ringgit => number_currency::MYR, - NumberFormat::Leu => number_currency::RON, - NumberFormat::ArgentinePeso => number_currency::ARS, - NumberFormat::UruguayanPeso => number_currency::UYU, - NumberFormat::Percent => number_currency::PERCENT, - } + pub fn currency(&self) -> &'static number_currency::Currency { + match self { + NumberFormat::Num => number_currency::NUMBER, + NumberFormat::USD => number_currency::USD, + NumberFormat::CanadianDollar => number_currency::CANADIAN_DOLLAR, + NumberFormat::EUR => number_currency::EUR, + NumberFormat::Pound => number_currency::GIP, + NumberFormat::Yen => number_currency::CNY, + NumberFormat::Ruble => number_currency::RUB, + NumberFormat::Rupee => number_currency::INR, + NumberFormat::Won => number_currency::KRW, + NumberFormat::Yuan => number_currency::YUAN, + NumberFormat::Real => number_currency::BRL, + NumberFormat::Lira => number_currency::TRY, + NumberFormat::Rupiah => number_currency::IDR, + NumberFormat::Franc => number_currency::CHF, + NumberFormat::HongKongDollar => number_currency::HONG_KONG_DOLLAR, + NumberFormat::NewZealandDollar => number_currency::NEW_ZEALAND_DOLLAR, + NumberFormat::Krona => number_currency::SEK, + NumberFormat::NorwegianKrone => number_currency::NOK, + NumberFormat::MexicanPeso => number_currency::MEXICAN_PESO, + NumberFormat::Rand => number_currency::ZAR, + NumberFormat::NewTaiwanDollar => number_currency::NEW_TAIWAN_DOLLAR, + NumberFormat::DanishKrone => number_currency::DKK, + NumberFormat::Baht => number_currency::THB, + NumberFormat::Forint => number_currency::HUF, + NumberFormat::Koruna => number_currency::KORUNA, + NumberFormat::Shekel => number_currency::SHEKEL, + NumberFormat::ChileanPeso => number_currency::CLP, + NumberFormat::PhilippinePeso => number_currency::PHP, + NumberFormat::Dirham => number_currency::AED, + NumberFormat::ColombianPeso => number_currency::COP, + NumberFormat::Riyal => number_currency::SAR, + NumberFormat::Ringgit => number_currency::MYR, + NumberFormat::Leu => number_currency::RON, + NumberFormat::ArgentinePeso => number_currency::ARS, + NumberFormat::UruguayanPeso => number_currency::UYU, + NumberFormat::Percent => number_currency::PERCENT, } + } - pub fn symbol(&self) -> String { - self.currency().symbol.to_string() - } + pub fn symbol(&self) -> String { + self.currency().symbol.to_string() + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_filter.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_filter.rs index fecc381fb7..3e2058be97 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_filter.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_filter.rs @@ -7,79 +7,79 @@ use rust_decimal::Decimal; use std::str::FromStr; impl NumberFilterPB { - pub fn is_visible(&self, num_cell_data: &NumberCellData) -> bool { - if self.content.is_empty() { - match self.condition { - NumberFilterConditionPB::NumberIsEmpty => { - return num_cell_data.is_empty(); - } - NumberFilterConditionPB::NumberIsNotEmpty => { - return !num_cell_data.is_empty(); - } - _ => {} - } - } - match num_cell_data.decimal().as_ref() { - None => false, - Some(cell_decimal) => { - let decimal = Decimal::from_str(&self.content).unwrap_or_else(|_| Decimal::zero()); - match self.condition { - NumberFilterConditionPB::Equal => cell_decimal == &decimal, - NumberFilterConditionPB::NotEqual => cell_decimal != &decimal, - NumberFilterConditionPB::GreaterThan => cell_decimal > &decimal, - NumberFilterConditionPB::LessThan => cell_decimal < &decimal, - NumberFilterConditionPB::GreaterThanOrEqualTo => cell_decimal >= &decimal, - NumberFilterConditionPB::LessThanOrEqualTo => cell_decimal <= &decimal, - _ => true, - } - } - } + pub fn is_visible(&self, num_cell_data: &NumberCellData) -> bool { + if self.content.is_empty() { + match self.condition { + NumberFilterConditionPB::NumberIsEmpty => { + return num_cell_data.is_empty(); + }, + NumberFilterConditionPB::NumberIsNotEmpty => { + return !num_cell_data.is_empty(); + }, + _ => {}, + } } + match num_cell_data.decimal().as_ref() { + None => false, + Some(cell_decimal) => { + let decimal = Decimal::from_str(&self.content).unwrap_or_else(|_| Decimal::zero()); + match self.condition { + NumberFilterConditionPB::Equal => cell_decimal == &decimal, + NumberFilterConditionPB::NotEqual => cell_decimal != &decimal, + NumberFilterConditionPB::GreaterThan => cell_decimal > &decimal, + NumberFilterConditionPB::LessThan => cell_decimal < &decimal, + NumberFilterConditionPB::GreaterThanOrEqualTo => cell_decimal >= &decimal, + NumberFilterConditionPB::LessThanOrEqualTo => cell_decimal <= &decimal, + _ => true, + } + }, + } + } } #[cfg(test)] mod tests { - use crate::entities::{NumberFilterConditionPB, NumberFilterPB}; - use crate::services::field::{NumberCellData, NumberFormat}; - #[test] - fn number_filter_equal_test() { - let number_filter = NumberFilterPB { - condition: NumberFilterConditionPB::Equal, - content: "123".to_owned(), - }; + use crate::entities::{NumberFilterConditionPB, NumberFilterPB}; + use crate::services::field::{NumberCellData, NumberFormat}; + #[test] + fn number_filter_equal_test() { + let number_filter = NumberFilterPB { + condition: NumberFilterConditionPB::Equal, + content: "123".to_owned(), + }; - for (num_str, visible) in [("123", true), ("1234", false), ("", false)] { - let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); - assert_eq!(number_filter.is_visible(&data), visible); - } - - let format = NumberFormat::USD; - for (num_str, visible) in [("$123", true), ("1234", false), ("", false)] { - let data = NumberCellData::from_format_str(num_str, true, &format).unwrap(); - assert_eq!(number_filter.is_visible(&data), visible); - } - } - #[test] - fn number_filter_greater_than_test() { - let number_filter = NumberFilterPB { - condition: NumberFilterConditionPB::GreaterThan, - content: "12".to_owned(), - }; - for (num_str, visible) in [("123", true), ("10", false), ("30", true), ("", false)] { - let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); - assert_eq!(number_filter.is_visible(&data), visible); - } + for (num_str, visible) in [("123", true), ("1234", false), ("", false)] { + let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); + assert_eq!(number_filter.is_visible(&data), visible); } - #[test] - fn number_filter_less_than_test() { - let number_filter = NumberFilterPB { - condition: NumberFilterConditionPB::LessThan, - content: "100".to_owned(), - }; - for (num_str, visible) in [("12", true), ("1234", false), ("30", true), ("", false)] { - let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); - assert_eq!(number_filter.is_visible(&data), visible); - } + let format = NumberFormat::USD; + for (num_str, visible) in [("$123", true), ("1234", false), ("", false)] { + let data = NumberCellData::from_format_str(num_str, true, &format).unwrap(); + assert_eq!(number_filter.is_visible(&data), visible); } + } + #[test] + fn number_filter_greater_than_test() { + let number_filter = NumberFilterPB { + condition: NumberFilterConditionPB::GreaterThan, + content: "12".to_owned(), + }; + for (num_str, visible) in [("123", true), ("10", false), ("30", true), ("", false)] { + let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); + assert_eq!(number_filter.is_visible(&data), visible); + } + } + + #[test] + fn number_filter_less_than_test() { + let number_filter = NumberFilterPB { + condition: NumberFilterConditionPB::LessThan, + content: "100".to_owned(), + }; + for (num_str, visible) in [("12", true), ("1234", false), ("30", true), ("", false)] { + let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); + assert_eq!(number_filter.is_visible(&data), visible); + } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_tests.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_tests.rs index ac73756264..2b7d00fe6c 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_tests.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_tests.rs @@ -1,448 +1,668 @@ #[cfg(test)] mod tests { - use crate::entities::FieldType; - use crate::services::cell::CellDataDecoder; - use crate::services::field::FieldBuilder; + use crate::entities::FieldType; + use crate::services::cell::CellDataDecoder; + use crate::services::field::FieldBuilder; - use crate::services::field::{strip_currency_symbol, NumberFormat, NumberTypeOptionPB}; - use grid_model::FieldRevision; - use strum::IntoEnumIterator; + use crate::services::field::{strip_currency_symbol, NumberFormat, NumberTypeOptionPB}; + use grid_model::FieldRevision; + use strum::IntoEnumIterator; - /// Testing when the input is not a number. - #[test] - fn number_type_option_invalid_input_test() { - let type_option = NumberTypeOptionPB::default(); - let field_type = FieldType::Number; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); + /// Testing when the input is not a number. + #[test] + fn number_type_option_invalid_input_test() { + let type_option = NumberTypeOptionPB::default(); + let field_type = FieldType::Number; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); - // Input is empty String - assert_number(&type_option, "", "", &field_type, &field_rev); + // Input is empty String + assert_number(&type_option, "", "", &field_type, &field_rev); - // Input is letter - assert_number(&type_option, "abc", "", &field_type, &field_rev); + // Input is letter + assert_number(&type_option, "abc", "", &field_type, &field_rev); + } + + /// Testing the strip_currency_symbol function. It should return the string without the input symbol. + #[test] + fn number_type_option_strip_symbol_test() { + // Remove the $ symbol + assert_eq!(strip_currency_symbol("$18,443"), "18,443".to_owned()); + // Remove the ¥ symbol + assert_eq!(strip_currency_symbol("¥0.2"), "0.2".to_owned()); + } + + /// Format the input number to the corresponding format string. + #[test] + fn number_type_option_format_number_test() { + let mut type_option = NumberTypeOptionPB::default(); + let field_type = FieldType::Number; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + + for format in NumberFormat::iter() { + type_option.format = format; + match format { + NumberFormat::Num => { + assert_number(&type_option, "18443", "18443", &field_type, &field_rev); + }, + NumberFormat::USD => { + assert_number(&type_option, "18443", "$18,443", &field_type, &field_rev); + }, + NumberFormat::CanadianDollar => { + assert_number(&type_option, "18443", "CA$18,443", &field_type, &field_rev) + }, + NumberFormat::EUR => { + assert_number(&type_option, "18443", "€18.443", &field_type, &field_rev) + }, + NumberFormat::Pound => { + assert_number(&type_option, "18443", "£18,443", &field_type, &field_rev) + }, + + NumberFormat::Yen => { + assert_number(&type_option, "18443", "¥18,443", &field_type, &field_rev); + }, + NumberFormat::Ruble => { + assert_number(&type_option, "18443", "18.443RUB", &field_type, &field_rev) + }, + NumberFormat::Rupee => { + assert_number(&type_option, "18443", "₹18,443", &field_type, &field_rev) + }, + NumberFormat::Won => { + assert_number(&type_option, "18443", "₩18,443", &field_type, &field_rev) + }, + + NumberFormat::Yuan => { + assert_number(&type_option, "18443", "CN¥18,443", &field_type, &field_rev); + }, + NumberFormat::Real => { + assert_number(&type_option, "18443", "R$18,443", &field_type, &field_rev); + }, + NumberFormat::Lira => { + assert_number(&type_option, "18443", "TRY18.443", &field_type, &field_rev) + }, + NumberFormat::Rupiah => { + assert_number(&type_option, "18443", "IDR18,443", &field_type, &field_rev) + }, + NumberFormat::Franc => { + assert_number(&type_option, "18443", "CHF18,443", &field_type, &field_rev) + }, + NumberFormat::HongKongDollar => { + assert_number(&type_option, "18443", "HZ$18,443", &field_type, &field_rev) + }, + NumberFormat::NewZealandDollar => { + assert_number(&type_option, "18443", "NZ$18,443", &field_type, &field_rev) + }, + NumberFormat::Krona => { + assert_number(&type_option, "18443", "18 443SEK", &field_type, &field_rev) + }, + NumberFormat::NorwegianKrone => { + assert_number(&type_option, "18443", "18,443NOK", &field_type, &field_rev) + }, + NumberFormat::MexicanPeso => { + assert_number(&type_option, "18443", "MX$18,443", &field_type, &field_rev) + }, + NumberFormat::Rand => { + assert_number(&type_option, "18443", "ZAR18,443", &field_type, &field_rev) + }, + NumberFormat::NewTaiwanDollar => { + assert_number(&type_option, "18443", "NT$18,443", &field_type, &field_rev) + }, + NumberFormat::DanishKrone => { + assert_number(&type_option, "18443", "18.443DKK", &field_type, &field_rev) + }, + NumberFormat::Baht => { + assert_number(&type_option, "18443", "THB18,443", &field_type, &field_rev) + }, + NumberFormat::Forint => { + assert_number(&type_option, "18443", "18 443HUF", &field_type, &field_rev) + }, + NumberFormat::Koruna => { + assert_number(&type_option, "18443", "18 443CZK", &field_type, &field_rev) + }, + NumberFormat::Shekel => { + assert_number(&type_option, "18443", "18 443Kč", &field_type, &field_rev) + }, + NumberFormat::ChileanPeso => { + assert_number(&type_option, "18443", "CLP18.443", &field_type, &field_rev) + }, + NumberFormat::PhilippinePeso => { + assert_number(&type_option, "18443", "₱18,443", &field_type, &field_rev) + }, + NumberFormat::Dirham => { + assert_number(&type_option, "18443", "18,443AED", &field_type, &field_rev) + }, + NumberFormat::ColombianPeso => { + assert_number(&type_option, "18443", "COP18.443", &field_type, &field_rev) + }, + NumberFormat::Riyal => { + assert_number(&type_option, "18443", "SAR18,443", &field_type, &field_rev) + }, + NumberFormat::Ringgit => { + assert_number(&type_option, "18443", "MYR18,443", &field_type, &field_rev) + }, + NumberFormat::Leu => { + assert_number(&type_option, "18443", "18.443RON", &field_type, &field_rev) + }, + NumberFormat::ArgentinePeso => { + assert_number(&type_option, "18443", "ARS18.443", &field_type, &field_rev) + }, + NumberFormat::UruguayanPeso => { + assert_number(&type_option, "18443", "UYU18.443", &field_type, &field_rev) + }, + NumberFormat::Percent => { + assert_number(&type_option, "18443", "18,443%", &field_type, &field_rev) + }, + } } + } - /// Testing the strip_currency_symbol function. It should return the string without the input symbol. - #[test] - fn number_type_option_strip_symbol_test() { - // Remove the $ symbol - assert_eq!(strip_currency_symbol("$18,443"), "18,443".to_owned()); - // Remove the ¥ symbol - assert_eq!(strip_currency_symbol("¥0.2"), "0.2".to_owned()); + /// Format the input String to the corresponding format string. + #[test] + fn number_type_option_format_str_test() { + let mut type_option = NumberTypeOptionPB::default(); + let field_type = FieldType::Number; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + + for format in NumberFormat::iter() { + type_option.format = format; + match format { + NumberFormat::Num => { + assert_number(&type_option, "18443", "18443", &field_type, &field_rev); + assert_number(&type_option, "0.2", "0.2", &field_type, &field_rev); + assert_number(&type_option, "", "", &field_type, &field_rev); + assert_number(&type_option, "abc", "", &field_type, &field_rev); + }, + NumberFormat::USD => { + assert_number(&type_option, "$18,44", "$1,844", &field_type, &field_rev); + assert_number(&type_option, "$0.2", "$0.2", &field_type, &field_rev); + assert_number(&type_option, "$1844", "$1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "$1,844", &field_type, &field_rev); + }, + NumberFormat::CanadianDollar => { + assert_number( + &type_option, + "CA$18,44", + "CA$1,844", + &field_type, + &field_rev, + ); + assert_number(&type_option, "CA$0.2", "CA$0.2", &field_type, &field_rev); + assert_number(&type_option, "CA$1844", "CA$1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "CA$1,844", &field_type, &field_rev); + }, + NumberFormat::EUR => { + assert_number(&type_option, "€18.44", "€18,44", &field_type, &field_rev); + assert_number(&type_option, "€0.5", "€0,5", &field_type, &field_rev); + assert_number(&type_option, "€1844", "€1.844", &field_type, &field_rev); + assert_number(&type_option, "1844", "€1.844", &field_type, &field_rev); + }, + NumberFormat::Pound => { + assert_number(&type_option, "£18,44", "£1,844", &field_type, &field_rev); + assert_number(&type_option, "£0.2", "£0.2", &field_type, &field_rev); + assert_number(&type_option, "£1844", "£1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "£1,844", &field_type, &field_rev); + }, + NumberFormat::Yen => { + assert_number(&type_option, "¥18,44", "¥1,844", &field_type, &field_rev); + assert_number(&type_option, "¥0.2", "¥0.2", &field_type, &field_rev); + assert_number(&type_option, "¥1844", "¥1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "¥1,844", &field_type, &field_rev); + }, + NumberFormat::Ruble => { + assert_number( + &type_option, + "RUB18.44", + "18,44RUB", + &field_type, + &field_rev, + ); + assert_number(&type_option, "0.5", "0,5RUB", &field_type, &field_rev); + assert_number(&type_option, "RUB1844", "1.844RUB", &field_type, &field_rev); + assert_number(&type_option, "1844", "1.844RUB", &field_type, &field_rev); + }, + NumberFormat::Rupee => { + assert_number(&type_option, "₹18,44", "₹1,844", &field_type, &field_rev); + assert_number(&type_option, "₹0.2", "₹0.2", &field_type, &field_rev); + assert_number(&type_option, "₹1844", "₹1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "₹1,844", &field_type, &field_rev); + }, + NumberFormat::Won => { + assert_number(&type_option, "₩18,44", "₩1,844", &field_type, &field_rev); + assert_number(&type_option, "₩0.3", "₩0", &field_type, &field_rev); + assert_number(&type_option, "₩1844", "₩1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "₩1,844", &field_type, &field_rev); + }, + NumberFormat::Yuan => { + assert_number( + &type_option, + "CN¥18,44", + "CN¥1,844", + &field_type, + &field_rev, + ); + assert_number(&type_option, "CN¥0.2", "CN¥0.2", &field_type, &field_rev); + assert_number(&type_option, "CN¥1844", "CN¥1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "CN¥1,844", &field_type, &field_rev); + }, + NumberFormat::Real => { + assert_number(&type_option, "R$18,44", "R$1,844", &field_type, &field_rev); + assert_number(&type_option, "R$0.2", "R$0.2", &field_type, &field_rev); + assert_number(&type_option, "R$1844", "R$1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "R$1,844", &field_type, &field_rev); + }, + NumberFormat::Lira => { + assert_number( + &type_option, + "TRY18.44", + "TRY18,44", + &field_type, + &field_rev, + ); + assert_number(&type_option, "TRY0.5", "TRY0,5", &field_type, &field_rev); + assert_number(&type_option, "TRY1844", "TRY1.844", &field_type, &field_rev); + assert_number(&type_option, "1844", "TRY1.844", &field_type, &field_rev); + }, + NumberFormat::Rupiah => { + assert_number( + &type_option, + "IDR18,44", + "IDR1,844", + &field_type, + &field_rev, + ); + assert_number(&type_option, "IDR0.2", "IDR0.2", &field_type, &field_rev); + assert_number(&type_option, "IDR1844", "IDR1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "IDR1,844", &field_type, &field_rev); + }, + NumberFormat::Franc => { + assert_number( + &type_option, + "CHF18,44", + "CHF1,844", + &field_type, + &field_rev, + ); + assert_number(&type_option, "CHF0.2", "CHF0.2", &field_type, &field_rev); + assert_number(&type_option, "CHF1844", "CHF1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "CHF1,844", &field_type, &field_rev); + }, + NumberFormat::HongKongDollar => { + assert_number( + &type_option, + "HZ$18,44", + "HZ$1,844", + &field_type, + &field_rev, + ); + assert_number(&type_option, "HZ$0.2", "HZ$0.2", &field_type, &field_rev); + assert_number(&type_option, "HZ$1844", "HZ$1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "HZ$1,844", &field_type, &field_rev); + }, + NumberFormat::NewZealandDollar => { + assert_number( + &type_option, + "NZ$18,44", + "NZ$1,844", + &field_type, + &field_rev, + ); + assert_number(&type_option, "NZ$0.2", "NZ$0.2", &field_type, &field_rev); + assert_number(&type_option, "NZ$1844", "NZ$1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "NZ$1,844", &field_type, &field_rev); + }, + NumberFormat::Krona => { + assert_number( + &type_option, + "SEK18,44", + "18,44SEK", + &field_type, + &field_rev, + ); + assert_number(&type_option, "SEK0.2", "0,2SEK", &field_type, &field_rev); + assert_number(&type_option, "SEK1844", "1 844SEK", &field_type, &field_rev); + assert_number(&type_option, "1844", "1 844SEK", &field_type, &field_rev); + }, + NumberFormat::NorwegianKrone => { + assert_number( + &type_option, + "NOK18,44", + "1,844NOK", + &field_type, + &field_rev, + ); + assert_number(&type_option, "NOK0.2", "0.2NOK", &field_type, &field_rev); + assert_number(&type_option, "NOK1844", "1,844NOK", &field_type, &field_rev); + assert_number(&type_option, "1844", "1,844NOK", &field_type, &field_rev); + }, + NumberFormat::MexicanPeso => { + assert_number( + &type_option, + "MX$18,44", + "MX$1,844", + &field_type, + &field_rev, + ); + assert_number(&type_option, "MX$0.2", "MX$0.2", &field_type, &field_rev); + assert_number(&type_option, "MX$1844", "MX$1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "MX$1,844", &field_type, &field_rev); + }, + NumberFormat::Rand => { + assert_number( + &type_option, + "ZAR18,44", + "ZAR1,844", + &field_type, + &field_rev, + ); + assert_number(&type_option, "ZAR0.2", "ZAR0.2", &field_type, &field_rev); + assert_number(&type_option, "ZAR1844", "ZAR1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "ZAR1,844", &field_type, &field_rev); + }, + NumberFormat::NewTaiwanDollar => { + assert_number( + &type_option, + "NT$18,44", + "NT$1,844", + &field_type, + &field_rev, + ); + assert_number(&type_option, "NT$0.2", "NT$0.2", &field_type, &field_rev); + assert_number(&type_option, "NT$1844", "NT$1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "NT$1,844", &field_type, &field_rev); + }, + NumberFormat::DanishKrone => { + assert_number( + &type_option, + "DKK18.44", + "18,44DKK", + &field_type, + &field_rev, + ); + assert_number(&type_option, "DKK0.5", "0,5DKK", &field_type, &field_rev); + assert_number(&type_option, "DKK1844", "1.844DKK", &field_type, &field_rev); + assert_number(&type_option, "1844", "1.844DKK", &field_type, &field_rev); + }, + NumberFormat::Baht => { + assert_number( + &type_option, + "THB18,44", + "THB1,844", + &field_type, + &field_rev, + ); + assert_number(&type_option, "THB0.2", "THB0.2", &field_type, &field_rev); + assert_number(&type_option, "THB1844", "THB1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "THB1,844", &field_type, &field_rev); + }, + NumberFormat::Forint => { + assert_number(&type_option, "HUF18,44", "18HUF", &field_type, &field_rev); + assert_number(&type_option, "HUF0.3", "0HUF", &field_type, &field_rev); + assert_number(&type_option, "HUF1844", "1 844HUF", &field_type, &field_rev); + assert_number(&type_option, "1844", "1 844HUF", &field_type, &field_rev); + }, + NumberFormat::Koruna => { + assert_number( + &type_option, + "CZK18,44", + "18,44CZK", + &field_type, + &field_rev, + ); + assert_number(&type_option, "CZK0.2", "0,2CZK", &field_type, &field_rev); + assert_number(&type_option, "CZK1844", "1 844CZK", &field_type, &field_rev); + assert_number(&type_option, "1844", "1 844CZK", &field_type, &field_rev); + }, + NumberFormat::Shekel => { + assert_number(&type_option, "Kč18,44", "18,44Kč", &field_type, &field_rev); + assert_number(&type_option, "Kč0.2", "0,2Kč", &field_type, &field_rev); + assert_number(&type_option, "Kč1844", "1 844Kč", &field_type, &field_rev); + assert_number(&type_option, "1844", "1 844Kč", &field_type, &field_rev); + }, + NumberFormat::ChileanPeso => { + assert_number(&type_option, "CLP18.44", "CLP18", &field_type, &field_rev); + assert_number(&type_option, "0.5", "CLP0", &field_type, &field_rev); + assert_number(&type_option, "CLP1844", "CLP1.844", &field_type, &field_rev); + assert_number(&type_option, "1844", "CLP1.844", &field_type, &field_rev); + }, + NumberFormat::PhilippinePeso => { + assert_number(&type_option, "₱18,44", "₱1,844", &field_type, &field_rev); + assert_number(&type_option, "₱0.2", "₱0.2", &field_type, &field_rev); + assert_number(&type_option, "₱1844", "₱1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "₱1,844", &field_type, &field_rev); + }, + NumberFormat::Dirham => { + assert_number( + &type_option, + "AED18,44", + "1,844AED", + &field_type, + &field_rev, + ); + assert_number(&type_option, "AED0.2", "0.2AED", &field_type, &field_rev); + assert_number(&type_option, "AED1844", "1,844AED", &field_type, &field_rev); + assert_number(&type_option, "1844", "1,844AED", &field_type, &field_rev); + }, + NumberFormat::ColombianPeso => { + assert_number( + &type_option, + "COP18.44", + "COP18,44", + &field_type, + &field_rev, + ); + assert_number(&type_option, "0.5", "COP0,5", &field_type, &field_rev); + assert_number(&type_option, "COP1844", "COP1.844", &field_type, &field_rev); + assert_number(&type_option, "1844", "COP1.844", &field_type, &field_rev); + }, + NumberFormat::Riyal => { + assert_number( + &type_option, + "SAR18,44", + "SAR1,844", + &field_type, + &field_rev, + ); + assert_number(&type_option, "SAR0.2", "SAR0.2", &field_type, &field_rev); + assert_number(&type_option, "SAR1844", "SAR1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "SAR1,844", &field_type, &field_rev); + }, + + NumberFormat::Ringgit => { + assert_number( + &type_option, + "MYR18,44", + "MYR1,844", + &field_type, + &field_rev, + ); + assert_number(&type_option, "MYR0.2", "MYR0.2", &field_type, &field_rev); + assert_number(&type_option, "MYR1844", "MYR1,844", &field_type, &field_rev); + assert_number(&type_option, "1844", "MYR1,844", &field_type, &field_rev); + }, + NumberFormat::Leu => { + assert_number( + &type_option, + "RON18.44", + "18,44RON", + &field_type, + &field_rev, + ); + assert_number(&type_option, "0.5", "0,5RON", &field_type, &field_rev); + assert_number(&type_option, "RON1844", "1.844RON", &field_type, &field_rev); + assert_number(&type_option, "1844", "1.844RON", &field_type, &field_rev); + }, + NumberFormat::ArgentinePeso => { + assert_number( + &type_option, + "ARS18.44", + "ARS18,44", + &field_type, + &field_rev, + ); + assert_number(&type_option, "0.5", "ARS0,5", &field_type, &field_rev); + assert_number(&type_option, "ARS1844", "ARS1.844", &field_type, &field_rev); + assert_number(&type_option, "1844", "ARS1.844", &field_type, &field_rev); + }, + NumberFormat::UruguayanPeso => { + assert_number( + &type_option, + "UYU18.44", + "UYU18,44", + &field_type, + &field_rev, + ); + assert_number(&type_option, "0.5", "UYU0,5", &field_type, &field_rev); + assert_number(&type_option, "UYU1844", "UYU1.844", &field_type, &field_rev); + assert_number(&type_option, "1844", "UYU1.844", &field_type, &field_rev); + }, + NumberFormat::Percent => { + assert_number(&type_option, "1", "1%", &field_type, &field_rev); + assert_number(&type_option, "10.1", "10.1%", &field_type, &field_rev); + assert_number(&type_option, "100", "100%", &field_type, &field_rev); + }, + } } + } - /// Format the input number to the corresponding format string. - #[test] - fn number_type_option_format_number_test() { - let mut type_option = NumberTypeOptionPB::default(); - let field_type = FieldType::Number; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); + /// Carry out the sign positive to input number + #[test] + fn number_description_sign_test() { + let mut type_option = NumberTypeOptionPB { + sign_positive: false, + ..Default::default() + }; + let field_type = FieldType::Number; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); - for format in NumberFormat::iter() { - type_option.format = format; - match format { - NumberFormat::Num => { - assert_number(&type_option, "18443", "18443", &field_type, &field_rev); - } - NumberFormat::USD => { - assert_number(&type_option, "18443", "$18,443", &field_type, &field_rev); - } - NumberFormat::CanadianDollar => { - assert_number(&type_option, "18443", "CA$18,443", &field_type, &field_rev) - } - NumberFormat::EUR => assert_number(&type_option, "18443", "€18.443", &field_type, &field_rev), - NumberFormat::Pound => assert_number(&type_option, "18443", "£18,443", &field_type, &field_rev), + for format in NumberFormat::iter() { + type_option.format = format; + match format { + NumberFormat::Num => { + assert_number(&type_option, "18443", "18443", &field_type, &field_rev); + }, + NumberFormat::USD => { + assert_number(&type_option, "18443", "-$18,443", &field_type, &field_rev); + }, + NumberFormat::CanadianDollar => { + assert_number(&type_option, "18443", "-CA$18,443", &field_type, &field_rev) + }, + NumberFormat::EUR => { + assert_number(&type_option, "18443", "-€18.443", &field_type, &field_rev) + }, + NumberFormat::Pound => { + assert_number(&type_option, "18443", "-£18,443", &field_type, &field_rev) + }, - NumberFormat::Yen => { - assert_number(&type_option, "18443", "¥18,443", &field_type, &field_rev); - } - NumberFormat::Ruble => assert_number(&type_option, "18443", "18.443RUB", &field_type, &field_rev), - NumberFormat::Rupee => assert_number(&type_option, "18443", "₹18,443", &field_type, &field_rev), - NumberFormat::Won => assert_number(&type_option, "18443", "₩18,443", &field_type, &field_rev), + NumberFormat::Yen => { + assert_number(&type_option, "18443", "-¥18,443", &field_type, &field_rev); + }, + NumberFormat::Ruble => { + assert_number(&type_option, "18443", "-18.443RUB", &field_type, &field_rev) + }, + NumberFormat::Rupee => { + assert_number(&type_option, "18443", "-₹18,443", &field_type, &field_rev) + }, + NumberFormat::Won => { + assert_number(&type_option, "18443", "-₩18,443", &field_type, &field_rev) + }, - NumberFormat::Yuan => { - assert_number(&type_option, "18443", "CN¥18,443", &field_type, &field_rev); - } - NumberFormat::Real => { - assert_number(&type_option, "18443", "R$18,443", &field_type, &field_rev); - } - NumberFormat::Lira => assert_number(&type_option, "18443", "TRY18.443", &field_type, &field_rev), - NumberFormat::Rupiah => assert_number(&type_option, "18443", "IDR18,443", &field_type, &field_rev), - NumberFormat::Franc => assert_number(&type_option, "18443", "CHF18,443", &field_type, &field_rev), - NumberFormat::HongKongDollar => { - assert_number(&type_option, "18443", "HZ$18,443", &field_type, &field_rev) - } - NumberFormat::NewZealandDollar => { - assert_number(&type_option, "18443", "NZ$18,443", &field_type, &field_rev) - } - NumberFormat::Krona => assert_number(&type_option, "18443", "18 443SEK", &field_type, &field_rev), - NumberFormat::NorwegianKrone => { - assert_number(&type_option, "18443", "18,443NOK", &field_type, &field_rev) - } - NumberFormat::MexicanPeso => assert_number(&type_option, "18443", "MX$18,443", &field_type, &field_rev), - NumberFormat::Rand => assert_number(&type_option, "18443", "ZAR18,443", &field_type, &field_rev), - NumberFormat::NewTaiwanDollar => { - assert_number(&type_option, "18443", "NT$18,443", &field_type, &field_rev) - } - NumberFormat::DanishKrone => assert_number(&type_option, "18443", "18.443DKK", &field_type, &field_rev), - NumberFormat::Baht => assert_number(&type_option, "18443", "THB18,443", &field_type, &field_rev), - NumberFormat::Forint => assert_number(&type_option, "18443", "18 443HUF", &field_type, &field_rev), - NumberFormat::Koruna => assert_number(&type_option, "18443", "18 443CZK", &field_type, &field_rev), - NumberFormat::Shekel => assert_number(&type_option, "18443", "18 443Kč", &field_type, &field_rev), - NumberFormat::ChileanPeso => assert_number(&type_option, "18443", "CLP18.443", &field_type, &field_rev), - NumberFormat::PhilippinePeso => { - assert_number(&type_option, "18443", "₱18,443", &field_type, &field_rev) - } - NumberFormat::Dirham => assert_number(&type_option, "18443", "18,443AED", &field_type, &field_rev), - NumberFormat::ColombianPeso => { - assert_number(&type_option, "18443", "COP18.443", &field_type, &field_rev) - } - NumberFormat::Riyal => assert_number(&type_option, "18443", "SAR18,443", &field_type, &field_rev), - NumberFormat::Ringgit => assert_number(&type_option, "18443", "MYR18,443", &field_type, &field_rev), - NumberFormat::Leu => assert_number(&type_option, "18443", "18.443RON", &field_type, &field_rev), - NumberFormat::ArgentinePeso => { - assert_number(&type_option, "18443", "ARS18.443", &field_type, &field_rev) - } - NumberFormat::UruguayanPeso => { - assert_number(&type_option, "18443", "UYU18.443", &field_type, &field_rev) - } - NumberFormat::Percent => assert_number(&type_option, "18443", "18,443%", &field_type, &field_rev), - } - } + NumberFormat::Yuan => { + assert_number(&type_option, "18443", "-CN¥18,443", &field_type, &field_rev); + }, + NumberFormat::Real => { + assert_number(&type_option, "18443", "-R$18,443", &field_type, &field_rev); + }, + NumberFormat::Lira => { + assert_number(&type_option, "18443", "-TRY18.443", &field_type, &field_rev) + }, + NumberFormat::Rupiah => { + assert_number(&type_option, "18443", "-IDR18,443", &field_type, &field_rev) + }, + NumberFormat::Franc => { + assert_number(&type_option, "18443", "-CHF18,443", &field_type, &field_rev) + }, + NumberFormat::HongKongDollar => { + assert_number(&type_option, "18443", "-HZ$18,443", &field_type, &field_rev) + }, + NumberFormat::NewZealandDollar => { + assert_number(&type_option, "18443", "-NZ$18,443", &field_type, &field_rev) + }, + NumberFormat::Krona => { + assert_number(&type_option, "18443", "-18 443SEK", &field_type, &field_rev) + }, + NumberFormat::NorwegianKrone => { + assert_number(&type_option, "18443", "-18,443NOK", &field_type, &field_rev) + }, + NumberFormat::MexicanPeso => { + assert_number(&type_option, "18443", "-MX$18,443", &field_type, &field_rev) + }, + NumberFormat::Rand => { + assert_number(&type_option, "18443", "-ZAR18,443", &field_type, &field_rev) + }, + NumberFormat::NewTaiwanDollar => { + assert_number(&type_option, "18443", "-NT$18,443", &field_type, &field_rev) + }, + NumberFormat::DanishKrone => { + assert_number(&type_option, "18443", "-18.443DKK", &field_type, &field_rev) + }, + NumberFormat::Baht => { + assert_number(&type_option, "18443", "-THB18,443", &field_type, &field_rev) + }, + NumberFormat::Forint => { + assert_number(&type_option, "18443", "-18 443HUF", &field_type, &field_rev) + }, + NumberFormat::Koruna => { + assert_number(&type_option, "18443", "-18 443CZK", &field_type, &field_rev) + }, + NumberFormat::Shekel => { + assert_number(&type_option, "18443", "-18 443Kč", &field_type, &field_rev) + }, + NumberFormat::ChileanPeso => { + assert_number(&type_option, "18443", "-CLP18.443", &field_type, &field_rev) + }, + NumberFormat::PhilippinePeso => { + assert_number(&type_option, "18443", "-₱18,443", &field_type, &field_rev) + }, + NumberFormat::Dirham => { + assert_number(&type_option, "18443", "-18,443AED", &field_type, &field_rev) + }, + NumberFormat::ColombianPeso => { + assert_number(&type_option, "18443", "-COP18.443", &field_type, &field_rev) + }, + NumberFormat::Riyal => { + assert_number(&type_option, "18443", "-SAR18,443", &field_type, &field_rev) + }, + NumberFormat::Ringgit => { + assert_number(&type_option, "18443", "-MYR18,443", &field_type, &field_rev) + }, + NumberFormat::Leu => { + assert_number(&type_option, "18443", "-18.443RON", &field_type, &field_rev) + }, + NumberFormat::ArgentinePeso => { + assert_number(&type_option, "18443", "-ARS18.443", &field_type, &field_rev) + }, + NumberFormat::UruguayanPeso => { + assert_number(&type_option, "18443", "-UYU18.443", &field_type, &field_rev) + }, + NumberFormat::Percent => { + assert_number(&type_option, "18443", "-18,443%", &field_type, &field_rev) + }, + } } + } - /// Format the input String to the corresponding format string. - #[test] - fn number_type_option_format_str_test() { - let mut type_option = NumberTypeOptionPB::default(); - let field_type = FieldType::Number; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - - for format in NumberFormat::iter() { - type_option.format = format; - match format { - NumberFormat::Num => { - assert_number(&type_option, "18443", "18443", &field_type, &field_rev); - assert_number(&type_option, "0.2", "0.2", &field_type, &field_rev); - assert_number(&type_option, "", "", &field_type, &field_rev); - assert_number(&type_option, "abc", "", &field_type, &field_rev); - } - NumberFormat::USD => { - assert_number(&type_option, "$18,44", "$1,844", &field_type, &field_rev); - assert_number(&type_option, "$0.2", "$0.2", &field_type, &field_rev); - assert_number(&type_option, "$1844", "$1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "$1,844", &field_type, &field_rev); - } - NumberFormat::CanadianDollar => { - assert_number(&type_option, "CA$18,44", "CA$1,844", &field_type, &field_rev); - assert_number(&type_option, "CA$0.2", "CA$0.2", &field_type, &field_rev); - assert_number(&type_option, "CA$1844", "CA$1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "CA$1,844", &field_type, &field_rev); - } - NumberFormat::EUR => { - assert_number(&type_option, "€18.44", "€18,44", &field_type, &field_rev); - assert_number(&type_option, "€0.5", "€0,5", &field_type, &field_rev); - assert_number(&type_option, "€1844", "€1.844", &field_type, &field_rev); - assert_number(&type_option, "1844", "€1.844", &field_type, &field_rev); - } - NumberFormat::Pound => { - assert_number(&type_option, "£18,44", "£1,844", &field_type, &field_rev); - assert_number(&type_option, "£0.2", "£0.2", &field_type, &field_rev); - assert_number(&type_option, "£1844", "£1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "£1,844", &field_type, &field_rev); - } - NumberFormat::Yen => { - assert_number(&type_option, "¥18,44", "¥1,844", &field_type, &field_rev); - assert_number(&type_option, "¥0.2", "¥0.2", &field_type, &field_rev); - assert_number(&type_option, "¥1844", "¥1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "¥1,844", &field_type, &field_rev); - } - NumberFormat::Ruble => { - assert_number(&type_option, "RUB18.44", "18,44RUB", &field_type, &field_rev); - assert_number(&type_option, "0.5", "0,5RUB", &field_type, &field_rev); - assert_number(&type_option, "RUB1844", "1.844RUB", &field_type, &field_rev); - assert_number(&type_option, "1844", "1.844RUB", &field_type, &field_rev); - } - NumberFormat::Rupee => { - assert_number(&type_option, "₹18,44", "₹1,844", &field_type, &field_rev); - assert_number(&type_option, "₹0.2", "₹0.2", &field_type, &field_rev); - assert_number(&type_option, "₹1844", "₹1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "₹1,844", &field_type, &field_rev); - } - NumberFormat::Won => { - assert_number(&type_option, "₩18,44", "₩1,844", &field_type, &field_rev); - assert_number(&type_option, "₩0.3", "₩0", &field_type, &field_rev); - assert_number(&type_option, "₩1844", "₩1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "₩1,844", &field_type, &field_rev); - } - NumberFormat::Yuan => { - assert_number(&type_option, "CN¥18,44", "CN¥1,844", &field_type, &field_rev); - assert_number(&type_option, "CN¥0.2", "CN¥0.2", &field_type, &field_rev); - assert_number(&type_option, "CN¥1844", "CN¥1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "CN¥1,844", &field_type, &field_rev); - } - NumberFormat::Real => { - assert_number(&type_option, "R$18,44", "R$1,844", &field_type, &field_rev); - assert_number(&type_option, "R$0.2", "R$0.2", &field_type, &field_rev); - assert_number(&type_option, "R$1844", "R$1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "R$1,844", &field_type, &field_rev); - } - NumberFormat::Lira => { - assert_number(&type_option, "TRY18.44", "TRY18,44", &field_type, &field_rev); - assert_number(&type_option, "TRY0.5", "TRY0,5", &field_type, &field_rev); - assert_number(&type_option, "TRY1844", "TRY1.844", &field_type, &field_rev); - assert_number(&type_option, "1844", "TRY1.844", &field_type, &field_rev); - } - NumberFormat::Rupiah => { - assert_number(&type_option, "IDR18,44", "IDR1,844", &field_type, &field_rev); - assert_number(&type_option, "IDR0.2", "IDR0.2", &field_type, &field_rev); - assert_number(&type_option, "IDR1844", "IDR1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "IDR1,844", &field_type, &field_rev); - } - NumberFormat::Franc => { - assert_number(&type_option, "CHF18,44", "CHF1,844", &field_type, &field_rev); - assert_number(&type_option, "CHF0.2", "CHF0.2", &field_type, &field_rev); - assert_number(&type_option, "CHF1844", "CHF1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "CHF1,844", &field_type, &field_rev); - } - NumberFormat::HongKongDollar => { - assert_number(&type_option, "HZ$18,44", "HZ$1,844", &field_type, &field_rev); - assert_number(&type_option, "HZ$0.2", "HZ$0.2", &field_type, &field_rev); - assert_number(&type_option, "HZ$1844", "HZ$1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "HZ$1,844", &field_type, &field_rev); - } - NumberFormat::NewZealandDollar => { - assert_number(&type_option, "NZ$18,44", "NZ$1,844", &field_type, &field_rev); - assert_number(&type_option, "NZ$0.2", "NZ$0.2", &field_type, &field_rev); - assert_number(&type_option, "NZ$1844", "NZ$1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "NZ$1,844", &field_type, &field_rev); - } - NumberFormat::Krona => { - assert_number(&type_option, "SEK18,44", "18,44SEK", &field_type, &field_rev); - assert_number(&type_option, "SEK0.2", "0,2SEK", &field_type, &field_rev); - assert_number(&type_option, "SEK1844", "1 844SEK", &field_type, &field_rev); - assert_number(&type_option, "1844", "1 844SEK", &field_type, &field_rev); - } - NumberFormat::NorwegianKrone => { - assert_number(&type_option, "NOK18,44", "1,844NOK", &field_type, &field_rev); - assert_number(&type_option, "NOK0.2", "0.2NOK", &field_type, &field_rev); - assert_number(&type_option, "NOK1844", "1,844NOK", &field_type, &field_rev); - assert_number(&type_option, "1844", "1,844NOK", &field_type, &field_rev); - } - NumberFormat::MexicanPeso => { - assert_number(&type_option, "MX$18,44", "MX$1,844", &field_type, &field_rev); - assert_number(&type_option, "MX$0.2", "MX$0.2", &field_type, &field_rev); - assert_number(&type_option, "MX$1844", "MX$1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "MX$1,844", &field_type, &field_rev); - } - NumberFormat::Rand => { - assert_number(&type_option, "ZAR18,44", "ZAR1,844", &field_type, &field_rev); - assert_number(&type_option, "ZAR0.2", "ZAR0.2", &field_type, &field_rev); - assert_number(&type_option, "ZAR1844", "ZAR1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "ZAR1,844", &field_type, &field_rev); - } - NumberFormat::NewTaiwanDollar => { - assert_number(&type_option, "NT$18,44", "NT$1,844", &field_type, &field_rev); - assert_number(&type_option, "NT$0.2", "NT$0.2", &field_type, &field_rev); - assert_number(&type_option, "NT$1844", "NT$1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "NT$1,844", &field_type, &field_rev); - } - NumberFormat::DanishKrone => { - assert_number(&type_option, "DKK18.44", "18,44DKK", &field_type, &field_rev); - assert_number(&type_option, "DKK0.5", "0,5DKK", &field_type, &field_rev); - assert_number(&type_option, "DKK1844", "1.844DKK", &field_type, &field_rev); - assert_number(&type_option, "1844", "1.844DKK", &field_type, &field_rev); - } - NumberFormat::Baht => { - assert_number(&type_option, "THB18,44", "THB1,844", &field_type, &field_rev); - assert_number(&type_option, "THB0.2", "THB0.2", &field_type, &field_rev); - assert_number(&type_option, "THB1844", "THB1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "THB1,844", &field_type, &field_rev); - } - NumberFormat::Forint => { - assert_number(&type_option, "HUF18,44", "18HUF", &field_type, &field_rev); - assert_number(&type_option, "HUF0.3", "0HUF", &field_type, &field_rev); - assert_number(&type_option, "HUF1844", "1 844HUF", &field_type, &field_rev); - assert_number(&type_option, "1844", "1 844HUF", &field_type, &field_rev); - } - NumberFormat::Koruna => { - assert_number(&type_option, "CZK18,44", "18,44CZK", &field_type, &field_rev); - assert_number(&type_option, "CZK0.2", "0,2CZK", &field_type, &field_rev); - assert_number(&type_option, "CZK1844", "1 844CZK", &field_type, &field_rev); - assert_number(&type_option, "1844", "1 844CZK", &field_type, &field_rev); - } - NumberFormat::Shekel => { - assert_number(&type_option, "Kč18,44", "18,44Kč", &field_type, &field_rev); - assert_number(&type_option, "Kč0.2", "0,2Kč", &field_type, &field_rev); - assert_number(&type_option, "Kč1844", "1 844Kč", &field_type, &field_rev); - assert_number(&type_option, "1844", "1 844Kč", &field_type, &field_rev); - } - NumberFormat::ChileanPeso => { - assert_number(&type_option, "CLP18.44", "CLP18", &field_type, &field_rev); - assert_number(&type_option, "0.5", "CLP0", &field_type, &field_rev); - assert_number(&type_option, "CLP1844", "CLP1.844", &field_type, &field_rev); - assert_number(&type_option, "1844", "CLP1.844", &field_type, &field_rev); - } - NumberFormat::PhilippinePeso => { - assert_number(&type_option, "₱18,44", "₱1,844", &field_type, &field_rev); - assert_number(&type_option, "₱0.2", "₱0.2", &field_type, &field_rev); - assert_number(&type_option, "₱1844", "₱1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "₱1,844", &field_type, &field_rev); - } - NumberFormat::Dirham => { - assert_number(&type_option, "AED18,44", "1,844AED", &field_type, &field_rev); - assert_number(&type_option, "AED0.2", "0.2AED", &field_type, &field_rev); - assert_number(&type_option, "AED1844", "1,844AED", &field_type, &field_rev); - assert_number(&type_option, "1844", "1,844AED", &field_type, &field_rev); - } - NumberFormat::ColombianPeso => { - assert_number(&type_option, "COP18.44", "COP18,44", &field_type, &field_rev); - assert_number(&type_option, "0.5", "COP0,5", &field_type, &field_rev); - assert_number(&type_option, "COP1844", "COP1.844", &field_type, &field_rev); - assert_number(&type_option, "1844", "COP1.844", &field_type, &field_rev); - } - NumberFormat::Riyal => { - assert_number(&type_option, "SAR18,44", "SAR1,844", &field_type, &field_rev); - assert_number(&type_option, "SAR0.2", "SAR0.2", &field_type, &field_rev); - assert_number(&type_option, "SAR1844", "SAR1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "SAR1,844", &field_type, &field_rev); - } - - NumberFormat::Ringgit => { - assert_number(&type_option, "MYR18,44", "MYR1,844", &field_type, &field_rev); - assert_number(&type_option, "MYR0.2", "MYR0.2", &field_type, &field_rev); - assert_number(&type_option, "MYR1844", "MYR1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "MYR1,844", &field_type, &field_rev); - } - NumberFormat::Leu => { - assert_number(&type_option, "RON18.44", "18,44RON", &field_type, &field_rev); - assert_number(&type_option, "0.5", "0,5RON", &field_type, &field_rev); - assert_number(&type_option, "RON1844", "1.844RON", &field_type, &field_rev); - assert_number(&type_option, "1844", "1.844RON", &field_type, &field_rev); - } - NumberFormat::ArgentinePeso => { - assert_number(&type_option, "ARS18.44", "ARS18,44", &field_type, &field_rev); - assert_number(&type_option, "0.5", "ARS0,5", &field_type, &field_rev); - assert_number(&type_option, "ARS1844", "ARS1.844", &field_type, &field_rev); - assert_number(&type_option, "1844", "ARS1.844", &field_type, &field_rev); - } - NumberFormat::UruguayanPeso => { - assert_number(&type_option, "UYU18.44", "UYU18,44", &field_type, &field_rev); - assert_number(&type_option, "0.5", "UYU0,5", &field_type, &field_rev); - assert_number(&type_option, "UYU1844", "UYU1.844", &field_type, &field_rev); - assert_number(&type_option, "1844", "UYU1.844", &field_type, &field_rev); - } - NumberFormat::Percent => { - assert_number(&type_option, "1", "1%", &field_type, &field_rev); - assert_number(&type_option, "10.1", "10.1%", &field_type, &field_rev); - assert_number(&type_option, "100", "100%", &field_type, &field_rev); - } - } - } - } - - /// Carry out the sign positive to input number - #[test] - fn number_description_sign_test() { - let mut type_option = NumberTypeOptionPB { - sign_positive: false, - ..Default::default() - }; - let field_type = FieldType::Number; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - - for format in NumberFormat::iter() { - type_option.format = format; - match format { - NumberFormat::Num => { - assert_number(&type_option, "18443", "18443", &field_type, &field_rev); - } - NumberFormat::USD => { - assert_number(&type_option, "18443", "-$18,443", &field_type, &field_rev); - } - NumberFormat::CanadianDollar => { - assert_number(&type_option, "18443", "-CA$18,443", &field_type, &field_rev) - } - NumberFormat::EUR => assert_number(&type_option, "18443", "-€18.443", &field_type, &field_rev), - NumberFormat::Pound => assert_number(&type_option, "18443", "-£18,443", &field_type, &field_rev), - - NumberFormat::Yen => { - assert_number(&type_option, "18443", "-¥18,443", &field_type, &field_rev); - } - NumberFormat::Ruble => assert_number(&type_option, "18443", "-18.443RUB", &field_type, &field_rev), - NumberFormat::Rupee => assert_number(&type_option, "18443", "-₹18,443", &field_type, &field_rev), - NumberFormat::Won => assert_number(&type_option, "18443", "-₩18,443", &field_type, &field_rev), - - NumberFormat::Yuan => { - assert_number(&type_option, "18443", "-CN¥18,443", &field_type, &field_rev); - } - NumberFormat::Real => { - assert_number(&type_option, "18443", "-R$18,443", &field_type, &field_rev); - } - NumberFormat::Lira => assert_number(&type_option, "18443", "-TRY18.443", &field_type, &field_rev), - NumberFormat::Rupiah => assert_number(&type_option, "18443", "-IDR18,443", &field_type, &field_rev), - NumberFormat::Franc => assert_number(&type_option, "18443", "-CHF18,443", &field_type, &field_rev), - NumberFormat::HongKongDollar => { - assert_number(&type_option, "18443", "-HZ$18,443", &field_type, &field_rev) - } - NumberFormat::NewZealandDollar => { - assert_number(&type_option, "18443", "-NZ$18,443", &field_type, &field_rev) - } - NumberFormat::Krona => assert_number(&type_option, "18443", "-18 443SEK", &field_type, &field_rev), - NumberFormat::NorwegianKrone => { - assert_number(&type_option, "18443", "-18,443NOK", &field_type, &field_rev) - } - NumberFormat::MexicanPeso => { - assert_number(&type_option, "18443", "-MX$18,443", &field_type, &field_rev) - } - NumberFormat::Rand => assert_number(&type_option, "18443", "-ZAR18,443", &field_type, &field_rev), - NumberFormat::NewTaiwanDollar => { - assert_number(&type_option, "18443", "-NT$18,443", &field_type, &field_rev) - } - NumberFormat::DanishKrone => { - assert_number(&type_option, "18443", "-18.443DKK", &field_type, &field_rev) - } - NumberFormat::Baht => assert_number(&type_option, "18443", "-THB18,443", &field_type, &field_rev), - NumberFormat::Forint => assert_number(&type_option, "18443", "-18 443HUF", &field_type, &field_rev), - NumberFormat::Koruna => assert_number(&type_option, "18443", "-18 443CZK", &field_type, &field_rev), - NumberFormat::Shekel => assert_number(&type_option, "18443", "-18 443Kč", &field_type, &field_rev), - NumberFormat::ChileanPeso => { - assert_number(&type_option, "18443", "-CLP18.443", &field_type, &field_rev) - } - NumberFormat::PhilippinePeso => { - assert_number(&type_option, "18443", "-₱18,443", &field_type, &field_rev) - } - NumberFormat::Dirham => assert_number(&type_option, "18443", "-18,443AED", &field_type, &field_rev), - NumberFormat::ColombianPeso => { - assert_number(&type_option, "18443", "-COP18.443", &field_type, &field_rev) - } - NumberFormat::Riyal => assert_number(&type_option, "18443", "-SAR18,443", &field_type, &field_rev), - NumberFormat::Ringgit => assert_number(&type_option, "18443", "-MYR18,443", &field_type, &field_rev), - NumberFormat::Leu => assert_number(&type_option, "18443", "-18.443RON", &field_type, &field_rev), - NumberFormat::ArgentinePeso => { - assert_number(&type_option, "18443", "-ARS18.443", &field_type, &field_rev) - } - NumberFormat::UruguayanPeso => { - assert_number(&type_option, "18443", "-UYU18.443", &field_type, &field_rev) - } - NumberFormat::Percent => assert_number(&type_option, "18443", "-18,443%", &field_type, &field_rev), - } - } - } - - fn assert_number( - type_option: &NumberTypeOptionPB, - input_str: &str, - expected_str: &str, - field_type: &FieldType, - field_rev: &FieldRevision, - ) { - assert_eq!( - type_option - .decode_cell_str(input_str.to_owned(), field_type, field_rev) - .unwrap() - .to_string(), - expected_str.to_owned() - ); - } + fn assert_number( + type_option: &NumberTypeOptionPB, + input_str: &str, + expected_str: &str, + field_type: &FieldType, + field_rev: &FieldRevision, + ) { + assert_eq!( + type_option + .decode_cell_str(input_str.to_owned(), field_type, field_rev) + .unwrap() + .to_string(), + expected_str.to_owned() + ); + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option.rs index 22057167f1..d7369cc0a5 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option.rs @@ -3,8 +3,8 @@ use crate::impl_type_option; use crate::services::cell::{CellDataChangeset, CellDataDecoder, TypeCellData}; use crate::services::field::type_options::number_type_option::format::*; use crate::services::field::{ - BoxTypeOptionBuilder, NumberCellData, StrCellData, TypeOption, TypeOptionBuilder, TypeOptionCellData, - TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, + BoxTypeOptionBuilder, NumberCellData, StrCellData, TypeOption, TypeOptionBuilder, + TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, }; use bytes::Bytes; use fancy_regex::Regex; @@ -24,218 +24,227 @@ impl_into_box_type_option_builder!(NumberTypeOptionBuilder); impl_builder_from_json_str_and_from_bytes!(NumberTypeOptionBuilder, NumberTypeOptionPB); impl NumberTypeOptionBuilder { - pub fn name(mut self, name: &str) -> Self { - self.0.name = name.to_string(); - self - } + pub fn name(mut self, name: &str) -> Self { + self.0.name = name.to_string(); + self + } - pub fn set_format(mut self, format: NumberFormat) -> Self { - self.0.set_format(format); - self - } + pub fn set_format(mut self, format: NumberFormat) -> Self { + self.0.set_format(format); + self + } - pub fn scale(mut self, scale: u32) -> Self { - self.0.scale = scale; - self - } + pub fn scale(mut self, scale: u32) -> Self { + self.0.scale = scale; + self + } - pub fn positive(mut self, positive: bool) -> Self { - self.0.sign_positive = positive; - self - } + pub fn positive(mut self, positive: bool) -> Self { + self.0.sign_positive = positive; + self + } } impl TypeOptionBuilder for NumberTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::Number - } + fn field_type(&self) -> FieldType { + FieldType::Number + } - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } + fn serializer(&self) -> &dyn TypeOptionDataSerializer { + &self.0 + } } // Number #[derive(Clone, Debug, Serialize, Deserialize, ProtoBuf)] pub struct NumberTypeOptionPB { - #[pb(index = 1)] - pub format: NumberFormat, + #[pb(index = 1)] + pub format: NumberFormat, - #[pb(index = 2)] - pub scale: u32, + #[pb(index = 2)] + pub scale: u32, - #[pb(index = 3)] - pub symbol: String, + #[pb(index = 3)] + pub symbol: String, - #[pb(index = 4)] - pub sign_positive: bool, + #[pb(index = 4)] + pub sign_positive: bool, - #[pb(index = 5)] - pub name: String, + #[pb(index = 5)] + pub name: String, } impl_type_option!(NumberTypeOptionPB, FieldType::Number); impl TypeOption for NumberTypeOptionPB { - type CellData = StrCellData; - type CellChangeset = NumberCellChangeset; - type CellProtobufType = StrCellData; - type CellFilter = NumberFilterPB; + type CellData = StrCellData; + type CellChangeset = NumberCellChangeset; + type CellProtobufType = StrCellData; + type CellFilter = NumberFilterPB; } impl TypeOptionCellData for NumberTypeOptionPB { - fn convert_to_protobuf(&self, cell_data: ::CellData) -> ::CellProtobufType { - cell_data - } + fn convert_to_protobuf( + &self, + cell_data: ::CellData, + ) -> ::CellProtobufType { + cell_data + } - fn decode_type_option_cell_str(&self, cell_str: String) -> FlowyResult<::CellData> { - Ok(cell_str.into()) - } + fn decode_type_option_cell_str( + &self, + cell_str: String, + ) -> FlowyResult<::CellData> { + Ok(cell_str.into()) + } } impl NumberTypeOptionPB { - pub fn new() -> Self { - Self::default() - } + pub fn new() -> Self { + Self::default() + } - pub(crate) fn format_cell_data(&self, s: &str) -> FlowyResult { - match self.format { - NumberFormat::Num => { - if SCIENTIFIC_NOTATION_REGEX.is_match(s).unwrap() { - match Decimal::from_scientific(&s.to_lowercase()) { - Ok(value, ..) => Ok(NumberCellData::from_decimal(value)), - Err(_) => Ok(NumberCellData::new()), - } - } else { - let draw_numer_string = NUM_REGEX.replace_all(s, ""); - let strnum = match draw_numer_string.matches('.').count() { - 0 | 1 => draw_numer_string.to_string(), - _ => match EXTRACT_NUM_REGEX.captures(&draw_numer_string) { - Ok(captures) => match captures { - Some(capture) => capture[1].to_string(), - None => "".to_string(), - }, - Err(_) => "".to_string(), - }, - }; - match Decimal::from_str(&strnum) { - Ok(value, ..) => Ok(NumberCellData::from_decimal(value)), - Err(_) => Ok(NumberCellData::new()), - } - } - } - _ => NumberCellData::from_format_str(s, self.sign_positive, &self.format), + pub(crate) fn format_cell_data(&self, s: &str) -> FlowyResult { + match self.format { + NumberFormat::Num => { + if SCIENTIFIC_NOTATION_REGEX.is_match(s).unwrap() { + match Decimal::from_scientific(&s.to_lowercase()) { + Ok(value, ..) => Ok(NumberCellData::from_decimal(value)), + Err(_) => Ok(NumberCellData::new()), + } + } else { + let draw_numer_string = NUM_REGEX.replace_all(s, ""); + let strnum = match draw_numer_string.matches('.').count() { + 0 | 1 => draw_numer_string.to_string(), + _ => match EXTRACT_NUM_REGEX.captures(&draw_numer_string) { + Ok(captures) => match captures { + Some(capture) => capture[1].to_string(), + None => "".to_string(), + }, + Err(_) => "".to_string(), + }, + }; + match Decimal::from_str(&strnum) { + Ok(value, ..) => Ok(NumberCellData::from_decimal(value)), + Err(_) => Ok(NumberCellData::new()), + } } + }, + _ => NumberCellData::from_format_str(s, self.sign_positive, &self.format), } + } - pub fn set_format(&mut self, format: NumberFormat) { - self.format = format; - self.symbol = format.symbol(); - } + pub fn set_format(&mut self, format: NumberFormat) { + self.format = format; + self.symbol = format.symbol(); + } } pub(crate) fn strip_currency_symbol(s: T) -> String { - let mut s = s.to_string(); - for symbol in CURRENCY_SYMBOL.iter() { - if s.starts_with(symbol) { - s = s.strip_prefix(symbol).unwrap_or("").to_string(); - break; - } + let mut s = s.to_string(); + for symbol in CURRENCY_SYMBOL.iter() { + if s.starts_with(symbol) { + s = s.strip_prefix(symbol).unwrap_or("").to_string(); + break; } - s + } + s } impl TypeOptionTransform for NumberTypeOptionPB {} impl CellDataDecoder for NumberTypeOptionPB { - fn decode_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult<::CellData> { - if decoded_field_type.is_date() { - return Ok(Default::default()); - } - - let str_cell_data = self.decode_type_option_cell_str(cell_str)?; - let s = self.format_cell_data(&str_cell_data)?.to_string(); - Ok(s.into()) + fn decode_cell_str( + &self, + cell_str: String, + decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> FlowyResult<::CellData> { + if decoded_field_type.is_date() { + return Ok(Default::default()); } - fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { - match self.format_cell_data(&cell_data) { - Ok(cell_data) => cell_data.to_string(), - Err(_) => "".to_string(), - } + let str_cell_data = self.decode_type_option_cell_str(cell_str)?; + let s = self.format_cell_data(&str_cell_data)?.to_string(); + Ok(s.into()) + } + + fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { + match self.format_cell_data(&cell_data) { + Ok(cell_data) => cell_data.to_string(), + Err(_) => "".to_string(), } + } } pub type NumberCellChangeset = String; impl CellDataChangeset for NumberTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - _type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - let data = changeset.trim().to_string(); - let number_cell_data = self.format_cell_data(&data)?; + fn apply_changeset( + &self, + changeset: ::CellChangeset, + _type_cell_data: Option, + ) -> FlowyResult<(String, ::CellData)> { + let data = changeset.trim().to_string(); + let number_cell_data = self.format_cell_data(&data)?; - match self.format { - NumberFormat::Num => Ok((number_cell_data.to_string(), number_cell_data.to_string().into())), - _ => Ok((data, number_cell_data.to_string().into())), - } + match self.format { + NumberFormat::Num => Ok(( + number_cell_data.to_string(), + number_cell_data.to_string().into(), + )), + _ => Ok((data, number_cell_data.to_string().into())), } + } } impl TypeOptionCellDataFilter for NumberTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_number() { - return true; - } - match self.format_cell_data(cell_data) { - Ok(cell_data) => filter.is_visible(&cell_data), - Err(_) => true, - } + fn apply_filter( + &self, + filter: &::CellFilter, + field_type: &FieldType, + cell_data: &::CellData, + ) -> bool { + if !field_type.is_number() { + return true; } + match self.format_cell_data(cell_data) { + Ok(cell_data) => filter.is_visible(&cell_data), + Err(_) => true, + } + } } impl TypeOptionCellDataCompare for NumberTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - cell_data.0.cmp(&other_cell_data.0) - } + fn apply_cmp( + &self, + cell_data: &::CellData, + other_cell_data: &::CellData, + ) -> Ordering { + cell_data.0.cmp(&other_cell_data.0) + } } impl std::default::Default for NumberTypeOptionPB { - fn default() -> Self { - let format = NumberFormat::default(); - let symbol = format.symbol(); - NumberTypeOptionPB { - format, - scale: 0, - symbol, - sign_positive: true, - name: "Number".to_string(), - } + fn default() -> Self { + let format = NumberFormat::default(); + let symbol = format.symbol(); + NumberTypeOptionPB { + format, + scale: 0, + symbol, + sign_positive: true, + name: "Number".to_string(), } + } } lazy_static! { - static ref NUM_REGEX: Regex = Regex::new(r"[^\d\.]").unwrap(); + static ref NUM_REGEX: Regex = Regex::new(r"[^\d\.]").unwrap(); } lazy_static! { - static ref SCIENTIFIC_NOTATION_REGEX: Regex = Regex::new(r"([+-]?\d*\.?\d+)e([+-]?\d+)").unwrap(); + static ref SCIENTIFIC_NOTATION_REGEX: Regex = Regex::new(r"([+-]?\d*\.?\d+)e([+-]?\d+)").unwrap(); } lazy_static! { - static ref EXTRACT_NUM_REGEX: Regex = Regex::new(r"^(\d+\.\d+)(?:\.\d+)*$").unwrap(); + static ref EXTRACT_NUM_REGEX: Regex = Regex::new(r"^(\d+\.\d+)(?:\.\d+)*$").unwrap(); } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option_entities.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option_entities.rs index 326eb3db30..ab650f8a7d 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option_entities.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option_entities.rs @@ -9,66 +9,66 @@ use std::str::FromStr; #[derive(Default)] pub struct NumberCellData { - decimal: Option, - money: Option, + decimal: Option, + money: Option, } impl NumberCellData { - pub fn new() -> Self { - Self { - decimal: Default::default(), - money: None, - } + pub fn new() -> Self { + Self { + decimal: Default::default(), + money: None, } + } - pub fn from_format_str(s: &str, sign_positive: bool, format: &NumberFormat) -> FlowyResult { - let mut num_str = strip_currency_symbol(s); - let currency = format.currency(); - if num_str.is_empty() { - return Ok(Self::default()); - } - match Decimal::from_str(&num_str) { - Ok(mut decimal) => { - decimal.set_sign_positive(sign_positive); - let money = Money::from_decimal(decimal, currency); - Ok(Self::from_money(money)) - } - Err(_) => match Money::from_str(&num_str, currency) { - Ok(money) => Ok(NumberCellData::from_money(money)), - Err(_) => { - num_str.retain(|c| !STRIP_SYMBOL.contains(&c.to_string())); - if num_str.chars().all(char::is_numeric) { - Self::from_format_str(&num_str, sign_positive, format) - } else { - // returns empty string if it can be formatted - Ok(Self::default()) - } - } - }, - } + pub fn from_format_str(s: &str, sign_positive: bool, format: &NumberFormat) -> FlowyResult { + let mut num_str = strip_currency_symbol(s); + let currency = format.currency(); + if num_str.is_empty() { + return Ok(Self::default()); } + match Decimal::from_str(&num_str) { + Ok(mut decimal) => { + decimal.set_sign_positive(sign_positive); + let money = Money::from_decimal(decimal, currency); + Ok(Self::from_money(money)) + }, + Err(_) => match Money::from_str(&num_str, currency) { + Ok(money) => Ok(NumberCellData::from_money(money)), + Err(_) => { + num_str.retain(|c| !STRIP_SYMBOL.contains(&c.to_string())); + if num_str.chars().all(char::is_numeric) { + Self::from_format_str(&num_str, sign_positive, format) + } else { + // returns empty string if it can be formatted + Ok(Self::default()) + } + }, + }, + } + } - pub fn from_decimal(decimal: Decimal) -> Self { - Self { - decimal: Some(decimal), - money: None, - } + pub fn from_decimal(decimal: Decimal) -> Self { + Self { + decimal: Some(decimal), + money: None, } + } - pub fn from_money(money: Money) -> Self { - Self { - decimal: Some(*money.amount()), - money: Some(money.to_string()), - } + pub fn from_money(money: Money) -> Self { + Self { + decimal: Some(*money.amount()), + money: Some(money.to_string()), } + } - pub fn decimal(&self) -> &Option { - &self.decimal - } + pub fn decimal(&self) -> &Option { + &self.decimal + } - pub fn is_empty(&self) -> bool { - self.decimal.is_none() - } + pub fn is_empty(&self) -> bool { + self.decimal.is_none() + } } // impl FromStr for NumberCellData { @@ -84,43 +84,43 @@ impl NumberCellData { // } impl ToString for NumberCellData { - fn to_string(&self) -> String { - match &self.money { - None => match self.decimal { - None => String::default(), - Some(decimal) => decimal.to_string(), - }, - Some(money) => money.to_string(), - } + fn to_string(&self) -> String { + match &self.money { + None => match self.decimal { + None => String::default(), + Some(decimal) => decimal.to_string(), + }, + Some(money) => money.to_string(), } + } } impl DecodedCellData for NumberCellData { - type Object = NumberCellData; + type Object = NumberCellData; - fn is_empty(&self) -> bool { - self.decimal.is_none() - } + fn is_empty(&self) -> bool { + self.decimal.is_none() + } } pub struct NumberCellDataParser(); impl CellProtobufBlobParser for NumberCellDataParser { - type Object = NumberCellData; - fn parser(bytes: &Bytes) -> FlowyResult { - match String::from_utf8(bytes.to_vec()) { - Ok(s) => NumberCellData::from_format_str(&s, true, &NumberFormat::Num), - Err(_) => Ok(NumberCellData::default()), - } + type Object = NumberCellData; + fn parser(bytes: &Bytes) -> FlowyResult { + match String::from_utf8(bytes.to_vec()) { + Ok(s) => NumberCellData::from_format_str(&s, true, &NumberFormat::Num), + Err(_) => Ok(NumberCellData::default()), } + } } pub struct NumberCellCustomDataParser(pub NumberFormat); impl CellBytesCustomParser for NumberCellCustomDataParser { - type Object = NumberCellData; - fn parse(&self, bytes: &Bytes) -> FlowyResult { - match String::from_utf8(bytes.to_vec()) { - Ok(s) => NumberCellData::from_format_str(&s, true, &self.0), - Err(_) => Ok(NumberCellData::default()), - } + type Object = NumberCellData; + fn parse(&self, bytes: &Bytes) -> FlowyResult { + match String::from_utf8(bytes.to_vec()) { + Ok(s) => NumberCellData::from_format_str(&s, true, &self.0), + Err(_) => Ok(NumberCellData::default()), } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/checklist_filter.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/checklist_filter.rs index 2f10547a81..c1f3de0a52 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/checklist_filter.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/checklist_filter.rs @@ -2,35 +2,39 @@ use crate::entities::{ChecklistFilterConditionPB, ChecklistFilterPB}; use crate::services::field::{SelectOptionPB, SelectedSelectOptions}; impl ChecklistFilterPB { - pub fn is_visible(&self, all_options: &[SelectOptionPB], selected_options: &SelectedSelectOptions) -> bool { - let selected_option_ids = selected_options - .options - .iter() - .map(|option| option.id.as_str()) - .collect::>(); + pub fn is_visible( + &self, + all_options: &[SelectOptionPB], + selected_options: &SelectedSelectOptions, + ) -> bool { + let selected_option_ids = selected_options + .options + .iter() + .map(|option| option.id.as_str()) + .collect::>(); - let mut all_option_ids = all_options - .iter() - .map(|option| option.id.as_str()) - .collect::>(); + let mut all_option_ids = all_options + .iter() + .map(|option| option.id.as_str()) + .collect::>(); - match self.condition { - ChecklistFilterConditionPB::IsComplete => { - if selected_option_ids.is_empty() { - return false; - } - - all_option_ids.retain(|option_id| !selected_option_ids.contains(option_id)); - all_option_ids.is_empty() - } - ChecklistFilterConditionPB::IsIncomplete => { - if selected_option_ids.is_empty() { - return true; - } - - all_option_ids.retain(|option_id| !selected_option_ids.contains(option_id)); - !all_option_ids.is_empty() - } + match self.condition { + ChecklistFilterConditionPB::IsComplete => { + if selected_option_ids.is_empty() { + return false; } + + all_option_ids.retain(|option_id| !selected_option_ids.contains(option_id)); + all_option_ids.is_empty() + }, + ChecklistFilterConditionPB::IsIncomplete => { + if selected_option_ids.is_empty() { + return true; + } + + all_option_ids.retain(|option_id| !selected_option_ids.contains(option_id)); + !all_option_ids.is_empty() + }, } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/checklist_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/checklist_type_option.rs index 0ab1b907f0..da2f0a1909 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/checklist_type_option.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/checklist_type_option.rs @@ -2,9 +2,9 @@ use crate::entities::{ChecklistFilterPB, FieldType}; use crate::impl_type_option; use crate::services::cell::{CellDataChangeset, FromCellString, TypeCellData}; use crate::services::field::{ - BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds, SelectOptionPB, - SelectTypeOptionSharedAction, SelectedSelectOptions, TypeOption, TypeOptionBuilder, TypeOptionCellData, - TypeOptionCellDataCompare, TypeOptionCellDataFilter, + BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds, + SelectOptionPB, SelectTypeOptionSharedAction, SelectedSelectOptions, TypeOption, + TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, }; use bytes::Bytes; use flowy_derive::ProtoBuf; @@ -16,100 +16,112 @@ use std::cmp::Ordering; // Multiple select #[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] pub struct ChecklistTypeOptionPB { - #[pb(index = 1)] - pub options: Vec, + #[pb(index = 1)] + pub options: Vec, - #[pb(index = 2)] - pub disable_color: bool, + #[pb(index = 2)] + pub disable_color: bool, } impl_type_option!(ChecklistTypeOptionPB, FieldType::Checklist); impl TypeOption for ChecklistTypeOptionPB { - type CellData = SelectOptionIds; - type CellChangeset = SelectOptionCellChangeset; - type CellProtobufType = SelectOptionCellDataPB; - type CellFilter = ChecklistFilterPB; + type CellData = SelectOptionIds; + type CellChangeset = SelectOptionCellChangeset; + type CellProtobufType = SelectOptionCellDataPB; + type CellFilter = ChecklistFilterPB; } impl TypeOptionCellData for ChecklistTypeOptionPB { - fn convert_to_protobuf(&self, cell_data: ::CellData) -> ::CellProtobufType { - self.get_selected_options(cell_data) - } + fn convert_to_protobuf( + &self, + cell_data: ::CellData, + ) -> ::CellProtobufType { + self.get_selected_options(cell_data) + } - fn decode_type_option_cell_str(&self, cell_str: String) -> FlowyResult<::CellData> { - SelectOptionIds::from_cell_str(&cell_str) - } + fn decode_type_option_cell_str( + &self, + cell_str: String, + ) -> FlowyResult<::CellData> { + SelectOptionIds::from_cell_str(&cell_str) + } } impl SelectTypeOptionSharedAction for ChecklistTypeOptionPB { - fn number_of_max_options(&self) -> Option { - None - } + fn number_of_max_options(&self) -> Option { + None + } - fn options(&self) -> &Vec { - &self.options - } + fn options(&self) -> &Vec { + &self.options + } - fn mut_options(&mut self) -> &mut Vec { - &mut self.options - } + fn mut_options(&mut self) -> &mut Vec { + &mut self.options + } } impl CellDataChangeset for ChecklistTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - let insert_option_ids = changeset - .insert_option_ids - .into_iter() - .filter(|insert_option_id| self.options.iter().any(|option| &option.id == insert_option_id)) - .collect::>(); + fn apply_changeset( + &self, + changeset: ::CellChangeset, + type_cell_data: Option, + ) -> FlowyResult<(String, ::CellData)> { + let insert_option_ids = changeset + .insert_option_ids + .into_iter() + .filter(|insert_option_id| { + self + .options + .iter() + .any(|option| &option.id == insert_option_id) + }) + .collect::>(); - let select_option_ids = match type_cell_data { - None => SelectOptionIds::from(insert_option_ids), - Some(type_cell_data) => { - let mut select_ids: SelectOptionIds = type_cell_data.cell_str.into(); - for insert_option_id in insert_option_ids { - if !select_ids.contains(&insert_option_id) { - select_ids.push(insert_option_id); - } - } + let select_option_ids = match type_cell_data { + None => SelectOptionIds::from(insert_option_ids), + Some(type_cell_data) => { + let mut select_ids: SelectOptionIds = type_cell_data.cell_str.into(); + for insert_option_id in insert_option_ids { + if !select_ids.contains(&insert_option_id) { + select_ids.push(insert_option_id); + } + } - for delete_option_id in changeset.delete_option_ids { - select_ids.retain(|id| id != &delete_option_id); - } + for delete_option_id in changeset.delete_option_ids { + select_ids.retain(|id| id != &delete_option_id); + } - select_ids - } - }; - Ok((select_option_ids.to_string(), select_option_ids)) - } + select_ids + }, + }; + Ok((select_option_ids.to_string(), select_option_ids)) + } } impl TypeOptionCellDataFilter for ChecklistTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_check_list() { - return true; - } - let selected_options = SelectedSelectOptions::from(self.get_selected_options(cell_data.clone())); - filter.is_visible(&self.options, &selected_options) + fn apply_filter( + &self, + filter: &::CellFilter, + field_type: &FieldType, + cell_data: &::CellData, + ) -> bool { + if !field_type.is_check_list() { + return true; } + let selected_options = + SelectedSelectOptions::from(self.get_selected_options(cell_data.clone())); + filter.is_visible(&self.options, &selected_options) + } } impl TypeOptionCellDataCompare for ChecklistTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - cell_data.len().cmp(&other_cell_data.len()) - } + fn apply_cmp( + &self, + cell_data: &::CellData, + other_cell_data: &::CellData, + ) -> Ordering { + cell_data.len().cmp(&other_cell_data.len()) + } } #[derive(Default)] @@ -117,18 +129,18 @@ pub struct ChecklistTypeOptionBuilder(ChecklistTypeOptionPB); impl_into_box_type_option_builder!(ChecklistTypeOptionBuilder); impl_builder_from_json_str_and_from_bytes!(ChecklistTypeOptionBuilder, ChecklistTypeOptionPB); impl ChecklistTypeOptionBuilder { - pub fn add_option(mut self, opt: SelectOptionPB) -> Self { - self.0.options.push(opt); - self - } + pub fn add_option(mut self, opt: SelectOptionPB) -> Self { + self.0.options.push(opt); + self + } } impl TypeOptionBuilder for ChecklistTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::Checklist - } + fn field_type(&self) -> FieldType { + FieldType::Checklist + } - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } + fn serializer(&self) -> &dyn TypeOptionDataSerializer { + &self.0 + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/multi_select_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/multi_select_type_option.rs index 13179b91a0..038d5ff1e5 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/multi_select_type_option.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/multi_select_type_option.rs @@ -4,9 +4,9 @@ use crate::services::cell::{CellDataChangeset, FromCellString, TypeCellData}; use std::cmp::{min, Ordering}; use crate::services::field::{ - default_order, BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds, - SelectOptionPB, SelectTypeOptionSharedAction, SelectedSelectOptions, TypeOption, TypeOptionBuilder, - TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, + default_order, BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, + SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction, SelectedSelectOptions, TypeOption, + TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, }; use bytes::Bytes; use flowy_derive::ProtoBuf; @@ -17,278 +17,296 @@ use serde::{Deserialize, Serialize}; // Multiple select #[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] pub struct MultiSelectTypeOptionPB { - #[pb(index = 1)] - pub options: Vec, + #[pb(index = 1)] + pub options: Vec, - #[pb(index = 2)] - pub disable_color: bool, + #[pb(index = 2)] + pub disable_color: bool, } impl_type_option!(MultiSelectTypeOptionPB, FieldType::MultiSelect); impl TypeOption for MultiSelectTypeOptionPB { - type CellData = SelectOptionIds; - type CellChangeset = SelectOptionCellChangeset; - type CellProtobufType = SelectOptionCellDataPB; - type CellFilter = SelectOptionFilterPB; + type CellData = SelectOptionIds; + type CellChangeset = SelectOptionCellChangeset; + type CellProtobufType = SelectOptionCellDataPB; + type CellFilter = SelectOptionFilterPB; } impl TypeOptionCellData for MultiSelectTypeOptionPB { - fn convert_to_protobuf(&self, cell_data: ::CellData) -> ::CellProtobufType { - self.get_selected_options(cell_data) - } + fn convert_to_protobuf( + &self, + cell_data: ::CellData, + ) -> ::CellProtobufType { + self.get_selected_options(cell_data) + } - fn decode_type_option_cell_str(&self, cell_str: String) -> FlowyResult<::CellData> { - SelectOptionIds::from_cell_str(&cell_str) - } + fn decode_type_option_cell_str( + &self, + cell_str: String, + ) -> FlowyResult<::CellData> { + SelectOptionIds::from_cell_str(&cell_str) + } } impl SelectTypeOptionSharedAction for MultiSelectTypeOptionPB { - fn number_of_max_options(&self) -> Option { - None - } + fn number_of_max_options(&self) -> Option { + None + } - fn options(&self) -> &Vec { - &self.options - } + fn options(&self) -> &Vec { + &self.options + } - fn mut_options(&mut self) -> &mut Vec { - &mut self.options - } + fn mut_options(&mut self) -> &mut Vec { + &mut self.options + } } impl CellDataChangeset for MultiSelectTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - let insert_option_ids = changeset - .insert_option_ids - .into_iter() - .filter(|insert_option_id| self.options.iter().any(|option| &option.id == insert_option_id)) - .collect::>(); + fn apply_changeset( + &self, + changeset: ::CellChangeset, + type_cell_data: Option, + ) -> FlowyResult<(String, ::CellData)> { + let insert_option_ids = changeset + .insert_option_ids + .into_iter() + .filter(|insert_option_id| { + self + .options + .iter() + .any(|option| &option.id == insert_option_id) + }) + .collect::>(); - let select_option_ids = match type_cell_data { - None => SelectOptionIds::from(insert_option_ids), - Some(type_cell_data) => { - let mut select_ids: SelectOptionIds = type_cell_data.cell_str.into(); - for insert_option_id in insert_option_ids { - if !select_ids.contains(&insert_option_id) { - select_ids.push(insert_option_id); - } - } + let select_option_ids = match type_cell_data { + None => SelectOptionIds::from(insert_option_ids), + Some(type_cell_data) => { + let mut select_ids: SelectOptionIds = type_cell_data.cell_str.into(); + for insert_option_id in insert_option_ids { + if !select_ids.contains(&insert_option_id) { + select_ids.push(insert_option_id); + } + } - for delete_option_id in changeset.delete_option_ids { - select_ids.retain(|id| id != &delete_option_id); - } + for delete_option_id in changeset.delete_option_ids { + select_ids.retain(|id| id != &delete_option_id); + } - tracing::trace!("Multi-select cell data: {}", select_ids.to_string()); - select_ids - } - }; - Ok((select_option_ids.to_string(), select_option_ids)) - } + tracing::trace!("Multi-select cell data: {}", select_ids.to_string()); + select_ids + }, + }; + Ok((select_option_ids.to_string(), select_option_ids)) + } } impl TypeOptionCellDataFilter for MultiSelectTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_multi_select() { - return true; - } - let selected_options = SelectedSelectOptions::from(self.get_selected_options(cell_data.clone())); - filter.is_visible(&selected_options, FieldType::MultiSelect) + fn apply_filter( + &self, + filter: &::CellFilter, + field_type: &FieldType, + cell_data: &::CellData, + ) -> bool { + if !field_type.is_multi_select() { + return true; } + let selected_options = + SelectedSelectOptions::from(self.get_selected_options(cell_data.clone())); + filter.is_visible(&selected_options, FieldType::MultiSelect) + } } impl TypeOptionCellDataCompare for MultiSelectTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - for i in 0..min(cell_data.len(), other_cell_data.len()) { - let order = match ( - cell_data - .get(i) - .and_then(|id| self.options.iter().find(|option| &option.id == id)), - other_cell_data - .get(i) - .and_then(|id| self.options.iter().find(|option| &option.id == id)), - ) { - (Some(left), Some(right)) => left.name.cmp(&right.name), - (Some(_), None) => Ordering::Greater, - (None, Some(_)) => Ordering::Less, - (None, None) => default_order(), - }; + fn apply_cmp( + &self, + cell_data: &::CellData, + other_cell_data: &::CellData, + ) -> Ordering { + for i in 0..min(cell_data.len(), other_cell_data.len()) { + let order = match ( + cell_data + .get(i) + .and_then(|id| self.options.iter().find(|option| &option.id == id)), + other_cell_data + .get(i) + .and_then(|id| self.options.iter().find(|option| &option.id == id)), + ) { + (Some(left), Some(right)) => left.name.cmp(&right.name), + (Some(_), None) => Ordering::Greater, + (None, Some(_)) => Ordering::Less, + (None, None) => default_order(), + }; - if order.is_ne() { - return order; - } - } - default_order() + if order.is_ne() { + return order; + } } + default_order() + } } #[derive(Default)] pub struct MultiSelectTypeOptionBuilder(MultiSelectTypeOptionPB); impl_into_box_type_option_builder!(MultiSelectTypeOptionBuilder); impl_builder_from_json_str_and_from_bytes!(MultiSelectTypeOptionBuilder, MultiSelectTypeOptionPB); impl MultiSelectTypeOptionBuilder { - pub fn add_option(mut self, opt: SelectOptionPB) -> Self { - self.0.options.push(opt); - self - } + pub fn add_option(mut self, opt: SelectOptionPB) -> Self { + self.0.options.push(opt); + self + } } impl TypeOptionBuilder for MultiSelectTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::MultiSelect - } + fn field_type(&self) -> FieldType { + FieldType::MultiSelect + } - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } + fn serializer(&self) -> &dyn TypeOptionDataSerializer { + &self.0 + } } #[cfg(test)] mod tests { - use crate::entities::FieldType; - use crate::services::cell::CellDataChangeset; - use crate::services::field::type_options::selection_type_option::*; - use crate::services::field::{CheckboxTypeOptionBuilder, FieldBuilder, TypeOptionBuilder, TypeOptionTransform}; - use crate::services::field::{MultiSelectTypeOptionBuilder, MultiSelectTypeOptionPB}; + use crate::entities::FieldType; + use crate::services::cell::CellDataChangeset; + use crate::services::field::type_options::selection_type_option::*; + use crate::services::field::{ + CheckboxTypeOptionBuilder, FieldBuilder, TypeOptionBuilder, TypeOptionTransform, + }; + use crate::services::field::{MultiSelectTypeOptionBuilder, MultiSelectTypeOptionPB}; - #[test] - fn multi_select_transform_with_checkbox_type_option_test() { - let checkbox_type_option_builder = CheckboxTypeOptionBuilder::default(); - let checkbox_type_option_data = checkbox_type_option_builder.serializer().json_str(); + #[test] + fn multi_select_transform_with_checkbox_type_option_test() { + let checkbox_type_option_builder = CheckboxTypeOptionBuilder::default(); + let checkbox_type_option_data = checkbox_type_option_builder.serializer().json_str(); - let mut multi_select = MultiSelectTypeOptionBuilder::default().0; - multi_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data.clone()); - debug_assert_eq!(multi_select.options.len(), 2); + let mut multi_select = MultiSelectTypeOptionBuilder::default().0; + multi_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data.clone()); + debug_assert_eq!(multi_select.options.len(), 2); - // Already contain the yes/no option. It doesn't need to insert new options - multi_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data); - debug_assert_eq!(multi_select.options.len(), 2); - } + // Already contain the yes/no option. It doesn't need to insert new options + multi_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data); + debug_assert_eq!(multi_select.options.len(), 2); + } - #[test] - fn multi_select_transform_with_single_select_type_option_test() { - let mut singleselect_type_option_builder = SingleSelectTypeOptionBuilder::default(); + #[test] + fn multi_select_transform_with_single_select_type_option_test() { + let mut singleselect_type_option_builder = SingleSelectTypeOptionBuilder::default(); - let google = SelectOptionPB::new("Google"); - singleselect_type_option_builder = singleselect_type_option_builder.add_option(google); + let google = SelectOptionPB::new("Google"); + singleselect_type_option_builder = singleselect_type_option_builder.add_option(google); - let facebook = SelectOptionPB::new("Facebook"); - singleselect_type_option_builder = singleselect_type_option_builder.add_option(facebook); + let facebook = SelectOptionPB::new("Facebook"); + singleselect_type_option_builder = singleselect_type_option_builder.add_option(facebook); - let singleselect_type_option_data = singleselect_type_option_builder.serializer().json_str(); + let singleselect_type_option_data = singleselect_type_option_builder.serializer().json_str(); - let mut multi_select = MultiSelectTypeOptionBuilder::default().0; - multi_select.transform_type_option(FieldType::MultiSelect, singleselect_type_option_data.clone()); - debug_assert_eq!(multi_select.options.len(), 2); + let mut multi_select = MultiSelectTypeOptionBuilder::default().0; + multi_select.transform_type_option( + FieldType::MultiSelect, + singleselect_type_option_data.clone(), + ); + debug_assert_eq!(multi_select.options.len(), 2); - // Already contain the yes/no option. It doesn't need to insert new options - multi_select.transform_type_option(FieldType::MultiSelect, singleselect_type_option_data); - debug_assert_eq!(multi_select.options.len(), 2); - } + // Already contain the yes/no option. It doesn't need to insert new options + multi_select.transform_type_option(FieldType::MultiSelect, singleselect_type_option_data); + debug_assert_eq!(multi_select.options.len(), 2); + } - // #[test] + // #[test] - #[test] - fn multi_select_insert_multi_option_test() { - let google = SelectOptionPB::new("Google"); - let facebook = SelectOptionPB::new("Facebook"); - let multi_select = MultiSelectTypeOptionBuilder::default() - .add_option(google.clone()) - .add_option(facebook.clone()); + #[test] + fn multi_select_insert_multi_option_test() { + let google = SelectOptionPB::new("Google"); + let facebook = SelectOptionPB::new("Facebook"); + let multi_select = MultiSelectTypeOptionBuilder::default() + .add_option(google.clone()) + .add_option(facebook.clone()); - let field_rev = FieldBuilder::new(multi_select).name("Platform").build(); - let type_option = MultiSelectTypeOptionPB::from(&field_rev); - let option_ids = vec![google.id, facebook.id]; - let changeset = SelectOptionCellChangeset::from_insert_options(option_ids.clone()); - let select_option_ids: SelectOptionIds = type_option.apply_changeset(changeset, None).unwrap().1; + let field_rev = FieldBuilder::new(multi_select).name("Platform").build(); + let type_option = MultiSelectTypeOptionPB::from(&field_rev); + let option_ids = vec![google.id, facebook.id]; + let changeset = SelectOptionCellChangeset::from_insert_options(option_ids.clone()); + let select_option_ids: SelectOptionIds = + type_option.apply_changeset(changeset, None).unwrap().1; - assert_eq!(&*select_option_ids, &option_ids); - } + assert_eq!(&*select_option_ids, &option_ids); + } - #[test] - fn multi_select_unselect_multi_option_test() { - let google = SelectOptionPB::new("Google"); - let facebook = SelectOptionPB::new("Facebook"); - let multi_select = MultiSelectTypeOptionBuilder::default() - .add_option(google.clone()) - .add_option(facebook.clone()); + #[test] + fn multi_select_unselect_multi_option_test() { + let google = SelectOptionPB::new("Google"); + let facebook = SelectOptionPB::new("Facebook"); + let multi_select = MultiSelectTypeOptionBuilder::default() + .add_option(google.clone()) + .add_option(facebook.clone()); - let field_rev = FieldBuilder::new(multi_select).name("Platform").build(); - let type_option = MultiSelectTypeOptionPB::from(&field_rev); - let option_ids = vec![google.id, facebook.id]; + let field_rev = FieldBuilder::new(multi_select).name("Platform").build(); + let type_option = MultiSelectTypeOptionPB::from(&field_rev); + let option_ids = vec![google.id, facebook.id]; - // insert - let changeset = SelectOptionCellChangeset::from_insert_options(option_ids.clone()); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert_eq!(&*select_option_ids, &option_ids); + // insert + let changeset = SelectOptionCellChangeset::from_insert_options(option_ids.clone()); + let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; + assert_eq!(&*select_option_ids, &option_ids); - // delete - let changeset = SelectOptionCellChangeset::from_delete_options(option_ids); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert!(select_option_ids.is_empty()); - } + // delete + let changeset = SelectOptionCellChangeset::from_delete_options(option_ids); + let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; + assert!(select_option_ids.is_empty()); + } - #[test] - fn multi_select_insert_single_option_test() { - let google = SelectOptionPB::new("Google"); - let multi_select = MultiSelectTypeOptionBuilder::default().add_option(google.clone()); + #[test] + fn multi_select_insert_single_option_test() { + let google = SelectOptionPB::new("Google"); + let multi_select = MultiSelectTypeOptionBuilder::default().add_option(google.clone()); - let field_rev = FieldBuilder::new(multi_select) - .name("Platform") - .visibility(true) - .build(); + let field_rev = FieldBuilder::new(multi_select) + .name("Platform") + .visibility(true) + .build(); - let type_option = MultiSelectTypeOptionPB::from(&field_rev); - let changeset = SelectOptionCellChangeset::from_insert_option_id(&google.id); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert_eq!(select_option_ids.to_string(), google.id); - } + let type_option = MultiSelectTypeOptionPB::from(&field_rev); + let changeset = SelectOptionCellChangeset::from_insert_option_id(&google.id); + let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; + assert_eq!(select_option_ids.to_string(), google.id); + } - #[test] - fn multi_select_insert_non_exist_option_test() { - let google = SelectOptionPB::new("Google"); - let multi_select = MultiSelectTypeOptionBuilder::default(); - let field_rev = FieldBuilder::new(multi_select) - .name("Platform") - .visibility(true) - .build(); + #[test] + fn multi_select_insert_non_exist_option_test() { + let google = SelectOptionPB::new("Google"); + let multi_select = MultiSelectTypeOptionBuilder::default(); + let field_rev = FieldBuilder::new(multi_select) + .name("Platform") + .visibility(true) + .build(); - let type_option = MultiSelectTypeOptionPB::from(&field_rev); - let changeset = SelectOptionCellChangeset::from_insert_option_id(&google.id); - let (_, select_option_ids) = type_option.apply_changeset(changeset, None).unwrap(); - assert!(select_option_ids.is_empty()); - } + let type_option = MultiSelectTypeOptionPB::from(&field_rev); + let changeset = SelectOptionCellChangeset::from_insert_option_id(&google.id); + let (_, select_option_ids) = type_option.apply_changeset(changeset, None).unwrap(); + assert!(select_option_ids.is_empty()); + } - #[test] - fn multi_select_insert_invalid_option_id_test() { - let google = SelectOptionPB::new("Google"); - let multi_select = MultiSelectTypeOptionBuilder::default().add_option(google); + #[test] + fn multi_select_insert_invalid_option_id_test() { + let google = SelectOptionPB::new("Google"); + let multi_select = MultiSelectTypeOptionBuilder::default().add_option(google); - let field_rev = FieldBuilder::new(multi_select) - .name("Platform") - .visibility(true) - .build(); + let field_rev = FieldBuilder::new(multi_select) + .name("Platform") + .visibility(true) + .build(); - let type_option = MultiSelectTypeOptionPB::from(&field_rev); + let type_option = MultiSelectTypeOptionPB::from(&field_rev); - // empty option id string - let changeset = SelectOptionCellChangeset::from_insert_option_id(""); - let (cell_str, _) = type_option.apply_changeset(changeset, None).unwrap(); - assert_eq!(cell_str, ""); + // empty option id string + let changeset = SelectOptionCellChangeset::from_insert_option_id(""); + let (cell_str, _) = type_option.apply_changeset(changeset, None).unwrap(); + assert_eq!(cell_str, ""); - let changeset = SelectOptionCellChangeset::from_insert_option_id("123,456"); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert!(select_option_ids.is_empty()); - } + let changeset = SelectOptionCellChangeset::from_insert_option_id("123,456"); + let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; + assert!(select_option_ids.is_empty()); + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/select_filter.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/select_filter.rs index 528c4178b8..bfade9dbf1 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/select_filter.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/select_filter.rs @@ -4,281 +4,312 @@ use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB}; use crate::services::field::SelectedSelectOptions; impl SelectOptionFilterPB { - pub fn is_visible(&self, selected_options: &SelectedSelectOptions, field_type: FieldType) -> bool { - let selected_option_ids: Vec<&String> = selected_options.options.iter().map(|option| &option.id).collect(); - match self.condition { - SelectOptionConditionPB::OptionIs => match field_type { - FieldType::SingleSelect => { - if self.option_ids.is_empty() { - return true; - } + pub fn is_visible( + &self, + selected_options: &SelectedSelectOptions, + field_type: FieldType, + ) -> bool { + let selected_option_ids: Vec<&String> = selected_options + .options + .iter() + .map(|option| &option.id) + .collect(); + match self.condition { + SelectOptionConditionPB::OptionIs => match field_type { + FieldType::SingleSelect => { + if self.option_ids.is_empty() { + return true; + } - if selected_options.options.is_empty() { - return false; - } + if selected_options.options.is_empty() { + return false; + } - let required_options = self - .option_ids - .iter() - .filter(|id| selected_option_ids.contains(id)) - .collect::>(); + let required_options = self + .option_ids + .iter() + .filter(|id| selected_option_ids.contains(id)) + .collect::>(); - !required_options.is_empty() - } - FieldType::MultiSelect => { - if self.option_ids.is_empty() { - return true; - } + !required_options.is_empty() + }, + FieldType::MultiSelect => { + if self.option_ids.is_empty() { + return true; + } - let required_options = self - .option_ids - .iter() - .filter(|id| selected_option_ids.contains(id)) - .collect::>(); + let required_options = self + .option_ids + .iter() + .filter(|id| selected_option_ids.contains(id)) + .collect::>(); - !required_options.is_empty() - } - _ => false, - }, - SelectOptionConditionPB::OptionIsNot => match field_type { - FieldType::SingleSelect => { - if self.option_ids.is_empty() { - return true; - } + !required_options.is_empty() + }, + _ => false, + }, + SelectOptionConditionPB::OptionIsNot => match field_type { + FieldType::SingleSelect => { + if self.option_ids.is_empty() { + return true; + } - if selected_options.options.is_empty() { - return false; - } + if selected_options.options.is_empty() { + return false; + } - let required_options = self - .option_ids - .iter() - .filter(|id| selected_option_ids.contains(id)) - .collect::>(); + let required_options = self + .option_ids + .iter() + .filter(|id| selected_option_ids.contains(id)) + .collect::>(); - required_options.is_empty() - } - FieldType::MultiSelect => { - let required_options = self - .option_ids - .iter() - .filter(|id| selected_option_ids.contains(id)) - .collect::>(); + required_options.is_empty() + }, + FieldType::MultiSelect => { + let required_options = self + .option_ids + .iter() + .filter(|id| selected_option_ids.contains(id)) + .collect::>(); - required_options.is_empty() - } - _ => false, - }, - SelectOptionConditionPB::OptionIsEmpty => selected_option_ids.is_empty(), - SelectOptionConditionPB::OptionIsNotEmpty => !selected_option_ids.is_empty(), - } + required_options.is_empty() + }, + _ => false, + }, + SelectOptionConditionPB::OptionIsEmpty => selected_option_ids.is_empty(), + SelectOptionConditionPB::OptionIsNotEmpty => !selected_option_ids.is_empty(), } + } } #[cfg(test)] mod tests { - #![allow(clippy::all)] - use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB}; - use crate::services::field::selection_type_option::{SelectOptionPB, SelectedSelectOptions}; + #![allow(clippy::all)] + use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB}; + use crate::services::field::selection_type_option::{SelectOptionPB, SelectedSelectOptions}; - #[test] - fn select_option_filter_is_empty_test() { - let option = SelectOptionPB::new("A"); - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIsEmpty, - option_ids: vec![], - }; + #[test] + fn select_option_filter_is_empty_test() { + let option = SelectOptionPB::new("A"); + let filter = SelectOptionFilterPB { + condition: SelectOptionConditionPB::OptionIsEmpty, + option_ids: vec![], + }; - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options: vec![] }, FieldType::SingleSelect), - true - ); - assert_eq!( - filter.is_visible( - &SelectedSelectOptions { - options: vec![option.clone()] - }, - FieldType::SingleSelect - ), - false, - ); + assert_eq!( + filter.is_visible( + &SelectedSelectOptions { options: vec![] }, + FieldType::SingleSelect + ), + true + ); + assert_eq!( + filter.is_visible( + &SelectedSelectOptions { + options: vec![option.clone()] + }, + FieldType::SingleSelect + ), + false, + ); - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options: vec![] }, FieldType::MultiSelect), - true - ); - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options: vec![option] }, FieldType::MultiSelect), - false, - ); + assert_eq!( + filter.is_visible( + &SelectedSelectOptions { options: vec![] }, + FieldType::MultiSelect + ), + true + ); + assert_eq!( + filter.is_visible( + &SelectedSelectOptions { + options: vec![option] + }, + FieldType::MultiSelect + ), + false, + ); + } + + #[test] + fn select_option_filter_is_not_empty_test() { + let option_1 = SelectOptionPB::new("A"); + let option_2 = SelectOptionPB::new("B"); + let filter = SelectOptionFilterPB { + condition: SelectOptionConditionPB::OptionIsNotEmpty, + option_ids: vec![option_1.id.clone(), option_2.id.clone()], + }; + + assert_eq!( + filter.is_visible( + &SelectedSelectOptions { + options: vec![option_1.clone()] + }, + FieldType::SingleSelect + ), + true + ); + assert_eq!( + filter.is_visible( + &SelectedSelectOptions { options: vec![] }, + FieldType::SingleSelect + ), + false, + ); + + assert_eq!( + filter.is_visible( + &SelectedSelectOptions { + options: vec![option_1.clone()] + }, + FieldType::MultiSelect + ), + true + ); + assert_eq!( + filter.is_visible( + &SelectedSelectOptions { options: vec![] }, + FieldType::MultiSelect + ), + false, + ); + } + + #[test] + fn single_select_option_filter_is_not_test() { + let option_1 = SelectOptionPB::new("A"); + let option_2 = SelectOptionPB::new("B"); + let option_3 = SelectOptionPB::new("C"); + let filter = SelectOptionFilterPB { + condition: SelectOptionConditionPB::OptionIsNot, + option_ids: vec![option_1.id.clone(), option_2.id.clone()], + }; + + for (options, is_visible) in vec![ + (vec![option_2.clone()], false), + (vec![option_1.clone()], false), + (vec![option_3.clone()], true), + (vec![option_1.clone(), option_2.clone()], false), + ] { + assert_eq!( + filter.is_visible(&SelectedSelectOptions { options }, FieldType::SingleSelect), + is_visible + ); } + } - #[test] - fn select_option_filter_is_not_empty_test() { - let option_1 = SelectOptionPB::new("A"); - let option_2 = SelectOptionPB::new("B"); - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIsNotEmpty, - option_ids: vec![option_1.id.clone(), option_2.id.clone()], - }; + #[test] + fn single_select_option_filter_is_test() { + let option_1 = SelectOptionPB::new("A"); + let option_2 = SelectOptionPB::new("B"); + let option_3 = SelectOptionPB::new("c"); - assert_eq!( - filter.is_visible( - &SelectedSelectOptions { - options: vec![option_1.clone()] - }, - FieldType::SingleSelect - ), - true - ); - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options: vec![] }, FieldType::SingleSelect), - false, - ); - - assert_eq!( - filter.is_visible( - &SelectedSelectOptions { - options: vec![option_1.clone()] - }, - FieldType::MultiSelect - ), - true - ); - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options: vec![] }, FieldType::MultiSelect), - false, - ); + let filter = SelectOptionFilterPB { + condition: SelectOptionConditionPB::OptionIs, + option_ids: vec![option_1.id.clone()], + }; + for (options, is_visible) in vec![ + (vec![option_1.clone()], true), + (vec![option_2.clone()], false), + (vec![option_3.clone()], false), + (vec![option_1.clone(), option_2.clone()], true), + ] { + assert_eq!( + filter.is_visible(&SelectedSelectOptions { options }, FieldType::SingleSelect), + is_visible + ); } + } - #[test] - fn single_select_option_filter_is_not_test() { - let option_1 = SelectOptionPB::new("A"); - let option_2 = SelectOptionPB::new("B"); - let option_3 = SelectOptionPB::new("C"); - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIsNot, - option_ids: vec![option_1.id.clone(), option_2.id.clone()], - }; + #[test] + fn single_select_option_filter_is_test2() { + let option_1 = SelectOptionPB::new("A"); + let option_2 = SelectOptionPB::new("B"); - for (options, is_visible) in vec![ - (vec![option_2.clone()], false), - (vec![option_1.clone()], false), - (vec![option_3.clone()], true), - (vec![option_1.clone(), option_2.clone()], false), - ] { - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options }, FieldType::SingleSelect), - is_visible - ); - } + let filter = SelectOptionFilterPB { + condition: SelectOptionConditionPB::OptionIs, + option_ids: vec![], + }; + for (options, is_visible) in vec![ + (vec![option_1.clone()], true), + (vec![option_2.clone()], true), + (vec![option_1.clone(), option_2.clone()], true), + ] { + assert_eq!( + filter.is_visible(&SelectedSelectOptions { options }, FieldType::SingleSelect), + is_visible + ); } + } - #[test] - fn single_select_option_filter_is_test() { - let option_1 = SelectOptionPB::new("A"); - let option_2 = SelectOptionPB::new("B"); - let option_3 = SelectOptionPB::new("c"); + #[test] + fn multi_select_option_filter_not_contains_test() { + let option_1 = SelectOptionPB::new("A"); + let option_2 = SelectOptionPB::new("B"); + let option_3 = SelectOptionPB::new("C"); + let filter = SelectOptionFilterPB { + condition: SelectOptionConditionPB::OptionIsNot, + option_ids: vec![option_1.id.clone(), option_2.id.clone()], + }; - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![option_1.id.clone()], - }; - for (options, is_visible) in vec![ - (vec![option_1.clone()], true), - (vec![option_2.clone()], false), - (vec![option_3.clone()], false), - (vec![option_1.clone(), option_2.clone()], true), - ] { - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options }, FieldType::SingleSelect), - is_visible - ); - } + for (options, is_visible) in vec![ + (vec![option_1.clone(), option_2.clone()], false), + (vec![option_1.clone()], false), + (vec![option_2.clone()], false), + (vec![option_3.clone()], true), + ( + vec![option_1.clone(), option_2.clone(), option_3.clone()], + false, + ), + (vec![], true), + ] { + assert_eq!( + filter.is_visible(&SelectedSelectOptions { options }, FieldType::MultiSelect), + is_visible + ); } + } + #[test] + fn multi_select_option_filter_contains_test() { + let option_1 = SelectOptionPB::new("A"); + let option_2 = SelectOptionPB::new("B"); + let option_3 = SelectOptionPB::new("C"); - #[test] - fn single_select_option_filter_is_test2() { - let option_1 = SelectOptionPB::new("A"); - let option_2 = SelectOptionPB::new("B"); - - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![], - }; - for (options, is_visible) in vec![ - (vec![option_1.clone()], true), - (vec![option_2.clone()], true), - (vec![option_1.clone(), option_2.clone()], true), - ] { - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options }, FieldType::SingleSelect), - is_visible - ); - } + let filter = SelectOptionFilterPB { + condition: SelectOptionConditionPB::OptionIs, + option_ids: vec![option_1.id.clone(), option_2.id.clone()], + }; + for (options, is_visible) in vec![ + ( + vec![option_1.clone(), option_2.clone(), option_3.clone()], + true, + ), + (vec![option_2.clone(), option_1.clone()], true), + (vec![option_2.clone()], true), + (vec![option_1.clone(), option_3.clone()], true), + (vec![option_3.clone()], false), + ] { + assert_eq!( + filter.is_visible(&SelectedSelectOptions { options }, FieldType::MultiSelect), + is_visible + ); } + } - #[test] - fn multi_select_option_filter_not_contains_test() { - let option_1 = SelectOptionPB::new("A"); - let option_2 = SelectOptionPB::new("B"); - let option_3 = SelectOptionPB::new("C"); - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIsNot, - option_ids: vec![option_1.id.clone(), option_2.id.clone()], - }; + #[test] + fn multi_select_option_filter_contains_test2() { + let option_1 = SelectOptionPB::new("A"); - for (options, is_visible) in vec![ - (vec![option_1.clone(), option_2.clone()], false), - (vec![option_1.clone()], false), - (vec![option_2.clone()], false), - (vec![option_3.clone()], true), - (vec![option_1.clone(), option_2.clone(), option_3.clone()], false), - (vec![], true), - ] { - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options }, FieldType::MultiSelect), - is_visible - ); - } - } - #[test] - fn multi_select_option_filter_contains_test() { - let option_1 = SelectOptionPB::new("A"); - let option_2 = SelectOptionPB::new("B"); - let option_3 = SelectOptionPB::new("C"); - - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![option_1.id.clone(), option_2.id.clone()], - }; - for (options, is_visible) in vec![ - (vec![option_1.clone(), option_2.clone(), option_3.clone()], true), - (vec![option_2.clone(), option_1.clone()], true), - (vec![option_2.clone()], true), - (vec![option_1.clone(), option_3.clone()], true), - (vec![option_3.clone()], false), - ] { - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options }, FieldType::MultiSelect), - is_visible - ); - } - } - - #[test] - fn multi_select_option_filter_contains_test2() { - let option_1 = SelectOptionPB::new("A"); - - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![], - }; - for (options, is_visible) in vec![(vec![option_1.clone()], true), (vec![], true)] { - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options }, FieldType::MultiSelect), - is_visible - ); - } + let filter = SelectOptionFilterPB { + condition: SelectOptionConditionPB::OptionIs, + option_ids: vec![], + }; + for (options, is_visible) in vec![(vec![option_1.clone()], true), (vec![], true)] { + assert_eq!( + filter.is_visible(&SelectedSelectOptions { options }, FieldType::MultiSelect), + is_visible + ); } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/select_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/select_type_option.rs index 627358aa3e..48fb63b96d 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/select_type_option.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/select_type_option.rs @@ -1,14 +1,14 @@ use crate::entities::parser::NotEmptyStr; use crate::entities::{CellIdPB, CellIdParams, FieldType}; use crate::services::cell::{ - CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangesetString, FromCellString, - ToCellChangesetString, + CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangesetString, + FromCellString, ToCellChangesetString, }; use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformHelper; use crate::services::field::{ - CheckboxCellData, ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB, TypeOption, - TypeOptionCellData, TypeOptionTransform, + CheckboxCellData, ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB, + TypeOption, TypeOptionCellData, TypeOptionTransform, }; use bytes::Bytes; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; @@ -22,231 +22,248 @@ pub const SELECTION_IDS_SEPARATOR: &str = ","; /// [SelectOptionPB] represents an option for a single select, and multiple select. #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, ProtoBuf)] pub struct SelectOptionPB { - #[pb(index = 1)] - pub id: String, + #[pb(index = 1)] + pub id: String, - #[pb(index = 2)] - pub name: String, + #[pb(index = 2)] + pub name: String, - #[pb(index = 3)] - pub color: SelectOptionColorPB, + #[pb(index = 3)] + pub color: SelectOptionColorPB, } pub fn gen_option_id() -> String { - nanoid!(4) + nanoid!(4) } impl SelectOptionPB { - pub fn new(name: &str) -> Self { - SelectOptionPB { - id: gen_option_id(), - name: name.to_owned(), - color: SelectOptionColorPB::default(), - } + pub fn new(name: &str) -> Self { + SelectOptionPB { + id: gen_option_id(), + name: name.to_owned(), + color: SelectOptionColorPB::default(), } + } - pub fn with_color(name: &str, color: SelectOptionColorPB) -> Self { - SelectOptionPB { - id: nanoid!(4), - name: name.to_owned(), - color, - } + pub fn with_color(name: &str, color: SelectOptionColorPB) -> Self { + SelectOptionPB { + id: nanoid!(4), + name: name.to_owned(), + color, } + } } #[derive(ProtoBuf_Enum, PartialEq, Eq, Serialize, Deserialize, Debug, Clone)] #[repr(u8)] pub enum SelectOptionColorPB { - Purple = 0, - Pink = 1, - LightPink = 2, - Orange = 3, - Yellow = 4, - Lime = 5, - Green = 6, - Aqua = 7, - Blue = 8, + Purple = 0, + Pink = 1, + LightPink = 2, + Orange = 3, + Yellow = 4, + Lime = 5, + Green = 6, + Aqua = 7, + Blue = 8, } impl std::default::Default for SelectOptionColorPB { - fn default() -> Self { - SelectOptionColorPB::Purple - } + fn default() -> Self { + SelectOptionColorPB::Purple + } } -pub fn make_selected_options(ids: SelectOptionIds, options: &[SelectOptionPB]) -> Vec { - ids.iter() - .flat_map(|option_id| options.iter().find(|option| &option.id == option_id).cloned()) - .collect() +pub fn make_selected_options( + ids: SelectOptionIds, + options: &[SelectOptionPB], +) -> Vec { + ids + .iter() + .flat_map(|option_id| { + options + .iter() + .find(|option| &option.id == option_id) + .cloned() + }) + .collect() } /// Defines the shared actions used by SingleSelect or Multi-Select. pub trait SelectTypeOptionSharedAction: TypeOptionDataSerializer + Send + Sync { - /// Returns `None` means there is no limited - fn number_of_max_options(&self) -> Option; + /// Returns `None` means there is no limited + fn number_of_max_options(&self) -> Option; - /// Insert the `SelectOptionPB` into corresponding type option. - fn insert_option(&mut self, new_option: SelectOptionPB) { - let options = self.mut_options(); - if let Some(index) = options - .iter() - .position(|option| option.id == new_option.id || option.name == new_option.name) - { - options.remove(index); - options.insert(index, new_option); - } else { - options.insert(0, new_option); - } + /// Insert the `SelectOptionPB` into corresponding type option. + fn insert_option(&mut self, new_option: SelectOptionPB) { + let options = self.mut_options(); + if let Some(index) = options + .iter() + .position(|option| option.id == new_option.id || option.name == new_option.name) + { + options.remove(index); + options.insert(index, new_option); + } else { + options.insert(0, new_option); } + } - fn delete_option(&mut self, delete_option: SelectOptionPB) { - let options = self.mut_options(); - if let Some(index) = options.iter().position(|option| option.id == delete_option.id) { - options.remove(index); - } + fn delete_option(&mut self, delete_option: SelectOptionPB) { + let options = self.mut_options(); + if let Some(index) = options + .iter() + .position(|option| option.id == delete_option.id) + { + options.remove(index); } + } - fn create_option(&self, name: &str) -> SelectOptionPB { - let color = new_select_option_color(self.options()); - SelectOptionPB::with_color(name, color) + fn create_option(&self, name: &str) -> SelectOptionPB { + let color = new_select_option_color(self.options()); + SelectOptionPB::with_color(name, color) + } + + /// Return a list of options that are selected by user + fn get_selected_options(&self, ids: SelectOptionIds) -> SelectOptionCellDataPB { + let mut select_options = make_selected_options(ids, self.options()); + match self.number_of_max_options() { + None => {}, + Some(number_of_max_options) => { + select_options.truncate(number_of_max_options); + }, } - - /// Return a list of options that are selected by user - fn get_selected_options(&self, ids: SelectOptionIds) -> SelectOptionCellDataPB { - let mut select_options = make_selected_options(ids, self.options()); - match self.number_of_max_options() { - None => {} - Some(number_of_max_options) => { - select_options.truncate(number_of_max_options); - } - } - SelectOptionCellDataPB { - options: self.options().clone(), - select_options, - } + SelectOptionCellDataPB { + options: self.options().clone(), + select_options, } + } - fn options(&self) -> &Vec; + fn options(&self) -> &Vec; - fn mut_options(&mut self) -> &mut Vec; + fn mut_options(&mut self) -> &mut Vec; } impl TypeOptionTransform for T where - T: SelectTypeOptionSharedAction - + TypeOption - + TypeOptionDataSerializer - + CellDataDecoder, + T: SelectTypeOptionSharedAction + + TypeOption + + TypeOptionDataSerializer + + CellDataDecoder, { - fn transformable(&self) -> bool { - true - } + fn transformable(&self) -> bool { + true + } - fn transform_type_option(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String) { - SelectOptionTypeOptionTransformHelper::transform_type_option( - self, - &old_type_option_field_type, - old_type_option_data, - ); - } + fn transform_type_option( + &mut self, + old_type_option_field_type: FieldType, + old_type_option_data: String, + ) { + SelectOptionTypeOptionTransformHelper::transform_type_option( + self, + &old_type_option_field_type, + old_type_option_data, + ); + } - fn transform_type_option_cell_str( - &self, - cell_str: &str, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> Option<::CellData> { - match decoded_field_type { - FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => None, - FieldType::Checkbox => match CheckboxCellData::from_cell_str(cell_str) { - Ok(checkbox_cell_data) => { - let cell_content = checkbox_cell_data.to_string(); - let mut transformed_ids = Vec::new(); - let options = self.options(); - if let Some(option) = options.iter().find(|option| option.name == cell_content) { - transformed_ids.push(option.id.clone()); - } - Some(SelectOptionIds::from(transformed_ids)) - } - Err(_) => None, - }, - FieldType::RichText => SelectOptionIds::from_cell_str(cell_str).ok(), - _ => Some(SelectOptionIds::from(vec![])), - } + fn transform_type_option_cell_str( + &self, + cell_str: &str, + decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> Option<::CellData> { + match decoded_field_type { + FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => None, + FieldType::Checkbox => match CheckboxCellData::from_cell_str(cell_str) { + Ok(checkbox_cell_data) => { + let cell_content = checkbox_cell_data.to_string(); + let mut transformed_ids = Vec::new(); + let options = self.options(); + if let Some(option) = options.iter().find(|option| option.name == cell_content) { + transformed_ids.push(option.id.clone()); + } + Some(SelectOptionIds::from(transformed_ids)) + }, + Err(_) => None, + }, + FieldType::RichText => SelectOptionIds::from_cell_str(cell_str).ok(), + _ => Some(SelectOptionIds::from(vec![])), } + } } impl CellDataDecoder for T where - T: SelectTypeOptionSharedAction + TypeOption + TypeOptionCellData, + T: SelectTypeOptionSharedAction + TypeOption + TypeOptionCellData, { - fn decode_cell_str( - &self, - cell_str: String, - _decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult<::CellData> { - self.decode_type_option_cell_str(cell_str) - } + fn decode_cell_str( + &self, + cell_str: String, + _decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> FlowyResult<::CellData> { + self.decode_type_option_cell_str(cell_str) + } - fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { - self.get_selected_options(cell_data) - .select_options - .into_iter() - .map(|option| option.name) - .collect::>() - .join(SELECTION_IDS_SEPARATOR) - } + fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { + self + .get_selected_options(cell_data) + .select_options + .into_iter() + .map(|option| option.name) + .collect::>() + .join(SELECTION_IDS_SEPARATOR) + } } pub fn select_type_option_from_field_rev( - field_rev: &FieldRevision, + field_rev: &FieldRevision, ) -> FlowyResult> { - let field_type: FieldType = field_rev.ty.into(); - match &field_type { - FieldType::SingleSelect => { - let type_option = SingleSelectTypeOptionPB::from(field_rev); - Ok(Box::new(type_option)) - } - FieldType::MultiSelect => { - let type_option = MultiSelectTypeOptionPB::from(field_rev); - Ok(Box::new(type_option)) - } - FieldType::Checklist => { - let type_option = ChecklistTypeOptionPB::from(field_rev); - Ok(Box::new(type_option)) - } - ty => { - tracing::error!("Unsupported field type: {:?} for this handler", ty); - Err(ErrorCode::FieldInvalidOperation.into()) - } - } + let field_type: FieldType = field_rev.ty.into(); + match &field_type { + FieldType::SingleSelect => { + let type_option = SingleSelectTypeOptionPB::from(field_rev); + Ok(Box::new(type_option)) + }, + FieldType::MultiSelect => { + let type_option = MultiSelectTypeOptionPB::from(field_rev); + Ok(Box::new(type_option)) + }, + FieldType::Checklist => { + let type_option = ChecklistTypeOptionPB::from(field_rev); + Ok(Box::new(type_option)) + }, + ty => { + tracing::error!("Unsupported field type: {:?} for this handler", ty); + Err(ErrorCode::FieldInvalidOperation.into()) + }, + } } pub fn new_select_option_color(options: &Vec) -> SelectOptionColorPB { - let mut freq: Vec = vec![0; 9]; + let mut freq: Vec = vec![0; 9]; - for option in options { - freq[option.color.to_owned() as usize] += 1; - } + for option in options { + freq[option.color.to_owned() as usize] += 1; + } - match freq - .into_iter() - .enumerate() - .min_by_key(|(_, v)| *v) - .map(|(idx, _val)| idx) - .unwrap() - { - 0 => SelectOptionColorPB::Purple, - 1 => SelectOptionColorPB::Pink, - 2 => SelectOptionColorPB::LightPink, - 3 => SelectOptionColorPB::Orange, - 4 => SelectOptionColorPB::Yellow, - 5 => SelectOptionColorPB::Lime, - 6 => SelectOptionColorPB::Green, - 7 => SelectOptionColorPB::Aqua, - 8 => SelectOptionColorPB::Blue, - _ => SelectOptionColorPB::Purple, - } + match freq + .into_iter() + .enumerate() + .min_by_key(|(_, v)| *v) + .map(|(idx, _val)| idx) + .unwrap() + { + 0 => SelectOptionColorPB::Purple, + 1 => SelectOptionColorPB::Pink, + 2 => SelectOptionColorPB::LightPink, + 3 => SelectOptionColorPB::Orange, + 4 => SelectOptionColorPB::Yellow, + 5 => SelectOptionColorPB::Lime, + 6 => SelectOptionColorPB::Green, + 7 => SelectOptionColorPB::Aqua, + 8 => SelectOptionColorPB::Blue, + _ => SelectOptionColorPB::Purple, + } } /// List of select option ids @@ -258,276 +275,279 @@ pub fn new_select_option_color(options: &Vec) -> SelectOptionCol pub struct SelectOptionIds(Vec); impl SelectOptionIds { - pub fn new() -> Self { - Self::default() - } - pub fn into_inner(self) -> Vec { - self.0 - } + pub fn new() -> Self { + Self::default() + } + pub fn into_inner(self) -> Vec { + self.0 + } } impl FromCellString for SelectOptionIds { - fn from_cell_str(s: &str) -> FlowyResult - where - Self: Sized, - { - Ok(Self::from(s.to_owned())) - } + fn from_cell_str(s: &str) -> FlowyResult + where + Self: Sized, + { + Ok(Self::from(s.to_owned())) + } } impl std::convert::From for SelectOptionIds { - fn from(s: String) -> Self { - if s.is_empty() { - return Self(vec![]); - } - - let ids = s - .split(SELECTION_IDS_SEPARATOR) - .map(|id| id.to_string()) - .collect::>(); - Self(ids) + fn from(s: String) -> Self { + if s.is_empty() { + return Self(vec![]); } + + let ids = s + .split(SELECTION_IDS_SEPARATOR) + .map(|id| id.to_string()) + .collect::>(); + Self(ids) + } } impl std::convert::From> for SelectOptionIds { - fn from(ids: Vec) -> Self { - let ids = ids.into_iter().filter(|id| !id.is_empty()).collect::>(); - Self(ids) - } + fn from(ids: Vec) -> Self { + let ids = ids + .into_iter() + .filter(|id| !id.is_empty()) + .collect::>(); + Self(ids) + } } impl ToString for SelectOptionIds { - /// Returns a string that consists list of ids, placing a commas - /// separator between each - fn to_string(&self) -> String { - self.0.join(SELECTION_IDS_SEPARATOR) - } + /// Returns a string that consists list of ids, placing a commas + /// separator between each + fn to_string(&self) -> String { + self.0.join(SELECTION_IDS_SEPARATOR) + } } impl std::convert::From> for SelectOptionIds { - fn from(s: Option) -> Self { - match s { - None => Self(vec![]), - Some(s) => Self::from(s), - } + fn from(s: Option) -> Self { + match s { + None => Self(vec![]), + Some(s) => Self::from(s), } + } } impl std::ops::Deref for SelectOptionIds { - type Target = Vec; + type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } impl std::ops::DerefMut for SelectOptionIds { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } } impl DecodedCellData for SelectOptionIds { - type Object = SelectOptionIds; + type Object = SelectOptionIds; - fn is_empty(&self) -> bool { - self.0.is_empty() - } + fn is_empty(&self) -> bool { + self.0.is_empty() + } } pub struct SelectOptionIdsParser(); impl CellProtobufBlobParser for SelectOptionIdsParser { - type Object = SelectOptionIds; - fn parser(bytes: &Bytes) -> FlowyResult { - match String::from_utf8(bytes.to_vec()) { - Ok(s) => Ok(SelectOptionIds::from(s)), - Err(_) => Ok(SelectOptionIds::from("".to_owned())), - } + type Object = SelectOptionIds; + fn parser(bytes: &Bytes) -> FlowyResult { + match String::from_utf8(bytes.to_vec()) { + Ok(s) => Ok(SelectOptionIds::from(s)), + Err(_) => Ok(SelectOptionIds::from("".to_owned())), } + } } impl DecodedCellData for SelectOptionCellDataPB { - type Object = SelectOptionCellDataPB; + type Object = SelectOptionCellDataPB; - fn is_empty(&self) -> bool { - self.select_options.is_empty() - } + fn is_empty(&self) -> bool { + self.select_options.is_empty() + } } pub struct SelectOptionCellDataParser(); impl CellProtobufBlobParser for SelectOptionCellDataParser { - type Object = SelectOptionCellDataPB; + type Object = SelectOptionCellDataPB; - fn parser(bytes: &Bytes) -> FlowyResult { - SelectOptionCellDataPB::try_from(bytes.as_ref()).map_err(internal_error) - } + fn parser(bytes: &Bytes) -> FlowyResult { + SelectOptionCellDataPB::try_from(bytes.as_ref()).map_err(internal_error) + } } #[derive(Clone, Debug, Default, ProtoBuf)] pub struct SelectOptionCellChangesetPB { - #[pb(index = 1)] - pub cell_identifier: CellIdPB, + #[pb(index = 1)] + pub cell_identifier: CellIdPB, - #[pb(index = 2)] - pub insert_option_ids: Vec, + #[pb(index = 2)] + pub insert_option_ids: Vec, - #[pb(index = 3)] - pub delete_option_ids: Vec, + #[pb(index = 3)] + pub delete_option_ids: Vec, } pub struct SelectOptionCellChangesetParams { - pub cell_identifier: CellIdParams, - pub insert_option_ids: Vec, - pub delete_option_ids: Vec, + pub cell_identifier: CellIdParams, + pub insert_option_ids: Vec, + pub delete_option_ids: Vec, } impl TryInto for SelectOptionCellChangesetPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let cell_identifier: CellIdParams = self.cell_identifier.try_into()?; - let insert_option_ids = self - .insert_option_ids - .into_iter() - .flat_map(|option_id| match NotEmptyStr::parse(option_id) { - Ok(option_id) => Some(option_id.0), - Err(_) => { - tracing::error!("The insert option id should not be empty"); - None - } - }) - .collect::>(); + fn try_into(self) -> Result { + let cell_identifier: CellIdParams = self.cell_identifier.try_into()?; + let insert_option_ids = self + .insert_option_ids + .into_iter() + .flat_map(|option_id| match NotEmptyStr::parse(option_id) { + Ok(option_id) => Some(option_id.0), + Err(_) => { + tracing::error!("The insert option id should not be empty"); + None + }, + }) + .collect::>(); - let delete_option_ids = self - .delete_option_ids - .into_iter() - .flat_map(|option_id| match NotEmptyStr::parse(option_id) { - Ok(option_id) => Some(option_id.0), - Err(_) => { - tracing::error!("The deleted option id should not be empty"); - None - } - }) - .collect::>(); + let delete_option_ids = self + .delete_option_ids + .into_iter() + .flat_map(|option_id| match NotEmptyStr::parse(option_id) { + Ok(option_id) => Some(option_id.0), + Err(_) => { + tracing::error!("The deleted option id should not be empty"); + None + }, + }) + .collect::>(); - Ok(SelectOptionCellChangesetParams { - cell_identifier, - insert_option_ids, - delete_option_ids, - }) - } + Ok(SelectOptionCellChangesetParams { + cell_identifier, + insert_option_ids, + delete_option_ids, + }) + } } #[derive(Clone, Serialize, Deserialize, Debug)] pub struct SelectOptionCellChangeset { - pub insert_option_ids: Vec, - pub delete_option_ids: Vec, + pub insert_option_ids: Vec, + pub delete_option_ids: Vec, } impl FromCellChangesetString for SelectOptionCellChangeset { - fn from_changeset(changeset: String) -> FlowyResult - where - Self: Sized, - { - serde_json::from_str::(&changeset).map_err(internal_error) - } + fn from_changeset(changeset: String) -> FlowyResult + where + Self: Sized, + { + serde_json::from_str::(&changeset).map_err(internal_error) + } } impl ToCellChangesetString for SelectOptionCellChangeset { - fn to_cell_changeset_str(&self) -> String { - serde_json::to_string(self).unwrap_or_default() - } + fn to_cell_changeset_str(&self) -> String { + serde_json::to_string(self).unwrap_or_default() + } } impl SelectOptionCellChangeset { - pub fn from_insert_option_id(option_id: &str) -> Self { - SelectOptionCellChangeset { - insert_option_ids: vec![option_id.to_string()], - delete_option_ids: vec![], - } + pub fn from_insert_option_id(option_id: &str) -> Self { + SelectOptionCellChangeset { + insert_option_ids: vec![option_id.to_string()], + delete_option_ids: vec![], } + } - pub fn from_insert_options(option_ids: Vec) -> Self { - SelectOptionCellChangeset { - insert_option_ids: option_ids, - delete_option_ids: vec![], - } + pub fn from_insert_options(option_ids: Vec) -> Self { + SelectOptionCellChangeset { + insert_option_ids: option_ids, + delete_option_ids: vec![], } + } - pub fn from_delete_option_id(option_id: &str) -> Self { - SelectOptionCellChangeset { - insert_option_ids: vec![], - delete_option_ids: vec![option_id.to_string()], - } + pub fn from_delete_option_id(option_id: &str) -> Self { + SelectOptionCellChangeset { + insert_option_ids: vec![], + delete_option_ids: vec![option_id.to_string()], } + } - pub fn from_delete_options(option_ids: Vec) -> Self { - SelectOptionCellChangeset { - insert_option_ids: vec![], - delete_option_ids: option_ids, - } + pub fn from_delete_options(option_ids: Vec) -> Self { + SelectOptionCellChangeset { + insert_option_ids: vec![], + delete_option_ids: option_ids, } + } } /// [SelectOptionCellDataPB] contains a list of user's selected options and a list of all the options /// that the cell can use. #[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] pub struct SelectOptionCellDataPB { - /// The available options that the cell can use. - #[pb(index = 1)] - pub options: Vec, + /// The available options that the cell can use. + #[pb(index = 1)] + pub options: Vec, - /// The selected options for the cell. - #[pb(index = 2)] - pub select_options: Vec, + /// The selected options for the cell. + #[pb(index = 2)] + pub select_options: Vec, } /// [SelectOptionChangesetPB] describes the changes of a FieldTypeOptionData. For the moment, /// it is used by [MultiSelectTypeOptionPB] and [SingleSelectTypeOptionPB]. #[derive(Clone, Debug, Default, ProtoBuf)] pub struct SelectOptionChangesetPB { - #[pb(index = 1)] - pub cell_identifier: CellIdPB, + #[pb(index = 1)] + pub cell_identifier: CellIdPB, - #[pb(index = 2)] - pub insert_options: Vec, + #[pb(index = 2)] + pub insert_options: Vec, - #[pb(index = 3)] - pub update_options: Vec, + #[pb(index = 3)] + pub update_options: Vec, - #[pb(index = 4)] - pub delete_options: Vec, + #[pb(index = 4)] + pub delete_options: Vec, } pub struct SelectOptionChangeset { - pub cell_path: CellIdParams, - pub insert_options: Vec, - pub update_options: Vec, - pub delete_options: Vec, + pub cell_path: CellIdParams, + pub insert_options: Vec, + pub update_options: Vec, + pub delete_options: Vec, } impl TryInto for SelectOptionChangesetPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let cell_identifier = self.cell_identifier.try_into()?; - Ok(SelectOptionChangeset { - cell_path: cell_identifier, - insert_options: self.insert_options, - update_options: self.update_options, - delete_options: self.delete_options, - }) - } + fn try_into(self) -> Result { + let cell_identifier = self.cell_identifier.try_into()?; + Ok(SelectOptionChangeset { + cell_path: cell_identifier, + insert_options: self.insert_options, + update_options: self.update_options, + delete_options: self.delete_options, + }) + } } pub struct SelectedSelectOptions { - pub(crate) options: Vec, + pub(crate) options: Vec, } impl std::convert::From for SelectedSelectOptions { - fn from(data: SelectOptionCellDataPB) -> Self { - Self { - options: data.select_options, - } + fn from(data: SelectOptionCellDataPB) -> Self { + Self { + options: data.select_options, } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/single_select_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/single_select_type_option.rs index 3fa6897f8e..766dc77d8f 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/single_select_type_option.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/single_select_type_option.rs @@ -4,11 +4,11 @@ use crate::services::cell::{CellDataChangeset, FromCellString, TypeCellData}; use std::cmp::Ordering; use crate::services::field::{ - default_order, BoxTypeOptionBuilder, SelectOptionCellDataPB, SelectedSelectOptions, TypeOption, TypeOptionBuilder, - TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, + default_order, BoxTypeOptionBuilder, SelectOptionCellDataPB, SelectedSelectOptions, TypeOption, + TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, }; use crate::services::field::{ - SelectOptionCellChangeset, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction, + SelectOptionCellChangeset, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction, }; use bytes::Bytes; use flowy_derive::ProtoBuf; @@ -19,106 +19,118 @@ use serde::{Deserialize, Serialize}; // Single select #[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] pub struct SingleSelectTypeOptionPB { - #[pb(index = 1)] - pub options: Vec, + #[pb(index = 1)] + pub options: Vec, - #[pb(index = 2)] - pub disable_color: bool, + #[pb(index = 2)] + pub disable_color: bool, } impl_type_option!(SingleSelectTypeOptionPB, FieldType::SingleSelect); impl TypeOption for SingleSelectTypeOptionPB { - type CellData = SelectOptionIds; - type CellChangeset = SelectOptionCellChangeset; - type CellProtobufType = SelectOptionCellDataPB; - type CellFilter = SelectOptionFilterPB; + type CellData = SelectOptionIds; + type CellChangeset = SelectOptionCellChangeset; + type CellProtobufType = SelectOptionCellDataPB; + type CellFilter = SelectOptionFilterPB; } impl TypeOptionCellData for SingleSelectTypeOptionPB { - fn convert_to_protobuf(&self, cell_data: ::CellData) -> ::CellProtobufType { - self.get_selected_options(cell_data) - } + fn convert_to_protobuf( + &self, + cell_data: ::CellData, + ) -> ::CellProtobufType { + self.get_selected_options(cell_data) + } - fn decode_type_option_cell_str(&self, cell_str: String) -> FlowyResult<::CellData> { - SelectOptionIds::from_cell_str(&cell_str) - } + fn decode_type_option_cell_str( + &self, + cell_str: String, + ) -> FlowyResult<::CellData> { + SelectOptionIds::from_cell_str(&cell_str) + } } impl SelectTypeOptionSharedAction for SingleSelectTypeOptionPB { - fn number_of_max_options(&self) -> Option { - Some(1) - } + fn number_of_max_options(&self) -> Option { + Some(1) + } - fn options(&self) -> &Vec { - &self.options - } + fn options(&self) -> &Vec { + &self.options + } - fn mut_options(&mut self) -> &mut Vec { - &mut self.options - } + fn mut_options(&mut self) -> &mut Vec { + &mut self.options + } } impl CellDataChangeset for SingleSelectTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - _type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - let mut insert_option_ids = changeset - .insert_option_ids - .into_iter() - .filter(|insert_option_id| self.options.iter().any(|option| &option.id == insert_option_id)) - .collect::>(); + fn apply_changeset( + &self, + changeset: ::CellChangeset, + _type_cell_data: Option, + ) -> FlowyResult<(String, ::CellData)> { + let mut insert_option_ids = changeset + .insert_option_ids + .into_iter() + .filter(|insert_option_id| { + self + .options + .iter() + .any(|option| &option.id == insert_option_id) + }) + .collect::>(); - // In single select, the insert_option_ids should only contain one select option id. - // Sometimes, the insert_option_ids may contain list of option ids. For example, - // copy/paste a ids string. - let select_option_ids = if insert_option_ids.is_empty() { - SelectOptionIds::from(insert_option_ids) - } else { - // Just take the first select option - let _ = insert_option_ids.drain(1..); - SelectOptionIds::from(insert_option_ids) - }; - Ok((select_option_ids.to_string(), select_option_ids)) - } + // In single select, the insert_option_ids should only contain one select option id. + // Sometimes, the insert_option_ids may contain list of option ids. For example, + // copy/paste a ids string. + let select_option_ids = if insert_option_ids.is_empty() { + SelectOptionIds::from(insert_option_ids) + } else { + // Just take the first select option + let _ = insert_option_ids.drain(1..); + SelectOptionIds::from(insert_option_ids) + }; + Ok((select_option_ids.to_string(), select_option_ids)) + } } impl TypeOptionCellDataFilter for SingleSelectTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_single_select() { - return true; - } - let selected_options = SelectedSelectOptions::from(self.get_selected_options(cell_data.clone())); - filter.is_visible(&selected_options, FieldType::SingleSelect) + fn apply_filter( + &self, + filter: &::CellFilter, + field_type: &FieldType, + cell_data: &::CellData, + ) -> bool { + if !field_type.is_single_select() { + return true; } + let selected_options = + SelectedSelectOptions::from(self.get_selected_options(cell_data.clone())); + filter.is_visible(&selected_options, FieldType::SingleSelect) + } } impl TypeOptionCellDataCompare for SingleSelectTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - match ( - cell_data - .first() - .and_then(|id| self.options.iter().find(|option| &option.id == id)), - other_cell_data - .first() - .and_then(|id| self.options.iter().find(|option| &option.id == id)), - ) { - (Some(left), Some(right)) => left.name.cmp(&right.name), - (Some(_), None) => Ordering::Greater, - (None, Some(_)) => Ordering::Less, - (None, None) => default_order(), - } + fn apply_cmp( + &self, + cell_data: &::CellData, + other_cell_data: &::CellData, + ) -> Ordering { + match ( + cell_data + .first() + .and_then(|id| self.options.iter().find(|option| &option.id == id)), + other_cell_data + .first() + .and_then(|id| self.options.iter().find(|option| &option.id == id)), + ) { + (Some(left), Some(right)) => left.name.cmp(&right.name), + (Some(_), None) => Ordering::Greater, + (None, Some(_)) => Ordering::Less, + (None, None) => default_order(), } + } } #[derive(Default)] pub struct SingleSelectTypeOptionBuilder(SingleSelectTypeOptionPB); @@ -126,125 +138,126 @@ impl_into_box_type_option_builder!(SingleSelectTypeOptionBuilder); impl_builder_from_json_str_and_from_bytes!(SingleSelectTypeOptionBuilder, SingleSelectTypeOptionPB); impl SingleSelectTypeOptionBuilder { - pub fn add_option(mut self, opt: SelectOptionPB) -> Self { - self.0.options.push(opt); - self - } + pub fn add_option(mut self, opt: SelectOptionPB) -> Self { + self.0.options.push(opt); + self + } } impl TypeOptionBuilder for SingleSelectTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::SingleSelect - } + fn field_type(&self) -> FieldType { + FieldType::SingleSelect + } - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } + fn serializer(&self) -> &dyn TypeOptionDataSerializer { + &self.0 + } } #[cfg(test)] mod tests { - use crate::entities::FieldType; - use crate::services::cell::CellDataChangeset; - use crate::services::field::type_options::*; - use crate::services::field::{FieldBuilder, TypeOptionBuilder}; + use crate::entities::FieldType; + use crate::services::cell::CellDataChangeset; + use crate::services::field::type_options::*; + use crate::services::field::{FieldBuilder, TypeOptionBuilder}; - #[test] - fn single_select_transform_with_checkbox_type_option_test() { - let checkbox_type_option_builder = CheckboxTypeOptionBuilder::default(); - let checkbox_type_option_data = checkbox_type_option_builder.serializer().json_str(); + #[test] + fn single_select_transform_with_checkbox_type_option_test() { + let checkbox_type_option_builder = CheckboxTypeOptionBuilder::default(); + let checkbox_type_option_data = checkbox_type_option_builder.serializer().json_str(); - let mut single_select = SingleSelectTypeOptionBuilder::default().0; - single_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data.clone()); - debug_assert_eq!(single_select.options.len(), 2); + let mut single_select = SingleSelectTypeOptionBuilder::default().0; + single_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data.clone()); + debug_assert_eq!(single_select.options.len(), 2); - // Already contain the yes/no option. It doesn't need to insert new options - single_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data); - debug_assert_eq!(single_select.options.len(), 2); - } + // Already contain the yes/no option. It doesn't need to insert new options + single_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data); + debug_assert_eq!(single_select.options.len(), 2); + } - #[test] - fn single_select_transform_with_multi_select_type_option_test() { - let mut multiselect_type_option_builder = MultiSelectTypeOptionBuilder::default(); + #[test] + fn single_select_transform_with_multi_select_type_option_test() { + let mut multiselect_type_option_builder = MultiSelectTypeOptionBuilder::default(); - let google = SelectOptionPB::new("Google"); - multiselect_type_option_builder = multiselect_type_option_builder.add_option(google); + let google = SelectOptionPB::new("Google"); + multiselect_type_option_builder = multiselect_type_option_builder.add_option(google); - let facebook = SelectOptionPB::new("Facebook"); - multiselect_type_option_builder = multiselect_type_option_builder.add_option(facebook); + let facebook = SelectOptionPB::new("Facebook"); + multiselect_type_option_builder = multiselect_type_option_builder.add_option(facebook); - let multiselect_type_option_data = multiselect_type_option_builder.serializer().json_str(); + let multiselect_type_option_data = multiselect_type_option_builder.serializer().json_str(); - let mut single_select = SingleSelectTypeOptionBuilder::default().0; - single_select.transform_type_option(FieldType::MultiSelect, multiselect_type_option_data.clone()); - debug_assert_eq!(single_select.options.len(), 2); + let mut single_select = SingleSelectTypeOptionBuilder::default().0; + single_select + .transform_type_option(FieldType::MultiSelect, multiselect_type_option_data.clone()); + debug_assert_eq!(single_select.options.len(), 2); - // Already contain the yes/no option. It doesn't need to insert new options - single_select.transform_type_option(FieldType::MultiSelect, multiselect_type_option_data); - debug_assert_eq!(single_select.options.len(), 2); - } + // Already contain the yes/no option. It doesn't need to insert new options + single_select.transform_type_option(FieldType::MultiSelect, multiselect_type_option_data); + debug_assert_eq!(single_select.options.len(), 2); + } - #[test] - fn single_select_insert_multi_option_test() { - let google = SelectOptionPB::new("Google"); - let facebook = SelectOptionPB::new("Facebook"); - let single_select = SingleSelectTypeOptionBuilder::default() - .add_option(google.clone()) - .add_option(facebook.clone()); + #[test] + fn single_select_insert_multi_option_test() { + let google = SelectOptionPB::new("Google"); + let facebook = SelectOptionPB::new("Facebook"); + let single_select = SingleSelectTypeOptionBuilder::default() + .add_option(google.clone()) + .add_option(facebook.clone()); - let field_rev = FieldBuilder::new(single_select).name("Platform").build(); - let type_option = SingleSelectTypeOptionPB::from(&field_rev); - let option_ids = vec![google.id.clone(), facebook.id]; - let changeset = SelectOptionCellChangeset::from_insert_options(option_ids); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert_eq!(&*select_option_ids, &vec![google.id]); - } + let field_rev = FieldBuilder::new(single_select).name("Platform").build(); + let type_option = SingleSelectTypeOptionPB::from(&field_rev); + let option_ids = vec![google.id.clone(), facebook.id]; + let changeset = SelectOptionCellChangeset::from_insert_options(option_ids); + let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; + assert_eq!(&*select_option_ids, &vec![google.id]); + } - #[test] - fn single_select_unselect_multi_option_test() { - let google = SelectOptionPB::new("Google"); - let facebook = SelectOptionPB::new("Facebook"); - let single_select = SingleSelectTypeOptionBuilder::default() - .add_option(google.clone()) - .add_option(facebook.clone()); + #[test] + fn single_select_unselect_multi_option_test() { + let google = SelectOptionPB::new("Google"); + let facebook = SelectOptionPB::new("Facebook"); + let single_select = SingleSelectTypeOptionBuilder::default() + .add_option(google.clone()) + .add_option(facebook.clone()); - let field_rev = FieldBuilder::new(single_select).name("Platform").build(); - let type_option = SingleSelectTypeOptionPB::from(&field_rev); - let option_ids = vec![google.id.clone(), facebook.id]; + let field_rev = FieldBuilder::new(single_select).name("Platform").build(); + let type_option = SingleSelectTypeOptionPB::from(&field_rev); + let option_ids = vec![google.id.clone(), facebook.id]; - // insert - let changeset = SelectOptionCellChangeset::from_insert_options(option_ids.clone()); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert_eq!(&*select_option_ids, &vec![google.id]); + // insert + let changeset = SelectOptionCellChangeset::from_insert_options(option_ids.clone()); + let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; + assert_eq!(&*select_option_ids, &vec![google.id]); - // delete - let changeset = SelectOptionCellChangeset::from_delete_options(option_ids); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert!(select_option_ids.is_empty()); - } + // delete + let changeset = SelectOptionCellChangeset::from_delete_options(option_ids); + let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; + assert!(select_option_ids.is_empty()); + } - #[test] - fn single_select_insert_non_exist_option_test() { - let google = SelectOptionPB::new("Google"); - let single_select = SingleSelectTypeOptionBuilder::default(); - let field_rev = FieldBuilder::new(single_select).name("Platform").build(); - let type_option = SingleSelectTypeOptionPB::from(&field_rev); + #[test] + fn single_select_insert_non_exist_option_test() { + let google = SelectOptionPB::new("Google"); + let single_select = SingleSelectTypeOptionBuilder::default(); + let field_rev = FieldBuilder::new(single_select).name("Platform").build(); + let type_option = SingleSelectTypeOptionPB::from(&field_rev); - let option_ids = vec![google.id]; - let changeset = SelectOptionCellChangeset::from_insert_options(option_ids); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; + let option_ids = vec![google.id]; + let changeset = SelectOptionCellChangeset::from_insert_options(option_ids); + let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert!(select_option_ids.is_empty()); - } + assert!(select_option_ids.is_empty()); + } - #[test] - fn single_select_insert_invalid_option_id_test() { - let single_select = SingleSelectTypeOptionBuilder::default(); - let field_rev = FieldBuilder::new(single_select).name("Platform").build(); - let type_option = SingleSelectTypeOptionPB::from(&field_rev); + #[test] + fn single_select_insert_invalid_option_id_test() { + let single_select = SingleSelectTypeOptionBuilder::default(); + let field_rev = FieldBuilder::new(single_select).name("Platform").build(); + let type_option = SingleSelectTypeOptionPB::from(&field_rev); - let changeset = SelectOptionCellChangeset::from_insert_option_id(""); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert!(select_option_ids.is_empty()); - } + let changeset = SelectOptionCellChangeset::from_insert_option_id(""); + let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; + assert!(select_option_ids.is_empty()); + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/type_option_transform.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/type_option_transform.rs index f699ce5bb5..f662371770 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/type_option_transform.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/type_option_transform.rs @@ -1,8 +1,8 @@ use crate::entities::FieldType; use crate::services::field::{ - MultiSelectTypeOptionPB, SelectOptionColorPB, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction, - SingleSelectTypeOptionPB, TypeOption, CHECK, UNCHECK, + MultiSelectTypeOptionPB, SelectOptionColorPB, SelectOptionIds, SelectOptionPB, + SelectTypeOptionSharedAction, SingleSelectTypeOptionPB, TypeOption, CHECK, UNCHECK, }; use grid_model::TypeOptionDataDeserializer; @@ -10,73 +10,84 @@ use grid_model::TypeOptionDataDeserializer; /// Handles how to transform the cell data when switching between different field types pub(crate) struct SelectOptionTypeOptionTransformHelper(); impl SelectOptionTypeOptionTransformHelper { - /// Transform the TypeOptionData from 'field_type' to single select option type. - /// - /// # Arguments - /// - /// * `old_field_type`: the FieldType of the passed-in TypeOptionData - /// - pub fn transform_type_option(shared: &mut T, old_field_type: &FieldType, old_type_option_data: String) - where - T: SelectTypeOptionSharedAction + TypeOption, - { - match old_field_type { - FieldType::Checkbox => { - //add Yes and No options if it does not exist. - if !shared.options().iter().any(|option| option.name == CHECK) { - let check_option = SelectOptionPB::with_color(CHECK, SelectOptionColorPB::Green); - shared.mut_options().push(check_option); - } - - if !shared.options().iter().any(|option| option.name == UNCHECK) { - let uncheck_option = SelectOptionPB::with_color(UNCHECK, SelectOptionColorPB::Yellow); - shared.mut_options().push(uncheck_option); - } - } - FieldType::MultiSelect => { - let options = MultiSelectTypeOptionPB::from_json_str(&old_type_option_data).options; - options.iter().for_each(|new_option| { - if !shared.options().iter().any(|option| option.name == new_option.name) { - shared.mut_options().push(new_option.clone()); - } - }) - } - FieldType::SingleSelect => { - let options = SingleSelectTypeOptionPB::from_json_str(&old_type_option_data).options; - options.iter().for_each(|new_option| { - if !shared.options().iter().any(|option| option.name == new_option.name) { - shared.mut_options().push(new_option.clone()); - } - }) - } - _ => {} + /// Transform the TypeOptionData from 'field_type' to single select option type. + /// + /// # Arguments + /// + /// * `old_field_type`: the FieldType of the passed-in TypeOptionData + /// + pub fn transform_type_option( + shared: &mut T, + old_field_type: &FieldType, + old_type_option_data: String, + ) where + T: SelectTypeOptionSharedAction + TypeOption, + { + match old_field_type { + FieldType::Checkbox => { + //add Yes and No options if it does not exist. + if !shared.options().iter().any(|option| option.name == CHECK) { + let check_option = SelectOptionPB::with_color(CHECK, SelectOptionColorPB::Green); + shared.mut_options().push(check_option); } - } - // pub fn transform_e_option_cell_data( - // // shared: &T, - // // cell_data: String, - // // decoded_field_type: &FieldType, - // // ) -> ::CellData - // // where - // // T: SelectTypeOptionSharedAction + TypeOption + CellDataDecoder, - // // { - // // match decoded_field_type { - // // FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => { - // // self.try_decode_cell_data(cell_data) - // // } - // // FieldType::Checkbox => { - // // // transform the cell data to the option id - // // let mut transformed_ids = Vec::new(); - // // let options = shared.options(); - // // cell_data.iter().for_each(|name| { - // // if let Some(option) = options.iter().find(|option| &option.name == name) { - // // transformed_ids.push(option.id.clone()); - // // } - // // }); - // // SelectOptionIds::from(transformed_ids) - // // } - // // _ => SelectOptionIds::from(vec![]), - // // } - // // }typ + if !shared.options().iter().any(|option| option.name == UNCHECK) { + let uncheck_option = SelectOptionPB::with_color(UNCHECK, SelectOptionColorPB::Yellow); + shared.mut_options().push(uncheck_option); + } + }, + FieldType::MultiSelect => { + let options = MultiSelectTypeOptionPB::from_json_str(&old_type_option_data).options; + options.iter().for_each(|new_option| { + if !shared + .options() + .iter() + .any(|option| option.name == new_option.name) + { + shared.mut_options().push(new_option.clone()); + } + }) + }, + FieldType::SingleSelect => { + let options = SingleSelectTypeOptionPB::from_json_str(&old_type_option_data).options; + options.iter().for_each(|new_option| { + if !shared + .options() + .iter() + .any(|option| option.name == new_option.name) + { + shared.mut_options().push(new_option.clone()); + } + }) + }, + _ => {}, + } + } + + // pub fn transform_e_option_cell_data( + // // shared: &T, + // // cell_data: String, + // // decoded_field_type: &FieldType, + // // ) -> ::CellData + // // where + // // T: SelectTypeOptionSharedAction + TypeOption + CellDataDecoder, + // // { + // // match decoded_field_type { + // // FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => { + // // self.try_decode_cell_data(cell_data) + // // } + // // FieldType::Checkbox => { + // // // transform the cell data to the option id + // // let mut transformed_ids = Vec::new(); + // // let options = shared.options(); + // // cell_data.iter().for_each(|name| { + // // if let Some(option) = options.iter().find(|option| &option.name == name) { + // // transformed_ids.push(option.id.clone()); + // // } + // // }); + // // SelectOptionIds::from(transformed_ids) + // // } + // // _ => SelectOptionIds::from(vec![]), + // // } + // // }typ } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_filter.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_filter.rs index f336418c21..f684dcc56b 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_filter.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_filter.rs @@ -1,83 +1,83 @@ use crate::entities::{TextFilterConditionPB, TextFilterPB}; impl TextFilterPB { - pub fn is_visible>(&self, cell_data: T) -> bool { - let cell_data = cell_data.as_ref().to_lowercase(); - let content = &self.content.to_lowercase(); - match self.condition { - TextFilterConditionPB::Is => &cell_data == content, - TextFilterConditionPB::IsNot => &cell_data != content, - TextFilterConditionPB::Contains => cell_data.contains(content), - TextFilterConditionPB::DoesNotContain => !cell_data.contains(content), - TextFilterConditionPB::StartsWith => cell_data.starts_with(content), - TextFilterConditionPB::EndsWith => cell_data.ends_with(content), - TextFilterConditionPB::TextIsEmpty => cell_data.is_empty(), - TextFilterConditionPB::TextIsNotEmpty => !cell_data.is_empty(), - } + pub fn is_visible>(&self, cell_data: T) -> bool { + let cell_data = cell_data.as_ref().to_lowercase(); + let content = &self.content.to_lowercase(); + match self.condition { + TextFilterConditionPB::Is => &cell_data == content, + TextFilterConditionPB::IsNot => &cell_data != content, + TextFilterConditionPB::Contains => cell_data.contains(content), + TextFilterConditionPB::DoesNotContain => !cell_data.contains(content), + TextFilterConditionPB::StartsWith => cell_data.starts_with(content), + TextFilterConditionPB::EndsWith => cell_data.ends_with(content), + TextFilterConditionPB::TextIsEmpty => cell_data.is_empty(), + TextFilterConditionPB::TextIsNotEmpty => !cell_data.is_empty(), } + } } #[cfg(test)] mod tests { - #![allow(clippy::all)] - use crate::entities::{TextFilterConditionPB, TextFilterPB}; + #![allow(clippy::all)] + use crate::entities::{TextFilterConditionPB, TextFilterPB}; - #[test] - fn text_filter_equal_test() { - let text_filter = TextFilterPB { - condition: TextFilterConditionPB::Is, - content: "appflowy".to_owned(), - }; + #[test] + fn text_filter_equal_test() { + let text_filter = TextFilterPB { + condition: TextFilterConditionPB::Is, + content: "appflowy".to_owned(), + }; - assert!(text_filter.is_visible("AppFlowy")); - assert_eq!(text_filter.is_visible("appflowy"), true); - assert_eq!(text_filter.is_visible("Appflowy"), true); - assert_eq!(text_filter.is_visible("AppFlowy.io"), false); - } - #[test] - fn text_filter_start_with_test() { - let text_filter = TextFilterPB { - condition: TextFilterConditionPB::StartsWith, - content: "appflowy".to_owned(), - }; + assert!(text_filter.is_visible("AppFlowy")); + assert_eq!(text_filter.is_visible("appflowy"), true); + assert_eq!(text_filter.is_visible("Appflowy"), true); + assert_eq!(text_filter.is_visible("AppFlowy.io"), false); + } + #[test] + fn text_filter_start_with_test() { + let text_filter = TextFilterPB { + condition: TextFilterConditionPB::StartsWith, + content: "appflowy".to_owned(), + }; - assert_eq!(text_filter.is_visible("AppFlowy.io"), true); - assert_eq!(text_filter.is_visible(""), false); - assert_eq!(text_filter.is_visible("https"), false); - } + assert_eq!(text_filter.is_visible("AppFlowy.io"), true); + assert_eq!(text_filter.is_visible(""), false); + assert_eq!(text_filter.is_visible("https"), false); + } - #[test] - fn text_filter_end_with_test() { - let text_filter = TextFilterPB { - condition: TextFilterConditionPB::EndsWith, - content: "appflowy".to_owned(), - }; + #[test] + fn text_filter_end_with_test() { + let text_filter = TextFilterPB { + condition: TextFilterConditionPB::EndsWith, + content: "appflowy".to_owned(), + }; - assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true); - assert_eq!(text_filter.is_visible("App"), false); - assert_eq!(text_filter.is_visible("appflowy.io"), false); - } - #[test] - fn text_filter_empty_test() { - let text_filter = TextFilterPB { - condition: TextFilterConditionPB::TextIsEmpty, - content: "appflowy".to_owned(), - }; + assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true); + assert_eq!(text_filter.is_visible("App"), false); + assert_eq!(text_filter.is_visible("appflowy.io"), false); + } + #[test] + fn text_filter_empty_test() { + let text_filter = TextFilterPB { + condition: TextFilterConditionPB::TextIsEmpty, + content: "appflowy".to_owned(), + }; - assert_eq!(text_filter.is_visible(""), true); - assert_eq!(text_filter.is_visible("App"), false); - } - #[test] - fn text_filter_contain_test() { - let text_filter = TextFilterPB { - condition: TextFilterConditionPB::Contains, - content: "appflowy".to_owned(), - }; + assert_eq!(text_filter.is_visible(""), true); + assert_eq!(text_filter.is_visible("App"), false); + } + #[test] + fn text_filter_contain_test() { + let text_filter = TextFilterPB { + condition: TextFilterConditionPB::Contains, + content: "appflowy".to_owned(), + }; - assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true); - assert_eq!(text_filter.is_visible("AppFlowy"), true); - assert_eq!(text_filter.is_visible("App"), false); - assert_eq!(text_filter.is_visible(""), false); - assert_eq!(text_filter.is_visible("github"), false); - } + assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true); + assert_eq!(text_filter.is_visible("AppFlowy"), true); + assert_eq!(text_filter.is_visible("App"), false); + assert_eq!(text_filter.is_visible(""), false); + assert_eq!(text_filter.is_visible("github"), false); + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_tests.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_tests.rs index 898c40465f..0e63708c6c 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_tests.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_tests.rs @@ -1,67 +1,72 @@ #[cfg(test)] mod tests { - use crate::entities::FieldType; - use crate::services::cell::stringify_cell_data; + use crate::entities::FieldType; + use crate::services::cell::stringify_cell_data; - use crate::services::field::FieldBuilder; - use crate::services::field::*; + use crate::services::field::FieldBuilder; + use crate::services::field::*; - // Test parser the cell data which field's type is FieldType::Date to cell data - // which field's type is FieldType::Text - #[test] - fn date_type_to_text_type() { - let field_type = FieldType::DateTime; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); + // Test parser the cell data which field's type is FieldType::Date to cell data + // which field's type is FieldType::Text + #[test] + fn date_type_to_text_type() { + let field_type = FieldType::DateTime; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_eq!( - stringify_cell_data(1647251762.to_string(), &FieldType::RichText, &field_type, &field_rev), - "Mar 14,2022" - ); - } + assert_eq!( + stringify_cell_data( + 1647251762.to_string(), + &FieldType::RichText, + &field_type, + &field_rev + ), + "Mar 14,2022" + ); + } - // Test parser the cell data which field's type is FieldType::SingleSelect to cell data - // which field's type is FieldType::Text - #[test] - fn single_select_to_text_type() { - let field_type = FieldType::SingleSelect; - let done_option = SelectOptionPB::new("Done"); - let option_id = done_option.id.clone(); - let single_select = SingleSelectTypeOptionBuilder::default().add_option(done_option.clone()); - let field_rev = FieldBuilder::new(single_select).build(); + // Test parser the cell data which field's type is FieldType::SingleSelect to cell data + // which field's type is FieldType::Text + #[test] + fn single_select_to_text_type() { + let field_type = FieldType::SingleSelect; + let done_option = SelectOptionPB::new("Done"); + let option_id = done_option.id.clone(); + let single_select = SingleSelectTypeOptionBuilder::default().add_option(done_option.clone()); + let field_rev = FieldBuilder::new(single_select).build(); - assert_eq!( - stringify_cell_data(option_id, &FieldType::RichText, &field_type, &field_rev), - done_option.name, - ); - } - /* - - [Unit Test] Testing the switching from Multi-selection type to Text type - - Tracking : https://github.com/AppFlowy-IO/AppFlowy/issues/1183 - */ - #[test] - fn multiselect_to_text_type() { - let field_type = FieldType::MultiSelect; + assert_eq!( + stringify_cell_data(option_id, &FieldType::RichText, &field_type, &field_rev), + done_option.name, + ); + } + /* + - [Unit Test] Testing the switching from Multi-selection type to Text type + - Tracking : https://github.com/AppFlowy-IO/AppFlowy/issues/1183 + */ + #[test] + fn multiselect_to_text_type() { + let field_type = FieldType::MultiSelect; - let france = SelectOptionPB::new("france"); - let france_option_id = france.id.clone(); + let france = SelectOptionPB::new("france"); + let france_option_id = france.id.clone(); - let argentina = SelectOptionPB::new("argentina"); - let argentina_option_id = argentina.id.clone(); + let argentina = SelectOptionPB::new("argentina"); + let argentina_option_id = argentina.id.clone(); - let multi_select = MultiSelectTypeOptionBuilder::default() - .add_option(france.clone()) - .add_option(argentina.clone()); + let multi_select = MultiSelectTypeOptionBuilder::default() + .add_option(france.clone()) + .add_option(argentina.clone()); - let field_rev = FieldBuilder::new(multi_select).build(); + let field_rev = FieldBuilder::new(multi_select).build(); - assert_eq!( - stringify_cell_data( - format!("{},{}", france_option_id, argentina_option_id), - &FieldType::RichText, - &field_type, - &field_rev - ), - format!("{},{}", france.name, argentina.name) - ); - } + assert_eq!( + stringify_cell_data( + format!("{},{}", france_option_id, argentina_option_id), + &FieldType::RichText, + &field_type, + &field_rev + ), + format!("{},{}", france.name, argentina.name) + ); + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_type_option.rs index 5ecf67dd84..7bb730cfaa 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_type_option.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_type_option.rs @@ -1,12 +1,12 @@ use crate::entities::{FieldType, TextFilterPB}; use crate::impl_type_option; use crate::services::cell::{ - stringify_cell_data, CellDataChangeset, CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellString, - TypeCellData, + stringify_cell_data, CellDataChangeset, CellDataDecoder, CellProtobufBlobParser, DecodedCellData, + FromCellString, TypeCellData, }; use crate::services::field::{ - BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare, - TypeOptionCellDataFilter, TypeOptionTransform, + BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, + TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, }; use bytes::Bytes; use flowy_derive::ProtoBuf; @@ -22,234 +22,253 @@ impl_into_box_type_option_builder!(RichTextTypeOptionBuilder); impl_builder_from_json_str_and_from_bytes!(RichTextTypeOptionBuilder, RichTextTypeOptionPB); impl TypeOptionBuilder for RichTextTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::RichText - } + fn field_type(&self) -> FieldType { + FieldType::RichText + } - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } + fn serializer(&self) -> &dyn TypeOptionDataSerializer { + &self.0 + } } /// For the moment, the `RichTextTypeOptionPB` is empty. The `data` property is not /// used yet. #[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)] pub struct RichTextTypeOptionPB { - #[pb(index = 1)] - #[serde(default)] - data: String, + #[pb(index = 1)] + #[serde(default)] + data: String, } impl_type_option!(RichTextTypeOptionPB, FieldType::RichText); impl TypeOption for RichTextTypeOptionPB { - type CellData = StrCellData; - type CellChangeset = String; - type CellProtobufType = StrCellData; - type CellFilter = TextFilterPB; + type CellData = StrCellData; + type CellChangeset = String; + type CellProtobufType = StrCellData; + type CellFilter = TextFilterPB; } impl TypeOptionTransform for RichTextTypeOptionPB { - fn transformable(&self) -> bool { - true - } + fn transformable(&self) -> bool { + true + } - fn transform_type_option(&mut self, _old_type_option_field_type: FieldType, _old_type_option_data: String) {} + fn transform_type_option( + &mut self, + _old_type_option_field_type: FieldType, + _old_type_option_data: String, + ) { + } - fn transform_type_option_cell_str( - &self, - cell_str: &str, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> Option<::CellData> { - if decoded_field_type.is_date() - || decoded_field_type.is_single_select() - || decoded_field_type.is_multi_select() - || decoded_field_type.is_number() - || decoded_field_type.is_url() - { - Some(stringify_cell_data(cell_str.to_owned(), decoded_field_type, decoded_field_type, field_rev).into()) - } else { - StrCellData::from_cell_str(cell_str).ok() - } + fn transform_type_option_cell_str( + &self, + cell_str: &str, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> Option<::CellData> { + if decoded_field_type.is_date() + || decoded_field_type.is_single_select() + || decoded_field_type.is_multi_select() + || decoded_field_type.is_number() + || decoded_field_type.is_url() + { + Some( + stringify_cell_data( + cell_str.to_owned(), + decoded_field_type, + decoded_field_type, + field_rev, + ) + .into(), + ) + } else { + StrCellData::from_cell_str(cell_str).ok() } + } } impl TypeOptionCellData for RichTextTypeOptionPB { - fn convert_to_protobuf(&self, cell_data: ::CellData) -> ::CellProtobufType { - cell_data - } + fn convert_to_protobuf( + &self, + cell_data: ::CellData, + ) -> ::CellProtobufType { + cell_data + } - fn decode_type_option_cell_str(&self, cell_str: String) -> FlowyResult<::CellData> { - StrCellData::from_cell_str(&cell_str) - } + fn decode_type_option_cell_str( + &self, + cell_str: String, + ) -> FlowyResult<::CellData> { + StrCellData::from_cell_str(&cell_str) + } } impl CellDataDecoder for RichTextTypeOptionPB { - fn decode_cell_str( - &self, - cell_str: String, - _decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult<::CellData> { - StrCellData::from_cell_str(&cell_str) - } + fn decode_cell_str( + &self, + cell_str: String, + _decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> FlowyResult<::CellData> { + StrCellData::from_cell_str(&cell_str) + } - fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { - cell_data.to_string() - } + fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { + cell_data.to_string() + } } impl CellDataChangeset for RichTextTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - _type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - if changeset.len() > 10000 { - Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000")) - } else { - let text_cell_data = StrCellData(changeset); - Ok((text_cell_data.to_string(), text_cell_data)) - } + fn apply_changeset( + &self, + changeset: ::CellChangeset, + _type_cell_data: Option, + ) -> FlowyResult<(String, ::CellData)> { + if changeset.len() > 10000 { + Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000")) + } else { + let text_cell_data = StrCellData(changeset); + Ok((text_cell_data.to_string(), text_cell_data)) } + } } impl TypeOptionCellDataFilter for RichTextTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_text() { - return false; - } - - filter.is_visible(cell_data) + fn apply_filter( + &self, + filter: &::CellFilter, + field_type: &FieldType, + cell_data: &::CellData, + ) -> bool { + if !field_type.is_text() { + return false; } + + filter.is_visible(cell_data) + } } impl TypeOptionCellDataCompare for RichTextTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - cell_data.0.cmp(&other_cell_data.0) - } + fn apply_cmp( + &self, + cell_data: &::CellData, + other_cell_data: &::CellData, + ) -> Ordering { + cell_data.0.cmp(&other_cell_data.0) + } } #[derive(Clone)] pub struct TextCellData(pub String); impl AsRef for TextCellData { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } impl std::ops::Deref for TextCellData { - type Target = String; + type Target = String; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } impl FromCellString for TextCellData { - fn from_cell_str(s: &str) -> FlowyResult - where - Self: Sized, - { - Ok(TextCellData(s.to_owned())) - } + fn from_cell_str(s: &str) -> FlowyResult + where + Self: Sized, + { + Ok(TextCellData(s.to_owned())) + } } impl ToString for TextCellData { - fn to_string(&self) -> String { - self.0.clone() - } + fn to_string(&self) -> String { + self.0.clone() + } } impl DecodedCellData for TextCellData { - type Object = TextCellData; + type Object = TextCellData; - fn is_empty(&self) -> bool { - self.0.is_empty() - } + fn is_empty(&self) -> bool { + self.0.is_empty() + } } pub struct TextCellDataParser(); impl CellProtobufBlobParser for TextCellDataParser { - type Object = TextCellData; - fn parser(bytes: &Bytes) -> FlowyResult { - match String::from_utf8(bytes.to_vec()) { - Ok(s) => Ok(TextCellData(s)), - Err(_) => Ok(TextCellData("".to_owned())), - } + type Object = TextCellData; + fn parser(bytes: &Bytes) -> FlowyResult { + match String::from_utf8(bytes.to_vec()) { + Ok(s) => Ok(TextCellData(s)), + Err(_) => Ok(TextCellData("".to_owned())), } + } } #[derive(Default, Debug, Clone)] pub struct StrCellData(pub String); impl std::ops::Deref for StrCellData { - type Target = String; + type Target = String; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } impl std::ops::DerefMut for StrCellData { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } } impl FromCellString for StrCellData { - fn from_cell_str(s: &str) -> FlowyResult { - Ok(Self(s.to_owned())) - } + fn from_cell_str(s: &str) -> FlowyResult { + Ok(Self(s.to_owned())) + } } impl std::convert::From for StrCellData { - fn from(s: String) -> Self { - Self(s) - } + fn from(s: String) -> Self { + Self(s) + } } impl ToString for StrCellData { - fn to_string(&self) -> String { - self.0.clone() - } + fn to_string(&self) -> String { + self.0.clone() + } } impl std::convert::From for String { - fn from(value: StrCellData) -> Self { - value.0 - } + fn from(value: StrCellData) -> Self { + value.0 + } } impl std::convert::From<&str> for StrCellData { - fn from(s: &str) -> Self { - Self(s.to_owned()) - } + fn from(s: &str) -> Self { + Self(s.to_owned()) + } } impl std::convert::TryFrom for Bytes { - type Error = ProtobufError; + type Error = ProtobufError; - fn try_from(value: StrCellData) -> Result { - Ok(Bytes::from(value.0)) - } + fn try_from(value: StrCellData) -> Result { + Ok(Bytes::from(value.0)) + } } impl AsRef<[u8]> for StrCellData { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } } impl AsRef for StrCellData { - fn as_ref(&self) -> &str { - self.0.as_str() - } + fn as_ref(&self) -> &str { + self.0.as_str() + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/type_option.rs index f10025b84e..7b28bbfbb5 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/type_option.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/type_option.rs @@ -1,5 +1,7 @@ use crate::entities::FieldType; -use crate::services::cell::{CellDataDecoder, FromCellChangesetString, FromCellString, ToCellChangesetString}; +use crate::services::cell::{ + CellDataDecoder, FromCellChangesetString, FromCellString, ToCellChangesetString, +}; use crate::services::filter::FromFilterString; use bytes::Bytes; @@ -10,107 +12,118 @@ use std::cmp::Ordering; use std::fmt::Debug; pub trait TypeOption { - /// `CellData` represents as the decoded model for current type option. Each of them impl the - /// `FromCellString` and `Default` trait. If the cell string can not be decoded into the specified - /// cell data type then the default value will be returned. - /// For example: - /// FieldType::Checkbox => CheckboxCellData - /// FieldType::Date => DateCellData - /// FieldType::URL => URLCellData - /// - /// Uses `StrCellData` for any `TypeOption` if their cell data is pure `String`. - /// - type CellData: FromCellString + ToString + Default + Send + Sync + Clone + Debug + 'static; + /// `CellData` represents as the decoded model for current type option. Each of them impl the + /// `FromCellString` and `Default` trait. If the cell string can not be decoded into the specified + /// cell data type then the default value will be returned. + /// For example: + /// FieldType::Checkbox => CheckboxCellData + /// FieldType::Date => DateCellData + /// FieldType::URL => URLCellData + /// + /// Uses `StrCellData` for any `TypeOption` if their cell data is pure `String`. + /// + type CellData: FromCellString + ToString + Default + Send + Sync + Clone + Debug + 'static; - /// Represents as the corresponding field type cell changeset. - /// The changeset must implements the `FromCellChangesetString` and the `ToCellChangesetString` trait. - /// These two traits are auto implemented for `String`. - /// - type CellChangeset: FromCellChangesetString + ToCellChangesetString; + /// Represents as the corresponding field type cell changeset. + /// The changeset must implements the `FromCellChangesetString` and the `ToCellChangesetString` trait. + /// These two traits are auto implemented for `String`. + /// + type CellChangeset: FromCellChangesetString + ToCellChangesetString; - /// For the moment, the protobuf type only be used in the FFI of `Dart`. If the decoded cell - /// struct is just a `String`, then use the `StrCellData` as its `CellProtobufType`. - /// Otherwise, providing a custom protobuf type as its `CellProtobufType`. - /// For example: - /// FieldType::Date => DateCellDataPB - /// FieldType::URL => URLCellDataPB - /// - type CellProtobufType: TryInto + Debug; + /// For the moment, the protobuf type only be used in the FFI of `Dart`. If the decoded cell + /// struct is just a `String`, then use the `StrCellData` as its `CellProtobufType`. + /// Otherwise, providing a custom protobuf type as its `CellProtobufType`. + /// For example: + /// FieldType::Date => DateCellDataPB + /// FieldType::URL => URLCellDataPB + /// + type CellProtobufType: TryInto + Debug; - /// Represents as the filter configuration for this type option. - type CellFilter: FromFilterString + Send + Sync + 'static; + /// Represents as the filter configuration for this type option. + type CellFilter: FromFilterString + Send + Sync + 'static; } pub trait TypeOptionCellData: TypeOption { - /// Convert the decoded cell data into corresponding `Protobuf struct`. - /// For example: - /// FieldType::URL => URLCellDataPB - /// FieldType::Date=> DateCellDataPB - fn convert_to_protobuf(&self, cell_data: ::CellData) -> ::CellProtobufType; + /// Convert the decoded cell data into corresponding `Protobuf struct`. + /// For example: + /// FieldType::URL => URLCellDataPB + /// FieldType::Date=> DateCellDataPB + fn convert_to_protobuf( + &self, + cell_data: ::CellData, + ) -> ::CellProtobufType; - /// Decodes the opaque cell string to corresponding data struct. - // For example, the cell data is timestamp if its field type is `FieldType::Date`. This cell - // data can not directly show to user. So it needs to be encode as the date string with custom - // format setting. Encode `1647251762` to `"Mar 14,2022` - fn decode_type_option_cell_str(&self, cell_str: String) -> FlowyResult<::CellData>; + /// Decodes the opaque cell string to corresponding data struct. + // For example, the cell data is timestamp if its field type is `FieldType::Date`. This cell + // data can not directly show to user. So it needs to be encode as the date string with custom + // format setting. Encode `1647251762` to `"Mar 14,2022` + fn decode_type_option_cell_str( + &self, + cell_str: String, + ) -> FlowyResult<::CellData>; } pub trait TypeOptionTransform: TypeOption { - /// Returns true if the current `TypeOption` provides custom type option transformation - fn transformable(&self) -> bool { - false - } + /// Returns true if the current `TypeOption` provides custom type option transformation + fn transformable(&self) -> bool { + false + } - /// Transform the TypeOption from one field type to another - /// For example, when switching from `checkbox` type-option to `single-select` - /// type-option, adding the `Yes` option if the `single-select` type-option doesn't contain it. - /// But the cell content is a string, `Yes`, it's need to do the cell content transform. - /// The `Yes` string will be transformed to the `Yes` option id. - /// - /// # Arguments - /// - /// * `old_type_option_field_type`: the FieldType of the passed-in TypeOption - /// * `old_type_option_data`: the data that can be parsed into corresponding `TypeOption`. - /// - /// - fn transform_type_option(&mut self, _old_type_option_field_type: FieldType, _old_type_option_data: String) {} + /// Transform the TypeOption from one field type to another + /// For example, when switching from `checkbox` type-option to `single-select` + /// type-option, adding the `Yes` option if the `single-select` type-option doesn't contain it. + /// But the cell content is a string, `Yes`, it's need to do the cell content transform. + /// The `Yes` string will be transformed to the `Yes` option id. + /// + /// # Arguments + /// + /// * `old_type_option_field_type`: the FieldType of the passed-in TypeOption + /// * `old_type_option_data`: the data that can be parsed into corresponding `TypeOption`. + /// + /// + fn transform_type_option( + &mut self, + _old_type_option_field_type: FieldType, + _old_type_option_data: String, + ) { + } - /// Transform the cell data from one field type to another - /// - /// # Arguments - /// - /// * `cell_str`: the cell string of the current field type - /// * `decoded_field_type`: the field type of the cell data that's going to be transformed into - /// current `TypeOption` field type. - /// - fn transform_type_option_cell_str( - &self, - _cell_str: &str, - _decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> Option<::CellData> { - None - } + /// Transform the cell data from one field type to another + /// + /// # Arguments + /// + /// * `cell_str`: the cell string of the current field type + /// * `decoded_field_type`: the field type of the cell data that's going to be transformed into + /// current `TypeOption` field type. + /// + fn transform_type_option_cell_str( + &self, + _cell_str: &str, + _decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> Option<::CellData> { + None + } } pub trait TypeOptionCellDataFilter: TypeOption + CellDataDecoder { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool; + fn apply_filter( + &self, + filter: &::CellFilter, + field_type: &FieldType, + cell_data: &::CellData, + ) -> bool; } #[inline(always)] pub fn default_order() -> Ordering { - Ordering::Equal + Ordering::Equal } pub trait TypeOptionCellDataCompare: TypeOption { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering; + fn apply_cmp( + &self, + cell_data: &::CellData, + other_cell_data: &::CellData, + ) -> Ordering; } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/type_option_cell.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/type_option_cell.rs index fd0d9a1d94..48c6eaad2d 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/type_option_cell.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/type_option_cell.rs @@ -1,12 +1,13 @@ use crate::entities::FieldType; use crate::services::cell::{ - AtomicCellDataCache, AtomicCellFilterCache, CellDataChangeset, CellDataDecoder, CellProtobufBlob, - FromCellChangesetString, FromCellString, TypeCellData, + AtomicCellDataCache, AtomicCellFilterCache, CellDataChangeset, CellDataDecoder, CellProtobufBlob, + FromCellChangesetString, FromCellString, TypeCellData, }; use crate::services::field::{ - CheckboxTypeOptionPB, ChecklistTypeOptionPB, DateTypeOptionPB, MultiSelectTypeOptionPB, NumberTypeOptionPB, - RichTextTypeOptionPB, SingleSelectTypeOptionPB, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare, - TypeOptionCellDataFilter, TypeOptionTransform, URLTypeOptionPB, + CheckboxTypeOptionPB, ChecklistTypeOptionPB, DateTypeOptionPB, MultiSelectTypeOptionPB, + NumberTypeOptionPB, RichTextTypeOptionPB, SingleSelectTypeOptionPB, TypeOption, + TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, + URLTypeOptionPB, }; use crate::services::filter::FilterType; use flowy_error::FlowyResult; @@ -23,533 +24,547 @@ use std::hash::{Hash, Hasher}; /// 2.there are no generic types parameters. /// pub trait TypeOptionCellDataHandler { - fn handle_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> FlowyResult; + fn handle_cell_str( + &self, + cell_str: String, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> FlowyResult; - fn handle_cell_changeset( - &self, - cell_changeset: String, - old_type_cell_data: Option, - field_rev: &FieldRevision, - ) -> FlowyResult; + fn handle_cell_changeset( + &self, + cell_changeset: String, + old_type_cell_data: Option, + field_rev: &FieldRevision, + ) -> FlowyResult; - fn handle_cell_compare(&self, left_cell_data: &str, right_cell_data: &str, field_rev: &FieldRevision) -> Ordering; + fn handle_cell_compare( + &self, + left_cell_data: &str, + right_cell_data: &str, + field_rev: &FieldRevision, + ) -> Ordering; - fn handle_cell_filter( - &self, - filter_type: &FilterType, - field_rev: &FieldRevision, - type_cell_data: TypeCellData, - ) -> bool; + fn handle_cell_filter( + &self, + filter_type: &FilterType, + field_rev: &FieldRevision, + type_cell_data: TypeCellData, + ) -> bool; - /// Decode the cell_str to corresponding cell data, and then return the display string of the - /// cell data. - fn stringify_cell_str(&self, cell_str: String, decoded_field_type: &FieldType, field_rev: &FieldRevision) - -> String; + /// Decode the cell_str to corresponding cell data, and then return the display string of the + /// cell data. + fn stringify_cell_str( + &self, + cell_str: String, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> String; - fn get_cell_data( - &self, - cell_str: String, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> FlowyResult; + fn get_cell_data( + &self, + cell_str: String, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> FlowyResult; } struct CellDataCacheKey(u64); impl CellDataCacheKey { - pub fn new(field_rev: &FieldRevision, decoded_field_type: FieldType, cell_str: &str) -> Self { - let mut hasher = DefaultHasher::new(); - if let Some(type_option_str) = field_rev.get_type_option_str(&decoded_field_type) { - type_option_str.hash(&mut hasher); - } - hasher.write(field_rev.id.as_bytes()); - hasher.write_u8(decoded_field_type as u8); - cell_str.hash(&mut hasher); - Self(hasher.finish()) + pub fn new(field_rev: &FieldRevision, decoded_field_type: FieldType, cell_str: &str) -> Self { + let mut hasher = DefaultHasher::new(); + if let Some(type_option_str) = field_rev.get_type_option_str(&decoded_field_type) { + type_option_str.hash(&mut hasher); } + hasher.write(field_rev.id.as_bytes()); + hasher.write_u8(decoded_field_type as u8); + cell_str.hash(&mut hasher); + Self(hasher.finish()) + } } impl AsRef for CellDataCacheKey { - fn as_ref(&self) -> &u64 { - &self.0 - } + fn as_ref(&self) -> &u64 { + &self.0 + } } struct TypeOptionCellDataHandlerImpl { + inner: T, + cell_data_cache: Option, + cell_filter_cache: Option, +} + +impl TypeOptionCellDataHandlerImpl +where + T: TypeOption + + CellDataDecoder + + CellDataChangeset + + TypeOptionCellData + + TypeOptionTransform + + TypeOptionCellDataFilter + + TypeOptionCellDataCompare + + 'static, +{ + pub fn new_with_boxed( inner: T, - cell_data_cache: Option, cell_filter_cache: Option, + cell_data_cache: Option, + ) -> Box { + Box::new(Self { + inner, + cell_data_cache, + cell_filter_cache, + }) as Box + } } impl TypeOptionCellDataHandlerImpl where - T: TypeOption - + CellDataDecoder - + CellDataChangeset - + TypeOptionCellData - + TypeOptionTransform - + TypeOptionCellDataFilter - + TypeOptionCellDataCompare - + 'static, + T: TypeOption + CellDataDecoder, { - pub fn new_with_boxed( - inner: T, - cell_filter_cache: Option, - cell_data_cache: Option, - ) -> Box { - Box::new(Self { - inner, - cell_data_cache, - cell_filter_cache, - }) as Box - } -} - -impl TypeOptionCellDataHandlerImpl -where - T: TypeOption + CellDataDecoder, -{ - fn get_decoded_cell_data( - &self, - cell_str: String, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> FlowyResult<::CellData> { - let key = CellDataCacheKey::new(field_rev, decoded_field_type.clone(), &cell_str); - if let Some(cell_data_cache) = self.cell_data_cache.as_ref() { - let read_guard = cell_data_cache.read(); - if let Some(cell_data) = read_guard.get(key.as_ref()).cloned() { - tracing::trace!( - "Cell cache hit: field_type:{}, cell_str: {}, cell_data: {:?}", - decoded_field_type, - cell_str, - cell_data - ); - return Ok(cell_data); - } - } - - let cell_data = self.decode_cell_str(cell_str.clone(), decoded_field_type, field_rev)?; - if let Some(cell_data_cache) = self.cell_data_cache.as_ref() { - tracing::trace!( - "Cell cache update: field_type:{}, cell_str: {}, cell_data: {:?}", - decoded_field_type, - cell_str, - cell_data - ); - cell_data_cache.write().insert(key.as_ref(), cell_data.clone()); - } - Ok(cell_data) + fn get_decoded_cell_data( + &self, + cell_str: String, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> FlowyResult<::CellData> { + let key = CellDataCacheKey::new(field_rev, decoded_field_type.clone(), &cell_str); + if let Some(cell_data_cache) = self.cell_data_cache.as_ref() { + let read_guard = cell_data_cache.read(); + if let Some(cell_data) = read_guard.get(key.as_ref()).cloned() { + tracing::trace!( + "Cell cache hit: field_type:{}, cell_str: {}, cell_data: {:?}", + decoded_field_type, + cell_str, + cell_data + ); + return Ok(cell_data); + } } - fn set_decoded_cell_data( - &self, - cell_str: &str, - cell_data: ::CellData, - field_rev: &FieldRevision, - ) { - if let Some(cell_data_cache) = self.cell_data_cache.as_ref() { - let field_type: FieldType = field_rev.ty.into(); - let key = CellDataCacheKey::new(field_rev, field_type.clone(), cell_str); - tracing::trace!( - "Cell cache update: field_type:{}, cell_str: {}, cell_data: {:?}", - field_type, - cell_str, - cell_data - ); - cell_data_cache.write().insert(key.as_ref(), cell_data); - } + let cell_data = self.decode_cell_str(cell_str.clone(), decoded_field_type, field_rev)?; + if let Some(cell_data_cache) = self.cell_data_cache.as_ref() { + tracing::trace!( + "Cell cache update: field_type:{}, cell_str: {}, cell_data: {:?}", + decoded_field_type, + cell_str, + cell_data + ); + cell_data_cache + .write() + .insert(key.as_ref(), cell_data.clone()); } + Ok(cell_data) + } + + fn set_decoded_cell_data( + &self, + cell_str: &str, + cell_data: ::CellData, + field_rev: &FieldRevision, + ) { + if let Some(cell_data_cache) = self.cell_data_cache.as_ref() { + let field_type: FieldType = field_rev.ty.into(); + let key = CellDataCacheKey::new(field_rev, field_type.clone(), cell_str); + tracing::trace!( + "Cell cache update: field_type:{}, cell_str: {}, cell_data: {:?}", + field_type, + cell_str, + cell_data + ); + cell_data_cache.write().insert(key.as_ref(), cell_data); + } + } } impl std::ops::Deref for TypeOptionCellDataHandlerImpl { - type Target = T; + type Target = T; - fn deref(&self) -> &Self::Target { - &self.inner - } + fn deref(&self) -> &Self::Target { + &self.inner + } } impl TypeOption for TypeOptionCellDataHandlerImpl where - T: TypeOption, + T: TypeOption, { - type CellData = T::CellData; - type CellChangeset = T::CellChangeset; - type CellProtobufType = T::CellProtobufType; - type CellFilter = T::CellFilter; + type CellData = T::CellData; + type CellChangeset = T::CellChangeset; + type CellProtobufType = T::CellProtobufType; + type CellFilter = T::CellFilter; } impl TypeOptionCellDataHandler for TypeOptionCellDataHandlerImpl where - T: TypeOption - + CellDataDecoder - + CellDataChangeset - + TypeOptionCellData - + TypeOptionTransform - + TypeOptionCellDataFilter - + TypeOptionCellDataCompare, + T: TypeOption + + CellDataDecoder + + CellDataChangeset + + TypeOptionCellData + + TypeOptionTransform + + TypeOptionCellDataFilter + + TypeOptionCellDataCompare, { - fn handle_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> FlowyResult { - let cell_data = self - .get_cell_data(cell_str, decoded_field_type, field_rev)? - .unbox_or_default::<::CellData>(); + fn handle_cell_str( + &self, + cell_str: String, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> FlowyResult { + let cell_data = self + .get_cell_data(cell_str, decoded_field_type, field_rev)? + .unbox_or_default::<::CellData>(); - CellProtobufBlob::from(self.convert_to_protobuf(cell_data)) + CellProtobufBlob::from(self.convert_to_protobuf(cell_data)) + } + + fn handle_cell_changeset( + &self, + cell_changeset: String, + old_type_cell_data: Option, + field_rev: &FieldRevision, + ) -> FlowyResult { + let changeset = ::CellChangeset::from_changeset(cell_changeset)?; + let (cell_str, cell_data) = self.apply_changeset(changeset, old_type_cell_data)?; + self.set_decoded_cell_data(&cell_str, cell_data, field_rev); + Ok(cell_str) + } + + fn handle_cell_compare( + &self, + left_cell_data: &str, + right_cell_data: &str, + field_rev: &FieldRevision, + ) -> Ordering { + let field_type: FieldType = field_rev.ty.into(); + let left = self + .get_decoded_cell_data(left_cell_data.to_owned(), &field_type, field_rev) + .unwrap_or_default(); + let right = self + .get_decoded_cell_data(right_cell_data.to_owned(), &field_type, field_rev) + .unwrap_or_default(); + self.apply_cmp(&left, &right) + } + + fn handle_cell_filter( + &self, + filter_type: &FilterType, + field_rev: &FieldRevision, + type_cell_data: TypeCellData, + ) -> bool { + let perform_filter = || { + let filter_cache = self.cell_filter_cache.as_ref()?.read(); + let cell_filter = filter_cache.get::<::CellFilter>(filter_type)?; + let cell_data = self + .get_decoded_cell_data(type_cell_data.cell_str, &filter_type.field_type, field_rev) + .ok()?; + Some(self.apply_filter(cell_filter, &filter_type.field_type, &cell_data)) + }; + + perform_filter().unwrap_or(true) + } + + fn stringify_cell_str( + &self, + cell_str: String, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> String { + if self.transformable() { + let cell_data = self.transform_type_option_cell_str(&cell_str, decoded_field_type, field_rev); + if let Some(cell_data) = cell_data { + return self.decode_cell_data_to_str(cell_data); + } } - - fn handle_cell_changeset( - &self, - cell_changeset: String, - old_type_cell_data: Option, - field_rev: &FieldRevision, - ) -> FlowyResult { - let changeset = ::CellChangeset::from_changeset(cell_changeset)?; - let (cell_str, cell_data) = self.apply_changeset(changeset, old_type_cell_data)?; - self.set_decoded_cell_data(&cell_str, cell_data, field_rev); - Ok(cell_str) + match ::CellData::from_cell_str(&cell_str) { + Ok(cell_data) => self.decode_cell_data_to_str(cell_data), + Err(_) => "".to_string(), } + } - fn handle_cell_compare(&self, left_cell_data: &str, right_cell_data: &str, field_rev: &FieldRevision) -> Ordering { - let field_type: FieldType = field_rev.ty.into(); - let left = self - .get_decoded_cell_data(left_cell_data.to_owned(), &field_type, field_rev) - .unwrap_or_default(); - let right = self - .get_decoded_cell_data(right_cell_data.to_owned(), &field_type, field_rev) - .unwrap_or_default(); - self.apply_cmp(&left, &right) - } - - fn handle_cell_filter( - &self, - filter_type: &FilterType, - field_rev: &FieldRevision, - type_cell_data: TypeCellData, - ) -> bool { - let perform_filter = || { - let filter_cache = self.cell_filter_cache.as_ref()?.read(); - let cell_filter = filter_cache.get::<::CellFilter>(filter_type)?; - let cell_data = self - .get_decoded_cell_data(type_cell_data.cell_str, &filter_type.field_type, field_rev) - .ok()?; - Some(self.apply_filter(cell_filter, &filter_type.field_type, &cell_data)) - }; - - perform_filter().unwrap_or(true) - } - - fn stringify_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> String { - if self.transformable() { - let cell_data = self.transform_type_option_cell_str(&cell_str, decoded_field_type, field_rev); - if let Some(cell_data) = cell_data { - return self.decode_cell_data_to_str(cell_data); - } - } - match ::CellData::from_cell_str(&cell_str) { - Ok(cell_data) => self.decode_cell_data_to_str(cell_data), - Err(_) => "".to_string(), - } - } - - fn get_cell_data( - &self, - cell_str: String, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> FlowyResult { - 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)?, - Some(cell_data) => cell_data, - } - } else { - self.get_decoded_cell_data(cell_str, decoded_field_type, field_rev)? - }; - Ok(BoxCellData::new(cell_data)) - } + fn get_cell_data( + &self, + cell_str: String, + decoded_field_type: &FieldType, + field_rev: &FieldRevision, + ) -> FlowyResult { + 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)?, + Some(cell_data) => cell_data, + } + } else { + self.get_decoded_cell_data(cell_str, decoded_field_type, field_rev)? + }; + Ok(BoxCellData::new(cell_data)) + } } pub struct TypeOptionCellExt<'a> { - field_rev: &'a FieldRevision, - cell_data_cache: Option, - cell_filter_cache: Option, + field_rev: &'a FieldRevision, + cell_data_cache: Option, + cell_filter_cache: Option, } impl<'a> TypeOptionCellExt<'a> { - pub fn new_with_cell_data_cache( - field_rev: &'a FieldRevision, - cell_data_cache: Option, - ) -> Self { - Self { - field_rev, - cell_data_cache, - cell_filter_cache: None, - } + pub fn new_with_cell_data_cache( + field_rev: &'a FieldRevision, + cell_data_cache: Option, + ) -> Self { + Self { + field_rev, + cell_data_cache, + cell_filter_cache: None, } + } - pub fn new( - field_rev: &'a FieldRevision, - cell_data_cache: Option, - cell_filter_cache: Option, - ) -> Self { - let mut this = Self::new_with_cell_data_cache(field_rev, cell_data_cache); - this.cell_filter_cache = cell_filter_cache; - this - } + pub fn new( + field_rev: &'a FieldRevision, + cell_data_cache: Option, + cell_filter_cache: Option, + ) -> Self { + let mut this = Self::new_with_cell_data_cache(field_rev, cell_data_cache); + this.cell_filter_cache = cell_filter_cache; + this + } - pub fn get_cells(&self) -> Vec { - let field_type: FieldType = self.field_rev.ty.into(); - match self.get_type_option_cell_data_handler(&field_type) { - None => vec![], - Some(_handler) => { - todo!() - } - } + pub fn get_cells(&self) -> Vec { + let field_type: FieldType = self.field_rev.ty.into(); + match self.get_type_option_cell_data_handler(&field_type) { + None => vec![], + Some(_handler) => { + todo!() + }, } + } - pub fn get_type_option_cell_data_handler( - &self, - field_type: &FieldType, - ) -> Option> { - match field_type { - FieldType::RichText => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - FieldType::Number => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - FieldType::DateTime => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - FieldType::SingleSelect => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - FieldType::MultiSelect => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - FieldType::Checkbox => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - FieldType::URL => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - FieldType::Checklist => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - } + pub fn get_type_option_cell_data_handler( + &self, + field_type: &FieldType, + ) -> Option> { + match field_type { + FieldType::RichText => self + .field_rev + .get_type_option::(field_type.into()) + .map(|type_option| { + TypeOptionCellDataHandlerImpl::new_with_boxed( + type_option, + self.cell_filter_cache.clone(), + self.cell_data_cache.clone(), + ) + }), + FieldType::Number => self + .field_rev + .get_type_option::(field_type.into()) + .map(|type_option| { + TypeOptionCellDataHandlerImpl::new_with_boxed( + type_option, + self.cell_filter_cache.clone(), + self.cell_data_cache.clone(), + ) + }), + FieldType::DateTime => self + .field_rev + .get_type_option::(field_type.into()) + .map(|type_option| { + TypeOptionCellDataHandlerImpl::new_with_boxed( + type_option, + self.cell_filter_cache.clone(), + self.cell_data_cache.clone(), + ) + }), + FieldType::SingleSelect => self + .field_rev + .get_type_option::(field_type.into()) + .map(|type_option| { + TypeOptionCellDataHandlerImpl::new_with_boxed( + type_option, + self.cell_filter_cache.clone(), + self.cell_data_cache.clone(), + ) + }), + FieldType::MultiSelect => self + .field_rev + .get_type_option::(field_type.into()) + .map(|type_option| { + TypeOptionCellDataHandlerImpl::new_with_boxed( + type_option, + self.cell_filter_cache.clone(), + self.cell_data_cache.clone(), + ) + }), + FieldType::Checkbox => self + .field_rev + .get_type_option::(field_type.into()) + .map(|type_option| { + TypeOptionCellDataHandlerImpl::new_with_boxed( + type_option, + self.cell_filter_cache.clone(), + self.cell_data_cache.clone(), + ) + }), + FieldType::URL => self + .field_rev + .get_type_option::(field_type.into()) + .map(|type_option| { + TypeOptionCellDataHandlerImpl::new_with_boxed( + type_option, + self.cell_filter_cache.clone(), + self.cell_data_cache.clone(), + ) + }), + FieldType::Checklist => self + .field_rev + .get_type_option::(field_type.into()) + .map(|type_option| { + TypeOptionCellDataHandlerImpl::new_with_boxed( + type_option, + self.cell_filter_cache.clone(), + self.cell_data_cache.clone(), + ) + }), } + } } pub fn transform_type_option( - type_option_data: &str, - new_field_type: &FieldType, - old_type_option_data: Option, - old_field_type: FieldType, + type_option_data: &str, + new_field_type: &FieldType, + old_type_option_data: Option, + old_field_type: FieldType, ) -> String { - let mut transform_handler = get_type_option_transform_handler(type_option_data, new_field_type); - if let Some(old_type_option_data) = old_type_option_data { - transform_handler.transform(old_field_type, old_type_option_data); - } - transform_handler.json_str() + let mut transform_handler = get_type_option_transform_handler(type_option_data, new_field_type); + if let Some(old_type_option_data) = old_type_option_data { + transform_handler.transform(old_field_type, old_type_option_data); + } + transform_handler.json_str() } /// A helper trait that used to erase the `Self` of `TypeOption` trait to make it become a Object-safe trait. pub trait TypeOptionTransformHandler { - fn transform(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String); + fn transform(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String); - fn json_str(&self) -> String; + fn json_str(&self) -> String; } impl TypeOptionTransformHandler for T where - T: TypeOptionTransform + TypeOptionDataSerializer, + T: TypeOptionTransform + TypeOptionDataSerializer, { - fn transform(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String) { - if self.transformable() { - self.transform_type_option(old_type_option_field_type, old_type_option_data) - } + fn transform(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String) { + if self.transformable() { + self.transform_type_option(old_type_option_field_type, old_type_option_data) } + } - fn json_str(&self) -> String { - self.json_str() - } + fn json_str(&self) -> String { + self.json_str() + } } fn get_type_option_transform_handler( - type_option_data: &str, - field_type: &FieldType, + type_option_data: &str, + field_type: &FieldType, ) -> Box { - match field_type { - FieldType::RichText => { - Box::new(RichTextTypeOptionPB::from_json_str(type_option_data)) as Box - } - FieldType::Number => { - Box::new(NumberTypeOptionPB::from_json_str(type_option_data)) as Box - } - FieldType::DateTime => { - Box::new(DateTypeOptionPB::from_json_str(type_option_data)) as Box - } - FieldType::SingleSelect => { - Box::new(SingleSelectTypeOptionPB::from_json_str(type_option_data)) as Box - } - FieldType::MultiSelect => { - Box::new(MultiSelectTypeOptionPB::from_json_str(type_option_data)) as Box - } - FieldType::Checkbox => { - Box::new(CheckboxTypeOptionPB::from_json_str(type_option_data)) as Box - } - FieldType::URL => { - Box::new(URLTypeOptionPB::from_json_str(type_option_data)) as Box - } - FieldType::Checklist => { - Box::new(ChecklistTypeOptionPB::from_json_str(type_option_data)) as Box - } - } + match field_type { + FieldType::RichText => Box::new(RichTextTypeOptionPB::from_json_str(type_option_data)) + as Box, + FieldType::Number => Box::new(NumberTypeOptionPB::from_json_str(type_option_data)) + as Box, + FieldType::DateTime => Box::new(DateTypeOptionPB::from_json_str(type_option_data)) + as Box, + FieldType::SingleSelect => Box::new(SingleSelectTypeOptionPB::from_json_str(type_option_data)) + as Box, + FieldType::MultiSelect => Box::new(MultiSelectTypeOptionPB::from_json_str(type_option_data)) + as Box, + FieldType::Checkbox => Box::new(CheckboxTypeOptionPB::from_json_str(type_option_data)) + as Box, + FieldType::URL => Box::new(URLTypeOptionPB::from_json_str(type_option_data)) + as Box, + FieldType::Checklist => Box::new(ChecklistTypeOptionPB::from_json_str(type_option_data)) + as Box, + } } pub struct BoxCellData(Box); impl BoxCellData { - fn new(value: T) -> Self - where - T: Send + Sync + 'static, - { - Self(Box::new(value)) - } + fn new(value: T) -> Self + where + T: Send + Sync + 'static, + { + Self(Box::new(value)) + } - fn unbox_or_default(self) -> T - where - T: Default + 'static, - { - match self.0.downcast::() { - Ok(value) => *value, - Err(_) => T::default(), - } + fn unbox_or_default(self) -> T + where + T: Default + 'static, + { + match self.0.downcast::() { + Ok(value) => *value, + Err(_) => T::default(), } + } - pub(crate) fn unbox_or_none(self) -> Option - where - T: Default + 'static, - { - match self.0.downcast::() { - Ok(value) => Some(*value), - Err(_) => None, - } + pub(crate) fn unbox_or_none(self) -> Option + where + T: Default + 'static, + { + match self.0.downcast::() { + Ok(value) => Some(*value), + Err(_) => None, } + } - #[allow(dead_code)] - fn downcast_ref(&self) -> Option<&T> { - self.0.downcast_ref() - } + #[allow(dead_code)] + fn downcast_ref(&self) -> Option<&T> { + self.0.downcast_ref() + } } pub struct RowSingleCellData { - pub row_id: String, - pub field_id: String, - pub field_type: FieldType, - pub cell_data: BoxCellData, + pub row_id: String, + pub field_id: String, + pub field_type: FieldType, + pub cell_data: BoxCellData, } macro_rules! into_cell_data { - ($func_name:ident,$return_ty:ty) => { - #[allow(dead_code)] - pub fn $func_name(self) -> Option<$return_ty> { - self.cell_data.unbox_or_none() - } - }; + ($func_name:ident,$return_ty:ty) => { + #[allow(dead_code)] + pub fn $func_name(self) -> Option<$return_ty> { + self.cell_data.unbox_or_none() + } + }; } impl RowSingleCellData { - into_cell_data!( - into_text_field_cell_data, - ::CellData - ); - into_cell_data!( - into_number_field_cell_data, - ::CellData - ); - into_cell_data!(into_url_field_cell_data, ::CellData); - into_cell_data!( - into_single_select_field_cell_data, - ::CellData - ); - into_cell_data!( - into_multi_select_field_cell_data, - ::CellData - ); - into_cell_data!(into_date_field_cell_data, ::CellData); - into_cell_data!( - into_check_list_field_cell_data, - ::CellData - ); + into_cell_data!( + into_text_field_cell_data, + ::CellData + ); + into_cell_data!( + into_number_field_cell_data, + ::CellData + ); + into_cell_data!( + into_url_field_cell_data, + ::CellData + ); + into_cell_data!( + into_single_select_field_cell_data, + ::CellData + ); + into_cell_data!( + into_multi_select_field_cell_data, + ::CellData + ); + into_cell_data!( + into_date_field_cell_data, + ::CellData + ); + into_cell_data!( + into_check_list_field_cell_data, + ::CellData + ); } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_tests.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_tests.rs index 3b55ff1acb..9ce915299f 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_tests.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_tests.rs @@ -1,164 +1,167 @@ #[cfg(test)] mod tests { - use crate::entities::FieldType; - use crate::services::cell::CellDataChangeset; + use crate::entities::FieldType; + use crate::services::cell::CellDataChangeset; - use crate::services::field::FieldBuilder; - use crate::services::field::URLTypeOptionPB; - use grid_model::FieldRevision; + use crate::services::field::FieldBuilder; + use crate::services::field::URLTypeOptionPB; + use grid_model::FieldRevision; - /// The expected_str will equal to the input string, but the expected_url will be empty if there's no - /// http url in the input string. - #[test] - fn url_type_option_does_not_contain_url_test() { - let type_option = URLTypeOptionPB::default(); - let field_type = FieldType::URL; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_url(&type_option, "123", "123", "", &field_rev); - assert_url(&type_option, "", "", "", &field_rev); - } + /// The expected_str will equal to the input string, but the expected_url will be empty if there's no + /// http url in the input string. + #[test] + fn url_type_option_does_not_contain_url_test() { + let type_option = URLTypeOptionPB::default(); + let field_type = FieldType::URL; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + assert_url(&type_option, "123", "123", "", &field_rev); + assert_url(&type_option, "", "", "", &field_rev); + } - /// The expected_str will equal to the input string, but the expected_url will not be empty - /// if there's a http url in the input string. - #[test] - fn url_type_option_contains_url_test() { - let type_option = URLTypeOptionPB::default(); - let field_type = FieldType::URL; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_url( - &type_option, - "AppFlowy website - https://www.appflowy.io", - "AppFlowy website - https://www.appflowy.io", - "https://www.appflowy.io/", - &field_rev, - ); + /// The expected_str will equal to the input string, but the expected_url will not be empty + /// if there's a http url in the input string. + #[test] + fn url_type_option_contains_url_test() { + let type_option = URLTypeOptionPB::default(); + let field_type = FieldType::URL; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + assert_url( + &type_option, + "AppFlowy website - https://www.appflowy.io", + "AppFlowy website - https://www.appflowy.io", + "https://www.appflowy.io/", + &field_rev, + ); - assert_url( - &type_option, - "AppFlowy website appflowy.io", - "AppFlowy website appflowy.io", - "https://appflowy.io", - &field_rev, - ); - } + assert_url( + &type_option, + "AppFlowy website appflowy.io", + "AppFlowy website appflowy.io", + "https://appflowy.io", + &field_rev, + ); + } - /// if there's a http url and some words following it in the input string. - #[test] - fn url_type_option_contains_url_with_string_after_test() { - let type_option = URLTypeOptionPB::default(); - let field_type = FieldType::URL; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_url( - &type_option, - "AppFlowy website - https://www.appflowy.io welcome!", - "AppFlowy website - https://www.appflowy.io welcome!", - "https://www.appflowy.io/", - &field_rev, - ); + /// if there's a http url and some words following it in the input string. + #[test] + fn url_type_option_contains_url_with_string_after_test() { + let type_option = URLTypeOptionPB::default(); + let field_type = FieldType::URL; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + assert_url( + &type_option, + "AppFlowy website - https://www.appflowy.io welcome!", + "AppFlowy website - https://www.appflowy.io welcome!", + "https://www.appflowy.io/", + &field_rev, + ); - assert_url( - &type_option, - "AppFlowy website appflowy.io welcome!", - "AppFlowy website appflowy.io welcome!", - "https://appflowy.io", - &field_rev, - ); - } + assert_url( + &type_option, + "AppFlowy website appflowy.io welcome!", + "AppFlowy website appflowy.io welcome!", + "https://appflowy.io", + &field_rev, + ); + } - /// if there's a http url and special words following it in the input string. - #[test] - fn url_type_option_contains_url_with_special_string_after_test() { - let type_option = URLTypeOptionPB::default(); - let field_type = FieldType::URL; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_url( - &type_option, - "AppFlowy website - https://www.appflowy.io!", - "AppFlowy website - https://www.appflowy.io!", - "https://www.appflowy.io/", - &field_rev, - ); + /// if there's a http url and special words following it in the input string. + #[test] + fn url_type_option_contains_url_with_special_string_after_test() { + let type_option = URLTypeOptionPB::default(); + let field_type = FieldType::URL; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + assert_url( + &type_option, + "AppFlowy website - https://www.appflowy.io!", + "AppFlowy website - https://www.appflowy.io!", + "https://www.appflowy.io/", + &field_rev, + ); - assert_url( - &type_option, - "AppFlowy website appflowy.io!", - "AppFlowy website appflowy.io!", - "https://appflowy.io", - &field_rev, - ); - } + assert_url( + &type_option, + "AppFlowy website appflowy.io!", + "AppFlowy website appflowy.io!", + "https://appflowy.io", + &field_rev, + ); + } - /// if there's a level4 url in the input string. - #[test] - fn level4_url_type_test() { - let type_option = URLTypeOptionPB::default(); - let field_type = FieldType::URL; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_url( - &type_option, - "test - https://tester.testgroup.appflowy.io", - "test - https://tester.testgroup.appflowy.io", - "https://tester.testgroup.appflowy.io/", - &field_rev, - ); + /// if there's a level4 url in the input string. + #[test] + fn level4_url_type_test() { + let type_option = URLTypeOptionPB::default(); + let field_type = FieldType::URL; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + assert_url( + &type_option, + "test - https://tester.testgroup.appflowy.io", + "test - https://tester.testgroup.appflowy.io", + "https://tester.testgroup.appflowy.io/", + &field_rev, + ); - assert_url( - &type_option, - "test tester.testgroup.appflowy.io", - "test tester.testgroup.appflowy.io", - "https://tester.testgroup.appflowy.io", - &field_rev, - ); - } + assert_url( + &type_option, + "test tester.testgroup.appflowy.io", + "test tester.testgroup.appflowy.io", + "https://tester.testgroup.appflowy.io", + &field_rev, + ); + } - /// urls with different top level domains. - #[test] - fn different_top_level_domains_test() { - let type_option = URLTypeOptionPB::default(); - let field_type = FieldType::URL; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_url( - &type_option, - "appflowy - https://appflowy.com", - "appflowy - https://appflowy.com", - "https://appflowy.com/", - &field_rev, - ); + /// urls with different top level domains. + #[test] + fn different_top_level_domains_test() { + let type_option = URLTypeOptionPB::default(); + let field_type = FieldType::URL; + let field_rev = FieldBuilder::from_field_type(&field_type).build(); + assert_url( + &type_option, + "appflowy - https://appflowy.com", + "appflowy - https://appflowy.com", + "https://appflowy.com/", + &field_rev, + ); - assert_url( - &type_option, - "appflowy - https://appflowy.top", - "appflowy - https://appflowy.top", - "https://appflowy.top/", - &field_rev, - ); + assert_url( + &type_option, + "appflowy - https://appflowy.top", + "appflowy - https://appflowy.top", + "https://appflowy.top/", + &field_rev, + ); - assert_url( - &type_option, - "appflowy - https://appflowy.net", - "appflowy - https://appflowy.net", - "https://appflowy.net/", - &field_rev, - ); + assert_url( + &type_option, + "appflowy - https://appflowy.net", + "appflowy - https://appflowy.net", + "https://appflowy.net/", + &field_rev, + ); - assert_url( - &type_option, - "appflowy - https://appflowy.edu", - "appflowy - https://appflowy.edu", - "https://appflowy.edu/", - &field_rev, - ); - } + assert_url( + &type_option, + "appflowy - https://appflowy.edu", + "appflowy - https://appflowy.edu", + "https://appflowy.edu/", + &field_rev, + ); + } - fn assert_url( - type_option: &URLTypeOptionPB, - input_str: &str, - expected_str: &str, - expected_url: &str, - _field_rev: &FieldRevision, - ) { - let decode_cell_data = type_option.apply_changeset(input_str.to_owned(), None).unwrap().1; - assert_eq!(expected_str.to_owned(), decode_cell_data.content); - assert_eq!(expected_url.to_owned(), decode_cell_data.url); - } + fn assert_url( + type_option: &URLTypeOptionPB, + input_str: &str, + expected_str: &str, + expected_url: &str, + _field_rev: &FieldRevision, + ) { + let decode_cell_data = type_option + .apply_changeset(input_str.to_owned(), None) + .unwrap() + .1; + assert_eq!(expected_str.to_owned(), decode_cell_data.content); + assert_eq!(expected_url.to_owned(), decode_cell_data.url); + } } diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_type_option.rs index b4518c1685..ef6c92d1b5 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_type_option.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_type_option.rs @@ -2,8 +2,9 @@ use crate::entities::{FieldType, TextFilterPB}; use crate::impl_type_option; use crate::services::cell::{CellDataChangeset, CellDataDecoder, FromCellString, TypeCellData}; use crate::services::field::{ - BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare, - TypeOptionCellDataFilter, TypeOptionTransform, URLCellData, URLCellDataPB, + BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, + TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, URLCellData, + URLCellDataPB, }; use bytes::Bytes; use fancy_regex::Regex; @@ -20,121 +21,127 @@ impl_into_box_type_option_builder!(URLTypeOptionBuilder); impl_builder_from_json_str_and_from_bytes!(URLTypeOptionBuilder, URLTypeOptionPB); impl TypeOptionBuilder for URLTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::URL - } + fn field_type(&self) -> FieldType { + FieldType::URL + } - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } + fn serializer(&self) -> &dyn TypeOptionDataSerializer { + &self.0 + } } #[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] pub struct URLTypeOptionPB { - #[pb(index = 1)] - pub url: String, + #[pb(index = 1)] + pub url: String, - #[pb(index = 2)] - pub content: String, + #[pb(index = 2)] + pub content: String, } impl_type_option!(URLTypeOptionPB, FieldType::URL); impl TypeOption for URLTypeOptionPB { - type CellData = URLCellData; - type CellChangeset = URLCellChangeset; - type CellProtobufType = URLCellDataPB; - type CellFilter = TextFilterPB; + type CellData = URLCellData; + type CellChangeset = URLCellChangeset; + type CellProtobufType = URLCellDataPB; + type CellFilter = TextFilterPB; } impl TypeOptionTransform for URLTypeOptionPB {} impl TypeOptionCellData for URLTypeOptionPB { - fn convert_to_protobuf(&self, cell_data: ::CellData) -> ::CellProtobufType { - cell_data.into() - } + fn convert_to_protobuf( + &self, + cell_data: ::CellData, + ) -> ::CellProtobufType { + cell_data.into() + } - fn decode_type_option_cell_str(&self, cell_str: String) -> FlowyResult<::CellData> { - URLCellData::from_cell_str(&cell_str) - } + fn decode_type_option_cell_str( + &self, + cell_str: String, + ) -> FlowyResult<::CellData> { + URLCellData::from_cell_str(&cell_str) + } } impl CellDataDecoder for URLTypeOptionPB { - fn decode_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult<::CellData> { - if !decoded_field_type.is_url() { - return Ok(Default::default()); - } - - self.decode_type_option_cell_str(cell_str) + fn decode_cell_str( + &self, + cell_str: String, + decoded_field_type: &FieldType, + _field_rev: &FieldRevision, + ) -> FlowyResult<::CellData> { + if !decoded_field_type.is_url() { + return Ok(Default::default()); } - fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { - cell_data.content - } + self.decode_type_option_cell_str(cell_str) + } + + fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { + cell_data.content + } } pub type URLCellChangeset = String; impl CellDataChangeset for URLTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - _type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - let mut url = "".to_string(); - if let Ok(Some(m)) = URL_REGEX.find(&changeset) { - url = auto_append_scheme(m.as_str()); - } - let url_cell_data = URLCellData { - url, - content: changeset, - }; - Ok((url_cell_data.to_string(), url_cell_data)) + fn apply_changeset( + &self, + changeset: ::CellChangeset, + _type_cell_data: Option, + ) -> FlowyResult<(String, ::CellData)> { + let mut url = "".to_string(); + if let Ok(Some(m)) = URL_REGEX.find(&changeset) { + url = auto_append_scheme(m.as_str()); } + let url_cell_data = URLCellData { + url, + content: changeset, + }; + Ok((url_cell_data.to_string(), url_cell_data)) + } } impl TypeOptionCellDataFilter for URLTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_url() { - return true; - } - - filter.is_visible(cell_data) + fn apply_filter( + &self, + filter: &::CellFilter, + field_type: &FieldType, + cell_data: &::CellData, + ) -> bool { + if !field_type.is_url() { + return true; } + + filter.is_visible(cell_data) + } } impl TypeOptionCellDataCompare for URLTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - cell_data.content.cmp(&other_cell_data.content) - } + fn apply_cmp( + &self, + cell_data: &::CellData, + other_cell_data: &::CellData, + ) -> Ordering { + cell_data.content.cmp(&other_cell_data.content) + } } fn auto_append_scheme(s: &str) -> String { - // Only support https scheme by now - match url::Url::parse(s) { - Ok(url) => { - if url.scheme() == "https" { - url.into() - } else { - format!("https://{}", s) - } - } - Err(_) => { - format!("https://{}", s) - } - } + // Only support https scheme by now + match url::Url::parse(s) { + Ok(url) => { + if url.scheme() == "https" { + url.into() + } else { + format!("https://{}", s) + } + }, + Err(_) => { + format!("https://{}", s) + }, + } } lazy_static! { diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_type_option_entities.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_type_option_entities.rs index f9e98bbcca..e85ad35d9f 100644 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_type_option_entities.rs +++ b/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_type_option_entities.rs @@ -6,89 +6,89 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, ProtoBuf)] pub struct URLCellDataPB { - #[pb(index = 1)] - pub url: String, + #[pb(index = 1)] + pub url: String, - #[pb(index = 2)] - pub content: String, + #[pb(index = 2)] + pub content: String, } impl From for URLCellDataPB { - fn from(data: URLCellData) -> Self { - Self { - url: data.url, - content: data.content, - } + fn from(data: URLCellData) -> Self { + Self { + url: data.url, + content: data.content, } + } } impl DecodedCellData for URLCellDataPB { - type Object = URLCellDataPB; + type Object = URLCellDataPB; - fn is_empty(&self) -> bool { - self.content.is_empty() - } + fn is_empty(&self) -> bool { + self.content.is_empty() + } } #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct URLCellData { - pub url: String, - pub content: String, + pub url: String, + pub content: String, } impl URLCellData { - pub fn new(s: &str) -> Self { - Self { - url: "".to_string(), - content: s.to_string(), - } + pub fn new(s: &str) -> Self { + Self { + url: "".to_string(), + content: s.to_string(), } + } - pub fn to_json(&self) -> FlowyResult { - serde_json::to_string(self).map_err(internal_error) - } + pub fn to_json(&self) -> FlowyResult { + serde_json::to_string(self).map_err(internal_error) + } } impl From for URLCellData { - fn from(data: URLCellDataPB) -> Self { - Self { - url: data.url, - content: data.content, - } + fn from(data: URLCellDataPB) -> Self { + Self { + url: data.url, + content: data.content, } + } } impl AsRef for URLCellData { - fn as_ref(&self) -> &str { - &self.url - } + fn as_ref(&self) -> &str { + &self.url + } } impl DecodedCellData for URLCellData { - type Object = URLCellData; + type Object = URLCellData; - fn is_empty(&self) -> bool { - self.content.is_empty() - } + fn is_empty(&self) -> bool { + self.content.is_empty() + } } pub struct URLCellDataParser(); impl CellProtobufBlobParser for URLCellDataParser { - type Object = URLCellDataPB; + type Object = URLCellDataPB; - fn parser(bytes: &Bytes) -> FlowyResult { - URLCellDataPB::try_from(bytes.as_ref()).map_err(internal_error) - } + fn parser(bytes: &Bytes) -> FlowyResult { + URLCellDataPB::try_from(bytes.as_ref()).map_err(internal_error) + } } impl FromCellString for URLCellData { - fn from_cell_str(s: &str) -> FlowyResult { - serde_json::from_str::(s).map_err(internal_error) - } + fn from_cell_str(s: &str) -> FlowyResult { + serde_json::from_str::(s).map_err(internal_error) + } } impl ToString for URLCellData { - fn to_string(&self) -> String { - self.to_json().unwrap() - } + fn to_string(&self) -> String { + self.to_json().unwrap() + } } diff --git a/frontend/rust-lib/flowy-database/src/services/filter/controller.rs b/frontend/rust-lib/flowy-database/src/services/filter/controller.rs index eebf899463..330fb7118a 100644 --- a/frontend/rust-lib/flowy-database/src/services/filter/controller.rs +++ b/frontend/rust-lib/flowy-database/src/services/filter/controller.rs @@ -1,8 +1,12 @@ use crate::entities::filter_entities::*; use crate::entities::{FieldType, InsertedRowPB, RowPB}; -use crate::services::cell::{AnyTypeCache, AtomicCellDataCache, AtomicCellFilterCache, TypeCellData}; +use crate::services::cell::{ + AnyTypeCache, AtomicCellDataCache, AtomicCellFilterCache, TypeCellData, +}; use crate::services::field::*; -use crate::services::filter::{FilterChangeset, FilterResult, FilterResultNotification, FilterType}; +use crate::services::filter::{ + FilterChangeset, FilterResult, FilterResultNotification, FilterType, +}; use crate::services::row::DatabaseBlockRowRevision; use crate::services::view_editor::{DatabaseViewChanged, GridViewChangedNotifier}; use dashmap::DashMap; @@ -18,335 +22,381 @@ use tokio::sync::RwLock; type RowId = String; pub trait FilterDelegate: Send + Sync + 'static { - fn get_filter_rev(&self, filter_type: FilterType) -> Fut>>; - fn get_field_rev(&self, field_id: &str) -> Fut>>; - fn get_field_revs(&self, field_ids: Option>) -> Fut>>; - fn get_blocks(&self) -> Fut>; - fn get_row_rev(&self, rows_id: &str) -> Fut)>>; + fn get_filter_rev(&self, filter_type: FilterType) -> Fut>>; + fn get_field_rev(&self, field_id: &str) -> Fut>>; + fn get_field_revs(&self, field_ids: Option>) -> Fut>>; + fn get_blocks(&self) -> Fut>; + fn get_row_rev(&self, rows_id: &str) -> Fut)>>; } pub trait FromFilterString { - fn from_filter_rev(filter_rev: &FilterRevision) -> Self - where - Self: Sized; + fn from_filter_rev(filter_rev: &FilterRevision) -> Self + where + Self: Sized; } pub struct FilterController { - view_id: String, - handler_id: String, - delegate: Box, - result_by_row_id: DashMap, - cell_data_cache: AtomicCellDataCache, - cell_filter_cache: AtomicCellFilterCache, - task_scheduler: Arc>, - notifier: GridViewChangedNotifier, + view_id: String, + handler_id: String, + delegate: Box, + result_by_row_id: DashMap, + cell_data_cache: AtomicCellDataCache, + cell_filter_cache: AtomicCellFilterCache, + task_scheduler: Arc>, + notifier: GridViewChangedNotifier, } impl FilterController { - pub async fn new( - view_id: &str, - handler_id: &str, - delegate: T, - task_scheduler: Arc>, - filter_revs: Vec>, - cell_data_cache: AtomicCellDataCache, - notifier: GridViewChangedNotifier, - ) -> Self - where - T: FilterDelegate + 'static, - { - let this = Self { - view_id: view_id.to_string(), - handler_id: handler_id.to_string(), - delegate: Box::new(delegate), - result_by_row_id: DashMap::default(), - cell_data_cache, - cell_filter_cache: AnyTypeCache::::new(), - task_scheduler, - notifier, - }; - this.refresh_filters(filter_revs).await; - this - } + pub async fn new( + view_id: &str, + handler_id: &str, + delegate: T, + task_scheduler: Arc>, + filter_revs: Vec>, + cell_data_cache: AtomicCellDataCache, + notifier: GridViewChangedNotifier, + ) -> Self + where + T: FilterDelegate + 'static, + { + let this = Self { + view_id: view_id.to_string(), + handler_id: handler_id.to_string(), + delegate: Box::new(delegate), + result_by_row_id: DashMap::default(), + cell_data_cache, + cell_filter_cache: AnyTypeCache::::new(), + task_scheduler, + notifier, + }; + this.refresh_filters(filter_revs).await; + this + } - pub async fn close(&self) { - self.task_scheduler - .write() - .await - .unregister_handler(&self.handler_id) - .await; - } + pub async fn close(&self) { + self + .task_scheduler + .write() + .await + .unregister_handler(&self.handler_id) + .await; + } - #[tracing::instrument(name = "schedule_filter_task", level = "trace", skip(self))] - async fn gen_task(&self, task_type: FilterEvent, qos: QualityOfService) { - let task_id = self.task_scheduler.read().await.next_task_id(); - let task = Task::new(&self.handler_id, task_id, TaskContent::Text(task_type.to_string()), qos); - self.task_scheduler.write().await.add_task(task); - } + #[tracing::instrument(name = "schedule_filter_task", level = "trace", skip(self))] + async fn gen_task(&self, task_type: FilterEvent, qos: QualityOfService) { + let task_id = self.task_scheduler.read().await.next_task_id(); + let task = Task::new( + &self.handler_id, + task_id, + TaskContent::Text(task_type.to_string()), + qos, + ); + self.task_scheduler.write().await.add_task(task); + } - pub async fn filter_row_revs(&self, row_revs: &mut Vec>) { - if self.cell_filter_cache.read().is_empty() { - return; + pub async fn filter_row_revs(&self, row_revs: &mut Vec>) { + if self.cell_filter_cache.read().is_empty() { + return; + } + let field_rev_by_field_id = self.get_filter_revs_map().await; + row_revs.iter().for_each(|row_rev| { + let _ = filter_row( + row_rev, + &self.result_by_row_id, + &field_rev_by_field_id, + &self.cell_data_cache, + &self.cell_filter_cache, + ); + }); + + row_revs.retain(|row_rev| { + self + .result_by_row_id + .get(&row_rev.id) + .map(|result| result.is_visible()) + .unwrap_or(false) + }); + } + + async fn get_filter_revs_map(&self) -> HashMap> { + self + .delegate + .get_field_revs(None) + .await + .into_iter() + .map(|field_rev| (field_rev.id.clone(), field_rev)) + .collect::>>() + } + + #[tracing::instrument( + name = "process_filter_task", + level = "trace", + skip_all, + fields(filter_result), + err + )] + pub async fn process(&self, predicate: &str) -> FlowyResult<()> { + let event_type = FilterEvent::from_str(predicate).unwrap(); + match event_type { + FilterEvent::FilterDidChanged => self.filter_all_rows().await?, + FilterEvent::RowDidChanged(row_id) => self.filter_row(row_id).await?, + } + Ok(()) + } + + async fn filter_row(&self, row_id: String) -> FlowyResult<()> { + if let Some((_, row_rev)) = self.delegate.get_row_rev(&row_id).await { + let field_rev_by_field_id = self.get_filter_revs_map().await; + let mut notification = + FilterResultNotification::new(self.view_id.clone(), row_rev.block_id.clone()); + if let Some((row_id, is_visible)) = filter_row( + &row_rev, + &self.result_by_row_id, + &field_rev_by_field_id, + &self.cell_data_cache, + &self.cell_filter_cache, + ) { + if is_visible { + if let Some((index, row_rev)) = self.delegate.get_row_rev(&row_id).await { + let row_pb = RowPB::from(row_rev.as_ref()); + notification + .visible_rows + .push(InsertedRowPB::with_index(row_pb, index as i32)) + } + } else { + notification.invisible_rows.push(row_id); } - let field_rev_by_field_id = self.get_filter_revs_map().await; - row_revs.iter().for_each(|row_rev| { - let _ = filter_row( - row_rev, - &self.result_by_row_id, - &field_rev_by_field_id, - &self.cell_data_cache, - &self.cell_filter_cache, + } + + let _ = self + .notifier + .send(DatabaseViewChanged::FilterNotification(notification)); + } + Ok(()) + } + + async fn filter_all_rows(&self) -> FlowyResult<()> { + let field_rev_by_field_id = self.get_filter_revs_map().await; + for block in self.delegate.get_blocks().await.into_iter() { + // The row_ids contains the row that its visibility was changed. + let mut visible_rows = vec![]; + let mut invisible_rows = vec![]; + + for (index, row_rev) in block.row_revs.iter().enumerate() { + if let Some((row_id, is_visible)) = filter_row( + row_rev, + &self.result_by_row_id, + &field_rev_by_field_id, + &self.cell_data_cache, + &self.cell_filter_cache, + ) { + if is_visible { + let row_pb = RowPB::from(row_rev.as_ref()); + visible_rows.push(InsertedRowPB::with_index(row_pb, index as i32)) + } else { + invisible_rows.push(row_id); + } + } + } + + let notification = FilterResultNotification { + view_id: self.view_id.clone(), + block_id: block.block_id, + invisible_rows, + visible_rows, + }; + tracing::Span::current().record("filter_result", format!("{:?}", ¬ification).as_str()); + let _ = self + .notifier + .send(DatabaseViewChanged::FilterNotification(notification)); + } + Ok(()) + } + + pub async fn did_receive_row_changed(&self, row_id: &str) { + self + .gen_task( + FilterEvent::RowDidChanged(row_id.to_string()), + QualityOfService::UserInteractive, + ) + .await + } + + #[tracing::instrument(level = "trace", skip(self))] + pub async fn did_receive_changes( + &self, + changeset: FilterChangeset, + ) -> Option { + let mut notification: Option = None; + if let Some(filter_type) = &changeset.insert_filter { + if let Some(filter) = self.filter_from_filter_type(filter_type).await { + notification = Some(FilterChangesetNotificationPB::from_insert( + &self.view_id, + vec![filter], + )); + } + if let Some(filter_rev) = self.delegate.get_filter_rev(filter_type.clone()).await { + self.refresh_filters(vec![filter_rev]).await; + } + } + + if let Some(updated_filter_type) = changeset.update_filter { + if let Some(old_filter_type) = updated_filter_type.old { + let new_filter = self.filter_from_filter_type(&updated_filter_type.new).await; + let old_filter = self.filter_from_filter_type(&old_filter_type).await; + + // Get the filter id + let mut filter_id = old_filter.map(|filter| filter.id); + if filter_id.is_none() { + filter_id = new_filter.as_ref().map(|filter| filter.id.clone()); + } + + // Update the corresponding filter in the cache + if let Some(filter_rev) = self + .delegate + .get_filter_rev(updated_filter_type.new.clone()) + .await + { + self.refresh_filters(vec![filter_rev]).await; + } + + if let Some(filter_id) = filter_id { + notification = Some(FilterChangesetNotificationPB::from_update( + &self.view_id, + vec![UpdatedFilter { + filter_id, + filter: new_filter, + }], + )); + } + } + } + + if let Some(filter_type) = &changeset.delete_filter { + if let Some(filter) = self.filter_from_filter_type(filter_type).await { + notification = Some(FilterChangesetNotificationPB::from_delete( + &self.view_id, + vec![filter], + )); + } + self.cell_filter_cache.write().remove(filter_type); + } + + self + .gen_task(FilterEvent::FilterDidChanged, QualityOfService::Background) + .await; + tracing::trace!("{:?}", notification); + notification + } + + async fn filter_from_filter_type(&self, filter_type: &FilterType) -> Option { + self + .delegate + .get_filter_rev(filter_type.clone()) + .await + .map(|filter| FilterPB::from(filter.as_ref())) + } + + #[tracing::instrument(level = "trace", skip_all)] + async fn refresh_filters(&self, filter_revs: Vec>) { + for filter_rev in filter_revs { + if let Some(field_rev) = self.delegate.get_field_rev(&filter_rev.field_id).await { + let filter_type = FilterType::from(&field_rev); + tracing::trace!("Create filter with type: {:?}", filter_type); + match &filter_type.field_type { + FieldType::RichText => { + self.cell_filter_cache.write().insert( + &filter_type, + TextFilterPB::from_filter_rev(filter_rev.as_ref()), ); - }); - - row_revs.retain(|row_rev| { - self.result_by_row_id - .get(&row_rev.id) - .map(|result| result.is_visible()) - .unwrap_or(false) - }); - } - - async fn get_filter_revs_map(&self) -> HashMap> { - self.delegate - .get_field_revs(None) - .await - .into_iter() - .map(|field_rev| (field_rev.id.clone(), field_rev)) - .collect::>>() - } - - #[tracing::instrument(name = "process_filter_task", level = "trace", skip_all, fields(filter_result), err)] - pub async fn process(&self, predicate: &str) -> FlowyResult<()> { - let event_type = FilterEvent::from_str(predicate).unwrap(); - match event_type { - FilterEvent::FilterDidChanged => self.filter_all_rows().await?, - FilterEvent::RowDidChanged(row_id) => self.filter_row(row_id).await?, - } - Ok(()) - } - - async fn filter_row(&self, row_id: String) -> FlowyResult<()> { - if let Some((_, row_rev)) = self.delegate.get_row_rev(&row_id).await { - let field_rev_by_field_id = self.get_filter_revs_map().await; - let mut notification = FilterResultNotification::new(self.view_id.clone(), row_rev.block_id.clone()); - if let Some((row_id, is_visible)) = filter_row( - &row_rev, - &self.result_by_row_id, - &field_rev_by_field_id, - &self.cell_data_cache, - &self.cell_filter_cache, - ) { - if is_visible { - if let Some((index, row_rev)) = self.delegate.get_row_rev(&row_id).await { - let row_pb = RowPB::from(row_rev.as_ref()); - notification - .visible_rows - .push(InsertedRowPB::with_index(row_pb, index as i32)) - } - } else { - notification.invisible_rows.push(row_id); - } - } - - let _ = self - .notifier - .send(DatabaseViewChanged::FilterNotification(notification)); - } - Ok(()) - } - - async fn filter_all_rows(&self) -> FlowyResult<()> { - let field_rev_by_field_id = self.get_filter_revs_map().await; - for block in self.delegate.get_blocks().await.into_iter() { - // The row_ids contains the row that its visibility was changed. - let mut visible_rows = vec![]; - let mut invisible_rows = vec![]; - - for (index, row_rev) in block.row_revs.iter().enumerate() { - if let Some((row_id, is_visible)) = filter_row( - row_rev, - &self.result_by_row_id, - &field_rev_by_field_id, - &self.cell_data_cache, - &self.cell_filter_cache, - ) { - if is_visible { - let row_pb = RowPB::from(row_rev.as_ref()); - visible_rows.push(InsertedRowPB::with_index(row_pb, index as i32)) - } else { - invisible_rows.push(row_id); - } - } - } - - let notification = FilterResultNotification { - view_id: self.view_id.clone(), - block_id: block.block_id, - invisible_rows, - visible_rows, - }; - tracing::Span::current().record("filter_result", format!("{:?}", ¬ification).as_str()); - let _ = self - .notifier - .send(DatabaseViewChanged::FilterNotification(notification)); - } - Ok(()) - } - - pub async fn did_receive_row_changed(&self, row_id: &str) { - self.gen_task( - FilterEvent::RowDidChanged(row_id.to_string()), - QualityOfService::UserInteractive, - ) - .await - } - - #[tracing::instrument(level = "trace", skip(self))] - pub async fn did_receive_changes(&self, changeset: FilterChangeset) -> Option { - let mut notification: Option = None; - if let Some(filter_type) = &changeset.insert_filter { - if let Some(filter) = self.filter_from_filter_type(filter_type).await { - notification = Some(FilterChangesetNotificationPB::from_insert(&self.view_id, vec![filter])); - } - if let Some(filter_rev) = self.delegate.get_filter_rev(filter_type.clone()).await { - self.refresh_filters(vec![filter_rev]).await; - } - } - - if let Some(updated_filter_type) = changeset.update_filter { - if let Some(old_filter_type) = updated_filter_type.old { - let new_filter = self.filter_from_filter_type(&updated_filter_type.new).await; - let old_filter = self.filter_from_filter_type(&old_filter_type).await; - - // Get the filter id - let mut filter_id = old_filter.map(|filter| filter.id); - if filter_id.is_none() { - filter_id = new_filter.as_ref().map(|filter| filter.id.clone()); - } - - // Update the corresponding filter in the cache - if let Some(filter_rev) = self.delegate.get_filter_rev(updated_filter_type.new.clone()).await { - self.refresh_filters(vec![filter_rev]).await; - } - - if let Some(filter_id) = filter_id { - notification = Some(FilterChangesetNotificationPB::from_update( - &self.view_id, - vec![UpdatedFilter { - filter_id, - filter: new_filter, - }], - )); - } - } - } - - if let Some(filter_type) = &changeset.delete_filter { - if let Some(filter) = self.filter_from_filter_type(filter_type).await { - notification = Some(FilterChangesetNotificationPB::from_delete(&self.view_id, vec![filter])); - } - self.cell_filter_cache.write().remove(filter_type); - } - - self.gen_task(FilterEvent::FilterDidChanged, QualityOfService::Background) - .await; - tracing::trace!("{:?}", notification); - notification - } - - async fn filter_from_filter_type(&self, filter_type: &FilterType) -> Option { - self.delegate - .get_filter_rev(filter_type.clone()) - .await - .map(|filter| FilterPB::from(filter.as_ref())) - } - - #[tracing::instrument(level = "trace", skip_all)] - async fn refresh_filters(&self, filter_revs: Vec>) { - for filter_rev in filter_revs { - if let Some(field_rev) = self.delegate.get_field_rev(&filter_rev.field_id).await { - let filter_type = FilterType::from(&field_rev); - tracing::trace!("Create filter with type: {:?}", filter_type); - match &filter_type.field_type { - FieldType::RichText => { - self.cell_filter_cache - .write() - .insert(&filter_type, TextFilterPB::from_filter_rev(filter_rev.as_ref())); - } - FieldType::Number => { - self.cell_filter_cache - .write() - .insert(&filter_type, NumberFilterPB::from_filter_rev(filter_rev.as_ref())); - } - FieldType::DateTime => { - self.cell_filter_cache - .write() - .insert(&filter_type, DateFilterPB::from_filter_rev(filter_rev.as_ref())); - } - FieldType::SingleSelect | FieldType::MultiSelect => { - self.cell_filter_cache - .write() - .insert(&filter_type, SelectOptionFilterPB::from_filter_rev(filter_rev.as_ref())); - } - FieldType::Checkbox => { - self.cell_filter_cache - .write() - .insert(&filter_type, CheckboxFilterPB::from_filter_rev(filter_rev.as_ref())); - } - FieldType::URL => { - self.cell_filter_cache - .write() - .insert(&filter_type, TextFilterPB::from_filter_rev(filter_rev.as_ref())); - } - FieldType::Checklist => { - self.cell_filter_cache - .write() - .insert(&filter_type, ChecklistFilterPB::from_filter_rev(filter_rev.as_ref())); - } - } - } + }, + FieldType::Number => { + self.cell_filter_cache.write().insert( + &filter_type, + NumberFilterPB::from_filter_rev(filter_rev.as_ref()), + ); + }, + FieldType::DateTime => { + self.cell_filter_cache.write().insert( + &filter_type, + DateFilterPB::from_filter_rev(filter_rev.as_ref()), + ); + }, + FieldType::SingleSelect | FieldType::MultiSelect => { + self.cell_filter_cache.write().insert( + &filter_type, + SelectOptionFilterPB::from_filter_rev(filter_rev.as_ref()), + ); + }, + FieldType::Checkbox => { + self.cell_filter_cache.write().insert( + &filter_type, + CheckboxFilterPB::from_filter_rev(filter_rev.as_ref()), + ); + }, + FieldType::URL => { + self.cell_filter_cache.write().insert( + &filter_type, + TextFilterPB::from_filter_rev(filter_rev.as_ref()), + ); + }, + FieldType::Checklist => { + self.cell_filter_cache.write().insert( + &filter_type, + ChecklistFilterPB::from_filter_rev(filter_rev.as_ref()), + ); + }, } + } } + } } /// Returns None if there is no change in this row after applying the filter #[tracing::instrument(level = "trace", skip_all)] fn filter_row( - row_rev: &Arc, - result_by_row_id: &DashMap, - field_rev_by_field_id: &HashMap>, - cell_data_cache: &AtomicCellDataCache, - cell_filter_cache: &AtomicCellFilterCache, + row_rev: &Arc, + result_by_row_id: &DashMap, + field_rev_by_field_id: &HashMap>, + cell_data_cache: &AtomicCellDataCache, + cell_filter_cache: &AtomicCellFilterCache, ) -> Option<(String, bool)> { - // Create a filter result cache if it's not exist - let mut filter_result = result_by_row_id - .entry(row_rev.id.clone()) - .or_insert_with(FilterResult::default); - let old_is_visible = filter_result.is_visible(); + // Create a filter result cache if it's not exist + let mut filter_result = result_by_row_id + .entry(row_rev.id.clone()) + .or_insert_with(FilterResult::default); + let old_is_visible = filter_result.is_visible(); - // Iterate each cell of the row to check its visibility - for (field_id, field_rev) in field_rev_by_field_id { - let filter_type = FilterType::from(field_rev); - if !cell_filter_cache.read().contains(&filter_type) { - filter_result.visible_by_filter_id.remove(&filter_type); - continue; - } - - let cell_rev = row_rev.cells.get(field_id); - // if the visibility of the cell_rew is changed, which means the visibility of the - // row is changed too. - if let Some(is_visible) = filter_cell(&filter_type, field_rev, cell_rev, cell_data_cache, cell_filter_cache) { - filter_result.visible_by_filter_id.insert(filter_type, is_visible); - } + // Iterate each cell of the row to check its visibility + for (field_id, field_rev) in field_rev_by_field_id { + let filter_type = FilterType::from(field_rev); + if !cell_filter_cache.read().contains(&filter_type) { + filter_result.visible_by_filter_id.remove(&filter_type); + continue; } - let is_visible = filter_result.is_visible(); - if old_is_visible != is_visible { - Some((row_rev.id.clone(), is_visible)) - } else { - None + let cell_rev = row_rev.cells.get(field_id); + // if the visibility of the cell_rew is changed, which means the visibility of the + // row is changed too. + if let Some(is_visible) = filter_cell( + &filter_type, + field_rev, + cell_rev, + cell_data_cache, + cell_filter_cache, + ) { + filter_result + .visible_by_filter_id + .insert(filter_type, is_visible); } + } + + let is_visible = filter_result.is_visible(); + if old_is_visible != is_visible { + Some((row_rev.id.clone(), is_visible)) + } else { + None + } } // Returns None if there is no change in this cell after applying the filter @@ -354,49 +404,49 @@ fn filter_row( #[tracing::instrument(level = "trace", skip_all, fields(cell_content))] fn filter_cell( - filter_type: &FilterType, - field_rev: &Arc, - cell_rev: Option<&CellRevision>, - cell_data_cache: &AtomicCellDataCache, - cell_filter_cache: &AtomicCellFilterCache, + filter_type: &FilterType, + field_rev: &Arc, + cell_rev: Option<&CellRevision>, + cell_data_cache: &AtomicCellDataCache, + cell_filter_cache: &AtomicCellFilterCache, ) -> Option { - let type_cell_data = match cell_rev { - None => TypeCellData::from_field_type(&filter_type.field_type), - Some(cell_rev) => match TypeCellData::try_from(cell_rev) { - Ok(cell_data) => cell_data, - Err(err) => { - tracing::error!("Deserialize TypeCellData failed: {}", err); - TypeCellData::from_field_type(&filter_type.field_type) - } - }, - }; + let type_cell_data = match cell_rev { + None => TypeCellData::from_field_type(&filter_type.field_type), + Some(cell_rev) => match TypeCellData::try_from(cell_rev) { + Ok(cell_data) => cell_data, + Err(err) => { + tracing::error!("Deserialize TypeCellData failed: {}", err); + TypeCellData::from_field_type(&filter_type.field_type) + }, + }, + }; - let handler = TypeOptionCellExt::new( - field_rev.as_ref(), - Some(cell_data_cache.clone()), - Some(cell_filter_cache.clone()), - ) - .get_type_option_cell_data_handler(&filter_type.field_type)?; + let handler = TypeOptionCellExt::new( + field_rev.as_ref(), + Some(cell_data_cache.clone()), + Some(cell_filter_cache.clone()), + ) + .get_type_option_cell_data_handler(&filter_type.field_type)?; - let is_visible = handler.handle_cell_filter(filter_type, field_rev.as_ref(), type_cell_data); - Some(is_visible) + let is_visible = handler.handle_cell_filter(filter_type, field_rev.as_ref(), type_cell_data); + Some(is_visible) } #[derive(Serialize, Deserialize, Clone, Debug)] enum FilterEvent { - FilterDidChanged, - RowDidChanged(String), + FilterDidChanged, + RowDidChanged(String), } impl ToString for FilterEvent { - fn to_string(&self) -> String { - serde_json::to_string(self).unwrap() - } + fn to_string(&self) -> String { + serde_json::to_string(self).unwrap() + } } impl FromStr for FilterEvent { - type Err = serde_json::Error; - fn from_str(s: &str) -> Result { - serde_json::from_str(s) - } + type Err = serde_json::Error; + fn from_str(s: &str) -> Result { + serde_json::from_str(s) + } } diff --git a/frontend/rust-lib/flowy-database/src/services/filter/entities.rs b/frontend/rust-lib/flowy-database/src/services/filter/entities.rs index dd9362977a..edcffdbb63 100644 --- a/frontend/rust-lib/flowy-database/src/services/filter/entities.rs +++ b/frontend/rust-lib/flowy-database/src/services/filter/entities.rs @@ -1,127 +1,130 @@ use crate::entities::{ - AlterFilterParams, DatabaseSettingChangesetParams, DeleteFilterParams, FieldType, InsertedRowPB, + AlterFilterParams, DatabaseSettingChangesetParams, DeleteFilterParams, FieldType, InsertedRowPB, }; use grid_model::{FieldRevision, FieldTypeRevision}; use std::sync::Arc; #[derive(Debug)] pub struct FilterChangeset { - pub(crate) insert_filter: Option, - pub(crate) update_filter: Option, - pub(crate) delete_filter: Option, + pub(crate) insert_filter: Option, + pub(crate) update_filter: Option, + pub(crate) delete_filter: Option, } #[derive(Debug)] pub struct UpdatedFilterType { - pub old: Option, - pub new: FilterType, + pub old: Option, + pub new: FilterType, } impl UpdatedFilterType { - pub fn new(old: Option, new: FilterType) -> UpdatedFilterType { - Self { old, new } - } + pub fn new(old: Option, new: FilterType) -> UpdatedFilterType { + Self { old, new } + } } impl FilterChangeset { - pub fn from_insert(filter_type: FilterType) -> Self { - Self { - insert_filter: Some(filter_type), - update_filter: None, - delete_filter: None, - } + pub fn from_insert(filter_type: FilterType) -> Self { + Self { + insert_filter: Some(filter_type), + update_filter: None, + delete_filter: None, } + } - pub fn from_update(filter_type: UpdatedFilterType) -> Self { - Self { - insert_filter: None, - update_filter: Some(filter_type), - delete_filter: None, - } + pub fn from_update(filter_type: UpdatedFilterType) -> Self { + Self { + insert_filter: None, + update_filter: Some(filter_type), + delete_filter: None, } - pub fn from_delete(filter_type: FilterType) -> Self { - Self { - insert_filter: None, - update_filter: None, - delete_filter: Some(filter_type), - } + } + pub fn from_delete(filter_type: FilterType) -> Self { + Self { + insert_filter: None, + update_filter: None, + delete_filter: Some(filter_type), } + } } impl std::convert::From<&DatabaseSettingChangesetParams> for FilterChangeset { - fn from(params: &DatabaseSettingChangesetParams) -> Self { - let insert_filter = params.insert_filter.as_ref().map(|insert_filter_params| FilterType { - field_id: insert_filter_params.field_id.clone(), - field_type: insert_filter_params.field_type.into(), - }); + fn from(params: &DatabaseSettingChangesetParams) -> Self { + let insert_filter = params + .insert_filter + .as_ref() + .map(|insert_filter_params| FilterType { + field_id: insert_filter_params.field_id.clone(), + field_type: insert_filter_params.field_type.into(), + }); - let delete_filter = params - .delete_filter - .as_ref() - .map(|delete_filter_params| delete_filter_params.filter_type.clone()); - FilterChangeset { - insert_filter, - update_filter: None, - delete_filter, - } + let delete_filter = params + .delete_filter + .as_ref() + .map(|delete_filter_params| delete_filter_params.filter_type.clone()); + FilterChangeset { + insert_filter, + update_filter: None, + delete_filter, } + } } #[derive(Hash, Eq, PartialEq, Debug, Clone)] pub struct FilterType { - pub field_id: String, - pub field_type: FieldType, + pub field_id: String, + pub field_type: FieldType, } impl From for FieldTypeRevision { - fn from(filter_type: FilterType) -> Self { - filter_type.field_type.into() - } + fn from(filter_type: FilterType) -> Self { + filter_type.field_type.into() + } } impl std::convert::From<&Arc> for FilterType { - fn from(rev: &Arc) -> Self { - Self { - field_id: rev.id.clone(), - field_type: rev.ty.into(), - } + fn from(rev: &Arc) -> Self { + Self { + field_id: rev.id.clone(), + field_type: rev.ty.into(), } + } } impl std::convert::From<&AlterFilterParams> for FilterType { - fn from(params: &AlterFilterParams) -> Self { - let field_type: FieldType = params.field_type.into(); - Self { - field_id: params.field_id.clone(), - field_type, - } + fn from(params: &AlterFilterParams) -> Self { + let field_type: FieldType = params.field_type.into(); + Self { + field_id: params.field_id.clone(), + field_type, } + } } impl std::convert::From<&DeleteFilterParams> for FilterType { - fn from(params: &DeleteFilterParams) -> Self { - params.filter_type.clone() - } + fn from(params: &DeleteFilterParams) -> Self { + params.filter_type.clone() + } } #[derive(Clone, Debug)] pub struct FilterResultNotification { - pub view_id: String, - pub block_id: String, + pub view_id: String, + pub block_id: String, - // Indicates there will be some new rows being visible from invisible state. - pub visible_rows: Vec, + // Indicates there will be some new rows being visible from invisible state. + pub visible_rows: Vec, - // Indicates there will be some new rows being invisible from visible state. - pub invisible_rows: Vec, + // Indicates there will be some new rows being invisible from visible state. + pub invisible_rows: Vec, } impl FilterResultNotification { - pub fn new(view_id: String, block_id: String) -> Self { - Self { - view_id, - block_id, - visible_rows: vec![], - invisible_rows: vec![], - } + pub fn new(view_id: String, block_id: String) -> Self { + Self { + view_id, + block_id, + visible_rows: vec![], + invisible_rows: vec![], } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/filter/task.rs b/frontend/rust-lib/flowy-database/src/services/filter/task.rs index 1ba83ff531..c3d99ecbfd 100644 --- a/frontend/rust-lib/flowy-database/src/services/filter/task.rs +++ b/frontend/rust-lib/flowy-database/src/services/filter/task.rs @@ -5,56 +5,56 @@ use std::collections::HashMap; use std::sync::Arc; pub struct FilterTaskHandler { - handler_id: String, - filter_controller: Arc, + handler_id: String, + filter_controller: Arc, } impl FilterTaskHandler { - pub fn new(handler_id: String, filter_controller: Arc) -> Self { - Self { - handler_id, - filter_controller, - } + pub fn new(handler_id: String, filter_controller: Arc) -> Self { + Self { + handler_id, + filter_controller, } + } } impl TaskHandler for FilterTaskHandler { - fn handler_id(&self) -> &str { - &self.handler_id - } + fn handler_id(&self) -> &str { + &self.handler_id + } - fn handler_name(&self) -> &str { - "FilterTaskHandler" - } + fn handler_name(&self) -> &str { + "FilterTaskHandler" + } - fn run(&self, content: TaskContent) -> BoxResultFuture<(), anyhow::Error> { - let filter_controller = self.filter_controller.clone(); - Box::pin(async move { - if let TaskContent::Text(predicate) = content { - filter_controller - .process(&predicate) - .await - .map_err(anyhow::Error::from)?; - } - Ok(()) - }) - } + fn run(&self, content: TaskContent) -> BoxResultFuture<(), anyhow::Error> { + let filter_controller = self.filter_controller.clone(); + Box::pin(async move { + if let TaskContent::Text(predicate) = content { + filter_controller + .process(&predicate) + .await + .map_err(anyhow::Error::from)?; + } + Ok(()) + }) + } } /// Refresh the filter according to the field id. #[derive(Default)] pub(crate) struct FilterResult { - pub(crate) visible_by_filter_id: HashMap, + pub(crate) visible_by_filter_id: HashMap, } impl FilterResult { - pub(crate) fn is_visible(&self) -> bool { - let mut is_visible = true; - for visible in self.visible_by_filter_id.values() { - if !is_visible { - break; - } - is_visible = *visible; - } - is_visible + pub(crate) fn is_visible(&self) -> bool { + let mut is_visible = true; + for visible in self.visible_by_filter_id.values() { + if !is_visible { + break; + } + is_visible = *visible; } + is_visible + } } diff --git a/frontend/rust-lib/flowy-database/src/services/grid_editor.rs b/frontend/rust-lib/flowy-database/src/services/grid_editor.rs index 67de455d36..318cb0127c 100644 --- a/frontend/rust-lib/flowy-database/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-database/src/services/grid_editor.rs @@ -4,12 +4,12 @@ use crate::manager::DatabaseUser; use crate::notification::{send_notification, DatabaseNotification}; use crate::services::block_manager::DatabaseBlockManager; use crate::services::cell::{ - apply_cell_data_changeset, get_type_cell_protobuf, stringify_cell_data, AnyTypeCache, AtomicCellDataCache, - CellProtobufBlob, ToCellChangesetString, TypeCellData, + apply_cell_data_changeset, get_type_cell_protobuf, stringify_cell_data, AnyTypeCache, + AtomicCellDataCache, CellProtobufBlob, ToCellChangesetString, TypeCellData, }; use crate::services::field::{ - default_type_option_builder_from_type, transform_type_option, type_option_builder_from_bytes, FieldBuilder, - RowSingleCellData, + default_type_option_builder_from_type, transform_type_option, type_option_builder_from_bytes, + FieldBuilder, RowSingleCellData, }; use crate::services::filter::FilterType; @@ -18,11 +18,14 @@ use crate::services::persistence::block_index::BlockIndexCache; use crate::services::row::{DatabaseBlockRow, DatabaseBlockRowRevision, RowRevisionBuilder}; use crate::services::view_editor::{DatabaseViewChanged, DatabaseViewManager}; use bytes::Bytes; -use flowy_client_sync::client_database::{DatabaseRevisionChangeset, DatabaseRevisionPad, JsonDeserializer}; +use flowy_client_sync::client_database::{ + DatabaseRevisionChangeset, DatabaseRevisionPad, JsonDeserializer, +}; use flowy_client_sync::errors::{SyncError, SyncResult}; use flowy_error::{FlowyError, FlowyResult}; use flowy_revision::{ - RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, RevisionObjectSerializer, + RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, + RevisionObjectSerializer, }; use flowy_sqlite::ConnectionPool; use flowy_task::TaskDispatcher; @@ -37,901 +40,1021 @@ use std::sync::Arc; use tokio::sync::{broadcast, RwLock}; pub struct DatabaseRevisionEditor { - pub database_id: String, - #[allow(dead_code)] - user: Arc, - database_pad: Arc>, - view_manager: Arc, - rev_manager: Arc>>, - block_manager: Arc, - cell_data_cache: AtomicCellDataCache, + pub database_id: String, + #[allow(dead_code)] + user: Arc, + database_pad: Arc>, + view_manager: Arc, + rev_manager: Arc>>, + block_manager: Arc, + cell_data_cache: AtomicCellDataCache, } impl Drop for DatabaseRevisionEditor { - fn drop(&mut self) { - tracing::trace!("Drop GridRevisionEditor"); - } + fn drop(&mut self) { + tracing::trace!("Drop GridRevisionEditor"); + } } impl DatabaseRevisionEditor { - pub async fn new( - database_id: &str, - user: Arc, - database_pad: Arc>, - rev_manager: RevisionManager>, - persistence: Arc, - task_scheduler: Arc>, - ) -> FlowyResult> { - let rev_manager = Arc::new(rev_manager); - let cell_data_cache = AnyTypeCache::::new(); + pub async fn new( + database_id: &str, + user: Arc, + database_pad: Arc>, + rev_manager: RevisionManager>, + persistence: Arc, + task_scheduler: Arc>, + ) -> FlowyResult> { + let rev_manager = Arc::new(rev_manager); + let cell_data_cache = AnyTypeCache::::new(); - // Block manager - let (block_event_tx, block_event_rx) = broadcast::channel(100); - let block_meta_revs = database_pad.read().await.get_block_meta_revs(); - let block_manager = - Arc::new(DatabaseBlockManager::new(&user, block_meta_revs, persistence, block_event_tx).await?); - let delegate = Arc::new(GridViewEditorDelegateImpl { - pad: database_pad.clone(), - block_manager: block_manager.clone(), - task_scheduler, - cell_data_cache: cell_data_cache.clone(), - }); + // Block manager + let (block_event_tx, block_event_rx) = broadcast::channel(100); + let block_meta_revs = database_pad.read().await.get_block_meta_revs(); + let block_manager = Arc::new( + DatabaseBlockManager::new(&user, block_meta_revs, persistence, block_event_tx).await?, + ); + let delegate = Arc::new(GridViewEditorDelegateImpl { + pad: database_pad.clone(), + block_manager: block_manager.clone(), + task_scheduler, + cell_data_cache: cell_data_cache.clone(), + }); - // View manager - let view_manager = Arc::new( - DatabaseViewManager::new( - database_id.to_owned(), - user.clone(), - delegate, - cell_data_cache.clone(), - block_event_rx, - ) - .await?, + // View manager + let view_manager = Arc::new( + DatabaseViewManager::new( + database_id.to_owned(), + user.clone(), + delegate, + cell_data_cache.clone(), + block_event_rx, + ) + .await?, + ); + + let editor = Arc::new(Self { + database_id: database_id.to_owned(), + user, + database_pad, + rev_manager, + block_manager, + view_manager, + cell_data_cache, + }); + + Ok(editor) + } + + #[tracing::instrument(name = "close grid editor", level = "trace", skip_all)] + pub async fn close(&self) { + self.block_manager.close().await; + self.rev_manager.generate_snapshot().await; + self.rev_manager.close().await; + self.view_manager.close(&self.database_id).await; + } + + /// Save the type-option data to disk and send a `DatabaseNotification::DidUpdateField` notification + /// to dart side. + /// + /// It will do nothing if the passed-in type_option_data is empty + /// # Arguments + /// + /// * `grid_id`: the id of the grid + /// * `field_id`: the id of the field + /// * `type_option_data`: the updated type-option data. The `type-option` data might be empty + /// if there is no type-option config for that field. For example, the `RichTextTypeOptionPB`. + /// + pub async fn update_field_type_option( + &self, + _grid_id: &str, + field_id: &str, + type_option_data: Vec, + old_field_rev: Option>, + ) -> FlowyResult<()> { + let result = self.get_field_rev(field_id).await; + if result.is_none() { + tracing::warn!("Can't find the field with id: {}", field_id); + return Ok(()); + } + let field_rev = result.unwrap(); + self + .modify(|grid| { + let changeset = grid.modify_field(field_id, |field| { + let deserializer = TypeOptionJsonDeserializer(field_rev.ty.into()); + match deserializer.deserialize(type_option_data) { + Ok(json_str) => { + let field_type = field.ty; + field.insert_type_option_str(&field_type, json_str); + }, + Err(err) => { + tracing::error!("Deserialize data to type option json failed: {}", err); + }, + } + Ok(Some(())) + })?; + Ok(changeset) + }) + .await?; + + self + .view_manager + .did_update_view_field_type_option(field_id, old_field_rev) + .await?; + self.notify_did_update_grid_field(field_id).await?; + Ok(()) + } + + pub async fn next_field_rev(&self, field_type: &FieldType) -> FlowyResult { + let name = format!( + "Property {}", + self.database_pad.read().await.get_fields().len() + 1 + ); + let field_rev = FieldBuilder::from_field_type(field_type) + .name(&name) + .build(); + Ok(field_rev) + } + + pub async fn create_new_field_rev(&self, field_rev: FieldRevision) -> FlowyResult<()> { + let field_id = field_rev.id.clone(); + self + .modify(|grid| Ok(grid.create_field_rev(field_rev, None)?)) + .await?; + self.notify_did_insert_grid_field(&field_id).await?; + + Ok(()) + } + + pub async fn create_new_field_rev_with_type_option( + &self, + field_type: &FieldType, + type_option_data: Option>, + ) -> FlowyResult { + let mut field_rev = self.next_field_rev(field_type).await?; + if let Some(type_option_data) = type_option_data { + let type_option_builder = type_option_builder_from_bytes(type_option_data, field_type); + field_rev.insert_type_option(type_option_builder.serializer()); + } + self + .modify(|grid| Ok(grid.create_field_rev(field_rev.clone(), None)?)) + .await?; + self.notify_did_insert_grid_field(&field_rev.id).await?; + + Ok(field_rev) + } + + pub async fn contain_field(&self, field_id: &str) -> bool { + self.database_pad.read().await.contain_field(field_id) + } + + pub async fn update_field(&self, params: FieldChangesetParams) -> FlowyResult<()> { + let field_id = params.field_id.clone(); + self + .modify(|grid| { + let changeset = grid.modify_field(¶ms.field_id, |field| { + if let Some(name) = params.name { + field.name = name; + } + if let Some(desc) = params.desc { + field.desc = desc; + } + if let Some(field_type) = params.field_type { + field.ty = field_type; + } + if let Some(frozen) = params.frozen { + field.frozen = frozen; + } + if let Some(visibility) = params.visibility { + field.visibility = visibility; + } + if let Some(width) = params.width { + field.width = width; + } + Ok(Some(())) + })?; + Ok(changeset) + }) + .await?; + self.notify_did_update_grid_field(&field_id).await?; + Ok(()) + } + + pub async fn modify_field_rev(&self, field_id: &str, f: F) -> FlowyResult<()> + where + F: for<'a> FnOnce(&'a mut FieldRevision) -> FlowyResult>, + { + let mut is_changed = false; + let old_field_rev = self.get_field_rev(field_id).await; + self + .modify(|grid| { + let changeset = grid.modify_field(field_id, |field_rev| { + f(field_rev).map_err(|e| SyncError::internal().context(e)) + })?; + is_changed = changeset.is_some(); + Ok(changeset) + }) + .await?; + + if is_changed { + match self + .view_manager + .did_update_view_field_type_option(field_id, old_field_rev) + .await + { + Ok(_) => {}, + Err(e) => tracing::error!("View manager update field failed: {:?}", e), + } + self.notify_did_update_grid_field(field_id).await?; + } + Ok(()) + } + + pub async fn delete_field(&self, field_id: &str) -> FlowyResult<()> { + self + .modify(|grid_pad| Ok(grid_pad.delete_field_rev(field_id)?)) + .await?; + let field_order = FieldIdPB::from(field_id); + let notified_changeset = DatabaseFieldChangesetPB::delete(&self.database_id, vec![field_order]); + self.notify_did_update_grid(notified_changeset).await?; + Ok(()) + } + + pub async fn group_by_field(&self, field_id: &str) -> FlowyResult<()> { + self.view_manager.group_by_field(field_id).await?; + Ok(()) + } + + /// Switch the field with id to a new field type. + /// + /// If the field type is not exist before, the default type-option data will be created. + /// Each field type has its corresponding data, aka, the type-option data. Check out [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid#fieldtype) + /// for more information + /// + /// # Arguments + /// + /// * `field_id`: the id of the field + /// * `new_field_type`: the new field type of the field + /// + pub async fn switch_to_field_type( + &self, + field_id: &str, + new_field_type: &FieldType, + ) -> FlowyResult<()> { + // + let make_default_type_option = || -> String { + return default_type_option_builder_from_type(new_field_type) + .serializer() + .json_str(); + }; + + let type_option_transform = |old_field_type: FieldTypeRevision, + old_type_option: Option, + new_type_option: String| { + let old_field_type: FieldType = old_field_type.into(); + transform_type_option( + &new_type_option, + new_field_type, + old_type_option, + old_field_type, + ) + }; + + self + .modify(|grid| { + Ok(grid.switch_to_field( + field_id, + new_field_type.clone(), + make_default_type_option, + type_option_transform, + )?) + }) + .await?; + + self.notify_did_update_grid_field(field_id).await?; + + Ok(()) + } + + pub async fn duplicate_field(&self, field_id: &str) -> FlowyResult<()> { + let duplicated_field_id = gen_field_id(); + self + .modify(|grid| Ok(grid.duplicate_field_rev(field_id, &duplicated_field_id)?)) + .await?; + + self + .notify_did_insert_grid_field(&duplicated_field_id) + .await?; + Ok(()) + } + + pub async fn get_field_rev(&self, field_id: &str) -> Option> { + let field_rev = self + .database_pad + .read() + .await + .get_field_rev(field_id)? + .1 + .clone(); + Some(field_rev) + } + + pub async fn get_field_revs( + &self, + field_ids: Option>, + ) -> FlowyResult>> { + if field_ids.is_none() { + let field_revs = self.database_pad.read().await.get_field_revs(None)?; + return Ok(field_revs); + } + + let field_ids = field_ids.unwrap_or_default(); + let expected_len = field_ids.len(); + let field_revs = self + .database_pad + .read() + .await + .get_field_revs(Some(field_ids))?; + if expected_len != 0 && field_revs.len() != expected_len { + tracing::error!( + "This is a bug. The len of the field_revs should equal to {}", + expected_len + ); + debug_assert!(field_revs.len() == expected_len); + } + Ok(field_revs) + } + + pub async fn create_block(&self, block_meta_rev: GridBlockMetaRevision) -> FlowyResult<()> { + self + .modify(|grid_pad| Ok(grid_pad.create_block_meta_rev(block_meta_rev)?)) + .await?; + Ok(()) + } + + pub async fn update_block(&self, changeset: GridBlockMetaRevisionChangeset) -> FlowyResult<()> { + self + .modify(|grid_pad| Ok(grid_pad.update_block_rev(changeset)?)) + .await?; + Ok(()) + } + + pub async fn create_row(&self, params: CreateRowParams) -> FlowyResult { + let mut row_rev = self.create_row_rev().await?; + + self + .view_manager + .will_create_row(&mut row_rev, ¶ms) + .await; + + let row_pb = self + .create_row_pb(row_rev, params.start_row_id.clone()) + .await?; + + self.view_manager.did_create_row(&row_pb, ¶ms).await; + Ok(row_pb) + } + + #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> { + self.view_manager.move_group(params).await?; + Ok(()) + } + + pub async fn insert_rows(&self, row_revs: Vec) -> FlowyResult> { + let block_id = self.block_id().await?; + let mut rows_by_block_id: HashMap> = HashMap::new(); + let mut row_orders = vec![]; + for row_rev in row_revs { + row_orders.push(RowPB::from(&row_rev)); + rows_by_block_id + .entry(block_id.clone()) + .or_insert_with(Vec::new) + .push(row_rev); + } + let changesets = self.block_manager.insert_row(rows_by_block_id).await?; + for changeset in changesets { + self.update_block(changeset).await?; + } + Ok(row_orders) + } + + pub async fn update_row(&self, changeset: RowChangeset) -> FlowyResult<()> { + let row_id = changeset.row_id.clone(); + let old_row = self.get_row_rev(&row_id).await?; + self.block_manager.update_row(changeset).await?; + self.view_manager.did_update_row(old_row, &row_id).await; + Ok(()) + } + + /// Returns all the rows in this block. + pub async fn get_row_pbs(&self, view_id: &str, block_id: &str) -> FlowyResult> { + let rows = self.view_manager.get_row_revs(view_id, block_id).await?; + let rows = rows + .into_iter() + .map(|row_rev| RowPB::from(&row_rev)) + .collect(); + Ok(rows) + } + + pub async fn get_all_row_revs(&self, view_id: &str) -> FlowyResult>> { + let mut all_rows = vec![]; + let blocks = self.block_manager.get_blocks(None).await?; + for block in blocks { + let rows = self + .view_manager + .get_row_revs(view_id, &block.block_id) + .await?; + all_rows.extend(rows); + } + Ok(all_rows) + } + + pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult>> { + match self.block_manager.get_row_rev(row_id).await? { + None => Ok(None), + Some((_, row_rev)) => Ok(Some(row_rev)), + } + } + + pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> { + let row_rev = self.block_manager.delete_row(row_id).await?; + tracing::trace!("Did delete row:{:?}", row_rev); + if let Some(row_rev) = row_rev { + self.view_manager.did_delete_row(row_rev).await; + } + Ok(()) + } + + pub async fn subscribe_view_changed( + &self, + view_id: &str, + ) -> FlowyResult> { + self.view_manager.subscribe_view_changed(view_id).await + } + + pub async fn duplicate_row(&self, _row_id: &str) -> FlowyResult<()> { + Ok(()) + } + + /// Returns the cell data that encoded in protobuf. + pub async fn get_cell(&self, params: &CellIdParams) -> Option { + let (field_type, cell_bytes) = self.get_type_cell_protobuf(params).await?; + Some(CellPB::new( + ¶ms.field_id, + ¶ms.row_id, + field_type, + cell_bytes.to_vec(), + )) + } + + /// Returns a string that represents the current field_type's cell data. + /// For example: + /// Multi-Select: list of the option's name separated by a comma. + /// Number: 123 => $123 if the currency set. + /// Date: 1653609600 => May 27,2022 + /// + pub async fn get_cell_display_str(&self, params: &CellIdParams) -> String { + let display_str = || async { + let field_rev = self.get_field_rev(¶ms.field_id).await?; + let field_type: FieldType = field_rev.ty.into(); + let cell_rev = self + .get_cell_rev(¶ms.row_id, ¶ms.field_id) + .await + .ok()??; + let type_cell_data: TypeCellData = cell_rev.try_into().ok()?; + Some(stringify_cell_data( + type_cell_data.cell_str, + &field_type, + &field_type, + &field_rev, + )) + }; + + display_str().await.unwrap_or_default() + } + + pub async fn get_cell_protobuf(&self, params: &CellIdParams) -> Option { + let (_, cell_data) = self.get_type_cell_protobuf(params).await?; + Some(cell_data) + } + + async fn get_type_cell_protobuf( + &self, + params: &CellIdParams, + ) -> Option<(FieldType, CellProtobufBlob)> { + let field_rev = self.get_field_rev(¶ms.field_id).await?; + let (_, row_rev) = self + .block_manager + .get_row_rev(¶ms.row_id) + .await + .ok()??; + let cell_rev = row_rev.cells.get(¶ms.field_id)?.clone(); + Some(get_type_cell_protobuf( + cell_rev.type_cell_data, + &field_rev, + Some(self.cell_data_cache.clone()), + )) + } + + pub async fn get_cell_rev( + &self, + row_id: &str, + field_id: &str, + ) -> FlowyResult> { + match self.block_manager.get_row_rev(row_id).await? { + None => Ok(None), + Some((_, row_rev)) => { + let cell_rev = row_rev.cells.get(field_id).cloned(); + Ok(cell_rev) + }, + } + } + + /// Returns the list of cells corresponding to the given field. + pub async fn get_cells_for_field( + &self, + view_id: &str, + field_id: &str, + ) -> FlowyResult> { + let view_editor = self.view_manager.get_view_editor(view_id).await?; + view_editor.get_cells_for_field(field_id).await + } + + #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn update_cell_with_changeset( + &self, + row_id: &str, + field_id: &str, + cell_changeset: T, + ) -> FlowyResult<()> { + match self.database_pad.read().await.get_field_rev(field_id) { + None => { + let msg = format!("Field with id:{} not found", &field_id); + Err(FlowyError::internal().context(msg)) + }, + Some((_, field_rev)) => { + tracing::trace!( + "Cell changeset: id:{} / value:{:?}", + &field_id, + cell_changeset ); + let old_row_rev = self.get_row_rev(row_id).await?.clone(); + let cell_rev = self.get_cell_rev(row_id, field_id).await?; + // Update the changeset.data property with the return value. + let type_cell_data = apply_cell_data_changeset( + cell_changeset, + cell_rev, + field_rev, + Some(self.cell_data_cache.clone()), + )?; + let cell_changeset = CellChangesetPB { + database_id: self.database_id.clone(), + row_id: row_id.to_owned(), + field_id: field_id.to_owned(), + type_cell_data, + }; + self.block_manager.update_cell(cell_changeset).await?; + self.view_manager.did_update_row(old_row_rev, row_id).await; + Ok(()) + }, + } + } - let editor = Arc::new(Self { - database_id: database_id.to_owned(), - user, - database_pad, - rev_manager, - block_manager, - view_manager, - cell_data_cache, - }); + #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn update_cell( + &self, + row_id: String, + field_id: String, + cell_changeset: T, + ) -> FlowyResult<()> { + self + .update_cell_with_changeset(&row_id, &field_id, cell_changeset) + .await + } - Ok(editor) + pub async fn get_block_meta_revs(&self) -> FlowyResult>> { + let block_meta_revs = self.database_pad.read().await.get_block_meta_revs(); + Ok(block_meta_revs) + } + + pub async fn get_blocks( + &self, + block_ids: Option>, + ) -> FlowyResult> { + let block_ids = match block_ids { + None => self + .database_pad + .read() + .await + .get_block_meta_revs() + .iter() + .map(|block_rev| block_rev.block_id.clone()) + .collect::>(), + Some(block_ids) => block_ids, + }; + let blocks = self.block_manager.get_blocks(Some(block_ids)).await?; + Ok(blocks) + } + + pub async fn delete_rows(&self, block_rows: Vec) -> FlowyResult<()> { + let changesets = self.block_manager.delete_rows(block_rows).await?; + for changeset in changesets { + self.update_block(changeset).await?; + } + Ok(()) + } + + pub async fn get_database(&self, view_id: &str) -> FlowyResult { + let pad = self.database_pad.read().await; + let fields = pad + .get_field_revs(None)? + .iter() + .map(FieldIdPB::from) + .collect(); + let mut all_rows = vec![]; + for block_rev in pad.get_block_meta_revs() { + if let Ok(rows) = self.get_row_pbs(view_id, &block_rev.block_id).await { + all_rows.extend(rows); + } } - #[tracing::instrument(name = "close grid editor", level = "trace", skip_all)] - pub async fn close(&self) { - self.block_manager.close().await; - self.rev_manager.generate_snapshot().await; - self.rev_manager.close().await; - self.view_manager.close(&self.database_id).await; - } + Ok(DatabasePB { + id: self.database_id.clone(), + fields, + rows: all_rows, + }) + } - /// Save the type-option data to disk and send a `DatabaseNotification::DidUpdateField` notification - /// to dart side. - /// - /// It will do nothing if the passed-in type_option_data is empty - /// # Arguments - /// - /// * `grid_id`: the id of the grid - /// * `field_id`: the id of the field - /// * `type_option_data`: the updated type-option data. The `type-option` data might be empty - /// if there is no type-option config for that field. For example, the `RichTextTypeOptionPB`. - /// - pub async fn update_field_type_option( - &self, - _grid_id: &str, - field_id: &str, - type_option_data: Vec, - old_field_rev: Option>, - ) -> FlowyResult<()> { - let result = self.get_field_rev(field_id).await; - if result.is_none() { - tracing::warn!("Can't find the field with id: {}", field_id); - return Ok(()); + pub async fn get_setting(&self) -> FlowyResult { + self.view_manager.get_setting().await + } + + pub async fn get_all_filters(&self) -> FlowyResult> { + Ok( + self + .view_manager + .get_all_filters() + .await? + .into_iter() + .map(|filter| FilterPB::from(filter.as_ref())) + .collect(), + ) + } + + pub async fn get_filters(&self, filter_id: FilterType) -> FlowyResult>> { + self.view_manager.get_filters(&filter_id).await + } + + pub async fn create_or_update_filter(&self, params: AlterFilterParams) -> FlowyResult<()> { + self.view_manager.create_or_update_filter(params).await?; + Ok(()) + } + + pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { + self.view_manager.delete_filter(params).await?; + Ok(()) + } + + pub async fn get_all_sorts(&self, view_id: &str) -> FlowyResult> { + Ok( + self + .view_manager + .get_all_sorts(view_id) + .await? + .into_iter() + .map(|sort| SortPB::from(sort.as_ref())) + .collect(), + ) + } + + pub async fn delete_all_sorts(&self, view_id: &str) -> FlowyResult<()> { + self.view_manager.delete_all_sorts(view_id).await + } + + pub async fn delete_sort(&self, params: DeleteSortParams) -> FlowyResult<()> { + self.view_manager.delete_sort(params).await?; + Ok(()) + } + + pub async fn create_or_update_sort(&self, params: AlterSortParams) -> FlowyResult { + let sort_rev = self.view_manager.create_or_update_sort(params).await?; + Ok(sort_rev) + } + + pub async fn insert_group(&self, params: InsertGroupParams) -> FlowyResult<()> { + self.view_manager.insert_or_update_group(params).await + } + + pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { + self.view_manager.delete_group(params).await + } + + pub async fn move_row(&self, params: MoveRowParams) -> FlowyResult<()> { + let MoveRowParams { + view_id: _, + from_row_id, + to_row_id, + } = params; + + match self.block_manager.get_row_rev(&from_row_id).await? { + None => tracing::warn!("Move row failed, can not find the row:{}", from_row_id), + Some((_, row_rev)) => { + match ( + self.block_manager.index_of_row(&from_row_id).await, + self.block_manager.index_of_row(&to_row_id).await, + ) { + (Some(from_index), Some(to_index)) => { + tracing::trace!("Move row from {} to {}", from_index, to_index); + self + .block_manager + .move_row(row_rev.clone(), from_index, to_index) + .await?; + }, + (_, None) => tracing::warn!("Can not find the from row id: {}", from_row_id), + (None, _) => tracing::warn!("Can not find the to row id: {}", to_row_id), } - let field_rev = result.unwrap(); - self.modify(|grid| { - let changeset = grid.modify_field(field_id, |field| { - let deserializer = TypeOptionJsonDeserializer(field_rev.ty.into()); - match deserializer.deserialize(type_option_data) { - Ok(json_str) => { - let field_type = field.ty; - field.insert_type_option_str(&field_type, json_str); - } - Err(err) => { - tracing::error!("Deserialize data to type option json failed: {}", err); - } - } - Ok(Some(())) - })?; - Ok(changeset) - }) - .await?; - - self.view_manager - .did_update_view_field_type_option(field_id, old_field_rev) - .await?; - self.notify_did_update_grid_field(field_id).await?; - Ok(()) + }, } + Ok(()) + } - pub async fn next_field_rev(&self, field_type: &FieldType) -> FlowyResult { - let name = format!("Property {}", self.database_pad.read().await.get_fields().len() + 1); - let field_rev = FieldBuilder::from_field_type(field_type).name(&name).build(); - Ok(field_rev) + pub async fn move_group_row(&self, params: MoveGroupRowParams) -> FlowyResult<()> { + let MoveGroupRowParams { + view_id, + from_row_id, + to_group_id, + to_row_id, + } = params; + + match self.block_manager.get_row_rev(&from_row_id).await? { + None => tracing::warn!("Move row failed, can not find the row:{}", from_row_id), + Some((_, row_rev)) => { + let block_manager = self.block_manager.clone(); + self + .view_manager + .move_group_row(row_rev, to_group_id, to_row_id.clone(), |row_changeset| { + to_fut(async move { + tracing::trace!("Row data changed: {:?}", row_changeset); + let cell_changesets = row_changeset + .cell_by_field_id + .into_iter() + .map(|(field_id, cell_rev)| CellChangesetPB { + database_id: view_id.clone(), + row_id: row_changeset.row_id.clone(), + field_id, + type_cell_data: cell_rev.type_cell_data, + }) + .collect::>(); + + for cell_changeset in cell_changesets { + match block_manager.update_cell(cell_changeset).await { + Ok(_) => {}, + Err(e) => tracing::error!("Apply cell changeset error:{:?}", e), + } + } + }) + }) + .await?; + }, } + Ok(()) + } - pub async fn create_new_field_rev(&self, field_rev: FieldRevision) -> FlowyResult<()> { - let field_id = field_rev.id.clone(); - self.modify(|grid| Ok(grid.create_field_rev(field_rev, None)?)).await?; - self.notify_did_insert_grid_field(&field_id).await?; + pub async fn move_field(&self, params: MoveFieldParams) -> FlowyResult<()> { + let MoveFieldParams { + view_id: _, + field_id, + from_index, + to_index, + } = params; - Ok(()) + self + .modify(|grid_pad| { + Ok(grid_pad.move_field(&field_id, from_index as usize, to_index as usize)?) + }) + .await?; + if let Some((index, field_rev)) = self.database_pad.read().await.get_field_rev(&field_id) { + let delete_field_order = FieldIdPB::from(field_id); + let insert_field = IndexFieldPB::from_field_rev(field_rev, index); + let notified_changeset = DatabaseFieldChangesetPB { + database_id: self.database_id.clone(), + inserted_fields: vec![insert_field], + deleted_fields: vec![delete_field_order], + updated_fields: vec![], + }; + + self.notify_did_update_grid(notified_changeset).await?; } + Ok(()) + } - pub async fn create_new_field_rev_with_type_option( - &self, - field_type: &FieldType, - type_option_data: Option>, - ) -> FlowyResult { - let mut field_rev = self.next_field_rev(field_type).await?; - if let Some(type_option_data) = type_option_data { - let type_option_builder = type_option_builder_from_bytes(type_option_data, field_type); - field_rev.insert_type_option(type_option_builder.serializer()); - } - self.modify(|grid| Ok(grid.create_field_rev(field_rev.clone(), None)?)) - .await?; - self.notify_did_insert_grid_field(&field_rev.id).await?; + pub async fn duplicate_grid(&self) -> FlowyResult { + let grid_pad = self.database_pad.read().await; + let grid_view_revision_data = self.view_manager.duplicate_database_view().await?; + let original_blocks = grid_pad.get_block_meta_revs(); + let (duplicated_fields, duplicated_blocks) = grid_pad.duplicate_grid_block_meta().await; - Ok(field_rev) + let mut blocks_meta_data = vec![]; + if original_blocks.len() == duplicated_blocks.len() { + for (index, original_block_meta) in original_blocks.iter().enumerate() { + let grid_block_meta_editor = self + .block_manager + .get_block_editor(&original_block_meta.block_id) + .await?; + let duplicated_block_id = &duplicated_blocks[index].block_id; + + tracing::trace!("Duplicate block:{} meta data", duplicated_block_id); + let duplicated_block_meta_data = grid_block_meta_editor + .duplicate_block(duplicated_block_id) + .await; + blocks_meta_data.push(duplicated_block_meta_data); + } + } else { + debug_assert_eq!(original_blocks.len(), duplicated_blocks.len()); } + drop(grid_pad); - pub async fn contain_field(&self, field_id: &str) -> bool { - self.database_pad.read().await.contain_field(field_id) + Ok(BuildDatabaseContext { + field_revs: duplicated_fields.into_iter().map(Arc::new).collect(), + block_metas: duplicated_blocks, + blocks: blocks_meta_data, + grid_view_revision_data, + }) + } + + #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn load_groups(&self) -> FlowyResult { + self.view_manager.load_groups().await + } + + async fn create_row_rev(&self) -> FlowyResult { + let field_revs = self.database_pad.read().await.get_field_revs(None)?; + let block_id = self.block_id().await?; + + // insert empty row below the row whose id is upper_row_id + let row_rev = RowRevisionBuilder::new(&block_id, &field_revs).build(); + Ok(row_rev) + } + + async fn create_row_pb( + &self, + row_rev: RowRevision, + start_row_id: Option, + ) -> FlowyResult { + let row_pb = RowPB::from(&row_rev); + let block_id = row_rev.block_id.clone(); + + // insert the row + let row_count = self.block_manager.create_row(row_rev, start_row_id).await?; + + // update block row count + let changeset = GridBlockMetaRevisionChangeset::from_row_count(block_id, row_count); + self.update_block(changeset).await?; + Ok(row_pb) + } + + async fn modify(&self, f: F) -> FlowyResult<()> + where + F: + for<'a> FnOnce(&'a mut DatabaseRevisionPad) -> FlowyResult>, + { + let mut write_guard = self.database_pad.write().await; + if let Some(changeset) = f(&mut write_guard)? { + self.apply_change(changeset).await?; } + Ok(()) + } - pub async fn update_field(&self, params: FieldChangesetParams) -> FlowyResult<()> { - let field_id = params.field_id.clone(); - self.modify(|grid| { - let changeset = grid.modify_field(¶ms.field_id, |field| { - if let Some(name) = params.name { - field.name = name; - } - if let Some(desc) = params.desc { - field.desc = desc; - } - if let Some(field_type) = params.field_type { - field.ty = field_type; - } - if let Some(frozen) = params.frozen { - field.frozen = frozen; - } - if let Some(visibility) = params.visibility { - field.visibility = visibility; - } - if let Some(width) = params.width { - field.width = width; - } - Ok(Some(())) - })?; - Ok(changeset) - }) - .await?; - self.notify_did_update_grid_field(&field_id).await?; - Ok(()) + async fn apply_change(&self, change: DatabaseRevisionChangeset) -> FlowyResult<()> { + let DatabaseRevisionChangeset { + operations: delta, + md5, + } = change; + let data = delta.json_bytes(); + let _ = self.rev_manager.add_local_revision(data, md5).await?; + Ok(()) + } + + async fn block_id(&self) -> FlowyResult { + match self.database_pad.read().await.get_block_meta_revs().last() { + None => Err(FlowyError::internal().context("There is no grid block in this grid")), + Some(grid_block) => Ok(grid_block.block_id.clone()), } + } - pub async fn modify_field_rev(&self, field_id: &str, f: F) -> FlowyResult<()> - where - F: for<'a> FnOnce(&'a mut FieldRevision) -> FlowyResult>, + #[tracing::instrument(level = "trace", skip_all, err)] + async fn notify_did_insert_grid_field(&self, field_id: &str) -> FlowyResult<()> { + if let Some((index, field_rev)) = self.database_pad.read().await.get_field_rev(field_id) { + let index_field = IndexFieldPB::from_field_rev(field_rev, index); + let notified_changeset = + DatabaseFieldChangesetPB::insert(&self.database_id, vec![index_field]); + self.notify_did_update_grid(notified_changeset).await?; + } + Ok(()) + } + + #[tracing::instrument(level = "trace", skip_all, err)] + async fn notify_did_update_grid_field(&self, field_id: &str) -> FlowyResult<()> { + if let Some((_, field_rev)) = self + .database_pad + .read() + .await + .get_field_rev(field_id) + .map(|(index, field)| (index, field.clone())) { - let mut is_changed = false; - let old_field_rev = self.get_field_rev(field_id).await; - self.modify(|grid| { - let changeset = grid.modify_field(field_id, |field_rev| { - f(field_rev).map_err(|e| SyncError::internal().context(e)) - })?; - is_changed = changeset.is_some(); - Ok(changeset) - }) - .await?; + let updated_field = FieldPB::from(field_rev); + let notified_changeset = + DatabaseFieldChangesetPB::update(&self.database_id, vec![updated_field.clone()]); + self.notify_did_update_grid(notified_changeset).await?; - if is_changed { - match self - .view_manager - .did_update_view_field_type_option(field_id, old_field_rev) - .await - { - Ok(_) => {} - Err(e) => tracing::error!("View manager update field failed: {:?}", e), - } - self.notify_did_update_grid_field(field_id).await?; - } - Ok(()) + send_notification(field_id, DatabaseNotification::DidUpdateField) + .payload(updated_field) + .send(); } - pub async fn delete_field(&self, field_id: &str) -> FlowyResult<()> { - self.modify(|grid_pad| Ok(grid_pad.delete_field_rev(field_id)?)).await?; - let field_order = FieldIdPB::from(field_id); - let notified_changeset = DatabaseFieldChangesetPB::delete(&self.database_id, vec![field_order]); - self.notify_did_update_grid(notified_changeset).await?; - Ok(()) - } - - pub async fn group_by_field(&self, field_id: &str) -> FlowyResult<()> { - self.view_manager.group_by_field(field_id).await?; - Ok(()) - } - - /// Switch the field with id to a new field type. - /// - /// If the field type is not exist before, the default type-option data will be created. - /// Each field type has its corresponding data, aka, the type-option data. Check out [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid#fieldtype) - /// for more information - /// - /// # Arguments - /// - /// * `field_id`: the id of the field - /// * `new_field_type`: the new field type of the field - /// - pub async fn switch_to_field_type(&self, field_id: &str, new_field_type: &FieldType) -> FlowyResult<()> { - // - let make_default_type_option = || -> String { - return default_type_option_builder_from_type(new_field_type) - .serializer() - .json_str(); - }; - - let type_option_transform = - |old_field_type: FieldTypeRevision, old_type_option: Option, new_type_option: String| { - let old_field_type: FieldType = old_field_type.into(); - transform_type_option(&new_type_option, new_field_type, old_type_option, old_field_type) - }; - - self.modify(|grid| { - Ok(grid.switch_to_field( - field_id, - new_field_type.clone(), - make_default_type_option, - type_option_transform, - )?) - }) - .await?; - - self.notify_did_update_grid_field(field_id).await?; - - Ok(()) - } - - pub async fn duplicate_field(&self, field_id: &str) -> FlowyResult<()> { - let duplicated_field_id = gen_field_id(); - self.modify(|grid| Ok(grid.duplicate_field_rev(field_id, &duplicated_field_id)?)) - .await?; - - self.notify_did_insert_grid_field(&duplicated_field_id).await?; - Ok(()) - } - - pub async fn get_field_rev(&self, field_id: &str) -> Option> { - let field_rev = self.database_pad.read().await.get_field_rev(field_id)?.1.clone(); - Some(field_rev) - } - - pub async fn get_field_revs(&self, field_ids: Option>) -> FlowyResult>> { - if field_ids.is_none() { - let field_revs = self.database_pad.read().await.get_field_revs(None)?; - return Ok(field_revs); - } - - let field_ids = field_ids.unwrap_or_default(); - let expected_len = field_ids.len(); - let field_revs = self.database_pad.read().await.get_field_revs(Some(field_ids))?; - if expected_len != 0 && field_revs.len() != expected_len { - tracing::error!( - "This is a bug. The len of the field_revs should equal to {}", - expected_len - ); - debug_assert!(field_revs.len() == expected_len); - } - Ok(field_revs) - } - - pub async fn create_block(&self, block_meta_rev: GridBlockMetaRevision) -> FlowyResult<()> { - self.modify(|grid_pad| Ok(grid_pad.create_block_meta_rev(block_meta_rev)?)) - .await?; - Ok(()) - } - - pub async fn update_block(&self, changeset: GridBlockMetaRevisionChangeset) -> FlowyResult<()> { - self.modify(|grid_pad| Ok(grid_pad.update_block_rev(changeset)?)) - .await?; - Ok(()) - } - - pub async fn create_row(&self, params: CreateRowParams) -> FlowyResult { - let mut row_rev = self.create_row_rev().await?; - - self.view_manager.will_create_row(&mut row_rev, ¶ms).await; - - let row_pb = self.create_row_pb(row_rev, params.start_row_id.clone()).await?; - - self.view_manager.did_create_row(&row_pb, ¶ms).await; - Ok(row_pb) - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> { - self.view_manager.move_group(params).await?; - Ok(()) - } - - pub async fn insert_rows(&self, row_revs: Vec) -> FlowyResult> { - let block_id = self.block_id().await?; - let mut rows_by_block_id: HashMap> = HashMap::new(); - let mut row_orders = vec![]; - for row_rev in row_revs { - row_orders.push(RowPB::from(&row_rev)); - rows_by_block_id - .entry(block_id.clone()) - .or_insert_with(Vec::new) - .push(row_rev); - } - let changesets = self.block_manager.insert_row(rows_by_block_id).await?; - for changeset in changesets { - self.update_block(changeset).await?; - } - Ok(row_orders) - } - - pub async fn update_row(&self, changeset: RowChangeset) -> FlowyResult<()> { - let row_id = changeset.row_id.clone(); - let old_row = self.get_row_rev(&row_id).await?; - self.block_manager.update_row(changeset).await?; - self.view_manager.did_update_row(old_row, &row_id).await; - Ok(()) - } - - /// Returns all the rows in this block. - pub async fn get_row_pbs(&self, view_id: &str, block_id: &str) -> FlowyResult> { - let rows = self.view_manager.get_row_revs(view_id, block_id).await?; - let rows = rows.into_iter().map(|row_rev| RowPB::from(&row_rev)).collect(); - Ok(rows) - } - - pub async fn get_all_row_revs(&self, view_id: &str) -> FlowyResult>> { - let mut all_rows = vec![]; - let blocks = self.block_manager.get_blocks(None).await?; - for block in blocks { - let rows = self.view_manager.get_row_revs(view_id, &block.block_id).await?; - all_rows.extend(rows); - } - Ok(all_rows) - } - - pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult>> { - match self.block_manager.get_row_rev(row_id).await? { - None => Ok(None), - Some((_, row_rev)) => Ok(Some(row_rev)), - } - } - - pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> { - let row_rev = self.block_manager.delete_row(row_id).await?; - tracing::trace!("Did delete row:{:?}", row_rev); - if let Some(row_rev) = row_rev { - self.view_manager.did_delete_row(row_rev).await; - } - Ok(()) - } - - pub async fn subscribe_view_changed(&self, view_id: &str) -> FlowyResult> { - self.view_manager.subscribe_view_changed(view_id).await - } - - pub async fn duplicate_row(&self, _row_id: &str) -> FlowyResult<()> { - Ok(()) - } - - /// Returns the cell data that encoded in protobuf. - pub async fn get_cell(&self, params: &CellIdParams) -> Option { - let (field_type, cell_bytes) = self.get_type_cell_protobuf(params).await?; - Some(CellPB::new( - ¶ms.field_id, - ¶ms.row_id, - field_type, - cell_bytes.to_vec(), - )) - } - - /// Returns a string that represents the current field_type's cell data. - /// For example: - /// Multi-Select: list of the option's name separated by a comma. - /// Number: 123 => $123 if the currency set. - /// Date: 1653609600 => May 27,2022 - /// - pub async fn get_cell_display_str(&self, params: &CellIdParams) -> String { - let display_str = || async { - let field_rev = self.get_field_rev(¶ms.field_id).await?; - let field_type: FieldType = field_rev.ty.into(); - let cell_rev = self.get_cell_rev(¶ms.row_id, ¶ms.field_id).await.ok()??; - let type_cell_data: TypeCellData = cell_rev.try_into().ok()?; - Some(stringify_cell_data( - type_cell_data.cell_str, - &field_type, - &field_type, - &field_rev, - )) - }; - - display_str().await.unwrap_or_default() - } - - pub async fn get_cell_protobuf(&self, params: &CellIdParams) -> Option { - let (_, cell_data) = self.get_type_cell_protobuf(params).await?; - Some(cell_data) - } - - async fn get_type_cell_protobuf(&self, params: &CellIdParams) -> Option<(FieldType, CellProtobufBlob)> { - let field_rev = self.get_field_rev(¶ms.field_id).await?; - let (_, row_rev) = self.block_manager.get_row_rev(¶ms.row_id).await.ok()??; - let cell_rev = row_rev.cells.get(¶ms.field_id)?.clone(); - Some(get_type_cell_protobuf( - cell_rev.type_cell_data, - &field_rev, - Some(self.cell_data_cache.clone()), - )) - } - - pub async fn get_cell_rev(&self, row_id: &str, field_id: &str) -> FlowyResult> { - match self.block_manager.get_row_rev(row_id).await? { - None => Ok(None), - Some((_, row_rev)) => { - let cell_rev = row_rev.cells.get(field_id).cloned(); - Ok(cell_rev) - } - } - } - - /// Returns the list of cells corresponding to the given field. - pub async fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> FlowyResult> { - let view_editor = self.view_manager.get_view_editor(view_id).await?; - view_editor.get_cells_for_field(field_id).await - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn update_cell_with_changeset( - &self, - row_id: &str, - field_id: &str, - cell_changeset: T, - ) -> FlowyResult<()> { - match self.database_pad.read().await.get_field_rev(field_id) { - None => { - let msg = format!("Field with id:{} not found", &field_id); - Err(FlowyError::internal().context(msg)) - } - Some((_, field_rev)) => { - tracing::trace!("Cell changeset: id:{} / value:{:?}", &field_id, cell_changeset); - let old_row_rev = self.get_row_rev(row_id).await?.clone(); - let cell_rev = self.get_cell_rev(row_id, field_id).await?; - // Update the changeset.data property with the return value. - let type_cell_data = - apply_cell_data_changeset(cell_changeset, cell_rev, field_rev, Some(self.cell_data_cache.clone()))?; - let cell_changeset = CellChangesetPB { - database_id: self.database_id.clone(), - row_id: row_id.to_owned(), - field_id: field_id.to_owned(), - type_cell_data, - }; - self.block_manager.update_cell(cell_changeset).await?; - self.view_manager.did_update_row(old_row_rev, row_id).await; - Ok(()) - } - } - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn update_cell( - &self, - row_id: String, - field_id: String, - cell_changeset: T, - ) -> FlowyResult<()> { - self.update_cell_with_changeset(&row_id, &field_id, cell_changeset) - .await - } - - pub async fn get_block_meta_revs(&self) -> FlowyResult>> { - let block_meta_revs = self.database_pad.read().await.get_block_meta_revs(); - Ok(block_meta_revs) - } - - pub async fn get_blocks(&self, block_ids: Option>) -> FlowyResult> { - let block_ids = match block_ids { - None => self - .database_pad - .read() - .await - .get_block_meta_revs() - .iter() - .map(|block_rev| block_rev.block_id.clone()) - .collect::>(), - Some(block_ids) => block_ids, - }; - let blocks = self.block_manager.get_blocks(Some(block_ids)).await?; - Ok(blocks) - } - - pub async fn delete_rows(&self, block_rows: Vec) -> FlowyResult<()> { - let changesets = self.block_manager.delete_rows(block_rows).await?; - for changeset in changesets { - self.update_block(changeset).await?; - } - Ok(()) - } - - pub async fn get_database(&self, view_id: &str) -> FlowyResult { - let pad = self.database_pad.read().await; - let fields = pad.get_field_revs(None)?.iter().map(FieldIdPB::from).collect(); - let mut all_rows = vec![]; - for block_rev in pad.get_block_meta_revs() { - if let Ok(rows) = self.get_row_pbs(view_id, &block_rev.block_id).await { - all_rows.extend(rows); - } - } - - Ok(DatabasePB { - id: self.database_id.clone(), - fields, - rows: all_rows, - }) - } - - pub async fn get_setting(&self) -> FlowyResult { - self.view_manager.get_setting().await - } - - pub async fn get_all_filters(&self) -> FlowyResult> { - Ok(self - .view_manager - .get_all_filters() - .await? - .into_iter() - .map(|filter| FilterPB::from(filter.as_ref())) - .collect()) - } - - pub async fn get_filters(&self, filter_id: FilterType) -> FlowyResult>> { - self.view_manager.get_filters(&filter_id).await - } - - pub async fn create_or_update_filter(&self, params: AlterFilterParams) -> FlowyResult<()> { - self.view_manager.create_or_update_filter(params).await?; - Ok(()) - } - - pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { - self.view_manager.delete_filter(params).await?; - Ok(()) - } - - pub async fn get_all_sorts(&self, view_id: &str) -> FlowyResult> { - Ok(self - .view_manager - .get_all_sorts(view_id) - .await? - .into_iter() - .map(|sort| SortPB::from(sort.as_ref())) - .collect()) - } - - pub async fn delete_all_sorts(&self, view_id: &str) -> FlowyResult<()> { - self.view_manager.delete_all_sorts(view_id).await - } - - pub async fn delete_sort(&self, params: DeleteSortParams) -> FlowyResult<()> { - self.view_manager.delete_sort(params).await?; - Ok(()) - } - - pub async fn create_or_update_sort(&self, params: AlterSortParams) -> FlowyResult { - let sort_rev = self.view_manager.create_or_update_sort(params).await?; - Ok(sort_rev) - } - - pub async fn insert_group(&self, params: InsertGroupParams) -> FlowyResult<()> { - self.view_manager.insert_or_update_group(params).await - } - - pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { - self.view_manager.delete_group(params).await - } - - pub async fn move_row(&self, params: MoveRowParams) -> FlowyResult<()> { - let MoveRowParams { - view_id: _, - from_row_id, - to_row_id, - } = params; - - match self.block_manager.get_row_rev(&from_row_id).await? { - None => tracing::warn!("Move row failed, can not find the row:{}", from_row_id), - Some((_, row_rev)) => { - match ( - self.block_manager.index_of_row(&from_row_id).await, - self.block_manager.index_of_row(&to_row_id).await, - ) { - (Some(from_index), Some(to_index)) => { - tracing::trace!("Move row from {} to {}", from_index, to_index); - self.block_manager - .move_row(row_rev.clone(), from_index, to_index) - .await?; - } - (_, None) => tracing::warn!("Can not find the from row id: {}", from_row_id), - (None, _) => tracing::warn!("Can not find the to row id: {}", to_row_id), - } - } - } - Ok(()) - } - - pub async fn move_group_row(&self, params: MoveGroupRowParams) -> FlowyResult<()> { - let MoveGroupRowParams { - view_id, - from_row_id, - to_group_id, - to_row_id, - } = params; - - match self.block_manager.get_row_rev(&from_row_id).await? { - None => tracing::warn!("Move row failed, can not find the row:{}", from_row_id), - Some((_, row_rev)) => { - let block_manager = self.block_manager.clone(); - self.view_manager - .move_group_row(row_rev, to_group_id, to_row_id.clone(), |row_changeset| { - to_fut(async move { - tracing::trace!("Row data changed: {:?}", row_changeset); - let cell_changesets = row_changeset - .cell_by_field_id - .into_iter() - .map(|(field_id, cell_rev)| CellChangesetPB { - database_id: view_id.clone(), - row_id: row_changeset.row_id.clone(), - field_id, - type_cell_data: cell_rev.type_cell_data, - }) - .collect::>(); - - for cell_changeset in cell_changesets { - match block_manager.update_cell(cell_changeset).await { - Ok(_) => {} - Err(e) => tracing::error!("Apply cell changeset error:{:?}", e), - } - } - }) - }) - .await?; - } - } - Ok(()) - } - - pub async fn move_field(&self, params: MoveFieldParams) -> FlowyResult<()> { - let MoveFieldParams { - view_id: _, - field_id, - from_index, - to_index, - } = params; - - self.modify(|grid_pad| Ok(grid_pad.move_field(&field_id, from_index as usize, to_index as usize)?)) - .await?; - if let Some((index, field_rev)) = self.database_pad.read().await.get_field_rev(&field_id) { - let delete_field_order = FieldIdPB::from(field_id); - let insert_field = IndexFieldPB::from_field_rev(field_rev, index); - let notified_changeset = DatabaseFieldChangesetPB { - database_id: self.database_id.clone(), - inserted_fields: vec![insert_field], - deleted_fields: vec![delete_field_order], - updated_fields: vec![], - }; - - self.notify_did_update_grid(notified_changeset).await?; - } - Ok(()) - } - - pub async fn duplicate_grid(&self) -> FlowyResult { - let grid_pad = self.database_pad.read().await; - let grid_view_revision_data = self.view_manager.duplicate_database_view().await?; - let original_blocks = grid_pad.get_block_meta_revs(); - let (duplicated_fields, duplicated_blocks) = grid_pad.duplicate_grid_block_meta().await; - - let mut blocks_meta_data = vec![]; - if original_blocks.len() == duplicated_blocks.len() { - for (index, original_block_meta) in original_blocks.iter().enumerate() { - let grid_block_meta_editor = self - .block_manager - .get_block_editor(&original_block_meta.block_id) - .await?; - let duplicated_block_id = &duplicated_blocks[index].block_id; - - tracing::trace!("Duplicate block:{} meta data", duplicated_block_id); - let duplicated_block_meta_data = grid_block_meta_editor.duplicate_block(duplicated_block_id).await; - blocks_meta_data.push(duplicated_block_meta_data); - } - } else { - debug_assert_eq!(original_blocks.len(), duplicated_blocks.len()); - } - drop(grid_pad); - - Ok(BuildDatabaseContext { - field_revs: duplicated_fields.into_iter().map(Arc::new).collect(), - block_metas: duplicated_blocks, - blocks: blocks_meta_data, - grid_view_revision_data, - }) - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn load_groups(&self) -> FlowyResult { - self.view_manager.load_groups().await - } - - async fn create_row_rev(&self) -> FlowyResult { - let field_revs = self.database_pad.read().await.get_field_revs(None)?; - let block_id = self.block_id().await?; - - // insert empty row below the row whose id is upper_row_id - let row_rev = RowRevisionBuilder::new(&block_id, &field_revs).build(); - Ok(row_rev) - } - - async fn create_row_pb(&self, row_rev: RowRevision, start_row_id: Option) -> FlowyResult { - let row_pb = RowPB::from(&row_rev); - let block_id = row_rev.block_id.clone(); - - // insert the row - let row_count = self.block_manager.create_row(row_rev, start_row_id).await?; - - // update block row count - let changeset = GridBlockMetaRevisionChangeset::from_row_count(block_id, row_count); - self.update_block(changeset).await?; - Ok(row_pb) - } - - async fn modify(&self, f: F) -> FlowyResult<()> - where - F: for<'a> FnOnce(&'a mut DatabaseRevisionPad) -> FlowyResult>, - { - let mut write_guard = self.database_pad.write().await; - if let Some(changeset) = f(&mut write_guard)? { - self.apply_change(changeset).await?; - } - Ok(()) - } - - async fn apply_change(&self, change: DatabaseRevisionChangeset) -> FlowyResult<()> { - let DatabaseRevisionChangeset { operations: delta, md5 } = change; - let data = delta.json_bytes(); - let _ = self.rev_manager.add_local_revision(data, md5).await?; - Ok(()) - } - - async fn block_id(&self) -> FlowyResult { - match self.database_pad.read().await.get_block_meta_revs().last() { - None => Err(FlowyError::internal().context("There is no grid block in this grid")), - Some(grid_block) => Ok(grid_block.block_id.clone()), - } - } - - #[tracing::instrument(level = "trace", skip_all, err)] - async fn notify_did_insert_grid_field(&self, field_id: &str) -> FlowyResult<()> { - if let Some((index, field_rev)) = self.database_pad.read().await.get_field_rev(field_id) { - let index_field = IndexFieldPB::from_field_rev(field_rev, index); - let notified_changeset = DatabaseFieldChangesetPB::insert(&self.database_id, vec![index_field]); - self.notify_did_update_grid(notified_changeset).await?; - } - Ok(()) - } - - #[tracing::instrument(level = "trace", skip_all, err)] - async fn notify_did_update_grid_field(&self, field_id: &str) -> FlowyResult<()> { - if let Some((_, field_rev)) = self - .database_pad - .read() - .await - .get_field_rev(field_id) - .map(|(index, field)| (index, field.clone())) - { - let updated_field = FieldPB::from(field_rev); - let notified_changeset = DatabaseFieldChangesetPB::update(&self.database_id, vec![updated_field.clone()]); - self.notify_did_update_grid(notified_changeset).await?; - - send_notification(field_id, DatabaseNotification::DidUpdateField) - .payload(updated_field) - .send(); - } - - Ok(()) - } - - async fn notify_did_update_grid(&self, changeset: DatabaseFieldChangesetPB) -> FlowyResult<()> { - send_notification(&self.database_id, DatabaseNotification::DidUpdateFields) - .payload(changeset) - .send(); - Ok(()) - } + Ok(()) + } + + async fn notify_did_update_grid(&self, changeset: DatabaseFieldChangesetPB) -> FlowyResult<()> { + send_notification(&self.database_id, DatabaseNotification::DidUpdateFields) + .payload(changeset) + .send(); + Ok(()) + } } #[cfg(feature = "flowy_unit_test")] impl DatabaseRevisionEditor { - pub fn rev_manager(&self) -> Arc>> { - self.rev_manager.clone() - } + pub fn rev_manager(&self) -> Arc>> { + self.rev_manager.clone() + } - pub fn grid_pad(&self) -> Arc> { - self.database_pad.clone() - } + pub fn grid_pad(&self) -> Arc> { + self.database_pad.clone() + } } pub struct GridRevisionSerde(); impl RevisionObjectDeserializer for GridRevisionSerde { - type Output = DatabaseRevisionPad; + type Output = DatabaseRevisionPad; - fn deserialize_revisions(_object_id: &str, revisions: Vec) -> FlowyResult { - let pad = DatabaseRevisionPad::from_revisions(revisions)?; - Ok(pad) - } + fn deserialize_revisions( + _object_id: &str, + revisions: Vec, + ) -> FlowyResult { + let pad = DatabaseRevisionPad::from_revisions(revisions)?; + Ok(pad) + } - fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { - None - } + fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { + None + } } impl RevisionObjectSerializer for GridRevisionSerde { - fn combine_revisions(revisions: Vec) -> FlowyResult { - let operations = make_operations_from_revisions::(revisions)?; - Ok(operations.json_bytes()) - } + fn combine_revisions(revisions: Vec) -> FlowyResult { + let operations = make_operations_from_revisions::(revisions)?; + Ok(operations.json_bytes()) + } } pub struct GridRevisionCloudService { - #[allow(dead_code)] - token: String, + #[allow(dead_code)] + token: String, } impl GridRevisionCloudService { - pub fn new(token: String) -> Self { - Self { token } - } + pub fn new(token: String) -> Self { + Self { token } + } } impl RevisionCloudService for GridRevisionCloudService { - #[tracing::instrument(level = "trace", skip(self))] - fn fetch_object(&self, _user_id: &str, _object_id: &str) -> FutureResult, FlowyError> { - FutureResult::new(async move { Ok(vec![]) }) - } + #[tracing::instrument(level = "trace", skip(self))] + fn fetch_object( + &self, + _user_id: &str, + _object_id: &str, + ) -> FutureResult, FlowyError> { + FutureResult::new(async move { Ok(vec![]) }) + } } pub struct GridRevisionMergeable(); impl RevisionMergeable for GridRevisionMergeable { - fn combine_revisions(&self, revisions: Vec) -> FlowyResult { - GridRevisionSerde::combine_revisions(revisions) - } + fn combine_revisions(&self, revisions: Vec) -> FlowyResult { + GridRevisionSerde::combine_revisions(revisions) + } } struct TypeOptionJsonDeserializer(FieldType); impl JsonDeserializer for TypeOptionJsonDeserializer { - fn deserialize(&self, type_option_data: Vec) -> SyncResult { - // The type_option_data sent from Dart is serialized by protobuf. - let builder = type_option_builder_from_bytes(type_option_data, &self.0); - let json = builder.serializer().json_str(); - tracing::trace!("Deserialize type-option data to: {}", json); - Ok(json) - } + fn deserialize(&self, type_option_data: Vec) -> SyncResult { + // The type_option_data sent from Dart is serialized by protobuf. + let builder = type_option_builder_from_bytes(type_option_data, &self.0); + let json = builder.serializer().json_str(); + tracing::trace!("Deserialize type-option data to: {}", json); + Ok(json) + } } diff --git a/frontend/rust-lib/flowy-database/src/services/grid_editor_trait_impl.rs b/frontend/rust-lib/flowy-database/src/services/grid_editor_trait_impl.rs index 66b054fdd8..f5a7151148 100644 --- a/frontend/rust-lib/flowy-database/src/services/grid_editor_trait_impl.rs +++ b/frontend/rust-lib/flowy-database/src/services/grid_editor_trait_impl.rs @@ -13,79 +13,82 @@ use std::sync::Arc; use tokio::sync::RwLock; pub(crate) struct GridViewEditorDelegateImpl { - pub(crate) pad: Arc>, - pub(crate) block_manager: Arc, - pub(crate) task_scheduler: Arc>, - pub(crate) cell_data_cache: AtomicCellDataCache, + pub(crate) pad: Arc>, + pub(crate) block_manager: Arc, + pub(crate) task_scheduler: Arc>, + pub(crate) cell_data_cache: AtomicCellDataCache, } impl DatabaseViewEditorDelegate for GridViewEditorDelegateImpl { - fn get_field_revs(&self, field_ids: Option>) -> Fut>> { - let pad = self.pad.clone(); - to_fut(async move { - match pad.read().await.get_field_revs(field_ids) { - Ok(field_revs) => field_revs, - Err(e) => { - tracing::error!("[GridViewRevisionDelegate] get field revisions failed: {}", e); - vec![] - } - } - }) - } - fn get_field_rev(&self, field_id: &str) -> Fut>> { - let pad = self.pad.clone(); - let field_id = field_id.to_owned(); - to_fut(async move { Some(pad.read().await.get_field_rev(&field_id)?.1.clone()) }) - } + fn get_field_revs(&self, field_ids: Option>) -> Fut>> { + let pad = self.pad.clone(); + to_fut(async move { + match pad.read().await.get_field_revs(field_ids) { + Ok(field_revs) => field_revs, + Err(e) => { + tracing::error!( + "[GridViewRevisionDelegate] get field revisions failed: {}", + e + ); + vec![] + }, + } + }) + } + fn get_field_rev(&self, field_id: &str) -> Fut>> { + let pad = self.pad.clone(); + let field_id = field_id.to_owned(); + to_fut(async move { Some(pad.read().await.get_field_rev(&field_id)?.1.clone()) }) + } - fn index_of_row(&self, row_id: &str) -> Fut> { - let block_manager = self.block_manager.clone(); - let row_id = row_id.to_owned(); - to_fut(async move { block_manager.index_of_row(&row_id).await }) - } + fn index_of_row(&self, row_id: &str) -> Fut> { + let block_manager = self.block_manager.clone(); + let row_id = row_id.to_owned(); + to_fut(async move { block_manager.index_of_row(&row_id).await }) + } - fn get_row_rev(&self, row_id: &str) -> Fut)>> { - let block_manager = self.block_manager.clone(); - let row_id = row_id.to_owned(); - to_fut(async move { - match block_manager.get_row_rev(&row_id).await { - Ok(indexed_row) => indexed_row, - Err(_) => None, - } - }) - } + fn get_row_rev(&self, row_id: &str) -> Fut)>> { + let block_manager = self.block_manager.clone(); + let row_id = row_id.to_owned(); + to_fut(async move { + match block_manager.get_row_rev(&row_id).await { + Ok(indexed_row) => indexed_row, + Err(_) => None, + } + }) + } - fn get_row_revs(&self, block_id: Option>) -> Fut>> { - let block_manager = self.block_manager.clone(); + fn get_row_revs(&self, block_id: Option>) -> Fut>> { + let block_manager = self.block_manager.clone(); - to_fut(async move { - let blocks = block_manager.get_blocks(block_id).await.unwrap(); - blocks - .into_iter() - .flat_map(|block| block.row_revs) - .collect::>>() - }) - } + to_fut(async move { + let blocks = block_manager.get_blocks(block_id).await.unwrap(); + blocks + .into_iter() + .flat_map(|block| block.row_revs) + .collect::>>() + }) + } - // /// Returns the list of cells corresponding to the given field. - // pub async fn get_cells_for_field(&self, field_id: &str) -> FlowyResult> { - // } + // /// Returns the list of cells corresponding to the given field. + // pub async fn get_cells_for_field(&self, field_id: &str) -> FlowyResult> { + // } - fn get_blocks(&self) -> Fut> { - let block_manager = self.block_manager.clone(); - to_fut(async move { block_manager.get_blocks(None).await.unwrap_or_default() }) - } + fn get_blocks(&self) -> Fut> { + let block_manager = self.block_manager.clone(); + to_fut(async move { block_manager.get_blocks(None).await.unwrap_or_default() }) + } - fn get_task_scheduler(&self) -> Arc> { - self.task_scheduler.clone() - } + fn get_task_scheduler(&self) -> Arc> { + self.task_scheduler.clone() + } - fn get_type_option_cell_handler( - &self, - field_rev: &FieldRevision, - field_type: &FieldType, - ) -> Option> { - TypeOptionCellExt::new_with_cell_data_cache(field_rev, Some(self.cell_data_cache.clone())) - .get_type_option_cell_data_handler(field_type) - } + fn get_type_option_cell_handler( + &self, + field_rev: &FieldRevision, + field_type: &FieldType, + ) -> Option> { + TypeOptionCellExt::new_with_cell_data_cache(field_rev, Some(self.cell_data_cache.clone())) + .get_type_option_cell_data_handler(field_type) + } } diff --git a/frontend/rust-lib/flowy-database/src/services/group/action.rs b/frontend/rust-lib/flowy-database/src/services/group/action.rs index 16bed6e2e8..f99171307e 100644 --- a/frontend/rust-lib/flowy-database/src/services/group/action.rs +++ b/frontend/rust-lib/flowy-database/src/services/group/action.rs @@ -11,98 +11,117 @@ use std::sync::Arc; /// For example, the `CheckboxGroupController` implements this trait to provide custom behavior. /// pub trait GroupCustomize: Send + Sync { - type CellData: DecodedCellData; - /// Returns the a value of the cell if the cell data is not exist. - /// The default value is `None` - /// - /// Determine which group the row is placed in based on the data of the cell. If the cell data - /// is None. The row will be put in to the `No status` group - /// - fn placeholder_cell(&self) -> Option { - None - } + type CellData: DecodedCellData; + /// Returns the a value of the cell if the cell data is not exist. + /// The default value is `None` + /// + /// Determine which group the row is placed in based on the data of the cell. If the cell data + /// is None. The row will be put in to the `No status` group + /// + fn placeholder_cell(&self) -> Option { + None + } - /// Returns a bool value to determine whether the group should contain this cell or not. - fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool; + /// Returns a bool value to determine whether the group should contain this cell or not. + fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool; - fn create_or_delete_group_when_cell_changed( - &mut self, - _row_rev: &RowRevision, - _old_cell_data: Option<&Self::CellData>, - _cell_data: &Self::CellData, - ) -> FlowyResult<(Option, Option)> { - Ok((None, None)) - } + fn create_or_delete_group_when_cell_changed( + &mut self, + _row_rev: &RowRevision, + _old_cell_data: Option<&Self::CellData>, + _cell_data: &Self::CellData, + ) -> FlowyResult<(Option, Option)> { + Ok((None, None)) + } - /// Adds or removes a row if the cell data match the group filter. - /// It gets called after editing the cell or row - /// - fn add_or_remove_row_when_cell_changed( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellData, - ) -> Vec; + /// Adds or removes a row if the cell data match the group filter. + /// It gets called after editing the cell or row + /// + fn add_or_remove_row_when_cell_changed( + &mut self, + row_rev: &RowRevision, + cell_data: &Self::CellData, + ) -> Vec; - /// Deletes the row from the group - fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellData) -> Vec; + /// Deletes the row from the group + fn delete_row( + &mut self, + row_rev: &RowRevision, + cell_data: &Self::CellData, + ) -> Vec; - /// Move row from one group to another - fn move_row(&mut self, cell_data: &Self::CellData, context: MoveGroupRowContext) -> Vec; + /// Move row from one group to another + fn move_row( + &mut self, + cell_data: &Self::CellData, + context: MoveGroupRowContext, + ) -> Vec; - /// Returns None if there is no need to delete the group when corresponding row get removed - fn delete_group_when_move_row(&mut self, _row_rev: &RowRevision, _cell_data: &Self::CellData) -> Option { - None - } + /// Returns None if there is no need to delete the group when corresponding row get removed + fn delete_group_when_move_row( + &mut self, + _row_rev: &RowRevision, + _cell_data: &Self::CellData, + ) -> Option { + None + } } /// Defines the shared actions any group controller can perform. pub trait GroupControllerActions: Send + Sync { - /// The field that is used for grouping the rows - fn field_id(&self) -> &str; + /// The field that is used for grouping the rows + fn field_id(&self) -> &str; - /// Returns number of groups the current field has - fn groups(&self) -> Vec<&Group>; + /// Returns number of groups the current field has + fn groups(&self) -> Vec<&Group>; - /// Returns the index and the group data with group_id - fn get_group(&self, group_id: &str) -> Option<(usize, Group)>; + /// Returns the index and the group data with group_id + fn get_group(&self, group_id: &str) -> Option<(usize, Group)>; - /// Separates the rows into different groups - fn fill_groups(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()>; + /// Separates the rows into different groups + fn fill_groups( + &mut self, + row_revs: &[Arc], + field_rev: &FieldRevision, + ) -> FlowyResult<()>; - /// Remove the group with from_group_id and insert it to the index with to_group_id - fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()>; + /// Remove the group with from_group_id and insert it to the index with to_group_id + fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()>; - /// Insert/Remove the row to the group if the corresponding cell data is changed - fn did_update_group_row( - &mut self, - old_row_rev: &Option>, - row_rev: &RowRevision, - field_rev: &FieldRevision, - ) -> FlowyResult; + /// Insert/Remove the row to the group if the corresponding cell data is changed + fn did_update_group_row( + &mut self, + old_row_rev: &Option>, + row_rev: &RowRevision, + field_rev: &FieldRevision, + ) -> FlowyResult; - /// Remove the row from the group if the row gets deleted - fn did_delete_delete_row( - &mut self, - row_rev: &RowRevision, - field_rev: &FieldRevision, - ) -> FlowyResult; + /// Remove the row from the group if the row gets deleted + fn did_delete_delete_row( + &mut self, + row_rev: &RowRevision, + field_rev: &FieldRevision, + ) -> FlowyResult; - /// Move the row from one group to another group - fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult; + /// Move the row from one group to another group + fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult; - /// Update the group if the corresponding field is changed - fn did_update_group_field(&mut self, field_rev: &FieldRevision) -> FlowyResult>; + /// Update the group if the corresponding field is changed + fn did_update_group_field( + &mut self, + field_rev: &FieldRevision, + ) -> FlowyResult>; } #[derive(Debug)] pub struct DidUpdateGroupRowResult { - pub(crate) inserted_group: Option, - pub(crate) deleted_group: Option, - pub(crate) row_changesets: Vec, + pub(crate) inserted_group: Option, + pub(crate) deleted_group: Option, + pub(crate) row_changesets: Vec, } #[derive(Debug)] pub struct DidMoveGroupRowResult { - pub(crate) deleted_group: Option, - pub(crate) row_changesets: Vec, + pub(crate) deleted_group: Option, + pub(crate) row_changesets: Vec, } diff --git a/frontend/rust-lib/flowy-database/src/services/group/configuration.rs b/frontend/rust-lib/flowy-database/src/services/group/configuration.rs index 89d775bffd..27f78e8113 100644 --- a/frontend/rust-lib/flowy-database/src/services/group/configuration.rs +++ b/frontend/rust-lib/flowy-database/src/services/group/configuration.rs @@ -3,7 +3,8 @@ use crate::services::field::RowSingleCellData; use crate::services::group::{default_group_configuration, GeneratedGroupContext, Group}; use flowy_error::{FlowyError, FlowyResult}; use grid_model::{ - FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRevision, + FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, + GroupRevision, }; use indexmap::IndexMap; use lib_infra::future::Fut; @@ -13,27 +14,31 @@ use std::marker::PhantomData; use std::sync::Arc; pub trait GroupConfigurationReader: Send + Sync + 'static { - fn get_configuration(&self) -> Fut>>; - fn get_configuration_cells(&self, field_id: &str) -> Fut>>; + fn get_configuration(&self) -> Fut>>; + fn get_configuration_cells(&self, field_id: &str) -> Fut>>; } pub trait GroupConfigurationWriter: Send + Sync + 'static { - fn save_configuration( - &self, - field_id: &str, - field_type: FieldTypeRevision, - group_configuration: GroupConfigurationRevision, - ) -> Fut>; + fn save_configuration( + &self, + field_id: &str, + field_type: FieldTypeRevision, + group_configuration: GroupConfigurationRevision, + ) -> Fut>; } impl std::fmt::Display for GroupContext { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - self.groups_map.iter().for_each(|(_, group)| { - let _ = f.write_fmt(format_args!("Group:{} has {} rows \n", group.id, group.rows.len())); - }); + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.groups_map.iter().for_each(|(_, group)| { + let _ = f.write_fmt(format_args!( + "Group:{} has {} rows \n", + group.id, + group.rows.len() + )); + }); - Ok(()) - } + Ok(()) + } } /// A [GroupContext] represents as the groups memory cache @@ -42,407 +47,442 @@ impl std::fmt::Display for GroupContext { /// /// The `context` contains a list of [Group]s and the grouping [FieldRevision] pub struct GroupContext { - pub view_id: String, - /// The group configuration restored from the disk. - /// - /// Uses the [GroupConfigurationReader] to read the configuration data from disk - configuration: Arc, - configuration_phantom: PhantomData, + pub view_id: String, + /// The group configuration restored from the disk. + /// + /// Uses the [GroupConfigurationReader] to read the configuration data from disk + configuration: Arc, + configuration_phantom: PhantomData, - /// The grouping field - field_rev: Arc, + /// The grouping field + field_rev: Arc, - /// Cache all the groups - groups_map: IndexMap, + /// Cache all the groups + groups_map: IndexMap, - /// A reader that implement the [GroupConfigurationReader] trait - /// - #[allow(dead_code)] - reader: Arc, + /// A reader that implement the [GroupConfigurationReader] trait + /// + #[allow(dead_code)] + reader: Arc, - /// A writer that implement the [GroupConfigurationWriter] trait is used to save the - /// configuration to disk - /// - writer: Arc, + /// A writer that implement the [GroupConfigurationWriter] trait is used to save the + /// configuration to disk + /// + writer: Arc, } impl GroupContext where - C: GroupConfigurationContentSerde, + C: GroupConfigurationContentSerde, { - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn new( - view_id: String, - field_rev: Arc, - reader: Arc, - writer: Arc, - ) -> FlowyResult { - let configuration = match reader.get_configuration().await { - None => { - let default_configuration = default_group_configuration(&field_rev); - writer - .save_configuration(&field_rev.id, field_rev.ty, default_configuration.clone()) - .await?; - Arc::new(default_configuration) - } - Some(configuration) => configuration, - }; + #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn new( + view_id: String, + field_rev: Arc, + reader: Arc, + writer: Arc, + ) -> FlowyResult { + let configuration = match reader.get_configuration().await { + None => { + let default_configuration = default_group_configuration(&field_rev); + writer + .save_configuration(&field_rev.id, field_rev.ty, default_configuration.clone()) + .await?; + Arc::new(default_configuration) + }, + Some(configuration) => configuration, + }; - Ok(Self { - view_id, - field_rev, - groups_map: IndexMap::new(), - reader, - writer, - configuration, - configuration_phantom: PhantomData, - }) + Ok(Self { + view_id, + field_rev, + groups_map: IndexMap::new(), + reader, + writer, + configuration, + configuration_phantom: PhantomData, + }) + } + + /// Returns the no `status` group + /// + /// We take the `id` of the `field` as the no status group id + pub(crate) fn get_no_status_group(&self) -> Option<&Group> { + self.groups_map.get(&self.field_rev.id) + } + + pub(crate) fn get_mut_no_status_group(&mut self) -> Option<&mut Group> { + self.groups_map.get_mut(&self.field_rev.id) + } + + pub(crate) fn groups(&self) -> Vec<&Group> { + self.groups_map.values().collect() + } + + pub(crate) fn get_mut_group(&mut self, group_id: &str) -> Option<&mut Group> { + self.groups_map.get_mut(group_id) + } + + // Returns the index and group specified by the group_id + pub(crate) fn get_group(&self, group_id: &str) -> Option<(usize, &Group)> { + match ( + self.groups_map.get_index_of(group_id), + self.groups_map.get(group_id), + ) { + (Some(index), Some(group)) => Some((index, group)), + _ => None, } + } - /// Returns the no `status` group - /// - /// We take the `id` of the `field` as the no status group id - pub(crate) fn get_no_status_group(&self) -> Option<&Group> { - self.groups_map.get(&self.field_rev.id) - } + /// Iterate mut the groups without `No status` group + pub(crate) fn iter_mut_status_groups(&mut self, mut each: impl FnMut(&mut Group)) { + self.groups_map.iter_mut().for_each(|(_, group)| { + if group.id != self.field_rev.id { + each(group); + } + }); + } - pub(crate) fn get_mut_no_status_group(&mut self) -> Option<&mut Group> { - self.groups_map.get_mut(&self.field_rev.id) - } + pub(crate) fn iter_mut_groups(&mut self, mut each: impl FnMut(&mut Group)) { + self.groups_map.iter_mut().for_each(|(_, group)| { + each(group); + }); + } + #[tracing::instrument(level = "trace", skip(self), err)] + pub(crate) fn add_new_group(&mut self, group_rev: GroupRevision) -> FlowyResult { + let group = Group::new( + group_rev.id.clone(), + self.field_rev.id.clone(), + group_rev.name.clone(), + group_rev.id.clone(), + ); + self.groups_map.insert(group_rev.id.clone(), group); + let (index, group) = self.get_group(&group_rev.id).unwrap(); + let insert_group = InsertedGroupPB { + group: GroupPB::from(group.clone()), + index: index as i32, + }; + self.mut_configuration(|configuration| { + configuration.groups.push(group_rev); + true + })?; - pub(crate) fn groups(&self) -> Vec<&Group> { - self.groups_map.values().collect() - } + Ok(insert_group) + } - pub(crate) fn get_mut_group(&mut self, group_id: &str) -> Option<&mut Group> { - self.groups_map.get_mut(group_id) - } + #[tracing::instrument(level = "trace", skip(self))] + pub(crate) fn delete_group(&mut self, deleted_group_id: &str) -> FlowyResult<()> { + self.groups_map.remove(deleted_group_id); + self.mut_configuration(|configuration| { + configuration + .groups + .retain(|group| group.id != deleted_group_id); + true + })?; + Ok(()) + } - // Returns the index and group specified by the group_id - pub(crate) fn get_group(&self, group_id: &str) -> Option<(usize, &Group)> { - match (self.groups_map.get_index_of(group_id), self.groups_map.get(group_id)) { - (Some(index), Some(group)) => Some((index, group)), - _ => None, - } - } - - /// Iterate mut the groups without `No status` group - pub(crate) fn iter_mut_status_groups(&mut self, mut each: impl FnMut(&mut Group)) { - self.groups_map.iter_mut().for_each(|(_, group)| { - if group.id != self.field_rev.id { - each(group); - } - }); - } - - pub(crate) fn iter_mut_groups(&mut self, mut each: impl FnMut(&mut Group)) { - self.groups_map.iter_mut().for_each(|(_, group)| { - each(group); - }); - } - #[tracing::instrument(level = "trace", skip(self), err)] - pub(crate) fn add_new_group(&mut self, group_rev: GroupRevision) -> FlowyResult { - let group = Group::new( - group_rev.id.clone(), - self.field_rev.id.clone(), - group_rev.name.clone(), - group_rev.id.clone(), - ); - self.groups_map.insert(group_rev.id.clone(), group); - let (index, group) = self.get_group(&group_rev.id).unwrap(); - let insert_group = InsertedGroupPB { - group: GroupPB::from(group.clone()), - index: index as i32, - }; - self.mut_configuration(|configuration| { - configuration.groups.push(group_rev); - true - })?; - - Ok(insert_group) - } - - #[tracing::instrument(level = "trace", skip(self))] - pub(crate) fn delete_group(&mut self, deleted_group_id: &str) -> FlowyResult<()> { - self.groups_map.remove(deleted_group_id); - self.mut_configuration(|configuration| { - configuration.groups.retain(|group| group.id != deleted_group_id); - true - })?; - Ok(()) - } - - pub(crate) fn move_group(&mut self, from_id: &str, to_id: &str) -> FlowyResult<()> { - let from_index = self.groups_map.get_index_of(from_id); - let to_index = self.groups_map.get_index_of(to_id); - match (from_index, to_index) { - (Some(from_index), Some(to_index)) => { - self.groups_map.move_index(from_index, to_index); - - self.mut_configuration(|configuration| { - let from_index = configuration.groups.iter().position(|group| group.id == from_id); - let to_index = configuration.groups.iter().position(|group| group.id == to_id); - if let (Some(from), Some(to)) = &(from_index, to_index) { - tracing::trace!("Move group from index:{:?} to index:{:?}", from_index, to_index); - let group = configuration.groups.remove(*from); - configuration.groups.insert(*to, group); - } - tracing::debug!( - "Group order: {:?} ", - configuration - .groups - .iter() - .map(|group| group.name.clone()) - .collect::>() - .join(",") - ); - - from_index.is_some() && to_index.is_some() - })?; - Ok(()) - } - _ => Err(FlowyError::record_not_found().context("Moving group failed. Groups are not exist")), - } - } - - /// Reset the memory cache of the groups and update the group configuration - /// - /// # Arguments - /// - /// * `generated_group_configs`: the generated groups contains a list of [GeneratedGroupConfig]. - /// - /// Each [FieldType] can implement the [GroupGenerator] trait in order to generate different - /// groups. For example, the FieldType::Checkbox has the [CheckboxGroupGenerator] that implements - /// the [GroupGenerator] trait. - /// - /// Consider the passed-in generated_group_configs as new groups, the groups in the current - /// [GroupConfigurationRevision] as old groups. The old groups and the new groups will be merged - /// while keeping the order of the old groups. - /// - #[tracing::instrument(level = "trace", skip(self, generated_group_context), err)] - pub(crate) fn init_groups( - &mut self, - generated_group_context: GeneratedGroupContext, - ) -> FlowyResult> { - let GeneratedGroupContext { - no_status_group, - group_configs, - } = generated_group_context; - - let mut new_groups = vec![]; - let mut filter_content_map = HashMap::new(); - group_configs.into_iter().for_each(|generate_group| { - filter_content_map.insert(generate_group.group_rev.id.clone(), generate_group.filter_content); - new_groups.push(generate_group.group_rev); - }); - - let mut old_groups = self.configuration.groups.clone(); - // clear all the groups if grouping by a new field - if self.configuration.field_id != self.field_rev.id { - old_groups.clear(); - } - - // The `all_group_revs` is the combination of the new groups and old groups - let MergeGroupResult { - mut all_group_revs, - new_group_revs, - deleted_group_revs, - } = merge_groups(no_status_group, old_groups, new_groups); - - let deleted_group_ids = deleted_group_revs - .into_iter() - .map(|group_rev| group_rev.id) - .collect::>(); + pub(crate) fn move_group(&mut self, from_id: &str, to_id: &str) -> FlowyResult<()> { + let from_index = self.groups_map.get_index_of(from_id); + let to_index = self.groups_map.get_index_of(to_id); + match (from_index, to_index) { + (Some(from_index), Some(to_index)) => { + self.groups_map.move_index(from_index, to_index); self.mut_configuration(|configuration| { - let mut is_changed = !deleted_group_ids.is_empty(); - // Remove the groups + let from_index = configuration + .groups + .iter() + .position(|group| group.id == from_id); + let to_index = configuration + .groups + .iter() + .position(|group| group.id == to_id); + if let (Some(from), Some(to)) = &(from_index, to_index) { + tracing::trace!( + "Move group from index:{:?} to index:{:?}", + from_index, + to_index + ); + let group = configuration.groups.remove(*from); + configuration.groups.insert(*to, group); + } + tracing::debug!( + "Group order: {:?} ", configuration - .groups - .retain(|group| !deleted_group_ids.contains(&group.id)); + .groups + .iter() + .map(|group| group.name.clone()) + .collect::>() + .join(",") + ); - // Update/Insert new groups - for group_rev in &mut all_group_revs { - match configuration - .groups - .iter() - .position(|old_group_rev| old_group_rev.id == group_rev.id) - { - None => { - // Push the group to the end of the list if it doesn't exist in the group - configuration.groups.push(group_rev.clone()); - is_changed = true; - } - Some(pos) => { - let mut old_group = configuration.groups.get_mut(pos).unwrap(); - // Take the old group setting - group_rev.update_with_other(old_group); - if !is_changed { - is_changed = is_group_changed(group_rev, old_group); - } - // Consider the the name of the `group_rev` as the newest. - old_group.name = group_rev.name.clone(); - } - } + from_index.is_some() && to_index.is_some() + })?; + Ok(()) + }, + _ => Err(FlowyError::record_not_found().context("Moving group failed. Groups are not exist")), + } + } + + /// Reset the memory cache of the groups and update the group configuration + /// + /// # Arguments + /// + /// * `generated_group_configs`: the generated groups contains a list of [GeneratedGroupConfig]. + /// + /// Each [FieldType] can implement the [GroupGenerator] trait in order to generate different + /// groups. For example, the FieldType::Checkbox has the [CheckboxGroupGenerator] that implements + /// the [GroupGenerator] trait. + /// + /// Consider the passed-in generated_group_configs as new groups, the groups in the current + /// [GroupConfigurationRevision] as old groups. The old groups and the new groups will be merged + /// while keeping the order of the old groups. + /// + #[tracing::instrument(level = "trace", skip(self, generated_group_context), err)] + pub(crate) fn init_groups( + &mut self, + generated_group_context: GeneratedGroupContext, + ) -> FlowyResult> { + let GeneratedGroupContext { + no_status_group, + group_configs, + } = generated_group_context; + + let mut new_groups = vec![]; + let mut filter_content_map = HashMap::new(); + group_configs.into_iter().for_each(|generate_group| { + filter_content_map.insert( + generate_group.group_rev.id.clone(), + generate_group.filter_content, + ); + new_groups.push(generate_group.group_rev); + }); + + let mut old_groups = self.configuration.groups.clone(); + // clear all the groups if grouping by a new field + if self.configuration.field_id != self.field_rev.id { + old_groups.clear(); + } + + // The `all_group_revs` is the combination of the new groups and old groups + let MergeGroupResult { + mut all_group_revs, + new_group_revs, + deleted_group_revs, + } = merge_groups(no_status_group, old_groups, new_groups); + + let deleted_group_ids = deleted_group_revs + .into_iter() + .map(|group_rev| group_rev.id) + .collect::>(); + + self.mut_configuration(|configuration| { + let mut is_changed = !deleted_group_ids.is_empty(); + // Remove the groups + configuration + .groups + .retain(|group| !deleted_group_ids.contains(&group.id)); + + // Update/Insert new groups + for group_rev in &mut all_group_revs { + match configuration + .groups + .iter() + .position(|old_group_rev| old_group_rev.id == group_rev.id) + { + None => { + // Push the group to the end of the list if it doesn't exist in the group + configuration.groups.push(group_rev.clone()); + is_changed = true; + }, + Some(pos) => { + let mut old_group = configuration.groups.get_mut(pos).unwrap(); + // Take the old group setting + group_rev.update_with_other(old_group); + if !is_changed { + is_changed = is_group_changed(group_rev, old_group); } - is_changed - })?; - - // Update the memory cache of the groups - all_group_revs.into_iter().for_each(|group_rev| { - let filter_content = filter_content_map - .get(&group_rev.id) - .cloned() - .unwrap_or_else(|| "".to_owned()); - let group = Group::new(group_rev.id, self.field_rev.id.clone(), group_rev.name, filter_content); - self.groups_map.insert(group.id.clone(), group); - }); - - let initial_groups = new_group_revs - .into_iter() - .flat_map(|group_rev| { - let filter_content = filter_content_map.get(&group_rev.id)?; - let group = Group::new( - group_rev.id, - self.field_rev.id.clone(), - group_rev.name, - filter_content.clone(), - ); - Some(GroupPB::from(group)) - }) - .collect(); - - let changeset = GroupChangesetPB { - view_id: self.view_id.clone(), - initial_groups, - deleted_groups: deleted_group_ids, - update_groups: vec![], - inserted_groups: vec![], - }; - tracing::trace!("Group changeset: {:?}", changeset); - if changeset.is_empty() { - Ok(None) - } else { - Ok(Some(changeset)) + // Consider the the name of the `group_rev` as the newest. + old_group.name = group_rev.name.clone(); + }, } - } + } + is_changed + })?; - #[allow(dead_code)] - pub(crate) async fn hide_group(&mut self, group_id: &str) -> FlowyResult<()> { - self.mut_group_rev(group_id, |group_rev| { - group_rev.visible = false; - })?; - Ok(()) - } + // Update the memory cache of the groups + all_group_revs.into_iter().for_each(|group_rev| { + let filter_content = filter_content_map + .get(&group_rev.id) + .cloned() + .unwrap_or_else(|| "".to_owned()); + let group = Group::new( + group_rev.id, + self.field_rev.id.clone(), + group_rev.name, + filter_content, + ); + self.groups_map.insert(group.id.clone(), group); + }); - #[allow(dead_code)] - pub(crate) async fn show_group(&mut self, group_id: &str) -> FlowyResult<()> { - self.mut_group_rev(group_id, |group_rev| { - group_rev.visible = true; - })?; - Ok(()) - } + let initial_groups = new_group_revs + .into_iter() + .flat_map(|group_rev| { + let filter_content = filter_content_map.get(&group_rev.id)?; + let group = Group::new( + group_rev.id, + self.field_rev.id.clone(), + group_rev.name, + filter_content.clone(), + ); + Some(GroupPB::from(group)) + }) + .collect(); - pub(crate) async fn get_all_cells(&self) -> Vec { - self.reader - .get_configuration_cells(&self.field_rev.id) - .await - .unwrap_or_default() + let changeset = GroupChangesetPB { + view_id: self.view_id.clone(), + initial_groups, + deleted_groups: deleted_group_ids, + update_groups: vec![], + inserted_groups: vec![], + }; + tracing::trace!("Group changeset: {:?}", changeset); + if changeset.is_empty() { + Ok(None) + } else { + Ok(Some(changeset)) } + } - fn mut_configuration( - &mut self, - mut_configuration_fn: impl FnOnce(&mut GroupConfigurationRevision) -> bool, - ) -> FlowyResult<()> { - let configuration = Arc::make_mut(&mut self.configuration); - let is_changed = mut_configuration_fn(configuration); - if is_changed { - let configuration = (*self.configuration).clone(); - let writer = self.writer.clone(); - let field_id = self.field_rev.id.clone(); - let field_type = self.field_rev.ty; - tokio::spawn(async move { - match writer.save_configuration(&field_id, field_type, configuration).await { - Ok(_) => {} - Err(e) => { - tracing::error!("Save group configuration failed: {}", e); - } - } - }); + #[allow(dead_code)] + pub(crate) async fn hide_group(&mut self, group_id: &str) -> FlowyResult<()> { + self.mut_group_rev(group_id, |group_rev| { + group_rev.visible = false; + })?; + Ok(()) + } + + #[allow(dead_code)] + pub(crate) async fn show_group(&mut self, group_id: &str) -> FlowyResult<()> { + self.mut_group_rev(group_id, |group_rev| { + group_rev.visible = true; + })?; + Ok(()) + } + + pub(crate) async fn get_all_cells(&self) -> Vec { + self + .reader + .get_configuration_cells(&self.field_rev.id) + .await + .unwrap_or_default() + } + + fn mut_configuration( + &mut self, + mut_configuration_fn: impl FnOnce(&mut GroupConfigurationRevision) -> bool, + ) -> FlowyResult<()> { + let configuration = Arc::make_mut(&mut self.configuration); + let is_changed = mut_configuration_fn(configuration); + if is_changed { + let configuration = (*self.configuration).clone(); + let writer = self.writer.clone(); + let field_id = self.field_rev.id.clone(); + let field_type = self.field_rev.ty; + tokio::spawn(async move { + match writer + .save_configuration(&field_id, field_type, configuration) + .await + { + Ok(_) => {}, + Err(e) => { + tracing::error!("Save group configuration failed: {}", e); + }, } - Ok(()) + }); } + Ok(()) + } - fn mut_group_rev(&mut self, group_id: &str, mut_groups_fn: impl Fn(&mut GroupRevision)) -> FlowyResult<()> { - self.mut_configuration(|configuration| { - match configuration.groups.iter_mut().find(|group| group.id == group_id) { - None => false, - Some(group_rev) => { - mut_groups_fn(group_rev); - true - } - } - }) - } + fn mut_group_rev( + &mut self, + group_id: &str, + mut_groups_fn: impl Fn(&mut GroupRevision), + ) -> FlowyResult<()> { + self.mut_configuration(|configuration| { + match configuration + .groups + .iter_mut() + .find(|group| group.id == group_id) + { + None => false, + Some(group_rev) => { + mut_groups_fn(group_rev); + true + }, + } + }) + } } /// Merge the new groups into old groups while keeping the order in the old groups /// fn merge_groups( - no_status_group: Option, - old_groups: Vec, - new_groups: Vec, + no_status_group: Option, + old_groups: Vec, + new_groups: Vec, ) -> MergeGroupResult { - let mut merge_result = MergeGroupResult::new(); - // group_map is a helper map is used to filter out the new groups. - let mut new_group_map: IndexMap = IndexMap::new(); - new_groups.into_iter().for_each(|group_rev| { - new_group_map.insert(group_rev.id.clone(), group_rev); - }); + let mut merge_result = MergeGroupResult::new(); + // group_map is a helper map is used to filter out the new groups. + let mut new_group_map: IndexMap = IndexMap::new(); + new_groups.into_iter().for_each(|group_rev| { + new_group_map.insert(group_rev.id.clone(), group_rev); + }); - // The group is ordered in old groups. Add them before adding the new groups - for old in old_groups { - if let Some(new) = new_group_map.remove(&old.id) { - merge_result.all_group_revs.push(new.clone()); - } else { - merge_result.deleted_group_revs.push(old); - } + // The group is ordered in old groups. Add them before adding the new groups + for old in old_groups { + if let Some(new) = new_group_map.remove(&old.id) { + merge_result.all_group_revs.push(new.clone()); + } else { + merge_result.deleted_group_revs.push(old); } + } - // Find out the new groups - let new_groups = new_group_map.into_values(); - for (_, group) in new_groups.into_iter().enumerate() { - merge_result.all_group_revs.push(group.clone()); - merge_result.new_group_revs.push(group); - } + // Find out the new groups + let new_groups = new_group_map.into_values(); + for (_, group) in new_groups.into_iter().enumerate() { + merge_result.all_group_revs.push(group.clone()); + merge_result.new_group_revs.push(group); + } - // The `No status` group index is initialized to 0 - if let Some(no_status_group) = no_status_group { - merge_result.all_group_revs.insert(0, no_status_group); - } - merge_result + // The `No status` group index is initialized to 0 + if let Some(no_status_group) = no_status_group { + merge_result.all_group_revs.insert(0, no_status_group); + } + merge_result } fn is_group_changed(new: &GroupRevision, old: &GroupRevision) -> bool { - if new.name != old.name { - return true; - } - false + if new.name != old.name { + return true; + } + false } struct MergeGroupResult { - // Contains the new groups and the updated groups - all_group_revs: Vec, - new_group_revs: Vec, - deleted_group_revs: Vec, + // Contains the new groups and the updated groups + all_group_revs: Vec, + new_group_revs: Vec, + deleted_group_revs: Vec, } impl MergeGroupResult { - fn new() -> Self { - Self { - all_group_revs: vec![], - new_group_revs: vec![], - deleted_group_revs: vec![], - } + fn new() -> Self { + Self { + all_group_revs: vec![], + new_group_revs: vec![], + deleted_group_revs: vec![], } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller.rs b/frontend/rust-lib/flowy-database/src/services/group/controller.rs index 7320dec2e6..38b9a7f321 100644 --- a/frontend/rust-lib/flowy-database/src/services/group/controller.rs +++ b/frontend/rust-lib/flowy-database/src/services/group/controller.rs @@ -2,14 +2,14 @@ use crate::entities::{GroupChangesetPB, GroupRowsNotificationPB, InsertedRowPB, use crate::services::cell::{get_type_cell_protobuf, CellProtobufBlobParser, DecodedCellData}; use crate::services::group::action::{ - DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerActions, GroupCustomize, + DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerActions, GroupCustomize, }; use crate::services::group::configuration::GroupContext; use crate::services::group::entities::Group; use flowy_error::FlowyResult; use grid_model::{ - CellRevision, FieldRevision, GroupConfigurationContentSerde, GroupRevision, RowChangeset, RowRevision, - TypeOptionDataDeserializer, + CellRevision, FieldRevision, GroupConfigurationContentSerde, GroupRevision, RowChangeset, + RowRevision, TypeOptionDataDeserializer, }; use std::marker::PhantomData; use std::sync::Arc; @@ -23,317 +23,338 @@ use std::sync::Arc; /// be used. /// pub trait GroupController: GroupControllerActions + Send + Sync { - fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str); - fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str); + fn will_create_row( + &mut self, + row_rev: &mut RowRevision, + field_rev: &FieldRevision, + group_id: &str, + ); + fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str); } /// The [GroupGenerator] trait is used to generate the groups for different [FieldType] pub trait GroupGenerator { - type Context; - type TypeOptionType; + type Context; + type TypeOptionType; - fn generate_groups( - field_rev: &FieldRevision, - group_ctx: &Self::Context, - type_option: &Option, - ) -> GeneratedGroupContext; + fn generate_groups( + field_rev: &FieldRevision, + group_ctx: &Self::Context, + type_option: &Option, + ) -> GeneratedGroupContext; } pub struct GeneratedGroupContext { - pub no_status_group: Option, - pub group_configs: Vec, + pub no_status_group: Option, + pub group_configs: Vec, } pub struct GeneratedGroupConfig { - pub group_rev: GroupRevision, - pub filter_content: String, + pub group_rev: GroupRevision, + pub filter_content: String, } pub struct MoveGroupRowContext<'a> { - pub row_rev: &'a RowRevision, - pub row_changeset: &'a mut RowChangeset, - pub field_rev: &'a FieldRevision, - pub to_group_id: &'a str, - pub to_row_id: Option, + pub row_rev: &'a RowRevision, + pub row_changeset: &'a mut RowChangeset, + pub field_rev: &'a FieldRevision, + pub to_group_id: &'a str, + pub to_row_id: Option, } /// C: represents the group configuration that impl [GroupConfigurationSerde] /// T: the type-option data deserializer that impl [TypeOptionDataDeserializer] /// G: the group generator, [GroupGenerator] /// P: the parser that impl [CellProtobufBlobParser] for the CellBytes pub struct GenericGroupController { - pub field_id: String, - pub type_option: Option, - pub group_ctx: GroupContext, - group_action_phantom: PhantomData, - cell_parser_phantom: PhantomData

, + pub field_id: String, + pub type_option: Option, + pub group_ctx: GroupContext, + group_action_phantom: PhantomData, + cell_parser_phantom: PhantomData

, } impl GenericGroupController where - C: GroupConfigurationContentSerde, - T: TypeOptionDataDeserializer, - G: GroupGenerator, TypeOptionType = T>, + C: GroupConfigurationContentSerde, + T: TypeOptionDataDeserializer, + G: GroupGenerator, TypeOptionType = T>, { - pub async fn new(field_rev: &Arc, mut configuration: GroupContext) -> FlowyResult { - let type_option = field_rev.get_type_option::(field_rev.ty); - let generated_group_context = G::generate_groups(field_rev, &configuration, &type_option); - let _ = configuration.init_groups(generated_group_context)?; + pub async fn new( + field_rev: &Arc, + mut configuration: GroupContext, + ) -> FlowyResult { + let type_option = field_rev.get_type_option::(field_rev.ty); + let generated_group_context = G::generate_groups(field_rev, &configuration, &type_option); + let _ = configuration.init_groups(generated_group_context)?; - Ok(Self { - field_id: field_rev.id.clone(), - type_option, - group_ctx: configuration, - group_action_phantom: PhantomData, - cell_parser_phantom: PhantomData, - }) + Ok(Self { + field_id: field_rev.id.clone(), + type_option, + group_ctx: configuration, + group_action_phantom: PhantomData, + cell_parser_phantom: PhantomData, + }) + } + + // https://stackoverflow.com/questions/69413164/how-to-fix-this-clippy-warning-needless-collect + #[allow(clippy::needless_collect)] + fn update_no_status_group( + &mut self, + row_rev: &RowRevision, + other_group_changesets: &[GroupRowsNotificationPB], + ) -> Option { + let no_status_group = self.group_ctx.get_mut_no_status_group()?; + + // [other_group_inserted_row] contains all the inserted rows except the default group. + let other_group_inserted_row = other_group_changesets + .iter() + .flat_map(|changeset| &changeset.inserted_rows) + .collect::>(); + + // Calculate the inserted_rows of the default_group + let no_status_group_rows = other_group_changesets + .iter() + .flat_map(|changeset| &changeset.deleted_rows) + .cloned() + .filter(|row_id| { + // if the [other_group_inserted_row] contains the row_id of the row + // which means the row should not move to the default group. + !other_group_inserted_row + .iter() + .any(|inserted_row| &inserted_row.row.id == row_id) + }) + .collect::>(); + + let mut changeset = GroupRowsNotificationPB::new(no_status_group.id.clone()); + if !no_status_group_rows.is_empty() { + changeset + .inserted_rows + .push(InsertedRowPB::new(row_rev.into())); + no_status_group.add_row(row_rev.into()); } - // https://stackoverflow.com/questions/69413164/how-to-fix-this-clippy-warning-needless-collect - #[allow(clippy::needless_collect)] - fn update_no_status_group( - &mut self, - row_rev: &RowRevision, - other_group_changesets: &[GroupRowsNotificationPB], - ) -> Option { - let no_status_group = self.group_ctx.get_mut_no_status_group()?; + // [other_group_delete_rows] contains all the deleted rows except the default group. + let other_group_delete_rows: Vec = other_group_changesets + .iter() + .flat_map(|changeset| &changeset.deleted_rows) + .cloned() + .collect(); - // [other_group_inserted_row] contains all the inserted rows except the default group. - let other_group_inserted_row = other_group_changesets - .iter() - .flat_map(|changeset| &changeset.inserted_rows) - .collect::>(); + let default_group_deleted_rows = other_group_changesets + .iter() + .flat_map(|changeset| &changeset.inserted_rows) + .filter(|inserted_row| { + // if the [other_group_delete_rows] contain the inserted_row, which means this row should move + // out from the default_group. + let inserted_row_id = &inserted_row.row.id; + !other_group_delete_rows + .iter() + .any(|row_id| inserted_row_id == row_id) + }) + .collect::>(); - // Calculate the inserted_rows of the default_group - let no_status_group_rows = other_group_changesets - .iter() - .flat_map(|changeset| &changeset.deleted_rows) - .cloned() - .filter(|row_id| { - // if the [other_group_inserted_row] contains the row_id of the row - // which means the row should not move to the default group. - !other_group_inserted_row - .iter() - .any(|inserted_row| &inserted_row.row.id == row_id) - }) - .collect::>(); - - let mut changeset = GroupRowsNotificationPB::new(no_status_group.id.clone()); - if !no_status_group_rows.is_empty() { - changeset.inserted_rows.push(InsertedRowPB::new(row_rev.into())); - no_status_group.add_row(row_rev.into()); - } - - // [other_group_delete_rows] contains all the deleted rows except the default group. - let other_group_delete_rows: Vec = other_group_changesets - .iter() - .flat_map(|changeset| &changeset.deleted_rows) - .cloned() - .collect(); - - let default_group_deleted_rows = other_group_changesets - .iter() - .flat_map(|changeset| &changeset.inserted_rows) - .filter(|inserted_row| { - // if the [other_group_delete_rows] contain the inserted_row, which means this row should move - // out from the default_group. - let inserted_row_id = &inserted_row.row.id; - !other_group_delete_rows.iter().any(|row_id| inserted_row_id == row_id) - }) - .collect::>(); - - let mut deleted_row_ids = vec![]; - for row in &no_status_group.rows { - if default_group_deleted_rows - .iter() - .any(|deleted_row| deleted_row.row.id == row.id) - { - deleted_row_ids.push(row.id.clone()); - } - } - no_status_group.rows.retain(|row| !deleted_row_ids.contains(&row.id)); - changeset.deleted_rows.extend(deleted_row_ids); - Some(changeset) + let mut deleted_row_ids = vec![]; + for row in &no_status_group.rows { + if default_group_deleted_rows + .iter() + .any(|deleted_row| deleted_row.row.id == row.id) + { + deleted_row_ids.push(row.id.clone()); + } } + no_status_group + .rows + .retain(|row| !deleted_row_ids.contains(&row.id)); + changeset.deleted_rows.extend(deleted_row_ids); + Some(changeset) + } } impl GroupControllerActions for GenericGroupController where - P: CellProtobufBlobParser, - C: GroupConfigurationContentSerde, - T: TypeOptionDataDeserializer, - G: GroupGenerator, TypeOptionType = T>, + P: CellProtobufBlobParser, + C: GroupConfigurationContentSerde, + T: TypeOptionDataDeserializer, + G: GroupGenerator, TypeOptionType = T>, - Self: GroupCustomize, + Self: GroupCustomize, { - fn field_id(&self) -> &str { - &self.field_id - } + fn field_id(&self) -> &str { + &self.field_id + } - fn groups(&self) -> Vec<&Group> { - self.group_ctx.groups() - } + fn groups(&self) -> Vec<&Group> { + self.group_ctx.groups() + } - fn get_group(&self, group_id: &str) -> Option<(usize, Group)> { - let group = self.group_ctx.get_group(group_id)?; - Some((group.0, group.1.clone())) - } + fn get_group(&self, group_id: &str) -> Option<(usize, Group)> { + let group = self.group_ctx.get_group(group_id)?; + Some((group.0, group.1.clone())) + } - #[tracing::instrument(level = "trace", skip_all, fields(row_count=%row_revs.len(), group_result))] - fn fill_groups(&mut self, row_revs: &[Arc], field_rev: &FieldRevision) -> FlowyResult<()> { - for row_rev in row_revs { - let cell_rev = match row_rev.cells.get(&self.field_id) { - None => self.placeholder_cell(), - Some(cell_rev) => Some(cell_rev.clone()), - }; + #[tracing::instrument(level = "trace", skip_all, fields(row_count=%row_revs.len(), group_result))] + fn fill_groups( + &mut self, + row_revs: &[Arc], + field_rev: &FieldRevision, + ) -> FlowyResult<()> { + for row_rev in row_revs { + let cell_rev = match row_rev.cells.get(&self.field_id) { + None => self.placeholder_cell(), + Some(cell_rev) => Some(cell_rev.clone()), + }; - if let Some(cell_rev) = cell_rev { - let mut grouped_rows: Vec = vec![]; - let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data, field_rev, None).1; - let cell_data = cell_bytes.parser::

()?; - for group in self.group_ctx.groups() { - if self.can_group(&group.filter_content, &cell_data) { - grouped_rows.push(GroupedRow { - row: row_rev.into(), - group_id: group.id.clone(), - }); - } - } - - if !grouped_rows.is_empty() { - for group_row in grouped_rows { - if let Some(group) = self.group_ctx.get_mut_group(&group_row.group_id) { - group.add_row(group_row.row); - } - } - continue; - } - } - match self.group_ctx.get_mut_no_status_group() { - None => {} - Some(no_status_group) => no_status_group.add_row(row_rev.into()), - } + if let Some(cell_rev) = cell_rev { + let mut grouped_rows: Vec = vec![]; + let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data, field_rev, None).1; + let cell_data = cell_bytes.parser::

()?; + for group in self.group_ctx.groups() { + if self.can_group(&group.filter_content, &cell_data) { + grouped_rows.push(GroupedRow { + row: row_rev.into(), + group_id: group.id.clone(), + }); + } } - tracing::Span::current().record("group_result", format!("{},", self.group_ctx,).as_str()); - Ok(()) - } - - fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()> { - self.group_ctx.move_group(from_group_id, to_group_id) - } - - fn did_update_group_row( - &mut self, - old_row_rev: &Option>, - row_rev: &RowRevision, - field_rev: &FieldRevision, - ) -> FlowyResult { - // let cell_data = row_rev.cells.get(&self.field_id).and_then(|cell_rev| { - // let cell_data: Option

= get_type_cell_data(cell_rev, field_rev, None); - // cell_data - // }); - let mut result = DidUpdateGroupRowResult { - inserted_group: None, - deleted_group: None, - row_changesets: vec![], - }; - - if let Some(cell_data) = get_cell_data_from_row_rev::

(Some(row_rev), field_rev) { - let old_row_rev = old_row_rev.as_ref().map(|old| old.as_ref()); - let old_cell_data = get_cell_data_from_row_rev::

(old_row_rev, field_rev); - if let Ok((insert, delete)) = - self.create_or_delete_group_when_cell_changed(row_rev, old_cell_data.as_ref(), &cell_data) - { - result.inserted_group = insert; - result.deleted_group = delete; + if !grouped_rows.is_empty() { + for group_row in grouped_rows { + if let Some(group) = self.group_ctx.get_mut_group(&group_row.group_id) { + group.add_row(group_row.row); } - - let mut changesets = self.add_or_remove_row_when_cell_changed(row_rev, &cell_data); - if let Some(changeset) = self.update_no_status_group(row_rev, &changesets) { - if !changeset.is_empty() { - changesets.push(changeset); - } - } - result.row_changesets = changesets; + } + continue; } - - Ok(result) + } + match self.group_ctx.get_mut_no_status_group() { + None => {}, + Some(no_status_group) => no_status_group.add_row(row_rev.into()), + } } - fn did_delete_delete_row( - &mut self, - row_rev: &RowRevision, - field_rev: &FieldRevision, - ) -> FlowyResult { - // if the cell_rev is none, then the row must in the default group. - let mut result = DidMoveGroupRowResult { - deleted_group: None, - row_changesets: vec![], - }; - if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { - let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data.clone(), field_rev, None).1; - let cell_data = cell_bytes.parser::

()?; - if !cell_data.is_empty() { - tracing::error!("did_delete_delete_row {:?}", cell_rev.type_cell_data); - result.row_changesets = self.delete_row(row_rev, &cell_data); - return Ok(result); - } + tracing::Span::current().record("group_result", format!("{},", self.group_ctx,).as_str()); + Ok(()) + } + + fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()> { + self.group_ctx.move_group(from_group_id, to_group_id) + } + + fn did_update_group_row( + &mut self, + old_row_rev: &Option>, + row_rev: &RowRevision, + field_rev: &FieldRevision, + ) -> FlowyResult { + // let cell_data = row_rev.cells.get(&self.field_id).and_then(|cell_rev| { + // let cell_data: Option

= get_type_cell_data(cell_rev, field_rev, None); + // cell_data + // }); + let mut result = DidUpdateGroupRowResult { + inserted_group: None, + deleted_group: None, + row_changesets: vec![], + }; + + if let Some(cell_data) = get_cell_data_from_row_rev::

(Some(row_rev), field_rev) { + let old_row_rev = old_row_rev.as_ref().map(|old| old.as_ref()); + let old_cell_data = get_cell_data_from_row_rev::

(old_row_rev, field_rev); + if let Ok((insert, delete)) = + self.create_or_delete_group_when_cell_changed(row_rev, old_cell_data.as_ref(), &cell_data) + { + result.inserted_group = insert; + result.deleted_group = delete; + } + + let mut changesets = self.add_or_remove_row_when_cell_changed(row_rev, &cell_data); + if let Some(changeset) = self.update_no_status_group(row_rev, &changesets) { + if !changeset.is_empty() { + changesets.push(changeset); } + } + result.row_changesets = changesets; + } - match self.group_ctx.get_no_status_group() { - None => { - tracing::error!("Unexpected None value. It should have the no status group"); - } - Some(no_status_group) => { - if !no_status_group.contains_row(&row_rev.id) { - tracing::error!("The row: {} should be in the no status group", row_rev.id); - } - result.row_changesets = vec![GroupRowsNotificationPB::delete( - no_status_group.id.clone(), - vec![row_rev.id.clone()], - )]; - } + Ok(result) + } + + fn did_delete_delete_row( + &mut self, + row_rev: &RowRevision, + field_rev: &FieldRevision, + ) -> FlowyResult { + // if the cell_rev is none, then the row must in the default group. + let mut result = DidMoveGroupRowResult { + deleted_group: None, + row_changesets: vec![], + }; + if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { + let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data.clone(), field_rev, None).1; + let cell_data = cell_bytes.parser::

()?; + if !cell_data.is_empty() { + tracing::error!("did_delete_delete_row {:?}", cell_rev.type_cell_data); + result.row_changesets = self.delete_row(row_rev, &cell_data); + return Ok(result); + } + } + + match self.group_ctx.get_no_status_group() { + None => { + tracing::error!("Unexpected None value. It should have the no status group"); + }, + Some(no_status_group) => { + if !no_status_group.contains_row(&row_rev.id) { + tracing::error!("The row: {} should be in the no status group", row_rev.id); } - Ok(result) + result.row_changesets = vec![GroupRowsNotificationPB::delete( + no_status_group.id.clone(), + vec![row_rev.id.clone()], + )]; + }, } + Ok(result) + } - #[tracing::instrument(level = "trace", skip_all, err)] - fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult { - let mut result = DidMoveGroupRowResult { - deleted_group: None, - row_changesets: vec![], - }; - let cell_rev = match context.row_rev.cells.get(&self.field_id) { - Some(cell_rev) => Some(cell_rev.clone()), - None => self.placeholder_cell(), - }; + #[tracing::instrument(level = "trace", skip_all, err)] + fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult { + let mut result = DidMoveGroupRowResult { + deleted_group: None, + row_changesets: vec![], + }; + let cell_rev = match context.row_rev.cells.get(&self.field_id) { + Some(cell_rev) => Some(cell_rev.clone()), + None => self.placeholder_cell(), + }; - if let Some(cell_rev) = cell_rev { - let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data, context.field_rev, None).1; - let cell_data = cell_bytes.parser::

()?; - result.deleted_group = self.delete_group_when_move_row(context.row_rev, &cell_data); - result.row_changesets = self.move_row(&cell_data, context); - } else { - tracing::warn!("Unexpected moving group row, changes should not be empty"); - } - Ok(result) + if let Some(cell_rev) = cell_rev { + let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data, context.field_rev, None).1; + let cell_data = cell_bytes.parser::

()?; + result.deleted_group = self.delete_group_when_move_row(context.row_rev, &cell_data); + result.row_changesets = self.move_row(&cell_data, context); + } else { + tracing::warn!("Unexpected moving group row, changes should not be empty"); } + Ok(result) + } - fn did_update_group_field(&mut self, _field_rev: &FieldRevision) -> FlowyResult> { - Ok(None) - } + fn did_update_group_field( + &mut self, + _field_rev: &FieldRevision, + ) -> FlowyResult> { + Ok(None) + } } struct GroupedRow { - row: RowPB, - group_id: String, + row: RowPB, + group_id: String, } fn get_cell_data_from_row_rev( - row_rev: Option<&RowRevision>, - field_rev: &FieldRevision, + row_rev: Option<&RowRevision>, + field_rev: &FieldRevision, ) -> Option { - let cell_rev: &CellRevision = row_rev.and_then(|row_rev| row_rev.cells.get(&field_rev.id))?; - let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data.clone(), field_rev, None).1; - cell_bytes.parser::

().ok() + let cell_rev: &CellRevision = row_rev.and_then(|row_rev| row_rev.cells.get(&field_rev.id))?; + let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data.clone(), field_rev, None).1; + cell_bytes.parser::

().ok() } diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/checkbox_controller.rs b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/checkbox_controller.rs index e707b8048f..cc9076215a 100644 --- a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/checkbox_controller.rs +++ b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/checkbox_controller.rs @@ -1,157 +1,174 @@ use crate::entities::{GroupRowsNotificationPB, InsertedRowPB, RowPB}; -use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK}; +use crate::services::field::{ + CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK, +}; use crate::services::group::action::GroupCustomize; use crate::services::group::configuration::GroupContext; use crate::services::group::controller::{ - GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, + GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, }; use crate::services::cell::insert_checkbox_cell; use crate::services::group::{move_group_row, GeneratedGroupConfig, GeneratedGroupContext}; -use grid_model::{CellRevision, CheckboxGroupConfigurationRevision, FieldRevision, GroupRevision, RowRevision}; +use grid_model::{ + CellRevision, CheckboxGroupConfigurationRevision, FieldRevision, GroupRevision, RowRevision, +}; pub type CheckboxGroupController = GenericGroupController< - CheckboxGroupConfigurationRevision, - CheckboxTypeOptionPB, - CheckboxGroupGenerator, - CheckboxCellDataParser, + CheckboxGroupConfigurationRevision, + CheckboxTypeOptionPB, + CheckboxGroupGenerator, + CheckboxCellDataParser, >; pub type CheckboxGroupContext = GroupContext; impl GroupCustomize for CheckboxGroupController { - type CellData = CheckboxCellData; - fn placeholder_cell(&self) -> Option { - Some(CellRevision::new(UNCHECK.to_string())) - } + type CellData = CheckboxCellData; + fn placeholder_cell(&self) -> Option { + Some(CellRevision::new(UNCHECK.to_string())) + } - fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool { - if cell_data.is_check() { - content == CHECK + fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool { + if cell_data.is_check() { + content == CHECK + } else { + content == UNCHECK + } + } + + fn add_or_remove_row_when_cell_changed( + &mut self, + row_rev: &RowRevision, + cell_data: &Self::CellData, + ) -> Vec { + let mut changesets = vec![]; + self.group_ctx.iter_mut_status_groups(|group| { + let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); + let is_not_contained = !group.contains_row(&row_rev.id); + if group.id == CHECK { + if cell_data.is_uncheck() { + // Remove the row if the group.id is CHECK but the cell_data is UNCHECK + changeset.deleted_rows.push(row_rev.id.clone()); + group.remove_row(&row_rev.id); } else { - content == UNCHECK + // Add the row to the group if the group didn't contain the row + if is_not_contained { + let row_pb = RowPB::from(row_rev); + changeset + .inserted_rows + .push(InsertedRowPB::new(row_pb.clone())); + group.add_row(row_pb); + } } - } + } - fn add_or_remove_row_when_cell_changed( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellData, - ) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_status_groups(|group| { - let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); - let is_not_contained = !group.contains_row(&row_rev.id); - if group.id == CHECK { - if cell_data.is_uncheck() { - // Remove the row if the group.id is CHECK but the cell_data is UNCHECK - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); - } else { - // Add the row to the group if the group didn't contain the row - if is_not_contained { - let row_pb = RowPB::from(row_rev); - changeset.inserted_rows.push(InsertedRowPB::new(row_pb.clone())); - group.add_row(row_pb); - } - } - } + if group.id == UNCHECK { + if cell_data.is_check() { + // Remove the row if the group.id is UNCHECK but the cell_data is CHECK + changeset.deleted_rows.push(row_rev.id.clone()); + group.remove_row(&row_rev.id); + } else { + // Add the row to the group if the group didn't contain the row + if is_not_contained { + let row_pb = RowPB::from(row_rev); + changeset + .inserted_rows + .push(InsertedRowPB::new(row_pb.clone())); + group.add_row(row_pb); + } + } + } - if group.id == UNCHECK { - if cell_data.is_check() { - // Remove the row if the group.id is UNCHECK but the cell_data is CHECK - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); - } else { - // Add the row to the group if the group didn't contain the row - if is_not_contained { - let row_pb = RowPB::from(row_rev); - changeset.inserted_rows.push(InsertedRowPB::new(row_pb.clone())); - group.add_row(row_pb); - } - } - } + if !changeset.is_empty() { + changesets.push(changeset); + } + }); + changesets + } - if !changeset.is_empty() { - changesets.push(changeset); - } - }); - changesets - } + fn delete_row( + &mut self, + row_rev: &RowRevision, + _cell_data: &Self::CellData, + ) -> Vec { + let mut changesets = vec![]; + self.group_ctx.iter_mut_groups(|group| { + let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); + if group.contains_row(&row_rev.id) { + changeset.deleted_rows.push(row_rev.id.clone()); + group.remove_row(&row_rev.id); + } - fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellData) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_groups(|group| { - let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); - if group.contains_row(&row_rev.id) { - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); - } + if !changeset.is_empty() { + changesets.push(changeset); + } + }); + changesets + } - if !changeset.is_empty() { - changesets.push(changeset); - } - }); - changesets - } - - fn move_row( - &mut self, - _cell_data: &Self::CellData, - mut context: MoveGroupRowContext, - ) -> Vec { - let mut group_changeset = vec![]; - self.group_ctx.iter_mut_groups(|group| { - if let Some(changeset) = move_group_row(group, &mut context) { - group_changeset.push(changeset); - } - }); - group_changeset - } + fn move_row( + &mut self, + _cell_data: &Self::CellData, + mut context: MoveGroupRowContext, + ) -> Vec { + let mut group_changeset = vec![]; + self.group_ctx.iter_mut_groups(|group| { + if let Some(changeset) = move_group_row(group, &mut context) { + group_changeset.push(changeset); + } + }); + group_changeset + } } impl GroupController for CheckboxGroupController { - fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) { - match self.group_ctx.get_group(group_id) { - None => tracing::warn!("Can not find the group: {}", group_id), - Some((_, group)) => { - let is_check = group.id == CHECK; - let cell_rev = insert_checkbox_cell(is_check, field_rev); - row_rev.cells.insert(field_rev.id.clone(), cell_rev); - } - } + fn will_create_row( + &mut self, + row_rev: &mut RowRevision, + field_rev: &FieldRevision, + group_id: &str, + ) { + match self.group_ctx.get_group(group_id) { + None => tracing::warn!("Can not find the group: {}", group_id), + Some((_, group)) => { + let is_check = group.id == CHECK; + let cell_rev = insert_checkbox_cell(is_check, field_rev); + row_rev.cells.insert(field_rev.id.clone(), cell_rev); + }, } + } - fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str) { - if let Some(group) = self.group_ctx.get_mut_group(group_id) { - group.add_row(row_pb.clone()) - } + fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str) { + if let Some(group) = self.group_ctx.get_mut_group(group_id) { + group.add_row(row_pb.clone()) } + } } pub struct CheckboxGroupGenerator(); impl GroupGenerator for CheckboxGroupGenerator { - type Context = CheckboxGroupContext; - type TypeOptionType = CheckboxTypeOptionPB; + type Context = CheckboxGroupContext; + type TypeOptionType = CheckboxTypeOptionPB; - fn generate_groups( - _field_rev: &FieldRevision, - _group_ctx: &Self::Context, - _type_option: &Option, - ) -> GeneratedGroupContext { - let check_group = GeneratedGroupConfig { - group_rev: GroupRevision::new(CHECK.to_string(), "".to_string()), - filter_content: CHECK.to_string(), - }; + fn generate_groups( + _field_rev: &FieldRevision, + _group_ctx: &Self::Context, + _type_option: &Option, + ) -> GeneratedGroupContext { + let check_group = GeneratedGroupConfig { + group_rev: GroupRevision::new(CHECK.to_string(), "".to_string()), + filter_content: CHECK.to_string(), + }; - let uncheck_group = GeneratedGroupConfig { - group_rev: GroupRevision::new(UNCHECK.to_string(), "".to_string()), - filter_content: UNCHECK.to_string(), - }; + let uncheck_group = GeneratedGroupConfig { + group_rev: GroupRevision::new(UNCHECK.to_string(), "".to_string()), + filter_content: UNCHECK.to_string(), + }; - GeneratedGroupContext { - no_status_group: None, - group_configs: vec![check_group, uncheck_group], - } + GeneratedGroupContext { + no_status_group: None, + group_configs: vec![check_group, uncheck_group], } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/default_controller.rs b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/default_controller.rs index cece2ac520..7a793d67e7 100644 --- a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/default_controller.rs +++ b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/default_controller.rs @@ -1,5 +1,7 @@ use crate::entities::{GroupChangesetPB, RowPB}; -use crate::services::group::action::{DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerActions}; +use crate::services::group::action::{ + DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerActions, +}; use crate::services::group::{Group, GroupController, MoveGroupRowContext}; use flowy_error::FlowyResult; use grid_model::{FieldRevision, RowRevision}; @@ -10,89 +12,105 @@ use std::sync::Arc; /// means all rows will be grouped in the same group. /// pub struct DefaultGroupController { - pub field_id: String, - pub group: Group, + pub field_id: String, + pub group: Group, } const DEFAULT_GROUP_CONTROLLER: &str = "DefaultGroupController"; impl DefaultGroupController { - pub fn new(field_rev: &Arc) -> Self { - let group = Group::new( - DEFAULT_GROUP_CONTROLLER.to_owned(), - field_rev.id.clone(), - "".to_owned(), - "".to_owned(), - ); - Self { - field_id: field_rev.id.clone(), - group, - } + pub fn new(field_rev: &Arc) -> Self { + let group = Group::new( + DEFAULT_GROUP_CONTROLLER.to_owned(), + field_rev.id.clone(), + "".to_owned(), + "".to_owned(), + ); + Self { + field_id: field_rev.id.clone(), + group, } + } } impl GroupControllerActions for DefaultGroupController { - fn field_id(&self) -> &str { - &self.field_id - } + fn field_id(&self) -> &str { + &self.field_id + } - fn groups(&self) -> Vec<&Group> { - vec![&self.group] - } + fn groups(&self) -> Vec<&Group> { + vec![&self.group] + } - fn get_group(&self, _group_id: &str) -> Option<(usize, Group)> { - Some((0, self.group.clone())) - } + fn get_group(&self, _group_id: &str) -> Option<(usize, Group)> { + Some((0, self.group.clone())) + } - fn fill_groups(&mut self, row_revs: &[Arc], _field_rev: &FieldRevision) -> FlowyResult<()> { - row_revs.iter().for_each(|row_rev| { - self.group.add_row(RowPB::from(row_rev)); - }); - Ok(()) - } + fn fill_groups( + &mut self, + row_revs: &[Arc], + _field_rev: &FieldRevision, + ) -> FlowyResult<()> { + row_revs.iter().for_each(|row_rev| { + self.group.add_row(RowPB::from(row_rev)); + }); + Ok(()) + } - fn move_group(&mut self, _from_group_id: &str, _to_group_id: &str) -> FlowyResult<()> { - Ok(()) - } + fn move_group(&mut self, _from_group_id: &str, _to_group_id: &str) -> FlowyResult<()> { + Ok(()) + } - fn did_update_group_row( - &mut self, - _old_row_rev: &Option>, - _row_rev: &RowRevision, - _field_rev: &FieldRevision, - ) -> FlowyResult { - Ok(DidUpdateGroupRowResult { - inserted_group: None, - deleted_group: None, - row_changesets: vec![], - }) - } + fn did_update_group_row( + &mut self, + _old_row_rev: &Option>, + _row_rev: &RowRevision, + _field_rev: &FieldRevision, + ) -> FlowyResult { + Ok(DidUpdateGroupRowResult { + inserted_group: None, + deleted_group: None, + row_changesets: vec![], + }) + } - fn did_delete_delete_row( - &mut self, - _row_rev: &RowRevision, - _field_rev: &FieldRevision, - ) -> FlowyResult { - Ok(DidMoveGroupRowResult { - deleted_group: None, - row_changesets: vec![], - }) - } + fn did_delete_delete_row( + &mut self, + _row_rev: &RowRevision, + _field_rev: &FieldRevision, + ) -> FlowyResult { + Ok(DidMoveGroupRowResult { + deleted_group: None, + row_changesets: vec![], + }) + } - fn move_group_row(&mut self, _context: MoveGroupRowContext) -> FlowyResult { - Ok(DidMoveGroupRowResult { - deleted_group: None, - row_changesets: vec![], - }) - } + fn move_group_row( + &mut self, + _context: MoveGroupRowContext, + ) -> FlowyResult { + Ok(DidMoveGroupRowResult { + deleted_group: None, + row_changesets: vec![], + }) + } - fn did_update_group_field(&mut self, _field_rev: &FieldRevision) -> FlowyResult> { - Ok(None) - } + fn did_update_group_field( + &mut self, + _field_rev: &FieldRevision, + ) -> FlowyResult> { + Ok(None) + } } impl GroupController for DefaultGroupController { - fn will_create_row(&mut self, _row_rev: &mut RowRevision, _field_rev: &FieldRevision, _group_id: &str) {} + fn will_create_row( + &mut self, + _row_rev: &mut RowRevision, + _field_rev: &FieldRevision, + _group_id: &str, + ) { + } - fn did_create_row(&mut self, _row_rev: &RowPB, _group_id: &str) {} + fn did_create_row(&mut self, _row_rev: &RowPB, _group_id: &str) {} } diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs index ee18924f68..758e50b764 100644 --- a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs +++ b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs @@ -1,10 +1,12 @@ use crate::entities::{GroupRowsNotificationPB, RowPB}; use crate::services::cell::insert_select_option_cell; -use crate::services::field::{MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser}; +use crate::services::field::{ + MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser, +}; use crate::services::group::action::GroupCustomize; use crate::services::group::controller::{ - GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, + GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, }; use crate::services::group::controller_impls::select_option_controller::util::*; @@ -13,94 +15,108 @@ use grid_model::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevis // MultiSelect pub type MultiSelectGroupController = GenericGroupController< - SelectOptionGroupConfigurationRevision, - MultiSelectTypeOptionPB, - MultiSelectGroupGenerator, - SelectOptionCellDataParser, + SelectOptionGroupConfigurationRevision, + MultiSelectTypeOptionPB, + MultiSelectGroupGenerator, + SelectOptionCellDataParser, >; impl GroupCustomize for MultiSelectGroupController { - type CellData = SelectOptionCellDataPB; + type CellData = SelectOptionCellDataPB; - fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool { - cell_data.select_options.iter().any(|option| option.id == content) - } + fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool { + cell_data + .select_options + .iter() + .any(|option| option.id == content) + } - fn add_or_remove_row_when_cell_changed( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellData, - ) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_status_groups(|group| { - if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) { - changesets.push(changeset); - } - }); - changesets - } + fn add_or_remove_row_when_cell_changed( + &mut self, + row_rev: &RowRevision, + cell_data: &Self::CellData, + ) -> Vec { + let mut changesets = vec![]; + self.group_ctx.iter_mut_status_groups(|group| { + if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) { + changesets.push(changeset); + } + }); + changesets + } - fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellData) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_status_groups(|group| { - if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) { - changesets.push(changeset); - } - }); - changesets - } + fn delete_row( + &mut self, + row_rev: &RowRevision, + cell_data: &Self::CellData, + ) -> Vec { + let mut changesets = vec![]; + self.group_ctx.iter_mut_status_groups(|group| { + if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) { + changesets.push(changeset); + } + }); + changesets + } - fn move_row( - &mut self, - _cell_data: &Self::CellData, - mut context: MoveGroupRowContext, - ) -> Vec { - let mut group_changeset = vec![]; - self.group_ctx.iter_mut_groups(|group| { - if let Some(changeset) = move_group_row(group, &mut context) { - group_changeset.push(changeset); - } - }); - group_changeset - } + fn move_row( + &mut self, + _cell_data: &Self::CellData, + mut context: MoveGroupRowContext, + ) -> Vec { + let mut group_changeset = vec![]; + self.group_ctx.iter_mut_groups(|group| { + if let Some(changeset) = move_group_row(group, &mut context) { + group_changeset.push(changeset); + } + }); + group_changeset + } } impl GroupController for MultiSelectGroupController { - fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) { - match self.group_ctx.get_group(group_id) { - None => tracing::warn!("Can not find the group: {}", group_id), - Some((_, group)) => { - let cell_rev = insert_select_option_cell(vec![group.id.clone()], field_rev); - row_rev.cells.insert(field_rev.id.clone(), cell_rev); - } - } + fn will_create_row( + &mut self, + row_rev: &mut RowRevision, + field_rev: &FieldRevision, + group_id: &str, + ) { + match self.group_ctx.get_group(group_id) { + None => tracing::warn!("Can not find the group: {}", group_id), + Some((_, group)) => { + let cell_rev = insert_select_option_cell(vec![group.id.clone()], field_rev); + row_rev.cells.insert(field_rev.id.clone(), cell_rev); + }, } + } - fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str) { - if let Some(group) = self.group_ctx.get_mut_group(group_id) { - group.add_row(row_pb.clone()) - } + fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str) { + if let Some(group) = self.group_ctx.get_mut_group(group_id) { + group.add_row(row_pb.clone()) } + } } pub struct MultiSelectGroupGenerator(); impl GroupGenerator for MultiSelectGroupGenerator { - type Context = SelectOptionGroupContext; - type TypeOptionType = MultiSelectTypeOptionPB; + type Context = SelectOptionGroupContext; + type TypeOptionType = MultiSelectTypeOptionPB; - fn generate_groups( - field_rev: &FieldRevision, - group_ctx: &Self::Context, - type_option: &Option, - ) -> GeneratedGroupContext { - let group_configs = match type_option { - None => vec![], - Some(type_option) => generate_select_option_groups(&field_rev.id, group_ctx, &type_option.options), - }; + fn generate_groups( + field_rev: &FieldRevision, + group_ctx: &Self::Context, + type_option: &Option, + ) -> GeneratedGroupContext { + let group_configs = match type_option { + None => vec![], + Some(type_option) => { + generate_select_option_groups(&field_rev.id, group_ctx, &type_option.options) + }, + }; - GeneratedGroupContext { - no_status_group: Some(make_no_status_group(field_rev)), - group_configs, - } + GeneratedGroupContext { + no_status_group: Some(make_no_status_group(field_rev)), + group_configs, } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/single_select_controller.rs b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/single_select_controller.rs index ce469b1221..590b98fc17 100644 --- a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/single_select_controller.rs +++ b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/single_select_controller.rs @@ -1,10 +1,12 @@ use crate::entities::{GroupRowsNotificationPB, RowPB}; use crate::services::cell::insert_select_option_cell; -use crate::services::field::{SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB}; +use crate::services::field::{ + SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB, +}; use crate::services::group::action::GroupCustomize; use crate::services::group::controller::{ - GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, + GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, }; use crate::services::group::controller_impls::select_option_controller::util::*; use crate::services::group::entities::Group; @@ -14,92 +16,106 @@ use grid_model::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevis // SingleSelect pub type SingleSelectGroupController = GenericGroupController< - SelectOptionGroupConfigurationRevision, - SingleSelectTypeOptionPB, - SingleSelectGroupGenerator, - SelectOptionCellDataParser, + SelectOptionGroupConfigurationRevision, + SingleSelectTypeOptionPB, + SingleSelectGroupGenerator, + SelectOptionCellDataParser, >; impl GroupCustomize for SingleSelectGroupController { - type CellData = SelectOptionCellDataPB; - fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool { - cell_data.select_options.iter().any(|option| option.id == content) - } + type CellData = SelectOptionCellDataPB; + fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool { + cell_data + .select_options + .iter() + .any(|option| option.id == content) + } - fn add_or_remove_row_when_cell_changed( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellData, - ) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_status_groups(|group| { - if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) { - changesets.push(changeset); - } - }); - changesets - } + fn add_or_remove_row_when_cell_changed( + &mut self, + row_rev: &RowRevision, + cell_data: &Self::CellData, + ) -> Vec { + let mut changesets = vec![]; + self.group_ctx.iter_mut_status_groups(|group| { + if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) { + changesets.push(changeset); + } + }); + changesets + } - fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellData) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_status_groups(|group| { - if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) { - changesets.push(changeset); - } - }); - changesets - } + fn delete_row( + &mut self, + row_rev: &RowRevision, + cell_data: &Self::CellData, + ) -> Vec { + let mut changesets = vec![]; + self.group_ctx.iter_mut_status_groups(|group| { + if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) { + changesets.push(changeset); + } + }); + changesets + } - fn move_row( - &mut self, - _cell_data: &Self::CellData, - mut context: MoveGroupRowContext, - ) -> Vec { - let mut group_changeset = vec![]; - self.group_ctx.iter_mut_groups(|group| { - if let Some(changeset) = move_group_row(group, &mut context) { - group_changeset.push(changeset); - } - }); - group_changeset - } + fn move_row( + &mut self, + _cell_data: &Self::CellData, + mut context: MoveGroupRowContext, + ) -> Vec { + let mut group_changeset = vec![]; + self.group_ctx.iter_mut_groups(|group| { + if let Some(changeset) = move_group_row(group, &mut context) { + group_changeset.push(changeset); + } + }); + group_changeset + } } impl GroupController for SingleSelectGroupController { - fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) { - let group: Option<&mut Group> = self.group_ctx.get_mut_group(group_id); - match group { - None => {} - Some(group) => { - let cell_rev = insert_select_option_cell(vec![group.id.clone()], field_rev); - row_rev.cells.insert(field_rev.id.clone(), cell_rev); - } - } + fn will_create_row( + &mut self, + row_rev: &mut RowRevision, + field_rev: &FieldRevision, + group_id: &str, + ) { + let group: Option<&mut Group> = self.group_ctx.get_mut_group(group_id); + match group { + None => {}, + Some(group) => { + let cell_rev = insert_select_option_cell(vec![group.id.clone()], field_rev); + row_rev.cells.insert(field_rev.id.clone(), cell_rev); + }, } - fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str) { - if let Some(group) = self.group_ctx.get_mut_group(group_id) { - group.add_row(row_pb.clone()) - } + } + fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str) { + if let Some(group) = self.group_ctx.get_mut_group(group_id) { + group.add_row(row_pb.clone()) } + } } pub struct SingleSelectGroupGenerator(); impl GroupGenerator for SingleSelectGroupGenerator { - type Context = SelectOptionGroupContext; - type TypeOptionType = SingleSelectTypeOptionPB; - fn generate_groups( - field_rev: &FieldRevision, - group_ctx: &Self::Context, - type_option: &Option, - ) -> GeneratedGroupContext { - let group_configs = match type_option { - None => vec![], - Some(type_option) => generate_select_option_groups(&field_rev.id, group_ctx, &type_option.options), - }; + type Context = SelectOptionGroupContext; + type TypeOptionType = SingleSelectTypeOptionPB; + fn generate_groups( + field_rev: &FieldRevision, + group_ctx: &Self::Context, + type_option: &Option, + ) -> GeneratedGroupContext { + let group_configs = match type_option { + None => vec![], + Some(type_option) => { + generate_select_option_groups(&field_rev.id, group_ctx, &type_option.options) + }, + }; - GeneratedGroupContext { - no_status_group: Some(make_no_status_group(field_rev)), - group_configs, - } + GeneratedGroupContext { + no_status_group: Some(make_no_status_group(field_rev)), + group_configs, } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/util.rs b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/util.rs index 51f506dc59..87b22f3ba7 100644 --- a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/util.rs +++ b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/util.rs @@ -4,167 +4,176 @@ use crate::services::field::{SelectOptionCellDataPB, SelectOptionPB, CHECK}; use crate::services::group::configuration::GroupContext; use crate::services::group::controller::MoveGroupRowContext; use crate::services::group::{GeneratedGroupConfig, Group}; -use grid_model::{CellRevision, FieldRevision, GroupRevision, RowRevision, SelectOptionGroupConfigurationRevision}; +use grid_model::{ + CellRevision, FieldRevision, GroupRevision, RowRevision, SelectOptionGroupConfigurationRevision, +}; pub type SelectOptionGroupContext = GroupContext; pub fn add_or_remove_select_option_row( - group: &mut Group, - cell_data: &SelectOptionCellDataPB, - row_rev: &RowRevision, + group: &mut Group, + cell_data: &SelectOptionCellDataPB, + row_rev: &RowRevision, ) -> Option { - let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); - if cell_data.select_options.is_empty() { - if group.contains_row(&row_rev.id) { - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); + let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); + if cell_data.select_options.is_empty() { + if group.contains_row(&row_rev.id) { + changeset.deleted_rows.push(row_rev.id.clone()); + group.remove_row(&row_rev.id); + } + } else { + cell_data.select_options.iter().for_each(|option| { + if option.id == group.id { + if !group.contains_row(&row_rev.id) { + let row_pb = RowPB::from(row_rev); + changeset + .inserted_rows + .push(InsertedRowPB::new(row_pb.clone())); + group.add_row(row_pb); } - } else { - cell_data.select_options.iter().for_each(|option| { - if option.id == group.id { - if !group.contains_row(&row_rev.id) { - let row_pb = RowPB::from(row_rev); - changeset.inserted_rows.push(InsertedRowPB::new(row_pb.clone())); - group.add_row(row_pb); - } - } else if group.contains_row(&row_rev.id) { - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); - } - }); - } + } else if group.contains_row(&row_rev.id) { + changeset.deleted_rows.push(row_rev.id.clone()); + group.remove_row(&row_rev.id); + } + }); + } - if changeset.is_empty() { - None - } else { - Some(changeset) - } + if changeset.is_empty() { + None + } else { + Some(changeset) + } } pub fn remove_select_option_row( - group: &mut Group, - cell_data: &SelectOptionCellDataPB, - row_rev: &RowRevision, + group: &mut Group, + cell_data: &SelectOptionCellDataPB, + row_rev: &RowRevision, ) -> Option { - let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); - cell_data.select_options.iter().for_each(|option| { - if option.id == group.id && group.contains_row(&row_rev.id) { - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); - } - }); - - if changeset.is_empty() { - None - } else { - Some(changeset) + let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); + cell_data.select_options.iter().for_each(|option| { + if option.id == group.id && group.contains_row(&row_rev.id) { + changeset.deleted_rows.push(row_rev.id.clone()); + group.remove_row(&row_rev.id); } + }); + + if changeset.is_empty() { + None + } else { + Some(changeset) + } } -pub fn move_group_row(group: &mut Group, context: &mut MoveGroupRowContext) -> Option { - let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); - let MoveGroupRowContext { - row_rev, - row_changeset, - field_rev, - to_group_id, - to_row_id, - } = context; +pub fn move_group_row( + group: &mut Group, + context: &mut MoveGroupRowContext, +) -> Option { + let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); + let MoveGroupRowContext { + row_rev, + row_changeset, + field_rev, + to_group_id, + to_row_id, + } = context; - let from_index = group.index_of_row(&row_rev.id); - let to_index = match to_row_id { - None => None, - Some(to_row_id) => group.index_of_row(to_row_id), - }; + let from_index = group.index_of_row(&row_rev.id); + let to_index = match to_row_id { + None => None, + Some(to_row_id) => group.index_of_row(to_row_id), + }; - // Remove the row in which group contains it - if let Some(from_index) = &from_index { - changeset.deleted_rows.push(row_rev.id.clone()); - tracing::debug!("Group:{} remove {} at {}", group.id, row_rev.id, from_index); - group.remove_row(&row_rev.id); - } + // Remove the row in which group contains it + if let Some(from_index) = &from_index { + changeset.deleted_rows.push(row_rev.id.clone()); + tracing::debug!("Group:{} remove {} at {}", group.id, row_rev.id, from_index); + group.remove_row(&row_rev.id); + } - if group.id == *to_group_id { - let row_pb = RowPB::from(*row_rev); - let mut inserted_row = InsertedRowPB::new(row_pb.clone()); - match to_index { - None => { - changeset.inserted_rows.push(inserted_row); - tracing::debug!("Group:{} append row:{}", group.id, row_rev.id); - group.add_row(row_pb); - } - Some(to_index) => { - if to_index < group.number_of_row() { - tracing::debug!("Group:{} insert {} at {} ", group.id, row_rev.id, to_index); - inserted_row.index = Some(to_index as i32); - group.insert_row(to_index, row_pb); - } else { - tracing::warn!("Move to index: {} is out of bounds", to_index); - tracing::debug!("Group:{} append row:{}", group.id, row_rev.id); - group.add_row(row_pb); - } - changeset.inserted_rows.push(inserted_row); - } + if group.id == *to_group_id { + let row_pb = RowPB::from(*row_rev); + let mut inserted_row = InsertedRowPB::new(row_pb.clone()); + match to_index { + None => { + changeset.inserted_rows.push(inserted_row); + tracing::debug!("Group:{} append row:{}", group.id, row_rev.id); + group.add_row(row_pb); + }, + Some(to_index) => { + if to_index < group.number_of_row() { + tracing::debug!("Group:{} insert {} at {} ", group.id, row_rev.id, to_index); + inserted_row.index = Some(to_index as i32); + group.insert_row(to_index, row_pb); + } else { + tracing::warn!("Move to index: {} is out of bounds", to_index); + tracing::debug!("Group:{} append row:{}", group.id, row_rev.id); + group.add_row(row_pb); } + changeset.inserted_rows.push(inserted_row); + }, + } - // Update the corresponding row's cell content. - if from_index.is_none() { - let cell_rev = make_inserted_cell_rev(&group.id, field_rev); - if let Some(cell_rev) = cell_rev { - tracing::debug!( - "Update content of the cell in the row:{} to group:{}", - row_rev.id, - group.id - ); - row_changeset.cell_by_field_id.insert(field_rev.id.clone(), cell_rev); - changeset.updated_rows.push(RowPB::from(*row_rev)); - } - } - } - if changeset.is_empty() { - None - } else { - Some(changeset) + // Update the corresponding row's cell content. + if from_index.is_none() { + let cell_rev = make_inserted_cell_rev(&group.id, field_rev); + if let Some(cell_rev) = cell_rev { + tracing::debug!( + "Update content of the cell in the row:{} to group:{}", + row_rev.id, + group.id + ); + row_changeset + .cell_by_field_id + .insert(field_rev.id.clone(), cell_rev); + changeset.updated_rows.push(RowPB::from(*row_rev)); + } } + } + if changeset.is_empty() { + None + } else { + Some(changeset) + } } pub fn make_inserted_cell_rev(group_id: &str, field_rev: &FieldRevision) -> Option { - let field_type: FieldType = field_rev.ty.into(); - match field_type { - FieldType::SingleSelect => { - let cell_rev = insert_select_option_cell(vec![group_id.to_owned()], field_rev); - Some(cell_rev) - } - FieldType::MultiSelect => { - let cell_rev = insert_select_option_cell(vec![group_id.to_owned()], field_rev); - Some(cell_rev) - } - FieldType::Checkbox => { - let cell_rev = insert_checkbox_cell(group_id == CHECK, field_rev); - Some(cell_rev) - } - FieldType::URL => { - let cell_rev = insert_url_cell(group_id.to_owned(), field_rev); - Some(cell_rev) - } - _ => { - tracing::warn!("Unknown field type: {:?}", field_type); - None - } - } + let field_type: FieldType = field_rev.ty.into(); + match field_type { + FieldType::SingleSelect => { + let cell_rev = insert_select_option_cell(vec![group_id.to_owned()], field_rev); + Some(cell_rev) + }, + FieldType::MultiSelect => { + let cell_rev = insert_select_option_cell(vec![group_id.to_owned()], field_rev); + Some(cell_rev) + }, + FieldType::Checkbox => { + let cell_rev = insert_checkbox_cell(group_id == CHECK, field_rev); + Some(cell_rev) + }, + FieldType::URL => { + let cell_rev = insert_url_cell(group_id.to_owned(), field_rev); + Some(cell_rev) + }, + _ => { + tracing::warn!("Unknown field type: {:?}", field_type); + None + }, + } } pub fn generate_select_option_groups( - _field_id: &str, - _group_ctx: &SelectOptionGroupContext, - options: &[SelectOptionPB], + _field_id: &str, + _group_ctx: &SelectOptionGroupContext, + options: &[SelectOptionPB], ) -> Vec { - let groups = options - .iter() - .map(|option| GeneratedGroupConfig { - group_rev: GroupRevision::new(option.id.clone(), option.name.clone()), - filter_content: option.id.clone(), - }) - .collect(); + let groups = options + .iter() + .map(|option| GeneratedGroupConfig { + group_rev: GroupRevision::new(option.id.clone(), option.name.clone()), + filter_content: option.id.clone(), + }) + .collect(); - groups + groups } diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/url_controller.rs b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/url_controller.rs index fd17a0c81c..fd151ea9c0 100644 --- a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/url_controller.rs +++ b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/url_controller.rs @@ -4,189 +4,215 @@ use crate::services::field::{URLCellData, URLCellDataPB, URLCellDataParser, URLT use crate::services::group::action::GroupCustomize; use crate::services::group::configuration::GroupContext; use crate::services::group::controller::{ - GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, + GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, +}; +use crate::services::group::{ + make_no_status_group, move_group_row, GeneratedGroupConfig, GeneratedGroupContext, }; -use crate::services::group::{make_no_status_group, move_group_row, GeneratedGroupConfig, GeneratedGroupContext}; use flowy_error::FlowyResult; -use grid_model::{CellRevision, FieldRevision, GroupRevision, RowRevision, URLGroupConfigurationRevision}; +use grid_model::{ + CellRevision, FieldRevision, GroupRevision, RowRevision, URLGroupConfigurationRevision, +}; -pub type URLGroupController = - GenericGroupController; +pub type URLGroupController = GenericGroupController< + URLGroupConfigurationRevision, + URLTypeOptionPB, + URLGroupGenerator, + URLCellDataParser, +>; pub type URLGroupContext = GroupContext; impl GroupCustomize for URLGroupController { - type CellData = URLCellDataPB; + type CellData = URLCellDataPB; - fn placeholder_cell(&self) -> Option { - Some(CellRevision::new("".to_string())) + fn placeholder_cell(&self) -> Option { + Some(CellRevision::new("".to_string())) + } + + fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool { + cell_data.content == content + } + + fn create_or_delete_group_when_cell_changed( + &mut self, + row_rev: &RowRevision, + old_cell_data: Option<&Self::CellData>, + cell_data: &Self::CellData, + ) -> FlowyResult<(Option, Option)> { + // Just return if the group with this url already exists + let mut inserted_group = None; + if self.group_ctx.get_group(&cell_data.url).is_none() { + let cell_data: URLCellData = cell_data.clone().into(); + let group_revision = make_group_from_url_cell(&cell_data); + let mut new_group = self.group_ctx.add_new_group(group_revision)?; + new_group.group.rows.push(RowPB::from(row_rev)); + inserted_group = Some(new_group); } - fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool { - cell_data.content == content - } - - fn create_or_delete_group_when_cell_changed( - &mut self, - row_rev: &RowRevision, - old_cell_data: Option<&Self::CellData>, - cell_data: &Self::CellData, - ) -> FlowyResult<(Option, Option)> { - // Just return if the group with this url already exists - let mut inserted_group = None; - if self.group_ctx.get_group(&cell_data.url).is_none() { - let cell_data: URLCellData = cell_data.clone().into(); - let group_revision = make_group_from_url_cell(&cell_data); - let mut new_group = self.group_ctx.add_new_group(group_revision)?; - new_group.group.rows.push(RowPB::from(row_rev)); - inserted_group = Some(new_group); + // Delete the old url group if there are no rows in that group + let deleted_group = match old_cell_data + .and_then(|old_cell_data| self.group_ctx.get_group(&old_cell_data.content)) + { + None => None, + Some((_, group)) => { + if group.rows.len() == 1 { + Some(group.clone()) + } else { + None } + }, + }; - // Delete the old url group if there are no rows in that group - let deleted_group = - match old_cell_data.and_then(|old_cell_data| self.group_ctx.get_group(&old_cell_data.content)) { - None => None, - Some((_, group)) => { - if group.rows.len() == 1 { - Some(group.clone()) - } else { - None - } - } - }; + let deleted_group = match deleted_group { + None => None, + Some(group) => { + self.group_ctx.delete_group(&group.id)?; + Some(GroupPB::from(group.clone())) + }, + }; - let deleted_group = match deleted_group { - None => None, - Some(group) => { - self.group_ctx.delete_group(&group.id)?; - Some(GroupPB::from(group.clone())) - } - }; + Ok((inserted_group, deleted_group)) + } - Ok((inserted_group, deleted_group)) - } - - fn add_or_remove_row_when_cell_changed( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellData, - ) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_status_groups(|group| { - let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); - if group.id == cell_data.content { - if !group.contains_row(&row_rev.id) { - let row_pb = RowPB::from(row_rev); - changeset.inserted_rows.push(InsertedRowPB::new(row_pb.clone())); - group.add_row(row_pb); - } - } else if group.contains_row(&row_rev.id) { - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); - } - - if !changeset.is_empty() { - changesets.push(changeset); - } - }); - changesets - } - - fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellData) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_groups(|group| { - let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); - if group.contains_row(&row_rev.id) { - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); - } - - if !changeset.is_empty() { - changesets.push(changeset); - } - }); - changesets - } - - fn move_row( - &mut self, - _cell_data: &Self::CellData, - mut context: MoveGroupRowContext, - ) -> Vec { - let mut group_changeset = vec![]; - self.group_ctx.iter_mut_groups(|group| { - if let Some(changeset) = move_group_row(group, &mut context) { - group_changeset.push(changeset); - } - }); - group_changeset - } - - fn delete_group_when_move_row(&mut self, _row_rev: &RowRevision, cell_data: &Self::CellData) -> Option { - let mut deleted_group = None; - if let Some((_, group)) = self.group_ctx.get_group(&cell_data.content) { - if group.rows.len() == 1 { - deleted_group = Some(GroupPB::from(group.clone())); - } + fn add_or_remove_row_when_cell_changed( + &mut self, + row_rev: &RowRevision, + cell_data: &Self::CellData, + ) -> Vec { + let mut changesets = vec![]; + self.group_ctx.iter_mut_status_groups(|group| { + let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); + if group.id == cell_data.content { + if !group.contains_row(&row_rev.id) { + let row_pb = RowPB::from(row_rev); + changeset + .inserted_rows + .push(InsertedRowPB::new(row_pb.clone())); + group.add_row(row_pb); } - if deleted_group.is_some() { - let _ = self.group_ctx.delete_group(&deleted_group.as_ref().unwrap().group_id); - } - deleted_group + } else if group.contains_row(&row_rev.id) { + changeset.deleted_rows.push(row_rev.id.clone()); + group.remove_row(&row_rev.id); + } + + if !changeset.is_empty() { + changesets.push(changeset); + } + }); + changesets + } + + fn delete_row( + &mut self, + row_rev: &RowRevision, + _cell_data: &Self::CellData, + ) -> Vec { + let mut changesets = vec![]; + self.group_ctx.iter_mut_groups(|group| { + let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); + if group.contains_row(&row_rev.id) { + changeset.deleted_rows.push(row_rev.id.clone()); + group.remove_row(&row_rev.id); + } + + if !changeset.is_empty() { + changesets.push(changeset); + } + }); + changesets + } + + fn move_row( + &mut self, + _cell_data: &Self::CellData, + mut context: MoveGroupRowContext, + ) -> Vec { + let mut group_changeset = vec![]; + self.group_ctx.iter_mut_groups(|group| { + if let Some(changeset) = move_group_row(group, &mut context) { + group_changeset.push(changeset); + } + }); + group_changeset + } + + fn delete_group_when_move_row( + &mut self, + _row_rev: &RowRevision, + cell_data: &Self::CellData, + ) -> Option { + let mut deleted_group = None; + if let Some((_, group)) = self.group_ctx.get_group(&cell_data.content) { + if group.rows.len() == 1 { + deleted_group = Some(GroupPB::from(group.clone())); + } } + if deleted_group.is_some() { + let _ = self + .group_ctx + .delete_group(&deleted_group.as_ref().unwrap().group_id); + } + deleted_group + } } impl GroupController for URLGroupController { - fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str) { - match self.group_ctx.get_group(group_id) { - None => tracing::warn!("Can not find the group: {}", group_id), - Some((_, group)) => { - let cell_rev = insert_url_cell(group.id.clone(), field_rev); - row_rev.cells.insert(field_rev.id.clone(), cell_rev); - } - } + fn will_create_row( + &mut self, + row_rev: &mut RowRevision, + field_rev: &FieldRevision, + group_id: &str, + ) { + match self.group_ctx.get_group(group_id) { + None => tracing::warn!("Can not find the group: {}", group_id), + Some((_, group)) => { + let cell_rev = insert_url_cell(group.id.clone(), field_rev); + row_rev.cells.insert(field_rev.id.clone(), cell_rev); + }, } + } - fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str) { - if let Some(group) = self.group_ctx.get_mut_group(group_id) { - group.add_row(row_pb.clone()) - } + fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str) { + if let Some(group) = self.group_ctx.get_mut_group(group_id) { + group.add_row(row_pb.clone()) } + } } pub struct URLGroupGenerator(); impl GroupGenerator for URLGroupGenerator { - type Context = URLGroupContext; - type TypeOptionType = URLTypeOptionPB; + type Context = URLGroupContext; + type TypeOptionType = URLTypeOptionPB; - fn generate_groups( - field_rev: &FieldRevision, - group_ctx: &Self::Context, - _type_option: &Option, - ) -> GeneratedGroupContext { - // Read all the cells for the grouping field - let cells = futures::executor::block_on(group_ctx.get_all_cells()); + fn generate_groups( + field_rev: &FieldRevision, + group_ctx: &Self::Context, + _type_option: &Option, + ) -> GeneratedGroupContext { + // Read all the cells for the grouping field + let cells = futures::executor::block_on(group_ctx.get_all_cells()); - // Generate the groups - let group_configs = cells - .into_iter() - .flat_map(|value| value.into_url_field_cell_data()) - .map(|cell| GeneratedGroupConfig { - group_rev: make_group_from_url_cell(&cell), - filter_content: cell.content, - }) - .collect(); + // Generate the groups + let group_configs = cells + .into_iter() + .flat_map(|value| value.into_url_field_cell_data()) + .map(|cell| GeneratedGroupConfig { + group_rev: make_group_from_url_cell(&cell), + filter_content: cell.content, + }) + .collect(); - let no_status_group = Some(make_no_status_group(field_rev)); - GeneratedGroupContext { - no_status_group, - group_configs, - } + let no_status_group = Some(make_no_status_group(field_rev)); + GeneratedGroupContext { + no_status_group, + group_configs, } + } } fn make_group_from_url_cell(cell: &URLCellData) -> GroupRevision { - let group_id = cell.content.clone(); - let group_name = cell.content.clone(); - GroupRevision::new(group_id, group_name) + let group_id = cell.content.clone(); + let group_name = cell.content.clone(); + GroupRevision::new(group_id, group_name) } diff --git a/frontend/rust-lib/flowy-database/src/services/group/entities.rs b/frontend/rust-lib/flowy-database/src/services/group/entities.rs index d556dcd3c9..a664e22162 100644 --- a/frontend/rust-lib/flowy-database/src/services/group/entities.rs +++ b/frontend/rust-lib/flowy-database/src/services/group/entities.rs @@ -2,70 +2,74 @@ use crate::entities::RowPB; #[derive(Clone, PartialEq, Debug, Eq)] pub struct Group { - pub id: String, - pub field_id: String, - pub name: String, - pub is_default: bool, - pub is_visible: bool, - pub(crate) rows: Vec, + pub id: String, + pub field_id: String, + pub name: String, + pub is_default: bool, + pub is_visible: bool, + pub(crate) rows: Vec, - /// [filter_content] is used to determine which group the cell belongs to. - pub filter_content: String, + /// [filter_content] is used to determine which group the cell belongs to. + pub filter_content: String, } impl Group { - pub fn new(id: String, field_id: String, name: String, filter_content: String) -> Self { - let is_default = id == field_id; - Self { - id, - field_id, - is_default, - is_visible: true, - name, - rows: vec![], - filter_content, - } + pub fn new(id: String, field_id: String, name: String, filter_content: String) -> Self { + let is_default = id == field_id; + Self { + id, + field_id, + is_default, + is_visible: true, + name, + rows: vec![], + filter_content, } + } - pub fn contains_row(&self, row_id: &str) -> bool { - self.rows.iter().any(|row| row.id == row_id) + pub fn contains_row(&self, row_id: &str) -> bool { + self.rows.iter().any(|row| row.id == row_id) + } + + pub fn remove_row(&mut self, row_id: &str) { + match self.rows.iter().position(|row| row.id == row_id) { + None => {}, + Some(pos) => { + self.rows.remove(pos); + }, } + } - pub fn remove_row(&mut self, row_id: &str) { - match self.rows.iter().position(|row| row.id == row_id) { - None => {} - Some(pos) => { - self.rows.remove(pos); - } - } + pub fn add_row(&mut self, row_pb: RowPB) { + match self.rows.iter().find(|row| row.id == row_pb.id) { + None => { + self.rows.push(row_pb); + }, + Some(_) => {}, } + } - pub fn add_row(&mut self, row_pb: RowPB) { - match self.rows.iter().find(|row| row.id == row_pb.id) { - None => { - self.rows.push(row_pb); - } - Some(_) => {} - } - } - - pub fn insert_row(&mut self, index: usize, row_pb: RowPB) { - if index < self.rows.len() { - self.rows.insert(index, row_pb); - } else { - tracing::error!("Insert row index:{} beyond the bounds:{},", index, self.rows.len()); - } - } - - pub fn index_of_row(&self, row_id: &str) -> Option { - self.rows.iter().position(|row| row.id == row_id) - } - - pub fn number_of_row(&self) -> usize { + pub fn insert_row(&mut self, index: usize, row_pb: RowPB) { + if index < self.rows.len() { + self.rows.insert(index, row_pb); + } else { + tracing::error!( + "Insert row index:{} beyond the bounds:{},", + index, self.rows.len() + ); } + } - pub fn is_empty(&self) -> bool { - self.rows.is_empty() - } + pub fn index_of_row(&self, row_id: &str) -> Option { + self.rows.iter().position(|row| row.id == row_id) + } + + pub fn number_of_row(&self) -> usize { + self.rows.len() + } + + pub fn is_empty(&self) -> bool { + self.rows.is_empty() + } } diff --git a/frontend/rust-lib/flowy-database/src/services/group/group_util.rs b/frontend/rust-lib/flowy-database/src/services/group/group_util.rs index eb984cd9a2..27afeea0e7 100644 --- a/frontend/rust-lib/flowy-database/src/services/group/group_util.rs +++ b/frontend/rust-lib/flowy-database/src/services/group/group_util.rs @@ -2,15 +2,16 @@ use crate::entities::FieldType; use crate::services::group::configuration::GroupConfigurationReader; use crate::services::group::controller::GroupController; use crate::services::group::{ - CheckboxGroupContext, CheckboxGroupController, DefaultGroupController, GroupConfigurationWriter, - MultiSelectGroupController, SelectOptionGroupContext, SingleSelectGroupController, URLGroupContext, - URLGroupController, + CheckboxGroupContext, CheckboxGroupController, DefaultGroupController, GroupConfigurationWriter, + MultiSelectGroupController, SelectOptionGroupContext, SingleSelectGroupController, + URLGroupContext, URLGroupController, }; use flowy_error::FlowyResult; use grid_model::{ - CheckboxGroupConfigurationRevision, DateGroupConfigurationRevision, FieldRevision, GroupConfigurationRevision, - GroupRevision, LayoutRevision, NumberGroupConfigurationRevision, RowRevision, - SelectOptionGroupConfigurationRevision, TextGroupConfigurationRevision, URLGroupConfigurationRevision, + CheckboxGroupConfigurationRevision, DateGroupConfigurationRevision, FieldRevision, + GroupConfigurationRevision, GroupRevision, LayoutRevision, NumberGroupConfigurationRevision, + RowRevision, SelectOptionGroupConfigurationRevision, TextGroupConfigurationRevision, + URLGroupConfigurationRevision, }; use std::sync::Arc; @@ -27,68 +28,88 @@ use std::sync::Arc; /// #[tracing::instrument(level = "trace", skip_all, err)] pub async fn make_group_controller( - view_id: String, - field_rev: Arc, - row_revs: Vec>, - configuration_reader: R, - configuration_writer: W, + view_id: String, + field_rev: Arc, + row_revs: Vec>, + configuration_reader: R, + configuration_writer: W, ) -> FlowyResult> where - R: GroupConfigurationReader, - W: GroupConfigurationWriter, + R: GroupConfigurationReader, + W: GroupConfigurationWriter, { - let field_type: FieldType = field_rev.ty.into(); + let field_type: FieldType = field_rev.ty.into(); - let mut group_controller: Box; - let configuration_reader = Arc::new(configuration_reader); - let configuration_writer = Arc::new(configuration_writer); + let mut group_controller: Box; + let configuration_reader = Arc::new(configuration_reader); + let configuration_writer = Arc::new(configuration_writer); - match field_type { - FieldType::SingleSelect => { - let configuration = - SelectOptionGroupContext::new(view_id, field_rev.clone(), configuration_reader, configuration_writer) - .await?; - let controller = SingleSelectGroupController::new(&field_rev, configuration).await?; - group_controller = Box::new(controller); - } - FieldType::MultiSelect => { - let configuration = - SelectOptionGroupContext::new(view_id, field_rev.clone(), configuration_reader, configuration_writer) - .await?; - let controller = MultiSelectGroupController::new(&field_rev, configuration).await?; - group_controller = Box::new(controller); - } - FieldType::Checkbox => { - let configuration = - CheckboxGroupContext::new(view_id, field_rev.clone(), configuration_reader, configuration_writer) - .await?; - let controller = CheckboxGroupController::new(&field_rev, configuration).await?; - group_controller = Box::new(controller); - } - FieldType::URL => { - let configuration = - URLGroupContext::new(view_id, field_rev.clone(), configuration_reader, configuration_writer).await?; - let controller = URLGroupController::new(&field_rev, configuration).await?; - group_controller = Box::new(controller); - } - _ => { - group_controller = Box::new(DefaultGroupController::new(&field_rev)); - } - } + match field_type { + FieldType::SingleSelect => { + let configuration = SelectOptionGroupContext::new( + view_id, + field_rev.clone(), + configuration_reader, + configuration_writer, + ) + .await?; + let controller = SingleSelectGroupController::new(&field_rev, configuration).await?; + group_controller = Box::new(controller); + }, + FieldType::MultiSelect => { + let configuration = SelectOptionGroupContext::new( + view_id, + field_rev.clone(), + configuration_reader, + configuration_writer, + ) + .await?; + let controller = MultiSelectGroupController::new(&field_rev, configuration).await?; + group_controller = Box::new(controller); + }, + FieldType::Checkbox => { + let configuration = CheckboxGroupContext::new( + view_id, + field_rev.clone(), + configuration_reader, + configuration_writer, + ) + .await?; + let controller = CheckboxGroupController::new(&field_rev, configuration).await?; + group_controller = Box::new(controller); + }, + FieldType::URL => { + let configuration = URLGroupContext::new( + view_id, + field_rev.clone(), + configuration_reader, + configuration_writer, + ) + .await?; + let controller = URLGroupController::new(&field_rev, configuration).await?; + group_controller = Box::new(controller); + }, + _ => { + group_controller = Box::new(DefaultGroupController::new(&field_rev)); + }, + } - // Separates the rows into different groups - group_controller.fill_groups(&row_revs, &field_rev)?; - Ok(group_controller) + // Separates the rows into different groups + group_controller.fill_groups(&row_revs, &field_rev)?; + Ok(group_controller) } -pub fn find_group_field(field_revs: &[Arc], _layout: &LayoutRevision) -> Option> { - field_revs - .iter() - .find(|field_rev| { - let field_type: FieldType = field_rev.ty.into(); - field_type.can_be_group() - }) - .cloned() +pub fn find_group_field( + field_revs: &[Arc], + _layout: &LayoutRevision, +) -> Option> { + field_revs + .iter() + .find(|field_rev| { + let field_type: FieldType = field_rev.ty.into(); + field_type.can_be_group() + }) + .cloned() } /// Returns a `default` group configuration for the [FieldRevision] @@ -98,55 +119,66 @@ pub fn find_group_field(field_revs: &[Arc], _layout: &LayoutRevis /// * `field_rev`: making the group configuration for the field /// pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurationRevision { - let field_id = field_rev.id.clone(); - let field_type_rev = field_rev.ty; - let field_type: FieldType = field_rev.ty.into(); - match field_type { - FieldType::RichText => { - GroupConfigurationRevision::new(field_id, field_type_rev, TextGroupConfigurationRevision::default()) - .unwrap() - } - FieldType::Number => { - GroupConfigurationRevision::new(field_id, field_type_rev, NumberGroupConfigurationRevision::default()) - .unwrap() - } - FieldType::DateTime => { - GroupConfigurationRevision::new(field_id, field_type_rev, DateGroupConfigurationRevision::default()) - .unwrap() - } + let field_id = field_rev.id.clone(); + let field_type_rev = field_rev.ty; + let field_type: FieldType = field_rev.ty.into(); + match field_type { + FieldType::RichText => GroupConfigurationRevision::new( + field_id, + field_type_rev, + TextGroupConfigurationRevision::default(), + ) + .unwrap(), + FieldType::Number => GroupConfigurationRevision::new( + field_id, + field_type_rev, + NumberGroupConfigurationRevision::default(), + ) + .unwrap(), + FieldType::DateTime => GroupConfigurationRevision::new( + field_id, + field_type_rev, + DateGroupConfigurationRevision::default(), + ) + .unwrap(), - FieldType::SingleSelect => GroupConfigurationRevision::new( - field_id, - field_type_rev, - SelectOptionGroupConfigurationRevision::default(), - ) - .unwrap(), - FieldType::MultiSelect => GroupConfigurationRevision::new( - field_id, - field_type_rev, - SelectOptionGroupConfigurationRevision::default(), - ) - .unwrap(), - FieldType::Checklist => GroupConfigurationRevision::new( - field_id, - field_type_rev, - SelectOptionGroupConfigurationRevision::default(), - ) - .unwrap(), - FieldType::Checkbox => { - GroupConfigurationRevision::new(field_id, field_type_rev, CheckboxGroupConfigurationRevision::default()) - .unwrap() - } - FieldType::URL => { - GroupConfigurationRevision::new(field_id, field_type_rev, URLGroupConfigurationRevision::default()).unwrap() - } - } + FieldType::SingleSelect => GroupConfigurationRevision::new( + field_id, + field_type_rev, + SelectOptionGroupConfigurationRevision::default(), + ) + .unwrap(), + FieldType::MultiSelect => GroupConfigurationRevision::new( + field_id, + field_type_rev, + SelectOptionGroupConfigurationRevision::default(), + ) + .unwrap(), + FieldType::Checklist => GroupConfigurationRevision::new( + field_id, + field_type_rev, + SelectOptionGroupConfigurationRevision::default(), + ) + .unwrap(), + FieldType::Checkbox => GroupConfigurationRevision::new( + field_id, + field_type_rev, + CheckboxGroupConfigurationRevision::default(), + ) + .unwrap(), + FieldType::URL => GroupConfigurationRevision::new( + field_id, + field_type_rev, + URLGroupConfigurationRevision::default(), + ) + .unwrap(), + } } pub fn make_no_status_group(field_rev: &FieldRevision) -> GroupRevision { - GroupRevision { - id: field_rev.id.clone(), - name: format!("No {}", field_rev.name), - visible: true, - } + GroupRevision { + id: field_rev.id.clone(), + name: format!("No {}", field_rev.name), + visible: true, + } } diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/block_index.rs b/frontend/rust-lib/flowy-database/src/services/persistence/block_index.rs index 00bd6da3ae..d359b4d5c3 100644 --- a/frontend/rust-lib/flowy-database/src/services/persistence/block_index.rs +++ b/frontend/rust-lib/flowy-database/src/services/persistence/block_index.rs @@ -2,48 +2,48 @@ use crate::services::persistence::GridDatabase; use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; use flowy_error::FlowyResult; use flowy_sqlite::{ - prelude::*, - schema::{grid_block_index_table, grid_block_index_table::dsl}, + prelude::*, + schema::{grid_block_index_table, grid_block_index_table::dsl}, }; use std::sync::Arc; /// Allow getting the block id from row id. pub struct BlockIndexCache { - database: Arc, + database: Arc, } impl BlockIndexCache { - pub fn new(database: Arc) -> Self { - Self { database } - } + pub fn new(database: Arc) -> Self { + Self { database } + } - pub fn get_block_id(&self, row_id: &str) -> FlowyResult { - let conn = self.database.db_connection()?; - let block_id = dsl::grid_block_index_table - .filter(grid_block_index_table::row_id.eq(row_id)) - .select(grid_block_index_table::block_id) - .first::(&*conn)?; + pub fn get_block_id(&self, row_id: &str) -> FlowyResult { + let conn = self.database.db_connection()?; + let block_id = dsl::grid_block_index_table + .filter(grid_block_index_table::row_id.eq(row_id)) + .select(grid_block_index_table::block_id) + .first::(&*conn)?; - Ok(block_id) - } + Ok(block_id) + } - pub fn insert(&self, block_id: &str, row_id: &str) -> FlowyResult<()> { - let conn = self.database.db_connection()?; - let item = IndexItem { - row_id: row_id.to_string(), - block_id: block_id.to_string(), - }; - let _ = diesel::replace_into(grid_block_index_table::table) - .values(item) - .execute(&*conn)?; - Ok(()) - } + pub fn insert(&self, block_id: &str, row_id: &str) -> FlowyResult<()> { + let conn = self.database.db_connection()?; + let item = IndexItem { + row_id: row_id.to_string(), + block_id: block_id.to_string(), + }; + let _ = diesel::replace_into(grid_block_index_table::table) + .values(item) + .execute(&*conn)?; + Ok(()) + } } #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] #[table_name = "grid_block_index_table"] #[primary_key(row_id)] struct IndexItem { - row_id: String, - block_id: String, + row_id: String, + block_id: String, } diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/kv.rs b/frontend/rust-lib/flowy-database/src/services/persistence/kv.rs index 06c6a33313..f99646135b 100644 --- a/frontend/rust-lib/flowy-database/src/services/persistence/kv.rs +++ b/frontend/rust-lib/flowy-database/src/services/persistence/kv.rs @@ -4,8 +4,8 @@ use bytes::Bytes; use diesel::SqliteConnection; use flowy_error::{FlowyError, FlowyResult}; use flowy_sqlite::{ - prelude::*, - schema::{kv_table, kv_table::dsl}, + prelude::*, + schema::{kv_table, kv_table::dsl}, }; use std::sync::Arc; @@ -13,124 +13,140 @@ use std::sync::Arc; #[table_name = "kv_table"] #[primary_key(key)] pub struct KeyValue { - key: String, - value: Vec, + key: String, + value: Vec, } pub trait KVTransaction { - fn get>(&self, key: &str) -> FlowyResult>; - fn set>(&self, value: T) -> FlowyResult<()>; - fn remove(&self, key: &str) -> FlowyResult<()>; + fn get>( + &self, + key: &str, + ) -> FlowyResult>; + fn set>(&self, value: T) -> FlowyResult<()>; + fn remove(&self, key: &str) -> FlowyResult<()>; - fn batch_get>(&self, keys: Vec) - -> FlowyResult>; - fn batch_set>(&self, values: Vec) -> FlowyResult<()>; - fn batch_remove(&self, keys: Vec) -> FlowyResult<()>; + fn batch_get>( + &self, + keys: Vec, + ) -> FlowyResult>; + fn batch_set>(&self, values: Vec) -> FlowyResult<()>; + fn batch_remove(&self, keys: Vec) -> FlowyResult<()>; } pub struct DatabaseKVPersistence { - database: Arc, + database: Arc, } impl DatabaseKVPersistence { - pub fn new(database: Arc) -> Self { - Self { database } - } + pub fn new(database: Arc) -> Self { + Self { database } + } - pub fn begin_transaction(&self, f: F) -> FlowyResult - where - F: for<'a> FnOnce(SqliteTransaction<'a>) -> FlowyResult, - { - let conn = self.database.db_connection()?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - let sql_transaction = SqliteTransaction { conn: &conn }; - f(sql_transaction) - }) - } + pub fn begin_transaction(&self, f: F) -> FlowyResult + where + F: for<'a> FnOnce(SqliteTransaction<'a>) -> FlowyResult, + { + let conn = self.database.db_connection()?; + conn.immediate_transaction::<_, FlowyError, _>(|| { + let sql_transaction = SqliteTransaction { conn: &conn }; + f(sql_transaction) + }) + } } impl KVTransaction for DatabaseKVPersistence { - fn get>(&self, key: &str) -> FlowyResult> { - self.begin_transaction(|transaction| transaction.get(key)) - } + fn get>( + &self, + key: &str, + ) -> FlowyResult> { + self.begin_transaction(|transaction| transaction.get(key)) + } - fn set>(&self, value: T) -> FlowyResult<()> { - self.begin_transaction(|transaction| transaction.set(value)) - } + fn set>(&self, value: T) -> FlowyResult<()> { + self.begin_transaction(|transaction| transaction.set(value)) + } - fn remove(&self, key: &str) -> FlowyResult<()> { - self.begin_transaction(|transaction| transaction.remove(key)) - } + fn remove(&self, key: &str) -> FlowyResult<()> { + self.begin_transaction(|transaction| transaction.remove(key)) + } - fn batch_get>( - &self, - keys: Vec, - ) -> FlowyResult> { - self.begin_transaction(|transaction| transaction.batch_get(keys)) - } + fn batch_get>( + &self, + keys: Vec, + ) -> FlowyResult> { + self.begin_transaction(|transaction| transaction.batch_get(keys)) + } - fn batch_set>(&self, values: Vec) -> FlowyResult<()> { - self.begin_transaction(|transaction| transaction.batch_set(values)) - } + fn batch_set>(&self, values: Vec) -> FlowyResult<()> { + self.begin_transaction(|transaction| transaction.batch_set(values)) + } - fn batch_remove(&self, keys: Vec) -> FlowyResult<()> { - self.begin_transaction(|transaction| transaction.batch_remove(keys)) - } + fn batch_remove(&self, keys: Vec) -> FlowyResult<()> { + self.begin_transaction(|transaction| transaction.batch_remove(keys)) + } } pub struct SqliteTransaction<'a> { - conn: &'a SqliteConnection, + conn: &'a SqliteConnection, } impl<'a> KVTransaction for SqliteTransaction<'a> { - fn get>(&self, key: &str) -> FlowyResult> { - let item = dsl::kv_table - .filter(kv_table::key.eq(key)) - .first::(self.conn)?; - let value = T::try_from(Bytes::from(item.value)).unwrap(); - Ok(Some(value)) - } + fn get>( + &self, + key: &str, + ) -> FlowyResult> { + let item = dsl::kv_table + .filter(kv_table::key.eq(key)) + .first::(self.conn)?; + let value = T::try_from(Bytes::from(item.value)).unwrap(); + Ok(Some(value)) + } - fn set>(&self, value: T) -> FlowyResult<()> { - let item: KeyValue = value.into(); - let _ = diesel::replace_into(kv_table::table).values(&item).execute(self.conn)?; - Ok(()) - } + fn set>(&self, value: T) -> FlowyResult<()> { + let item: KeyValue = value.into(); + let _ = diesel::replace_into(kv_table::table) + .values(&item) + .execute(self.conn)?; + Ok(()) + } - fn remove(&self, key: &str) -> FlowyResult<()> { - let sql = dsl::kv_table.filter(kv_table::key.eq(key)); - let _ = diesel::delete(sql).execute(self.conn)?; - Ok(()) - } + fn remove(&self, key: &str) -> FlowyResult<()> { + let sql = dsl::kv_table.filter(kv_table::key.eq(key)); + let _ = diesel::delete(sql).execute(self.conn)?; + Ok(()) + } - fn batch_get>( - &self, - keys: Vec, - ) -> FlowyResult> { - let items = dsl::kv_table - .filter(kv_table::key.eq_any(&keys)) - .load::(self.conn)?; - let mut values = vec![]; - for item in items { - let value = T::try_from(Bytes::from(item.value)).unwrap(); - values.push(value); - } - Ok(values) + fn batch_get>( + &self, + keys: Vec, + ) -> FlowyResult> { + let items = dsl::kv_table + .filter(kv_table::key.eq_any(&keys)) + .load::(self.conn)?; + let mut values = vec![]; + for item in items { + let value = T::try_from(Bytes::from(item.value)).unwrap(); + values.push(value); } + Ok(values) + } - fn batch_set>(&self, values: Vec) -> FlowyResult<()> { - let items = values.into_iter().map(|value| value.into()).collect::>(); - let _ = diesel::replace_into(kv_table::table) - .values(&items) - .execute(self.conn)?; - Ok(()) - } + fn batch_set>(&self, values: Vec) -> FlowyResult<()> { + let items = values + .into_iter() + .map(|value| value.into()) + .collect::>(); + let _ = diesel::replace_into(kv_table::table) + .values(&items) + .execute(self.conn)?; + Ok(()) + } - fn batch_remove(&self, keys: Vec) -> FlowyResult<()> { - let sql = dsl::kv_table.filter(kv_table::key.eq_any(keys)); - let _ = diesel::delete(sql).execute(self.conn)?; - Ok(()) - } + fn batch_remove(&self, keys: Vec) -> FlowyResult<()> { + let sql = dsl::kv_table.filter(kv_table::key.eq_any(keys)); + let _ = diesel::delete(sql).execute(self.conn)?; + Ok(()) + } } // impl + GridIdentifiable> std::convert::From for KeyValue { diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/migration.rs b/frontend/rust-lib/flowy-database/src/services/persistence/migration.rs index ccb53e1918..e9b5fc7996 100644 --- a/frontend/rust-lib/flowy-database/src/services/persistence/migration.rs +++ b/frontend/rust-lib/flowy-database/src/services/persistence/migration.rs @@ -2,7 +2,9 @@ use crate::manager::DatabaseUser; use crate::services::persistence::rev_sqlite::SQLiteDatabaseRevisionPersistence; use crate::services::persistence::GridDatabase; use bytes::Bytes; -use flowy_client_sync::client_database::{make_database_rev_json_str, DatabaseOperationsBuilder, DatabaseRevisionPad}; +use flowy_client_sync::client_database::{ + make_database_rev_json_str, DatabaseOperationsBuilder, DatabaseRevisionPad, +}; use flowy_error::FlowyResult; use flowy_revision::reset::{RevisionResettable, RevisionStructReset}; use flowy_sqlite::kv::KV; @@ -14,70 +16,73 @@ use std::sync::Arc; const V1_MIGRATION: &str = "GRID_V1_MIGRATION"; pub(crate) struct DatabaseMigration { - user: Arc, - database: Arc, + user: Arc, + database: Arc, } impl DatabaseMigration { - pub fn new(user: Arc, database: Arc) -> Self { - Self { user, database } - } + pub fn new(user: Arc, database: Arc) -> Self { + Self { user, database } + } - pub async fn run_v1_migration(&self, grid_id: &str) -> FlowyResult<()> { - let user_id = self.user.user_id()?; - let key = migration_flag_key(&user_id, V1_MIGRATION, grid_id); - if KV::get_bool(&key) { - return Ok(()); - } - self.migration_grid_rev_struct(grid_id).await?; - tracing::trace!("Run grid:{} v1 migration", grid_id); - KV::set_bool(&key, true); - Ok(()) + pub async fn run_v1_migration(&self, grid_id: &str) -> FlowyResult<()> { + let user_id = self.user.user_id()?; + let key = migration_flag_key(&user_id, V1_MIGRATION, grid_id); + if KV::get_bool(&key) { + return Ok(()); } + self.migration_grid_rev_struct(grid_id).await?; + tracing::trace!("Run grid:{} v1 migration", grid_id); + KV::set_bool(&key, true); + Ok(()) + } - pub async fn migration_grid_rev_struct(&self, grid_id: &str) -> FlowyResult<()> { - let object = GridRevisionResettable { - grid_id: grid_id.to_owned(), - }; - let user_id = self.user.user_id()?; - let pool = self.database.db_pool()?; - let disk_cache = SQLiteDatabaseRevisionPersistence::new(&user_id, pool); - let reset = RevisionStructReset::new(&user_id, object, Arc::new(disk_cache)); - reset.run().await - } + pub async fn migration_grid_rev_struct(&self, grid_id: &str) -> FlowyResult<()> { + let object = GridRevisionResettable { + grid_id: grid_id.to_owned(), + }; + let user_id = self.user.user_id()?; + let pool = self.database.db_pool()?; + let disk_cache = SQLiteDatabaseRevisionPersistence::new(&user_id, pool); + let reset = RevisionStructReset::new(&user_id, object, Arc::new(disk_cache)); + reset.run().await + } } fn migration_flag_key(user_id: &str, version: &str, grid_id: &str) -> String { - md5(format!("{}{}{}", user_id, version, grid_id,)) + md5(format!("{}{}{}", user_id, version, grid_id,)) } pub struct GridRevisionResettable { - grid_id: String, + grid_id: String, } impl RevisionResettable for GridRevisionResettable { - fn target_id(&self) -> &str { - &self.grid_id - } + fn target_id(&self) -> &str { + &self.grid_id + } - fn reset_data(&self, revisions: Vec) -> FlowyResult { - let pad = DatabaseRevisionPad::from_revisions(revisions)?; - let json = pad.json_str()?; - let bytes = DatabaseOperationsBuilder::new().insert(&json).build().json_bytes(); - Ok(bytes) - } + fn reset_data(&self, revisions: Vec) -> FlowyResult { + let pad = DatabaseRevisionPad::from_revisions(revisions)?; + let json = pad.json_str()?; + let bytes = DatabaseOperationsBuilder::new() + .insert(&json) + .build() + .json_bytes(); + Ok(bytes) + } - fn default_target_rev_str(&self) -> FlowyResult { - let grid_rev = DatabaseRevision::default(); - let json = make_database_rev_json_str(&grid_rev)?; - Ok(json) - } + fn default_target_rev_str(&self) -> FlowyResult { + let grid_rev = DatabaseRevision::default(); + let json = make_database_rev_json_str(&grid_rev)?; + Ok(json) + } - fn read_record(&self) -> Option { - KV::get_str(self.target_id()) - } + fn read_record(&self) -> Option { + KV::get_str(self.target_id()) + } - fn set_record(&self, record: String) { - KV::set_str(self.target_id(), record); - } + fn set_record(&self, record: String) { + KV::set_str(self.target_id(), record); + } } diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-database/src/services/persistence/mod.rs index 6f9db8e7f8..a8cd551ffc 100644 --- a/frontend/rust-lib/flowy-database/src/services/persistence/mod.rs +++ b/frontend/rust-lib/flowy-database/src/services/persistence/mod.rs @@ -8,11 +8,11 @@ pub mod migration; pub mod rev_sqlite; pub trait GridDatabase: Send + Sync { - fn db_pool(&self) -> Result, FlowyError>; + fn db_pool(&self) -> Result, FlowyError>; - fn db_connection(&self) -> Result { - let pool = self.db_pool()?; - let conn = pool.get().map_err(|e| FlowyError::internal().context(e))?; - Ok(conn) - } + fn db_connection(&self) -> Result { + let pool = self.db_pool()?; + let conn = pool.get().map_err(|e| FlowyError::internal().context(e))?; + Ok(conn) + } } diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_block_sqlite_impl.rs b/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_block_sqlite_impl.rs index 379de332e0..bfd640bba1 100644 --- a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_block_sqlite_impl.rs +++ b/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_block_sqlite_impl.rs @@ -3,232 +3,247 @@ use diesel::{sql_types::Integer, update, SqliteConnection}; use flowy_error::{internal_error, FlowyError, FlowyResult}; use flowy_revision_persistence::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; use flowy_sqlite::{ - impl_sql_integer_expression, insert_or_ignore_into, - prelude::*, - schema::{grid_meta_rev_table, grid_meta_rev_table::dsl}, - ConnectionPool, + impl_sql_integer_expression, insert_or_ignore_into, + prelude::*, + schema::{grid_meta_rev_table, grid_meta_rev_table::dsl}, + ConnectionPool, }; use lib_infra::util::md5; use revision_model::{Revision, RevisionRange}; use std::sync::Arc; pub struct SQLiteDatabaseBlockRevisionPersistence { - user_id: String, - pub(crate) pool: Arc, + user_id: String, + pub(crate) pool: Arc, } impl RevisionDiskCache> for SQLiteDatabaseBlockRevisionPersistence { - type Error = FlowyError; + type Error = FlowyError; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - GridMetaRevisionSql::create(revision_records, &conn)?; - Ok(()) - } + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + GridMetaRevisionSql::create(revision_records, &conn)?; + Ok(()) + } - fn get_connection(&self) -> Result, Self::Error> { - Ok(self.pool.clone()) - } + fn get_connection(&self) -> Result, Self::Error> { + Ok(self.pool.clone()) + } - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - let records = GridMetaRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?; - Ok(records) - } + fn read_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result, Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + let records = GridMetaRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?; + Ok(records) + } - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, 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)?; - Ok(revisions) - } + fn read_revision_records_with_range( + &self, + object_id: &str, + range: &RevisionRange, + ) -> Result, 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)?; + Ok(revisions) + } - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - let conn = &*self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - for changeset in changesets { - GridMetaRevisionSql::update(changeset, conn)?; - } - Ok(()) - })?; - Ok(()) - } + fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { + let conn = &*self.pool.get().map_err(internal_error)?; + conn.immediate_transaction::<_, FlowyError, _>(|| { + for changeset in changesets { + GridMetaRevisionSql::update(changeset, conn)?; + } + Ok(()) + })?; + Ok(()) + } - fn delete_revision_records(&self, object_id: &str, rev_ids: Option>) -> Result<(), Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - GridMetaRevisionSql::delete(object_id, rev_ids, conn)?; - Ok(()) - } + fn delete_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result<(), Self::Error> { + let conn = &*self.pool.get().map_err(internal_error)?; + GridMetaRevisionSql::delete(object_id, rev_ids, conn)?; + Ok(()) + } - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> 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)?; - Ok(()) - }) - } + fn delete_and_insert_records( + &self, + object_id: &str, + deleted_rev_ids: Option>, + inserted_records: Vec, + ) -> 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)?; + Ok(()) + }) + } } impl SQLiteDatabaseBlockRevisionPersistence { - pub fn new(user_id: &str, pool: Arc) -> Self { - Self { - user_id: user_id.to_owned(), - pool, - } + pub fn new(user_id: &str, pool: Arc) -> Self { + Self { + user_id: user_id.to_owned(), + pool, } + } } struct GridMetaRevisionSql(); impl GridMetaRevisionSql { - fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { - // Batch insert: https://diesel.rs/guides/all-about-inserts.html + fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { + // Batch insert: https://diesel.rs/guides/all-about-inserts.html - let records = revision_records - .into_iter() - .map(|record| { - tracing::trace!( - "[GridMetaRevisionSql] create revision: {}:{:?}", - record.revision.object_id, - record.revision.rev_id - ); - let rev_state: GridBlockRevisionState = record.state.into(); - ( - dsl::object_id.eq(record.revision.object_id), - dsl::base_rev_id.eq(record.revision.base_rev_id), - dsl::rev_id.eq(record.revision.rev_id), - dsl::data.eq(record.revision.bytes), - dsl::state.eq(rev_state), - ) - }) - .collect::>(); - - let _ = insert_or_ignore_into(dsl::grid_meta_rev_table) - .values(&records) - .execute(conn)?; - Ok(()) - } - - fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { - let state: GridBlockRevisionState = changeset.state.clone().into(); - let filter = dsl::grid_meta_rev_table - .filter(dsl::rev_id.eq(changeset.rev_id)) - .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 {:?}", - changeset.rev_id, - changeset.state + let records = revision_records + .into_iter() + .map(|record| { + tracing::trace!( + "[GridMetaRevisionSql] create revision: {}:{:?}", + record.revision.object_id, + record.revision.rev_id ); - Ok(()) + let rev_state: GridBlockRevisionState = record.state.into(); + ( + dsl::object_id.eq(record.revision.object_id), + dsl::base_rev_id.eq(record.revision.base_rev_id), + dsl::rev_id.eq(record.revision.rev_id), + dsl::data.eq(record.revision.bytes), + dsl::state.eq(rev_state), + ) + }) + .collect::>(); + + let _ = insert_or_ignore_into(dsl::grid_meta_rev_table) + .values(&records) + .execute(conn)?; + Ok(()) + } + + fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { + let state: GridBlockRevisionState = changeset.state.clone().into(); + let filter = dsl::grid_meta_rev_table + .filter(dsl::rev_id.eq(changeset.rev_id)) + .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 {:?}", + changeset.rev_id, + changeset.state + ); + Ok(()) + } + + fn read( + user_id: &str, + object_id: &str, + rev_ids: Option>, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let mut sql = dsl::grid_meta_rev_table + .filter(dsl::object_id.eq(object_id)) + .into_boxed(); + if let Some(rev_ids) = rev_ids { + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); + } + let rows = sql + .order(dsl::rev_id.asc()) + .load::(conn)?; + let records = rows + .into_iter() + .map(|row| mk_revision_record_from_table(user_id, row)) + .collect::>(); + + Ok(records) + } + + fn read_with_range( + user_id: &str, + object_id: &str, + range: RevisionRange, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let rev_tables = dsl::grid_meta_rev_table + .filter(dsl::rev_id.ge(range.start)) + .filter(dsl::rev_id.le(range.end)) + .filter(dsl::object_id.eq(object_id)) + .order(dsl::rev_id.asc()) + .load::(conn)?; + + let revisions = rev_tables + .into_iter() + .map(|table| mk_revision_record_from_table(user_id, table)) + .collect::>(); + Ok(revisions) + } + + fn delete( + object_id: &str, + rev_ids: Option>, + conn: &SqliteConnection, + ) -> Result<(), FlowyError> { + let mut sql = diesel::delete(dsl::grid_meta_rev_table).into_boxed(); + sql = sql.filter(dsl::object_id.eq(object_id)); + + if let Some(rev_ids) = rev_ids { + tracing::trace!( + "[GridMetaRevisionSql] Delete revision: {}:{:?}", + object_id, + rev_ids + ); + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); } - fn read( - user_id: &str, - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let mut sql = dsl::grid_meta_rev_table - .filter(dsl::object_id.eq(object_id)) - .into_boxed(); - if let Some(rev_ids) = rev_ids { - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - let rows = sql.order(dsl::rev_id.asc()).load::(conn)?; - let records = rows - .into_iter() - .map(|row| mk_revision_record_from_table(user_id, row)) - .collect::>(); - - Ok(records) - } - - fn read_with_range( - user_id: &str, - object_id: &str, - range: RevisionRange, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let rev_tables = dsl::grid_meta_rev_table - .filter(dsl::rev_id.ge(range.start)) - .filter(dsl::rev_id.le(range.end)) - .filter(dsl::object_id.eq(object_id)) - .order(dsl::rev_id.asc()) - .load::(conn)?; - - let revisions = rev_tables - .into_iter() - .map(|table| mk_revision_record_from_table(user_id, table)) - .collect::>(); - Ok(revisions) - } - - fn delete(object_id: &str, rev_ids: Option>, conn: &SqliteConnection) -> Result<(), FlowyError> { - let mut sql = diesel::delete(dsl::grid_meta_rev_table).into_boxed(); - sql = sql.filter(dsl::object_id.eq(object_id)); - - if let Some(rev_ids) = rev_ids { - tracing::trace!("[GridMetaRevisionSql] Delete revision: {}:{:?}", object_id, rev_ids); - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - - let affected_row = sql.execute(conn)?; - tracing::trace!("[GridMetaRevisionSql] Delete {} rows", affected_row); - Ok(()) - } + let affected_row = sql.execute(conn)?; + tracing::trace!("[GridMetaRevisionSql] Delete {} rows", affected_row); + Ok(()) + } } #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] #[table_name = "grid_meta_rev_table"] struct GridBlockRevisionTable { - id: i32, - object_id: String, - base_rev_id: i64, - rev_id: i64, - data: Vec, - state: GridBlockRevisionState, + id: i32, + object_id: String, + base_rev_id: i64, + rev_id: i64, + data: Vec, + state: GridBlockRevisionState, } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] #[repr(i32)] #[sql_type = "Integer"] pub enum GridBlockRevisionState { - Sync = 0, - Ack = 1, + Sync = 0, + Ack = 1, } impl_sql_integer_expression!(GridBlockRevisionState); impl_rev_state_map!(GridBlockRevisionState); impl std::default::Default for GridBlockRevisionState { - fn default() -> Self { - GridBlockRevisionState::Sync - } + fn default() -> Self { + GridBlockRevisionState::Sync + } } fn mk_revision_record_from_table(_user_id: &str, table: GridBlockRevisionTable) -> SyncRecord { - let md5 = md5(&table.data); - let revision = Revision::new( - &table.object_id, - table.base_rev_id, - table.rev_id, - Bytes::from(table.data), - md5, - ); - SyncRecord { - revision, - state: table.state.into(), - write_to_disk: false, - } + let md5 = md5(&table.data); + let revision = Revision::new( + &table.object_id, + table.base_rev_id, + table.rev_id, + Bytes::from(table.data), + md5, + ); + SyncRecord { + revision, + state: table.state.into(), + write_to_disk: false, + } } diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_snapshot.rs b/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_snapshot.rs index aef3099180..c87d15c71d 100644 --- a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_snapshot.rs +++ b/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_snapshot.rs @@ -3,116 +3,116 @@ use bytes::Bytes; use flowy_error::{internal_error, FlowyResult}; use flowy_revision::{RevisionSnapshotData, RevisionSnapshotPersistence}; use flowy_sqlite::{ - prelude::*, - schema::{grid_rev_snapshot, grid_rev_snapshot::dsl}, - ConnectionPool, + prelude::*, + schema::{grid_rev_snapshot, grid_rev_snapshot::dsl}, + ConnectionPool, }; use lib_infra::util::timestamp; use std::sync::Arc; pub struct SQLiteDatabaseRevisionSnapshotPersistence { - object_id: String, - pool: Arc, + object_id: String, + pool: Arc, } impl SQLiteDatabaseRevisionSnapshotPersistence { - pub fn new(object_id: &str, pool: Arc) -> Self { - Self { - object_id: object_id.to_string(), - pool, - } + pub fn new(object_id: &str, pool: Arc) -> Self { + Self { + object_id: object_id.to_string(), + pool, } + } - fn gen_snapshot_id(&self, rev_id: i64) -> String { - format!("{}:{}", self.object_id, rev_id) - } + fn gen_snapshot_id(&self, rev_id: i64) -> String { + format!("{}:{}", self.object_id, rev_id) + } } impl RevisionSnapshotPersistence for SQLiteDatabaseRevisionSnapshotPersistence { - fn write_snapshot(&self, rev_id: i64, data: Vec) -> FlowyResult<()> { - let conn = self.pool.get().map_err(internal_error)?; - let snapshot_id = self.gen_snapshot_id(rev_id); - let timestamp = timestamp(); - let record = ( - dsl::snapshot_id.eq(&snapshot_id), - dsl::object_id.eq(&self.object_id), - dsl::rev_id.eq(rev_id), - dsl::base_rev_id.eq(rev_id), - dsl::timestamp.eq(timestamp), - dsl::data.eq(data), - ); - let _ = insert_or_ignore_into(dsl::grid_rev_snapshot) - .values(record) - .execute(&*conn)?; - Ok(()) + fn write_snapshot(&self, rev_id: i64, data: Vec) -> FlowyResult<()> { + let conn = self.pool.get().map_err(internal_error)?; + let snapshot_id = self.gen_snapshot_id(rev_id); + let timestamp = timestamp(); + let record = ( + dsl::snapshot_id.eq(&snapshot_id), + dsl::object_id.eq(&self.object_id), + dsl::rev_id.eq(rev_id), + dsl::base_rev_id.eq(rev_id), + dsl::timestamp.eq(timestamp), + dsl::data.eq(data), + ); + let _ = insert_or_ignore_into(dsl::grid_rev_snapshot) + .values(record) + .execute(&*conn)?; + Ok(()) - // conn.immediate_transaction::<_, FlowyError, _>(|| { - // let filter = dsl::grid_rev_snapshot - // .filter(dsl::object_id.eq(&self.object_id)) - // .filter(dsl::rev_id.eq(rev_id)); - // - // let is_exist: bool = select(exists(filter)).get_result(&*conn)?; - // match is_exist { - // false => { - // let record = ( - // dsl::object_id.eq(&self.object_id), - // dsl::rev_id.eq(rev_id), - // dsl::data.eq(data), - // ); - // insert_or_ignore_into(dsl::grid_rev_snapshot) - // .values(record) - // .execute(&*conn)?; - // } - // true => { - // let affected_row = update(filter).set(dsl::data.eq(data)).execute(&*conn)?; - // debug_assert_eq!(affected_row, 1); - // } - // } - // Ok(()) - // }) - } + // conn.immediate_transaction::<_, FlowyError, _>(|| { + // let filter = dsl::grid_rev_snapshot + // .filter(dsl::object_id.eq(&self.object_id)) + // .filter(dsl::rev_id.eq(rev_id)); + // + // let is_exist: bool = select(exists(filter)).get_result(&*conn)?; + // match is_exist { + // false => { + // let record = ( + // dsl::object_id.eq(&self.object_id), + // dsl::rev_id.eq(rev_id), + // dsl::data.eq(data), + // ); + // insert_or_ignore_into(dsl::grid_rev_snapshot) + // .values(record) + // .execute(&*conn)?; + // } + // true => { + // let affected_row = update(filter).set(dsl::data.eq(data)).execute(&*conn)?; + // debug_assert_eq!(affected_row, 1); + // } + // } + // Ok(()) + // }) + } - fn read_snapshot(&self, rev_id: i64) -> FlowyResult> { - let conn = self.pool.get().map_err(internal_error)?; - let snapshot_id = self.gen_snapshot_id(rev_id); - let record = dsl::grid_rev_snapshot - .filter(dsl::snapshot_id.eq(&snapshot_id)) - .first::(&*conn)?; + fn read_snapshot(&self, rev_id: i64) -> FlowyResult> { + let conn = self.pool.get().map_err(internal_error)?; + let snapshot_id = self.gen_snapshot_id(rev_id); + let record = dsl::grid_rev_snapshot + .filter(dsl::snapshot_id.eq(&snapshot_id)) + .first::(&*conn)?; - Ok(Some(record.into())) - } + Ok(Some(record.into())) + } - fn read_last_snapshot(&self) -> FlowyResult> { - let conn = self.pool.get().map_err(internal_error)?; - let latest_record = dsl::grid_rev_snapshot + fn read_last_snapshot(&self) -> FlowyResult> { + let conn = self.pool.get().map_err(internal_error)?; + let latest_record = dsl::grid_rev_snapshot .filter(dsl::object_id.eq(&self.object_id)) .order(dsl::timestamp.desc()) // .select(max(dsl::rev_id)) // .select((dsl::id, dsl::object_id, dsl::rev_id, dsl::data)) .first::(&*conn)?; - Ok(Some(latest_record.into())) - } + Ok(Some(latest_record.into())) + } } #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] #[table_name = "grid_rev_snapshot"] #[primary_key("snapshot_id")] struct GridSnapshotRecord { - snapshot_id: String, - object_id: String, - rev_id: i64, - base_rev_id: i64, - timestamp: i64, - data: Vec, + snapshot_id: String, + object_id: String, + rev_id: i64, + base_rev_id: i64, + timestamp: i64, + data: Vec, } impl std::convert::From for RevisionSnapshotData { - fn from(record: GridSnapshotRecord) -> Self { - RevisionSnapshotData { - rev_id: record.rev_id, - base_rev_id: record.base_rev_id, - timestamp: record.timestamp, - data: Bytes::from(record.data), - } + fn from(record: GridSnapshotRecord) -> Self { + RevisionSnapshotData { + rev_id: record.rev_id, + base_rev_id: record.base_rev_id, + timestamp: record.timestamp, + data: Bytes::from(record.data), } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_sqlite_impl.rs b/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_sqlite_impl.rs index 93ec18b9b5..5ac6f1f9d9 100644 --- a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_sqlite_impl.rs +++ b/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_sqlite_impl.rs @@ -3,230 +3,247 @@ use diesel::{sql_types::Integer, update, SqliteConnection}; use flowy_error::{internal_error, FlowyError, FlowyResult}; use flowy_revision_persistence::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; use flowy_sqlite::{ - impl_sql_integer_expression, insert_or_ignore_into, - prelude::*, - schema::{grid_rev_table, grid_rev_table::dsl}, - ConnectionPool, + impl_sql_integer_expression, insert_or_ignore_into, + prelude::*, + schema::{grid_rev_table, grid_rev_table::dsl}, + ConnectionPool, }; use lib_infra::util::md5; use revision_model::{Revision, RevisionRange}; use std::sync::Arc; pub struct SQLiteDatabaseRevisionPersistence { - user_id: String, - pub(crate) pool: Arc, + user_id: String, + pub(crate) pool: Arc, } impl RevisionDiskCache> for SQLiteDatabaseRevisionPersistence { - type Error = FlowyError; + type Error = FlowyError; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - GridRevisionSql::create(revision_records, &conn)?; - Ok(()) - } + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + GridRevisionSql::create(revision_records, &conn)?; + Ok(()) + } - fn get_connection(&self) -> Result, Self::Error> { - Ok(self.pool.clone()) - } + fn get_connection(&self) -> Result, Self::Error> { + Ok(self.pool.clone()) + } - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - let records = GridRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?; - Ok(records) - } + fn read_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result, Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + let records = GridRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?; + Ok(records) + } - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - let revisions = GridRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?; - Ok(revisions) - } + fn read_revision_records_with_range( + &self, + object_id: &str, + range: &RevisionRange, + ) -> Result, Self::Error> { + let conn = &*self.pool.get().map_err(internal_error)?; + let revisions = + GridRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?; + Ok(revisions) + } - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - let conn = &*self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - for changeset in changesets { - GridRevisionSql::update(changeset, conn)?; - } - Ok(()) - })?; - Ok(()) - } + fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { + let conn = &*self.pool.get().map_err(internal_error)?; + conn.immediate_transaction::<_, FlowyError, _>(|| { + for changeset in changesets { + GridRevisionSql::update(changeset, conn)?; + } + Ok(()) + })?; + Ok(()) + } - fn delete_revision_records(&self, object_id: &str, rev_ids: Option>) -> Result<(), Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - GridRevisionSql::delete(object_id, rev_ids, conn)?; - Ok(()) - } + fn delete_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result<(), Self::Error> { + let conn = &*self.pool.get().map_err(internal_error)?; + GridRevisionSql::delete(object_id, rev_ids, conn)?; + Ok(()) + } - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - GridRevisionSql::delete(object_id, deleted_rev_ids, &conn)?; - GridRevisionSql::create(inserted_records, &conn)?; - Ok(()) - }) - } + fn delete_and_insert_records( + &self, + object_id: &str, + deleted_rev_ids: Option>, + inserted_records: Vec, + ) -> Result<(), Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + conn.immediate_transaction::<_, FlowyError, _>(|| { + GridRevisionSql::delete(object_id, deleted_rev_ids, &conn)?; + GridRevisionSql::create(inserted_records, &conn)?; + Ok(()) + }) + } } impl SQLiteDatabaseRevisionPersistence { - pub fn new(user_id: &str, pool: Arc) -> Self { - Self { - user_id: user_id.to_owned(), - pool, - } + pub fn new(user_id: &str, pool: Arc) -> Self { + Self { + user_id: user_id.to_owned(), + pool, } + } } struct GridRevisionSql(); impl GridRevisionSql { - fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { - // Batch insert: https://diesel.rs/guides/all-about-inserts.html - let records = revision_records - .into_iter() - .map(|record| { - tracing::trace!( - "[GridRevisionSql] create revision: {}:{:?}", - record.revision.object_id, - record.revision.rev_id - ); - let rev_state: GridRevisionState = record.state.into(); - ( - dsl::object_id.eq(record.revision.object_id), - dsl::base_rev_id.eq(record.revision.base_rev_id), - dsl::rev_id.eq(record.revision.rev_id), - dsl::data.eq(record.revision.bytes), - dsl::state.eq(rev_state), - ) - }) - .collect::>(); - - let _ = insert_or_ignore_into(dsl::grid_rev_table) - .values(&records) - .execute(conn)?; - Ok(()) - } - - fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { - let state: GridRevisionState = changeset.state.clone().into(); - let filter = dsl::grid_rev_table - .filter(dsl::rev_id.eq(changeset.rev_id)) - .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 {:?}", - changeset.rev_id, - changeset.state + fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { + // Batch insert: https://diesel.rs/guides/all-about-inserts.html + let records = revision_records + .into_iter() + .map(|record| { + tracing::trace!( + "[GridRevisionSql] create revision: {}:{:?}", + record.revision.object_id, + record.revision.rev_id ); - Ok(()) + let rev_state: GridRevisionState = record.state.into(); + ( + dsl::object_id.eq(record.revision.object_id), + dsl::base_rev_id.eq(record.revision.base_rev_id), + dsl::rev_id.eq(record.revision.rev_id), + dsl::data.eq(record.revision.bytes), + dsl::state.eq(rev_state), + ) + }) + .collect::>(); + + let _ = insert_or_ignore_into(dsl::grid_rev_table) + .values(&records) + .execute(conn)?; + Ok(()) + } + + fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { + let state: GridRevisionState = changeset.state.clone().into(); + let filter = dsl::grid_rev_table + .filter(dsl::rev_id.eq(changeset.rev_id)) + .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 {:?}", + changeset.rev_id, + changeset.state + ); + Ok(()) + } + + fn read( + user_id: &str, + object_id: &str, + rev_ids: Option>, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let mut sql = dsl::grid_rev_table + .filter(dsl::object_id.eq(object_id)) + .into_boxed(); + if let Some(rev_ids) = rev_ids { + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); + } + let rows = sql + .order(dsl::rev_id.asc()) + .load::(conn)?; + let records = rows + .into_iter() + .map(|row| mk_revision_record_from_table(user_id, row)) + .collect::>(); + + Ok(records) + } + + fn read_with_range( + user_id: &str, + object_id: &str, + range: RevisionRange, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let rev_tables = dsl::grid_rev_table + .filter(dsl::rev_id.ge(range.start)) + .filter(dsl::rev_id.le(range.end)) + .filter(dsl::object_id.eq(object_id)) + .order(dsl::rev_id.asc()) + .load::(conn)?; + + let revisions = rev_tables + .into_iter() + .map(|table| mk_revision_record_from_table(user_id, table)) + .collect::>(); + Ok(revisions) + } + + fn delete( + object_id: &str, + rev_ids: Option>, + conn: &SqliteConnection, + ) -> Result<(), FlowyError> { + let mut sql = diesel::delete(dsl::grid_rev_table).into_boxed(); + sql = sql.filter(dsl::object_id.eq(object_id)); + + if let Some(rev_ids) = rev_ids { + tracing::trace!( + "[GridRevisionSql] Delete revision: {}:{:?}", + object_id, + rev_ids + ); + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); } - fn read( - user_id: &str, - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let mut sql = dsl::grid_rev_table.filter(dsl::object_id.eq(object_id)).into_boxed(); - if let Some(rev_ids) = rev_ids { - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - let rows = sql.order(dsl::rev_id.asc()).load::(conn)?; - let records = rows - .into_iter() - .map(|row| mk_revision_record_from_table(user_id, row)) - .collect::>(); - - Ok(records) - } - - fn read_with_range( - user_id: &str, - object_id: &str, - range: RevisionRange, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let rev_tables = dsl::grid_rev_table - .filter(dsl::rev_id.ge(range.start)) - .filter(dsl::rev_id.le(range.end)) - .filter(dsl::object_id.eq(object_id)) - .order(dsl::rev_id.asc()) - .load::(conn)?; - - let revisions = rev_tables - .into_iter() - .map(|table| mk_revision_record_from_table(user_id, table)) - .collect::>(); - Ok(revisions) - } - - fn delete(object_id: &str, rev_ids: Option>, conn: &SqliteConnection) -> Result<(), FlowyError> { - let mut sql = diesel::delete(dsl::grid_rev_table).into_boxed(); - sql = sql.filter(dsl::object_id.eq(object_id)); - - if let Some(rev_ids) = rev_ids { - tracing::trace!("[GridRevisionSql] Delete revision: {}:{:?}", object_id, rev_ids); - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - - let affected_row = sql.execute(conn)?; - tracing::trace!("[GridRevisionSql] Delete {} rows", affected_row); - Ok(()) - } + let affected_row = sql.execute(conn)?; + tracing::trace!("[GridRevisionSql] Delete {} rows", affected_row); + Ok(()) + } } #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] #[table_name = "grid_rev_table"] struct GridRevisionTable { - id: i32, - object_id: String, - base_rev_id: i64, - rev_id: i64, - data: Vec, - state: GridRevisionState, + id: i32, + object_id: String, + base_rev_id: i64, + rev_id: i64, + data: Vec, + state: GridRevisionState, } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] #[repr(i32)] #[sql_type = "Integer"] pub enum GridRevisionState { - Sync = 0, - Ack = 1, + Sync = 0, + Ack = 1, } impl_sql_integer_expression!(GridRevisionState); impl_rev_state_map!(GridRevisionState); impl std::default::Default for GridRevisionState { - fn default() -> Self { - GridRevisionState::Sync - } + fn default() -> Self { + GridRevisionState::Sync + } } fn mk_revision_record_from_table(_user_id: &str, table: GridRevisionTable) -> SyncRecord { - let md5 = md5(&table.data); - let revision = Revision::new( - &table.object_id, - table.base_rev_id, - table.rev_id, - Bytes::from(table.data), - md5, - ); - SyncRecord { - revision, - state: table.state.into(), - write_to_disk: false, - } + let md5 = md5(&table.data); + let revision = Revision::new( + &table.object_id, + table.base_rev_id, + table.rev_id, + Bytes::from(table.data), + md5, + ); + SyncRecord { + revision, + state: table.state.into(), + write_to_disk: false, + } } diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_view_sqlite_impl.rs b/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_view_sqlite_impl.rs index 5081951e30..1418b93df3 100644 --- a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_view_sqlite_impl.rs +++ b/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/grid_view_sqlite_impl.rs @@ -3,232 +3,247 @@ use diesel::{sql_types::Integer, update, SqliteConnection}; use flowy_error::{internal_error, FlowyError, FlowyResult}; use flowy_revision_persistence::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; use flowy_sqlite::{ - impl_sql_integer_expression, insert_or_ignore_into, - prelude::*, - schema::{grid_view_rev_table, grid_view_rev_table::dsl}, - ConnectionPool, + impl_sql_integer_expression, insert_or_ignore_into, + prelude::*, + schema::{grid_view_rev_table, grid_view_rev_table::dsl}, + ConnectionPool, }; use lib_infra::util::md5; use revision_model::{Revision, RevisionRange}; use std::sync::Arc; pub struct SQLiteGridViewRevisionPersistence { - user_id: String, - pub(crate) pool: Arc, + user_id: String, + pub(crate) pool: Arc, } impl SQLiteGridViewRevisionPersistence { - pub fn new(user_id: &str, pool: Arc) -> Self { - Self { - user_id: user_id.to_owned(), - pool, - } + pub fn new(user_id: &str, pool: Arc) -> Self { + Self { + user_id: user_id.to_owned(), + pool, } + } } impl RevisionDiskCache> for SQLiteGridViewRevisionPersistence { - type Error = FlowyError; + type Error = FlowyError; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - GridViewRevisionSql::create(revision_records, &conn)?; - Ok(()) - } + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + GridViewRevisionSql::create(revision_records, &conn)?; + Ok(()) + } - fn get_connection(&self) -> Result, Self::Error> { - Ok(self.pool.clone()) - } + fn get_connection(&self) -> Result, Self::Error> { + Ok(self.pool.clone()) + } - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - let records = GridViewRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?; - Ok(records) - } + fn read_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result, Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + let records = GridViewRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?; + Ok(records) + } - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, 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)?; - Ok(revisions) - } + fn read_revision_records_with_range( + &self, + object_id: &str, + range: &RevisionRange, + ) -> Result, 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)?; + Ok(revisions) + } - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - let conn = &*self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - for changeset in changesets { - GridViewRevisionSql::update(changeset, conn)?; - } - Ok(()) - })?; - Ok(()) - } + fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { + let conn = &*self.pool.get().map_err(internal_error)?; + conn.immediate_transaction::<_, FlowyError, _>(|| { + for changeset in changesets { + GridViewRevisionSql::update(changeset, conn)?; + } + Ok(()) + })?; + Ok(()) + } - fn delete_revision_records(&self, object_id: &str, rev_ids: Option>) -> Result<(), Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - GridViewRevisionSql::delete(object_id, rev_ids, conn)?; - Ok(()) - } + fn delete_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result<(), Self::Error> { + let conn = &*self.pool.get().map_err(internal_error)?; + GridViewRevisionSql::delete(object_id, rev_ids, conn)?; + Ok(()) + } - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> 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)?; - Ok(()) - }) - } + fn delete_and_insert_records( + &self, + object_id: &str, + deleted_rev_ids: Option>, + inserted_records: Vec, + ) -> 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)?; + Ok(()) + }) + } } struct GridViewRevisionSql(); impl GridViewRevisionSql { - fn create(revision_records: Vec, 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: {}:{:?}", - record.revision.object_id, - record.revision.rev_id - ); - let rev_state: GridViewRevisionState = record.state.into(); - ( - dsl::object_id.eq(record.revision.object_id), - dsl::base_rev_id.eq(record.revision.base_rev_id), - dsl::rev_id.eq(record.revision.rev_id), - dsl::data.eq(record.revision.bytes), - dsl::state.eq(rev_state), - ) - }) - .collect::>(); - - let _ = insert_or_ignore_into(dsl::grid_view_rev_table) - .values(&records) - .execute(conn)?; - Ok(()) - } - - fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { - let state: GridViewRevisionState = changeset.state.clone().into(); - let filter = dsl::grid_view_rev_table - .filter(dsl::rev_id.eq(changeset.rev_id)) - .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 {:?}", - changeset.rev_id, - changeset.state + fn create(revision_records: Vec, 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: {}:{:?}", + record.revision.object_id, + record.revision.rev_id ); - Ok(()) + let rev_state: GridViewRevisionState = record.state.into(); + ( + dsl::object_id.eq(record.revision.object_id), + dsl::base_rev_id.eq(record.revision.base_rev_id), + dsl::rev_id.eq(record.revision.rev_id), + dsl::data.eq(record.revision.bytes), + dsl::state.eq(rev_state), + ) + }) + .collect::>(); + + let _ = insert_or_ignore_into(dsl::grid_view_rev_table) + .values(&records) + .execute(conn)?; + Ok(()) + } + + fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { + let state: GridViewRevisionState = changeset.state.clone().into(); + let filter = dsl::grid_view_rev_table + .filter(dsl::rev_id.eq(changeset.rev_id)) + .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 {:?}", + changeset.rev_id, + changeset.state + ); + Ok(()) + } + + fn read( + user_id: &str, + object_id: &str, + rev_ids: Option>, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let mut sql = dsl::grid_view_rev_table + .filter(dsl::object_id.eq(object_id)) + .into_boxed(); + if let Some(rev_ids) = rev_ids { + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); + } + let rows = sql + .order(dsl::rev_id.asc()) + .load::(conn)?; + let records = rows + .into_iter() + .map(|row| mk_revision_record_from_table(user_id, row)) + .collect::>(); + + Ok(records) + } + + fn read_with_range( + user_id: &str, + object_id: &str, + range: RevisionRange, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let rev_tables = dsl::grid_view_rev_table + .filter(dsl::rev_id.ge(range.start)) + .filter(dsl::rev_id.le(range.end)) + .filter(dsl::object_id.eq(object_id)) + .order(dsl::rev_id.asc()) + .load::(conn)?; + + let revisions = rev_tables + .into_iter() + .map(|table| mk_revision_record_from_table(user_id, table)) + .collect::>(); + Ok(revisions) + } + + fn delete( + object_id: &str, + rev_ids: Option>, + conn: &SqliteConnection, + ) -> Result<(), FlowyError> { + let mut sql = diesel::delete(dsl::grid_view_rev_table).into_boxed(); + sql = sql.filter(dsl::object_id.eq(object_id)); + + if let Some(rev_ids) = rev_ids { + tracing::trace!( + "[GridViewRevisionSql] Delete revision: {}:{:?}", + object_id, + rev_ids + ); + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); } - fn read( - user_id: &str, - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let mut sql = dsl::grid_view_rev_table - .filter(dsl::object_id.eq(object_id)) - .into_boxed(); - if let Some(rev_ids) = rev_ids { - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - let rows = sql.order(dsl::rev_id.asc()).load::(conn)?; - let records = rows - .into_iter() - .map(|row| mk_revision_record_from_table(user_id, row)) - .collect::>(); - - Ok(records) - } - - fn read_with_range( - user_id: &str, - object_id: &str, - range: RevisionRange, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let rev_tables = dsl::grid_view_rev_table - .filter(dsl::rev_id.ge(range.start)) - .filter(dsl::rev_id.le(range.end)) - .filter(dsl::object_id.eq(object_id)) - .order(dsl::rev_id.asc()) - .load::(conn)?; - - let revisions = rev_tables - .into_iter() - .map(|table| mk_revision_record_from_table(user_id, table)) - .collect::>(); - Ok(revisions) - } - - fn delete(object_id: &str, rev_ids: Option>, conn: &SqliteConnection) -> Result<(), FlowyError> { - let mut sql = diesel::delete(dsl::grid_view_rev_table).into_boxed(); - sql = sql.filter(dsl::object_id.eq(object_id)); - - if let Some(rev_ids) = rev_ids { - tracing::trace!("[GridViewRevisionSql] Delete revision: {}:{:?}", object_id, rev_ids); - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - - let affected_row = sql.execute(conn)?; - tracing::trace!("[GridViewRevisionSql] Delete {} rows", affected_row); - Ok(()) - } + let affected_row = sql.execute(conn)?; + tracing::trace!("[GridViewRevisionSql] Delete {} rows", affected_row); + Ok(()) + } } #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] #[table_name = "grid_view_rev_table"] struct GridViewRevisionTable { - id: i32, - object_id: String, - base_rev_id: i64, - rev_id: i64, - data: Vec, - state: GridViewRevisionState, + id: i32, + object_id: String, + base_rev_id: i64, + rev_id: i64, + data: Vec, + state: GridViewRevisionState, } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] #[repr(i32)] #[sql_type = "Integer"] pub enum GridViewRevisionState { - Sync = 0, - Ack = 1, + Sync = 0, + Ack = 1, } impl_sql_integer_expression!(GridViewRevisionState); impl_rev_state_map!(GridViewRevisionState); impl std::default::Default for GridViewRevisionState { - fn default() -> Self { - GridViewRevisionState::Sync - } + fn default() -> Self { + GridViewRevisionState::Sync + } } fn mk_revision_record_from_table(_user_id: &str, table: GridViewRevisionTable) -> SyncRecord { - let md5 = md5(&table.data); - let revision = Revision::new( - &table.object_id, - table.base_rev_id, - table.rev_id, - Bytes::from(table.data), - md5, - ); - SyncRecord { - revision, - state: table.state.into(), - write_to_disk: false, - } + let md5 = md5(&table.data); + let revision = Revision::new( + &table.object_id, + table.base_rev_id, + table.rev_id, + Bytes::from(table.data), + md5, + ); + SyncRecord { + revision, + state: table.state.into(), + write_to_disk: false, + } } diff --git a/frontend/rust-lib/flowy-database/src/services/retry.rs b/frontend/rust-lib/flowy-database/src/services/retry.rs index 49eb5e0b50..9c27002eaf 100644 --- a/frontend/rust-lib/flowy-database/src/services/retry.rs +++ b/frontend/rust-lib/flowy-database/src/services/retry.rs @@ -8,23 +8,23 @@ use std::sync::Arc; use tokio::sync::RwLock; pub struct GetRowDataRetryAction { - pub row_id: String, - pub pad: Arc>, + pub row_id: String, + pub pad: Arc>, } impl Action for GetRowDataRetryAction { - type Future = Pin> + Send + Sync>>; - type Item = Option<(usize, Arc)>; - type Error = FlowyError; + type Future = Pin> + Send + Sync>>; + type Item = Option<(usize, Arc)>; + type Error = FlowyError; - fn run(&mut self) -> Self::Future { - let pad = self.pad.clone(); - let row_id = self.row_id.clone(); - Box::pin(async move { - match pad.try_read() { - Err(_) => Ok(None), - Ok(read_guard) => Ok(read_guard.get_row_rev(&row_id)), - } - }) - } + fn run(&mut self) -> Self::Future { + let pad = self.pad.clone(); + let row_id = self.row_id.clone(); + Box::pin(async move { + match pad.try_read() { + Err(_) => Ok(None), + Ok(read_guard) => Ok(read_guard.get_row_rev(&row_id)), + } + }) + } } diff --git a/frontend/rust-lib/flowy-database/src/services/row/row_builder.rs b/frontend/rust-lib/flowy-database/src/services/row/row_builder.rs index e66d4a5a64..a7f0dc42b1 100644 --- a/frontend/rust-lib/flowy-database/src/services/row/row_builder.rs +++ b/frontend/rust-lib/flowy-database/src/services/row/row_builder.rs @@ -1,6 +1,6 @@ use crate::services::cell::{ - insert_checkbox_cell, insert_date_cell, insert_number_cell, insert_select_option_cell, insert_text_cell, - insert_url_cell, + insert_checkbox_cell, insert_date_cell, insert_number_cell, insert_select_option_cell, + insert_text_cell, insert_url_cell, }; use grid_model::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT}; @@ -9,126 +9,132 @@ use std::collections::HashMap; use std::sync::Arc; pub struct RowRevisionBuilder<'a> { - block_id: String, - field_rev_map: HashMap<&'a String, Arc>, - payload: CreateRowRevisionPayload, + block_id: String, + field_rev_map: HashMap<&'a String, Arc>, + payload: CreateRowRevisionPayload, } impl<'a> RowRevisionBuilder<'a> { - pub fn new(block_id: &str, fields: &'a [Arc]) -> Self { - let field_rev_map = fields - .iter() - .map(|field| (&field.id, field.clone())) - .collect::>>(); + pub fn new(block_id: &str, fields: &'a [Arc]) -> Self { + let field_rev_map = fields + .iter() + .map(|field| (&field.id, field.clone())) + .collect::>>(); - let payload = CreateRowRevisionPayload { - row_id: gen_row_id(), - cell_by_field_id: Default::default(), - height: DEFAULT_ROW_HEIGHT, - visibility: true, - }; + let payload = CreateRowRevisionPayload { + row_id: gen_row_id(), + cell_by_field_id: Default::default(), + height: DEFAULT_ROW_HEIGHT, + visibility: true, + }; - let block_id = block_id.to_string(); + let block_id = block_id.to_string(); - Self { - block_id, - field_rev_map, - payload, - } + Self { + block_id, + field_rev_map, + payload, } + } - pub fn insert_text_cell(&mut self, field_id: &str, data: String) { - match self.field_rev_map.get(&field_id.to_owned()) { - None => tracing::warn!("Can't find the text field with id: {}", field_id), - Some(field_rev) => { - self.payload - .cell_by_field_id - .insert(field_id.to_owned(), insert_text_cell(data, field_rev)); - } - } - } - - pub fn insert_url_cell(&mut self, field_id: &str, data: String) { - match self.field_rev_map.get(&field_id.to_owned()) { - None => tracing::warn!("Can't find the url field with id: {}", field_id), - Some(field_rev) => { - self.payload - .cell_by_field_id - .insert(field_id.to_owned(), insert_url_cell(data, field_rev)); - } - } - } - - pub fn insert_number_cell(&mut self, field_id: &str, num: i64) { - match self.field_rev_map.get(&field_id.to_owned()) { - None => tracing::warn!("Can't find the number field with id: {}", field_id), - Some(field_rev) => { - self.payload - .cell_by_field_id - .insert(field_id.to_owned(), insert_number_cell(num, field_rev)); - } - } - } - - pub fn insert_checkbox_cell(&mut self, field_id: &str, is_check: bool) { - match self.field_rev_map.get(&field_id.to_owned()) { - None => tracing::warn!("Can't find the checkbox field with id: {}", field_id), - Some(field_rev) => { - self.payload - .cell_by_field_id - .insert(field_id.to_owned(), insert_checkbox_cell(is_check, field_rev)); - } - } - } - - pub fn insert_date_cell(&mut self, field_id: &str, timestamp: i64) { - match self.field_rev_map.get(&field_id.to_owned()) { - None => tracing::warn!("Can't find the date field with id: {}", field_id), - Some(field_rev) => { - self.payload - .cell_by_field_id - .insert(field_id.to_owned(), insert_date_cell(timestamp, field_rev)); - } - } - } - - pub fn insert_select_option_cell(&mut self, field_id: &str, option_ids: Vec) { - match self.field_rev_map.get(&field_id.to_owned()) { - None => tracing::warn!("Can't find the select option field with id: {}", field_id), - Some(field_rev) => { - self.payload - .cell_by_field_id - .insert(field_id.to_owned(), insert_select_option_cell(option_ids, field_rev)); - } - } - } - - #[allow(dead_code)] - pub fn height(mut self, height: i32) -> Self { - self.payload.height = height; + pub fn insert_text_cell(&mut self, field_id: &str, data: String) { + match self.field_rev_map.get(&field_id.to_owned()) { + None => tracing::warn!("Can't find the text field with id: {}", field_id), + Some(field_rev) => { self + .payload + .cell_by_field_id + .insert(field_id.to_owned(), insert_text_cell(data, field_rev)); + }, } + } - #[allow(dead_code)] - pub fn visibility(mut self, visibility: bool) -> Self { - self.payload.visibility = visibility; + pub fn insert_url_cell(&mut self, field_id: &str, data: String) { + match self.field_rev_map.get(&field_id.to_owned()) { + None => tracing::warn!("Can't find the url field with id: {}", field_id), + Some(field_rev) => { self + .payload + .cell_by_field_id + .insert(field_id.to_owned(), insert_url_cell(data, field_rev)); + }, } + } - pub fn build(self) -> RowRevision { - RowRevision { - id: self.payload.row_id, - block_id: self.block_id, - cells: self.payload.cell_by_field_id, - height: self.payload.height, - visibility: self.payload.visibility, - } + pub fn insert_number_cell(&mut self, field_id: &str, num: i64) { + match self.field_rev_map.get(&field_id.to_owned()) { + None => tracing::warn!("Can't find the number field with id: {}", field_id), + Some(field_rev) => { + self + .payload + .cell_by_field_id + .insert(field_id.to_owned(), insert_number_cell(num, field_rev)); + }, } + } + + pub fn insert_checkbox_cell(&mut self, field_id: &str, is_check: bool) { + match self.field_rev_map.get(&field_id.to_owned()) { + None => tracing::warn!("Can't find the checkbox field with id: {}", field_id), + Some(field_rev) => { + self.payload.cell_by_field_id.insert( + field_id.to_owned(), + insert_checkbox_cell(is_check, field_rev), + ); + }, + } + } + + pub fn insert_date_cell(&mut self, field_id: &str, timestamp: i64) { + match self.field_rev_map.get(&field_id.to_owned()) { + None => tracing::warn!("Can't find the date field with id: {}", field_id), + Some(field_rev) => { + self + .payload + .cell_by_field_id + .insert(field_id.to_owned(), insert_date_cell(timestamp, field_rev)); + }, + } + } + + pub fn insert_select_option_cell(&mut self, field_id: &str, option_ids: Vec) { + match self.field_rev_map.get(&field_id.to_owned()) { + None => tracing::warn!("Can't find the select option field with id: {}", field_id), + Some(field_rev) => { + self.payload.cell_by_field_id.insert( + field_id.to_owned(), + insert_select_option_cell(option_ids, field_rev), + ); + }, + } + } + + #[allow(dead_code)] + pub fn height(mut self, height: i32) -> Self { + self.payload.height = height; + self + } + + #[allow(dead_code)] + pub fn visibility(mut self, visibility: bool) -> Self { + self.payload.visibility = visibility; + self + } + + pub fn build(self) -> RowRevision { + RowRevision { + id: self.payload.row_id, + block_id: self.block_id, + cells: self.payload.cell_by_field_id, + height: self.payload.height, + visibility: self.payload.visibility, + } + } } pub struct CreateRowRevisionPayload { - pub row_id: String, - pub cell_by_field_id: IndexMap, - pub height: i32, - pub visibility: bool, + pub row_id: String, + pub cell_by_field_id: IndexMap, + pub height: i32, + pub visibility: bool, } diff --git a/frontend/rust-lib/flowy-database/src/services/row/row_loader.rs b/frontend/rust-lib/flowy-database/src/services/row/row_loader.rs index 26874a5910..de240c182e 100644 --- a/frontend/rust-lib/flowy-database/src/services/row/row_loader.rs +++ b/frontend/rust-lib/flowy-database/src/services/row/row_loader.rs @@ -4,31 +4,31 @@ use grid_model::RowRevision; use std::sync::Arc; pub struct DatabaseBlockRowRevision { - pub(crate) block_id: String, - pub row_revs: Vec>, + pub(crate) block_id: String, + pub row_revs: Vec>, } pub struct DatabaseBlockRow { - pub block_id: String, - pub row_ids: Vec, + pub block_id: String, + pub row_ids: Vec, } impl DatabaseBlockRow { - pub fn new(block_id: String, row_ids: Vec) -> Self { - Self { block_id, row_ids } - } + pub fn new(block_id: String, row_ids: Vec) -> Self { + Self { block_id, row_ids } + } } pub(crate) fn make_row_from_row_rev(row_rev: Arc) -> RowPB { - make_rows_from_row_revs(&[row_rev]).pop().unwrap() + make_rows_from_row_revs(&[row_rev]).pop().unwrap() } pub(crate) fn make_rows_from_row_revs(row_revs: &[Arc]) -> Vec { - let make_row = |row_rev: &Arc| RowPB { - block_id: row_rev.block_id.clone(), - id: row_rev.id.clone(), - height: row_rev.height, - }; + let make_row = |row_rev: &Arc| RowPB { + block_id: row_rev.block_id.clone(), + id: row_rev.id.clone(), + height: row_rev.height, + }; - row_revs.iter().map(make_row).collect::>() + row_revs.iter().map(make_row).collect::>() } diff --git a/frontend/rust-lib/flowy-database/src/services/setting/setting_builder.rs b/frontend/rust-lib/flowy-database/src/services/setting/setting_builder.rs index fa9f7dee07..07c0d1cfbd 100644 --- a/frontend/rust-lib/flowy-database/src/services/setting/setting_builder.rs +++ b/frontend/rust-lib/flowy-database/src/services/setting/setting_builder.rs @@ -1,35 +1,37 @@ -use crate::entities::{AlterFilterParams, DatabaseSettingChangesetParams, DeleteFilterParams, LayoutTypePB}; +use crate::entities::{ + AlterFilterParams, DatabaseSettingChangesetParams, DeleteFilterParams, LayoutTypePB, +}; pub struct GridSettingChangesetBuilder { - params: DatabaseSettingChangesetParams, + params: DatabaseSettingChangesetParams, } impl GridSettingChangesetBuilder { - pub fn new(grid_id: &str, layout_type: &LayoutTypePB) -> Self { - let params = DatabaseSettingChangesetParams { - database_id: grid_id.to_string(), - layout_type: layout_type.clone().into(), - insert_filter: None, - delete_filter: None, - insert_group: None, - delete_group: None, - alert_sort: None, - delete_sort: None, - }; - Self { params } - } + pub fn new(grid_id: &str, layout_type: &LayoutTypePB) -> Self { + let params = DatabaseSettingChangesetParams { + database_id: grid_id.to_string(), + layout_type: layout_type.clone().into(), + insert_filter: None, + delete_filter: None, + insert_group: None, + delete_group: None, + alert_sort: None, + delete_sort: None, + }; + Self { params } + } - pub fn insert_filter(mut self, params: AlterFilterParams) -> Self { - self.params.insert_filter = Some(params); - self - } + pub fn insert_filter(mut self, params: AlterFilterParams) -> Self { + self.params.insert_filter = Some(params); + self + } - pub fn delete_filter(mut self, params: DeleteFilterParams) -> Self { - self.params.delete_filter = Some(params); - self - } + pub fn delete_filter(mut self, params: DeleteFilterParams) -> Self { + self.params.delete_filter = Some(params); + self + } - pub fn build(self) -> DatabaseSettingChangesetParams { - self.params - } + pub fn build(self) -> DatabaseSettingChangesetParams { + self.params + } } diff --git a/frontend/rust-lib/flowy-database/src/services/sort/controller.rs b/frontend/rust-lib/flowy-database/src/services/sort/controller.rs index cdd7810530..9adcfc3d8b 100644 --- a/frontend/rust-lib/flowy-database/src/services/sort/controller.rs +++ b/frontend/rust-lib/flowy-database/src/services/sort/controller.rs @@ -2,7 +2,9 @@ use crate::entities::FieldType; use crate::entities::SortChangesetNotificationPB; use crate::services::cell::{AtomicCellDataCache, TypeCellData}; use crate::services::field::{default_order, TypeOptionCellExt}; -use crate::services::sort::{ReorderAllRowsResult, ReorderSingleRowResult, SortChangeset, SortType}; +use crate::services::sort::{ + ReorderAllRowsResult, ReorderSingleRowResult, SortChangeset, SortType, +}; use crate::services::view_editor::{DatabaseViewChanged, GridViewChangedNotifier}; use flowy_error::FlowyResult; use flowy_task::{QualityOfService, Task, TaskContent, TaskDispatcher}; @@ -17,240 +19,280 @@ use std::sync::Arc; use tokio::sync::RwLock; pub trait SortDelegate: Send + Sync { - fn get_sort_rev(&self, sort_type: SortType) -> Fut>>; - /// Returns all the rows after applying grid's filter - fn get_row_revs(&self) -> Fut>>; - fn get_field_rev(&self, field_id: &str) -> Fut>>; - fn get_field_revs(&self, field_ids: Option>) -> Fut>>; + fn get_sort_rev(&self, sort_type: SortType) -> Fut>>; + /// Returns all the rows after applying grid's filter + fn get_row_revs(&self) -> Fut>>; + fn get_field_rev(&self, field_id: &str) -> Fut>>; + fn get_field_revs(&self, field_ids: Option>) -> Fut>>; } pub struct SortController { - view_id: String, - handler_id: String, - delegate: Box, - task_scheduler: Arc>, - sorts: Vec>, - cell_data_cache: AtomicCellDataCache, - row_index_cache: HashMap, - notifier: GridViewChangedNotifier, + view_id: String, + handler_id: String, + delegate: Box, + task_scheduler: Arc>, + sorts: Vec>, + cell_data_cache: AtomicCellDataCache, + row_index_cache: HashMap, + notifier: GridViewChangedNotifier, } impl SortController { - pub fn new( - view_id: &str, - handler_id: &str, - sorts: Vec>, - delegate: T, - task_scheduler: Arc>, - cell_data_cache: AtomicCellDataCache, - notifier: GridViewChangedNotifier, - ) -> Self - where - T: SortDelegate + 'static, - { - Self { - view_id: view_id.to_string(), - handler_id: handler_id.to_string(), - delegate: Box::new(delegate), - task_scheduler, - sorts, - cell_data_cache, - row_index_cache: Default::default(), - notifier, - } + pub fn new( + view_id: &str, + handler_id: &str, + sorts: Vec>, + delegate: T, + task_scheduler: Arc>, + cell_data_cache: AtomicCellDataCache, + notifier: GridViewChangedNotifier, + ) -> Self + where + T: SortDelegate + 'static, + { + Self { + view_id: view_id.to_string(), + handler_id: handler_id.to_string(), + delegate: Box::new(delegate), + task_scheduler, + sorts, + cell_data_cache, + row_index_cache: Default::default(), + notifier, } + } - pub async fn close(&self) { - self.task_scheduler - .write() - .await - .unregister_handler(&self.handler_id) - .await; - } + pub async fn close(&self) { + self + .task_scheduler + .write() + .await + .unregister_handler(&self.handler_id) + .await; + } - pub async fn did_receive_row_changed(&self, row_id: &str) { - let task_type = SortEvent::RowDidChanged(row_id.to_string()); - self.gen_task(task_type, QualityOfService::Background).await; - } + pub async fn did_receive_row_changed(&self, row_id: &str) { + let task_type = SortEvent::RowDidChanged(row_id.to_string()); + self.gen_task(task_type, QualityOfService::Background).await; + } - #[tracing::instrument(name = "process_sort_task", level = "trace", skip_all, err)] - pub async fn process(&mut self, predicate: &str) -> FlowyResult<()> { - let event_type = SortEvent::from_str(predicate).unwrap(); - let mut row_revs = self.delegate.get_row_revs().await; - match event_type { - SortEvent::SortDidChanged => { - self.sort_rows(&mut row_revs).await; - let row_orders = row_revs - .iter() - .map(|row_rev| row_rev.id.clone()) - .collect::>(); + #[tracing::instrument(name = "process_sort_task", level = "trace", skip_all, err)] + pub async fn process(&mut self, predicate: &str) -> FlowyResult<()> { + let event_type = SortEvent::from_str(predicate).unwrap(); + let mut row_revs = self.delegate.get_row_revs().await; + match event_type { + SortEvent::SortDidChanged => { + self.sort_rows(&mut row_revs).await; + let row_orders = row_revs + .iter() + .map(|row_rev| row_rev.id.clone()) + .collect::>(); - let notification = ReorderAllRowsResult { - view_id: self.view_id.clone(), - row_orders, - }; + let notification = ReorderAllRowsResult { + view_id: self.view_id.clone(), + row_orders, + }; - let _ = self - .notifier - .send(DatabaseViewChanged::ReorderAllRowsNotification(notification)); - } - SortEvent::RowDidChanged(row_id) => { - let old_row_index = self.row_index_cache.get(&row_id).cloned(); - self.sort_rows(&mut row_revs).await; - let new_row_index = self.row_index_cache.get(&row_id).cloned(); - match (old_row_index, new_row_index) { - (Some(old_row_index), Some(new_row_index)) => { - if old_row_index == new_row_index { - return Ok(()); - } - let notification = ReorderSingleRowResult { - row_id, - view_id: self.view_id.clone(), - old_index: old_row_index, - new_index: new_row_index, - }; - let _ = self - .notifier - .send(DatabaseViewChanged::ReorderSingleRowNotification(notification)); - } - _ => tracing::trace!("The row index cache is outdated"), - } + let _ = self + .notifier + .send(DatabaseViewChanged::ReorderAllRowsNotification( + notification, + )); + }, + SortEvent::RowDidChanged(row_id) => { + let old_row_index = self.row_index_cache.get(&row_id).cloned(); + self.sort_rows(&mut row_revs).await; + let new_row_index = self.row_index_cache.get(&row_id).cloned(); + match (old_row_index, new_row_index) { + (Some(old_row_index), Some(new_row_index)) => { + if old_row_index == new_row_index { + return Ok(()); } + let notification = ReorderSingleRowResult { + row_id, + view_id: self.view_id.clone(), + old_index: old_row_index, + new_index: new_row_index, + }; + let _ = self + .notifier + .send(DatabaseViewChanged::ReorderSingleRowNotification( + notification, + )); + }, + _ => tracing::trace!("The row index cache is outdated"), } - Ok(()) + }, + } + Ok(()) + } + + #[tracing::instrument(name = "schedule_sort_task", level = "trace", skip(self))] + async fn gen_task(&self, task_type: SortEvent, qos: QualityOfService) { + let task_id = self.task_scheduler.read().await.next_task_id(); + let task = Task::new( + &self.handler_id, + task_id, + TaskContent::Text(task_type.to_string()), + qos, + ); + self.task_scheduler.write().await.add_task(task); + } + + pub async fn sort_rows(&mut self, rows: &mut Vec>) { + if self.sorts.is_empty() { + return; } - #[tracing::instrument(name = "schedule_sort_task", level = "trace", skip(self))] - async fn gen_task(&self, task_type: SortEvent, qos: QualityOfService) { - let task_id = self.task_scheduler.read().await.next_task_id(); - let task = Task::new(&self.handler_id, task_id, TaskContent::Text(task_type.to_string()), qos); - self.task_scheduler.write().await.add_task(task); + let field_revs = self.delegate.get_field_revs(None).await; + for sort in self.sorts.iter() { + rows + .par_sort_by(|left, right| cmp_row(left, right, sort, &field_revs, &self.cell_data_cache)); + } + rows.iter().enumerate().for_each(|(index, row)| { + self.row_index_cache.insert(row.id.to_string(), index); + }); + } + + pub async fn delete_all_sorts(&mut self) { + self.sorts.clear(); + self + .gen_task(SortEvent::SortDidChanged, QualityOfService::Background) + .await; + } + + pub async fn did_update_view_field_type_option(&self, _field_rev: &FieldRevision) { + // + } + + #[tracing::instrument(level = "trace", skip(self))] + pub async fn did_receive_changes( + &mut self, + changeset: SortChangeset, + ) -> SortChangesetNotificationPB { + let mut notification = SortChangesetNotificationPB::new(self.view_id.clone()); + if let Some(insert_sort) = changeset.insert_sort { + if let Some(sort) = self.delegate.get_sort_rev(insert_sort).await { + notification.insert_sorts.push(sort.as_ref().into()); + self.sorts.push(sort); + } } - pub async fn sort_rows(&mut self, rows: &mut Vec>) { - if self.sorts.is_empty() { - return; - } - - let field_revs = self.delegate.get_field_revs(None).await; - for sort in self.sorts.iter() { - rows.par_sort_by(|left, right| cmp_row(left, right, sort, &field_revs, &self.cell_data_cache)); - } - rows.iter().enumerate().for_each(|(index, row)| { - self.row_index_cache.insert(row.id.to_string(), index); - }); + if let Some(delete_sort_type) = changeset.delete_sort { + if let Some(index) = self + .sorts + .iter() + .position(|sort| sort.id == delete_sort_type.sort_id) + { + let sort = self.sorts.remove(index); + notification.delete_sorts.push(sort.as_ref().into()); + } } - pub async fn delete_all_sorts(&mut self) { - self.sorts.clear(); - self.gen_task(SortEvent::SortDidChanged, QualityOfService::Background) - .await; + if let Some(update_sort) = changeset.update_sort { + if let Some(updated_sort) = self.delegate.get_sort_rev(update_sort).await { + notification.update_sorts.push(updated_sort.as_ref().into()); + if let Some(index) = self + .sorts + .iter() + .position(|sort| sort.id == updated_sort.id) + { + self.sorts[index] = updated_sort; + } + } } - pub async fn did_update_view_field_type_option(&self, _field_rev: &FieldRevision) { - // - } - - #[tracing::instrument(level = "trace", skip(self))] - pub async fn did_receive_changes(&mut self, changeset: SortChangeset) -> SortChangesetNotificationPB { - let mut notification = SortChangesetNotificationPB::new(self.view_id.clone()); - if let Some(insert_sort) = changeset.insert_sort { - if let Some(sort) = self.delegate.get_sort_rev(insert_sort).await { - notification.insert_sorts.push(sort.as_ref().into()); - self.sorts.push(sort); - } - } - - if let Some(delete_sort_type) = changeset.delete_sort { - if let Some(index) = self.sorts.iter().position(|sort| sort.id == delete_sort_type.sort_id) { - let sort = self.sorts.remove(index); - notification.delete_sorts.push(sort.as_ref().into()); - } - } - - if let Some(update_sort) = changeset.update_sort { - if let Some(updated_sort) = self.delegate.get_sort_rev(update_sort).await { - notification.update_sorts.push(updated_sort.as_ref().into()); - if let Some(index) = self.sorts.iter().position(|sort| sort.id == updated_sort.id) { - self.sorts[index] = updated_sort; - } - } - } - - if !notification.is_empty() { - self.gen_task(SortEvent::SortDidChanged, QualityOfService::UserInteractive) - .await; - } - tracing::trace!("sort notification: {:?}", notification); - notification + if !notification.is_empty() { + self + .gen_task(SortEvent::SortDidChanged, QualityOfService::UserInteractive) + .await; } + tracing::trace!("sort notification: {:?}", notification); + notification + } } fn cmp_row( - left: &Arc, - right: &Arc, - sort: &Arc, - field_revs: &[Arc], - cell_data_cache: &AtomicCellDataCache, + left: &Arc, + right: &Arc, + sort: &Arc, + field_revs: &[Arc], + cell_data_cache: &AtomicCellDataCache, ) -> Ordering { - let order = match (left.cells.get(&sort.field_id), right.cells.get(&sort.field_id)) { - (Some(left_cell), Some(right_cell)) => { - let field_type: FieldType = sort.field_type.into(); - match field_revs.iter().find(|field_rev| field_rev.id == sort.field_id) { - None => default_order(), - Some(field_rev) => cmp_cell(left_cell, right_cell, field_rev, field_type, cell_data_cache), - } - } - (Some(_), None) => Ordering::Greater, - (None, Some(_)) => Ordering::Less, - _ => default_order(), - }; + let order = match ( + left.cells.get(&sort.field_id), + right.cells.get(&sort.field_id), + ) { + (Some(left_cell), Some(right_cell)) => { + let field_type: FieldType = sort.field_type.into(); + match field_revs + .iter() + .find(|field_rev| field_rev.id == sort.field_id) + { + None => default_order(), + Some(field_rev) => cmp_cell( + left_cell, + right_cell, + field_rev, + field_type, + cell_data_cache, + ), + } + }, + (Some(_), None) => Ordering::Greater, + (None, Some(_)) => Ordering::Less, + _ => default_order(), + }; - // The order is calculated by Ascending. So reverse the order if the SortCondition is descending. - match sort.condition { - SortCondition::Ascending => order, - SortCondition::Descending => order.reverse(), - } + // The order is calculated by Ascending. So reverse the order if the SortCondition is descending. + match sort.condition { + SortCondition::Ascending => order, + SortCondition::Descending => order.reverse(), + } } fn cmp_cell( - left_cell: &CellRevision, - right_cell: &CellRevision, - field_rev: &Arc, - field_type: FieldType, - cell_data_cache: &AtomicCellDataCache, + left_cell: &CellRevision, + right_cell: &CellRevision, + field_rev: &Arc, + field_type: FieldType, + cell_data_cache: &AtomicCellDataCache, ) -> Ordering { - match TypeOptionCellExt::new_with_cell_data_cache(field_rev.as_ref(), Some(cell_data_cache.clone())) - .get_type_option_cell_data_handler(&field_type) - { - None => default_order(), - Some(handler) => { - let cal_order = || { - let left_cell_str = TypeCellData::try_from(left_cell).ok()?.into_inner(); - let right_cell_str = TypeCellData::try_from(right_cell).ok()?.into_inner(); - let order = handler.handle_cell_compare(&left_cell_str, &right_cell_str, field_rev.as_ref()); - Option::::Some(order) - }; + match TypeOptionCellExt::new_with_cell_data_cache( + field_rev.as_ref(), + Some(cell_data_cache.clone()), + ) + .get_type_option_cell_data_handler(&field_type) + { + None => default_order(), + Some(handler) => { + let cal_order = || { + let left_cell_str = TypeCellData::try_from(left_cell).ok()?.into_inner(); + let right_cell_str = TypeCellData::try_from(right_cell).ok()?.into_inner(); + let order = + handler.handle_cell_compare(&left_cell_str, &right_cell_str, field_rev.as_ref()); + Option::::Some(order) + }; - cal_order().unwrap_or_else(default_order) - } - } + cal_order().unwrap_or_else(default_order) + }, + } } #[derive(Serialize, Deserialize, Clone, Debug)] enum SortEvent { - SortDidChanged, - RowDidChanged(String), + SortDidChanged, + RowDidChanged(String), } impl ToString for SortEvent { - fn to_string(&self) -> String { - serde_json::to_string(self).unwrap() - } + fn to_string(&self) -> String { + serde_json::to_string(self).unwrap() + } } impl FromStr for SortEvent { - type Err = serde_json::Error; - fn from_str(s: &str) -> Result { - serde_json::from_str(s) - } + type Err = serde_json::Error; + fn from_str(s: &str) -> Result { + serde_json::from_str(s) + } } diff --git a/frontend/rust-lib/flowy-database/src/services/sort/entities.rs b/frontend/rust-lib/flowy-database/src/services/sort/entities.rs index d711c4b90f..3386e33110 100644 --- a/frontend/rust-lib/flowy-database/src/services/sort/entities.rs +++ b/frontend/rust-lib/flowy-database/src/services/sort/entities.rs @@ -4,98 +4,101 @@ use std::sync::Arc; #[derive(Hash, Eq, PartialEq, Debug, Clone)] pub struct SortType { - pub field_id: String, - pub field_type: FieldType, + pub field_id: String, + pub field_type: FieldType, } impl From for FieldTypeRevision { - fn from(sort_type: SortType) -> Self { - sort_type.field_type.into() - } + fn from(sort_type: SortType) -> Self { + sort_type.field_type.into() + } } impl std::convert::From<&AlterSortParams> for SortType { - fn from(params: &AlterSortParams) -> Self { - Self { - field_id: params.field_id.clone(), - field_type: params.field_type.into(), - } + fn from(params: &AlterSortParams) -> Self { + Self { + field_id: params.field_id.clone(), + field_type: params.field_type.into(), } + } } impl std::convert::From<&Arc> for SortType { - fn from(rev: &Arc) -> Self { - Self { - field_id: rev.id.clone(), - field_type: rev.ty.into(), - } + fn from(rev: &Arc) -> Self { + Self { + field_id: rev.id.clone(), + field_type: rev.ty.into(), } + } } #[derive(Clone)] pub struct ReorderAllRowsResult { - pub view_id: String, - pub row_orders: Vec, + pub view_id: String, + pub row_orders: Vec, } impl ReorderAllRowsResult { - pub fn new(view_id: String, row_orders: Vec) -> Self { - Self { view_id, row_orders } + pub fn new(view_id: String, row_orders: Vec) -> Self { + Self { + view_id, + row_orders, } + } } #[derive(Clone)] pub struct ReorderSingleRowResult { - pub view_id: String, - pub row_id: String, - pub old_index: usize, - pub new_index: usize, + pub view_id: String, + pub row_id: String, + pub old_index: usize, + pub new_index: usize, } #[derive(Debug)] pub struct SortChangeset { - pub(crate) insert_sort: Option, - pub(crate) update_sort: Option, - pub(crate) delete_sort: Option, + pub(crate) insert_sort: Option, + pub(crate) update_sort: Option, + pub(crate) delete_sort: Option, } impl SortChangeset { - pub fn from_insert(sort: SortType) -> Self { - Self { - insert_sort: Some(sort), - update_sort: None, - delete_sort: None, - } + pub fn from_insert(sort: SortType) -> Self { + Self { + insert_sort: Some(sort), + update_sort: None, + delete_sort: None, } + } - pub fn from_update(sort: SortType) -> Self { - Self { - insert_sort: None, - update_sort: Some(sort), - delete_sort: None, - } + pub fn from_update(sort: SortType) -> Self { + Self { + insert_sort: None, + update_sort: Some(sort), + delete_sort: None, } + } - pub fn from_delete(deleted_sort: DeletedSortType) -> Self { - Self { - insert_sort: None, - update_sort: None, - delete_sort: Some(deleted_sort), - } + pub fn from_delete(deleted_sort: DeletedSortType) -> Self { + Self { + insert_sort: None, + update_sort: None, + delete_sort: Some(deleted_sort), } + } } #[derive(Debug)] pub struct DeletedSortType { - pub sort_type: SortType, - pub sort_id: String, + pub sort_type: SortType, + pub sort_id: String, } impl std::convert::From for DeletedSortType { - fn from(params: DeleteSortParams) -> Self { - Self { - sort_type: params.sort_type, - sort_id: params.sort_id, - } + fn from(params: DeleteSortParams) -> Self { + Self { + sort_type: params.sort_type, + sort_id: params.sort_id, } + } } diff --git a/frontend/rust-lib/flowy-database/src/services/sort/task.rs b/frontend/rust-lib/flowy-database/src/services/sort/task.rs index 5be54beb47..9bac020e8d 100644 --- a/frontend/rust-lib/flowy-database/src/services/sort/task.rs +++ b/frontend/rust-lib/flowy-database/src/services/sort/task.rs @@ -5,41 +5,41 @@ use std::sync::Arc; use tokio::sync::RwLock; pub struct SortTaskHandler { - handler_id: String, - #[allow(dead_code)] - sort_controller: Arc>, + handler_id: String, + #[allow(dead_code)] + sort_controller: Arc>, } impl SortTaskHandler { - pub fn new(handler_id: String, sort_controller: Arc>) -> Self { - Self { - handler_id, - sort_controller, - } + pub fn new(handler_id: String, sort_controller: Arc>) -> Self { + Self { + handler_id, + sort_controller, } + } } impl TaskHandler for SortTaskHandler { - fn handler_id(&self) -> &str { - &self.handler_id - } + fn handler_id(&self) -> &str { + &self.handler_id + } - fn handler_name(&self) -> &str { - "SortTaskHandler" - } + fn handler_name(&self) -> &str { + "SortTaskHandler" + } - fn run(&self, content: TaskContent) -> BoxResultFuture<(), anyhow::Error> { - let sort_controller = self.sort_controller.clone(); - Box::pin(async move { - if let TaskContent::Text(predicate) = content { - sort_controller - .write() - .await - .process(&predicate) - .await - .map_err(anyhow::Error::from)?; - } - Ok(()) - }) - } + fn run(&self, content: TaskContent) -> BoxResultFuture<(), anyhow::Error> { + let sort_controller = self.sort_controller.clone(); + Box::pin(async move { + if let TaskContent::Text(predicate) = content { + sort_controller + .write() + .await + .process(&predicate) + .await + .map_err(anyhow::Error::from)?; + } + Ok(()) + }) + } } diff --git a/frontend/rust-lib/flowy-database/src/services/view_editor/changed_notifier.rs b/frontend/rust-lib/flowy-database/src/services/view_editor/changed_notifier.rs index defb6fa76e..52605ec2dc 100644 --- a/frontend/rust-lib/flowy-database/src/services/view_editor/changed_notifier.rs +++ b/frontend/rust-lib/flowy-database/src/services/view_editor/changed_notifier.rs @@ -8,59 +8,67 @@ use tokio::sync::broadcast; #[derive(Clone)] pub enum DatabaseViewChanged { - FilterNotification(FilterResultNotification), - ReorderAllRowsNotification(ReorderAllRowsResult), - ReorderSingleRowNotification(ReorderSingleRowResult), + FilterNotification(FilterResultNotification), + ReorderAllRowsNotification(ReorderAllRowsResult), + ReorderSingleRowNotification(ReorderSingleRowResult), } pub type GridViewChangedNotifier = broadcast::Sender; -pub(crate) struct GridViewChangedReceiverRunner(pub(crate) Option>); +pub(crate) struct GridViewChangedReceiverRunner( + pub(crate) Option>, +); impl GridViewChangedReceiverRunner { - pub(crate) async fn run(mut self) { - let mut receiver = self.0.take().expect("Only take once"); - let stream = stream! { - loop { - match receiver.recv().await { - Ok(changed) => yield changed, - Err(_e) => break, - } + pub(crate) async fn run(mut self) { + let mut receiver = self.0.take().expect("Only take once"); + let stream = stream! { + loop { + match receiver.recv().await { + Ok(changed) => yield changed, + Err(_e) => break, } - }; - stream - .for_each(|changed| async { - match changed { - DatabaseViewChanged::FilterNotification(notification) => { - let changeset = ViewRowsVisibilityChangesetPB { - view_id: notification.view_id, - visible_rows: notification.visible_rows, - invisible_rows: notification.invisible_rows, - }; + } + }; + stream + .for_each(|changed| async { + match changed { + DatabaseViewChanged::FilterNotification(notification) => { + let changeset = ViewRowsVisibilityChangesetPB { + view_id: notification.view_id, + visible_rows: notification.visible_rows, + invisible_rows: notification.invisible_rows, + }; - send_notification(&changeset.view_id, DatabaseNotification::DidUpdateViewRowsVisibility) - .payload(changeset) - .send() - } - DatabaseViewChanged::ReorderAllRowsNotification(notification) => { - let row_orders = ReorderAllRowsPB { - row_orders: notification.row_orders, - }; - send_notification(¬ification.view_id, DatabaseNotification::DidReorderRows) - .payload(row_orders) - .send() - } - DatabaseViewChanged::ReorderSingleRowNotification(notification) => { - let reorder_row = ReorderSingleRowPB { - row_id: notification.row_id, - old_index: notification.old_index as i32, - new_index: notification.new_index as i32, - }; - send_notification(¬ification.view_id, DatabaseNotification::DidReorderSingleRow) - .payload(reorder_row) - .send() - } - } - }) - .await; - } + send_notification( + &changeset.view_id, + DatabaseNotification::DidUpdateViewRowsVisibility, + ) + .payload(changeset) + .send() + }, + DatabaseViewChanged::ReorderAllRowsNotification(notification) => { + let row_orders = ReorderAllRowsPB { + row_orders: notification.row_orders, + }; + send_notification(¬ification.view_id, DatabaseNotification::DidReorderRows) + .payload(row_orders) + .send() + }, + DatabaseViewChanged::ReorderSingleRowNotification(notification) => { + let reorder_row = ReorderSingleRowPB { + row_id: notification.row_id, + old_index: notification.old_index as i32, + new_index: notification.new_index as i32, + }; + send_notification( + ¬ification.view_id, + DatabaseNotification::DidReorderSingleRow, + ) + .payload(reorder_row) + .send() + }, + } + }) + .await; + } } diff --git a/frontend/rust-lib/flowy-database/src/services/view_editor/editor.rs b/frontend/rust-lib/flowy-database/src/services/view_editor/editor.rs index a37f3480a9..485c110747 100644 --- a/frontend/rust-lib/flowy-database/src/services/view_editor/editor.rs +++ b/frontend/rust-lib/flowy-database/src/services/view_editor/editor.rs @@ -3,24 +3,30 @@ use crate::notification::{send_notification, DatabaseNotification}; use crate::services::block_manager::DatabaseBlockEvent; use crate::services::cell::{AtomicCellDataCache, TypeCellData}; use crate::services::field::{RowSingleCellData, TypeOptionCellDataHandler}; -use crate::services::filter::{FilterChangeset, FilterController, FilterTaskHandler, FilterType, UpdatedFilterType}; +use crate::services::filter::{ + FilterChangeset, FilterController, FilterTaskHandler, FilterType, UpdatedFilterType, +}; use crate::services::group::{ - default_group_configuration, find_group_field, make_group_controller, Group, GroupConfigurationReader, - GroupController, MoveGroupRowContext, + default_group_configuration, find_group_field, make_group_controller, Group, + GroupConfigurationReader, GroupController, MoveGroupRowContext, }; use crate::services::row::DatabaseBlockRowRevision; -use crate::services::sort::{DeletedSortType, SortChangeset, SortController, SortTaskHandler, SortType}; +use crate::services::sort::{ + DeletedSortType, SortChangeset, SortController, SortTaskHandler, SortType, +}; use crate::services::view_editor::changed_notifier::GridViewChangedNotifier; use crate::services::view_editor::trait_impl::*; use crate::services::view_editor::GridViewChangedReceiverRunner; -use flowy_client_sync::client_database::{make_grid_view_operations, GridViewRevisionChangeset, GridViewRevisionPad}; +use flowy_client_sync::client_database::{ + make_grid_view_operations, GridViewRevisionChangeset, GridViewRevisionPad, +}; use flowy_error::FlowyResult; use flowy_revision::RevisionManager; use flowy_sqlite::ConnectionPool; use flowy_task::TaskDispatcher; use grid_model::{ - gen_grid_filter_id, gen_grid_sort_id, FieldRevision, FieldTypeRevision, FilterRevision, LayoutRevision, - RowChangeset, RowRevision, SortRevision, + gen_grid_filter_id, gen_grid_sort_id, FieldRevision, FieldTypeRevision, FilterRevision, + LayoutRevision, RowChangeset, RowRevision, SortRevision, }; use lib_infra::async_trait::async_trait; use lib_infra::future::Fut; @@ -33,875 +39,951 @@ use std::sync::Arc; use tokio::sync::{broadcast, RwLock}; pub trait DatabaseViewEditorDelegate: Send + Sync + 'static { - /// If the field_ids is None, then it will return all the field revisions - fn get_field_revs(&self, field_ids: Option>) -> Fut>>; + /// If the field_ids is None, then it will return all the field revisions + fn get_field_revs(&self, field_ids: Option>) -> Fut>>; - /// Returns the field with the field_id - fn get_field_rev(&self, field_id: &str) -> Fut>>; + /// Returns the field with the field_id + fn get_field_rev(&self, field_id: &str) -> Fut>>; - /// Returns the index of the row with row_id - fn index_of_row(&self, row_id: &str) -> Fut>; + /// Returns the index of the row with row_id + fn index_of_row(&self, row_id: &str) -> Fut>; - /// Returns the `index` and `RowRevision` with row_id - fn get_row_rev(&self, row_id: &str) -> Fut)>>; + /// Returns the `index` and `RowRevision` with row_id + fn get_row_rev(&self, row_id: &str) -> Fut)>>; - /// Returns all the rows that the block has. If the passed-in block_ids is None, then will return all the rows - /// The relationship between the grid and the block is: - /// A grid has a list of blocks - /// A block has a list of rows - /// A row has a list of cells - /// - fn get_row_revs(&self, block_ids: Option>) -> Fut>>; + /// Returns all the rows that the block has. If the passed-in block_ids is None, then will return all the rows + /// The relationship between the grid and the block is: + /// A grid has a list of blocks + /// A block has a list of rows + /// A row has a list of cells + /// + fn get_row_revs(&self, block_ids: Option>) -> Fut>>; - /// Get all the blocks that the current Grid has. - /// One grid has a list of blocks - fn get_blocks(&self) -> Fut>; + /// Get all the blocks that the current Grid has. + /// One grid has a list of blocks + fn get_blocks(&self) -> Fut>; - /// Returns a `TaskDispatcher` used to poll a `Task` - fn get_task_scheduler(&self) -> Arc>; + /// Returns a `TaskDispatcher` used to poll a `Task` + fn get_task_scheduler(&self) -> Arc>; - fn get_type_option_cell_handler( - &self, - field_rev: &FieldRevision, - field_type: &FieldType, - ) -> Option>; + fn get_type_option_cell_handler( + &self, + field_rev: &FieldRevision, + field_type: &FieldType, + ) -> Option>; } pub struct DatabaseViewRevisionEditor { - user_id: String, - view_id: String, - pad: Arc>, - rev_manager: Arc>>, - delegate: Arc, - group_controller: Arc>>, - filter_controller: Arc, - sort_controller: Arc>, - pub notifier: GridViewChangedNotifier, + user_id: String, + view_id: String, + pad: Arc>, + rev_manager: Arc>>, + delegate: Arc, + group_controller: Arc>>, + filter_controller: Arc, + sort_controller: Arc>, + pub notifier: GridViewChangedNotifier, } impl DatabaseViewRevisionEditor { - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn new( - user_id: &str, - token: &str, - view_id: String, - delegate: Arc, - cell_data_cache: AtomicCellDataCache, - mut rev_manager: RevisionManager>, - ) -> FlowyResult { - let (notifier, _) = broadcast::channel(100); - tokio::spawn(GridViewChangedReceiverRunner(Some(notifier.subscribe())).run()); - let cloud = Arc::new(GridViewRevisionCloudService { - token: token.to_owned(), - }); + #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn new( + user_id: &str, + token: &str, + view_id: String, + delegate: Arc, + cell_data_cache: AtomicCellDataCache, + mut rev_manager: RevisionManager>, + ) -> FlowyResult { + let (notifier, _) = broadcast::channel(100); + tokio::spawn(GridViewChangedReceiverRunner(Some(notifier.subscribe())).run()); + let cloud = Arc::new(GridViewRevisionCloudService { + token: token.to_owned(), + }); - let view_rev_pad = match rev_manager.initialize::(Some(cloud)).await { - Ok(pad) => pad, - Err(err) => { - // It shouldn't be here, because the snapshot should come to recue. - tracing::error!("Deserialize grid view revisions failed: {}", err); - let view = GridViewRevisionPad::new(view_id.to_owned(), view_id.to_owned(), LayoutRevision::Grid); - let bytes = make_grid_view_operations(&view).json_bytes(); - let reset_revision = Revision::initial_revision(&view_id, bytes); - let _ = rev_manager.reset_object(vec![reset_revision]).await; - view - } + let view_rev_pad = match rev_manager + .initialize::(Some(cloud)) + .await + { + Ok(pad) => pad, + Err(err) => { + // It shouldn't be here, because the snapshot should come to recue. + tracing::error!("Deserialize grid view revisions failed: {}", err); + let view = + GridViewRevisionPad::new(view_id.to_owned(), view_id.to_owned(), LayoutRevision::Grid); + let bytes = make_grid_view_operations(&view).json_bytes(); + let reset_revision = Revision::initial_revision(&view_id, bytes); + let _ = rev_manager.reset_object(vec![reset_revision]).await; + view + }, + }; + + let view_rev_pad = Arc::new(RwLock::new(view_rev_pad)); + let rev_manager = Arc::new(rev_manager); + let group_controller = new_group_controller( + user_id.to_owned(), + view_id.clone(), + view_rev_pad.clone(), + rev_manager.clone(), + delegate.clone(), + ) + .await?; + + let user_id = user_id.to_owned(); + let group_controller = Arc::new(RwLock::new(group_controller)); + let filter_controller = make_filter_controller( + &view_id, + delegate.clone(), + notifier.clone(), + cell_data_cache.clone(), + view_rev_pad.clone(), + ) + .await; + + let sort_controller = make_sort_controller( + &view_id, + delegate.clone(), + notifier.clone(), + filter_controller.clone(), + view_rev_pad.clone(), + cell_data_cache, + ) + .await; + Ok(Self { + pad: view_rev_pad, + user_id, + view_id, + rev_manager, + delegate, + group_controller, + filter_controller, + sort_controller, + notifier, + }) + } + + #[tracing::instrument(name = "close grid 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; + } + + pub async fn handle_block_event(&self, event: Cow<'_, DatabaseBlockEvent>) { + let changeset = match event.into_owned() { + DatabaseBlockEvent::InsertRow { block_id: _, row } => { + // + ViewRowsChangesetPB::from_insert(self.view_id.clone(), vec![row]) + }, + DatabaseBlockEvent::UpdateRow { block_id: _, row } => { + // + ViewRowsChangesetPB::from_update(self.view_id.clone(), vec![row]) + }, + DatabaseBlockEvent::DeleteRow { + block_id: _, + row_id, + } => { + // + ViewRowsChangesetPB::from_delete(self.view_id.clone(), vec![row_id]) + }, + DatabaseBlockEvent::Move { + block_id: _, + deleted_row_id, + inserted_row, + } => { + // + ViewRowsChangesetPB::from_move( + self.view_id.clone(), + vec![deleted_row_id], + vec![inserted_row], + ) + }, + }; + + send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows) + .payload(changeset) + .send(); + } + + pub async fn sort_rows(&self, rows: &mut Vec>) { + self.sort_controller.write().await.sort_rows(rows).await + } + + pub async fn filter_rows(&self, _block_id: &str, rows: &mut Vec>) { + self.filter_controller.filter_row_revs(rows).await; + } + + pub async fn duplicate_view_data(&self) -> FlowyResult { + let json_str = self.pad.read().await.json_str()?; + Ok(json_str) + } + + pub async fn will_create_view_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) { + if params.group_id.is_none() { + return; + } + let group_id = params.group_id.as_ref().unwrap(); + let _ = self + .mut_group_controller(|group_controller, field_rev| { + group_controller.will_create_row(row_rev, &field_rev, group_id); + Ok(()) + }) + .await; + } + + pub async fn did_create_view_row(&self, row_pb: &RowPB, params: &CreateRowParams) { + // Send the group notification if the current view has groups + match params.group_id.as_ref() { + None => {}, + Some(group_id) => { + let index = match params.start_row_id { + None => Some(0), + Some(_) => None, }; - let view_rev_pad = Arc::new(RwLock::new(view_rev_pad)); - let rev_manager = Arc::new(rev_manager); - let group_controller = new_group_controller( - user_id.to_owned(), - view_id.clone(), - view_rev_pad.clone(), - rev_manager.clone(), - delegate.clone(), - ) - .await?; - - let user_id = user_id.to_owned(); - let group_controller = Arc::new(RwLock::new(group_controller)); - let filter_controller = make_filter_controller( - &view_id, - delegate.clone(), - notifier.clone(), - cell_data_cache.clone(), - view_rev_pad.clone(), - ) - .await; - - let sort_controller = make_sort_controller( - &view_id, - delegate.clone(), - notifier.clone(), - filter_controller.clone(), - view_rev_pad.clone(), - cell_data_cache, - ) - .await; - Ok(Self { - pad: view_rev_pad, - user_id, - view_id, - rev_manager, - delegate, - group_controller, - filter_controller, - sort_controller, - notifier, - }) - } - - #[tracing::instrument(name = "close grid 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; - } - - pub async fn handle_block_event(&self, event: Cow<'_, DatabaseBlockEvent>) { - let changeset = match event.into_owned() { - DatabaseBlockEvent::InsertRow { block_id: _, row } => { - // - ViewRowsChangesetPB::from_insert(self.view_id.clone(), vec![row]) - } - DatabaseBlockEvent::UpdateRow { block_id: _, row } => { - // - ViewRowsChangesetPB::from_update(self.view_id.clone(), vec![row]) - } - DatabaseBlockEvent::DeleteRow { block_id: _, row_id } => { - // - ViewRowsChangesetPB::from_delete(self.view_id.clone(), vec![row_id]) - } - DatabaseBlockEvent::Move { - block_id: _, - deleted_row_id, - inserted_row, - } => { - // - ViewRowsChangesetPB::from_move(self.view_id.clone(), vec![deleted_row_id], vec![inserted_row]) - } + self + .group_controller + .write() + .await + .did_create_row(row_pb, group_id); + let inserted_row = InsertedRowPB { + row: row_pb.clone(), + index, + is_new: true, }; + let changeset = GroupRowsNotificationPB::insert(group_id.clone(), vec![inserted_row]); + self.notify_did_update_group_rows(changeset).await; + }, + } + } - send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows) - .payload(changeset) - .send(); + #[tracing::instrument(level = "trace", skip_all)] + pub async fn did_delete_view_row(&self, row_rev: &RowRevision) { + // Send the group notification if the current view has groups; + let result = self + .mut_group_controller(|group_controller, field_rev| { + group_controller.did_delete_delete_row(row_rev, &field_rev) + }) + .await; + + if let Some(result) = result { + tracing::trace!("Delete row in view changeset: {:?}", result.row_changesets); + for changeset in result.row_changesets { + self.notify_did_update_group_rows(changeset).await; + } + } + } + + pub async fn did_update_view_row( + &self, + old_row_rev: Option>, + row_rev: &RowRevision, + ) { + let result = self + .mut_group_controller(|group_controller, field_rev| { + Ok(group_controller.did_update_group_row(&old_row_rev, row_rev, &field_rev)) + }) + .await; + + if let Some(Ok(result)) = result { + let mut changeset = GroupChangesetPB { + view_id: self.view_id.clone(), + ..Default::default() + }; + if let Some(inserted_group) = result.inserted_group { + tracing::trace!("Create group after editing the row: {:?}", inserted_group); + changeset.inserted_groups.push(inserted_group); + } + if let Some(delete_group) = result.deleted_group { + tracing::trace!("Delete group after editing the row: {:?}", delete_group); + changeset.deleted_groups.push(delete_group.group_id); + } + self.notify_did_update_groups(changeset).await; + + tracing::trace!( + "Group changesets after editing the row: {:?}", + result.row_changesets + ); + for changeset in result.row_changesets { + self.notify_did_update_group_rows(changeset).await; + } } - pub async fn sort_rows(&self, rows: &mut Vec>) { - self.sort_controller.write().await.sort_rows(rows).await - } - - pub async fn filter_rows(&self, _block_id: &str, rows: &mut Vec>) { - self.filter_controller.filter_row_revs(rows).await; - } - - pub async fn duplicate_view_data(&self) -> FlowyResult { - let json_str = self.pad.read().await.json_str()?; - Ok(json_str) - } - - pub async fn will_create_view_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) { - if params.group_id.is_none() { - return; - } - let group_id = params.group_id.as_ref().unwrap(); - let _ = self - .mut_group_controller(|group_controller, field_rev| { - group_controller.will_create_row(row_rev, &field_rev, group_id); - Ok(()) - }) - .await; - } - - pub async fn did_create_view_row(&self, row_pb: &RowPB, params: &CreateRowParams) { - // Send the group notification if the current view has groups - match params.group_id.as_ref() { - None => {} - Some(group_id) => { - let index = match params.start_row_id { - None => Some(0), - Some(_) => None, - }; - - self.group_controller.write().await.did_create_row(row_pb, group_id); - let inserted_row = InsertedRowPB { - row: row_pb.clone(), - index, - is_new: true, - }; - let changeset = GroupRowsNotificationPB::insert(group_id.clone(), vec![inserted_row]); - self.notify_did_update_group_rows(changeset).await; - } - } - } - - #[tracing::instrument(level = "trace", skip_all)] - pub async fn did_delete_view_row(&self, row_rev: &RowRevision) { - // Send the group notification if the current view has groups; - let result = self - .mut_group_controller(|group_controller, field_rev| { - group_controller.did_delete_delete_row(row_rev, &field_rev) - }) - .await; - - if let Some(result) = result { - tracing::trace!("Delete row in view changeset: {:?}", result.row_changesets); - for changeset in result.row_changesets { - self.notify_did_update_group_rows(changeset).await; - } - } - } - - pub async fn did_update_view_row(&self, old_row_rev: Option>, row_rev: &RowRevision) { - let result = self - .mut_group_controller(|group_controller, field_rev| { - Ok(group_controller.did_update_group_row(&old_row_rev, row_rev, &field_rev)) - }) - .await; - - if let Some(Ok(result)) = result { - let mut changeset = GroupChangesetPB { - view_id: self.view_id.clone(), - ..Default::default() - }; - if let Some(inserted_group) = result.inserted_group { - tracing::trace!("Create group after editing the row: {:?}", inserted_group); - changeset.inserted_groups.push(inserted_group); - } - if let Some(delete_group) = result.deleted_group { - tracing::trace!("Delete group after editing the row: {:?}", delete_group); - changeset.deleted_groups.push(delete_group.group_id); - } - self.notify_did_update_groups(changeset).await; - - tracing::trace!("Group changesets after editing the row: {:?}", result.row_changesets); - for changeset in result.row_changesets { - self.notify_did_update_group_rows(changeset).await; - } - } - - let filter_controller = self.filter_controller.clone(); - let sort_controller = self.sort_controller.clone(); - let row_id = row_rev.id.clone(); - tokio::spawn(async move { - filter_controller.did_receive_row_changed(&row_id).await; - sort_controller.read().await.did_receive_row_changed(&row_id).await; - }); - } - - pub async fn move_view_group_row( - &self, - row_rev: &RowRevision, - row_changeset: &mut RowChangeset, - to_group_id: &str, - to_row_id: Option, - ) { - let result = self - .mut_group_controller(|group_controller, field_rev| { - let move_row_context = MoveGroupRowContext { - row_rev, - row_changeset, - field_rev: field_rev.as_ref(), - to_group_id, - to_row_id, - }; - group_controller.move_group_row(move_row_context) - }) - .await; - - if let Some(result) = result { - let mut changeset = GroupChangesetPB { - view_id: self.view_id.clone(), - ..Default::default() - }; - if let Some(delete_group) = result.deleted_group { - tracing::info!("Delete group after moving the row: {:?}", delete_group); - changeset.deleted_groups.push(delete_group.group_id); - } - self.notify_did_update_groups(changeset).await; - - for changeset in result.row_changesets { - self.notify_did_update_group_rows(changeset).await; - } - } - } - /// Only call once after grid view editor initialized - #[tracing::instrument(level = "trace", skip(self))] - pub async fn load_view_groups(&self) -> FlowyResult> { - let groups = self - .group_controller - .read() - .await - .groups() - .into_iter() - .cloned() - .collect::>(); - tracing::trace!("Number of groups: {}", groups.len()); - Ok(groups.into_iter().map(GroupPB::from).collect()) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn move_view_group(&self, params: MoveGroupParams) -> FlowyResult<()> { - self.group_controller - .write() - .await - .move_group(¶ms.from_group_id, ¶ms.to_group_id)?; - match self.group_controller.read().await.get_group(¶ms.from_group_id) { - None => tracing::warn!("Can not find the group with id: {}", params.from_group_id), - Some((index, group)) => { - let inserted_group = InsertedGroupPB { - group: GroupPB::from(group), - index: index as i32, - }; - - let changeset = GroupChangesetPB { - view_id: self.view_id.clone(), - inserted_groups: vec![inserted_group], - deleted_groups: vec![params.from_group_id.clone()], - update_groups: vec![], - initial_groups: vec![], - }; - - self.notify_did_update_groups(changeset).await; - } - } - Ok(()) - } - - pub async fn group_id(&self) -> String { - self.group_controller.read().await.field_id().to_string() - } - - /// Initialize new group when grouping by a new field - /// - pub async fn initialize_new_group(&self, params: InsertGroupParams) -> FlowyResult<()> { - if let Some(field_rev) = self.delegate.get_field_rev(¶ms.field_id).await { - self.modify(|pad| { - let configuration = default_group_configuration(&field_rev); - let changeset = - pad.insert_or_update_group_configuration(¶ms.field_id, ¶ms.field_type_rev, configuration)?; - Ok(changeset) - }) - .await?; - } - if self.group_controller.read().await.field_id() != params.field_id { - self.group_by_view_field(¶ms.field_id).await?; - self.notify_did_update_setting().await; - } - Ok(()) - } - - pub async fn delete_view_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { - self.modify(|pad| { - let changeset = pad.delete_group(¶ms.group_id, ¶ms.field_id, ¶ms.field_type_rev)?; - Ok(changeset) - }) + let filter_controller = self.filter_controller.clone(); + let sort_controller = self.sort_controller.clone(); + let row_id = row_rev.id.clone(); + tokio::spawn(async move { + filter_controller.did_receive_row_changed(&row_id).await; + sort_controller + .read() .await - } + .did_receive_row_changed(&row_id) + .await; + }); + } - pub async fn get_view_setting(&self) -> DatabaseViewSettingPB { - let field_revs = self.delegate.get_field_revs(None).await; - make_grid_setting(&*self.pad.read().await, &field_revs) - } + pub async fn move_view_group_row( + &self, + row_rev: &RowRevision, + row_changeset: &mut RowChangeset, + to_group_id: &str, + to_row_id: Option, + ) { + let result = self + .mut_group_controller(|group_controller, field_rev| { + let move_row_context = MoveGroupRowContext { + row_rev, + row_changeset, + field_rev: field_rev.as_ref(), + to_group_id, + to_row_id, + }; + group_controller.move_group_row(move_row_context) + }) + .await; - pub async fn get_all_view_sorts(&self) -> Vec> { - let field_revs = self.delegate.get_field_revs(None).await; - self.pad.read().await.get_all_sorts(&field_revs) - } + if let Some(result) = result { + let mut changeset = GroupChangesetPB { + view_id: self.view_id.clone(), + ..Default::default() + }; + if let Some(delete_group) = result.deleted_group { + tracing::info!("Delete group after moving the row: {:?}", delete_group); + changeset.deleted_groups.push(delete_group.group_id); + } + self.notify_did_update_groups(changeset).await; - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn insert_view_sort(&self, params: AlterSortParams) -> FlowyResult { - let sort_type = SortType::from(¶ms); - let is_exist = params.sort_id.is_some(); - let sort_id = match params.sort_id { - None => gen_grid_sort_id(), - Some(sort_id) => sort_id, + for changeset in result.row_changesets { + self.notify_did_update_group_rows(changeset).await; + } + } + } + /// Only call once after grid view editor initialized + #[tracing::instrument(level = "trace", skip(self))] + pub async fn load_view_groups(&self) -> FlowyResult> { + let groups = self + .group_controller + .read() + .await + .groups() + .into_iter() + .cloned() + .collect::>(); + tracing::trace!("Number of groups: {}", groups.len()); + Ok(groups.into_iter().map(GroupPB::from).collect()) + } + + #[tracing::instrument(level = "trace", skip(self), err)] + pub async fn move_view_group(&self, params: MoveGroupParams) -> FlowyResult<()> { + self + .group_controller + .write() + .await + .move_group(¶ms.from_group_id, ¶ms.to_group_id)?; + match self + .group_controller + .read() + .await + .get_group(¶ms.from_group_id) + { + None => tracing::warn!("Can not find the group with id: {}", params.from_group_id), + Some((index, group)) => { + let inserted_group = InsertedGroupPB { + group: GroupPB::from(group), + index: index as i32, }; - let sort_rev = SortRevision { - id: sort_id, - field_id: params.field_id.clone(), - field_type: params.field_type, - condition: params.condition.into(), + let changeset = GroupChangesetPB { + view_id: self.view_id.clone(), + inserted_groups: vec![inserted_group], + deleted_groups: vec![params.from_group_id.clone()], + update_groups: vec![], + initial_groups: vec![], }; - let mut sort_controller = self.sort_controller.write().await; - let changeset = if is_exist { - self.modify(|pad| { - let changeset = pad.update_sort(¶ms.field_id, sort_rev.clone())?; - Ok(changeset) - }) - .await?; - sort_controller - .did_receive_changes(SortChangeset::from_update(sort_type)) - .await - } else { - self.modify(|pad| { - let changeset = pad.insert_sort(¶ms.field_id, sort_rev.clone())?; - Ok(changeset) - }) - .await?; - sort_controller - .did_receive_changes(SortChangeset::from_insert(sort_type)) - .await - }; - self.notify_did_update_sort(changeset).await; - drop(sort_controller); - Ok(sort_rev) + self.notify_did_update_groups(changeset).await; + }, } + Ok(()) + } - pub async fn delete_view_sort(&self, params: DeleteSortParams) -> FlowyResult<()> { - let notification = self - .sort_controller - .write() - .await - .did_receive_changes(SortChangeset::from_delete(DeletedSortType::from(params.clone()))) - .await; + pub async fn group_id(&self) -> String { + self.group_controller.read().await.field_id().to_string() + } - let sort_type = params.sort_type; - self.modify(|pad| { - let changeset = pad.delete_sort(¶ms.sort_id, &sort_type.field_id, sort_type.field_type)?; - Ok(changeset) + /// Initialize new group when grouping by a new field + /// + pub async fn initialize_new_group(&self, params: InsertGroupParams) -> FlowyResult<()> { + if let Some(field_rev) = self.delegate.get_field_rev(¶ms.field_id).await { + self + .modify(|pad| { + let configuration = default_group_configuration(&field_rev); + let changeset = pad.insert_or_update_group_configuration( + ¶ms.field_id, + ¶ms.field_type_rev, + configuration, + )?; + Ok(changeset) }) .await?; - - self.notify_did_update_sort(notification).await; - Ok(()) } + if self.group_controller.read().await.field_id() != params.field_id { + self.group_by_view_field(¶ms.field_id).await?; + self.notify_did_update_setting().await; + } + Ok(()) + } - pub async fn delete_all_view_sorts(&self) -> FlowyResult<()> { - let all_sorts = self.get_all_view_sorts().await; - self.sort_controller.write().await.delete_all_sorts().await; - self.modify(|pad| { - let changeset = pad.delete_all_sorts()?; - Ok(changeset) + pub async fn delete_view_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { + self + .modify(|pad| { + let changeset = + pad.delete_group(¶ms.group_id, ¶ms.field_id, ¶ms.field_type_rev)?; + Ok(changeset) + }) + .await + } + + pub async fn get_view_setting(&self) -> DatabaseViewSettingPB { + let field_revs = self.delegate.get_field_revs(None).await; + make_grid_setting(&*self.pad.read().await, &field_revs) + } + + pub async fn get_all_view_sorts(&self) -> Vec> { + let field_revs = self.delegate.get_field_revs(None).await; + self.pad.read().await.get_all_sorts(&field_revs) + } + + #[tracing::instrument(level = "trace", skip(self), err)] + pub async fn insert_view_sort(&self, params: AlterSortParams) -> FlowyResult { + let sort_type = SortType::from(¶ms); + let is_exist = params.sort_id.is_some(); + let sort_id = match params.sort_id { + None => gen_grid_sort_id(), + Some(sort_id) => sort_id, + }; + + let sort_rev = SortRevision { + id: sort_id, + field_id: params.field_id.clone(), + field_type: params.field_type, + condition: params.condition.into(), + }; + + let mut sort_controller = self.sort_controller.write().await; + let changeset = if is_exist { + self + .modify(|pad| { + let changeset = pad.update_sort(¶ms.field_id, sort_rev.clone())?; + Ok(changeset) }) .await?; - - let mut notification = SortChangesetNotificationPB::new(self.view_id.clone()); - notification.delete_sorts = all_sorts.into_iter().map(|sort| SortPB::from(sort.as_ref())).collect(); - self.notify_did_update_sort(notification).await; - Ok(()) - } - - pub async fn get_all_view_filters(&self) -> Vec> { - let field_revs = self.delegate.get_field_revs(None).await; - self.pad.read().await.get_all_filters(&field_revs) - } - - pub async fn get_view_filters(&self, filter_type: &FilterType) -> Vec> { - let field_type_rev: FieldTypeRevision = filter_type.field_type.clone().into(); - self.pad - .read() - .await - .get_filters(&filter_type.field_id, &field_type_rev) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn insert_view_filter(&self, params: AlterFilterParams) -> FlowyResult<()> { - let filter_type = FilterType::from(¶ms); - let is_exist = params.filter_id.is_some(); - let filter_id = match params.filter_id { - None => gen_grid_filter_id(), - Some(filter_id) => filter_id, - }; - let filter_rev = FilterRevision { - id: filter_id.clone(), - field_id: params.field_id.clone(), - field_type: params.field_type, - condition: params.condition, - content: params.content, - }; - let filter_controller = self.filter_controller.clone(); - let changeset = if is_exist { - let old_filter_type = self - .delegate - .get_field_rev(¶ms.field_id) - .await - .map(|field| FilterType::from(&field)); - self.modify(|pad| { - let changeset = pad.update_filter(¶ms.field_id, filter_rev)?; - Ok(changeset) - }) - .await?; - filter_controller - .did_receive_changes(FilterChangeset::from_update(UpdatedFilterType::new( - old_filter_type, - filter_type, - ))) - .await - } else { - self.modify(|pad| { - let changeset = pad.insert_filter(¶ms.field_id, filter_rev)?; - Ok(changeset) - }) - .await?; - filter_controller - .did_receive_changes(FilterChangeset::from_insert(filter_type)) - .await - }; - drop(filter_controller); - - if let Some(changeset) = changeset { - self.notify_did_update_filter(changeset).await; - } - Ok(()) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn delete_view_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { - let filter_type = params.filter_type; - let changeset = self - .filter_controller - .did_receive_changes(FilterChangeset::from_delete(filter_type.clone())) - .await; - - self.modify(|pad| { - let changeset = pad.delete_filter(¶ms.filter_id, &filter_type.field_id, filter_type.field_type)?; - Ok(changeset) + sort_controller + .did_receive_changes(SortChangeset::from_update(sort_type)) + .await + } else { + self + .modify(|pad| { + let changeset = pad.insert_sort(¶ms.field_id, sort_rev.clone())?; + Ok(changeset) }) .await?; + sort_controller + .did_receive_changes(SortChangeset::from_insert(sort_type)) + .await + }; + self.notify_did_update_sort(changeset).await; + drop(sort_controller); + Ok(sort_rev) + } - if changeset.is_some() { - self.notify_did_update_filter(changeset.unwrap()).await; - } - Ok(()) + pub async fn delete_view_sort(&self, params: DeleteSortParams) -> FlowyResult<()> { + let notification = self + .sort_controller + .write() + .await + .did_receive_changes(SortChangeset::from_delete(DeletedSortType::from( + params.clone(), + ))) + .await; + + let sort_type = params.sort_type; + self + .modify(|pad| { + let changeset = + pad.delete_sort(¶ms.sort_id, &sort_type.field_id, sort_type.field_type)?; + Ok(changeset) + }) + .await?; + + self.notify_did_update_sort(notification).await; + Ok(()) + } + + pub async fn delete_all_view_sorts(&self) -> FlowyResult<()> { + let all_sorts = self.get_all_view_sorts().await; + self.sort_controller.write().await.delete_all_sorts().await; + self + .modify(|pad| { + let changeset = pad.delete_all_sorts()?; + Ok(changeset) + }) + .await?; + + let mut notification = SortChangesetNotificationPB::new(self.view_id.clone()); + notification.delete_sorts = all_sorts + .into_iter() + .map(|sort| SortPB::from(sort.as_ref())) + .collect(); + self.notify_did_update_sort(notification).await; + Ok(()) + } + + pub async fn get_all_view_filters(&self) -> Vec> { + let field_revs = self.delegate.get_field_revs(None).await; + self.pad.read().await.get_all_filters(&field_revs) + } + + pub async fn get_view_filters(&self, filter_type: &FilterType) -> Vec> { + let field_type_rev: FieldTypeRevision = filter_type.field_type.clone().into(); + self + .pad + .read() + .await + .get_filters(&filter_type.field_id, &field_type_rev) + } + + #[tracing::instrument(level = "trace", skip(self), err)] + pub async fn insert_view_filter(&self, params: AlterFilterParams) -> FlowyResult<()> { + let filter_type = FilterType::from(¶ms); + let is_exist = params.filter_id.is_some(); + let filter_id = match params.filter_id { + None => gen_grid_filter_id(), + Some(filter_id) => filter_id, + }; + let filter_rev = FilterRevision { + id: filter_id.clone(), + field_id: params.field_id.clone(), + field_type: params.field_type, + condition: params.condition, + content: params.content, + }; + let filter_controller = self.filter_controller.clone(); + let changeset = if is_exist { + let old_filter_type = self + .delegate + .get_field_rev(¶ms.field_id) + .await + .map(|field| FilterType::from(&field)); + self + .modify(|pad| { + let changeset = pad.update_filter(¶ms.field_id, filter_rev)?; + Ok(changeset) + }) + .await?; + filter_controller + .did_receive_changes(FilterChangeset::from_update(UpdatedFilterType::new( + old_filter_type, + filter_type, + ))) + .await + } else { + self + .modify(|pad| { + let changeset = pad.insert_filter(¶ms.field_id, filter_rev)?; + Ok(changeset) + }) + .await?; + filter_controller + .did_receive_changes(FilterChangeset::from_insert(filter_type)) + .await + }; + drop(filter_controller); + + if let Some(changeset) = changeset { + self.notify_did_update_filter(changeset).await; } + Ok(()) + } - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn did_update_view_field_type_option( - &self, - field_id: &str, - old_field_rev: Option>, - ) -> FlowyResult<()> { - if let Some(field_rev) = self.delegate.get_field_rev(field_id).await { - let old = old_field_rev.map(|old_field_rev| FilterType::from(&old_field_rev)); - let new = FilterType::from(&field_rev); - let filter_type = UpdatedFilterType::new(old, new); - let filter_changeset = FilterChangeset::from_update(filter_type); + #[tracing::instrument(level = "trace", skip(self), err)] + pub async fn delete_view_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { + let filter_type = params.filter_type; + let changeset = self + .filter_controller + .did_receive_changes(FilterChangeset::from_delete(filter_type.clone())) + .await; - self.sort_controller - .read() - .await - .did_update_view_field_type_option(&field_rev) - .await; + self + .modify(|pad| { + let changeset = pad.delete_filter( + ¶ms.filter_id, + &filter_type.field_id, + filter_type.field_type, + )?; + Ok(changeset) + }) + .await?; - let filter_controller = self.filter_controller.clone(); - let _ = tokio::spawn(async move { - if let Some(notification) = filter_controller.did_receive_changes(filter_changeset).await { - send_notification(¬ification.view_id, DatabaseNotification::DidUpdateFilter) - .payload(notification) - .send(); - } - }); - } - Ok(()) + if changeset.is_some() { + self.notify_did_update_filter(changeset.unwrap()).await; } + Ok(()) + } - /// - /// - /// # Arguments - /// - /// * `field_id`: - /// - #[tracing::instrument(level = "debug", skip_all, err)] - pub async fn group_by_view_field(&self, field_id: &str) -> FlowyResult<()> { - if let Some(field_rev) = self.delegate.get_field_rev(field_id).await { - let row_revs = self.delegate.get_row_revs(None).await; - let configuration_reader = GroupConfigurationReaderImpl { - pad: self.pad.clone(), - view_editor_delegate: self.delegate.clone(), - }; - let new_group_controller = new_group_controller_with_field_rev( - self.user_id.clone(), - self.view_id.clone(), - self.pad.clone(), - self.rev_manager.clone(), - field_rev, - row_revs, - configuration_reader, - ) - .await?; + #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn did_update_view_field_type_option( + &self, + field_id: &str, + old_field_rev: Option>, + ) -> FlowyResult<()> { + if let Some(field_rev) = self.delegate.get_field_rev(field_id).await { + let old = old_field_rev.map(|old_field_rev| FilterType::from(&old_field_rev)); + let new = FilterType::from(&field_rev); + let filter_type = UpdatedFilterType::new(old, new); + let filter_changeset = FilterChangeset::from_update(filter_type); - let new_groups = new_group_controller - .groups() - .into_iter() - .map(|group| GroupPB::from(group.clone())) - .collect(); + self + .sort_controller + .read() + .await + .did_update_view_field_type_option(&field_rev) + .await; - *self.group_controller.write().await = new_group_controller; - let changeset = GroupChangesetPB { - view_id: self.view_id.clone(), - initial_groups: new_groups, - ..Default::default() - }; - - debug_assert!(!changeset.is_empty()); - if !changeset.is_empty() { - send_notification(&changeset.view_id, DatabaseNotification::DidGroupByField) - .payload(changeset) - .send(); - } - } - Ok(()) - } - - pub(crate) async fn get_cells_for_field(&self, field_id: &str) -> FlowyResult> { - get_cells_for_field(self.delegate.clone(), field_id).await - } - - async fn notify_did_update_setting(&self) { - let setting = self.get_view_setting().await; - send_notification(&self.view_id, DatabaseNotification::DidUpdateSettings) - .payload(setting) - .send(); - } - - pub async fn notify_did_update_group_rows(&self, payload: GroupRowsNotificationPB) { - send_notification(&payload.group_id, DatabaseNotification::DidUpdateGroupRow) - .payload(payload) - .send(); - } - - pub async fn notify_did_update_filter(&self, notification: FilterChangesetNotificationPB) { - send_notification(¬ification.view_id, DatabaseNotification::DidUpdateFilter) + let filter_controller = self.filter_controller.clone(); + let _ = tokio::spawn(async move { + if let Some(notification) = filter_controller + .did_receive_changes(filter_changeset) + .await + { + send_notification(¬ification.view_id, DatabaseNotification::DidUpdateFilter) .payload(notification) .send(); - } - - pub async fn notify_did_update_sort(&self, notification: SortChangesetNotificationPB) { - if !notification.is_empty() { - send_notification(¬ification.view_id, DatabaseNotification::DidUpdateSort) - .payload(notification) - .send(); } + }); } + Ok(()) + } - async fn notify_did_update_groups(&self, changeset: GroupChangesetPB) { - send_notification(&self.view_id, DatabaseNotification::DidUpdateGroups) - .payload(changeset) - .send(); - } + /// + /// + /// # Arguments + /// + /// * `field_id`: + /// + #[tracing::instrument(level = "debug", skip_all, err)] + pub async fn group_by_view_field(&self, field_id: &str) -> FlowyResult<()> { + if let Some(field_rev) = self.delegate.get_field_rev(field_id).await { + let row_revs = self.delegate.get_row_revs(None).await; + let configuration_reader = GroupConfigurationReaderImpl { + pad: self.pad.clone(), + view_editor_delegate: self.delegate.clone(), + }; + let new_group_controller = new_group_controller_with_field_rev( + self.user_id.clone(), + self.view_id.clone(), + self.pad.clone(), + self.rev_manager.clone(), + field_rev, + row_revs, + configuration_reader, + ) + .await?; - async fn modify(&self, f: F) -> FlowyResult<()> - where - F: for<'a> FnOnce(&'a mut GridViewRevisionPad) -> FlowyResult>, - { - let mut write_guard = self.pad.write().await; - match f(&mut write_guard)? { - None => {} - Some(change) => { - apply_change(&self.user_id, self.rev_manager.clone(), change).await?; - } - } - Ok(()) - } + let new_groups = new_group_controller + .groups() + .into_iter() + .map(|group| GroupPB::from(group.clone())) + .collect(); - async fn mut_group_controller(&self, f: F) -> Option - where - F: FnOnce(&mut Box, Arc) -> FlowyResult, - { - let group_field_id = self.group_controller.read().await.field_id().to_owned(); - match self.delegate.get_field_rev(&group_field_id).await { - None => None, - Some(field_rev) => { - let mut write_guard = self.group_controller.write().await; - f(&mut write_guard, field_rev).ok() - } - } - } + *self.group_controller.write().await = new_group_controller; + let changeset = GroupChangesetPB { + view_id: self.view_id.clone(), + initial_groups: new_groups, + ..Default::default() + }; - #[allow(dead_code)] - async fn async_mut_group_controller(&self, f: F) -> Option - where - F: FnOnce(Arc>>, Arc) -> O, - O: Future> + Sync + 'static, - { - let group_field_id = self.group_controller.read().await.field_id().to_owned(); - match self.delegate.get_field_rev(&group_field_id).await { - None => None, - Some(field_rev) => { - let _write_guard = self.group_controller.write().await; - f(self.group_controller.clone(), field_rev).await.ok() - } - } + debug_assert!(!changeset.is_empty()); + if !changeset.is_empty() { + send_notification(&changeset.view_id, DatabaseNotification::DidGroupByField) + .payload(changeset) + .send(); + } } + Ok(()) + } + + pub(crate) async fn get_cells_for_field( + &self, + field_id: &str, + ) -> FlowyResult> { + get_cells_for_field(self.delegate.clone(), field_id).await + } + + async fn notify_did_update_setting(&self) { + let setting = self.get_view_setting().await; + send_notification(&self.view_id, DatabaseNotification::DidUpdateSettings) + .payload(setting) + .send(); + } + + pub async fn notify_did_update_group_rows(&self, payload: GroupRowsNotificationPB) { + send_notification(&payload.group_id, DatabaseNotification::DidUpdateGroupRow) + .payload(payload) + .send(); + } + + pub async fn notify_did_update_filter(&self, notification: FilterChangesetNotificationPB) { + send_notification(¬ification.view_id, DatabaseNotification::DidUpdateFilter) + .payload(notification) + .send(); + } + + pub async fn notify_did_update_sort(&self, notification: SortChangesetNotificationPB) { + if !notification.is_empty() { + send_notification(¬ification.view_id, DatabaseNotification::DidUpdateSort) + .payload(notification) + .send(); + } + } + + async fn notify_did_update_groups(&self, changeset: GroupChangesetPB) { + send_notification(&self.view_id, DatabaseNotification::DidUpdateGroups) + .payload(changeset) + .send(); + } + + async fn modify(&self, f: F) -> FlowyResult<()> + where + F: + for<'a> FnOnce(&'a mut GridViewRevisionPad) -> FlowyResult>, + { + let mut write_guard = self.pad.write().await; + match f(&mut write_guard)? { + None => {}, + Some(change) => { + apply_change(&self.user_id, self.rev_manager.clone(), change).await?; + }, + } + Ok(()) + } + + async fn mut_group_controller(&self, f: F) -> Option + where + F: FnOnce(&mut Box, Arc) -> FlowyResult, + { + let group_field_id = self.group_controller.read().await.field_id().to_owned(); + match self.delegate.get_field_rev(&group_field_id).await { + None => None, + Some(field_rev) => { + let mut write_guard = self.group_controller.write().await; + f(&mut write_guard, field_rev).ok() + }, + } + } + + #[allow(dead_code)] + async fn async_mut_group_controller(&self, f: F) -> Option + where + F: FnOnce(Arc>>, Arc) -> O, + O: Future> + Sync + 'static, + { + let group_field_id = self.group_controller.read().await.field_id().to_owned(); + match self.delegate.get_field_rev(&group_field_id).await { + None => None, + Some(field_rev) => { + let _write_guard = self.group_controller.write().await; + f(self.group_controller.clone(), field_rev).await.ok() + }, + } + } } /// Returns the list of cells corresponding to the given field. pub(crate) async fn get_cells_for_field( - delegate: Arc, - field_id: &str, + delegate: Arc, + field_id: &str, ) -> FlowyResult> { - let row_revs = delegate.get_row_revs(None).await; - let field_rev = delegate.get_field_rev(field_id).await.unwrap(); - let field_type: FieldType = field_rev.ty.into(); - let mut cells = vec![]; - if let Some(handler) = delegate.get_type_option_cell_handler(&field_rev, &field_type) { - for row_rev in row_revs { - if let Some(cell_rev) = row_rev.cells.get(field_id) { - if let Ok(type_cell_data) = TypeCellData::try_from(cell_rev) { - if let Ok(cell_data) = handler.get_cell_data(type_cell_data.cell_str, &field_type, &field_rev) { - cells.push(RowSingleCellData { - row_id: row_rev.id.clone(), - field_id: field_rev.id.clone(), - field_type: field_type.clone(), - cell_data, - }) - } - } - } + let row_revs = delegate.get_row_revs(None).await; + let field_rev = delegate.get_field_rev(field_id).await.unwrap(); + let field_type: FieldType = field_rev.ty.into(); + let mut cells = vec![]; + if let Some(handler) = delegate.get_type_option_cell_handler(&field_rev, &field_type) { + for row_rev in row_revs { + if let Some(cell_rev) = row_rev.cells.get(field_id) { + if let Ok(type_cell_data) = TypeCellData::try_from(cell_rev) { + if let Ok(cell_data) = + handler.get_cell_data(type_cell_data.cell_str, &field_type, &field_rev) + { + cells.push(RowSingleCellData { + row_id: row_rev.id.clone(), + field_id: field_rev.id.clone(), + field_type: field_type.clone(), + cell_data, + }) + } } + } } - Ok(cells) + } + Ok(cells) } #[async_trait] impl RefCountValue for DatabaseViewRevisionEditor { - async fn did_remove(&self) { - self.close().await; - } + async fn did_remove(&self) { + self.close().await; + } } async fn new_group_controller( - user_id: String, - view_id: String, - view_rev_pad: Arc>, - rev_manager: Arc>>, - delegate: Arc, + user_id: String, + view_id: String, + view_rev_pad: Arc>, + rev_manager: Arc>>, + delegate: Arc, ) -> FlowyResult> { - let configuration_reader = GroupConfigurationReaderImpl { - pad: view_rev_pad.clone(), - view_editor_delegate: delegate.clone(), - }; - let field_revs = delegate.get_field_revs(None).await; - let row_revs = delegate.get_row_revs(None).await; - let layout = view_rev_pad.read().await.layout(); - // Read the group field or find a new group field - let field_rev = configuration_reader - .get_configuration() - .await - .and_then(|configuration| { - field_revs - .iter() - .find(|field_rev| field_rev.id == configuration.field_id) - .cloned() - }) - .unwrap_or_else(|| find_group_field(&field_revs, &layout).unwrap()); - - new_group_controller_with_field_rev( - user_id, - view_id, - view_rev_pad, - rev_manager, - field_rev, - row_revs, - configuration_reader, - ) + let configuration_reader = GroupConfigurationReaderImpl { + pad: view_rev_pad.clone(), + view_editor_delegate: delegate.clone(), + }; + let field_revs = delegate.get_field_revs(None).await; + let row_revs = delegate.get_row_revs(None).await; + let layout = view_rev_pad.read().await.layout(); + // Read the group field or find a new group field + let field_rev = configuration_reader + .get_configuration() .await + .and_then(|configuration| { + field_revs + .iter() + .find(|field_rev| field_rev.id == configuration.field_id) + .cloned() + }) + .unwrap_or_else(|| find_group_field(&field_revs, &layout).unwrap()); + + new_group_controller_with_field_rev( + user_id, + view_id, + view_rev_pad, + rev_manager, + field_rev, + row_revs, + configuration_reader, + ) + .await } /// Returns a [GroupController] /// async fn new_group_controller_with_field_rev( - user_id: String, - view_id: String, - view_rev_pad: Arc>, - rev_manager: Arc>>, - field_rev: Arc, - row_revs: Vec>, - configuration_reader: GroupConfigurationReaderImpl, + user_id: String, + view_id: String, + view_rev_pad: Arc>, + rev_manager: Arc>>, + field_rev: Arc, + row_revs: Vec>, + configuration_reader: GroupConfigurationReaderImpl, ) -> FlowyResult> { - let configuration_writer = GroupConfigurationWriterImpl { - user_id, - rev_manager, - view_pad: view_rev_pad, - }; - make_group_controller(view_id, field_rev, row_revs, configuration_reader, configuration_writer).await + let configuration_writer = GroupConfigurationWriterImpl { + user_id, + rev_manager, + view_pad: view_rev_pad, + }; + make_group_controller( + view_id, + field_rev, + row_revs, + configuration_reader, + configuration_writer, + ) + .await } async fn make_filter_controller( - view_id: &str, - delegate: Arc, - notifier: GridViewChangedNotifier, - cell_data_cache: AtomicCellDataCache, - pad: Arc>, + view_id: &str, + delegate: Arc, + notifier: GridViewChangedNotifier, + cell_data_cache: AtomicCellDataCache, + pad: Arc>, ) -> Arc { - let field_revs = delegate.get_field_revs(None).await; - let filter_revs = pad.read().await.get_all_filters(&field_revs); - let task_scheduler = delegate.get_task_scheduler(); - let filter_delegate = GridViewFilterDelegateImpl { - editor_delegate: delegate.clone(), - view_revision_pad: pad, - }; - let handler_id = gen_handler_id(); - let filter_controller = FilterController::new( - view_id, - &handler_id, - filter_delegate, - task_scheduler.clone(), - filter_revs, - cell_data_cache, - notifier, - ) - .await; - let filter_controller = Arc::new(filter_controller); - task_scheduler - .write() - .await - .register_handler(FilterTaskHandler::new(handler_id, filter_controller.clone())); - filter_controller + let field_revs = delegate.get_field_revs(None).await; + let filter_revs = pad.read().await.get_all_filters(&field_revs); + let task_scheduler = delegate.get_task_scheduler(); + let filter_delegate = GridViewFilterDelegateImpl { + editor_delegate: delegate.clone(), + view_revision_pad: pad, + }; + let handler_id = gen_handler_id(); + let filter_controller = FilterController::new( + view_id, + &handler_id, + filter_delegate, + task_scheduler.clone(), + filter_revs, + cell_data_cache, + notifier, + ) + .await; + let filter_controller = Arc::new(filter_controller); + task_scheduler + .write() + .await + .register_handler(FilterTaskHandler::new( + handler_id, + filter_controller.clone(), + )); + filter_controller } async fn make_sort_controller( - view_id: &str, - delegate: Arc, - notifier: GridViewChangedNotifier, - filter_controller: Arc, - pad: Arc>, - cell_data_cache: AtomicCellDataCache, + view_id: &str, + delegate: Arc, + notifier: GridViewChangedNotifier, + filter_controller: Arc, + pad: Arc>, + cell_data_cache: AtomicCellDataCache, ) -> Arc> { - let handler_id = gen_handler_id(); - let field_revs = delegate.get_field_revs(None).await; - let sorts = pad.read().await.get_all_sorts(&field_revs); - let sort_delegate = GridViewSortDelegateImpl { - editor_delegate: delegate.clone(), - view_revision_pad: pad, - filter_controller, - }; - let task_scheduler = delegate.get_task_scheduler(); - let sort_controller = Arc::new(RwLock::new(SortController::new( - view_id, - &handler_id, - sorts, - sort_delegate, - task_scheduler.clone(), - cell_data_cache, - notifier, - ))); - task_scheduler - .write() - .await - .register_handler(SortTaskHandler::new(handler_id, sort_controller.clone())); + let handler_id = gen_handler_id(); + let field_revs = delegate.get_field_revs(None).await; + let sorts = pad.read().await.get_all_sorts(&field_revs); + let sort_delegate = GridViewSortDelegateImpl { + editor_delegate: delegate.clone(), + view_revision_pad: pad, + filter_controller, + }; + let task_scheduler = delegate.get_task_scheduler(); + let sort_controller = Arc::new(RwLock::new(SortController::new( + view_id, + &handler_id, + sorts, + sort_delegate, + task_scheduler.clone(), + cell_data_cache, + notifier, + ))); + task_scheduler + .write() + .await + .register_handler(SortTaskHandler::new(handler_id, sort_controller.clone())); - sort_controller + sort_controller } fn gen_handler_id() -> String { - nanoid!(10) + nanoid!(10) } #[cfg(test)] mod tests { - use flowy_client_sync::client_database::DatabaseOperations; + use flowy_client_sync::client_database::DatabaseOperations; - #[test] - fn test() { - let s1 = r#"[{"insert":"{\"view_id\":\"fTURELffPr\",\"grid_id\":\"fTURELffPr\",\"layout\":0,\"filters\":[],\"groups\":[]}"}]"#; - let _delta_1 = DatabaseOperations::from_json(s1).unwrap(); + #[test] + fn test() { + let s1 = r#"[{"insert":"{\"view_id\":\"fTURELffPr\",\"grid_id\":\"fTURELffPr\",\"layout\":0,\"filters\":[],\"groups\":[]}"}]"#; + let _delta_1 = DatabaseOperations::from_json(s1).unwrap(); - let s2 = r#"[{"retain":195},{"insert":"{\\\"group_id\\\":\\\"wD9i\\\",\\\"visible\\\":true},{\\\"group_id\\\":\\\"xZtv\\\",\\\"visible\\\":true},{\\\"group_id\\\":\\\"tFV2\\\",\\\"visible\\\":true}"},{"retain":10}]"#; - let _delta_2 = DatabaseOperations::from_json(s2).unwrap(); - } + let s2 = r#"[{"retain":195},{"insert":"{\\\"group_id\\\":\\\"wD9i\\\",\\\"visible\\\":true},{\\\"group_id\\\":\\\"xZtv\\\",\\\"visible\\\":true},{\\\"group_id\\\":\\\"tFV2\\\",\\\"visible\\\":true}"},{"retain":10}]"#; + let _delta_2 = DatabaseOperations::from_json(s2).unwrap(); + } } diff --git a/frontend/rust-lib/flowy-database/src/services/view_editor/editor_manager.rs b/frontend/rust-lib/flowy-database/src/services/view_editor/editor_manager.rs index 5ff9c271e9..3eab70006a 100644 --- a/frontend/rust-lib/flowy-database/src/services/view_editor/editor_manager.rs +++ b/frontend/rust-lib/flowy-database/src/services/view_editor/editor_manager.rs @@ -1,13 +1,13 @@ use crate::entities::{ - AlterFilterParams, AlterSortParams, CreateRowParams, DatabaseViewSettingPB, DeleteFilterParams, DeleteGroupParams, - DeleteSortParams, InsertGroupParams, MoveGroupParams, RepeatedGroupPB, RowPB, + AlterFilterParams, AlterSortParams, CreateRowParams, DatabaseViewSettingPB, DeleteFilterParams, + DeleteGroupParams, DeleteSortParams, InsertGroupParams, MoveGroupParams, RepeatedGroupPB, RowPB, }; use crate::manager::DatabaseUser; use crate::services::block_manager::DatabaseBlockEvent; use crate::services::cell::AtomicCellDataCache; use crate::services::filter::FilterType; use crate::services::persistence::rev_sqlite::{ - SQLiteDatabaseRevisionSnapshotPersistence, SQLiteGridViewRevisionPersistence, + SQLiteDatabaseRevisionSnapshotPersistence, SQLiteGridViewRevisionPersistence, }; use crate::services::view_editor::changed_notifier::*; use crate::services::view_editor::trait_impl::GridViewRevisionMergeable; @@ -23,287 +23,308 @@ use std::sync::Arc; use tokio::sync::{broadcast, RwLock}; pub struct DatabaseViewManager { - view_id: String, - user: Arc, - delegate: Arc, - view_editors: Arc>>>, - cell_data_cache: AtomicCellDataCache, + view_id: String, + user: Arc, + delegate: Arc, + view_editors: Arc>>>, + cell_data_cache: AtomicCellDataCache, } impl DatabaseViewManager { - pub async fn new( - view_id: String, - user: Arc, - delegate: Arc, - cell_data_cache: AtomicCellDataCache, - block_event_rx: broadcast::Receiver, - ) -> FlowyResult { - let view_editors = Arc::new(RwLock::new(RefCountHashMap::default())); - listen_on_database_block_event(block_event_rx, view_editors.clone()); - Ok(Self { - view_id, - user, - delegate, - cell_data_cache, - view_editors, - }) + pub async fn new( + view_id: String, + user: Arc, + delegate: Arc, + cell_data_cache: AtomicCellDataCache, + block_event_rx: broadcast::Receiver, + ) -> FlowyResult { + let view_editors = Arc::new(RwLock::new(RefCountHashMap::default())); + listen_on_database_block_event(block_event_rx, view_editors.clone()); + Ok(Self { + view_id, + user, + delegate, + cell_data_cache, + view_editors, + }) + } + + pub async fn close(&self, view_id: &str) { + self.view_editors.write().await.remove(view_id).await; + } + + pub async fn subscribe_view_changed( + &self, + view_id: &str, + ) -> FlowyResult> { + Ok(self.get_view_editor(view_id).await?.notifier.subscribe()) + } + + pub async fn get_row_revs( + &self, + view_id: &str, + block_id: &str, + ) -> FlowyResult>> { + let mut row_revs = self + .delegate + .get_row_revs(Some(vec![block_id.to_owned()])) + .await; + if let Ok(view_editor) = self.get_view_editor(view_id).await { + view_editor.filter_rows(block_id, &mut row_revs).await; + view_editor.sort_rows(&mut row_revs).await; } - pub async fn close(&self, view_id: &str) { - self.view_editors.write().await.remove(view_id).await; + Ok(row_revs) + } + + pub async fn duplicate_database_view(&self) -> FlowyResult { + let editor = self.get_default_view_editor().await?; + let view_data = editor.duplicate_view_data().await?; + Ok(view_data) + } + + /// When the row was created, we may need to modify the [RowRevision] according to the [CreateRowParams]. + pub async fn will_create_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) { + for view_editor in self.view_editors.read().await.values() { + view_editor.will_create_view_row(row_rev, params).await; } + } - pub async fn subscribe_view_changed(&self, view_id: &str) -> FlowyResult> { - Ok(self.get_view_editor(view_id).await?.notifier.subscribe()) + /// Notify the view that the row was created. For the moment, the view is just sending notifications. + pub async fn did_create_row(&self, row_pb: &RowPB, params: &CreateRowParams) { + for view_editor in self.view_editors.read().await.values() { + view_editor.did_create_view_row(row_pb, params).await; } + } - pub async fn get_row_revs(&self, view_id: &str, block_id: &str) -> FlowyResult>> { - let mut row_revs = self.delegate.get_row_revs(Some(vec![block_id.to_owned()])).await; - if let Ok(view_editor) = self.get_view_editor(view_id).await { - view_editor.filter_rows(block_id, &mut row_revs).await; - view_editor.sort_rows(&mut row_revs).await; - } - - Ok(row_revs) - } - - pub async fn duplicate_database_view(&self) -> FlowyResult { - let editor = self.get_default_view_editor().await?; - let view_data = editor.duplicate_view_data().await?; - Ok(view_data) - } - - /// When the row was created, we may need to modify the [RowRevision] according to the [CreateRowParams]. - pub async fn will_create_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) { + /// Insert/Delete the group's row if the corresponding cell data was changed. + pub async fn did_update_row(&self, old_row_rev: Option>, row_id: &str) { + match self.delegate.get_row_rev(row_id).await { + None => { + tracing::warn!("Can not find the row in grid view"); + }, + Some((_, row_rev)) => { for view_editor in self.view_editors.read().await.values() { - view_editor.will_create_view_row(row_rev, params).await; - } - } - - /// Notify the view that the row was created. For the moment, the view is just sending notifications. - pub async fn did_create_row(&self, row_pb: &RowPB, params: &CreateRowParams) { - for view_editor in self.view_editors.read().await.values() { - view_editor.did_create_view_row(row_pb, params).await; - } - } - - /// Insert/Delete the group's row if the corresponding cell data was changed. - pub async fn did_update_row(&self, old_row_rev: Option>, row_id: &str) { - match self.delegate.get_row_rev(row_id).await { - None => { - tracing::warn!("Can not find the row in grid view"); - } - Some((_, row_rev)) => { - for view_editor in self.view_editors.read().await.values() { - view_editor.did_update_view_row(old_row_rev.clone(), &row_rev).await; - } - } - } - } - - pub async fn group_by_field(&self, field_id: &str) -> FlowyResult<()> { - let view_editor = self.get_default_view_editor().await?; - view_editor.group_by_view_field(field_id).await?; - Ok(()) - } - - pub async fn did_delete_row(&self, row_rev: Arc) { - for view_editor in self.view_editors.read().await.values() { - view_editor.did_delete_view_row(&row_rev).await; - } - } - - pub async fn get_setting(&self) -> FlowyResult { - let view_editor = self.get_default_view_editor().await?; - Ok(view_editor.get_view_setting().await) - } - - pub async fn get_all_filters(&self) -> FlowyResult>> { - let view_editor = self.get_default_view_editor().await?; - Ok(view_editor.get_all_view_filters().await) - } - - pub async fn get_filters(&self, filter_id: &FilterType) -> FlowyResult>> { - let view_editor = self.get_default_view_editor().await?; - Ok(view_editor.get_view_filters(filter_id).await) - } - - pub async fn create_or_update_filter(&self, params: AlterFilterParams) -> FlowyResult<()> { - let view_editor = self.get_view_editor(¶ms.view_id).await?; - view_editor.insert_view_filter(params).await - } - - pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { - let view_editor = self.get_view_editor(¶ms.view_id).await?; - view_editor.delete_view_filter(params).await - } - - pub async fn get_all_sorts(&self, view_id: &str) -> FlowyResult>> { - let view_editor = self.get_view_editor(view_id).await?; - Ok(view_editor.get_all_view_sorts().await) - } - - pub async fn create_or_update_sort(&self, params: AlterSortParams) -> FlowyResult { - let view_editor = self.get_view_editor(¶ms.view_id).await?; - view_editor.insert_view_sort(params).await - } - - pub async fn delete_all_sorts(&self, view_id: &str) -> FlowyResult<()> { - let view_editor = self.get_view_editor(view_id).await?; - view_editor.delete_all_view_sorts().await - } - - pub async fn delete_sort(&self, params: DeleteSortParams) -> FlowyResult<()> { - let view_editor = self.get_view_editor(¶ms.view_id).await?; - view_editor.delete_view_sort(params).await - } - - pub async fn load_groups(&self) -> FlowyResult { - let view_editor = self.get_default_view_editor().await?; - let groups = view_editor.load_view_groups().await?; - Ok(RepeatedGroupPB { items: groups }) - } - - pub async fn insert_or_update_group(&self, params: InsertGroupParams) -> FlowyResult<()> { - let view_editor = self.get_default_view_editor().await?; - view_editor.initialize_new_group(params).await - } - - pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { - let view_editor = self.get_default_view_editor().await?; - view_editor.delete_view_group(params).await - } - - pub async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> { - let view_editor = self.get_default_view_editor().await?; - view_editor.move_view_group(params).await?; - Ok(()) - } - - /// It may generate a RowChangeset when the Row was moved from one group to another. - /// The return value, [RowChangeset], contains the changes made by the groups. - /// - pub async fn move_group_row( - &self, - row_rev: Arc, - to_group_id: String, - to_row_id: Option, - recv_row_changeset: impl FnOnce(RowChangeset) -> Fut<()>, - ) -> FlowyResult<()> { - let mut row_changeset = RowChangeset::new(row_rev.id.clone()); - let view_editor = self.get_default_view_editor().await?; - view_editor - .move_view_group_row(&row_rev, &mut row_changeset, &to_group_id, to_row_id.clone()) + view_editor + .did_update_view_row(old_row_rev.clone(), &row_rev) .await; - - if !row_changeset.is_empty() { - recv_row_changeset(row_changeset).await; } + }, + } + } - Ok(()) + pub async fn group_by_field(&self, field_id: &str) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + view_editor.group_by_view_field(field_id).await?; + Ok(()) + } + + pub async fn did_delete_row(&self, row_rev: Arc) { + for view_editor in self.view_editors.read().await.values() { + view_editor.did_delete_view_row(&row_rev).await; + } + } + + pub async fn get_setting(&self) -> FlowyResult { + let view_editor = self.get_default_view_editor().await?; + Ok(view_editor.get_view_setting().await) + } + + pub async fn get_all_filters(&self) -> FlowyResult>> { + let view_editor = self.get_default_view_editor().await?; + Ok(view_editor.get_all_view_filters().await) + } + + pub async fn get_filters(&self, filter_id: &FilterType) -> FlowyResult>> { + let view_editor = self.get_default_view_editor().await?; + Ok(view_editor.get_view_filters(filter_id).await) + } + + pub async fn create_or_update_filter(&self, params: AlterFilterParams) -> FlowyResult<()> { + let view_editor = self.get_view_editor(¶ms.view_id).await?; + view_editor.insert_view_filter(params).await + } + + pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { + let view_editor = self.get_view_editor(¶ms.view_id).await?; + view_editor.delete_view_filter(params).await + } + + pub async fn get_all_sorts(&self, view_id: &str) -> FlowyResult>> { + let view_editor = self.get_view_editor(view_id).await?; + Ok(view_editor.get_all_view_sorts().await) + } + + pub async fn create_or_update_sort(&self, params: AlterSortParams) -> FlowyResult { + let view_editor = self.get_view_editor(¶ms.view_id).await?; + view_editor.insert_view_sort(params).await + } + + pub async fn delete_all_sorts(&self, view_id: &str) -> FlowyResult<()> { + let view_editor = self.get_view_editor(view_id).await?; + view_editor.delete_all_view_sorts().await + } + + pub async fn delete_sort(&self, params: DeleteSortParams) -> FlowyResult<()> { + let view_editor = self.get_view_editor(¶ms.view_id).await?; + view_editor.delete_view_sort(params).await + } + + pub async fn load_groups(&self) -> FlowyResult { + let view_editor = self.get_default_view_editor().await?; + let groups = view_editor.load_view_groups().await?; + Ok(RepeatedGroupPB { items: groups }) + } + + pub async fn insert_or_update_group(&self, params: InsertGroupParams) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + view_editor.initialize_new_group(params).await + } + + pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + view_editor.delete_view_group(params).await + } + + pub async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + view_editor.move_view_group(params).await?; + Ok(()) + } + + /// It may generate a RowChangeset when the Row was moved from one group to another. + /// The return value, [RowChangeset], contains the changes made by the groups. + /// + pub async fn move_group_row( + &self, + row_rev: Arc, + to_group_id: String, + to_row_id: Option, + recv_row_changeset: impl FnOnce(RowChangeset) -> Fut<()>, + ) -> FlowyResult<()> { + let mut row_changeset = RowChangeset::new(row_rev.id.clone()); + let view_editor = self.get_default_view_editor().await?; + view_editor + .move_view_group_row( + &row_rev, + &mut row_changeset, + &to_group_id, + to_row_id.clone(), + ) + .await; + + if !row_changeset.is_empty() { + recv_row_changeset(row_changeset).await; } - /// Notifies the view's field type-option data is changed - /// For the moment, only the groups will be generated after the type-option data changed. A - /// [FieldRevision] has a property named type_options contains a list of type-option data. - /// # Arguments - /// - /// * `field_id`: the id of the field in current view - /// - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn did_update_view_field_type_option( - &self, - field_id: &str, - old_field_rev: Option>, - ) -> FlowyResult<()> { - let view_editor = self.get_default_view_editor().await?; - if view_editor.group_id().await == field_id { - view_editor.group_by_view_field(field_id).await?; - } + Ok(()) + } - view_editor - .did_update_view_field_type_option(field_id, old_field_rev) - .await?; - Ok(()) + /// Notifies the view's field type-option data is changed + /// For the moment, only the groups will be generated after the type-option data changed. A + /// [FieldRevision] has a property named type_options contains a list of type-option data. + /// # Arguments + /// + /// * `field_id`: the id of the field in current view + /// + #[tracing::instrument(level = "trace", skip(self), err)] + pub async fn did_update_view_field_type_option( + &self, + field_id: &str, + old_field_rev: Option>, + ) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + if view_editor.group_id().await == field_id { + view_editor.group_by_view_field(field_id).await?; } - pub async fn get_view_editor(&self, view_id: &str) -> FlowyResult> { - debug_assert!(!view_id.is_empty()); - if let Some(editor) = self.view_editors.read().await.get(view_id) { - return Ok(editor); - } - tracing::trace!("{:p} create view_editor", self); - let mut view_editors = self.view_editors.write().await; - let editor = Arc::new(self.make_view_editor(view_id).await?); - view_editors.insert(view_id.to_owned(), editor.clone()); - Ok(editor) - } + view_editor + .did_update_view_field_type_option(field_id, old_field_rev) + .await?; + Ok(()) + } - async fn get_default_view_editor(&self) -> FlowyResult> { - self.get_view_editor(&self.view_id).await + pub async fn get_view_editor( + &self, + view_id: &str, + ) -> FlowyResult> { + debug_assert!(!view_id.is_empty()); + if let Some(editor) = self.view_editors.read().await.get(view_id) { + return Ok(editor); } + tracing::trace!("{:p} create view_editor", self); + let mut view_editors = self.view_editors.write().await; + let editor = Arc::new(self.make_view_editor(view_id).await?); + view_editors.insert(view_id.to_owned(), editor.clone()); + Ok(editor) + } - async fn make_view_editor(&self, view_id: &str) -> FlowyResult { - let rev_manager = make_database_view_rev_manager(&self.user, view_id).await?; - let user_id = self.user.user_id()?; - let token = self.user.token()?; - let view_id = view_id.to_owned(); + async fn get_default_view_editor(&self) -> FlowyResult> { + self.get_view_editor(&self.view_id).await + } - DatabaseViewRevisionEditor::new( - &user_id, - &token, - view_id, - self.delegate.clone(), - self.cell_data_cache.clone(), - rev_manager, - ) - .await - } + async fn make_view_editor(&self, view_id: &str) -> FlowyResult { + let rev_manager = make_database_view_rev_manager(&self.user, view_id).await?; + let user_id = self.user.user_id()?; + let token = self.user.token()?; + let view_id = view_id.to_owned(); + + DatabaseViewRevisionEditor::new( + &user_id, + &token, + view_id, + self.delegate.clone(), + self.cell_data_cache.clone(), + rev_manager, + ) + .await + } } fn listen_on_database_block_event( - mut block_event_rx: broadcast::Receiver, - view_editors: Arc>>>, + mut block_event_rx: broadcast::Receiver, + view_editors: Arc>>>, ) { - tokio::spawn(async move { - loop { - while let Ok(event) = block_event_rx.recv().await { - let read_guard = view_editors.read().await; - let view_editors = read_guard.values(); - let event = if view_editors.len() == 1 { - Cow::Owned(event) - } else { - Cow::Borrowed(&event) - }; - for view_editor in view_editors.iter() { - view_editor.handle_block_event(event.clone()).await; - } - } + tokio::spawn(async move { + loop { + while let Ok(event) = block_event_rx.recv().await { + let read_guard = view_editors.read().await; + let view_editors = read_guard.values(); + let event = if view_editors.len() == 1 { + Cow::Owned(event) + } else { + Cow::Borrowed(&event) + }; + for view_editor in view_editors.iter() { + view_editor.handle_block_event(event.clone()).await; } - }); + } + } + }); } pub async fn make_database_view_rev_manager( - user: &Arc, - view_id: &str, + user: &Arc, + view_id: &str, ) -> FlowyResult>> { - let user_id = user.user_id()?; + let user_id = user.user_id()?; - // Create revision persistence - let pool = user.db_pool()?; - let disk_cache = SQLiteGridViewRevisionPersistence::new(&user_id, pool.clone()); - let configuration = RevisionPersistenceConfiguration::new(2, false); - let rev_persistence = RevisionPersistence::new(&user_id, view_id, disk_cache, configuration); + // Create revision persistence + let pool = user.db_pool()?; + let disk_cache = SQLiteGridViewRevisionPersistence::new(&user_id, pool.clone()); + let configuration = RevisionPersistenceConfiguration::new(2, false); + let rev_persistence = RevisionPersistence::new(&user_id, view_id, disk_cache, configuration); - // Create snapshot persistence - let snapshot_object_id = format!("grid_view:{}", view_id); - let snapshot_persistence = SQLiteDatabaseRevisionSnapshotPersistence::new(&snapshot_object_id, pool); + // Create snapshot persistence + let snapshot_object_id = format!("grid_view:{}", view_id); + let snapshot_persistence = + SQLiteDatabaseRevisionSnapshotPersistence::new(&snapshot_object_id, pool); - let rev_compress = GridViewRevisionMergeable(); - Ok(RevisionManager::new( - &user_id, - view_id, - rev_persistence, - rev_compress, - snapshot_persistence, - )) + let rev_compress = GridViewRevisionMergeable(); + Ok(RevisionManager::new( + &user_id, + view_id, + rev_persistence, + rev_compress, + snapshot_persistence, + )) } diff --git a/frontend/rust-lib/flowy-database/src/services/view_editor/trait_impl.rs b/frontend/rust-lib/flowy-database/src/services/view_editor/trait_impl.rs index 3692f121b0..ba734dc4aa 100644 --- a/frontend/rust-lib/flowy-database/src/services/view_editor/trait_impl.rs +++ b/frontend/rust-lib/flowy-database/src/services/view_editor/trait_impl.rs @@ -10,11 +10,13 @@ use flowy_client_sync::client_database::{GridViewRevisionChangeset, GridViewRevi use flowy_client_sync::make_operations_from_revisions; use flowy_error::{FlowyError, FlowyResult}; use flowy_revision::{ - RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, RevisionObjectSerializer, + RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, + RevisionObjectSerializer, }; use flowy_sqlite::ConnectionPool; use grid_model::{ - FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision, RowRevision, SortRevision, + FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision, RowRevision, + SortRevision, }; use lib_infra::future::{to_fut, Fut, FutureResult}; use lib_ot::core::EmptyAttributes; @@ -23,202 +25,217 @@ use std::sync::Arc; use tokio::sync::RwLock; pub(crate) struct GridViewRevisionCloudService { - #[allow(dead_code)] - pub(crate) token: String, + #[allow(dead_code)] + pub(crate) token: String, } impl RevisionCloudService for GridViewRevisionCloudService { - fn fetch_object(&self, _user_id: &str, _object_id: &str) -> FutureResult, FlowyError> { - FutureResult::new(async move { Ok(vec![]) }) - } + fn fetch_object( + &self, + _user_id: &str, + _object_id: &str, + ) -> FutureResult, FlowyError> { + FutureResult::new(async move { Ok(vec![]) }) + } } pub(crate) struct GridViewRevisionSerde(); impl RevisionObjectDeserializer for GridViewRevisionSerde { - type Output = GridViewRevisionPad; + type Output = GridViewRevisionPad; - fn deserialize_revisions(object_id: &str, revisions: Vec) -> FlowyResult { - let pad = GridViewRevisionPad::from_revisions(object_id, revisions)?; - Ok(pad) - } + fn deserialize_revisions(object_id: &str, revisions: Vec) -> FlowyResult { + let pad = GridViewRevisionPad::from_revisions(object_id, revisions)?; + Ok(pad) + } - fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { - None - } + fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { + None + } } impl RevisionObjectSerializer for GridViewRevisionSerde { - fn combine_revisions(revisions: Vec) -> FlowyResult { - let operations = make_operations_from_revisions::(revisions)?; - Ok(operations.json_bytes()) - } + fn combine_revisions(revisions: Vec) -> FlowyResult { + let operations = make_operations_from_revisions::(revisions)?; + Ok(operations.json_bytes()) + } } pub(crate) struct GridViewRevisionMergeable(); impl RevisionMergeable for GridViewRevisionMergeable { - fn combine_revisions(&self, revisions: Vec) -> FlowyResult { - GridViewRevisionSerde::combine_revisions(revisions) - } + fn combine_revisions(&self, revisions: Vec) -> FlowyResult { + GridViewRevisionSerde::combine_revisions(revisions) + } } pub(crate) struct GroupConfigurationReaderImpl { - pub(crate) pad: Arc>, - pub(crate) view_editor_delegate: Arc, + pub(crate) pad: Arc>, + pub(crate) view_editor_delegate: Arc, } impl GroupConfigurationReader for GroupConfigurationReaderImpl { - fn get_configuration(&self) -> Fut>> { - let view_pad = self.pad.clone(); - to_fut(async move { - let mut groups = view_pad.read().await.get_all_groups(); - if groups.is_empty() { - None - } else { - debug_assert_eq!(groups.len(), 1); - Some(groups.pop().unwrap()) - } - }) - } + fn get_configuration(&self) -> Fut>> { + let view_pad = self.pad.clone(); + to_fut(async move { + let mut groups = view_pad.read().await.get_all_groups(); + if groups.is_empty() { + None + } else { + debug_assert_eq!(groups.len(), 1); + Some(groups.pop().unwrap()) + } + }) + } - fn get_configuration_cells(&self, field_id: &str) -> Fut>> { - let field_id = field_id.to_owned(); - let view_editor_delegate = self.view_editor_delegate.clone(); - to_fut(async move { get_cells_for_field(view_editor_delegate, &field_id).await }) - } + fn get_configuration_cells(&self, field_id: &str) -> Fut>> { + let field_id = field_id.to_owned(); + let view_editor_delegate = self.view_editor_delegate.clone(); + to_fut(async move { get_cells_for_field(view_editor_delegate, &field_id).await }) + } } pub(crate) struct GroupConfigurationWriterImpl { - pub(crate) user_id: String, - pub(crate) rev_manager: Arc>>, - pub(crate) view_pad: Arc>, + pub(crate) user_id: String, + pub(crate) rev_manager: Arc>>, + pub(crate) view_pad: Arc>, } impl GroupConfigurationWriter for GroupConfigurationWriterImpl { - fn save_configuration( - &self, - field_id: &str, - field_type: FieldTypeRevision, - group_configuration: GroupConfigurationRevision, - ) -> Fut> { - let user_id = self.user_id.clone(); - let rev_manager = self.rev_manager.clone(); - let view_pad = self.view_pad.clone(); - let field_id = field_id.to_owned(); + fn save_configuration( + &self, + field_id: &str, + field_type: FieldTypeRevision, + group_configuration: GroupConfigurationRevision, + ) -> Fut> { + let user_id = self.user_id.clone(); + let rev_manager = self.rev_manager.clone(); + let view_pad = self.view_pad.clone(); + let field_id = field_id.to_owned(); - to_fut(async move { - let changeset = view_pad.write().await.insert_or_update_group_configuration( - &field_id, - &field_type, - group_configuration, - )?; + to_fut(async move { + let changeset = view_pad + .write() + .await + .insert_or_update_group_configuration(&field_id, &field_type, group_configuration)?; - if let Some(changeset) = changeset { - apply_change(&user_id, rev_manager, changeset).await?; - } - Ok(()) - }) - } + if let Some(changeset) = changeset { + apply_change(&user_id, rev_manager, changeset).await?; + } + Ok(()) + }) + } } pub(crate) async fn apply_change( - _user_id: &str, - rev_manager: Arc>>, - change: GridViewRevisionChangeset, + _user_id: &str, + rev_manager: Arc>>, + change: GridViewRevisionChangeset, ) -> FlowyResult<()> { - let GridViewRevisionChangeset { operations: delta, md5 } = change; - let data = delta.json_bytes(); - let _ = rev_manager.add_local_revision(data, md5).await?; - Ok(()) + let GridViewRevisionChangeset { + operations: delta, + md5, + } = change; + let data = delta.json_bytes(); + let _ = rev_manager.add_local_revision(data, md5).await?; + Ok(()) } -pub fn make_grid_setting(view_pad: &GridViewRevisionPad, field_revs: &[Arc]) -> DatabaseViewSettingPB { - let layout_type: LayoutTypePB = view_pad.layout.clone().into(); - let filters = view_pad.get_all_filters(field_revs); - let group_configurations = view_pad.get_groups_by_field_revs(field_revs); - let sorts = view_pad.get_all_sorts(field_revs); - DatabaseViewSettingPB { - support_layouts: ViewLayoutPB::all(), - current_layout: layout_type, - filters: filters.into(), - sorts: sorts.into(), - group_configurations: group_configurations.into(), - } +pub fn make_grid_setting( + view_pad: &GridViewRevisionPad, + field_revs: &[Arc], +) -> DatabaseViewSettingPB { + let layout_type: LayoutTypePB = view_pad.layout.clone().into(); + let filters = view_pad.get_all_filters(field_revs); + let group_configurations = view_pad.get_groups_by_field_revs(field_revs); + let sorts = view_pad.get_all_sorts(field_revs); + DatabaseViewSettingPB { + support_layouts: ViewLayoutPB::all(), + current_layout: layout_type, + filters: filters.into(), + sorts: sorts.into(), + group_configurations: group_configurations.into(), + } } pub(crate) struct GridViewFilterDelegateImpl { - pub(crate) editor_delegate: Arc, - pub(crate) view_revision_pad: Arc>, + pub(crate) editor_delegate: Arc, + pub(crate) view_revision_pad: Arc>, } impl FilterDelegate for GridViewFilterDelegateImpl { - fn get_filter_rev(&self, filter_type: FilterType) -> Fut>> { - let pad = self.view_revision_pad.clone(); - to_fut(async move { - let field_type_rev: FieldTypeRevision = filter_type.field_type.into(); - let mut filters = pad.read().await.get_filters(&filter_type.field_id, &field_type_rev); - if filters.is_empty() { - None - } else { - debug_assert_eq!(filters.len(), 1); - filters.pop() - } - }) - } + fn get_filter_rev(&self, filter_type: FilterType) -> Fut>> { + let pad = self.view_revision_pad.clone(); + to_fut(async move { + let field_type_rev: FieldTypeRevision = filter_type.field_type.into(); + let mut filters = pad + .read() + .await + .get_filters(&filter_type.field_id, &field_type_rev); + if filters.is_empty() { + None + } else { + debug_assert_eq!(filters.len(), 1); + filters.pop() + } + }) + } - fn get_field_rev(&self, field_id: &str) -> Fut>> { - self.editor_delegate.get_field_rev(field_id) - } + fn get_field_rev(&self, field_id: &str) -> Fut>> { + self.editor_delegate.get_field_rev(field_id) + } - fn get_field_revs(&self, field_ids: Option>) -> Fut>> { - self.editor_delegate.get_field_revs(field_ids) - } + fn get_field_revs(&self, field_ids: Option>) -> Fut>> { + self.editor_delegate.get_field_revs(field_ids) + } - fn get_blocks(&self) -> Fut> { - self.editor_delegate.get_blocks() - } + fn get_blocks(&self) -> Fut> { + self.editor_delegate.get_blocks() + } - fn get_row_rev(&self, row_id: &str) -> Fut)>> { - self.editor_delegate.get_row_rev(row_id) - } + fn get_row_rev(&self, row_id: &str) -> Fut)>> { + self.editor_delegate.get_row_rev(row_id) + } } pub(crate) struct GridViewSortDelegateImpl { - pub(crate) editor_delegate: Arc, - pub(crate) view_revision_pad: Arc>, - pub(crate) filter_controller: Arc, + pub(crate) editor_delegate: Arc, + pub(crate) view_revision_pad: Arc>, + pub(crate) filter_controller: Arc, } impl SortDelegate for GridViewSortDelegateImpl { - fn get_sort_rev(&self, sort_type: SortType) -> Fut>> { - let pad = self.view_revision_pad.clone(); - to_fut(async move { - let field_type_rev: FieldTypeRevision = sort_type.field_type.into(); - let mut sorts = pad.read().await.get_sorts(&sort_type.field_id, &field_type_rev); - if sorts.is_empty() { - None - } else { - // Currently, one sort_type should have one sort. - debug_assert_eq!(sorts.len(), 1); - sorts.pop() - } - }) - } + fn get_sort_rev(&self, sort_type: SortType) -> Fut>> { + let pad = self.view_revision_pad.clone(); + to_fut(async move { + let field_type_rev: FieldTypeRevision = sort_type.field_type.into(); + let mut sorts = pad + .read() + .await + .get_sorts(&sort_type.field_id, &field_type_rev); + if sorts.is_empty() { + None + } else { + // Currently, one sort_type should have one sort. + debug_assert_eq!(sorts.len(), 1); + sorts.pop() + } + }) + } - fn get_row_revs(&self) -> Fut>> { - let filter_controller = self.filter_controller.clone(); - let editor_delegate = self.editor_delegate.clone(); - to_fut(async move { - let mut row_revs = editor_delegate.get_row_revs(None).await; - filter_controller.filter_row_revs(&mut row_revs).await; - row_revs - }) - } + fn get_row_revs(&self) -> Fut>> { + let filter_controller = self.filter_controller.clone(); + let editor_delegate = self.editor_delegate.clone(); + to_fut(async move { + let mut row_revs = editor_delegate.get_row_revs(None).await; + filter_controller.filter_row_revs(&mut row_revs).await; + row_revs + }) + } - fn get_field_rev(&self, field_id: &str) -> Fut>> { - self.editor_delegate.get_field_rev(field_id) - } + fn get_field_rev(&self, field_id: &str) -> Fut>> { + self.editor_delegate.get_field_rev(field_id) + } - fn get_field_revs(&self, field_ids: Option>) -> Fut>> { - self.editor_delegate.get_field_revs(field_ids) - } + fn get_field_revs(&self, field_ids: Option>) -> Fut>> { + self.editor_delegate.get_field_revs(field_ids) + } } diff --git a/frontend/rust-lib/flowy-database/src/util.rs b/frontend/rust-lib/flowy-database/src/util.rs index 5243432204..0d29952da5 100644 --- a/frontend/rust-lib/flowy-database/src/util.rs +++ b/frontend/rust-lib/flowy-database/src/util.rs @@ -5,216 +5,224 @@ use flowy_client_sync::client_database::DatabaseBuilder; use grid_model::BuildDatabaseContext; pub fn make_default_grid() -> BuildDatabaseContext { - let mut database_builder = DatabaseBuilder::new(); - // text - let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Name") - .visibility(true) - .primary(true) - .build(); - database_builder.add_field(text_field); + let mut database_builder = DatabaseBuilder::new(); + // text + let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) + .name("Name") + .visibility(true) + .primary(true) + .build(); + database_builder.add_field(text_field); - // single select - let single_select = SingleSelectTypeOptionBuilder::default(); - let single_select_field = FieldBuilder::new(single_select).name("Type").visibility(true).build(); - database_builder.add_field(single_select_field); + // single select + let single_select = SingleSelectTypeOptionBuilder::default(); + let single_select_field = FieldBuilder::new(single_select) + .name("Type") + .visibility(true) + .build(); + database_builder.add_field(single_select_field); - // checkbox - let checkbox_field = FieldBuilder::from_field_type(&FieldType::Checkbox) - .name("Done") - .visibility(true) - .build(); - database_builder.add_field(checkbox_field); + // checkbox + let checkbox_field = FieldBuilder::from_field_type(&FieldType::Checkbox) + .name("Done") + .visibility(true) + .build(); + database_builder.add_field(checkbox_field); - database_builder.add_empty_row(); - database_builder.add_empty_row(); - database_builder.add_empty_row(); - database_builder.build() + database_builder.add_empty_row(); + database_builder.add_empty_row(); + database_builder.add_empty_row(); + database_builder.build() } pub fn make_default_board() -> BuildDatabaseContext { - let mut database_builder = DatabaseBuilder::new(); - // text - let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Description") - .visibility(true) - .primary(true) - .build(); - let text_field_id = text_field.id.clone(); - database_builder.add_field(text_field); + let mut database_builder = DatabaseBuilder::new(); + // text + let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) + .name("Description") + .visibility(true) + .primary(true) + .build(); + let text_field_id = text_field.id.clone(); + database_builder.add_field(text_field); - // single select - let to_do_option = SelectOptionPB::with_color("To Do", SelectOptionColorPB::Purple); - let doing_option = SelectOptionPB::with_color("Doing", SelectOptionColorPB::Orange); - let done_option = SelectOptionPB::with_color("Done", SelectOptionColorPB::Yellow); - let single_select_type_option = SingleSelectTypeOptionBuilder::default() - .add_option(to_do_option.clone()) - .add_option(doing_option) - .add_option(done_option); - let single_select_field = FieldBuilder::new(single_select_type_option) - .name("Status") - .visibility(true) - .build(); - let single_select_field_id = single_select_field.id.clone(); - database_builder.add_field(single_select_field); + // single select + let to_do_option = SelectOptionPB::with_color("To Do", SelectOptionColorPB::Purple); + let doing_option = SelectOptionPB::with_color("Doing", SelectOptionColorPB::Orange); + let done_option = SelectOptionPB::with_color("Done", SelectOptionColorPB::Yellow); + let single_select_type_option = SingleSelectTypeOptionBuilder::default() + .add_option(to_do_option.clone()) + .add_option(doing_option) + .add_option(done_option); + let single_select_field = FieldBuilder::new(single_select_type_option) + .name("Status") + .visibility(true) + .build(); + let single_select_field_id = single_select_field.id.clone(); + database_builder.add_field(single_select_field); - for i in 0..3 { - let mut row_builder = RowRevisionBuilder::new(database_builder.block_id(), database_builder.field_revs()); - row_builder.insert_select_option_cell(&single_select_field_id, vec![to_do_option.id.clone()]); - let data = format!("Card {}", i + 1); - row_builder.insert_text_cell(&text_field_id, data); - let row = row_builder.build(); - database_builder.add_row(row); - } + for i in 0..3 { + let mut row_builder = + RowRevisionBuilder::new(database_builder.block_id(), database_builder.field_revs()); + row_builder.insert_select_option_cell(&single_select_field_id, vec![to_do_option.id.clone()]); + let data = format!("Card {}", i + 1); + row_builder.insert_text_cell(&text_field_id, data); + let row = row_builder.build(); + database_builder.add_row(row); + } - database_builder.build() + database_builder.build() } pub fn make_default_calendar() -> BuildDatabaseContext { - let mut database_builder = DatabaseBuilder::new(); - // text - let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Description") - .visibility(true) - .primary(true) - .build(); - database_builder.add_field(text_field); + let mut database_builder = DatabaseBuilder::new(); + // text + let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) + .name("Description") + .visibility(true) + .primary(true) + .build(); + database_builder.add_field(text_field); - // date - let date_type_option = DateTypeOptionBuilder::default(); - let date_field = FieldBuilder::new(date_type_option) - .name("Date") - .visibility(true) - .build(); - database_builder.add_field(date_field); + // date + let date_type_option = DateTypeOptionBuilder::default(); + let date_field = FieldBuilder::new(date_type_option) + .name("Date") + .visibility(true) + .build(); + database_builder.add_field(date_field); - // single select - let multi_select_type_option = MultiSelectTypeOptionBuilder::default(); - let multi_select_field = FieldBuilder::new(multi_select_type_option) - .name("Tags") - .visibility(true) - .build(); - database_builder.add_field(multi_select_field); - database_builder.build() + // single select + let multi_select_type_option = MultiSelectTypeOptionBuilder::default(); + let multi_select_field = FieldBuilder::new(multi_select_type_option) + .name("Tags") + .visibility(true) + .build(); + database_builder.add_field(multi_select_field); + database_builder.build() } #[allow(dead_code)] pub fn make_default_board_2() -> BuildDatabaseContext { - let mut database_builder = DatabaseBuilder::new(); - // text - let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Description") - .visibility(true) - .primary(true) - .build(); - let text_field_id = text_field.id.clone(); - database_builder.add_field(text_field); + let mut database_builder = DatabaseBuilder::new(); + // text + let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) + .name("Description") + .visibility(true) + .primary(true) + .build(); + let text_field_id = text_field.id.clone(); + database_builder.add_field(text_field); - // single select - let to_do_option = SelectOptionPB::with_color("To Do", SelectOptionColorPB::Purple); - let doing_option = SelectOptionPB::with_color("Doing", SelectOptionColorPB::Orange); - let done_option = SelectOptionPB::with_color("Done", SelectOptionColorPB::Yellow); - let single_select_type_option = SingleSelectTypeOptionBuilder::default() - .add_option(to_do_option.clone()) - .add_option(doing_option.clone()) - .add_option(done_option.clone()); - let single_select_field = FieldBuilder::new(single_select_type_option) - .name("Status") - .visibility(true) - .build(); - let single_select_field_id = single_select_field.id.clone(); - database_builder.add_field(single_select_field); + // single select + let to_do_option = SelectOptionPB::with_color("To Do", SelectOptionColorPB::Purple); + let doing_option = SelectOptionPB::with_color("Doing", SelectOptionColorPB::Orange); + let done_option = SelectOptionPB::with_color("Done", SelectOptionColorPB::Yellow); + let single_select_type_option = SingleSelectTypeOptionBuilder::default() + .add_option(to_do_option.clone()) + .add_option(doing_option.clone()) + .add_option(done_option.clone()); + let single_select_field = FieldBuilder::new(single_select_type_option) + .name("Status") + .visibility(true) + .build(); + let single_select_field_id = single_select_field.id.clone(); + database_builder.add_field(single_select_field); - // MultiSelect - let work_option = SelectOptionPB::with_color("Work", SelectOptionColorPB::Aqua); - let travel_option = SelectOptionPB::with_color("Travel", SelectOptionColorPB::Green); - let fun_option = SelectOptionPB::with_color("Fun", SelectOptionColorPB::Lime); - let health_option = SelectOptionPB::with_color("Health", SelectOptionColorPB::Pink); - let multi_select_type_option = MultiSelectTypeOptionBuilder::default() - .add_option(travel_option.clone()) - .add_option(work_option.clone()) - .add_option(fun_option.clone()) - .add_option(health_option.clone()); - let multi_select_field = FieldBuilder::new(multi_select_type_option) - .name("Tags") - .visibility(true) - .build(); - let multi_select_field_id = multi_select_field.id.clone(); - database_builder.add_field(multi_select_field); + // MultiSelect + let work_option = SelectOptionPB::with_color("Work", SelectOptionColorPB::Aqua); + let travel_option = SelectOptionPB::with_color("Travel", SelectOptionColorPB::Green); + let fun_option = SelectOptionPB::with_color("Fun", SelectOptionColorPB::Lime); + let health_option = SelectOptionPB::with_color("Health", SelectOptionColorPB::Pink); + let multi_select_type_option = MultiSelectTypeOptionBuilder::default() + .add_option(travel_option.clone()) + .add_option(work_option.clone()) + .add_option(fun_option.clone()) + .add_option(health_option.clone()); + let multi_select_field = FieldBuilder::new(multi_select_type_option) + .name("Tags") + .visibility(true) + .build(); + let multi_select_field_id = multi_select_field.id.clone(); + database_builder.add_field(multi_select_field); - for i in 0..3 { - let mut row_builder = RowRevisionBuilder::new(database_builder.block_id(), database_builder.field_revs()); - row_builder.insert_select_option_cell(&single_select_field_id, vec![to_do_option.id.clone()]); - match i { - 0 => { - row_builder.insert_text_cell(&text_field_id, "Update AppFlowy Website".to_string()); - row_builder.insert_select_option_cell(&multi_select_field_id, vec![work_option.id.clone()]); - } - 1 => { - row_builder.insert_text_cell(&text_field_id, "Learn French".to_string()); - let mut options = SelectOptionIds::new(); - options.push(fun_option.id.clone()); - options.push(travel_option.id.clone()); - row_builder.insert_select_option_cell(&multi_select_field_id, vec![options.to_string()]); - } + for i in 0..3 { + let mut row_builder = + RowRevisionBuilder::new(database_builder.block_id(), database_builder.field_revs()); + row_builder.insert_select_option_cell(&single_select_field_id, vec![to_do_option.id.clone()]); + match i { + 0 => { + row_builder.insert_text_cell(&text_field_id, "Update AppFlowy Website".to_string()); + row_builder.insert_select_option_cell(&multi_select_field_id, vec![work_option.id.clone()]); + }, + 1 => { + row_builder.insert_text_cell(&text_field_id, "Learn French".to_string()); + let mut options = SelectOptionIds::new(); + options.push(fun_option.id.clone()); + options.push(travel_option.id.clone()); + row_builder.insert_select_option_cell(&multi_select_field_id, vec![options.to_string()]); + }, - 2 => { - row_builder.insert_text_cell(&text_field_id, "Exercise 4x/week".to_string()); - row_builder.insert_select_option_cell(&multi_select_field_id, vec![fun_option.id.clone()]); - } - _ => {} - } - let row = row_builder.build(); - database_builder.add_row(row); + 2 => { + row_builder.insert_text_cell(&text_field_id, "Exercise 4x/week".to_string()); + row_builder.insert_select_option_cell(&multi_select_field_id, vec![fun_option.id.clone()]); + }, + _ => {}, } + let row = row_builder.build(); + database_builder.add_row(row); + } - for i in 0..3 { - let mut row_builder = RowRevisionBuilder::new(database_builder.block_id(), database_builder.field_revs()); - row_builder.insert_select_option_cell(&single_select_field_id, vec![doing_option.id.clone()]); - match i { - 0 => { - row_builder.insert_text_cell(&text_field_id, "Learn how to swim".to_string()); - row_builder.insert_select_option_cell(&multi_select_field_id, vec![fun_option.id.clone()]); - } - 1 => { - row_builder.insert_text_cell(&text_field_id, "Meditate 10 mins each day".to_string()); - row_builder.insert_select_option_cell(&multi_select_field_id, vec![health_option.id.clone()]); - } + for i in 0..3 { + let mut row_builder = + RowRevisionBuilder::new(database_builder.block_id(), database_builder.field_revs()); + row_builder.insert_select_option_cell(&single_select_field_id, vec![doing_option.id.clone()]); + match i { + 0 => { + row_builder.insert_text_cell(&text_field_id, "Learn how to swim".to_string()); + row_builder.insert_select_option_cell(&multi_select_field_id, vec![fun_option.id.clone()]); + }, + 1 => { + row_builder.insert_text_cell(&text_field_id, "Meditate 10 mins each day".to_string()); + row_builder + .insert_select_option_cell(&multi_select_field_id, vec![health_option.id.clone()]); + }, - 2 => { - row_builder.insert_text_cell(&text_field_id, "Write atomic essays ".to_string()); - let mut options = SelectOptionIds::new(); - options.push(fun_option.id.clone()); - options.push(work_option.id.clone()); - row_builder.insert_select_option_cell(&multi_select_field_id, vec![options.to_string()]); - } - _ => {} - } - let row = row_builder.build(); - database_builder.add_row(row); + 2 => { + row_builder.insert_text_cell(&text_field_id, "Write atomic essays ".to_string()); + let mut options = SelectOptionIds::new(); + options.push(fun_option.id.clone()); + options.push(work_option.id.clone()); + row_builder.insert_select_option_cell(&multi_select_field_id, vec![options.to_string()]); + }, + _ => {}, } + let row = row_builder.build(); + database_builder.add_row(row); + } - for i in 0..2 { - let mut row_builder = RowRevisionBuilder::new(database_builder.block_id(), database_builder.field_revs()); - row_builder.insert_select_option_cell(&single_select_field_id, vec![done_option.id.clone()]); - match i { - 0 => { - row_builder.insert_text_cell(&text_field_id, "Publish an article".to_string()); - row_builder.insert_select_option_cell(&multi_select_field_id, vec![work_option.id.clone()]); - } - 1 => { - row_builder.insert_text_cell(&text_field_id, "Visit Chicago".to_string()); - let mut options = SelectOptionIds::new(); - options.push(travel_option.id.clone()); - options.push(fun_option.id.clone()); - row_builder.insert_select_option_cell(&multi_select_field_id, vec![options.to_string()]); - } + for i in 0..2 { + let mut row_builder = + RowRevisionBuilder::new(database_builder.block_id(), database_builder.field_revs()); + row_builder.insert_select_option_cell(&single_select_field_id, vec![done_option.id.clone()]); + match i { + 0 => { + row_builder.insert_text_cell(&text_field_id, "Publish an article".to_string()); + row_builder.insert_select_option_cell(&multi_select_field_id, vec![work_option.id.clone()]); + }, + 1 => { + row_builder.insert_text_cell(&text_field_id, "Visit Chicago".to_string()); + let mut options = SelectOptionIds::new(); + options.push(travel_option.id.clone()); + options.push(fun_option.id.clone()); + row_builder.insert_select_option_cell(&multi_select_field_id, vec![options.to_string()]); + }, - _ => {} - } - let row = row_builder.build(); - database_builder.add_row(row); + _ => {}, } + let row = row_builder.build(); + database_builder.add_row(row); + } - database_builder.build() + database_builder.build() } diff --git a/frontend/rust-lib/flowy-database/tests/grid/block_test/block_test.rs b/frontend/rust-lib/flowy-database/tests/grid/block_test/block_test.rs index bfccd5ba2d..5202f76dcc 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/block_test/block_test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/block_test/block_test.rs @@ -5,37 +5,41 @@ use grid_model::{GridBlockMetaRevision, GridBlockMetaRevisionChangeset}; #[tokio::test] async fn grid_create_block() { - let block_meta_rev = GridBlockMetaRevision::new(); - let scripts = vec![ - AssertBlockCount(1), - CreateBlock { block: block_meta_rev }, - AssertBlockCount(2), - ]; - DatabaseRowTest::new().await.run_scripts(scripts).await; + let block_meta_rev = GridBlockMetaRevision::new(); + let scripts = vec![ + AssertBlockCount(1), + CreateBlock { + block: block_meta_rev, + }, + AssertBlockCount(2), + ]; + DatabaseRowTest::new().await.run_scripts(scripts).await; } #[tokio::test] async fn grid_update_block() { - let block_meta_rev = GridBlockMetaRevision::new(); - let mut cloned_grid_block = block_meta_rev.clone(); - let changeset = GridBlockMetaRevisionChangeset { - block_id: block_meta_rev.block_id.clone(), - start_row_index: Some(2), - row_count: Some(10), - }; + let block_meta_rev = GridBlockMetaRevision::new(); + let mut cloned_grid_block = block_meta_rev.clone(); + let changeset = GridBlockMetaRevisionChangeset { + block_id: block_meta_rev.block_id.clone(), + start_row_index: Some(2), + row_count: Some(10), + }; - cloned_grid_block.start_row_index = 2; - cloned_grid_block.row_count = 10; + cloned_grid_block.start_row_index = 2; + cloned_grid_block.row_count = 10; - let scripts = vec![ - AssertBlockCount(1), - CreateBlock { block: block_meta_rev }, - UpdateBlock { changeset }, - AssertBlockCount(2), - AssertBlockEqual { - block_index: 1, - block: cloned_grid_block, - }, - ]; - DatabaseRowTest::new().await.run_scripts(scripts).await; + let scripts = vec![ + AssertBlockCount(1), + CreateBlock { + block: block_meta_rev, + }, + UpdateBlock { changeset }, + AssertBlockCount(2), + AssertBlockEqual { + block_index: 1, + block: cloned_grid_block, + }, + ]; + DatabaseRowTest::new().await.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-database/tests/grid/block_test/row_test.rs b/frontend/rust-lib/flowy-database/tests/grid/block_test/row_test.rs index ecf55e13af..822e9942ad 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/block_test/row_test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/block_test/row_test.rs @@ -7,129 +7,129 @@ use grid_model::RowChangeset; #[tokio::test] async fn grid_create_row_count_test() { - let mut test = DatabaseRowTest::new().await; - let scripts = vec![ - AssertRowCount(6), - CreateEmptyRow, - CreateEmptyRow, - CreateRow { - row_rev: test.row_builder().build(), - }, - AssertRowCount(9), - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseRowTest::new().await; + let scripts = vec![ + AssertRowCount(6), + CreateEmptyRow, + CreateEmptyRow, + CreateRow { + row_rev: test.row_builder().build(), + }, + AssertRowCount(9), + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_update_row() { - let mut test = DatabaseRowTest::new().await; - let row_rev = test.row_builder().build(); - let changeset = RowChangeset { - row_id: row_rev.id.clone(), - height: None, - visibility: None, - cell_by_field_id: Default::default(), - }; - let row_count = test.row_revs.len(); - let scripts = vec![CreateRow { row_rev }, UpdateRow { changeset }]; - test.run_scripts(scripts).await; + let mut test = DatabaseRowTest::new().await; + let row_rev = test.row_builder().build(); + let changeset = RowChangeset { + row_id: row_rev.id.clone(), + height: None, + visibility: None, + cell_by_field_id: Default::default(), + }; + let row_count = test.row_revs.len(); + let scripts = vec![CreateRow { row_rev }, UpdateRow { changeset }]; + test.run_scripts(scripts).await; - let expected_row = test.last_row().unwrap(); - let scripts = vec![AssertRow { expected_row }, AssertRowCount(row_count + 1)]; - test.run_scripts(scripts).await; + let expected_row = test.last_row().unwrap(); + let scripts = vec![AssertRow { expected_row }, AssertRowCount(row_count + 1)]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_delete_row() { - let mut test = DatabaseRowTest::new().await; - let row_1 = test.row_builder().build(); - let row_2 = test.row_builder().build(); - let row_ids = vec![row_1.id.clone(), row_2.id.clone()]; - let row_count = test.row_revs.len() as i32; - let scripts = vec![ - CreateRow { row_rev: row_1 }, - CreateRow { row_rev: row_2 }, - AssertBlockCount(1), - AssertBlock { - block_index: 0, - row_count: row_count + 2, - start_row_index: 0, - }, - DeleteRows { row_ids }, - AssertBlock { - block_index: 0, - row_count, - start_row_index: 0, - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseRowTest::new().await; + let row_1 = test.row_builder().build(); + let row_2 = test.row_builder().build(); + let row_ids = vec![row_1.id.clone(), row_2.id.clone()]; + let row_count = test.row_revs.len() as i32; + let scripts = vec![ + CreateRow { row_rev: row_1 }, + CreateRow { row_rev: row_2 }, + AssertBlockCount(1), + AssertBlock { + block_index: 0, + row_count: row_count + 2, + start_row_index: 0, + }, + DeleteRows { row_ids }, + AssertBlock { + block_index: 0, + row_count, + start_row_index: 0, + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_row_add_cells_test() { - let mut test = DatabaseRowTest::new().await; - let mut builder = CreateRowScriptBuilder::new(&test); - builder.insert(FieldType::RichText, "hello world", "hello world"); - builder.insert(FieldType::DateTime, "1647251762", "2022/03/14"); - builder.insert(FieldType::Number, "18,443", "$18,443.00"); - builder.insert(FieldType::Checkbox, "false", UNCHECK); - builder.insert(FieldType::URL, "https://appflowy.io", "https://appflowy.io"); - builder.insert_single_select_cell(|mut options| options.remove(0), COMPLETED); - builder.insert_multi_select_cell( - |options| options, - &vec![GOOGLE, FACEBOOK, TWITTER].join(SELECTION_IDS_SEPARATOR), - ); - let scripts = builder.build(); - test.run_scripts(scripts).await; + let mut test = DatabaseRowTest::new().await; + let mut builder = CreateRowScriptBuilder::new(&test); + builder.insert(FieldType::RichText, "hello world", "hello world"); + builder.insert(FieldType::DateTime, "1647251762", "2022/03/14"); + builder.insert(FieldType::Number, "18,443", "$18,443.00"); + builder.insert(FieldType::Checkbox, "false", UNCHECK); + builder.insert(FieldType::URL, "https://appflowy.io", "https://appflowy.io"); + builder.insert_single_select_cell(|mut options| options.remove(0), COMPLETED); + builder.insert_multi_select_cell( + |options| options, + &vec![GOOGLE, FACEBOOK, TWITTER].join(SELECTION_IDS_SEPARATOR), + ); + let scripts = builder.build(); + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_row_insert_number_test() { - let mut test = DatabaseRowTest::new().await; - for (val, expected) in &[("1647251762", "2022/03/14"), ("2022/03/14", ""), ("", "")] { - let mut builder = CreateRowScriptBuilder::new(&test); - builder.insert(FieldType::DateTime, val, expected); - let scripts = builder.build(); - test.run_scripts(scripts).await; - } + let mut test = DatabaseRowTest::new().await; + for (val, expected) in &[("1647251762", "2022/03/14"), ("2022/03/14", ""), ("", "")] { + let mut builder = CreateRowScriptBuilder::new(&test); + builder.insert(FieldType::DateTime, val, expected); + let scripts = builder.build(); + test.run_scripts(scripts).await; + } } #[tokio::test] async fn grid_row_insert_date_test() { - let mut test = DatabaseRowTest::new().await; - for (val, expected) in &[ - ("18,443", "$18,443.00"), - ("0", "$0.00"), - ("100000", "$100,000.00"), - ("$100,000.00", "$100,000.00"), - ("", ""), - ] { - let mut builder = CreateRowScriptBuilder::new(&test); - builder.insert(FieldType::Number, val, expected); - let scripts = builder.build(); - test.run_scripts(scripts).await; - } + let mut test = DatabaseRowTest::new().await; + for (val, expected) in &[ + ("18,443", "$18,443.00"), + ("0", "$0.00"), + ("100000", "$100,000.00"), + ("$100,000.00", "$100,000.00"), + ("", ""), + ] { + let mut builder = CreateRowScriptBuilder::new(&test); + builder.insert(FieldType::Number, val, expected); + let scripts = builder.build(); + test.run_scripts(scripts).await; + } } #[tokio::test] async fn grid_row_insert_single_select_test() { - let mut test = DatabaseRowTest::new().await; - let mut builder = CreateRowScriptBuilder::new(&test); - builder.insert_single_select_cell(|mut options| options.pop().unwrap(), PAUSED); - let scripts = builder.build(); - test.run_scripts(scripts).await; + let mut test = DatabaseRowTest::new().await; + let mut builder = CreateRowScriptBuilder::new(&test); + builder.insert_single_select_cell(|mut options| options.pop().unwrap(), PAUSED); + let scripts = builder.build(); + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_row_insert_multi_select_test() { - let mut test = DatabaseRowTest::new().await; - let mut builder = CreateRowScriptBuilder::new(&test); - builder.insert_multi_select_cell( - |mut options| { - options.remove(0); - options - }, - &vec![FACEBOOK, TWITTER].join(SELECTION_IDS_SEPARATOR), - ); - let scripts = builder.build(); - test.run_scripts(scripts).await; + let mut test = DatabaseRowTest::new().await; + let mut builder = CreateRowScriptBuilder::new(&test); + builder.insert_multi_select_cell( + |mut options| { + options.remove(0); + options + }, + &vec![FACEBOOK, TWITTER].join(SELECTION_IDS_SEPARATOR), + ); + let scripts = builder.build(); + test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-database/tests/grid/block_test/script.rs b/frontend/rust-lib/flowy-database/tests/grid/block_test/script.rs index ab17598065..a811da0f88 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/block_test/script.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/block_test/script.rs @@ -4,404 +4,422 @@ use crate::grid::database_editor::DatabaseEditorTest; use flowy_database::entities::{CellIdParams, CreateRowParams, FieldType, LayoutTypePB, RowPB}; use flowy_database::services::field::*; use flowy_database::services::row::DatabaseBlockRow; -use grid_model::{GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowChangeset, RowRevision}; +use grid_model::{ + GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowChangeset, RowRevision, +}; use std::collections::HashMap; use std::sync::Arc; use strum::IntoEnumIterator; pub enum RowScript { - CreateEmptyRow, - CreateRow { - row_rev: RowRevision, - }, - UpdateRow { - changeset: RowChangeset, - }, - AssertRow { - expected_row: RowRevision, - }, - DeleteRows { - row_ids: Vec, - }, - AssertCell { - row_id: String, - field_id: String, - field_type: FieldType, - expected: String, - }, - AssertRowCount(usize), - CreateBlock { - block: GridBlockMetaRevision, - }, - UpdateBlock { - changeset: GridBlockMetaRevisionChangeset, - }, - AssertBlockCount(usize), - AssertBlock { - block_index: usize, - row_count: i32, - start_row_index: i32, - }, - AssertBlockEqual { - block_index: usize, - block: GridBlockMetaRevision, - }, + CreateEmptyRow, + CreateRow { + row_rev: RowRevision, + }, + UpdateRow { + changeset: RowChangeset, + }, + AssertRow { + expected_row: RowRevision, + }, + DeleteRows { + row_ids: Vec, + }, + AssertCell { + row_id: String, + field_id: String, + field_type: FieldType, + expected: String, + }, + AssertRowCount(usize), + CreateBlock { + block: GridBlockMetaRevision, + }, + UpdateBlock { + changeset: GridBlockMetaRevisionChangeset, + }, + AssertBlockCount(usize), + AssertBlock { + block_index: usize, + row_count: i32, + start_row_index: i32, + }, + AssertBlockEqual { + block_index: usize, + block: GridBlockMetaRevision, + }, } pub struct DatabaseRowTest { - inner: DatabaseEditorTest, + inner: DatabaseEditorTest, } impl DatabaseRowTest { - pub async fn new() -> Self { - let editor_test = DatabaseEditorTest::new_table().await; - Self { inner: editor_test } - } + pub async fn new() -> Self { + let editor_test = DatabaseEditorTest::new_table().await; + Self { inner: editor_test } + } - pub fn last_row(&self) -> Option { - self.row_revs.last().map(|a| a.clone().as_ref().clone()) - } + pub fn last_row(&self) -> Option { + self.row_revs.last().map(|a| a.clone().as_ref().clone()) + } - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; + pub async fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + pub fn row_builder(&self) -> GridRowTestBuilder { + GridRowTestBuilder::new(self.block_id(), &self.field_revs) + } + + pub async fn run_script(&mut self, script: RowScript) { + match script { + RowScript::CreateEmptyRow => { + let params = CreateRowParams { + database_id: self.editor.database_id.clone(), + start_row_id: None, + group_id: None, + layout: LayoutTypePB::Grid, + }; + let row_order = self.editor.create_row(params).await.unwrap(); + self + .row_by_row_id + .insert(row_order.row_id().to_owned(), row_order); + self.row_revs = self.get_row_revs().await; + self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); + }, + RowScript::CreateRow { row_rev } => { + let row_orders = self.editor.insert_rows(vec![row_rev]).await.unwrap(); + for row_order in row_orders { + self + .row_by_row_id + .insert(row_order.row_id().to_owned(), row_order); } + self.row_revs = self.get_row_revs().await; + self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); + }, + RowScript::UpdateRow { changeset: change } => self.editor.update_row(change).await.unwrap(), + RowScript::DeleteRows { row_ids } => { + let row_pbs = row_ids + .into_iter() + .map(|row_id| self.row_by_row_id.get(&row_id).unwrap().clone()) + .collect::>(); + + let block_rows = block_from_row_pbs(row_pbs); + self.editor.delete_rows(block_rows).await.unwrap(); + self.row_revs = self.get_row_revs().await; + self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); + }, + RowScript::AssertCell { + row_id, + field_id, + field_type, + expected, + } => { + let id = CellIdParams { + database_id: self.view_id.clone(), + field_id, + row_id, + }; + self.compare_cell_content(id, field_type, expected).await; + }, + RowScript::AssertRow { expected_row } => { + let row = &*self + .row_revs + .iter() + .find(|row| row.id == expected_row.id) + .cloned() + .unwrap(); + assert_eq!(&expected_row, row); + }, + RowScript::AssertRowCount(expected_row_count) => { + assert_eq!(expected_row_count, self.row_revs.len()); + }, + RowScript::CreateBlock { block } => { + self.editor.create_block(block).await.unwrap(); + self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); + }, + RowScript::UpdateBlock { changeset: change } => { + self.editor.update_block(change).await.unwrap(); + }, + RowScript::AssertBlockCount(count) => { + assert_eq!( + self.editor.get_block_meta_revs().await.unwrap().len(), + count + ); + }, + RowScript::AssertBlock { + block_index, + row_count, + start_row_index, + } => { + assert_eq!(self.block_meta_revs[block_index].row_count, row_count); + assert_eq!( + self.block_meta_revs[block_index].start_row_index, + start_row_index + ); + }, + RowScript::AssertBlockEqual { block_index, block } => { + let blocks = self.editor.get_block_meta_revs().await.unwrap(); + let compared_block = blocks[block_index].clone(); + assert_eq!(compared_block, Arc::new(block)); + }, } + } - pub fn row_builder(&self) -> GridRowTestBuilder { - GridRowTestBuilder::new(self.block_id(), &self.field_revs) - } - - pub async fn run_script(&mut self, script: RowScript) { - match script { - RowScript::CreateEmptyRow => { - let params = CreateRowParams { - database_id: self.editor.database_id.clone(), - start_row_id: None, - group_id: None, - layout: LayoutTypePB::Grid, - }; - let row_order = self.editor.create_row(params).await.unwrap(); - self.row_by_row_id.insert(row_order.row_id().to_owned(), row_order); - self.row_revs = self.get_row_revs().await; - self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); - } - RowScript::CreateRow { row_rev } => { - let row_orders = self.editor.insert_rows(vec![row_rev]).await.unwrap(); - for row_order in row_orders { - self.row_by_row_id.insert(row_order.row_id().to_owned(), row_order); - } - self.row_revs = self.get_row_revs().await; - self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); - } - RowScript::UpdateRow { changeset: change } => self.editor.update_row(change).await.unwrap(), - RowScript::DeleteRows { row_ids } => { - let row_pbs = row_ids - .into_iter() - .map(|row_id| self.row_by_row_id.get(&row_id).unwrap().clone()) - .collect::>(); - - let block_rows = block_from_row_pbs(row_pbs); - self.editor.delete_rows(block_rows).await.unwrap(); - self.row_revs = self.get_row_revs().await; - self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); - } - RowScript::AssertCell { - row_id, - field_id, - field_type, - expected, - } => { - let id = CellIdParams { - database_id: self.view_id.clone(), - field_id, - row_id, - }; - self.compare_cell_content(id, field_type, expected).await; - } - RowScript::AssertRow { expected_row } => { - let row = &*self - .row_revs - .iter() - .find(|row| row.id == expected_row.id) - .cloned() - .unwrap(); - assert_eq!(&expected_row, row); - } - RowScript::AssertRowCount(expected_row_count) => { - assert_eq!(expected_row_count, self.row_revs.len()); - } - RowScript::CreateBlock { block } => { - self.editor.create_block(block).await.unwrap(); - self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); - } - RowScript::UpdateBlock { changeset: change } => { - self.editor.update_block(change).await.unwrap(); - } - RowScript::AssertBlockCount(count) => { - assert_eq!(self.editor.get_block_meta_revs().await.unwrap().len(), count); - } - RowScript::AssertBlock { - block_index, - row_count, - start_row_index, - } => { - assert_eq!(self.block_meta_revs[block_index].row_count, row_count); - assert_eq!(self.block_meta_revs[block_index].start_row_index, start_row_index); - } - RowScript::AssertBlockEqual { block_index, block } => { - let blocks = self.editor.get_block_meta_revs().await.unwrap(); - let compared_block = blocks[block_index].clone(); - assert_eq!(compared_block, Arc::new(block)); - } - } - } - - async fn compare_cell_content(&self, cell_id: CellIdParams, field_type: FieldType, expected: String) { - match field_type { - FieldType::RichText => { - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .parser::() - .unwrap(); - - assert_eq!(cell_data.as_ref(), &expected); - } - FieldType::Number => { - let field_rev = self.editor.get_field_rev(&cell_id.field_id).await.unwrap(); - let number_type_option = field_rev - .get_type_option::(FieldType::Number.into()) - .unwrap(); - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .custom_parser(NumberCellCustomDataParser(number_type_option.format)) - .unwrap(); - assert_eq!(cell_data.to_string(), expected); - } - FieldType::DateTime => { - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .parser::() - .unwrap(); - - assert_eq!(cell_data.date, expected); - } - FieldType::SingleSelect => { - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .parser::() - .unwrap(); - let select_option = cell_data.select_options.first().unwrap(); - assert_eq!(select_option.name, expected); - } - FieldType::MultiSelect => { - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .parser::() - .unwrap(); - - let s = cell_data - .select_options - .into_iter() - .map(|option| option.name) - .collect::>() - .join(SELECTION_IDS_SEPARATOR); - - assert_eq!(s, expected); - } - - FieldType::Checklist => { - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .parser::() - .unwrap(); - - let s = cell_data - .select_options - .into_iter() - .map(|option| option.name) - .collect::>() - .join(SELECTION_IDS_SEPARATOR); - - assert_eq!(s, expected); - } - FieldType::Checkbox => { - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .parser::() - .unwrap(); - assert_eq!(cell_data.to_string(), expected); - } - FieldType::URL => { - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .parser::() - .unwrap(); - - assert_eq!(cell_data.content, expected); - // assert_eq!(cell_data.url, expected); - } - } + async fn compare_cell_content( + &self, + cell_id: CellIdParams, + field_type: FieldType, + expected: String, + ) { + match field_type { + FieldType::RichText => { + let cell_data = self + .editor + .get_cell_protobuf(&cell_id) + .await + .unwrap() + .parser::() + .unwrap(); + + assert_eq!(cell_data.as_ref(), &expected); + }, + FieldType::Number => { + let field_rev = self.editor.get_field_rev(&cell_id.field_id).await.unwrap(); + let number_type_option = field_rev + .get_type_option::(FieldType::Number.into()) + .unwrap(); + let cell_data = self + .editor + .get_cell_protobuf(&cell_id) + .await + .unwrap() + .custom_parser(NumberCellCustomDataParser(number_type_option.format)) + .unwrap(); + assert_eq!(cell_data.to_string(), expected); + }, + FieldType::DateTime => { + let cell_data = self + .editor + .get_cell_protobuf(&cell_id) + .await + .unwrap() + .parser::() + .unwrap(); + + assert_eq!(cell_data.date, expected); + }, + FieldType::SingleSelect => { + let cell_data = self + .editor + .get_cell_protobuf(&cell_id) + .await + .unwrap() + .parser::() + .unwrap(); + let select_option = cell_data.select_options.first().unwrap(); + assert_eq!(select_option.name, expected); + }, + FieldType::MultiSelect => { + let cell_data = self + .editor + .get_cell_protobuf(&cell_id) + .await + .unwrap() + .parser::() + .unwrap(); + + let s = cell_data + .select_options + .into_iter() + .map(|option| option.name) + .collect::>() + .join(SELECTION_IDS_SEPARATOR); + + assert_eq!(s, expected); + }, + + FieldType::Checklist => { + let cell_data = self + .editor + .get_cell_protobuf(&cell_id) + .await + .unwrap() + .parser::() + .unwrap(); + + let s = cell_data + .select_options + .into_iter() + .map(|option| option.name) + .collect::>() + .join(SELECTION_IDS_SEPARATOR); + + assert_eq!(s, expected); + }, + FieldType::Checkbox => { + let cell_data = self + .editor + .get_cell_protobuf(&cell_id) + .await + .unwrap() + .parser::() + .unwrap(); + assert_eq!(cell_data.to_string(), expected); + }, + FieldType::URL => { + let cell_data = self + .editor + .get_cell_protobuf(&cell_id) + .await + .unwrap() + .parser::() + .unwrap(); + + assert_eq!(cell_data.content, expected); + // assert_eq!(cell_data.url, expected); + }, } + } } fn block_from_row_pbs(row_orders: Vec) -> Vec { - let mut map: HashMap = HashMap::new(); - row_orders.into_iter().for_each(|row_pb| { - let block_id = row_pb.block_id().to_owned(); - let cloned_block_id = block_id.clone(); - map.entry(block_id) - .or_insert_with(|| DatabaseBlockRow::new(cloned_block_id, vec![])) - .row_ids - .push(row_pb.id); - }); - map.into_values().collect::>() + let mut map: HashMap = HashMap::new(); + row_orders.into_iter().for_each(|row_pb| { + let block_id = row_pb.block_id().to_owned(); + let cloned_block_id = block_id.clone(); + map + .entry(block_id) + .or_insert_with(|| DatabaseBlockRow::new(cloned_block_id, vec![])) + .row_ids + .push(row_pb.id); + }); + map.into_values().collect::>() } impl std::ops::Deref for DatabaseRowTest { - type Target = DatabaseEditorTest; + type Target = DatabaseEditorTest; - fn deref(&self) -> &Self::Target { - &self.inner - } + fn deref(&self) -> &Self::Target { + &self.inner + } } impl std::ops::DerefMut for DatabaseRowTest { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } pub struct CreateRowScriptBuilder<'a> { - builder: GridRowTestBuilder<'a>, - data_by_field_type: HashMap, - output_by_field_type: HashMap, + builder: GridRowTestBuilder<'a>, + data_by_field_type: HashMap, + output_by_field_type: HashMap, } impl<'a> CreateRowScriptBuilder<'a> { - pub fn new(test: &'a DatabaseRowTest) -> Self { - Self { - builder: test.row_builder(), - data_by_field_type: HashMap::new(), - output_by_field_type: HashMap::new(), - } + pub fn new(test: &'a DatabaseRowTest) -> Self { + Self { + builder: test.row_builder(), + data_by_field_type: HashMap::new(), + output_by_field_type: HashMap::new(), } + } - pub fn insert(&mut self, field_type: FieldType, input: &str, expected: &str) { - self.data_by_field_type.insert( + pub fn insert(&mut self, field_type: FieldType, input: &str, expected: &str) { + self.data_by_field_type.insert( + field_type, + CellTestData { + input: input.to_string(), + expected: expected.to_owned(), + }, + ); + } + + pub fn insert_single_select_cell(&mut self, f: F, expected: &str) + where + F: Fn(Vec) -> SelectOptionPB, + { + let field_id = self.builder.insert_single_select_cell(f); + self.output_by_field_type.insert( + FieldType::SingleSelect, + CellTestOutput { + field_id, + expected: expected.to_owned(), + }, + ); + } + + pub fn insert_multi_select_cell(&mut self, f: F, expected: &str) + where + F: Fn(Vec) -> Vec, + { + let field_id = self.builder.insert_multi_select_cell(f); + self.output_by_field_type.insert( + FieldType::MultiSelect, + CellTestOutput { + field_id, + expected: expected.to_owned(), + }, + ); + } + + pub fn build(mut self) -> Vec { + let mut scripts = vec![]; + let output_by_field_type = &mut self.output_by_field_type; + + for field_type in FieldType::iter() { + let field_type: FieldType = field_type; + if let Some(data) = self.data_by_field_type.get(&field_type) { + let field_id = match field_type { + FieldType::RichText => self.builder.insert_text_cell(&data.input), + FieldType::Number => self.builder.insert_number_cell(&data.input), + FieldType::DateTime => self.builder.insert_date_cell(&data.input), + FieldType::Checkbox => self.builder.insert_checkbox_cell(&data.input), + FieldType::URL => self.builder.insert_url_cell(&data.input), + _ => "".to_owned(), + }; + + if !field_id.is_empty() { + output_by_field_type.insert( field_type, - CellTestData { - input: input.to_string(), - expected: expected.to_owned(), - }, - ); - } - - pub fn insert_single_select_cell(&mut self, f: F, expected: &str) - where - F: Fn(Vec) -> SelectOptionPB, - { - let field_id = self.builder.insert_single_select_cell(f); - self.output_by_field_type.insert( - FieldType::SingleSelect, CellTestOutput { - field_id, - expected: expected.to_owned(), + field_id, + expected: data.expected.clone(), }, - ); - } - - pub fn insert_multi_select_cell(&mut self, f: F, expected: &str) - where - F: Fn(Vec) -> Vec, - { - let field_id = self.builder.insert_multi_select_cell(f); - self.output_by_field_type.insert( - FieldType::MultiSelect, - CellTestOutput { - field_id, - expected: expected.to_owned(), - }, - ); - } - - pub fn build(mut self) -> Vec { - let mut scripts = vec![]; - let output_by_field_type = &mut self.output_by_field_type; - - for field_type in FieldType::iter() { - let field_type: FieldType = field_type; - if let Some(data) = self.data_by_field_type.get(&field_type) { - let field_id = match field_type { - FieldType::RichText => self.builder.insert_text_cell(&data.input), - FieldType::Number => self.builder.insert_number_cell(&data.input), - FieldType::DateTime => self.builder.insert_date_cell(&data.input), - FieldType::Checkbox => self.builder.insert_checkbox_cell(&data.input), - FieldType::URL => self.builder.insert_url_cell(&data.input), - _ => "".to_owned(), - }; - - if !field_id.is_empty() { - output_by_field_type.insert( - field_type, - CellTestOutput { - field_id, - expected: data.expected.clone(), - }, - ); - } - } + ); } - - let row_rev = self.builder.build(); - let row_id = row_rev.id.clone(); - scripts.push(CreateRow { row_rev }); - - for field_type in FieldType::iter() { - if let Some(data) = output_by_field_type.get(&field_type) { - let script = AssertCell { - row_id: row_id.clone(), - field_id: data.field_id.clone(), - field_type, - expected: data.expected.clone(), - }; - scripts.push(script); - } - } - scripts + } } + + let row_rev = self.builder.build(); + let row_id = row_rev.id.clone(); + scripts.push(CreateRow { row_rev }); + + for field_type in FieldType::iter() { + if let Some(data) = output_by_field_type.get(&field_type) { + let script = AssertCell { + row_id: row_id.clone(), + field_id: data.field_id.clone(), + field_type, + expected: data.expected.clone(), + }; + scripts.push(script); + } + } + scripts + } } pub struct CellTestData { - pub input: String, - pub expected: String, + pub input: String, + pub expected: String, } struct CellTestOutput { - field_id: String, - expected: String, + field_id: String, + expected: String, } diff --git a/frontend/rust-lib/flowy-database/tests/grid/block_test/util.rs b/frontend/rust-lib/flowy-database/tests/grid/block_test/util.rs index becb775a81..638ef06347 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/block_test/util.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/block_test/util.rs @@ -1,6 +1,7 @@ use flowy_database::entities::FieldType; use flowy_database::services::field::{ - ChecklistTypeOptionPB, DateCellChangeset, MultiSelectTypeOptionPB, SelectOptionPB, SingleSelectTypeOptionPB, + ChecklistTypeOptionPB, DateCellChangeset, MultiSelectTypeOptionPB, SelectOptionPB, + SingleSelectTypeOptionPB, }; use flowy_database::services::row::RowRevisionBuilder; use grid_model::{FieldRevision, RowRevision}; @@ -9,126 +10,143 @@ use std::sync::Arc; use strum::EnumCount; pub struct GridRowTestBuilder<'a> { - field_revs: &'a [Arc], - inner_builder: RowRevisionBuilder<'a>, + field_revs: &'a [Arc], + inner_builder: RowRevisionBuilder<'a>, } impl<'a> GridRowTestBuilder<'a> { - pub fn new(block_id: &str, field_revs: &'a [Arc]) -> Self { - assert_eq!(field_revs.len(), FieldType::COUNT); - let inner_builder = RowRevisionBuilder::new(block_id, field_revs); - Self { - field_revs, - inner_builder, - } + pub fn new(block_id: &str, field_revs: &'a [Arc]) -> Self { + assert_eq!(field_revs.len(), FieldType::COUNT); + let inner_builder = RowRevisionBuilder::new(block_id, field_revs); + Self { + field_revs, + inner_builder, } + } - pub fn insert_text_cell(&mut self, data: &str) -> String { - let text_field = self.field_rev_with_type(&FieldType::RichText); - self.inner_builder.insert_text_cell(&text_field.id, data.to_string()); + pub fn insert_text_cell(&mut self, data: &str) -> String { + let text_field = self.field_rev_with_type(&FieldType::RichText); + self + .inner_builder + .insert_text_cell(&text_field.id, data.to_string()); - text_field.id.clone() - } + text_field.id.clone() + } - pub fn insert_number_cell(&mut self, data: &str) -> String { - let number_field = self.field_rev_with_type(&FieldType::Number); - self.inner_builder.insert_text_cell(&number_field.id, data.to_string()); - number_field.id.clone() - } + pub fn insert_number_cell(&mut self, data: &str) -> String { + let number_field = self.field_rev_with_type(&FieldType::Number); + self + .inner_builder + .insert_text_cell(&number_field.id, data.to_string()); + number_field.id.clone() + } - pub fn insert_date_cell(&mut self, data: &str) -> String { - let value = serde_json::to_string(&DateCellChangeset { - date: Some(data.to_string()), - time: None, - is_utc: true, - }) - .unwrap(); - let date_field = self.field_rev_with_type(&FieldType::DateTime); - self.inner_builder.insert_text_cell(&date_field.id, value); - date_field.id.clone() - } + pub fn insert_date_cell(&mut self, data: &str) -> String { + let value = serde_json::to_string(&DateCellChangeset { + date: Some(data.to_string()), + time: None, + is_utc: true, + }) + .unwrap(); + let date_field = self.field_rev_with_type(&FieldType::DateTime); + self.inner_builder.insert_text_cell(&date_field.id, value); + date_field.id.clone() + } - pub fn insert_checkbox_cell(&mut self, data: &str) -> String { - let checkbox_field = self.field_rev_with_type(&FieldType::Checkbox); - self.inner_builder - .insert_text_cell(&checkbox_field.id, data.to_string()); + pub fn insert_checkbox_cell(&mut self, data: &str) -> String { + let checkbox_field = self.field_rev_with_type(&FieldType::Checkbox); + self + .inner_builder + .insert_text_cell(&checkbox_field.id, data.to_string()); - checkbox_field.id.clone() - } + checkbox_field.id.clone() + } - pub fn insert_url_cell(&mut self, content: &str) -> String { - let url_field = self.field_rev_with_type(&FieldType::URL); - self.inner_builder.insert_url_cell(&url_field.id, content.to_string()); - url_field.id.clone() - } + pub fn insert_url_cell(&mut self, content: &str) -> String { + let url_field = self.field_rev_with_type(&FieldType::URL); + self + .inner_builder + .insert_url_cell(&url_field.id, content.to_string()); + url_field.id.clone() + } - pub fn insert_single_select_cell(&mut self, f: F) -> String - where - F: Fn(Vec) -> SelectOptionPB, - { - let single_select_field = self.field_rev_with_type(&FieldType::SingleSelect); - let type_option = SingleSelectTypeOptionPB::from(&single_select_field); - let option = f(type_option.options); - self.inner_builder - .insert_select_option_cell(&single_select_field.id, vec![option.id]); + pub fn insert_single_select_cell(&mut self, f: F) -> String + where + F: Fn(Vec) -> SelectOptionPB, + { + let single_select_field = self.field_rev_with_type(&FieldType::SingleSelect); + let type_option = SingleSelectTypeOptionPB::from(&single_select_field); + let option = f(type_option.options); + self + .inner_builder + .insert_select_option_cell(&single_select_field.id, vec![option.id]); - single_select_field.id.clone() - } + single_select_field.id.clone() + } - pub fn insert_multi_select_cell(&mut self, f: F) -> String - where - F: Fn(Vec) -> Vec, - { - let multi_select_field = self.field_rev_with_type(&FieldType::MultiSelect); - let type_option = MultiSelectTypeOptionPB::from(&multi_select_field); - let options = f(type_option.options); - let ops_ids = options.iter().map(|option| option.id.clone()).collect::>(); - self.inner_builder - .insert_select_option_cell(&multi_select_field.id, ops_ids); + pub fn insert_multi_select_cell(&mut self, f: F) -> String + where + F: Fn(Vec) -> Vec, + { + let multi_select_field = self.field_rev_with_type(&FieldType::MultiSelect); + let type_option = MultiSelectTypeOptionPB::from(&multi_select_field); + let options = f(type_option.options); + let ops_ids = options + .iter() + .map(|option| option.id.clone()) + .collect::>(); + self + .inner_builder + .insert_select_option_cell(&multi_select_field.id, ops_ids); - multi_select_field.id.clone() - } + multi_select_field.id.clone() + } - pub fn insert_checklist_cell(&mut self, f: F) -> String - where - F: Fn(Vec) -> Vec, - { - let checklist_field = self.field_rev_with_type(&FieldType::Checklist); - let type_option = ChecklistTypeOptionPB::from(&checklist_field); - let options = f(type_option.options); - let ops_ids = options.iter().map(|option| option.id.clone()).collect::>(); - self.inner_builder - .insert_select_option_cell(&checklist_field.id, ops_ids); + pub fn insert_checklist_cell(&mut self, f: F) -> String + where + F: Fn(Vec) -> Vec, + { + let checklist_field = self.field_rev_with_type(&FieldType::Checklist); + let type_option = ChecklistTypeOptionPB::from(&checklist_field); + let options = f(type_option.options); + let ops_ids = options + .iter() + .map(|option| option.id.clone()) + .collect::>(); + self + .inner_builder + .insert_select_option_cell(&checklist_field.id, ops_ids); - checklist_field.id.clone() - } - pub fn field_rev_with_type(&self, field_type: &FieldType) -> FieldRevision { - self.field_revs - .iter() - .find(|field_rev| { - let t_field_type: FieldType = field_rev.ty.into(); - &t_field_type == field_type - }) - .unwrap() - .as_ref() - .clone() - } + checklist_field.id.clone() + } + pub fn field_rev_with_type(&self, field_type: &FieldType) -> FieldRevision { + self + .field_revs + .iter() + .find(|field_rev| { + let t_field_type: FieldType = field_rev.ty.into(); + &t_field_type == field_type + }) + .unwrap() + .as_ref() + .clone() + } - pub fn build(self) -> RowRevision { - self.inner_builder.build() - } + pub fn build(self) -> RowRevision { + self.inner_builder.build() + } } impl<'a> std::ops::Deref for GridRowTestBuilder<'a> { - type Target = RowRevisionBuilder<'a>; + type Target = RowRevisionBuilder<'a>; - fn deref(&self) -> &Self::Target { - &self.inner_builder - } + fn deref(&self) -> &Self::Target { + &self.inner_builder + } } impl<'a> std::ops::DerefMut for GridRowTestBuilder<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner_builder - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner_builder + } } diff --git a/frontend/rust-lib/flowy-database/tests/grid/cell_test/script.rs b/frontend/rust-lib/flowy-database/tests/grid/cell_test/script.rs index debcf1b1b2..1a698c314a 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/cell_test/script.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/cell_test/script.rs @@ -2,62 +2,69 @@ use crate::grid::database_editor::DatabaseEditorTest; use flowy_database::entities::CellChangesetPB; pub enum CellScript { - UpdateCell { changeset: CellChangesetPB, is_err: bool }, + UpdateCell { + changeset: CellChangesetPB, + is_err: bool, + }, } pub struct DatabaseCellTest { - inner: DatabaseEditorTest, + inner: DatabaseEditorTest, } impl DatabaseCellTest { - pub async fn new() -> Self { - let inner = DatabaseEditorTest::new_table().await; - Self { inner } - } + pub async fn new() -> Self { + let inner = DatabaseEditorTest::new_table().await; + Self { inner } + } - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - pub async fn run_script(&mut self, script: CellScript) { - // let grid_manager = self.sdk.grid_manager.clone(); - // let pool = self.sdk.user_session.db_pool().unwrap(); - // let rev_manager = self.editor.rev_manager(); - // let _cache = rev_manager.revision_cache().await; - - match script { - CellScript::UpdateCell { changeset, is_err } => { - let result = self - .editor - .update_cell_with_changeset(&changeset.row_id, &changeset.field_id, changeset.type_cell_data) - .await; - if is_err { - assert!(result.is_err()) - } else { - result.unwrap(); - } - } // CellScript::AssertGridRevisionPad => { - // sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await; - // let mut grid_rev_manager = grid_manager.make_grid_rev_manager(&self.grid_id, pool.clone()).unwrap(); - // let grid_pad = grid_rev_manager.load::(None).await.unwrap(); - // println!("{}", grid_pad.delta_str()); - // } + pub async fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + pub async fn run_script(&mut self, script: CellScript) { + // let grid_manager = self.sdk.grid_manager.clone(); + // let pool = self.sdk.user_session.db_pool().unwrap(); + // let rev_manager = self.editor.rev_manager(); + // let _cache = rev_manager.revision_cache().await; + + match script { + CellScript::UpdateCell { changeset, is_err } => { + let result = self + .editor + .update_cell_with_changeset( + &changeset.row_id, + &changeset.field_id, + changeset.type_cell_data, + ) + .await; + if is_err { + assert!(result.is_err()) + } else { + result.unwrap(); } + }, // CellScript::AssertGridRevisionPad => { + // sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await; + // let mut grid_rev_manager = grid_manager.make_grid_rev_manager(&self.grid_id, pool.clone()).unwrap(); + // let grid_pad = grid_rev_manager.load::(None).await.unwrap(); + // println!("{}", grid_pad.delta_str()); + // } } + } } impl std::ops::Deref for DatabaseCellTest { - type Target = DatabaseEditorTest; + type Target = DatabaseEditorTest; - fn deref(&self) -> &Self::Target { - &self.inner - } + fn deref(&self) -> &Self::Target { + &self.inner + } } impl std::ops::DerefMut for DatabaseCellTest { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } diff --git a/frontend/rust-lib/flowy-database/tests/grid/cell_test/test.rs b/frontend/rust-lib/flowy-database/tests/grid/cell_test/test.rs index c00fb9e1f6..8ec7ea9df5 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/cell_test/test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/cell_test/test.rs @@ -4,98 +4,100 @@ use crate::grid::field_test::util::make_date_cell_string; use flowy_database::entities::{CellChangesetPB, FieldType}; use flowy_database::services::cell::ToCellChangesetString; use flowy_database::services::field::selection_type_option::SelectOptionCellChangeset; -use flowy_database::services::field::{ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB}; +use flowy_database::services::field::{ + ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB, +}; #[tokio::test] async fn grid_cell_update() { - let mut test = DatabaseCellTest::new().await; - let field_revs = &test.field_revs; - let row_revs = &test.row_revs; - let grid_blocks = &test.block_meta_revs; + let mut test = DatabaseCellTest::new().await; + let field_revs = &test.field_revs; + let row_revs = &test.row_revs; + let grid_blocks = &test.block_meta_revs; - // For the moment, We only have one block to store rows - let block_id = &grid_blocks.first().unwrap().block_id; + // For the moment, We only have one block to store rows + let block_id = &grid_blocks.first().unwrap().block_id; - let mut scripts = vec![]; - for (_, row_rev) in row_revs.iter().enumerate() { - for field_rev in field_revs { - let field_type: FieldType = field_rev.ty.into(); - let data = match field_type { - FieldType::RichText => "".to_string(), - FieldType::Number => "123".to_string(), - FieldType::DateTime => make_date_cell_string("123"), - FieldType::SingleSelect => { - let type_option = SingleSelectTypeOptionPB::from(field_rev); - SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id) - .to_cell_changeset_str() - } - FieldType::MultiSelect => { - let type_option = MultiSelectTypeOptionPB::from(field_rev); - SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id) - .to_cell_changeset_str() - } - FieldType::Checklist => { - let type_option = ChecklistTypeOptionPB::from(field_rev); - SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id) - .to_cell_changeset_str() - } - FieldType::Checkbox => "1".to_string(), - FieldType::URL => "1".to_string(), - }; + let mut scripts = vec![]; + for (_, row_rev) in row_revs.iter().enumerate() { + for field_rev in field_revs { + let field_type: FieldType = field_rev.ty.into(); + let data = match field_type { + FieldType::RichText => "".to_string(), + FieldType::Number => "123".to_string(), + FieldType::DateTime => make_date_cell_string("123"), + FieldType::SingleSelect => { + let type_option = SingleSelectTypeOptionPB::from(field_rev); + SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id) + .to_cell_changeset_str() + }, + FieldType::MultiSelect => { + let type_option = MultiSelectTypeOptionPB::from(field_rev); + SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id) + .to_cell_changeset_str() + }, + FieldType::Checklist => { + let type_option = ChecklistTypeOptionPB::from(field_rev); + SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id) + .to_cell_changeset_str() + }, + FieldType::Checkbox => "1".to_string(), + FieldType::URL => "1".to_string(), + }; - scripts.push(UpdateCell { - changeset: CellChangesetPB { - database_id: block_id.to_string(), - row_id: row_rev.id.clone(), - field_id: field_rev.id.clone(), - type_cell_data: data, - }, - is_err: false, - }); - } + scripts.push(UpdateCell { + changeset: CellChangesetPB { + database_id: block_id.to_string(), + row_id: row_rev.id.clone(), + field_id: field_rev.id.clone(), + type_cell_data: data, + }, + is_err: false, + }); } + } - test.run_scripts(scripts).await; + test.run_scripts(scripts).await; } #[tokio::test] async fn text_cell_date_test() { - let test = DatabaseCellTest::new().await; - let text_field = test.get_first_field_rev(FieldType::RichText); - let cells = test - .editor - .get_cells_for_field(&test.view_id, &text_field.id) - .await - .unwrap(); + let test = DatabaseCellTest::new().await; + let text_field = test.get_first_field_rev(FieldType::RichText); + let cells = test + .editor + .get_cells_for_field(&test.view_id, &text_field.id) + .await + .unwrap(); - for (i, cell) in cells.into_iter().enumerate() { - let text = cell.into_text_field_cell_data().unwrap(); - match i { - 0 => assert_eq!(text.as_str(), "A"), - 1 => assert_eq!(text.as_str(), ""), - 2 => assert_eq!(text.as_str(), "C"), - 3 => assert_eq!(text.as_str(), "DA"), - 4 => assert_eq!(text.as_str(), "AE"), - 5 => assert_eq!(text.as_str(), "AE"), - _ => {} - } + for (i, cell) in cells.into_iter().enumerate() { + let text = cell.into_text_field_cell_data().unwrap(); + match i { + 0 => assert_eq!(text.as_str(), "A"), + 1 => assert_eq!(text.as_str(), ""), + 2 => assert_eq!(text.as_str(), "C"), + 3 => assert_eq!(text.as_str(), "DA"), + 4 => assert_eq!(text.as_str(), "AE"), + 5 => assert_eq!(text.as_str(), "AE"), + _ => {}, } + } } #[tokio::test] async fn url_cell_date_test() { - let test = DatabaseCellTest::new().await; - let url_field = test.get_first_field_rev(FieldType::URL); - let cells = test - .editor - .get_cells_for_field(&test.view_id, &url_field.id) - .await - .unwrap(); + let test = DatabaseCellTest::new().await; + let url_field = test.get_first_field_rev(FieldType::URL); + let cells = test + .editor + .get_cells_for_field(&test.view_id, &url_field.id) + .await + .unwrap(); - for (i, cell) in cells.into_iter().enumerate() { - let url_cell_data = cell.into_url_field_cell_data().unwrap(); - if i == 0 { - assert_eq!(url_cell_data.url.as_str(), "https://www.appflowy.io/"); - } + for (i, cell) in cells.into_iter().enumerate() { + let url_cell_data = cell.into_url_field_cell_data().unwrap(); + if i == 0 { + assert_eq!(url_cell_data.url.as_str(), "https://www.appflowy.io/"); } + } } diff --git a/frontend/rust-lib/flowy-database/tests/grid/database_editor.rs b/frontend/rust-lib/flowy-database/tests/grid/database_editor.rs index b4e185291b..0f90961018 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/database_editor.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/database_editor.rs @@ -13,181 +13,193 @@ use std::sync::Arc; use strum::EnumCount; pub struct DatabaseEditorTest { - pub sdk: FlowySDKTest, - pub view_id: String, - pub editor: Arc, - pub field_revs: Vec>, - pub block_meta_revs: Vec>, - pub row_revs: Vec>, - pub field_count: usize, - pub row_by_row_id: HashMap, + pub sdk: FlowySDKTest, + pub view_id: String, + pub editor: Arc, + pub field_revs: Vec>, + pub block_meta_revs: Vec>, + pub row_revs: Vec>, + pub field_count: usize, + pub row_by_row_id: HashMap, } impl DatabaseEditorTest { - pub async fn new_table() -> Self { - Self::new(LayoutTypePB::Grid).await + pub async fn new_table() -> Self { + Self::new(LayoutTypePB::Grid).await + } + + pub async fn new_board() -> Self { + Self::new(LayoutTypePB::Board).await + } + + pub async fn new(layout: LayoutTypePB) -> Self { + let sdk = FlowySDKTest::default(); + let _ = sdk.init_user().await; + let test = match layout { + LayoutTypePB::Grid => { + let build_context = make_test_grid(); + let view_data: Bytes = build_context.into(); + ViewTest::new_grid_view(&sdk, view_data.to_vec()).await + }, + LayoutTypePB::Board => { + let build_context = make_test_board(); + let view_data: Bytes = build_context.into(); + ViewTest::new_board_view(&sdk, view_data.to_vec()).await + }, + LayoutTypePB::Calendar => { + let build_context = make_test_calendar(); + let view_data: Bytes = build_context.into(); + ViewTest::new_calendar_view(&sdk, view_data.to_vec()).await + }, + }; + + let editor = sdk.grid_manager.open_database(&test.view.id).await.unwrap(); + let field_revs = editor.get_field_revs(None).await.unwrap(); + let block_meta_revs = editor.get_block_meta_revs().await.unwrap(); + let row_pbs = editor.get_all_row_revs(&test.view.id).await.unwrap(); + assert_eq!(block_meta_revs.len(), 1); + + // It seems like you should add the field in the make_test_grid() function. + // Because we assert the initialize count of the fields is equal to FieldType::COUNT. + assert_eq!(field_revs.len(), FieldType::COUNT); + + let grid_id = test.view.id; + Self { + sdk, + view_id: grid_id, + editor, + field_revs, + block_meta_revs, + row_revs: row_pbs, + field_count: FieldType::COUNT, + row_by_row_id: HashMap::default(), } + } - pub async fn new_board() -> Self { - Self::new(LayoutTypePB::Board).await - } + pub async fn get_row_revs(&self) -> Vec> { + self.editor.get_all_row_revs(&self.view_id).await.unwrap() + } - pub async fn new(layout: LayoutTypePB) -> Self { - let sdk = FlowySDKTest::default(); - let _ = sdk.init_user().await; - let test = match layout { - LayoutTypePB::Grid => { - let build_context = make_test_grid(); - let view_data: Bytes = build_context.into(); - ViewTest::new_grid_view(&sdk, view_data.to_vec()).await - } - LayoutTypePB::Board => { - let build_context = make_test_board(); - let view_data: Bytes = build_context.into(); - ViewTest::new_board_view(&sdk, view_data.to_vec()).await - } - LayoutTypePB::Calendar => { - let build_context = make_test_calendar(); - let view_data: Bytes = build_context.into(); - ViewTest::new_calendar_view(&sdk, view_data.to_vec()).await - } - }; + pub async fn grid_filters(&self) -> Vec { + self.editor.get_all_filters().await.unwrap() + } - let editor = sdk.grid_manager.open_database(&test.view.id).await.unwrap(); - let field_revs = editor.get_field_revs(None).await.unwrap(); - let block_meta_revs = editor.get_block_meta_revs().await.unwrap(); - let row_pbs = editor.get_all_row_revs(&test.view.id).await.unwrap(); - assert_eq!(block_meta_revs.len(), 1); + pub fn get_field_rev(&self, field_id: &str, field_type: FieldType) -> &Arc { + self + .field_revs + .iter() + .filter(|field_rev| { + let t_field_type: FieldType = field_rev.ty.into(); + field_rev.id == field_id && t_field_type == field_type + }) + .collect::>() + .pop() + .unwrap() + } - // It seems like you should add the field in the make_test_grid() function. - // Because we assert the initialize count of the fields is equal to FieldType::COUNT. - assert_eq!(field_revs.len(), FieldType::COUNT); + /// returns the first `FieldRevision` in the build-in test grid. + /// Not support duplicate `FieldType` in test grid yet. + pub fn get_first_field_rev(&self, field_type: FieldType) -> &Arc { + self + .field_revs + .iter() + .filter(|field_rev| { + let t_field_type: FieldType = field_rev.ty.into(); + t_field_type == field_type + }) + .collect::>() + .pop() + .unwrap() + } - let grid_id = test.view.id; - Self { - sdk, - view_id: grid_id, - editor, - field_revs, - block_meta_revs, - row_revs: row_pbs, - field_count: FieldType::COUNT, - row_by_row_id: HashMap::default(), - } - } + pub fn get_multi_select_type_option(&self, field_id: &str) -> Vec { + let field_type = FieldType::MultiSelect; + let field_rev = self.get_field_rev(field_id, field_type.clone()); + let type_option = field_rev + .get_type_option::(field_type.into()) + .unwrap(); + type_option.options + } - pub async fn get_row_revs(&self) -> Vec> { - self.editor.get_all_row_revs(&self.view_id).await.unwrap() - } + pub fn get_single_select_type_option(&self, field_id: &str) -> SingleSelectTypeOptionPB { + let field_type = FieldType::SingleSelect; + let field_rev = self.get_field_rev(field_id, field_type.clone()); + field_rev + .get_type_option::(field_type.into()) + .unwrap() + } - pub async fn grid_filters(&self) -> Vec { - self.editor.get_all_filters().await.unwrap() - } + #[allow(dead_code)] + pub fn get_checklist_type_option(&self, field_id: &str) -> ChecklistTypeOptionPB { + let field_type = FieldType::Checklist; + let field_rev = self.get_field_rev(field_id, field_type.clone()); + field_rev + .get_type_option::(field_type.into()) + .unwrap() + } - pub fn get_field_rev(&self, field_id: &str, field_type: FieldType) -> &Arc { - self.field_revs - .iter() - .filter(|field_rev| { - let t_field_type: FieldType = field_rev.ty.into(); - field_rev.id == field_id && t_field_type == field_type - }) - .collect::>() - .pop() - .unwrap() - } + #[allow(dead_code)] + pub fn get_checkbox_type_option(&self, field_id: &str) -> CheckboxTypeOptionPB { + let field_type = FieldType::Checkbox; + let field_rev = self.get_field_rev(field_id, field_type.clone()); + field_rev + .get_type_option::(field_type.into()) + .unwrap() + } - /// returns the first `FieldRevision` in the build-in test grid. - /// Not support duplicate `FieldType` in test grid yet. - pub fn get_first_field_rev(&self, field_type: FieldType) -> &Arc { - self.field_revs - .iter() - .filter(|field_rev| { - let t_field_type: FieldType = field_rev.ty.into(); - t_field_type == field_type - }) - .collect::>() - .pop() - .unwrap() - } + pub fn block_id(&self) -> &str { + &self.block_meta_revs.last().unwrap().block_id + } - pub fn get_multi_select_type_option(&self, field_id: &str) -> Vec { - let field_type = FieldType::MultiSelect; - let field_rev = self.get_field_rev(field_id, field_type.clone()); - let type_option = field_rev - .get_type_option::(field_type.into()) - .unwrap(); - type_option.options - } + pub async fn update_cell( + &mut self, + field_id: &str, + row_id: String, + cell_changeset: T, + ) { + let field_rev = self + .field_revs + .iter() + .find(|field_rev| field_rev.id == field_id) + .unwrap(); - pub fn get_single_select_type_option(&self, field_id: &str) -> SingleSelectTypeOptionPB { - let field_type = FieldType::SingleSelect; - let field_rev = self.get_field_rev(field_id, field_type.clone()); - field_rev - .get_type_option::(field_type.into()) - .unwrap() - } + self + .editor + .update_cell_with_changeset(&row_id, &field_rev.id, cell_changeset) + .await + .unwrap(); + } - #[allow(dead_code)] - pub fn get_checklist_type_option(&self, field_id: &str) -> ChecklistTypeOptionPB { - let field_type = FieldType::Checklist; - let field_rev = self.get_field_rev(field_id, field_type.clone()); - field_rev - .get_type_option::(field_type.into()) - .unwrap() - } + pub(crate) async fn update_text_cell(&mut self, row_id: String, content: &str) { + let field_rev = self + .field_revs + .iter() + .find(|field_rev| { + let field_type: FieldType = field_rev.ty.into(); + field_type == FieldType::RichText + }) + .unwrap() + .clone(); - #[allow(dead_code)] - pub fn get_checkbox_type_option(&self, field_id: &str) -> CheckboxTypeOptionPB { - let field_type = FieldType::Checkbox; - let field_rev = self.get_field_rev(field_id, field_type.clone()); - field_rev - .get_type_option::(field_type.into()) - .unwrap() - } + self + .update_cell(&field_rev.id, row_id, content.to_string()) + .await; + } - pub fn block_id(&self) -> &str { - &self.block_meta_revs.last().unwrap().block_id - } + pub(crate) async fn update_single_select_cell(&mut self, row_id: String, option_id: &str) { + let field_rev = self + .field_revs + .iter() + .find(|field_rev| { + let field_type: FieldType = field_rev.ty.into(); + field_type == FieldType::SingleSelect + }) + .unwrap() + .clone(); - pub async fn update_cell(&mut self, field_id: &str, row_id: String, cell_changeset: T) { - let field_rev = self - .field_revs - .iter() - .find(|field_rev| field_rev.id == field_id) - .unwrap(); - - self.editor - .update_cell_with_changeset(&row_id, &field_rev.id, cell_changeset) - .await - .unwrap(); - } - - pub(crate) async fn update_text_cell(&mut self, row_id: String, content: &str) { - let field_rev = self - .field_revs - .iter() - .find(|field_rev| { - let field_type: FieldType = field_rev.ty.into(); - field_type == FieldType::RichText - }) - .unwrap() - .clone(); - - self.update_cell(&field_rev.id, row_id, content.to_string()).await; - } - - pub(crate) async fn update_single_select_cell(&mut self, row_id: String, option_id: &str) { - let field_rev = self - .field_revs - .iter() - .find(|field_rev| { - let field_type: FieldType = field_rev.ty.into(); - field_type == FieldType::SingleSelect - }) - .unwrap() - .clone(); - - let cell_changeset = SelectOptionCellChangeset::from_insert_option_id(option_id); - self.update_cell(&field_rev.id, row_id, cell_changeset).await; - } + let cell_changeset = SelectOptionCellChangeset::from_insert_option_id(option_id); + self + .update_cell(&field_rev.id, row_id, cell_changeset) + .await; + } } diff --git a/frontend/rust-lib/flowy-database/tests/grid/field_test/script.rs b/frontend/rust-lib/flowy-database/tests/grid/field_test/script.rs index 5115ab50f1..2a2e851528 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/field_test/script.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/field_test/script.rs @@ -4,156 +4,175 @@ use flowy_database::services::cell::{stringify_cell_data, TypeCellData}; use grid_model::FieldRevision; pub enum FieldScript { - CreateField { - params: CreateFieldParams, - }, - UpdateField { - changeset: FieldChangesetParams, - }, - DeleteField { - field_rev: FieldRevision, - }, - SwitchToField { - field_id: String, - new_field_type: FieldType, - }, - UpdateTypeOption { - field_id: String, - type_option: Vec, - }, - AssertFieldCount(usize), - AssertFieldFrozen { - field_index: usize, - frozen: bool, - }, - AssertFieldTypeOptionEqual { - field_index: usize, - expected_type_option_data: String, - }, - AssertCellContent { - field_id: String, - row_index: usize, - from_field_type: FieldType, - expected_content: String, - }, + CreateField { + params: CreateFieldParams, + }, + UpdateField { + changeset: FieldChangesetParams, + }, + DeleteField { + field_rev: FieldRevision, + }, + SwitchToField { + field_id: String, + new_field_type: FieldType, + }, + UpdateTypeOption { + field_id: String, + type_option: Vec, + }, + AssertFieldCount(usize), + AssertFieldFrozen { + field_index: usize, + frozen: bool, + }, + AssertFieldTypeOptionEqual { + field_index: usize, + expected_type_option_data: String, + }, + AssertCellContent { + field_id: String, + row_index: usize, + from_field_type: FieldType, + expected_content: String, + }, } pub struct DatabaseFieldTest { - inner: DatabaseEditorTest, + inner: DatabaseEditorTest, } impl DatabaseFieldTest { - pub async fn new() -> Self { - let editor_test = DatabaseEditorTest::new_table().await; - Self { inner: editor_test } - } + pub async fn new() -> Self { + let editor_test = DatabaseEditorTest::new_table().await; + Self { inner: editor_test } + } - pub fn view_id(&self) -> String { - self.view_id.clone() - } + pub fn view_id(&self) -> String { + self.view_id.clone() + } - pub fn field_count(&self) -> usize { - self.field_count - } + pub fn field_count(&self) -> usize { + self.field_count + } - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - pub async fn run_script(&mut self, script: FieldScript) { - match script { - FieldScript::CreateField { params } => { - self.field_count += 1; - self.editor - .create_new_field_rev_with_type_option(¶ms.field_type, params.type_option_data) - .await - .unwrap(); - self.field_revs = self.editor.get_field_revs(None).await.unwrap(); - assert_eq!(self.field_count, self.field_revs.len()); - } - FieldScript::UpdateField { changeset: change } => { - self.editor.update_field(change).await.unwrap(); - self.field_revs = self.editor.get_field_revs(None).await.unwrap(); - } - FieldScript::DeleteField { field_rev } => { - if self.editor.contain_field(&field_rev.id).await { - self.field_count -= 1; - } - - self.editor.delete_field(&field_rev.id).await.unwrap(); - self.field_revs = self.editor.get_field_revs(None).await.unwrap(); - assert_eq!(self.field_count, self.field_revs.len()); - } - FieldScript::SwitchToField { - field_id, - new_field_type, - } => { - // - self.editor - .switch_to_field_type(&field_id, &new_field_type) - .await - .unwrap(); - self.field_revs = self.editor.get_field_revs(None).await.unwrap(); - } - FieldScript::UpdateTypeOption { field_id, type_option } => { - // - self.editor - .update_field_type_option(&self.view_id, &field_id, type_option, None) - .await - .unwrap(); - self.field_revs = self.editor.get_field_revs(None).await.unwrap(); - } - FieldScript::AssertFieldCount(count) => { - assert_eq!(self.editor.get_field_revs(None).await.unwrap().len(), count); - } - FieldScript::AssertFieldFrozen { field_index, frozen } => { - let field_revs = self.editor.get_field_revs(None).await.unwrap(); - let field_rev = field_revs[field_index].as_ref(); - assert_eq!(field_rev.frozen, frozen); - } - FieldScript::AssertFieldTypeOptionEqual { - field_index, - expected_type_option_data, - } => { - let field_revs = self.editor.get_field_revs(None).await.unwrap(); - let field_rev = field_revs[field_index].as_ref(); - let type_option_data = field_rev.get_type_option_str(field_rev.ty).unwrap(); - assert_eq!(type_option_data, expected_type_option_data); - } - FieldScript::AssertCellContent { - field_id, - row_index, - from_field_type, - expected_content, - } => { - let field_rev = self.editor.get_field_rev(&field_id).await.unwrap(); - let field_type: FieldType = field_rev.ty.into(); - - let rows = self.editor.get_database(&self.view_id()).await.unwrap().rows; - let row = rows.get(row_index).unwrap(); - let row_rev = self.editor.get_row_rev(&row.id).await.unwrap().unwrap(); - - let cell_rev = row_rev.cells.get(&field_id).unwrap().clone(); - let type_cell_data: TypeCellData = cell_rev.try_into().unwrap(); - let content = stringify_cell_data(type_cell_data.cell_str, &from_field_type, &field_type, &field_rev); - assert_eq!(content, expected_content); - } + pub async fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + pub async fn run_script(&mut self, script: FieldScript) { + match script { + FieldScript::CreateField { params } => { + self.field_count += 1; + self + .editor + .create_new_field_rev_with_type_option(¶ms.field_type, params.type_option_data) + .await + .unwrap(); + self.field_revs = self.editor.get_field_revs(None).await.unwrap(); + assert_eq!(self.field_count, self.field_revs.len()); + }, + FieldScript::UpdateField { changeset: change } => { + self.editor.update_field(change).await.unwrap(); + self.field_revs = self.editor.get_field_revs(None).await.unwrap(); + }, + FieldScript::DeleteField { field_rev } => { + if self.editor.contain_field(&field_rev.id).await { + self.field_count -= 1; } + + self.editor.delete_field(&field_rev.id).await.unwrap(); + self.field_revs = self.editor.get_field_revs(None).await.unwrap(); + assert_eq!(self.field_count, self.field_revs.len()); + }, + FieldScript::SwitchToField { + field_id, + new_field_type, + } => { + // + self + .editor + .switch_to_field_type(&field_id, &new_field_type) + .await + .unwrap(); + self.field_revs = self.editor.get_field_revs(None).await.unwrap(); + }, + FieldScript::UpdateTypeOption { + field_id, + type_option, + } => { + // + self + .editor + .update_field_type_option(&self.view_id, &field_id, type_option, None) + .await + .unwrap(); + self.field_revs = self.editor.get_field_revs(None).await.unwrap(); + }, + FieldScript::AssertFieldCount(count) => { + assert_eq!(self.editor.get_field_revs(None).await.unwrap().len(), count); + }, + FieldScript::AssertFieldFrozen { + field_index, + frozen, + } => { + let field_revs = self.editor.get_field_revs(None).await.unwrap(); + let field_rev = field_revs[field_index].as_ref(); + assert_eq!(field_rev.frozen, frozen); + }, + FieldScript::AssertFieldTypeOptionEqual { + field_index, + expected_type_option_data, + } => { + let field_revs = self.editor.get_field_revs(None).await.unwrap(); + let field_rev = field_revs[field_index].as_ref(); + let type_option_data = field_rev.get_type_option_str(field_rev.ty).unwrap(); + assert_eq!(type_option_data, expected_type_option_data); + }, + FieldScript::AssertCellContent { + field_id, + row_index, + from_field_type, + expected_content, + } => { + let field_rev = self.editor.get_field_rev(&field_id).await.unwrap(); + let field_type: FieldType = field_rev.ty.into(); + + let rows = self + .editor + .get_database(&self.view_id()) + .await + .unwrap() + .rows; + let row = rows.get(row_index).unwrap(); + let row_rev = self.editor.get_row_rev(&row.id).await.unwrap().unwrap(); + + let cell_rev = row_rev.cells.get(&field_id).unwrap().clone(); + let type_cell_data: TypeCellData = cell_rev.try_into().unwrap(); + let content = stringify_cell_data( + type_cell_data.cell_str, + &from_field_type, + &field_type, + &field_rev, + ); + assert_eq!(content, expected_content); + }, } + } } impl std::ops::Deref for DatabaseFieldTest { - type Target = DatabaseEditorTest; + type Target = DatabaseEditorTest; - fn deref(&self) -> &Self::Target { - &self.inner - } + fn deref(&self) -> &Self::Target { + &self.inner + } } impl std::ops::DerefMut for DatabaseFieldTest { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } diff --git a/frontend/rust-lib/flowy-database/tests/grid/field_test/test.rs b/frontend/rust-lib/flowy-database/tests/grid/field_test/test.rs index d3d1109135..b8db2c335c 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/field_test/test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/field_test/test.rs @@ -8,193 +8,206 @@ use flowy_database::services::field::{gen_option_id, SingleSelectTypeOptionPB, C #[tokio::test] async fn grid_create_field() { - let mut test = DatabaseFieldTest::new().await; - let (params, field_rev) = create_text_field(&test.view_id()); + let mut test = DatabaseFieldTest::new().await; + let (params, field_rev) = create_text_field(&test.view_id()); - let scripts = vec![ - CreateField { params }, - AssertFieldTypeOptionEqual { - field_index: test.field_count(), - expected_type_option_data: field_rev.get_type_option_str(field_rev.ty).unwrap().to_owned(), - }, - ]; - test.run_scripts(scripts).await; + let scripts = vec![ + CreateField { params }, + AssertFieldTypeOptionEqual { + field_index: test.field_count(), + expected_type_option_data: field_rev + .get_type_option_str(field_rev.ty) + .unwrap() + .to_owned(), + }, + ]; + test.run_scripts(scripts).await; - let (params, field_rev) = create_single_select_field(&test.view_id()); - let scripts = vec![ - CreateField { params }, - AssertFieldTypeOptionEqual { - field_index: test.field_count(), - expected_type_option_data: field_rev.get_type_option_str(field_rev.ty).unwrap().to_owned(), - }, - ]; - test.run_scripts(scripts).await; + let (params, field_rev) = create_single_select_field(&test.view_id()); + let scripts = vec![ + CreateField { params }, + AssertFieldTypeOptionEqual { + field_index: test.field_count(), + expected_type_option_data: field_rev + .get_type_option_str(field_rev.ty) + .unwrap() + .to_owned(), + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_create_duplicate_field() { - let mut test = DatabaseFieldTest::new().await; - let (params, _) = create_text_field(&test.view_id()); - let field_count = test.field_count(); - let expected_field_count = field_count + 1; - let scripts = vec![ - CreateField { params: params.clone() }, - AssertFieldCount(expected_field_count), - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFieldTest::new().await; + let (params, _) = create_text_field(&test.view_id()); + let field_count = test.field_count(); + let expected_field_count = field_count + 1; + let scripts = vec![ + CreateField { + params: params.clone(), + }, + AssertFieldCount(expected_field_count), + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_update_field_with_empty_change() { - let mut test = DatabaseFieldTest::new().await; - let (params, _) = create_single_select_field(&test.view_id()); - let create_field_index = test.field_count(); - let scripts = vec![CreateField { params }]; - test.run_scripts(scripts).await; + let mut test = DatabaseFieldTest::new().await; + let (params, _) = create_single_select_field(&test.view_id()); + let create_field_index = test.field_count(); + let scripts = vec![CreateField { params }]; + test.run_scripts(scripts).await; - let field_rev = (*test.field_revs.clone().pop().unwrap()).clone(); - let changeset = FieldChangesetParams { - field_id: field_rev.id.clone(), - database_id: test.view_id(), - ..Default::default() - }; + let field_rev = (*test.field_revs.clone().pop().unwrap()).clone(); + let changeset = FieldChangesetParams { + field_id: field_rev.id.clone(), + database_id: test.view_id(), + ..Default::default() + }; - let scripts = vec![ - UpdateField { changeset }, - AssertFieldTypeOptionEqual { - field_index: create_field_index, - expected_type_option_data: field_rev.get_type_option_str(field_rev.ty).unwrap().to_owned(), - }, - ]; - test.run_scripts(scripts).await; + let scripts = vec![ + UpdateField { changeset }, + AssertFieldTypeOptionEqual { + field_index: create_field_index, + expected_type_option_data: field_rev + .get_type_option_str(field_rev.ty) + .unwrap() + .to_owned(), + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_update_field() { - let mut test = DatabaseFieldTest::new().await; - let (params, _) = create_single_select_field(&test.view_id()); - let scripts = vec![CreateField { params }]; - let create_field_index = test.field_count(); - test.run_scripts(scripts).await; - // - let single_select_field = (*test.field_revs.clone().pop().unwrap()).clone(); - let mut single_select_type_option = SingleSelectTypeOptionPB::from(&single_select_field); - single_select_type_option.options.push(SelectOptionPB::new("Unknown")); + let mut test = DatabaseFieldTest::new().await; + let (params, _) = create_single_select_field(&test.view_id()); + let scripts = vec![CreateField { params }]; + let create_field_index = test.field_count(); + test.run_scripts(scripts).await; + // + let single_select_field = (*test.field_revs.clone().pop().unwrap()).clone(); + let mut single_select_type_option = SingleSelectTypeOptionPB::from(&single_select_field); + single_select_type_option + .options + .push(SelectOptionPB::new("Unknown")); - let changeset = FieldChangesetParams { - field_id: single_select_field.id.clone(), - database_id: test.view_id(), - frozen: Some(true), - width: Some(1000), - ..Default::default() - }; + let changeset = FieldChangesetParams { + field_id: single_select_field.id.clone(), + database_id: test.view_id(), + frozen: Some(true), + width: Some(1000), + ..Default::default() + }; - // The expected_field must be equal to the field that applied the changeset - let mut expected_field_rev = single_select_field.clone(); - expected_field_rev.frozen = true; - expected_field_rev.width = 1000; - expected_field_rev.insert_type_option(&single_select_type_option); + // The expected_field must be equal to the field that applied the changeset + let mut expected_field_rev = single_select_field.clone(); + expected_field_rev.frozen = true; + expected_field_rev.width = 1000; + expected_field_rev.insert_type_option(&single_select_type_option); - let scripts = vec![ - UpdateField { changeset }, - AssertFieldFrozen { - field_index: create_field_index, - frozen: true, - }, - ]; - test.run_scripts(scripts).await; + let scripts = vec![ + UpdateField { changeset }, + AssertFieldFrozen { + field_index: create_field_index, + frozen: true, + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_delete_field() { - let mut test = DatabaseFieldTest::new().await; - let original_field_count = test.field_count(); - let (params, _) = create_text_field(&test.view_id()); - let scripts = vec![CreateField { params }]; - test.run_scripts(scripts).await; + let mut test = DatabaseFieldTest::new().await; + let original_field_count = test.field_count(); + let (params, _) = create_text_field(&test.view_id()); + let scripts = vec![CreateField { params }]; + test.run_scripts(scripts).await; - let text_field_rev = (*test.field_revs.clone().pop().unwrap()).clone(); - let scripts = vec![ - DeleteField { - field_rev: text_field_rev, - }, - AssertFieldCount(original_field_count), - ]; - test.run_scripts(scripts).await; + let text_field_rev = (*test.field_revs.clone().pop().unwrap()).clone(); + let scripts = vec![ + DeleteField { + field_rev: text_field_rev, + }, + AssertFieldCount(original_field_count), + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_switch_from_select_option_to_checkbox_test() { - let mut test = DatabaseFieldTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::SingleSelect); + let mut test = DatabaseFieldTest::new().await; + let field_rev = test.get_first_field_rev(FieldType::SingleSelect); - // Update the type option data of single select option - let mut single_select_type_option = test.get_single_select_type_option(&field_rev.id); - single_select_type_option.options.clear(); - // Add a new option with name CHECK - single_select_type_option.options.push(SelectOptionPB { - id: gen_option_id(), - name: CHECK.to_string(), - color: Default::default(), - }); - // Add a new option with name UNCHECK - single_select_type_option.options.push(SelectOptionPB { - id: gen_option_id(), - name: UNCHECK.to_string(), - color: Default::default(), - }); + // Update the type option data of single select option + let mut single_select_type_option = test.get_single_select_type_option(&field_rev.id); + single_select_type_option.options.clear(); + // Add a new option with name CHECK + single_select_type_option.options.push(SelectOptionPB { + id: gen_option_id(), + name: CHECK.to_string(), + color: Default::default(), + }); + // Add a new option with name UNCHECK + single_select_type_option.options.push(SelectOptionPB { + id: gen_option_id(), + name: UNCHECK.to_string(), + color: Default::default(), + }); - let bytes: Bytes = single_select_type_option.try_into().unwrap(); - let scripts = vec![ - UpdateTypeOption { - field_id: field_rev.id.clone(), - type_option: bytes.to_vec(), - }, - SwitchToField { - field_id: field_rev.id.clone(), - new_field_type: FieldType::Checkbox, - }, - ]; - test.run_scripts(scripts).await; + let bytes: Bytes = single_select_type_option.try_into().unwrap(); + let scripts = vec![ + UpdateTypeOption { + field_id: field_rev.id.clone(), + type_option: bytes.to_vec(), + }, + SwitchToField { + field_id: field_rev.id.clone(), + new_field_type: FieldType::Checkbox, + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_switch_from_checkbox_to_select_option_test() { - let mut test = DatabaseFieldTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::Checkbox).clone(); - let scripts = vec![ - // switch to single-select field type - SwitchToField { - field_id: field_rev.id.clone(), - new_field_type: FieldType::SingleSelect, - }, - // Assert the cell content after switch the field type. The cell content will be changed if - // the FieldType::SingleSelect implement the cell data TypeOptionTransform. Check out the - // TypeOptionTransform trait for more information. - // - // Make sure which cell of the row you want to check. - AssertCellContent { - field_id: field_rev.id.clone(), - // the mock data of the checkbox with row_index one is "true" - row_index: 1, - // the from_field_type represents as the current field type - from_field_type: FieldType::Checkbox, - // The content of the checkbox should transform to the corresponding option name. - expected_content: CHECK.to_string(), - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFieldTest::new().await; + let field_rev = test.get_first_field_rev(FieldType::Checkbox).clone(); + let scripts = vec![ + // switch to single-select field type + SwitchToField { + field_id: field_rev.id.clone(), + new_field_type: FieldType::SingleSelect, + }, + // Assert the cell content after switch the field type. The cell content will be changed if + // the FieldType::SingleSelect implement the cell data TypeOptionTransform. Check out the + // TypeOptionTransform trait for more information. + // + // Make sure which cell of the row you want to check. + AssertCellContent { + field_id: field_rev.id.clone(), + // the mock data of the checkbox with row_index one is "true" + row_index: 1, + // the from_field_type represents as the current field type + from_field_type: FieldType::Checkbox, + // The content of the checkbox should transform to the corresponding option name. + expected_content: CHECK.to_string(), + }, + ]; + test.run_scripts(scripts).await; - let single_select_type_option = test.get_single_select_type_option(&field_rev.id); - assert_eq!(single_select_type_option.options.len(), 2); - assert!(single_select_type_option - .options - .iter() - .any(|option| option.name == UNCHECK)); - assert!(single_select_type_option - .options - .iter() - .any(|option| option.name == CHECK)); + let single_select_type_option = test.get_single_select_type_option(&field_rev.id); + assert_eq!(single_select_type_option.options.len(), 2); + assert!(single_select_type_option + .options + .iter() + .any(|option| option.name == UNCHECK)); + assert!(single_select_type_option + .options + .iter() + .any(|option| option.name == CHECK)); } // Test when switching the current field from Multi-select to Text test @@ -203,30 +216,30 @@ async fn grid_switch_from_checkbox_to_select_option_test() { // option1, option2 -> "option1.name, option2.name" #[tokio::test] async fn grid_switch_from_multi_select_to_text_test() { - let mut test = DatabaseFieldTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::MultiSelect).clone(); + let mut test = DatabaseFieldTest::new().await; + let field_rev = test.get_first_field_rev(FieldType::MultiSelect).clone(); - let multi_select_type_option = test.get_multi_select_type_option(&field_rev.id); + let multi_select_type_option = test.get_multi_select_type_option(&field_rev.id); - let script_switch_field = vec![SwitchToField { - field_id: field_rev.id.clone(), - new_field_type: FieldType::RichText, - }]; + let script_switch_field = vec![SwitchToField { + field_id: field_rev.id.clone(), + new_field_type: FieldType::RichText, + }]; - test.run_scripts(script_switch_field).await; + test.run_scripts(script_switch_field).await; - let script_assert_field = vec![AssertCellContent { - field_id: field_rev.id.clone(), - row_index: 0, - from_field_type: FieldType::MultiSelect, - expected_content: format!( - "{},{}", - multi_select_type_option.get(0).unwrap().name, - multi_select_type_option.get(1).unwrap().name - ), - }]; + let script_assert_field = vec![AssertCellContent { + field_id: field_rev.id.clone(), + row_index: 0, + from_field_type: FieldType::MultiSelect, + expected_content: format!( + "{},{}", + multi_select_type_option.get(0).unwrap().name, + multi_select_type_option.get(1).unwrap().name + ), + }]; - test.run_scripts(script_assert_field).await; + test.run_scripts(script_assert_field).await; } // Test when switching the current field from Checkbox to Text test @@ -235,28 +248,28 @@ async fn grid_switch_from_multi_select_to_text_test() { // unchecked -> "" #[tokio::test] async fn grid_switch_from_checkbox_to_text_test() { - let mut test = DatabaseFieldTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::Checkbox); + let mut test = DatabaseFieldTest::new().await; + let field_rev = test.get_first_field_rev(FieldType::Checkbox); - let scripts = vec![ - SwitchToField { - field_id: field_rev.id.clone(), - new_field_type: FieldType::RichText, - }, - AssertCellContent { - field_id: field_rev.id.clone(), - row_index: 1, - from_field_type: FieldType::Checkbox, - expected_content: "Yes".to_string(), - }, - AssertCellContent { - field_id: field_rev.id.clone(), - row_index: 2, - from_field_type: FieldType::Checkbox, - expected_content: "No".to_string(), - }, - ]; - test.run_scripts(scripts).await; + let scripts = vec![ + SwitchToField { + field_id: field_rev.id.clone(), + new_field_type: FieldType::RichText, + }, + AssertCellContent { + field_id: field_rev.id.clone(), + row_index: 1, + from_field_type: FieldType::Checkbox, + expected_content: "Yes".to_string(), + }, + AssertCellContent { + field_id: field_rev.id.clone(), + row_index: 2, + from_field_type: FieldType::Checkbox, + expected_content: "No".to_string(), + }, + ]; + test.run_scripts(scripts).await; } // Test when switching the current field from Checkbox to Text test @@ -265,22 +278,22 @@ async fn grid_switch_from_checkbox_to_text_test() { // "" -> unchecked #[tokio::test] async fn grid_switch_from_text_to_checkbox_test() { - let mut test = DatabaseFieldTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::RichText).clone(); + let mut test = DatabaseFieldTest::new().await; + let field_rev = test.get_first_field_rev(FieldType::RichText).clone(); - let scripts = vec![ - SwitchToField { - field_id: field_rev.id.clone(), - new_field_type: FieldType::Checkbox, - }, - AssertCellContent { - field_id: field_rev.id.clone(), - row_index: 0, - from_field_type: FieldType::RichText, - expected_content: "".to_string(), - }, - ]; - test.run_scripts(scripts).await; + let scripts = vec![ + SwitchToField { + field_id: field_rev.id.clone(), + new_field_type: FieldType::Checkbox, + }, + AssertCellContent { + field_id: field_rev.id.clone(), + row_index: 0, + from_field_type: FieldType::RichText, + expected_content: "".to_string(), + }, + ]; + test.run_scripts(scripts).await; } // Test when switching the current field from Date to Text test @@ -288,27 +301,27 @@ async fn grid_switch_from_text_to_checkbox_test() { // 1647251762 -> Mar 14,2022 (This string will be different base on current data setting) #[tokio::test] async fn grid_switch_from_date_to_text_test() { - let mut test = DatabaseFieldTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::DateTime).clone(); - let scripts = vec![ - SwitchToField { - field_id: field_rev.id.clone(), - new_field_type: FieldType::RichText, - }, - AssertCellContent { - field_id: field_rev.id.clone(), - row_index: 2, - from_field_type: FieldType::DateTime, - expected_content: "2022/03/14".to_string(), - }, - AssertCellContent { - field_id: field_rev.id.clone(), - row_index: 3, - from_field_type: FieldType::DateTime, - expected_content: "2022/11/17".to_string(), - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFieldTest::new().await; + let field_rev = test.get_first_field_rev(FieldType::DateTime).clone(); + let scripts = vec![ + SwitchToField { + field_id: field_rev.id.clone(), + new_field_type: FieldType::RichText, + }, + AssertCellContent { + field_id: field_rev.id.clone(), + row_index: 2, + from_field_type: FieldType::DateTime, + expected_content: "2022/03/14".to_string(), + }, + AssertCellContent { + field_id: field_rev.id.clone(), + row_index: 3, + from_field_type: FieldType::DateTime, + expected_content: "2022/11/17".to_string(), + }, + ]; + test.run_scripts(scripts).await; } // Test when switching the current field from Number to Text test @@ -316,27 +329,27 @@ async fn grid_switch_from_date_to_text_test() { // $1 -> "$1"(This string will be different base on current data setting) #[tokio::test] async fn grid_switch_from_number_to_text_test() { - let mut test = DatabaseFieldTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::Number).clone(); + let mut test = DatabaseFieldTest::new().await; + let field_rev = test.get_first_field_rev(FieldType::Number).clone(); - let scripts = vec![ - SwitchToField { - field_id: field_rev.id.clone(), - new_field_type: FieldType::RichText, - }, - AssertCellContent { - field_id: field_rev.id.clone(), - row_index: 0, - from_field_type: FieldType::Number, - expected_content: "$1".to_string(), - }, - AssertCellContent { - field_id: field_rev.id.clone(), - row_index: 4, - from_field_type: FieldType::Number, - expected_content: "".to_string(), - }, - ]; + let scripts = vec![ + SwitchToField { + field_id: field_rev.id.clone(), + new_field_type: FieldType::RichText, + }, + AssertCellContent { + field_id: field_rev.id.clone(), + row_index: 0, + from_field_type: FieldType::Number, + expected_content: "$1".to_string(), + }, + AssertCellContent { + field_id: field_rev.id.clone(), + row_index: 4, + from_field_type: FieldType::Number, + expected_content: "".to_string(), + }, + ]; - test.run_scripts(scripts).await; + test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-database/tests/grid/field_test/util.rs b/frontend/rust-lib/flowy-database/tests/grid/field_test/util.rs index 7723c4ad03..c851269420 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/field_test/util.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/field_test/util.rs @@ -4,61 +4,66 @@ use flowy_database::services::field::*; use grid_model::*; pub fn create_text_field(grid_id: &str) -> (CreateFieldParams, FieldRevision) { - let mut field_rev = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Name") - .visibility(true) - .build(); + let mut field_rev = FieldBuilder::new(RichTextTypeOptionBuilder::default()) + .name("Name") + .visibility(true) + .build(); - let cloned_field_rev = field_rev.clone(); + let cloned_field_rev = field_rev.clone(); - let type_option_data = field_rev - .get_type_option::(field_rev.ty) - .unwrap() - .protobuf_bytes() - .to_vec(); + let type_option_data = field_rev + .get_type_option::(field_rev.ty) + .unwrap() + .protobuf_bytes() + .to_vec(); - let type_option_builder = type_option_builder_from_bytes(type_option_data.clone(), &field_rev.ty.into()); - field_rev.insert_type_option(type_option_builder.serializer()); + let type_option_builder = + type_option_builder_from_bytes(type_option_data.clone(), &field_rev.ty.into()); + field_rev.insert_type_option(type_option_builder.serializer()); - let params = CreateFieldParams { - database_id: grid_id.to_owned(), - field_type: field_rev.ty.into(), - type_option_data: Some(type_option_data), - }; - (params, cloned_field_rev) + let params = CreateFieldParams { + database_id: grid_id.to_owned(), + field_type: field_rev.ty.into(), + type_option_data: Some(type_option_data), + }; + (params, cloned_field_rev) } pub fn create_single_select_field(grid_id: &str) -> (CreateFieldParams, FieldRevision) { - let single_select = SingleSelectTypeOptionBuilder::default() - .add_option(SelectOptionPB::new("Done")) - .add_option(SelectOptionPB::new("Progress")); + let single_select = SingleSelectTypeOptionBuilder::default() + .add_option(SelectOptionPB::new("Done")) + .add_option(SelectOptionPB::new("Progress")); - let mut field_rev = FieldBuilder::new(single_select).name("Name").visibility(true).build(); - let cloned_field_rev = field_rev.clone(); - let type_option_data = field_rev - .get_type_option::(field_rev.ty) - .unwrap() - .protobuf_bytes() - .to_vec(); + let mut field_rev = FieldBuilder::new(single_select) + .name("Name") + .visibility(true) + .build(); + let cloned_field_rev = field_rev.clone(); + let type_option_data = field_rev + .get_type_option::(field_rev.ty) + .unwrap() + .protobuf_bytes() + .to_vec(); - let type_option_builder = type_option_builder_from_bytes(type_option_data.clone(), &field_rev.ty.into()); - field_rev.insert_type_option(type_option_builder.serializer()); + let type_option_builder = + type_option_builder_from_bytes(type_option_data.clone(), &field_rev.ty.into()); + field_rev.insert_type_option(type_option_builder.serializer()); - let params = CreateFieldParams { - database_id: grid_id.to_owned(), - field_type: field_rev.ty.into(), - type_option_data: Some(type_option_data), - }; - (params, cloned_field_rev) + let params = CreateFieldParams { + database_id: grid_id.to_owned(), + field_type: field_rev.ty.into(), + type_option_data: Some(type_option_data), + }; + (params, cloned_field_rev) } // The grid will contains all existing field types and there are three empty rows in this grid. pub fn make_date_cell_string(s: &str) -> String { - serde_json::to_string(&DateCellChangeset { - date: Some(s.to_string()), - time: None, - is_utc: true, - }) - .unwrap() + serde_json::to_string(&DateCellChangeset { + date: Some(s.to_string()), + time: None, + is_utc: true, + }) + .unwrap() } diff --git a/frontend/rust-lib/flowy-database/tests/grid/filter_test/checkbox_filter_test.rs b/frontend/rust-lib/flowy-database/tests/grid/filter_test/checkbox_filter_test.rs index 646b26ba8d..178e9aad7c 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/filter_test/checkbox_filter_test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/filter_test/checkbox_filter_test.rs @@ -4,34 +4,34 @@ use flowy_database::entities::CheckboxFilterConditionPB; #[tokio::test] async fn grid_filter_checkbox_is_check_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - // The initial number of unchecked is 3 - // The initial number of checked is 2 - let scripts = vec![CreateCheckboxFilter { - condition: CheckboxFilterConditionPB::IsChecked, - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - 3, - }), - }]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let row_count = test.row_revs.len(); + // The initial number of unchecked is 3 + // The initial number of checked is 2 + let scripts = vec![CreateCheckboxFilter { + condition: CheckboxFilterConditionPB::IsChecked, + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - 3, + }), + }]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_checkbox_is_uncheck_test() { - let mut test = DatabaseFilterTest::new().await; - let expected = 3; - let row_count = test.row_revs.len(); - let scripts = vec![ - CreateCheckboxFilter { - condition: CheckboxFilterConditionPB::IsUnChecked, - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let expected = 3; + let row_count = test.row_revs.len(); + let scripts = vec![ + CreateCheckboxFilter { + condition: CheckboxFilterConditionPB::IsUnChecked, + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-database/tests/grid/filter_test/checklist_filter_test.rs b/frontend/rust-lib/flowy-database/tests/grid/filter_test/checklist_filter_test.rs index 5340a7c27c..8cb257eadc 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/filter_test/checklist_filter_test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/filter_test/checklist_filter_test.rs @@ -4,36 +4,36 @@ use flowy_database::entities::ChecklistFilterConditionPB; #[tokio::test] async fn grid_filter_checklist_is_incomplete_test() { - let mut test = DatabaseFilterTest::new().await; - let expected = 5; - let row_count = test.row_revs.len(); - let scripts = vec![ - CreateChecklistFilter { - condition: ChecklistFilterConditionPB::IsIncomplete, - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let expected = 5; + let row_count = test.row_revs.len(); + let scripts = vec![ + CreateChecklistFilter { + condition: ChecklistFilterConditionPB::IsIncomplete, + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_checklist_is_complete_test() { - let mut test = DatabaseFilterTest::new().await; - let expected = 1; - let row_count = test.row_revs.len(); - let scripts = vec![ - CreateChecklistFilter { - condition: ChecklistFilterConditionPB::IsComplete, - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let expected = 1; + let row_count = test.row_revs.len(); + let scripts = vec![ + CreateChecklistFilter { + condition: ChecklistFilterConditionPB::IsComplete, + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-database/tests/grid/filter_test/date_filter_test.rs b/frontend/rust-lib/flowy-database/tests/grid/filter_test/date_filter_test.rs index 47a459967b..79f2ef75e8 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/filter_test/date_filter_test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/filter_test/date_filter_test.rs @@ -4,105 +4,105 @@ use flowy_database::entities::DateFilterConditionPB; #[tokio::test] async fn grid_filter_date_is_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 3; - let scripts = vec![ - CreateDateFilter { - condition: DateFilterConditionPB::DateIs, - start: None, - end: None, - timestamp: Some(1647251762), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let row_count = test.row_revs.len(); + let expected = 3; + let scripts = vec![ + CreateDateFilter { + condition: DateFilterConditionPB::DateIs, + start: None, + end: None, + timestamp: Some(1647251762), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_date_after_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 3; - let scripts = vec![ - CreateDateFilter { - condition: DateFilterConditionPB::DateAfter, - start: None, - end: None, - timestamp: Some(1647251762), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let row_count = test.row_revs.len(); + let expected = 3; + let scripts = vec![ + CreateDateFilter { + condition: DateFilterConditionPB::DateAfter, + start: None, + end: None, + timestamp: Some(1647251762), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_date_on_or_after_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 3; - let scripts = vec![ - CreateDateFilter { - condition: DateFilterConditionPB::DateOnOrAfter, - start: None, - end: None, - timestamp: Some(1668359085), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let row_count = test.row_revs.len(); + let expected = 3; + let scripts = vec![ + CreateDateFilter { + condition: DateFilterConditionPB::DateOnOrAfter, + start: None, + end: None, + timestamp: Some(1668359085), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_date_on_or_before_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 4; - let scripts = vec![ - CreateDateFilter { - condition: DateFilterConditionPB::DateOnOrBefore, - start: None, - end: None, - timestamp: Some(1668359085), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let row_count = test.row_revs.len(); + let expected = 4; + let scripts = vec![ + CreateDateFilter { + condition: DateFilterConditionPB::DateOnOrBefore, + start: None, + end: None, + timestamp: Some(1668359085), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_date_within_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 5; - let scripts = vec![ - CreateDateFilter { - condition: DateFilterConditionPB::DateWithIn, - start: Some(1647251762), - end: Some(1668704685), - timestamp: None, - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let row_count = test.row_revs.len(); + let expected = 5; + let scripts = vec![ + CreateDateFilter { + condition: DateFilterConditionPB::DateWithIn, + start: Some(1647251762), + end: Some(1668704685), + timestamp: None, + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-database/tests/grid/filter_test/number_filter_test.rs b/frontend/rust-lib/flowy-database/tests/grid/filter_test/number_filter_test.rs index ee1b2b7b53..11045eb314 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/filter_test/number_filter_test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/filter_test/number_filter_test.rs @@ -4,115 +4,115 @@ use flowy_database::entities::NumberFilterConditionPB; #[tokio::test] async fn grid_filter_number_is_equal_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 1; - let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::Equal, - content: "1".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let row_count = test.row_revs.len(); + let expected = 1; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterConditionPB::Equal, + content: "1".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_number_is_less_than_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 2; - let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::LessThan, - content: "3".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let row_count = test.row_revs.len(); + let expected = 2; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterConditionPB::LessThan, + content: "3".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] #[should_panic] async fn grid_filter_number_is_less_than_test2() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 2; - let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::LessThan, - content: "$3".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let row_count = test.row_revs.len(); + let expected = 2; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterConditionPB::LessThan, + content: "$3".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_number_is_less_than_or_equal_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 3; - let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::LessThanOrEqualTo, - content: "3".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let row_count = test.row_revs.len(); + let expected = 3; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterConditionPB::LessThanOrEqualTo, + content: "3".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_number_is_empty_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 1; - let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::NumberIsEmpty, - content: "".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let row_count = test.row_revs.len(); + let expected = 1; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterConditionPB::NumberIsEmpty, + content: "".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_number_is_not_empty_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 5; - let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::NumberIsNotEmpty, - content: "".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let row_count = test.row_revs.len(); + let expected = 5; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterConditionPB::NumberIsNotEmpty, + content: "".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-database/tests/grid/filter_test/select_option_filter_test.rs b/frontend/rust-lib/flowy-database/tests/grid/filter_test/select_option_filter_test.rs index da1f6e4c16..f50a19f76a 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/filter_test/select_option_filter_test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/filter_test/select_option_filter_test.rs @@ -4,134 +4,134 @@ use flowy_database::entities::{FieldType, SelectOptionConditionPB}; #[tokio::test] async fn grid_filter_multi_select_is_empty_test() { - let mut test = DatabaseFilterTest::new().await; - let scripts = vec![ - CreateMultiSelectFilter { - condition: SelectOptionConditionPB::OptionIsEmpty, - option_ids: vec![], - }, - AssertNumberOfVisibleRows { expected: 3 }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let scripts = vec![ + CreateMultiSelectFilter { + condition: SelectOptionConditionPB::OptionIsEmpty, + option_ids: vec![], + }, + AssertNumberOfVisibleRows { expected: 3 }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_multi_select_is_not_empty_test() { - let mut test = DatabaseFilterTest::new().await; - let scripts = vec![ - CreateMultiSelectFilter { - condition: SelectOptionConditionPB::OptionIsNotEmpty, - option_ids: vec![], - }, - AssertNumberOfVisibleRows { expected: 3 }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let scripts = vec![ + CreateMultiSelectFilter { + condition: SelectOptionConditionPB::OptionIsNotEmpty, + option_ids: vec![], + }, + AssertNumberOfVisibleRows { expected: 3 }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_multi_select_is_test() { - let mut test = DatabaseFilterTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::MultiSelect); - let mut options = test.get_multi_select_type_option(&field_rev.id); - let scripts = vec![ - CreateMultiSelectFilter { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![options.remove(0).id, options.remove(0).id], - }, - AssertNumberOfVisibleRows { expected: 3 }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let field_rev = test.get_first_field_rev(FieldType::MultiSelect); + let mut options = test.get_multi_select_type_option(&field_rev.id); + let scripts = vec![ + CreateMultiSelectFilter { + condition: SelectOptionConditionPB::OptionIs, + option_ids: vec![options.remove(0).id, options.remove(0).id], + }, + AssertNumberOfVisibleRows { expected: 3 }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_multi_select_is_test2() { - let mut test = DatabaseFilterTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::MultiSelect); - let mut options = test.get_multi_select_type_option(&field_rev.id); - let scripts = vec![ - CreateMultiSelectFilter { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![options.remove(1).id], - }, - AssertNumberOfVisibleRows { expected: 2 }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let field_rev = test.get_first_field_rev(FieldType::MultiSelect); + let mut options = test.get_multi_select_type_option(&field_rev.id); + let scripts = vec![ + CreateMultiSelectFilter { + condition: SelectOptionConditionPB::OptionIs, + option_ids: vec![options.remove(1).id], + }, + AssertNumberOfVisibleRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_single_select_is_empty_test() { - let mut test = DatabaseFilterTest::new().await; - let expected = 2; - let row_count = test.row_revs.len(); - let scripts = vec![ - CreateSingleSelectFilter { - condition: SelectOptionConditionPB::OptionIsEmpty, - option_ids: vec![], - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let expected = 2; + let row_count = test.row_revs.len(); + let scripts = vec![ + CreateSingleSelectFilter { + condition: SelectOptionConditionPB::OptionIsEmpty, + option_ids: vec![], + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_single_select_is_test() { - let mut test = DatabaseFilterTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::SingleSelect); - let mut options = test.get_single_select_type_option(&field_rev.id).options; - let expected = 2; - let row_count = test.row_revs.len(); - let scripts = vec![ - CreateSingleSelectFilter { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![options.remove(0).id], - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected: 2 }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let field_rev = test.get_first_field_rev(FieldType::SingleSelect); + let mut options = test.get_single_select_type_option(&field_rev.id).options; + let expected = 2; + let row_count = test.row_revs.len(); + let scripts = vec![ + CreateSingleSelectFilter { + condition: SelectOptionConditionPB::OptionIs, + option_ids: vec![options.remove(0).id], + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_single_select_is_test2() { - let mut test = DatabaseFilterTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::SingleSelect); - let row_revs = test.get_row_revs().await; - let mut options = test.get_single_select_type_option(&field_rev.id).options; - let option = options.remove(0); - let row_count = test.row_revs.len(); + let mut test = DatabaseFilterTest::new().await; + let field_rev = test.get_first_field_rev(FieldType::SingleSelect); + let row_revs = test.get_row_revs().await; + let mut options = test.get_single_select_type_option(&field_rev.id).options; + let option = options.remove(0); + let row_count = test.row_revs.len(); - let scripts = vec![ - CreateSingleSelectFilter { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![option.id.clone()], - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - 2, - }), - }, - AssertNumberOfVisibleRows { expected: 2 }, - UpdateSingleSelectCell { - row_id: row_revs[1].id.clone(), - option_id: option.id.clone(), - changed: None, - }, - AssertNumberOfVisibleRows { expected: 3 }, - UpdateSingleSelectCell { - row_id: row_revs[1].id.clone(), - option_id: "".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 1, - }), - }, - AssertNumberOfVisibleRows { expected: 2 }, - ]; - test.run_scripts(scripts).await; + let scripts = vec![ + CreateSingleSelectFilter { + condition: SelectOptionConditionPB::OptionIs, + option_ids: vec![option.id.clone()], + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - 2, + }), + }, + AssertNumberOfVisibleRows { expected: 2 }, + UpdateSingleSelectCell { + row_id: row_revs[1].id.clone(), + option_id: option.id.clone(), + changed: None, + }, + AssertNumberOfVisibleRows { expected: 3 }, + UpdateSingleSelectCell { + row_id: row_revs[1].id.clone(), + option_id: "".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: 1, + }), + }, + AssertNumberOfVisibleRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-database/tests/grid/filter_test/text_filter_test.rs b/frontend/rust-lib/flowy-database/tests/grid/filter_test/text_filter_test.rs index 2c34eeeef2..56e41daea8 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/filter_test/text_filter_test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/filter_test/text_filter_test.rs @@ -1,241 +1,245 @@ use crate::grid::filter_test::script::FilterScript::*; use crate::grid::filter_test::script::*; -use flowy_database::entities::{AlterFilterPayloadPB, FieldType, TextFilterConditionPB, TextFilterPB}; +use flowy_database::entities::{ + AlterFilterPayloadPB, FieldType, TextFilterConditionPB, TextFilterPB, +}; use flowy_database::services::filter::FilterType; #[tokio::test] async fn grid_filter_text_is_empty_test() { - let mut test = DatabaseFilterTest::new().await; - let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::TextIsEmpty, - content: "".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 5, - }), - }, - AssertFilterCount { count: 1 }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let scripts = vec![ + CreateTextFilter { + condition: TextFilterConditionPB::TextIsEmpty, + content: "".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: 5, + }), + }, + AssertFilterCount { count: 1 }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_text_is_not_empty_test() { - let mut test = DatabaseFilterTest::new().await; - // Only one row's text of the initial rows is "" - let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::TextIsNotEmpty, - content: "".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 1, - }), - }, - AssertFilterCount { count: 1 }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + // Only one row's text of the initial rows is "" + let scripts = vec![ + CreateTextFilter { + condition: TextFilterConditionPB::TextIsNotEmpty, + content: "".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: 1, + }), + }, + AssertFilterCount { count: 1 }, + ]; + test.run_scripts(scripts).await; - let filter = test.grid_filters().await.pop().unwrap(); - let field_rev = test.get_first_field_rev(FieldType::RichText).clone(); - test.run_scripts(vec![ - DeleteFilter { - filter_id: filter.id, - filter_type: FilterType::from(&field_rev), - changed: Some(FilterRowChanged { - showing_num_of_rows: 1, - hiding_num_of_rows: 0, - }), - }, - AssertFilterCount { count: 0 }, + let filter = test.grid_filters().await.pop().unwrap(); + let field_rev = test.get_first_field_rev(FieldType::RichText).clone(); + test + .run_scripts(vec![ + DeleteFilter { + filter_id: filter.id, + filter_type: FilterType::from(&field_rev), + changed: Some(FilterRowChanged { + showing_num_of_rows: 1, + hiding_num_of_rows: 0, + }), + }, + AssertFilterCount { count: 0 }, ]) .await; } #[tokio::test] async fn grid_filter_is_text_test() { - let mut test = DatabaseFilterTest::new().await; - // Only one row's text of the initial rows is "A" - let scripts = vec![CreateTextFilter { - condition: TextFilterConditionPB::Is, - content: "A".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 5, - }), - }]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + // Only one row's text of the initial rows is "A" + let scripts = vec![CreateTextFilter { + condition: TextFilterConditionPB::Is, + content: "A".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: 5, + }), + }]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_contain_text_test() { - let mut test = DatabaseFilterTest::new().await; - let scripts = vec![CreateTextFilter { - condition: TextFilterConditionPB::Contains, - content: "A".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 2, - }), - }]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let scripts = vec![CreateTextFilter { + condition: TextFilterConditionPB::Contains, + content: "A".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: 2, + }), + }]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_contain_text_test2() { - let mut test = DatabaseFilterTest::new().await; - let row_revs = test.row_revs.clone(); + let mut test = DatabaseFilterTest::new().await; + let row_revs = test.row_revs.clone(); - let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::Contains, - content: "A".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 2, - }), - }, - UpdateTextCell { - row_id: row_revs[1].id.clone(), - text: "ABC".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 1, - hiding_num_of_rows: 0, - }), - }, - ]; - test.run_scripts(scripts).await; + let scripts = vec![ + CreateTextFilter { + condition: TextFilterConditionPB::Contains, + content: "A".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: 2, + }), + }, + UpdateTextCell { + row_id: row_revs[1].id.clone(), + text: "ABC".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 1, + hiding_num_of_rows: 0, + }), + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_does_not_contain_text_test() { - let mut test = DatabaseFilterTest::new().await; - // None of the initial rows contains the text "AB" - let scripts = vec![CreateTextFilter { - condition: TextFilterConditionPB::DoesNotContain, - content: "AB".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 0, - }), - }]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + // None of the initial rows contains the text "AB" + let scripts = vec![CreateTextFilter { + condition: TextFilterConditionPB::DoesNotContain, + content: "AB".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: 0, + }), + }]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_start_with_text_test() { - let mut test = DatabaseFilterTest::new().await; - let scripts = vec![CreateTextFilter { - condition: TextFilterConditionPB::StartsWith, - content: "A".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 3, - }), - }]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let scripts = vec![CreateTextFilter { + condition: TextFilterConditionPB::StartsWith, + content: "A".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: 3, + }), + }]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_ends_with_text_test() { - let mut test = DatabaseFilterTest::new().await; - let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::EndsWith, - content: "A".to_string(), - changed: None, - }, - AssertNumberOfVisibleRows { expected: 2 }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let scripts = vec![ + CreateTextFilter { + condition: TextFilterConditionPB::EndsWith, + content: "A".to_string(), + changed: None, + }, + AssertNumberOfVisibleRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_update_text_filter_test() { - let mut test = DatabaseFilterTest::new().await; - let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::EndsWith, - content: "A".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 4, - }), - }, - AssertNumberOfVisibleRows { expected: 2 }, - AssertFilterCount { count: 1 }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let scripts = vec![ + CreateTextFilter { + condition: TextFilterConditionPB::EndsWith, + content: "A".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: 4, + }), + }, + AssertNumberOfVisibleRows { expected: 2 }, + AssertFilterCount { count: 1 }, + ]; + test.run_scripts(scripts).await; - // Update the filter - let filter = test.get_all_filters().await.pop().unwrap(); - let scripts = vec![ - UpdateTextFilter { - filter, - condition: TextFilterConditionPB::Is, - content: "A".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 1, - }), - }, - AssertNumberOfVisibleRows { expected: 1 }, - ]; - test.run_scripts(scripts).await; + // Update the filter + let filter = test.get_all_filters().await.pop().unwrap(); + let scripts = vec![ + UpdateTextFilter { + filter, + condition: TextFilterConditionPB::Is, + content: "A".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: 1, + }), + }, + AssertNumberOfVisibleRows { expected: 1 }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn grid_filter_delete_test() { - let mut test = DatabaseFilterTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::RichText).clone(); - let text_filter = TextFilterPB { - condition: TextFilterConditionPB::TextIsEmpty, - content: "".to_string(), - }; - let payload = AlterFilterPayloadPB::new(&test.view_id(), &field_rev, text_filter); - let scripts = vec![ - InsertFilter { payload }, - AssertFilterCount { count: 1 }, - AssertNumberOfVisibleRows { expected: 1 }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let field_rev = test.get_first_field_rev(FieldType::RichText).clone(); + let text_filter = TextFilterPB { + condition: TextFilterConditionPB::TextIsEmpty, + content: "".to_string(), + }; + let payload = AlterFilterPayloadPB::new(&test.view_id(), &field_rev, text_filter); + let scripts = vec![ + InsertFilter { payload }, + AssertFilterCount { count: 1 }, + AssertNumberOfVisibleRows { expected: 1 }, + ]; + test.run_scripts(scripts).await; - let filter = test.grid_filters().await.pop().unwrap(); - test.run_scripts(vec![ - DeleteFilter { - filter_id: filter.id, - filter_type: FilterType::from(&field_rev), - changed: None, - }, - AssertFilterCount { count: 0 }, - AssertNumberOfVisibleRows { expected: 6 }, + let filter = test.grid_filters().await.pop().unwrap(); + test + .run_scripts(vec![ + DeleteFilter { + filter_id: filter.id, + filter_type: FilterType::from(&field_rev), + changed: None, + }, + AssertFilterCount { count: 0 }, + AssertNumberOfVisibleRows { expected: 6 }, ]) .await; } #[tokio::test] async fn grid_filter_update_empty_text_cell_test() { - let mut test = DatabaseFilterTest::new().await; - let row_revs = test.row_revs.clone(); - let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::TextIsEmpty, - content: "".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 5, - }), - }, - AssertFilterCount { count: 1 }, - UpdateTextCell { - row_id: row_revs[0].id.clone(), - text: "".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 1, - hiding_num_of_rows: 0, - }), - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseFilterTest::new().await; + let row_revs = test.row_revs.clone(); + let scripts = vec![ + CreateTextFilter { + condition: TextFilterConditionPB::TextIsEmpty, + content: "".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: 5, + }), + }, + AssertFilterCount { count: 1 }, + UpdateTextCell { + row_id: row_revs[0].id.clone(), + text: "".to_string(), + changed: Some(FilterRowChanged { + showing_num_of_rows: 1, + hiding_num_of_rows: 0, + }), + }, + ]; + test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-database/tests/grid/group_test/script.rs b/frontend/rust-lib/flowy-database/tests/grid/group_test/script.rs index f3ec9a24da..72e8899f47 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/group_test/script.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/group_test/script.rs @@ -1,299 +1,319 @@ use crate::grid::database_editor::DatabaseEditorTest; use flowy_database::entities::{ - CreateRowParams, FieldType, GroupPB, LayoutTypePB, MoveGroupParams, MoveGroupRowParams, RowPB, + CreateRowParams, FieldType, GroupPB, LayoutTypePB, MoveGroupParams, MoveGroupRowParams, RowPB, +}; +use flowy_database::services::cell::{ + delete_select_option_cell, insert_select_option_cell, insert_url_cell, }; -use flowy_database::services::cell::{delete_select_option_cell, insert_select_option_cell, insert_url_cell}; use flowy_database::services::field::{ - edit_single_select_type_option, SelectOptionPB, SelectTypeOptionSharedAction, SingleSelectTypeOptionPB, + edit_single_select_type_option, SelectOptionPB, SelectTypeOptionSharedAction, + SingleSelectTypeOptionPB, }; use grid_model::{FieldRevision, RowChangeset}; use std::sync::Arc; pub enum GroupScript { - AssertGroupRowCount { - group_index: usize, - row_count: usize, - }, - AssertGroupCount(usize), - AssertGroup { - group_index: usize, - expected_group: GroupPB, - }, - AssertRow { - group_index: usize, - row_index: usize, - row: RowPB, - }, - MoveRow { - from_group_index: usize, - from_row_index: usize, - to_group_index: usize, - to_row_index: usize, - }, - CreateRow { - group_index: usize, - }, - DeleteRow { - group_index: usize, - row_index: usize, - }, - UpdateGroupedCell { - from_group_index: usize, - row_index: usize, - to_group_index: usize, - }, - UpdateGroupedCellWithData { - from_group_index: usize, - row_index: usize, - cell_data: String, - }, - MoveGroup { - from_group_index: usize, - to_group_index: usize, - }, - UpdateSingleSelectSelectOption { - inserted_options: Vec, - }, - GroupByField { - field_id: String, - }, + AssertGroupRowCount { + group_index: usize, + row_count: usize, + }, + AssertGroupCount(usize), + AssertGroup { + group_index: usize, + expected_group: GroupPB, + }, + AssertRow { + group_index: usize, + row_index: usize, + row: RowPB, + }, + MoveRow { + from_group_index: usize, + from_row_index: usize, + to_group_index: usize, + to_row_index: usize, + }, + CreateRow { + group_index: usize, + }, + DeleteRow { + group_index: usize, + row_index: usize, + }, + UpdateGroupedCell { + from_group_index: usize, + row_index: usize, + to_group_index: usize, + }, + UpdateGroupedCellWithData { + from_group_index: usize, + row_index: usize, + cell_data: String, + }, + MoveGroup { + from_group_index: usize, + to_group_index: usize, + }, + UpdateSingleSelectSelectOption { + inserted_options: Vec, + }, + GroupByField { + field_id: String, + }, } pub struct DatabaseGroupTest { - inner: DatabaseEditorTest, + inner: DatabaseEditorTest, } impl DatabaseGroupTest { - pub async fn new() -> Self { - let editor_test = DatabaseEditorTest::new_board().await; - Self { inner: editor_test } + pub async fn new() -> Self { + let editor_test = DatabaseEditorTest::new_board().await; + Self { inner: editor_test } + } + + pub async fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; } + } - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } + pub async fn run_script(&mut self, script: GroupScript) { + match script { + GroupScript::AssertGroupRowCount { + group_index, + row_count, + } => { + assert_eq!(row_count, self.group_at_index(group_index).await.rows.len()); + }, + GroupScript::AssertGroupCount(count) => { + let groups = self.editor.load_groups().await.unwrap(); + assert_eq!(count, groups.len()); + }, + GroupScript::MoveRow { + from_group_index, + from_row_index, + to_group_index, + to_row_index, + } => { + let groups: Vec = self.editor.load_groups().await.unwrap().items; + let from_row = groups + .get(from_group_index) + .unwrap() + .rows + .get(from_row_index) + .unwrap(); + let to_group = groups.get(to_group_index).unwrap(); + let to_row = to_group.rows.get(to_row_index).unwrap(); + let params = MoveGroupRowParams { + view_id: self.inner.view_id.clone(), + from_row_id: from_row.id.clone(), + to_group_id: to_group.group_id.clone(), + to_row_id: Some(to_row.id.clone()), + }; + + self.editor.move_group_row(params).await.unwrap(); + }, + GroupScript::AssertRow { + group_index, + row_index, + row, + } => { + // + let group = self.group_at_index(group_index).await; + let compare_row = group.rows.get(row_index).unwrap().clone(); + assert_eq!(row.id, compare_row.id); + }, + GroupScript::CreateRow { group_index } => { + let group = self.group_at_index(group_index).await; + let params = CreateRowParams { + database_id: self.editor.database_id.clone(), + start_row_id: None, + group_id: Some(group.group_id.clone()), + layout: LayoutTypePB::Board, + }; + let _ = self.editor.create_row(params).await.unwrap(); + }, + GroupScript::DeleteRow { + group_index, + row_index, + } => { + let row = self.row_at_index(group_index, row_index).await; + self.editor.delete_row(&row.id).await.unwrap(); + }, + GroupScript::UpdateGroupedCell { + from_group_index, + row_index, + to_group_index, + } => { + let from_group = self.group_at_index(from_group_index).await; + let to_group = self.group_at_index(to_group_index).await; + let field_id = from_group.field_id; + let field_rev = self.editor.get_field_rev(&field_id).await.unwrap(); + let field_type: FieldType = field_rev.ty.into(); + + let cell_rev = if to_group.is_default { + match field_type { + FieldType::SingleSelect => { + delete_select_option_cell(vec![to_group.group_id.clone()], &field_rev) + }, + FieldType::MultiSelect => { + delete_select_option_cell(vec![to_group.group_id.clone()], &field_rev) + }, + _ => { + panic!("Unsupported group field type"); + }, + } + } else { + match field_type { + FieldType::SingleSelect => { + insert_select_option_cell(vec![to_group.group_id.clone()], &field_rev) + }, + FieldType::MultiSelect => { + insert_select_option_cell(vec![to_group.group_id.clone()], &field_rev) + }, + FieldType::URL => insert_url_cell(to_group.group_id.clone(), &field_rev), + _ => { + panic!("Unsupported group field type"); + }, + } + }; + + let row_id = self.row_at_index(from_group_index, row_index).await.id; + let mut row_changeset = RowChangeset::new(row_id); + row_changeset.cell_by_field_id.insert(field_id, cell_rev); + self.editor.update_row(row_changeset).await.unwrap(); + }, + GroupScript::UpdateGroupedCellWithData { + from_group_index, + row_index, + cell_data, + } => { + let from_group = self.group_at_index(from_group_index).await; + let field_id = from_group.field_id; + let field_rev = self.editor.get_field_rev(&field_id).await.unwrap(); + let field_type: FieldType = field_rev.ty.into(); + let cell_rev = match field_type { + FieldType::URL => insert_url_cell(cell_data, &field_rev), + _ => { + panic!("Unsupported group field type"); + }, + }; + + let row_id = self.row_at_index(from_group_index, row_index).await.id; + let mut row_changeset = RowChangeset::new(row_id); + row_changeset.cell_by_field_id.insert(field_id, cell_rev); + self.editor.update_row(row_changeset).await.unwrap(); + }, + GroupScript::MoveGroup { + from_group_index, + to_group_index, + } => { + let from_group = self.group_at_index(from_group_index).await; + let to_group = self.group_at_index(to_group_index).await; + let params = MoveGroupParams { + view_id: self.editor.database_id.clone(), + from_group_id: from_group.group_id, + to_group_id: to_group.group_id, + }; + self.editor.move_group(params).await.unwrap(); + // + }, + GroupScript::AssertGroup { + group_index, + expected_group: group_pb, + } => { + let group = self.group_at_index(group_index).await; + assert_eq!(group.group_id, group_pb.group_id); + assert_eq!(group.desc, group_pb.desc); + }, + GroupScript::UpdateSingleSelectSelectOption { inserted_options } => { + self + .edit_single_select_type_option(|type_option| { + for inserted_option in inserted_options { + type_option.insert_option(inserted_option); + } + }) + .await; + }, + GroupScript::GroupByField { field_id } => { + self.editor.group_by_field(&field_id).await.unwrap(); + }, } + } - pub async fn run_script(&mut self, script: GroupScript) { - match script { - GroupScript::AssertGroupRowCount { group_index, row_count } => { - assert_eq!(row_count, self.group_at_index(group_index).await.rows.len()); - } - GroupScript::AssertGroupCount(count) => { - let groups = self.editor.load_groups().await.unwrap(); - assert_eq!(count, groups.len()); - } - GroupScript::MoveRow { - from_group_index, - from_row_index, - to_group_index, - to_row_index, - } => { - let groups: Vec = self.editor.load_groups().await.unwrap().items; - let from_row = groups.get(from_group_index).unwrap().rows.get(from_row_index).unwrap(); - let to_group = groups.get(to_group_index).unwrap(); - let to_row = to_group.rows.get(to_row_index).unwrap(); - let params = MoveGroupRowParams { - view_id: self.inner.view_id.clone(), - from_row_id: from_row.id.clone(), - to_group_id: to_group.group_id.clone(), - to_row_id: Some(to_row.id.clone()), - }; + pub async fn group_at_index(&self, index: usize) -> GroupPB { + let groups = self.editor.load_groups().await.unwrap().items; + groups.get(index).unwrap().clone() + } - self.editor.move_group_row(params).await.unwrap(); - } - GroupScript::AssertRow { - group_index, - row_index, - row, - } => { - // - let group = self.group_at_index(group_index).await; - let compare_row = group.rows.get(row_index).unwrap().clone(); - assert_eq!(row.id, compare_row.id); - } - GroupScript::CreateRow { group_index } => { - let group = self.group_at_index(group_index).await; - let params = CreateRowParams { - database_id: self.editor.database_id.clone(), - start_row_id: None, - group_id: Some(group.group_id.clone()), - layout: LayoutTypePB::Board, - }; - let _ = self.editor.create_row(params).await.unwrap(); - } - GroupScript::DeleteRow { group_index, row_index } => { - let row = self.row_at_index(group_index, row_index).await; - self.editor.delete_row(&row.id).await.unwrap(); - } - GroupScript::UpdateGroupedCell { - from_group_index, - row_index, - to_group_index, - } => { - let from_group = self.group_at_index(from_group_index).await; - let to_group = self.group_at_index(to_group_index).await; - let field_id = from_group.field_id; - let field_rev = self.editor.get_field_rev(&field_id).await.unwrap(); - let field_type: FieldType = field_rev.ty.into(); + pub async fn row_at_index(&self, group_index: usize, row_index: usize) -> RowPB { + let groups = self.group_at_index(group_index).await; + groups.rows.get(row_index).unwrap().clone() + } - let cell_rev = if to_group.is_default { - match field_type { - FieldType::SingleSelect => { - delete_select_option_cell(vec![to_group.group_id.clone()], &field_rev) - } - FieldType::MultiSelect => { - delete_select_option_cell(vec![to_group.group_id.clone()], &field_rev) - } - _ => { - panic!("Unsupported group field type"); - } - } - } else { - match field_type { - FieldType::SingleSelect => { - insert_select_option_cell(vec![to_group.group_id.clone()], &field_rev) - } - FieldType::MultiSelect => { - insert_select_option_cell(vec![to_group.group_id.clone()], &field_rev) - } - FieldType::URL => insert_url_cell(to_group.group_id.clone(), &field_rev), - _ => { - panic!("Unsupported group field type"); - } - } - }; + #[allow(dead_code)] + pub async fn get_multi_select_field(&self) -> Arc { + let field = self + .inner + .field_revs + .iter() + .find(|field_rev| { + let field_type: FieldType = field_rev.ty.into(); + field_type.is_multi_select() + }) + .unwrap() + .clone(); + field + } - let row_id = self.row_at_index(from_group_index, row_index).await.id; - let mut row_changeset = RowChangeset::new(row_id); - row_changeset.cell_by_field_id.insert(field_id, cell_rev); - self.editor.update_row(row_changeset).await.unwrap(); - } - GroupScript::UpdateGroupedCellWithData { - from_group_index, - row_index, - cell_data, - } => { - let from_group = self.group_at_index(from_group_index).await; - let field_id = from_group.field_id; - let field_rev = self.editor.get_field_rev(&field_id).await.unwrap(); - let field_type: FieldType = field_rev.ty.into(); - let cell_rev = match field_type { - FieldType::URL => insert_url_cell(cell_data, &field_rev), - _ => { - panic!("Unsupported group field type"); - } - }; + pub async fn get_single_select_field(&self) -> Arc { + self + .inner + .field_revs + .iter() + .find(|field_rev| { + let field_type: FieldType = field_rev.ty.into(); + field_type.is_single_select() + }) + .unwrap() + .clone() + } - let row_id = self.row_at_index(from_group_index, row_index).await.id; - let mut row_changeset = RowChangeset::new(row_id); - row_changeset.cell_by_field_id.insert(field_id, cell_rev); - self.editor.update_row(row_changeset).await.unwrap(); - } - GroupScript::MoveGroup { - from_group_index, - to_group_index, - } => { - let from_group = self.group_at_index(from_group_index).await; - let to_group = self.group_at_index(to_group_index).await; - let params = MoveGroupParams { - view_id: self.editor.database_id.clone(), - from_group_id: from_group.group_id, - to_group_id: to_group.group_id, - }; - self.editor.move_group(params).await.unwrap(); - // - } - GroupScript::AssertGroup { - group_index, - expected_group: group_pb, - } => { - let group = self.group_at_index(group_index).await; - assert_eq!(group.group_id, group_pb.group_id); - assert_eq!(group.desc, group_pb.desc); - } - GroupScript::UpdateSingleSelectSelectOption { inserted_options } => { - self.edit_single_select_type_option(|type_option| { - for inserted_option in inserted_options { - type_option.insert_option(inserted_option); - } - }) - .await; - } - GroupScript::GroupByField { field_id } => { - self.editor.group_by_field(&field_id).await.unwrap(); - } - } - } + pub async fn edit_single_select_type_option( + &self, + action: impl FnOnce(&mut SingleSelectTypeOptionPB), + ) { + let single_select = self.get_single_select_field().await; + edit_single_select_type_option(&single_select.id, self.editor.clone(), action) + .await + .unwrap(); + } - pub async fn group_at_index(&self, index: usize) -> GroupPB { - let groups = self.editor.load_groups().await.unwrap().items; - groups.get(index).unwrap().clone() - } - - pub async fn row_at_index(&self, group_index: usize, row_index: usize) -> RowPB { - let groups = self.group_at_index(group_index).await; - groups.rows.get(row_index).unwrap().clone() - } - - #[allow(dead_code)] - pub async fn get_multi_select_field(&self) -> Arc { - let field = self - .inner - .field_revs - .iter() - .find(|field_rev| { - let field_type: FieldType = field_rev.ty.into(); - field_type.is_multi_select() - }) - .unwrap() - .clone(); - field - } - - pub async fn get_single_select_field(&self) -> Arc { - self.inner - .field_revs - .iter() - .find(|field_rev| { - let field_type: FieldType = field_rev.ty.into(); - field_type.is_single_select() - }) - .unwrap() - .clone() - } - - pub async fn edit_single_select_type_option(&self, action: impl FnOnce(&mut SingleSelectTypeOptionPB)) { - let single_select = self.get_single_select_field().await; - edit_single_select_type_option(&single_select.id, self.editor.clone(), action) - .await - .unwrap(); - } - - pub async fn get_url_field(&self) -> Arc { - self.inner - .field_revs - .iter() - .find(|field_rev| { - let field_type: FieldType = field_rev.ty.into(); - field_type.is_url() - }) - .unwrap() - .clone() - } + pub async fn get_url_field(&self) -> Arc { + self + .inner + .field_revs + .iter() + .find(|field_rev| { + let field_type: FieldType = field_rev.ty.into(); + field_type.is_url() + }) + .unwrap() + .clone() + } } impl std::ops::Deref for DatabaseGroupTest { - type Target = DatabaseEditorTest; + type Target = DatabaseEditorTest; - fn deref(&self) -> &Self::Target { - &self.inner - } + fn deref(&self) -> &Self::Target { + &self.inner + } } impl std::ops::DerefMut for DatabaseGroupTest { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } diff --git a/frontend/rust-lib/flowy-database/tests/grid/group_test/test.rs b/frontend/rust-lib/flowy-database/tests/grid/group_test/test.rs index 7c55b82e8a..42984f47f5 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/group_test/test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/group_test/test.rs @@ -5,484 +5,484 @@ use flowy_database::services::field::SelectOptionPB; #[tokio::test] async fn group_init_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![ - AssertGroupCount(4), - AssertGroupRowCount { - group_index: 1, - row_count: 2, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 2, - }, - AssertGroupRowCount { - group_index: 3, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 0, - row_count: 0, - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let scripts = vec![ + AssertGroupCount(4), + AssertGroupRowCount { + group_index: 1, + row_count: 2, + }, + AssertGroupRowCount { + group_index: 2, + row_count: 2, + }, + AssertGroupRowCount { + group_index: 3, + row_count: 1, + }, + AssertGroupRowCount { + group_index: 0, + row_count: 0, + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_move_row_test() { - let mut test = DatabaseGroupTest::new().await; - let group = test.group_at_index(1).await; - let scripts = vec![ - // Move the row at 0 in group0 to group1 at 1 - MoveRow { - from_group_index: 1, - from_row_index: 0, - to_group_index: 1, - to_row_index: 1, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 2, - }, - AssertRow { - group_index: 1, - row_index: 1, - row: group.rows.get(0).unwrap().clone(), - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let group = test.group_at_index(1).await; + let scripts = vec![ + // Move the row at 0 in group0 to group1 at 1 + MoveRow { + from_group_index: 1, + from_row_index: 0, + to_group_index: 1, + to_row_index: 1, + }, + AssertGroupRowCount { + group_index: 1, + row_count: 2, + }, + AssertRow { + group_index: 1, + row_index: 1, + row: group.rows.get(0).unwrap().clone(), + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_move_row_to_other_group_test() { - let mut test = DatabaseGroupTest::new().await; - let group = test.group_at_index(1).await; - let scripts = vec![ - MoveRow { - from_group_index: 1, - from_row_index: 0, - to_group_index: 2, - to_row_index: 1, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 3, - }, - AssertRow { - group_index: 2, - row_index: 1, - row: group.rows.get(0).unwrap().clone(), - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let group = test.group_at_index(1).await; + let scripts = vec![ + MoveRow { + from_group_index: 1, + from_row_index: 0, + to_group_index: 2, + to_row_index: 1, + }, + AssertGroupRowCount { + group_index: 1, + row_count: 1, + }, + AssertGroupRowCount { + group_index: 2, + row_count: 3, + }, + AssertRow { + group_index: 2, + row_index: 1, + row: group.rows.get(0).unwrap().clone(), + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_move_two_row_to_other_group_test() { - let mut test = DatabaseGroupTest::new().await; - let group_1 = test.group_at_index(1).await; - let scripts = vec![ - // Move row at index 0 from group 1 to group 2 at index 1 - MoveRow { - from_group_index: 1, - from_row_index: 0, - to_group_index: 2, - to_row_index: 1, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 3, - }, - AssertRow { - group_index: 2, - row_index: 1, - row: group_1.rows.get(0).unwrap().clone(), - }, - ]; - test.run_scripts(scripts).await; - - let group_1 = test.group_at_index(1).await; + let mut test = DatabaseGroupTest::new().await; + let group_1 = test.group_at_index(1).await; + let scripts = vec![ // Move row at index 0 from group 1 to group 2 at index 1 - let scripts = vec![ - MoveRow { - from_group_index: 1, - from_row_index: 0, - to_group_index: 2, - to_row_index: 1, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 0, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 4, - }, - AssertRow { - group_index: 2, - row_index: 1, - row: group_1.rows.get(0).unwrap().clone(), - }, - ]; - test.run_scripts(scripts).await; + MoveRow { + from_group_index: 1, + from_row_index: 0, + to_group_index: 2, + to_row_index: 1, + }, + AssertGroupRowCount { + group_index: 1, + row_count: 1, + }, + AssertGroupRowCount { + group_index: 2, + row_count: 3, + }, + AssertRow { + group_index: 2, + row_index: 1, + row: group_1.rows.get(0).unwrap().clone(), + }, + ]; + test.run_scripts(scripts).await; + + let group_1 = test.group_at_index(1).await; + // Move row at index 0 from group 1 to group 2 at index 1 + let scripts = vec![ + MoveRow { + from_group_index: 1, + from_row_index: 0, + to_group_index: 2, + to_row_index: 1, + }, + AssertGroupRowCount { + group_index: 1, + row_count: 0, + }, + AssertGroupRowCount { + group_index: 2, + row_count: 4, + }, + AssertRow { + group_index: 2, + row_index: 1, + row: group_1.rows.get(0).unwrap().clone(), + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_move_row_to_other_group_and_reorder_from_up_to_down_test() { - let mut test = DatabaseGroupTest::new().await; - let group_1 = test.group_at_index(1).await; - let group_2 = test.group_at_index(2).await; - let scripts = vec![ - MoveRow { - from_group_index: 1, - from_row_index: 0, - to_group_index: 2, - to_row_index: 1, - }, - AssertRow { - group_index: 2, - row_index: 1, - row: group_1.rows.get(0).unwrap().clone(), - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let group_1 = test.group_at_index(1).await; + let group_2 = test.group_at_index(2).await; + let scripts = vec![ + MoveRow { + from_group_index: 1, + from_row_index: 0, + to_group_index: 2, + to_row_index: 1, + }, + AssertRow { + group_index: 2, + row_index: 1, + row: group_1.rows.get(0).unwrap().clone(), + }, + ]; + test.run_scripts(scripts).await; - let scripts = vec![ - MoveRow { - from_group_index: 2, - from_row_index: 0, - to_group_index: 2, - to_row_index: 2, - }, - AssertRow { - group_index: 2, - row_index: 2, - row: group_2.rows.get(0).unwrap().clone(), - }, - ]; - test.run_scripts(scripts).await; + let scripts = vec![ + MoveRow { + from_group_index: 2, + from_row_index: 0, + to_group_index: 2, + to_row_index: 2, + }, + AssertRow { + group_index: 2, + row_index: 2, + row: group_2.rows.get(0).unwrap().clone(), + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_move_row_to_other_group_and_reorder_from_bottom_to_up_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![MoveRow { - from_group_index: 1, - from_row_index: 0, - to_group_index: 2, - to_row_index: 1, - }]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let scripts = vec![MoveRow { + from_group_index: 1, + from_row_index: 0, + to_group_index: 2, + to_row_index: 1, + }]; + test.run_scripts(scripts).await; - let group = test.group_at_index(2).await; - let scripts = vec![ - AssertGroupRowCount { - group_index: 2, - row_count: 3, - }, - MoveRow { - from_group_index: 2, - from_row_index: 2, - to_group_index: 2, - to_row_index: 0, - }, - AssertRow { - group_index: 2, - row_index: 0, - row: group.rows.get(2).unwrap().clone(), - }, - ]; - test.run_scripts(scripts).await; + let group = test.group_at_index(2).await; + let scripts = vec![ + AssertGroupRowCount { + group_index: 2, + row_count: 3, + }, + MoveRow { + from_group_index: 2, + from_row_index: 2, + to_group_index: 2, + to_row_index: 0, + }, + AssertRow { + group_index: 2, + row_index: 0, + row: group.rows.get(2).unwrap().clone(), + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_create_row_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![ - CreateRow { group_index: 1 }, - AssertGroupRowCount { - group_index: 1, - row_count: 3, - }, - CreateRow { group_index: 2 }, - CreateRow { group_index: 2 }, - AssertGroupRowCount { - group_index: 2, - row_count: 4, - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let scripts = vec![ + CreateRow { group_index: 1 }, + AssertGroupRowCount { + group_index: 1, + row_count: 3, + }, + CreateRow { group_index: 2 }, + CreateRow { group_index: 2 }, + AssertGroupRowCount { + group_index: 2, + row_count: 4, + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_delete_row_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![ - DeleteRow { - group_index: 1, - row_index: 0, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let scripts = vec![ + DeleteRow { + group_index: 1, + row_index: 0, + }, + AssertGroupRowCount { + group_index: 1, + row_count: 1, + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_delete_all_row_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![ - DeleteRow { - group_index: 1, - row_index: 0, - }, - DeleteRow { - group_index: 1, - row_index: 0, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 0, - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let scripts = vec![ + DeleteRow { + group_index: 1, + row_index: 0, + }, + DeleteRow { + group_index: 1, + row_index: 0, + }, + AssertGroupRowCount { + group_index: 1, + row_count: 0, + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_update_row_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![ - // Update the row at 0 in group0 by setting the row's group field data - UpdateGroupedCell { - from_group_index: 1, - row_index: 0, - to_group_index: 2, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 3, - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let scripts = vec![ + // Update the row at 0 in group0 by setting the row's group field data + UpdateGroupedCell { + from_group_index: 1, + row_index: 0, + to_group_index: 2, + }, + AssertGroupRowCount { + group_index: 1, + row_count: 1, + }, + AssertGroupRowCount { + group_index: 2, + row_count: 3, + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_reorder_group_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![ - // Update the row at 0 in group0 by setting the row's group field data - UpdateGroupedCell { - from_group_index: 1, - row_index: 0, - to_group_index: 2, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 3, - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let scripts = vec![ + // Update the row at 0 in group0 by setting the row's group field data + UpdateGroupedCell { + from_group_index: 1, + row_index: 0, + to_group_index: 2, + }, + AssertGroupRowCount { + group_index: 1, + row_count: 1, + }, + AssertGroupRowCount { + group_index: 2, + row_count: 3, + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_move_to_default_group_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![ - UpdateGroupedCell { - from_group_index: 1, - row_index: 0, - to_group_index: 0, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 0, - row_count: 1, - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let scripts = vec![ + UpdateGroupedCell { + from_group_index: 1, + row_index: 0, + to_group_index: 0, + }, + AssertGroupRowCount { + group_index: 1, + row_count: 1, + }, + AssertGroupRowCount { + group_index: 0, + row_count: 1, + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_move_from_default_group_test() { - let mut test = DatabaseGroupTest::new().await; - // Move one row from group 1 to group 0 - let scripts = vec![ - UpdateGroupedCell { - from_group_index: 1, - row_index: 0, - to_group_index: 0, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 0, - row_count: 1, - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + // Move one row from group 1 to group 0 + let scripts = vec![ + UpdateGroupedCell { + from_group_index: 1, + row_index: 0, + to_group_index: 0, + }, + AssertGroupRowCount { + group_index: 1, + row_count: 1, + }, + AssertGroupRowCount { + group_index: 0, + row_count: 1, + }, + ]; + test.run_scripts(scripts).await; - // Move one row from group 0 to group 1 - let scripts = vec![ - UpdateGroupedCell { - from_group_index: 0, - row_index: 0, - to_group_index: 1, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 2, - }, - AssertGroupRowCount { - group_index: 0, - row_count: 0, - }, - ]; - test.run_scripts(scripts).await; + // Move one row from group 0 to group 1 + let scripts = vec![ + UpdateGroupedCell { + from_group_index: 0, + row_index: 0, + to_group_index: 1, + }, + AssertGroupRowCount { + group_index: 1, + row_count: 2, + }, + AssertGroupRowCount { + group_index: 0, + row_count: 0, + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_move_group_test() { - let mut test = DatabaseGroupTest::new().await; - let group_0 = test.group_at_index(0).await; - let group_1 = test.group_at_index(1).await; - let scripts = vec![ - MoveGroup { - from_group_index: 0, - to_group_index: 1, - }, - AssertGroupRowCount { - group_index: 0, - row_count: 2, - }, - AssertGroup { - group_index: 0, - expected_group: group_1, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 0, - }, - AssertGroup { - group_index: 1, - expected_group: group_0, - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let group_0 = test.group_at_index(0).await; + let group_1 = test.group_at_index(1).await; + let scripts = vec![ + MoveGroup { + from_group_index: 0, + to_group_index: 1, + }, + AssertGroupRowCount { + group_index: 0, + row_count: 2, + }, + AssertGroup { + group_index: 0, + expected_group: group_1, + }, + AssertGroupRowCount { + group_index: 1, + row_count: 0, + }, + AssertGroup { + group_index: 1, + expected_group: group_0, + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_move_group_row_after_move_group_test() { - let mut test = DatabaseGroupTest::new().await; - let group_1 = test.group_at_index(1).await; - let group_2 = test.group_at_index(2).await; - let scripts = vec![ - MoveGroup { - from_group_index: 1, - to_group_index: 2, - }, - AssertGroup { - group_index: 1, - expected_group: group_2, - }, - AssertGroup { - group_index: 2, - expected_group: group_1, - }, - MoveRow { - from_group_index: 1, - from_row_index: 0, - to_group_index: 2, - to_row_index: 0, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 3, - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let group_1 = test.group_at_index(1).await; + let group_2 = test.group_at_index(2).await; + let scripts = vec![ + MoveGroup { + from_group_index: 1, + to_group_index: 2, + }, + AssertGroup { + group_index: 1, + expected_group: group_2, + }, + AssertGroup { + group_index: 2, + expected_group: group_1, + }, + MoveRow { + from_group_index: 1, + from_row_index: 0, + to_group_index: 2, + to_row_index: 0, + }, + AssertGroupRowCount { + group_index: 1, + row_count: 1, + }, + AssertGroupRowCount { + group_index: 2, + row_count: 3, + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_move_group_to_default_group_pos_test() { - let mut test = DatabaseGroupTest::new().await; - let group_0 = test.group_at_index(0).await; - let group_3 = test.group_at_index(3).await; - let scripts = vec![ - MoveGroup { - from_group_index: 3, - to_group_index: 0, - }, - AssertGroup { - group_index: 0, - expected_group: group_3, - }, - AssertGroup { - group_index: 1, - expected_group: group_0, - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let group_0 = test.group_at_index(0).await; + let group_3 = test.group_at_index(3).await; + let scripts = vec![ + MoveGroup { + from_group_index: 3, + to_group_index: 0, + }, + AssertGroup { + group_index: 0, + expected_group: group_3, + }, + AssertGroup { + group_index: 1, + expected_group: group_0, + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_insert_single_select_option_test() { - let mut test = DatabaseGroupTest::new().await; - let new_option_name = "New option"; - let scripts = vec![ - AssertGroupCount(4), - UpdateSingleSelectSelectOption { - inserted_options: vec![SelectOptionPB::new(new_option_name)], - }, - AssertGroupCount(5), - ]; - test.run_scripts(scripts).await; - let new_group = test.group_at_index(4).await; - assert_eq!(new_group.desc, new_option_name); + let mut test = DatabaseGroupTest::new().await; + let new_option_name = "New option"; + let scripts = vec![ + AssertGroupCount(4), + UpdateSingleSelectSelectOption { + inserted_options: vec![SelectOptionPB::new(new_option_name)], + }, + AssertGroupCount(5), + ]; + test.run_scripts(scripts).await; + let new_group = test.group_at_index(4).await; + assert_eq!(new_group.desc, new_option_name); } #[tokio::test] async fn group_group_by_other_field() { - let mut test = DatabaseGroupTest::new().await; - let multi_select_field = test.get_multi_select_field().await; - let scripts = vec![ - GroupByField { - field_id: multi_select_field.id.clone(), - }, - AssertGroupRowCount { - group_index: 1, - row_count: 3, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 2, - }, - AssertGroupCount(4), - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let multi_select_field = test.get_multi_select_field().await; + let scripts = vec![ + GroupByField { + field_id: multi_select_field.id.clone(), + }, + AssertGroupRowCount { + group_index: 1, + row_count: 3, + }, + AssertGroupRowCount { + group_index: 2, + row_count: 2, + }, + AssertGroupCount(4), + ]; + test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-database/tests/grid/group_test/url_group_test.rs b/frontend/rust-lib/flowy-database/tests/grid/group_test/url_group_test.rs index 5865c1f999..5be04108ab 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/group_test/url_group_test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/group_test/url_group_test.rs @@ -3,146 +3,146 @@ use crate::grid::group_test::script::GroupScript::*; #[tokio::test] async fn group_group_by_url() { - let mut test = DatabaseGroupTest::new().await; - let url_field = test.get_url_field().await; - let scripts = vec![ - GroupByField { - field_id: url_field.id.clone(), - }, - // no status group - AssertGroupRowCount { - group_index: 0, - row_count: 2, - }, - // https://appflowy.io - AssertGroupRowCount { - group_index: 1, - row_count: 2, - }, - // https://github.com/AppFlowy-IO/AppFlowy - AssertGroupRowCount { - group_index: 2, - row_count: 1, - }, - AssertGroupCount(3), - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let url_field = test.get_url_field().await; + let scripts = vec![ + GroupByField { + field_id: url_field.id.clone(), + }, + // no status group + AssertGroupRowCount { + group_index: 0, + row_count: 2, + }, + // https://appflowy.io + AssertGroupRowCount { + group_index: 1, + row_count: 2, + }, + // https://github.com/AppFlowy-IO/AppFlowy + AssertGroupRowCount { + group_index: 2, + row_count: 1, + }, + AssertGroupCount(3), + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_alter_url_to_another_group_url_test() { - let mut test = DatabaseGroupTest::new().await; - let url_field = test.get_url_field().await; - let scripts = vec![ - GroupByField { - field_id: url_field.id.clone(), - }, - // no status group - AssertGroupRowCount { - group_index: 0, - row_count: 2, - }, - // https://appflowy.io - AssertGroupRowCount { - group_index: 1, - row_count: 2, - }, - // https://github.com/AppFlowy-IO/AppFlowy - AssertGroupRowCount { - group_index: 2, - row_count: 1, - }, - // When moving the last row from 2nd group to 1nd group, the 2nd group will be removed - UpdateGroupedCell { - from_group_index: 2, - row_index: 0, - to_group_index: 1, - }, - AssertGroupCount(2), - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let url_field = test.get_url_field().await; + let scripts = vec![ + GroupByField { + field_id: url_field.id.clone(), + }, + // no status group + AssertGroupRowCount { + group_index: 0, + row_count: 2, + }, + // https://appflowy.io + AssertGroupRowCount { + group_index: 1, + row_count: 2, + }, + // https://github.com/AppFlowy-IO/AppFlowy + AssertGroupRowCount { + group_index: 2, + row_count: 1, + }, + // When moving the last row from 2nd group to 1nd group, the 2nd group will be removed + UpdateGroupedCell { + from_group_index: 2, + row_index: 0, + to_group_index: 1, + }, + AssertGroupCount(2), + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_alter_url_to_new_url_test() { - let mut test = DatabaseGroupTest::new().await; - let url_field = test.get_url_field().await; - let scripts = vec![ - GroupByField { - field_id: url_field.id.clone(), - }, - // When moving the last row from 2nd group to 1nd group, the 2nd group will be removed - UpdateGroupedCellWithData { - from_group_index: 0, - row_index: 0, - cell_data: "https://github.com/AppFlowy-IO".to_string(), - }, - // no status group - AssertGroupRowCount { - group_index: 0, - row_count: 1, - }, - // https://appflowy.io - AssertGroupRowCount { - group_index: 1, - row_count: 2, - }, - // https://github.com/AppFlowy-IO/AppFlowy - AssertGroupRowCount { - group_index: 2, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 3, - row_count: 1, - }, - AssertGroupCount(4), - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let url_field = test.get_url_field().await; + let scripts = vec![ + GroupByField { + field_id: url_field.id.clone(), + }, + // When moving the last row from 2nd group to 1nd group, the 2nd group will be removed + UpdateGroupedCellWithData { + from_group_index: 0, + row_index: 0, + cell_data: "https://github.com/AppFlowy-IO".to_string(), + }, + // no status group + AssertGroupRowCount { + group_index: 0, + row_count: 1, + }, + // https://appflowy.io + AssertGroupRowCount { + group_index: 1, + row_count: 2, + }, + // https://github.com/AppFlowy-IO/AppFlowy + AssertGroupRowCount { + group_index: 2, + row_count: 1, + }, + AssertGroupRowCount { + group_index: 3, + row_count: 1, + }, + AssertGroupCount(4), + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn group_move_url_group_row_test() { - let mut test = DatabaseGroupTest::new().await; - let url_field = test.get_url_field().await; - let scripts = vec![ - GroupByField { - field_id: url_field.id.clone(), - }, - // no status group - AssertGroupRowCount { - group_index: 0, - row_count: 2, - }, - // https://appflowy.io - AssertGroupRowCount { - group_index: 1, - row_count: 2, - }, - // https://github.com/AppFlowy-IO/AppFlowy - AssertGroupRowCount { - group_index: 2, - row_count: 1, - }, - AssertGroupCount(3), - MoveRow { - from_group_index: 0, - from_row_index: 0, - to_group_index: 1, - to_row_index: 0, - }, - AssertGroupRowCount { - group_index: 0, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 3, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 1, - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseGroupTest::new().await; + let url_field = test.get_url_field().await; + let scripts = vec![ + GroupByField { + field_id: url_field.id.clone(), + }, + // no status group + AssertGroupRowCount { + group_index: 0, + row_count: 2, + }, + // https://appflowy.io + AssertGroupRowCount { + group_index: 1, + row_count: 2, + }, + // https://github.com/AppFlowy-IO/AppFlowy + AssertGroupRowCount { + group_index: 2, + row_count: 1, + }, + AssertGroupCount(3), + MoveRow { + from_group_index: 0, + from_row_index: 0, + to_group_index: 1, + to_row_index: 0, + }, + AssertGroupRowCount { + group_index: 0, + row_count: 1, + }, + AssertGroupRowCount { + group_index: 1, + row_count: 3, + }, + AssertGroupRowCount { + group_index: 2, + row_count: 1, + }, + ]; + test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-database/tests/grid/mock_data/board_mock_data.rs b/frontend/rust-lib/flowy-database/tests/grid/mock_data/board_mock_data.rs index 015cc01e86..cceca19fa2 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/mock_data/board_mock_data.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/mock_data/board_mock_data.rs @@ -3,7 +3,7 @@ // #![allow(unused_imports)] use crate::grid::block_test::util::GridRowTestBuilder; use crate::grid::mock_data::{ - COMPLETED, FACEBOOK, FIRST_THING, GOOGLE, PAUSED, PLANNED, SECOND_THING, THIRD_THING, TWITTER, + COMPLETED, FACEBOOK, FIRST_THING, GOOGLE, PAUSED, PLANNED, SECOND_THING, THIRD_THING, TWITTER, }; use flowy_client_sync::client_database::DatabaseBuilder; @@ -18,174 +18,191 @@ use strum::IntoEnumIterator; // Kanban board unit test mock data pub fn make_test_board() -> BuildDatabaseContext { - let mut grid_builder = DatabaseBuilder::new(); - // Iterate through the FieldType to create the corresponding Field. - for field_type in FieldType::iter() { - let field_type: FieldType = field_type; + let mut grid_builder = DatabaseBuilder::new(); + // Iterate through the FieldType to create the corresponding Field. + for field_type in FieldType::iter() { + let field_type: FieldType = field_type; - // The - match field_type { - FieldType::RichText => { - let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Name") - .visibility(true) - .primary(true) - .build(); - grid_builder.add_field(text_field); - } - FieldType::Number => { - // Number - let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); - let number_field = FieldBuilder::new(number).name("Price").visibility(true).build(); - grid_builder.add_field(number_field); - } - FieldType::DateTime => { - // Date - let date = DateTypeOptionBuilder::default() - .date_format(DateFormat::US) - .time_format(TimeFormat::TwentyFourHour); - let date_field = FieldBuilder::new(date).name("Time").visibility(true).build(); - grid_builder.add_field(date_field); - } + // The + match field_type { + FieldType::RichText => { + let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) + .name("Name") + .visibility(true) + .primary(true) + .build(); + grid_builder.add_field(text_field); + }, + FieldType::Number => { + // Number + let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); + let number_field = FieldBuilder::new(number) + .name("Price") + .visibility(true) + .build(); + grid_builder.add_field(number_field); + }, + FieldType::DateTime => { + // Date + let date = DateTypeOptionBuilder::default() + .date_format(DateFormat::US) + .time_format(TimeFormat::TwentyFourHour); + let date_field = FieldBuilder::new(date) + .name("Time") + .visibility(true) + .build(); + grid_builder.add_field(date_field); + }, + FieldType::SingleSelect => { + // Single Select + let single_select = SingleSelectTypeOptionBuilder::default() + .add_option(SelectOptionPB::new(COMPLETED)) + .add_option(SelectOptionPB::new(PLANNED)) + .add_option(SelectOptionPB::new(PAUSED)); + let single_select_field = FieldBuilder::new(single_select) + .name("Status") + .visibility(true) + .build(); + grid_builder.add_field(single_select_field); + }, + FieldType::MultiSelect => { + // MultiSelect + let multi_select = MultiSelectTypeOptionBuilder::default() + .add_option(SelectOptionPB::new(GOOGLE)) + .add_option(SelectOptionPB::new(FACEBOOK)) + .add_option(SelectOptionPB::new(TWITTER)); + let multi_select_field = FieldBuilder::new(multi_select) + .name("Platform") + .visibility(true) + .build(); + grid_builder.add_field(multi_select_field); + }, + FieldType::Checkbox => { + // Checkbox + let checkbox = CheckboxTypeOptionBuilder::default(); + let checkbox_field = FieldBuilder::new(checkbox) + .name("is urgent") + .visibility(true) + .build(); + grid_builder.add_field(checkbox_field); + }, + FieldType::URL => { + // URL + let url = URLTypeOptionBuilder::default(); + let url_field = FieldBuilder::new(url).name("link").visibility(true).build(); + grid_builder.add_field(url_field); + }, + FieldType::Checklist => { + let checklist = ChecklistTypeOptionBuilder::default() + .add_option(SelectOptionPB::new(FIRST_THING)) + .add_option(SelectOptionPB::new(SECOND_THING)) + .add_option(SelectOptionPB::new(THIRD_THING)); + let checklist_field = FieldBuilder::new(checklist) + .name("TODO") + .visibility(true) + .build(); + grid_builder.add_field(checklist_field); + }, + } + } + + // We have many assumptions base on the number of the rows, so do not change the number of the loop. + for i in 0..5 { + let block_id = grid_builder.block_id().to_owned(); + let field_revs = grid_builder.field_revs(); + let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs); + match i { + 0 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("A"), + FieldType::Number => row_builder.insert_number_cell("1"), + // 1647251762 => Mar 14,2022 + FieldType::DateTime => row_builder.insert_date_cell("1647251762"), FieldType::SingleSelect => { - // Single Select - let single_select = SingleSelectTypeOptionBuilder::default() - .add_option(SelectOptionPB::new(COMPLETED)) - .add_option(SelectOptionPB::new(PLANNED)) - .add_option(SelectOptionPB::new(PAUSED)); - let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build(); - grid_builder.add_field(single_select_field); - } + row_builder.insert_single_select_cell(|mut options| options.remove(0)) + }, + FieldType::MultiSelect => row_builder + .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]), + FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), + FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"), + _ => "".to_owned(), + }; + } + }, + 1 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("B"), + FieldType::Number => row_builder.insert_number_cell("2"), + // 1647251762 => Mar 14,2022 + FieldType::DateTime => row_builder.insert_date_cell("1647251762"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(0)) + }, + FieldType::MultiSelect => row_builder + .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]), + FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), + _ => "".to_owned(), + }; + } + }, + 2 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("C"), + FieldType::Number => row_builder.insert_number_cell("3"), + // 1647251762 => Mar 14,2022 + FieldType::DateTime => row_builder.insert_date_cell("1647251762"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(1)) + }, FieldType::MultiSelect => { - // MultiSelect - let multi_select = MultiSelectTypeOptionBuilder::default() - .add_option(SelectOptionPB::new(GOOGLE)) - .add_option(SelectOptionPB::new(FACEBOOK)) - .add_option(SelectOptionPB::new(TWITTER)); - let multi_select_field = FieldBuilder::new(multi_select) - .name("Platform") - .visibility(true) - .build(); - grid_builder.add_field(multi_select_field); - } - FieldType::Checkbox => { - // Checkbox - let checkbox = CheckboxTypeOptionBuilder::default(); - let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build(); - grid_builder.add_field(checkbox_field); - } + row_builder.insert_multi_select_cell(|mut options| vec![options.remove(0)]) + }, + FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), FieldType::URL => { - // URL - let url = URLTypeOptionBuilder::default(); - let url_field = FieldBuilder::new(url).name("link").visibility(true).build(); - grid_builder.add_field(url_field); - } - FieldType::Checklist => { - let checklist = ChecklistTypeOptionBuilder::default() - .add_option(SelectOptionPB::new(FIRST_THING)) - .add_option(SelectOptionPB::new(SECOND_THING)) - .add_option(SelectOptionPB::new(THIRD_THING)); - let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build(); - grid_builder.add_field(checklist_field); - } + row_builder.insert_url_cell("https://github.com/AppFlowy-IO/AppFlowy") + }, + _ => "".to_owned(), + }; } + }, + 3 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("DA"), + FieldType::Number => row_builder.insert_number_cell("4"), + FieldType::DateTime => row_builder.insert_date_cell("1668704685"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(1)) + }, + FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), + FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"), + _ => "".to_owned(), + }; + } + }, + 4 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("AE"), + FieldType::Number => row_builder.insert_number_cell(""), + FieldType::DateTime => row_builder.insert_date_cell("1668359085"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(2)) + }, + + FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), + _ => "".to_owned(), + }; + } + }, + _ => {}, } - // We have many assumptions base on the number of the rows, so do not change the number of the loop. - for i in 0..5 { - let block_id = grid_builder.block_id().to_owned(); - let field_revs = grid_builder.field_revs(); - let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs); - match i { - 0 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("A"), - FieldType::Number => row_builder.insert_number_cell("1"), - // 1647251762 => Mar 14,2022 - FieldType::DateTime => row_builder.insert_date_cell("1647251762"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(0)) - } - FieldType::MultiSelect => row_builder - .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]), - FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), - FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"), - _ => "".to_owned(), - }; - } - } - 1 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("B"), - FieldType::Number => row_builder.insert_number_cell("2"), - // 1647251762 => Mar 14,2022 - FieldType::DateTime => row_builder.insert_date_cell("1647251762"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(0)) - } - FieldType::MultiSelect => row_builder - .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]), - FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), - _ => "".to_owned(), - }; - } - } - 2 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("C"), - FieldType::Number => row_builder.insert_number_cell("3"), - // 1647251762 => Mar 14,2022 - FieldType::DateTime => row_builder.insert_date_cell("1647251762"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(1)) - } - FieldType::MultiSelect => { - row_builder.insert_multi_select_cell(|mut options| vec![options.remove(0)]) - } - FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), - FieldType::URL => row_builder.insert_url_cell("https://github.com/AppFlowy-IO/AppFlowy"), - _ => "".to_owned(), - }; - } - } - 3 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("DA"), - FieldType::Number => row_builder.insert_number_cell("4"), - FieldType::DateTime => row_builder.insert_date_cell("1668704685"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(1)) - } - FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), - FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"), - _ => "".to_owned(), - }; - } - } - 4 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("AE"), - FieldType::Number => row_builder.insert_number_cell(""), - FieldType::DateTime => row_builder.insert_date_cell("1668359085"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(2)) - } - - FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), - _ => "".to_owned(), - }; - } - } - _ => {} - } - - let row_rev = row_builder.build(); - grid_builder.add_row(row_rev); - } - grid_builder.build() + let row_rev = row_builder.build(); + grid_builder.add_row(row_rev); + } + grid_builder.build() } diff --git a/frontend/rust-lib/flowy-database/tests/grid/mock_data/calendar_mock_data.rs b/frontend/rust-lib/flowy-database/tests/grid/mock_data/calendar_mock_data.rs index 9f28ec4f20..a354dc582b 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/mock_data/calendar_mock_data.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/mock_data/calendar_mock_data.rs @@ -2,5 +2,5 @@ use grid_model::BuildDatabaseContext; // Calendar unit test mock data pub fn make_test_calendar() -> BuildDatabaseContext { - todo!() + todo!() } diff --git a/frontend/rust-lib/flowy-database/tests/grid/mock_data/grid_mock_data.rs b/frontend/rust-lib/flowy-database/tests/grid/mock_data/grid_mock_data.rs index 882c3109c5..44d4af52ef 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/mock_data/grid_mock_data.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/mock_data/grid_mock_data.rs @@ -3,7 +3,7 @@ // #![allow(unused_imports)] use crate::grid::block_test::util::GridRowTestBuilder; use crate::grid::mock_data::{ - COMPLETED, FACEBOOK, FIRST_THING, GOOGLE, PAUSED, PLANNED, SECOND_THING, THIRD_THING, TWITTER, + COMPLETED, FACEBOOK, FIRST_THING, GOOGLE, PAUSED, PLANNED, SECOND_THING, THIRD_THING, TWITTER, }; use flowy_client_sync::client_database::DatabaseBuilder; @@ -17,177 +17,194 @@ use grid_model::*; use strum::IntoEnumIterator; pub fn make_test_grid() -> BuildDatabaseContext { - let mut grid_builder = DatabaseBuilder::new(); - // Iterate through the FieldType to create the corresponding Field. - for field_type in FieldType::iter() { - let field_type: FieldType = field_type; + let mut grid_builder = DatabaseBuilder::new(); + // Iterate through the FieldType to create the corresponding Field. + for field_type in FieldType::iter() { + let field_type: FieldType = field_type; - // The - match field_type { - FieldType::RichText => { - let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Name") - .visibility(true) - .primary(true) - .build(); - grid_builder.add_field(text_field); - } - FieldType::Number => { - // Number - let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); - let number_field = FieldBuilder::new(number).name("Price").visibility(true).build(); - grid_builder.add_field(number_field); - } - FieldType::DateTime => { - // Date - let date = DateTypeOptionBuilder::default() - .date_format(DateFormat::US) - .time_format(TimeFormat::TwentyFourHour); - let date_field = FieldBuilder::new(date).name("Time").visibility(true).build(); - grid_builder.add_field(date_field); - } - FieldType::SingleSelect => { - // Single Select - let single_select = SingleSelectTypeOptionBuilder::default() - .add_option(SelectOptionPB::new(COMPLETED)) - .add_option(SelectOptionPB::new(PLANNED)) - .add_option(SelectOptionPB::new(PAUSED)); - let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build(); - grid_builder.add_field(single_select_field); - } - FieldType::MultiSelect => { - // MultiSelect - let multi_select = MultiSelectTypeOptionBuilder::default() - .add_option(SelectOptionPB::new(GOOGLE)) - .add_option(SelectOptionPB::new(FACEBOOK)) - .add_option(SelectOptionPB::new(TWITTER)); - let multi_select_field = FieldBuilder::new(multi_select) - .name("Platform") - .visibility(true) - .build(); - grid_builder.add_field(multi_select_field); - } - FieldType::Checkbox => { - // Checkbox - let checkbox = CheckboxTypeOptionBuilder::default(); - let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build(); - grid_builder.add_field(checkbox_field); - } + // The + match field_type { + FieldType::RichText => { + let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) + .name("Name") + .visibility(true) + .primary(true) + .build(); + grid_builder.add_field(text_field); + }, + FieldType::Number => { + // Number + let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); + let number_field = FieldBuilder::new(number) + .name("Price") + .visibility(true) + .build(); + grid_builder.add_field(number_field); + }, + FieldType::DateTime => { + // Date + let date = DateTypeOptionBuilder::default() + .date_format(DateFormat::US) + .time_format(TimeFormat::TwentyFourHour); + let date_field = FieldBuilder::new(date) + .name("Time") + .visibility(true) + .build(); + grid_builder.add_field(date_field); + }, + FieldType::SingleSelect => { + // Single Select + let single_select = SingleSelectTypeOptionBuilder::default() + .add_option(SelectOptionPB::new(COMPLETED)) + .add_option(SelectOptionPB::new(PLANNED)) + .add_option(SelectOptionPB::new(PAUSED)); + let single_select_field = FieldBuilder::new(single_select) + .name("Status") + .visibility(true) + .build(); + grid_builder.add_field(single_select_field); + }, + FieldType::MultiSelect => { + // MultiSelect + let multi_select = MultiSelectTypeOptionBuilder::default() + .add_option(SelectOptionPB::new(GOOGLE)) + .add_option(SelectOptionPB::new(FACEBOOK)) + .add_option(SelectOptionPB::new(TWITTER)); + let multi_select_field = FieldBuilder::new(multi_select) + .name("Platform") + .visibility(true) + .build(); + grid_builder.add_field(multi_select_field); + }, + FieldType::Checkbox => { + // Checkbox + let checkbox = CheckboxTypeOptionBuilder::default(); + let checkbox_field = FieldBuilder::new(checkbox) + .name("is urgent") + .visibility(true) + .build(); + grid_builder.add_field(checkbox_field); + }, + FieldType::URL => { + // URL + let url = URLTypeOptionBuilder::default(); + let url_field = FieldBuilder::new(url).name("link").visibility(true).build(); + grid_builder.add_field(url_field); + }, + FieldType::Checklist => { + let checklist = ChecklistTypeOptionBuilder::default() + .add_option(SelectOptionPB::new(FIRST_THING)) + .add_option(SelectOptionPB::new(SECOND_THING)) + .add_option(SelectOptionPB::new(THIRD_THING)); + let checklist_field = FieldBuilder::new(checklist) + .name("TODO") + .visibility(true) + .build(); + grid_builder.add_field(checklist_field); + }, + } + } + + for i in 0..6 { + let block_id = grid_builder.block_id().to_owned(); + let field_revs = grid_builder.field_revs(); + let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs); + match i { + 0 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("A"), + FieldType::Number => row_builder.insert_number_cell("1"), + FieldType::DateTime => row_builder.insert_date_cell("1647251762"), + FieldType::MultiSelect => row_builder + .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]), + FieldType::Checklist => row_builder.insert_checklist_cell(|options| options), + FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), FieldType::URL => { - // URL - let url = URLTypeOptionBuilder::default(); - let url_field = FieldBuilder::new(url).name("link").visibility(true).build(); - grid_builder.add_field(url_field); - } - FieldType::Checklist => { - let checklist = ChecklistTypeOptionBuilder::default() - .add_option(SelectOptionPB::new(FIRST_THING)) - .add_option(SelectOptionPB::new(SECOND_THING)) - .add_option(SelectOptionPB::new(THIRD_THING)); - let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build(); - grid_builder.add_field(checklist_field); - } + row_builder.insert_url_cell("AppFlowy website - https://www.appflowy.io") + }, + _ => "".to_owned(), + }; } + }, + 1 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell(""), + FieldType::Number => row_builder.insert_number_cell("2"), + FieldType::DateTime => row_builder.insert_date_cell("1647251762"), + FieldType::MultiSelect => row_builder + .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(1)]), + FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), + _ => "".to_owned(), + }; + } + }, + 2 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("C"), + FieldType::Number => row_builder.insert_number_cell("3"), + FieldType::DateTime => row_builder.insert_date_cell("1647251762"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(0)) + }, + FieldType::MultiSelect => { + row_builder.insert_multi_select_cell(|mut options| vec![options.remove(1)]) + }, + FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), + _ => "".to_owned(), + }; + } + }, + 3 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("DA"), + FieldType::Number => row_builder.insert_number_cell("4"), + FieldType::DateTime => row_builder.insert_date_cell("1668704685"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(0)) + }, + FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), + _ => "".to_owned(), + }; + } + }, + 4 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("AE"), + FieldType::Number => row_builder.insert_number_cell(""), + FieldType::DateTime => row_builder.insert_date_cell("1668359085"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(1)) + }, + + FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), + _ => "".to_owned(), + }; + } + }, + 5 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("AE"), + FieldType::Number => row_builder.insert_number_cell("5"), + FieldType::DateTime => row_builder.insert_date_cell("1671938394"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(1)) + }, + FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), + _ => "".to_owned(), + }; + } + }, + _ => {}, } - for i in 0..6 { - let block_id = grid_builder.block_id().to_owned(); - let field_revs = grid_builder.field_revs(); - let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs); - match i { - 0 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("A"), - FieldType::Number => row_builder.insert_number_cell("1"), - FieldType::DateTime => row_builder.insert_date_cell("1647251762"), - FieldType::MultiSelect => row_builder - .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]), - FieldType::Checklist => row_builder.insert_checklist_cell(|options| options), - FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), - FieldType::URL => row_builder.insert_url_cell("AppFlowy website - https://www.appflowy.io"), - _ => "".to_owned(), - }; - } - } - 1 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell(""), - FieldType::Number => row_builder.insert_number_cell("2"), - FieldType::DateTime => row_builder.insert_date_cell("1647251762"), - FieldType::MultiSelect => row_builder - .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(1)]), - FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), - _ => "".to_owned(), - }; - } - } - 2 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("C"), - FieldType::Number => row_builder.insert_number_cell("3"), - FieldType::DateTime => row_builder.insert_date_cell("1647251762"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(0)) - } - FieldType::MultiSelect => { - row_builder.insert_multi_select_cell(|mut options| vec![options.remove(1)]) - } - FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), - _ => "".to_owned(), - }; - } - } - 3 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("DA"), - FieldType::Number => row_builder.insert_number_cell("4"), - FieldType::DateTime => row_builder.insert_date_cell("1668704685"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(0)) - } - FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), - _ => "".to_owned(), - }; - } - } - 4 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("AE"), - FieldType::Number => row_builder.insert_number_cell(""), - FieldType::DateTime => row_builder.insert_date_cell("1668359085"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(1)) - } - - FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), - _ => "".to_owned(), - }; - } - } - 5 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("AE"), - FieldType::Number => row_builder.insert_number_cell("5"), - FieldType::DateTime => row_builder.insert_date_cell("1671938394"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(1)) - } - FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), - _ => "".to_owned(), - }; - } - } - _ => {} - } - - let row_rev = row_builder.build(); - grid_builder.add_row(row_rev); - } - grid_builder.build() + let row_rev = row_builder.build(); + grid_builder.add_row(row_rev); + } + grid_builder.build() } diff --git a/frontend/rust-lib/flowy-database/tests/grid/snapshot_test/script.rs b/frontend/rust-lib/flowy-database/tests/grid/snapshot_test/script.rs index afebf19ea5..d6f80ebf7d 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/snapshot_test/script.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/snapshot_test/script.rs @@ -8,96 +8,96 @@ use std::time::Duration; use tokio::time::sleep; pub enum SnapshotScript { - WriteSnapshot, - #[allow(dead_code)] - AssertSnapshot { - rev_id: i64, - expected: Option, - }, - AssertSnapshotContent { - snapshot: RevisionSnapshotData, - expected: String, - }, - CreateField { - field_rev: FieldRevision, - }, - DeleteField { - field_rev: FieldRevision, - }, + WriteSnapshot, + #[allow(dead_code)] + AssertSnapshot { + rev_id: i64, + expected: Option, + }, + AssertSnapshotContent { + snapshot: RevisionSnapshotData, + expected: String, + }, + CreateField { + field_rev: FieldRevision, + }, + DeleteField { + field_rev: FieldRevision, + }, } pub struct DatabaseSnapshotTest { - inner: DatabaseEditorTest, - pub current_snapshot: Option, - pub current_revision: Option, + inner: DatabaseEditorTest, + pub current_snapshot: Option, + pub current_revision: Option, } impl DatabaseSnapshotTest { - pub async fn new() -> Self { - let editor_test = DatabaseEditorTest::new_table().await; - Self { - inner: editor_test, - current_snapshot: None, - current_revision: None, - } + pub async fn new() -> Self { + let editor_test = DatabaseEditorTest::new_table().await; + Self { + inner: editor_test, + current_snapshot: None, + current_revision: None, } + } - pub fn grid_id(&self) -> String { - self.view_id.clone() - } + pub fn grid_id(&self) -> String { + self.view_id.clone() + } - pub async fn grid_pad(&self) -> DatabaseRevisionPad { - self.editor.grid_pad().read().await.clone() - } + pub async fn grid_pad(&self) -> DatabaseRevisionPad { + self.editor.grid_pad().read().await.clone() + } - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } + pub async fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; } + } - pub async fn get_latest_snapshot(&self) -> Option { - self.editor.rev_manager().read_snapshot(None).await.unwrap() - } + pub async fn get_latest_snapshot(&self) -> Option { + self.editor.rev_manager().read_snapshot(None).await.unwrap() + } - pub async fn run_script(&mut self, script: SnapshotScript) { - let rev_manager = self.editor.rev_manager(); - match script { - SnapshotScript::WriteSnapshot => { - sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await; - rev_manager.generate_snapshot().await; - self.current_snapshot = rev_manager.read_snapshot(None).await.unwrap(); - } - SnapshotScript::AssertSnapshot { rev_id, expected } => { - let snapshot = rev_manager.read_snapshot(Some(rev_id)).await.unwrap(); - assert_eq!(snapshot, expected); - } - SnapshotScript::AssertSnapshotContent { snapshot, expected } => { - let operations = DatabaseOperations::from_bytes(snapshot.data).unwrap(); - let pad = DatabaseRevisionPad::from_operations(operations).unwrap(); - assert_eq!(pad.json_str().unwrap(), expected); - } - SnapshotScript::CreateField { field_rev } => { - self.editor.create_new_field_rev(field_rev).await.unwrap(); - let current_rev_id = rev_manager.rev_id(); - self.current_revision = rev_manager.get_revision(current_rev_id).await; - } - SnapshotScript::DeleteField { field_rev } => { - self.editor.delete_field(&field_rev.id).await.unwrap(); - } - } + pub async fn run_script(&mut self, script: SnapshotScript) { + let rev_manager = self.editor.rev_manager(); + match script { + SnapshotScript::WriteSnapshot => { + sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await; + rev_manager.generate_snapshot().await; + self.current_snapshot = rev_manager.read_snapshot(None).await.unwrap(); + }, + SnapshotScript::AssertSnapshot { rev_id, expected } => { + let snapshot = rev_manager.read_snapshot(Some(rev_id)).await.unwrap(); + assert_eq!(snapshot, expected); + }, + SnapshotScript::AssertSnapshotContent { snapshot, expected } => { + let operations = DatabaseOperations::from_bytes(snapshot.data).unwrap(); + let pad = DatabaseRevisionPad::from_operations(operations).unwrap(); + assert_eq!(pad.json_str().unwrap(), expected); + }, + SnapshotScript::CreateField { field_rev } => { + self.editor.create_new_field_rev(field_rev).await.unwrap(); + let current_rev_id = rev_manager.rev_id(); + self.current_revision = rev_manager.get_revision(current_rev_id).await; + }, + SnapshotScript::DeleteField { field_rev } => { + self.editor.delete_field(&field_rev.id).await.unwrap(); + }, } + } } impl std::ops::Deref for DatabaseSnapshotTest { - type Target = DatabaseEditorTest; + type Target = DatabaseEditorTest; - fn deref(&self) -> &Self::Target { - &self.inner - } + fn deref(&self) -> &Self::Target { + &self.inner + } } impl std::ops::DerefMut for DatabaseSnapshotTest { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } diff --git a/frontend/rust-lib/flowy-database/tests/grid/snapshot_test/test.rs b/frontend/rust-lib/flowy-database/tests/grid/snapshot_test/test.rs index b2896de3d3..98bb26813d 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/snapshot_test/test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/snapshot_test/test.rs @@ -3,43 +3,45 @@ use crate::grid::snapshot_test::script::{DatabaseSnapshotTest, SnapshotScript::* #[tokio::test] async fn snapshot_create_test() { - let mut test = DatabaseSnapshotTest::new().await; - let (_, field_rev) = create_text_field(&test.grid_id()); - let scripts = vec![CreateField { field_rev }, WriteSnapshot]; - test.run_scripts(scripts).await; + let mut test = DatabaseSnapshotTest::new().await; + let (_, field_rev) = create_text_field(&test.grid_id()); + let scripts = vec![CreateField { field_rev }, WriteSnapshot]; + test.run_scripts(scripts).await; - let snapshot = test.current_snapshot.clone().unwrap(); - let content = test.grid_pad().await.json_str().unwrap(); - test.run_scripts(vec![AssertSnapshotContent { - snapshot, - expected: content, + let snapshot = test.current_snapshot.clone().unwrap(); + let content = test.grid_pad().await.json_str().unwrap(); + test + .run_scripts(vec![AssertSnapshotContent { + snapshot, + expected: content, }]) .await; } #[tokio::test] async fn snapshot_multi_version_test() { - let mut test = DatabaseSnapshotTest::new().await; - let original_content = test.grid_pad().await.json_str().unwrap(); + let mut test = DatabaseSnapshotTest::new().await; + let original_content = test.grid_pad().await.json_str().unwrap(); - // Create a field - let (_, field_rev) = create_text_field(&test.grid_id()); - let scripts = vec![ - CreateField { - field_rev: field_rev.clone(), - }, - WriteSnapshot, - ]; - test.run_scripts(scripts).await; + // Create a field + let (_, field_rev) = create_text_field(&test.grid_id()); + let scripts = vec![ + CreateField { + field_rev: field_rev.clone(), + }, + WriteSnapshot, + ]; + test.run_scripts(scripts).await; - // Delete a field - let scripts = vec![DeleteField { field_rev }, WriteSnapshot]; - test.run_scripts(scripts).await; + // Delete a field + let scripts = vec![DeleteField { field_rev }, WriteSnapshot]; + test.run_scripts(scripts).await; - // The latest snapshot will be the same as the original content. - test.run_scripts(vec![AssertSnapshotContent { - snapshot: test.get_latest_snapshot().await.unwrap(), - expected: original_content, + // The latest snapshot will be the same as the original content. + test + .run_scripts(vec![AssertSnapshotContent { + snapshot: test.get_latest_snapshot().await.unwrap(), + expected: original_content, }]) .await; } diff --git a/frontend/rust-lib/flowy-database/tests/grid/sort_test/checkbox_and_text_test.rs b/frontend/rust-lib/flowy-database/tests/grid/sort_test/checkbox_and_text_test.rs index a7c303e5a7..17f4859b9e 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/sort_test/checkbox_and_text_test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/sort_test/checkbox_and_text_test.rs @@ -4,47 +4,47 @@ use grid_model::SortCondition; #[tokio::test] async fn sort_checkbox_and_then_text_by_descending_test() { - let mut test = DatabaseSortTest::new().await; - let checkbox_field = test.get_first_field_rev(FieldType::Checkbox); - let text_field = test.get_first_field_rev(FieldType::RichText); - let scripts = vec![ - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"], - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["A", "", "C", "DA", "AE", "AE"], - }, - // Insert checkbox sort - InsertSort { - field_rev: checkbox_field.clone(), - condition: SortCondition::Descending, - }, - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"], - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["A", "", "AE", "C", "DA", "AE"], - }, - // Insert text sort. After inserting the text sort, the order of the rows - // will be changed. - // before: ["A", "", "AE", "C", "DA", "AE"] - // after: ["", "A", "AE", "AE", "C", "DA"] - InsertSort { - field_rev: text_field.clone(), - condition: SortCondition::Ascending, - }, - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"], - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["", "A", "AE", "AE", "C", "DA"], - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseSortTest::new().await; + let checkbox_field = test.get_first_field_rev(FieldType::Checkbox); + let text_field = test.get_first_field_rev(FieldType::RichText); + let scripts = vec![ + AssertCellContentOrder { + field_id: checkbox_field.id.clone(), + orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"], + }, + AssertCellContentOrder { + field_id: text_field.id.clone(), + orders: vec!["A", "", "C", "DA", "AE", "AE"], + }, + // Insert checkbox sort + InsertSort { + field_rev: checkbox_field.clone(), + condition: SortCondition::Descending, + }, + AssertCellContentOrder { + field_id: checkbox_field.id.clone(), + orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"], + }, + AssertCellContentOrder { + field_id: text_field.id.clone(), + orders: vec!["A", "", "AE", "C", "DA", "AE"], + }, + // Insert text sort. After inserting the text sort, the order of the rows + // will be changed. + // before: ["A", "", "AE", "C", "DA", "AE"] + // after: ["", "A", "AE", "AE", "C", "DA"] + InsertSort { + field_rev: text_field.clone(), + condition: SortCondition::Ascending, + }, + AssertCellContentOrder { + field_id: checkbox_field.id.clone(), + orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"], + }, + AssertCellContentOrder { + field_id: text_field.id.clone(), + orders: vec!["", "A", "AE", "AE", "C", "DA"], + }, + ]; + test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-database/tests/grid/sort_test/multi_sort_test.rs b/frontend/rust-lib/flowy-database/tests/grid/sort_test/multi_sort_test.rs index cd52c7b2dd..20692cdc0f 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/sort_test/multi_sort_test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/sort_test/multi_sort_test.rs @@ -5,42 +5,42 @@ use grid_model::SortCondition; #[tokio::test] async fn sort_text_with_checkbox_by_ascending_test() { - let mut test = DatabaseSortTest::new().await; - let text_field = test.get_first_field_rev(FieldType::RichText).clone(); - let checkbox_field = test.get_first_field_rev(FieldType::Checkbox).clone(); - let scripts = vec![ - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["A", "", "C", "DA", "AE", "AE"], - }, - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "No", "No", "No"], - }, - InsertSort { - field_rev: text_field.clone(), - condition: SortCondition::Ascending, - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["", "A", "AE", "AE", "C", "DA"], - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseSortTest::new().await; + let text_field = test.get_first_field_rev(FieldType::RichText).clone(); + let checkbox_field = test.get_first_field_rev(FieldType::Checkbox).clone(); + let scripts = vec![ + AssertCellContentOrder { + field_id: text_field.id.clone(), + orders: vec!["A", "", "C", "DA", "AE", "AE"], + }, + AssertCellContentOrder { + field_id: checkbox_field.id.clone(), + orders: vec!["Yes", "Yes", "No", "No", "No"], + }, + InsertSort { + field_rev: text_field.clone(), + condition: SortCondition::Ascending, + }, + AssertCellContentOrder { + field_id: text_field.id.clone(), + orders: vec!["", "A", "AE", "AE", "C", "DA"], + }, + ]; + test.run_scripts(scripts).await; - let scripts = vec![ - InsertSort { - field_rev: checkbox_field.clone(), - condition: SortCondition::Descending, - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["", "A", "AE", "AE", "C", "DA"], - }, - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "Yes", "No", "No"], - }, - ]; - test.run_scripts(scripts).await; + let scripts = vec![ + InsertSort { + field_rev: checkbox_field.clone(), + condition: SortCondition::Descending, + }, + AssertCellContentOrder { + field_id: text_field.id.clone(), + orders: vec!["", "A", "AE", "AE", "C", "DA"], + }, + AssertCellContentOrder { + field_id: checkbox_field.id.clone(), + orders: vec!["Yes", "Yes", "Yes", "No", "No"], + }, + ]; + test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-database/tests/grid/sort_test/script.rs b/frontend/rust-lib/flowy-database/tests/grid/sort_test/script.rs index 5f12ca57a7..19c494b3ed 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/sort_test/script.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/sort_test/script.rs @@ -11,159 +11,186 @@ use std::time::Duration; use tokio::sync::broadcast::Receiver; pub enum SortScript { - InsertSort { - field_rev: Arc, - condition: SortCondition, - }, - DeleteSort { - field_rev: Arc, - sort_id: String, - }, - AssertCellContentOrder { - field_id: String, - orders: Vec<&'static str>, - }, - UpdateTextCell { - row_id: String, - text: String, - }, - AssertSortChanged { - old_row_orders: Vec<&'static str>, - new_row_orders: Vec<&'static str>, - }, - Wait { - millis: u64, - }, + InsertSort { + field_rev: Arc, + condition: SortCondition, + }, + DeleteSort { + field_rev: Arc, + sort_id: String, + }, + AssertCellContentOrder { + field_id: String, + orders: Vec<&'static str>, + }, + UpdateTextCell { + row_id: String, + text: String, + }, + AssertSortChanged { + old_row_orders: Vec<&'static str>, + new_row_orders: Vec<&'static str>, + }, + Wait { + millis: u64, + }, } pub struct DatabaseSortTest { - inner: DatabaseEditorTest, - pub current_sort_rev: Option, - recv: Option>, + inner: DatabaseEditorTest, + pub current_sort_rev: Option, + recv: Option>, } impl DatabaseSortTest { - pub async fn new() -> Self { - let editor_test = DatabaseEditorTest::new_table().await; - Self { - inner: editor_test, - current_sort_rev: None, - recv: None, - } + pub async fn new() -> Self { + let editor_test = DatabaseEditorTest::new_table().await; + Self { + inner: editor_test, + current_sort_rev: None, + recv: None, } - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } + } + pub async fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; } + } - pub async fn run_script(&mut self, script: SortScript) { - match script { - SortScript::InsertSort { condition, field_rev } => { - self.recv = Some(self.editor.subscribe_view_changed(&self.view_id).await.unwrap()); - let params = AlterSortParams { - view_id: self.view_id.clone(), - field_id: field_rev.id.clone(), - sort_id: None, - field_type: field_rev.ty, - condition: condition.into(), - }; - let sort_rev = self.editor.create_or_update_sort(params).await.unwrap(); - self.current_sort_rev = Some(sort_rev); - } - SortScript::DeleteSort { field_rev, sort_id } => { - self.recv = Some(self.editor.subscribe_view_changed(&self.view_id).await.unwrap()); - let params = DeleteSortParams { - view_id: self.view_id.clone(), - sort_type: SortType::from(&field_rev), - sort_id, - }; - self.editor.delete_sort(params).await.unwrap(); - self.current_sort_rev = None; - } - SortScript::AssertCellContentOrder { field_id, orders } => { - let mut cells = vec![]; - let rows = self.editor.get_database(&self.view_id).await.unwrap().rows; - for row in rows { - let params = CellIdParams { - database_id: self.view_id.clone(), - field_id: field_id.clone(), - row_id: row.id, - }; - let cell = self.editor.get_cell_display_str(¶ms).await; - cells.push(cell); - } - if orders.is_empty() { - assert_eq!(cells, orders); - } else { - let len = min(cells.len(), orders.len()); - assert_eq!(cells.split_at(len).0, orders); - } - } - SortScript::UpdateTextCell { row_id, text } => { - self.recv = Some(self.editor.subscribe_view_changed(&self.view_id).await.unwrap()); - self.update_text_cell(row_id, &text).await; - } - SortScript::AssertSortChanged { - new_row_orders, - old_row_orders, - } => { - if let Some(receiver) = self.recv.take() { - assert_sort_changed( - receiver, - new_row_orders.into_iter().map(|order| order.to_owned()).collect(), - old_row_orders.into_iter().map(|order| order.to_owned()).collect(), - ) - .await; - } - } - SortScript::Wait { millis } => { - tokio::time::sleep(Duration::from_millis(millis)).await; - } + pub async fn run_script(&mut self, script: SortScript) { + match script { + SortScript::InsertSort { + condition, + field_rev, + } => { + self.recv = Some( + self + .editor + .subscribe_view_changed(&self.view_id) + .await + .unwrap(), + ); + let params = AlterSortParams { + view_id: self.view_id.clone(), + field_id: field_rev.id.clone(), + sort_id: None, + field_type: field_rev.ty, + condition: condition.into(), + }; + let sort_rev = self.editor.create_or_update_sort(params).await.unwrap(); + self.current_sort_rev = Some(sort_rev); + }, + SortScript::DeleteSort { field_rev, sort_id } => { + self.recv = Some( + self + .editor + .subscribe_view_changed(&self.view_id) + .await + .unwrap(), + ); + let params = DeleteSortParams { + view_id: self.view_id.clone(), + sort_type: SortType::from(&field_rev), + sort_id, + }; + self.editor.delete_sort(params).await.unwrap(); + self.current_sort_rev = None; + }, + SortScript::AssertCellContentOrder { field_id, orders } => { + let mut cells = vec![]; + let rows = self.editor.get_database(&self.view_id).await.unwrap().rows; + for row in rows { + let params = CellIdParams { + database_id: self.view_id.clone(), + field_id: field_id.clone(), + row_id: row.id, + }; + let cell = self.editor.get_cell_display_str(¶ms).await; + cells.push(cell); } + if orders.is_empty() { + assert_eq!(cells, orders); + } else { + let len = min(cells.len(), orders.len()); + assert_eq!(cells.split_at(len).0, orders); + } + }, + SortScript::UpdateTextCell { row_id, text } => { + self.recv = Some( + self + .editor + .subscribe_view_changed(&self.view_id) + .await + .unwrap(), + ); + self.update_text_cell(row_id, &text).await; + }, + SortScript::AssertSortChanged { + new_row_orders, + old_row_orders, + } => { + if let Some(receiver) = self.recv.take() { + assert_sort_changed( + receiver, + new_row_orders + .into_iter() + .map(|order| order.to_owned()) + .collect(), + old_row_orders + .into_iter() + .map(|order| order.to_owned()) + .collect(), + ) + .await; + } + }, + SortScript::Wait { millis } => { + tokio::time::sleep(Duration::from_millis(millis)).await; + }, } + } } async fn assert_sort_changed( - mut receiver: Receiver, - new_row_orders: Vec, - old_row_orders: Vec, + mut receiver: Receiver, + new_row_orders: Vec, + old_row_orders: Vec, ) { - let stream = stream! { - loop { - tokio::select! { - changed = receiver.recv() => yield changed.unwrap(), - _ = tokio::time::sleep(Duration::from_secs(2)) => break, - }; - } - }; + let stream = stream! { + loop { + tokio::select! { + changed = receiver.recv() => yield changed.unwrap(), + _ = tokio::time::sleep(Duration::from_secs(2)) => break, + }; + } + }; - stream - .for_each(|changed| async { - match changed { - DatabaseViewChanged::ReorderAllRowsNotification(_changed) => {} - DatabaseViewChanged::ReorderSingleRowNotification(changed) => { - let mut old_row_orders = old_row_orders.clone(); - let old = old_row_orders.remove(changed.old_index); - old_row_orders.insert(changed.new_index, old); - assert_eq!(old_row_orders, new_row_orders); - } - _ => {} - } - }) - .await; + stream + .for_each(|changed| async { + match changed { + DatabaseViewChanged::ReorderAllRowsNotification(_changed) => {}, + DatabaseViewChanged::ReorderSingleRowNotification(changed) => { + let mut old_row_orders = old_row_orders.clone(); + let old = old_row_orders.remove(changed.old_index); + old_row_orders.insert(changed.new_index, old); + assert_eq!(old_row_orders, new_row_orders); + }, + _ => {}, + } + }) + .await; } impl std::ops::Deref for DatabaseSortTest { - type Target = DatabaseEditorTest; + type Target = DatabaseEditorTest; - fn deref(&self) -> &Self::Target { - &self.inner - } + fn deref(&self) -> &Self::Target { + &self.inner + } } impl std::ops::DerefMut for DatabaseSortTest { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } } diff --git a/frontend/rust-lib/flowy-database/tests/grid/sort_test/single_sort_test.rs b/frontend/rust-lib/flowy-database/tests/grid/sort_test/single_sort_test.rs index 4327e353c3..145c642563 100644 --- a/frontend/rust-lib/flowy-database/tests/grid/sort_test/single_sort_test.rs +++ b/frontend/rust-lib/flowy-database/tests/grid/sort_test/single_sort_test.rs @@ -4,254 +4,266 @@ use grid_model::SortCondition; #[tokio::test] async fn sort_text_by_ascending_test() { - let mut test = DatabaseSortTest::new().await; - let text_field = test.get_first_field_rev(FieldType::RichText); - let scripts = vec![ - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["A", "", "C", "DA", "AE", "AE"], - }, - InsertSort { - field_rev: text_field.clone(), - condition: SortCondition::Ascending, - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["", "A", "AE", "AE", "C", "DA"], - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseSortTest::new().await; + let text_field = test.get_first_field_rev(FieldType::RichText); + let scripts = vec![ + AssertCellContentOrder { + field_id: text_field.id.clone(), + orders: vec!["A", "", "C", "DA", "AE", "AE"], + }, + InsertSort { + field_rev: text_field.clone(), + condition: SortCondition::Ascending, + }, + AssertCellContentOrder { + field_id: text_field.id.clone(), + orders: vec!["", "A", "AE", "AE", "C", "DA"], + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn sort_change_notification_by_update_text_test() { - let mut test = DatabaseSortTest::new().await; - let text_field = test.get_first_field_rev(FieldType::RichText).clone(); - let scripts = vec![ - InsertSort { - field_rev: text_field.clone(), - condition: SortCondition::Ascending, - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["", "A", "AE", "AE", "C", "DA"], - }, - // Wait the insert task to finish. The cost of time should be less than 200 milliseconds. - Wait { millis: 200 }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseSortTest::new().await; + let text_field = test.get_first_field_rev(FieldType::RichText).clone(); + let scripts = vec![ + InsertSort { + field_rev: text_field.clone(), + condition: SortCondition::Ascending, + }, + AssertCellContentOrder { + field_id: text_field.id.clone(), + orders: vec!["", "A", "AE", "AE", "C", "DA"], + }, + // Wait the insert task to finish. The cost of time should be less than 200 milliseconds. + Wait { millis: 200 }, + ]; + test.run_scripts(scripts).await; - let row_revs = test.get_row_revs().await; - let scripts = vec![ - UpdateTextCell { - row_id: row_revs[2].id.clone(), - text: "E".to_string(), - }, - AssertSortChanged { - old_row_orders: vec!["", "A", "E", "AE", "C", "DA"], - new_row_orders: vec!["", "A", "AE", "C", "DA", "E"], - }, - ]; - test.run_scripts(scripts).await; + let row_revs = test.get_row_revs().await; + let scripts = vec![ + UpdateTextCell { + row_id: row_revs[2].id.clone(), + text: "E".to_string(), + }, + AssertSortChanged { + old_row_orders: vec!["", "A", "E", "AE", "C", "DA"], + new_row_orders: vec!["", "A", "AE", "C", "DA", "E"], + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn sort_text_by_ascending_and_delete_sort_test() { - let mut test = DatabaseSortTest::new().await; - let text_field = test.get_first_field_rev(FieldType::RichText).clone(); - let scripts = vec![InsertSort { - field_rev: text_field.clone(), - condition: SortCondition::Ascending, - }]; - test.run_scripts(scripts).await; - let sort_rev = test.current_sort_rev.as_ref().unwrap(); - let scripts = vec![ - DeleteSort { - field_rev: text_field.clone(), - sort_id: sort_rev.id.clone(), - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["A", "", "C", "DA", "AE"], - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseSortTest::new().await; + let text_field = test.get_first_field_rev(FieldType::RichText).clone(); + let scripts = vec![InsertSort { + field_rev: text_field.clone(), + condition: SortCondition::Ascending, + }]; + test.run_scripts(scripts).await; + let sort_rev = test.current_sort_rev.as_ref().unwrap(); + let scripts = vec![ + DeleteSort { + field_rev: text_field.clone(), + sort_id: sort_rev.id.clone(), + }, + AssertCellContentOrder { + field_id: text_field.id.clone(), + orders: vec!["A", "", "C", "DA", "AE"], + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn sort_text_by_descending_test() { - let mut test = DatabaseSortTest::new().await; - let text_field = test.get_first_field_rev(FieldType::RichText); - let scripts = vec![ - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["A", "", "C", "DA", "AE", "AE"], - }, - InsertSort { - field_rev: text_field.clone(), - condition: SortCondition::Descending, - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["DA", "C", "AE", "AE", "A", ""], - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseSortTest::new().await; + let text_field = test.get_first_field_rev(FieldType::RichText); + let scripts = vec![ + AssertCellContentOrder { + field_id: text_field.id.clone(), + orders: vec!["A", "", "C", "DA", "AE", "AE"], + }, + InsertSort { + field_rev: text_field.clone(), + condition: SortCondition::Descending, + }, + AssertCellContentOrder { + field_id: text_field.id.clone(), + orders: vec!["DA", "C", "AE", "AE", "A", ""], + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn sort_checkbox_by_ascending_test() { - let mut test = DatabaseSortTest::new().await; - let checkbox_field = test.get_first_field_rev(FieldType::Checkbox); - let scripts = vec![ - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "No", "No", "No"], - }, - InsertSort { - field_rev: checkbox_field.clone(), - condition: SortCondition::Ascending, - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseSortTest::new().await; + let checkbox_field = test.get_first_field_rev(FieldType::Checkbox); + let scripts = vec![ + AssertCellContentOrder { + field_id: checkbox_field.id.clone(), + orders: vec!["Yes", "Yes", "No", "No", "No"], + }, + InsertSort { + field_rev: checkbox_field.clone(), + condition: SortCondition::Ascending, + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn sort_checkbox_by_descending_test() { - let mut test = DatabaseSortTest::new().await; - let checkbox_field = test.get_first_field_rev(FieldType::Checkbox); - let scripts = vec![ - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"], - }, - InsertSort { - field_rev: checkbox_field.clone(), - condition: SortCondition::Descending, - }, - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"], - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseSortTest::new().await; + let checkbox_field = test.get_first_field_rev(FieldType::Checkbox); + let scripts = vec![ + AssertCellContentOrder { + field_id: checkbox_field.id.clone(), + orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"], + }, + InsertSort { + field_rev: checkbox_field.clone(), + condition: SortCondition::Descending, + }, + AssertCellContentOrder { + field_id: checkbox_field.id.clone(), + orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"], + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn sort_date_by_ascending_test() { - let mut test = DatabaseSortTest::new().await; - let date_field = test.get_first_field_rev(FieldType::DateTime); - let scripts = vec![ - AssertCellContentOrder { - field_id: date_field.id.clone(), - orders: vec!["2022/03/14", "2022/03/14", "2022/03/14", "2022/11/17", "2022/11/13"], - }, - InsertSort { - field_rev: date_field.clone(), - condition: SortCondition::Ascending, - }, - AssertCellContentOrder { - field_id: date_field.id.clone(), - orders: vec!["2022/03/14", "2022/03/14", "2022/03/14", "2022/11/13", "2022/11/17"], - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseSortTest::new().await; + let date_field = test.get_first_field_rev(FieldType::DateTime); + let scripts = vec![ + AssertCellContentOrder { + field_id: date_field.id.clone(), + orders: vec![ + "2022/03/14", + "2022/03/14", + "2022/03/14", + "2022/11/17", + "2022/11/13", + ], + }, + InsertSort { + field_rev: date_field.clone(), + condition: SortCondition::Ascending, + }, + AssertCellContentOrder { + field_id: date_field.id.clone(), + orders: vec![ + "2022/03/14", + "2022/03/14", + "2022/03/14", + "2022/11/13", + "2022/11/17", + ], + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn sort_date_by_descending_test() { - let mut test = DatabaseSortTest::new().await; - let date_field = test.get_first_field_rev(FieldType::DateTime); - let scripts = vec![ - AssertCellContentOrder { - field_id: date_field.id.clone(), - orders: vec![ - "2022/03/14", - "2022/03/14", - "2022/03/14", - "2022/11/17", - "2022/11/13", - "2022/12/25", - ], - }, - InsertSort { - field_rev: date_field.clone(), - condition: SortCondition::Descending, - }, - AssertCellContentOrder { - field_id: date_field.id.clone(), - orders: vec![ - "2022/12/25", - "2022/11/17", - "2022/11/13", - "2022/03/14", - "2022/03/14", - "2022/03/14", - ], - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseSortTest::new().await; + let date_field = test.get_first_field_rev(FieldType::DateTime); + let scripts = vec![ + AssertCellContentOrder { + field_id: date_field.id.clone(), + orders: vec![ + "2022/03/14", + "2022/03/14", + "2022/03/14", + "2022/11/17", + "2022/11/13", + "2022/12/25", + ], + }, + InsertSort { + field_rev: date_field.clone(), + condition: SortCondition::Descending, + }, + AssertCellContentOrder { + field_id: date_field.id.clone(), + orders: vec![ + "2022/12/25", + "2022/11/17", + "2022/11/13", + "2022/03/14", + "2022/03/14", + "2022/03/14", + ], + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn sort_number_by_descending_test() { - let mut test = DatabaseSortTest::new().await; - let number_field = test.get_first_field_rev(FieldType::Number); - let scripts = vec![ - AssertCellContentOrder { - field_id: number_field.id.clone(), - orders: vec!["$1", "$2", "$3", "$4", "", "$5"], - }, - InsertSort { - field_rev: number_field.clone(), - condition: SortCondition::Descending, - }, - AssertCellContentOrder { - field_id: number_field.id.clone(), - orders: vec!["$5", "$4", "$3", "$2", "$1", ""], - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseSortTest::new().await; + let number_field = test.get_first_field_rev(FieldType::Number); + let scripts = vec![ + AssertCellContentOrder { + field_id: number_field.id.clone(), + orders: vec!["$1", "$2", "$3", "$4", "", "$5"], + }, + InsertSort { + field_rev: number_field.clone(), + condition: SortCondition::Descending, + }, + AssertCellContentOrder { + field_id: number_field.id.clone(), + orders: vec!["$5", "$4", "$3", "$2", "$1", ""], + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn sort_single_select_by_descending_test() { - let mut test = DatabaseSortTest::new().await; - let single_select = test.get_first_field_rev(FieldType::SingleSelect); - let scripts = vec![ - AssertCellContentOrder { - field_id: single_select.id.clone(), - orders: vec!["", "", "Completed", "Completed", "Planned", "Planned"], - }, - InsertSort { - field_rev: single_select.clone(), - condition: SortCondition::Descending, - }, - AssertCellContentOrder { - field_id: single_select.id.clone(), - orders: vec!["Planned", "Planned", "Completed", "Completed", "", ""], - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseSortTest::new().await; + let single_select = test.get_first_field_rev(FieldType::SingleSelect); + let scripts = vec![ + AssertCellContentOrder { + field_id: single_select.id.clone(), + orders: vec!["", "", "Completed", "Completed", "Planned", "Planned"], + }, + InsertSort { + field_rev: single_select.clone(), + condition: SortCondition::Descending, + }, + AssertCellContentOrder { + field_id: single_select.id.clone(), + orders: vec!["Planned", "Planned", "Completed", "Completed", "", ""], + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn sort_multi_select_by_ascending_test() { - let mut test = DatabaseSortTest::new().await; - let multi_select = test.get_first_field_rev(FieldType::MultiSelect); - let scripts = vec![ - AssertCellContentOrder { - field_id: multi_select.id.clone(), - orders: vec!["Google,Facebook", "Google,Twitter", "Facebook", "", "", ""], - }, - InsertSort { - field_rev: multi_select.clone(), - condition: SortCondition::Ascending, - }, - AssertCellContentOrder { - field_id: multi_select.id.clone(), - orders: vec!["", "", "", "Facebook", "Google,Facebook", "Google,Twitter"], - }, - ]; - test.run_scripts(scripts).await; + let mut test = DatabaseSortTest::new().await; + let multi_select = test.get_first_field_rev(FieldType::MultiSelect); + let scripts = vec![ + AssertCellContentOrder { + field_id: multi_select.id.clone(), + orders: vec!["Google,Facebook", "Google,Twitter", "Facebook", "", "", ""], + }, + InsertSort { + field_rev: multi_select.clone(), + condition: SortCondition::Ascending, + }, + AssertCellContentOrder { + field_id: multi_select.id.clone(), + orders: vec!["", "", "", "Facebook", "Google,Facebook", "Google,Twitter"], + }, + ]; + test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-derive/src/dart_event/mod.rs b/frontend/rust-lib/flowy-derive/src/dart_event/mod.rs index 0ba7a79e5e..bc5cc7ca15 100644 --- a/frontend/rust-lib/flowy-derive/src/dart_event/mod.rs +++ b/frontend/rust-lib/flowy-derive/src/dart_event/mod.rs @@ -2,7 +2,7 @@ use proc_macro2::TokenStream; // #[proc_macro_derive(DartEvent, attributes(event_ty))] pub fn expand_enum_derive(_input: &syn::DeriveInput) -> Result> { - Ok(TokenStream::default()) + Ok(TokenStream::default()) } // use flowy_ast::{ASTContainer, Ctxt}; diff --git a/frontend/rust-lib/flowy-derive/src/lib.rs b/frontend/rust-lib/flowy-derive/src/lib.rs index 06cbd2b8b2..ea855f5759 100644 --- a/frontend/rust-lib/flowy-derive/src/lib.rs +++ b/frontend/rust-lib/flowy-derive/src/lib.rs @@ -15,35 +15,37 @@ mod proto_buf; // Inspired by https://serde.rs/attributes.html #[proc_macro_derive(ProtoBuf, attributes(pb))] pub fn derive_proto_buf(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - proto_buf::expand_derive(&input) - .unwrap_or_else(to_compile_errors) - .into() + let input = parse_macro_input!(input as DeriveInput); + proto_buf::expand_derive(&input) + .unwrap_or_else(to_compile_errors) + .into() } #[proc_macro_derive(ProtoBuf_Enum, attributes(pb))] pub fn derive_proto_buf_enum(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - proto_buf::expand_enum_derive(&input) - .unwrap_or_else(to_compile_errors) - .into() + let input = parse_macro_input!(input as DeriveInput); + proto_buf::expand_enum_derive(&input) + .unwrap_or_else(to_compile_errors) + .into() } #[proc_macro_derive(Flowy_Event, attributes(event, event_err))] pub fn derive_dart_event(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - dart_event::expand_enum_derive(&input) - .unwrap_or_else(to_compile_errors) - .into() + let input = parse_macro_input!(input as DeriveInput); + dart_event::expand_enum_derive(&input) + .unwrap_or_else(to_compile_errors) + .into() } #[proc_macro_derive(Node, attributes(node, nodes, node_type))] pub fn derive_node(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - node::expand_derive(&input).unwrap_or_else(to_compile_errors).into() + let input = parse_macro_input!(input as DeriveInput); + node::expand_derive(&input) + .unwrap_or_else(to_compile_errors) + .into() } fn to_compile_errors(errors: Vec) -> proc_macro2::TokenStream { - let compile_errors = errors.iter().map(syn::Error::to_compile_error); - quote!(#(#compile_errors)*) + let compile_errors = errors.iter().map(syn::Error::to_compile_error); + quote!(#(#compile_errors)*) } diff --git a/frontend/rust-lib/flowy-derive/src/node/mod.rs b/frontend/rust-lib/flowy-derive/src/node/mod.rs index 3058a6d460..7061ed94f1 100644 --- a/frontend/rust-lib/flowy-derive/src/node/mod.rs +++ b/frontend/rust-lib/flowy-derive/src/node/mod.rs @@ -2,63 +2,63 @@ use flowy_ast::{ASTContainer, ASTField, ASTResult}; use proc_macro2::TokenStream; pub fn expand_derive(input: &syn::DeriveInput) -> Result> { - let ast_result = ASTResult::new(); - let cont = match ASTContainer::from_ast(&ast_result, input) { - Some(cont) => cont, - None => return Err(ast_result.check().unwrap_err()), - }; + let ast_result = ASTResult::new(); + let cont = match ASTContainer::from_ast(&ast_result, input) { + Some(cont) => cont, + None => return Err(ast_result.check().unwrap_err()), + }; - let mut token_stream: TokenStream = TokenStream::default(); - token_stream.extend(make_helper_funcs_token_stream(&cont)); - token_stream.extend(make_to_node_data_token_stream(&cont)); + let mut token_stream: TokenStream = TokenStream::default(); + token_stream.extend(make_helper_funcs_token_stream(&cont)); + token_stream.extend(make_to_node_data_token_stream(&cont)); - if let Some(get_value_token_stream) = make_get_set_value_token_steam(&cont) { - token_stream.extend(get_value_token_stream); - } + if let Some(get_value_token_stream) = make_get_set_value_token_steam(&cont) { + token_stream.extend(get_value_token_stream); + } - token_stream.extend(make_alter_children_token_stream(&ast_result, &cont)); - ast_result.check()?; - Ok(token_stream) + token_stream.extend(make_alter_children_token_stream(&ast_result, &cont)); + ast_result.check()?; + Ok(token_stream) } pub fn make_helper_funcs_token_stream(ast: &ASTContainer) -> TokenStream { - let mut token_streams = TokenStream::default(); - let struct_ident = &ast.ident; - token_streams.extend(quote! { - impl #struct_ident { - pub fn get_path(&self) -> Option { - let node_id = &self.node_id?; - Some(self.tree.read().path_from_node_id(node_id.clone())) - } - } - }); - token_streams + let mut token_streams = TokenStream::default(); + let struct_ident = &ast.ident; + token_streams.extend(quote! { + impl #struct_ident { + pub fn get_path(&self) -> Option { + let node_id = &self.node_id?; + Some(self.tree.read().path_from_node_id(node_id.clone())) + } + } + }); + token_streams } pub fn make_alter_children_token_stream(ast_result: &ASTResult, ast: &ASTContainer) -> TokenStream { - let mut token_streams = TokenStream::default(); - let children_fields = ast - .data - .all_fields() - .filter(|field| field.node_attrs.has_child) - .collect::>(); + let mut token_streams = TokenStream::default(); + let children_fields = ast + .data + .all_fields() + .filter(|field| field.node_attrs.has_child) + .collect::>(); - if !children_fields.is_empty() { - let struct_ident = &ast.ident; - if children_fields.len() > 1 { - ast_result.error_spanned_by(struct_ident, "Only one children property"); - return token_streams; - } - let children_field = children_fields.first().unwrap(); - let field_name = children_field.name().unwrap(); - let child_name = children_field.node_attrs.child_name.as_ref().unwrap(); - let get_func_name = format_ident!("get_{}", child_name.value()); - let get_mut_func_name = format_ident!("get_mut_{}", child_name.value()); - let add_func_name = format_ident!("add_{}", child_name.value()); - let remove_func_name = format_ident!("remove_{}", child_name.value()); - let ty = children_field.bracket_inner_ty.as_ref().unwrap().clone(); + if !children_fields.is_empty() { + let struct_ident = &ast.ident; + if children_fields.len() > 1 { + ast_result.error_spanned_by(struct_ident, "Only one children property"); + return token_streams; + } + let children_field = children_fields.first().unwrap(); + let field_name = children_field.name().unwrap(); + let child_name = children_field.node_attrs.child_name.as_ref().unwrap(); + let get_func_name = format_ident!("get_{}", child_name.value()); + let get_mut_func_name = format_ident!("get_mut_{}", child_name.value()); + let add_func_name = format_ident!("add_{}", child_name.value()); + let remove_func_name = format_ident!("remove_{}", child_name.value()); + let ty = children_field.bracket_inner_ty.as_ref().unwrap().clone(); - token_streams.extend(quote! { + token_streams.extend(quote! { impl #struct_ident { pub fn #get_func_name>(&self, id: T) -> Option<&#ty> { let id = id.as_ref(); @@ -112,108 +112,114 @@ pub fn make_alter_children_token_stream(ast_result: &ASTResult, ast: &ASTContain } } }); - } + } - token_streams + token_streams } pub fn make_to_node_data_token_stream(ast: &ASTContainer) -> TokenStream { - let struct_ident = &ast.ident; - let mut token_streams = TokenStream::default(); - let node_type = ast - .node_type - .as_ref() - .expect("Define the type of the node by using #[node_type = \"xx\" in the struct"); - let set_key_values = ast - .data - .all_fields() - .filter(|field| !field.node_attrs.has_child) - .flat_map(|field| { - let mut field_name = field.name().expect("the name of the field should not be empty"); - let original_field_name = field.name().expect("the name of the field should not be empty"); - if let Some(rename) = &field.node_attrs.rename { - field_name = format_ident!("{}", rename.value()); - } - let field_name_str = field_name.to_string(); - quote! { - .insert_attribute(#field_name_str, self.#original_field_name.clone()) - } - }); - - let children_fields = ast - .data - .all_fields() - .filter(|field| field.node_attrs.has_child) - .collect::>(); - - let childrens_token_streams = match children_fields.is_empty() { - true => { - quote! { - let children = vec![]; - } - } - false => { - let children_field = children_fields.first().unwrap(); - let original_field_name = children_field - .name() - .expect("the name of the field should not be empty"); - quote! { - let children = self.#original_field_name.iter().map(|value| value.to_node_data()).collect::>(); - } - } - }; - - token_streams.extend(quote! { - impl ToNodeData for #struct_ident { - fn to_node_data(&self) -> NodeData { - #childrens_token_streams - - let builder = NodeDataBuilder::new(#node_type) - #(#set_key_values)* - .extend_node_data(children); - - builder.build() - } - } + let struct_ident = &ast.ident; + let mut token_streams = TokenStream::default(); + let node_type = ast + .node_type + .as_ref() + .expect("Define the type of the node by using #[node_type = \"xx\" in the struct"); + let set_key_values = ast + .data + .all_fields() + .filter(|field| !field.node_attrs.has_child) + .flat_map(|field| { + let mut field_name = field + .name() + .expect("the name of the field should not be empty"); + let original_field_name = field + .name() + .expect("the name of the field should not be empty"); + if let Some(rename) = &field.node_attrs.rename { + field_name = format_ident!("{}", rename.value()); + } + let field_name_str = field_name.to_string(); + quote! { + .insert_attribute(#field_name_str, self.#original_field_name.clone()) + } }); - token_streams + let children_fields = ast + .data + .all_fields() + .filter(|field| field.node_attrs.has_child) + .collect::>(); + + let childrens_token_streams = match children_fields.is_empty() { + true => { + quote! { + let children = vec![]; + } + }, + false => { + let children_field = children_fields.first().unwrap(); + let original_field_name = children_field + .name() + .expect("the name of the field should not be empty"); + quote! { + let children = self.#original_field_name.iter().map(|value| value.to_node_data()).collect::>(); + } + }, + }; + + token_streams.extend(quote! { + impl ToNodeData for #struct_ident { + fn to_node_data(&self) -> NodeData { + #childrens_token_streams + + let builder = NodeDataBuilder::new(#node_type) + #(#set_key_values)* + .extend_node_data(children); + + builder.build() + } + } + }); + + token_streams } pub fn make_get_set_value_token_steam(ast: &ASTContainer) -> Option { - let struct_ident = &ast.ident; - let mut token_streams = TokenStream::default(); + let struct_ident = &ast.ident; + let mut token_streams = TokenStream::default(); - let tree = format_ident!("tree"); - for field in ast.data.all_fields() { - if field.node_attrs.has_child { - continue; - } + let tree = format_ident!("tree"); + for field in ast.data.all_fields() { + if field.node_attrs.has_child { + continue; + } - let mut field_name = field.name().expect("the name of the field should not be empty"); - if let Some(rename) = &field.node_attrs.rename { - field_name = format_ident!("{}", rename.value()); - } + let mut field_name = field + .name() + .expect("the name of the field should not be empty"); + if let Some(rename) = &field.node_attrs.rename { + field_name = format_ident!("{}", rename.value()); + } - let field_name_str = field_name.to_string(); - let get_func_name = format_ident!("get_{}", field_name); - let set_func_name = format_ident!("set_{}", field_name); - let get_value_return_ty = field.ty; - let set_value_input_ty = field.ty; + let field_name_str = field_name.to_string(); + let get_func_name = format_ident!("get_{}", field_name); + let set_func_name = format_ident!("set_{}", field_name); + let get_value_return_ty = field.ty; + let set_value_input_ty = field.ty; - if let Some(get_value_with_fn) = &field.node_attrs.get_node_value_with { - token_streams.extend(quote! { - impl #struct_ident { - pub fn #get_func_name(&self) -> Option<#get_value_return_ty> { - let node_id = self.node_id.as_ref()?; - #get_value_with_fn(self.#tree.clone(), node_id, #field_name_str) - } - } - }); - } + if let Some(get_value_with_fn) = &field.node_attrs.get_node_value_with { + token_streams.extend(quote! { + impl #struct_ident { + pub fn #get_func_name(&self) -> Option<#get_value_return_ty> { + let node_id = self.node_id.as_ref()?; + #get_value_with_fn(self.#tree.clone(), node_id, #field_name_str) + } + } + }); + } - if let Some(set_value_with_fn) = &field.node_attrs.set_node_value_with { - token_streams.extend(quote! { + if let Some(set_value_with_fn) = &field.node_attrs.set_node_value_with { + token_streams.extend(quote! { impl #struct_ident { pub fn #set_func_name(&self, value: #set_value_input_ty) { if let Some(node_id) = self.node_id.as_ref() { @@ -222,7 +228,7 @@ pub fn make_get_set_value_token_steam(ast: &ASTContainer) -> Option } } }); - } } - Some(token_streams) + } + Some(token_streams) } diff --git a/frontend/rust-lib/flowy-derive/src/proto_buf/deserialize.rs b/frontend/rust-lib/flowy-derive/src/proto_buf/deserialize.rs index a781fae30e..0762ae3e6f 100644 --- a/frontend/rust-lib/flowy-derive/src/proto_buf/deserialize.rs +++ b/frontend/rust-lib/flowy-derive/src/proto_buf/deserialize.rs @@ -3,245 +3,261 @@ use flowy_ast::*; use proc_macro2::{Span, TokenStream}; pub fn make_de_token_steam(ast_result: &ASTResult, ast: &ASTContainer) -> Option { - let pb_ty = ast.pb_attrs.pb_struct_type()?; - let struct_ident = &ast.ident; + let pb_ty = ast.pb_attrs.pb_struct_type()?; + let struct_ident = &ast.ident; - let build_take_fields = ast - .data - .all_fields() - .filter(|f| !f.pb_attrs.skip_pb_deserializing()) - .flat_map(|field| { - if let Some(func) = field.pb_attrs.deserialize_pb_with() { - let member = &field.member; - Some(quote! { o.#member=#struct_ident::#func(pb); }) - } else if field.pb_attrs.is_one_of() { - token_stream_for_one_of(ast_result, field) - } else { - token_stream_for_field(ast_result, &field.member, field.ty, false) - } - }); + let build_take_fields = ast + .data + .all_fields() + .filter(|f| !f.pb_attrs.skip_pb_deserializing()) + .flat_map(|field| { + if let Some(func) = field.pb_attrs.deserialize_pb_with() { + let member = &field.member; + Some(quote! { o.#member=#struct_ident::#func(pb); }) + } else if field.pb_attrs.is_one_of() { + token_stream_for_one_of(ast_result, field) + } else { + token_stream_for_field(ast_result, &field.member, field.ty, false) + } + }); - let de_token_stream: TokenStream = quote! { - impl std::convert::TryFrom for #struct_ident { - type Error = ::protobuf::ProtobufError; - fn try_from(bytes: bytes::Bytes) -> Result { - Self::try_from(&bytes) - } - } + let de_token_stream: TokenStream = quote! { + impl std::convert::TryFrom for #struct_ident { + type Error = ::protobuf::ProtobufError; + fn try_from(bytes: bytes::Bytes) -> Result { + Self::try_from(&bytes) + } + } - impl std::convert::TryFrom<&bytes::Bytes> for #struct_ident { - type Error = ::protobuf::ProtobufError; - fn try_from(bytes: &bytes::Bytes) -> Result { - let pb: crate::protobuf::#pb_ty = ::protobuf::Message::parse_from_bytes(bytes)?; - Ok(#struct_ident::from(pb)) - } - } + impl std::convert::TryFrom<&bytes::Bytes> for #struct_ident { + type Error = ::protobuf::ProtobufError; + fn try_from(bytes: &bytes::Bytes) -> Result { + let pb: crate::protobuf::#pb_ty = ::protobuf::Message::parse_from_bytes(bytes)?; + Ok(#struct_ident::from(pb)) + } + } - impl std::convert::TryFrom<&[u8]> for #struct_ident { - type Error = ::protobuf::ProtobufError; - fn try_from(bytes: &[u8]) -> Result { - let pb: crate::protobuf::#pb_ty = ::protobuf::Message::parse_from_bytes(bytes)?; - Ok(#struct_ident::from(pb)) - } - } + impl std::convert::TryFrom<&[u8]> for #struct_ident { + type Error = ::protobuf::ProtobufError; + fn try_from(bytes: &[u8]) -> Result { + let pb: crate::protobuf::#pb_ty = ::protobuf::Message::parse_from_bytes(bytes)?; + Ok(#struct_ident::from(pb)) + } + } - impl std::convert::From for #struct_ident { - fn from(mut pb: crate::protobuf::#pb_ty) -> Self { - let mut o = Self::default(); - #(#build_take_fields)* - o - } - } - }; + impl std::convert::From for #struct_ident { + fn from(mut pb: crate::protobuf::#pb_ty) -> Self { + let mut o = Self::default(); + #(#build_take_fields)* + o + } + } + }; - Some(de_token_stream) - // None + Some(de_token_stream) + // None } fn token_stream_for_one_of(ast_result: &ASTResult, field: &ASTField) -> Option { - let member = &field.member; - let ident = get_member_ident(ast_result, member)?; - let ty_info = match parse_ty(ast_result, field.ty) { - Ok(ty_info) => ty_info, - Err(e) => { - eprintln!("token_stream_for_one_of failed: {:?} with error: {}", member, e); - panic!(); - } - }?; - let bracketed_ty_info = ty_info.bracket_ty_info.as_ref().as_ref(); - let has_func = format_ident!("has_{}", ident.to_string()); - match ident_category(bracketed_ty_info.unwrap().ident) { - TypeCategory::Enum => { - let get_func = format_ident!("get_{}", ident.to_string()); - let ty = bracketed_ty_info.unwrap().ty; - Some(quote! { - if pb.#has_func() { - let enum_de_from_pb = #ty::from(&pb.#get_func()); - o.#member = Some(enum_de_from_pb); - } - }) - } - TypeCategory::Primitive => { - let get_func = format_ident!("get_{}", ident.to_string()); - Some(quote! { - if pb.#has_func() { - o.#member=Some(pb.#get_func()); - } - }) - } - TypeCategory::Str => { - let take_func = format_ident!("take_{}", ident.to_string()); - Some(quote! { - if pb.#has_func() { - o.#member=Some(pb.#take_func()); - } - }) - } - TypeCategory::Array => { - let take_func = format_ident!("take_{}", ident.to_string()); - Some(quote! { - if pb.#has_func() { - o.#member=Some(pb.#take_func()); - } - }) - } - _ => { - let take_func = format_ident!("take_{}", ident.to_string()); - let ty = bracketed_ty_info.unwrap().ty; - Some(quote! { - if pb.#has_func() { - let val = #ty::from(pb.#take_func()); - o.#member=Some(val); - } - }) - } - } + let member = &field.member; + let ident = get_member_ident(ast_result, member)?; + let ty_info = match parse_ty(ast_result, field.ty) { + Ok(ty_info) => ty_info, + Err(e) => { + eprintln!( + "token_stream_for_one_of failed: {:?} with error: {}", + member, e + ); + panic!(); + }, + }?; + let bracketed_ty_info = ty_info.bracket_ty_info.as_ref().as_ref(); + let has_func = format_ident!("has_{}", ident.to_string()); + match ident_category(bracketed_ty_info.unwrap().ident) { + TypeCategory::Enum => { + let get_func = format_ident!("get_{}", ident.to_string()); + let ty = bracketed_ty_info.unwrap().ty; + Some(quote! { + if pb.#has_func() { + let enum_de_from_pb = #ty::from(&pb.#get_func()); + o.#member = Some(enum_de_from_pb); + } + }) + }, + TypeCategory::Primitive => { + let get_func = format_ident!("get_{}", ident.to_string()); + Some(quote! { + if pb.#has_func() { + o.#member=Some(pb.#get_func()); + } + }) + }, + TypeCategory::Str => { + let take_func = format_ident!("take_{}", ident.to_string()); + Some(quote! { + if pb.#has_func() { + o.#member=Some(pb.#take_func()); + } + }) + }, + TypeCategory::Array => { + let take_func = format_ident!("take_{}", ident.to_string()); + Some(quote! { + if pb.#has_func() { + o.#member=Some(pb.#take_func()); + } + }) + }, + _ => { + let take_func = format_ident!("take_{}", ident.to_string()); + let ty = bracketed_ty_info.unwrap().ty; + Some(quote! { + if pb.#has_func() { + let val = #ty::from(pb.#take_func()); + o.#member=Some(val); + } + }) + }, + } } fn token_stream_for_field( - ast_result: &ASTResult, - member: &syn::Member, - ty: &syn::Type, - is_option: bool, + ast_result: &ASTResult, + member: &syn::Member, + ty: &syn::Type, + is_option: bool, ) -> Option { - let ident = get_member_ident(ast_result, member)?; - let ty_info = match parse_ty(ast_result, ty) { - Ok(ty_info) => ty_info, - Err(e) => { - eprintln!("token_stream_for_field: {:?} with error: {}", member, e); - panic!() - } - }?; - match ident_category(ty_info.ident) { - TypeCategory::Array => { - assert_bracket_ty_is_some(ast_result, &ty_info); - token_stream_for_vec(ast_result, member, &ty_info.bracket_ty_info.unwrap()) - } - TypeCategory::Map => { - assert_bracket_ty_is_some(ast_result, &ty_info); - token_stream_for_map(ast_result, member, &ty_info.bracket_ty_info.unwrap()) - } - TypeCategory::Protobuf => { - // if the type wrapped by SingularPtrField, should call take first - let take = syn::Ident::new("take", Span::call_site()); - // inner_type_ty would be the type of the field. (e.g value of AnyData) - let ty = ty_info.ty; - Some(quote! { - let some_value = pb.#member.#take(); - if some_value.is_some() { - let struct_de_from_pb = #ty::from(some_value.unwrap()); - o.#member = struct_de_from_pb; - } - }) - } + let ident = get_member_ident(ast_result, member)?; + let ty_info = match parse_ty(ast_result, ty) { + Ok(ty_info) => ty_info, + Err(e) => { + eprintln!("token_stream_for_field: {:?} with error: {}", member, e); + panic!() + }, + }?; + match ident_category(ty_info.ident) { + TypeCategory::Array => { + assert_bracket_ty_is_some(ast_result, &ty_info); + token_stream_for_vec(ast_result, member, &ty_info.bracket_ty_info.unwrap()) + }, + TypeCategory::Map => { + assert_bracket_ty_is_some(ast_result, &ty_info); + token_stream_for_map(ast_result, member, &ty_info.bracket_ty_info.unwrap()) + }, + TypeCategory::Protobuf => { + // if the type wrapped by SingularPtrField, should call take first + let take = syn::Ident::new("take", Span::call_site()); + // inner_type_ty would be the type of the field. (e.g value of AnyData) + let ty = ty_info.ty; + Some(quote! { + let some_value = pb.#member.#take(); + if some_value.is_some() { + let struct_de_from_pb = #ty::from(some_value.unwrap()); + o.#member = struct_de_from_pb; + } + }) + }, - TypeCategory::Enum => { - let ty = ty_info.ty; - Some(quote! { - let enum_de_from_pb = #ty::from(&pb.#member); - o.#member = enum_de_from_pb; + TypeCategory::Enum => { + let ty = ty_info.ty; + Some(quote! { + let enum_de_from_pb = #ty::from(&pb.#member); + o.#member = enum_de_from_pb; - }) - } - TypeCategory::Str => { - let take_ident = syn::Ident::new(&format!("take_{}", ident), Span::call_site()); - if is_option { - Some(quote! { - if pb.#member.is_empty() { - o.#member = None; - } else { - o.#member = Some(pb.#take_ident()); - } - }) + }) + }, + TypeCategory::Str => { + let take_ident = syn::Ident::new(&format!("take_{}", ident), Span::call_site()); + if is_option { + Some(quote! { + if pb.#member.is_empty() { + o.#member = None; } else { - Some(quote! { - o.#member = pb.#take_ident(); - }) + o.#member = Some(pb.#take_ident()); } - } - TypeCategory::Opt => token_stream_for_field(ast_result, member, ty_info.bracket_ty_info.unwrap().ty, true), - TypeCategory::Primitive | TypeCategory::Bytes => { - // eprintln!("😄 #{:?}", &field.name().unwrap()); - if is_option { - Some(quote! { o.#member = Some(pb.#member.clone()); }) - } else { - Some(quote! { o.#member = pb.#member.clone(); }) - } - } - } + }) + } else { + Some(quote! { + o.#member = pb.#take_ident(); + }) + } + }, + TypeCategory::Opt => token_stream_for_field( + ast_result, + member, + ty_info.bracket_ty_info.unwrap().ty, + true, + ), + TypeCategory::Primitive | TypeCategory::Bytes => { + // eprintln!("😄 #{:?}", &field.name().unwrap()); + if is_option { + Some(quote! { o.#member = Some(pb.#member.clone()); }) + } else { + Some(quote! { o.#member = pb.#member.clone(); }) + } + }, + } } -fn token_stream_for_vec(ctxt: &ASTResult, member: &syn::Member, bracketed_type: &TyInfo) -> Option { - let ident = get_member_ident(ctxt, member)?; +fn token_stream_for_vec( + ctxt: &ASTResult, + member: &syn::Member, + bracketed_type: &TyInfo, +) -> Option { + let ident = get_member_ident(ctxt, member)?; - match ident_category(bracketed_type.ident) { - TypeCategory::Protobuf => { - let ty = bracketed_type.ty; - // Deserialize from pb struct of type vec, should call take_xx(), get the - // repeated_field and then calling the into_iter。 - let take_ident = format_ident!("take_{}", ident.to_string()); - Some(quote! { - o.#member = pb.#take_ident() - .into_iter() - .map(|m| #ty::from(m)) - .collect(); - }) - } - TypeCategory::Bytes => { - // Vec - Some(quote! { - o.#member = pb.#member.clone(); - }) - } - _ => { - // String - let take_ident = format_ident!("take_{}", ident.to_string()); - Some(quote! { - o.#member = pb.#take_ident().into_vec(); - }) - } - } + match ident_category(bracketed_type.ident) { + TypeCategory::Protobuf => { + let ty = bracketed_type.ty; + // Deserialize from pb struct of type vec, should call take_xx(), get the + // repeated_field and then calling the into_iter。 + let take_ident = format_ident!("take_{}", ident.to_string()); + Some(quote! { + o.#member = pb.#take_ident() + .into_iter() + .map(|m| #ty::from(m)) + .collect(); + }) + }, + TypeCategory::Bytes => { + // Vec + Some(quote! { + o.#member = pb.#member.clone(); + }) + }, + _ => { + // String + let take_ident = format_ident!("take_{}", ident.to_string()); + Some(quote! { + o.#member = pb.#take_ident().into_vec(); + }) + }, + } } -fn token_stream_for_map(ast_result: &ASTResult, member: &syn::Member, ty_info: &TyInfo) -> Option { - let ident = get_member_ident(ast_result, member)?; - let take_ident = format_ident!("take_{}", ident.to_string()); - let ty = ty_info.ty; +fn token_stream_for_map( + ast_result: &ASTResult, + member: &syn::Member, + ty_info: &TyInfo, +) -> Option { + let ident = get_member_ident(ast_result, member)?; + let take_ident = format_ident!("take_{}", ident.to_string()); + let ty = ty_info.ty; - match ident_category(ty_info.ident) { - TypeCategory::Protobuf => Some(quote! { - let mut m: std::collections::HashMap = std::collections::HashMap::new(); - pb.#take_ident().into_iter().for_each(|(k,v)| { - m.insert(k.clone(), #ty::from(v)); - }); - o.#member = m; - }), - _ => Some(quote! { - let mut m: std::collections::HashMap = std::collections::HashMap::new(); - pb.#take_ident().into_iter().for_each(|(k,mut v)| { - m.insert(k.clone(), v); - }); - o.#member = m; - }), - } + match ident_category(ty_info.ident) { + TypeCategory::Protobuf => Some(quote! { + let mut m: std::collections::HashMap = std::collections::HashMap::new(); + pb.#take_ident().into_iter().for_each(|(k,v)| { + m.insert(k.clone(), #ty::from(v)); + }); + o.#member = m; + }), + _ => Some(quote! { + let mut m: std::collections::HashMap = std::collections::HashMap::new(); + pb.#take_ident().into_iter().for_each(|(k,mut v)| { + m.insert(k.clone(), v); + }); + o.#member = m; + }), + } } diff --git a/frontend/rust-lib/flowy-derive/src/proto_buf/enum_serde.rs b/frontend/rust-lib/flowy-derive/src/proto_buf/enum_serde.rs index 63fa0e4883..01aec5dae1 100644 --- a/frontend/rust-lib/flowy-derive/src/proto_buf/enum_serde.rs +++ b/frontend/rust-lib/flowy-derive/src/proto_buf/enum_serde.rs @@ -3,37 +3,37 @@ use proc_macro2::TokenStream; #[allow(dead_code)] pub fn make_enum_token_stream(_ast_result: &ASTResult, cont: &ASTContainer) -> Option { - let enum_ident = &cont.ident; - let pb_enum = cont.pb_attrs.pb_enum_type()?; - let build_to_pb_enum = cont.data.all_idents().map(|i| { - let token_stream: TokenStream = quote! { - #enum_ident::#i => crate::protobuf::#pb_enum::#i, - }; - token_stream - }); + let enum_ident = &cont.ident; + let pb_enum = cont.pb_attrs.pb_enum_type()?; + let build_to_pb_enum = cont.data.all_idents().map(|i| { + let token_stream: TokenStream = quote! { + #enum_ident::#i => crate::protobuf::#pb_enum::#i, + }; + token_stream + }); - let build_from_pb_enum = cont.data.all_idents().map(|i| { - let token_stream: TokenStream = quote! { - crate::protobuf::#pb_enum::#i => #enum_ident::#i, - }; - token_stream - }); + let build_from_pb_enum = cont.data.all_idents().map(|i| { + let token_stream: TokenStream = quote! { + crate::protobuf::#pb_enum::#i => #enum_ident::#i, + }; + token_stream + }); - Some(quote! { - impl std::convert::From<&crate::protobuf::#pb_enum> for #enum_ident { - fn from(pb:&crate::protobuf::#pb_enum) -> Self { - match pb { - #(#build_from_pb_enum)* - } - } - } + Some(quote! { + impl std::convert::From<&crate::protobuf::#pb_enum> for #enum_ident { + fn from(pb:&crate::protobuf::#pb_enum) -> Self { + match pb { + #(#build_from_pb_enum)* + } + } + } - impl std::convert::From<#enum_ident> for crate::protobuf::#pb_enum{ - fn from(o: #enum_ident) -> crate::protobuf::#pb_enum { - match o { - #(#build_to_pb_enum)* - } - } - } - }) + impl std::convert::From<#enum_ident> for crate::protobuf::#pb_enum{ + fn from(o: #enum_ident) -> crate::protobuf::#pb_enum { + match o { + #(#build_to_pb_enum)* + } + } + } + }) } diff --git a/frontend/rust-lib/flowy-derive/src/proto_buf/mod.rs b/frontend/rust-lib/flowy-derive/src/proto_buf/mod.rs index 68c5b9d80e..03271482ed 100644 --- a/frontend/rust-lib/flowy-derive/src/proto_buf/mod.rs +++ b/frontend/rust-lib/flowy-derive/src/proto_buf/mod.rs @@ -4,48 +4,49 @@ mod serialize; mod util; use crate::proto_buf::{ - deserialize::make_de_token_steam, enum_serde::make_enum_token_stream, serialize::make_se_token_stream, + deserialize::make_de_token_steam, enum_serde::make_enum_token_stream, + serialize::make_se_token_stream, }; use flowy_ast::*; use proc_macro2::TokenStream; use std::default::Default; pub fn expand_derive(input: &syn::DeriveInput) -> Result> { - let ast_result = ASTResult::new(); - let cont = match ASTContainer::from_ast(&ast_result, input) { - Some(cont) => cont, - None => return Err(ast_result.check().unwrap_err()), - }; + let ast_result = ASTResult::new(); + let cont = match ASTContainer::from_ast(&ast_result, input) { + Some(cont) => cont, + None => return Err(ast_result.check().unwrap_err()), + }; - let mut token_stream: TokenStream = TokenStream::default(); + let mut token_stream: TokenStream = TokenStream::default(); - if let Some(de_token_stream) = make_de_token_steam(&ast_result, &cont) { - token_stream.extend(de_token_stream); - } + if let Some(de_token_stream) = make_de_token_steam(&ast_result, &cont) { + token_stream.extend(de_token_stream); + } - if let Some(se_token_stream) = make_se_token_stream(&ast_result, &cont) { - token_stream.extend(se_token_stream); - } + if let Some(se_token_stream) = make_se_token_stream(&ast_result, &cont) { + token_stream.extend(se_token_stream); + } - ast_result.check()?; - Ok(token_stream) + ast_result.check()?; + Ok(token_stream) } pub fn expand_enum_derive(input: &syn::DeriveInput) -> Result> { - let ast_result = ASTResult::new(); - let cont = match ASTContainer::from_ast(&ast_result, input) { - Some(cont) => cont, - None => return Err(ast_result.check().unwrap_err()), - }; + let ast_result = ASTResult::new(); + let cont = match ASTContainer::from_ast(&ast_result, input) { + Some(cont) => cont, + None => return Err(ast_result.check().unwrap_err()), + }; - let mut token_stream: TokenStream = TokenStream::default(); + let mut token_stream: TokenStream = TokenStream::default(); - if let Some(enum_token_stream) = make_enum_token_stream(&ast_result, &cont) { - token_stream.extend(enum_token_stream); - } + if let Some(enum_token_stream) = make_enum_token_stream(&ast_result, &cont) { + token_stream.extend(enum_token_stream); + } - ast_result.check()?; - Ok(token_stream) + ast_result.check()?; + Ok(token_stream) } // #[macro_use] // macro_rules! impl_try_for_primitive_type { diff --git a/frontend/rust-lib/flowy-derive/src/proto_buf/serialize.rs b/frontend/rust-lib/flowy-derive/src/proto_buf/serialize.rs index 97481acc88..4e1ee2d6ea 100644 --- a/frontend/rust-lib/flowy-derive/src/proto_buf/serialize.rs +++ b/frontend/rust-lib/flowy-derive/src/proto_buf/serialize.rs @@ -4,181 +4,213 @@ use flowy_ast::*; use proc_macro2::TokenStream; pub fn make_se_token_stream(ast_result: &ASTResult, ast: &ASTContainer) -> Option { - let pb_ty = ast.pb_attrs.pb_struct_type()?; - let struct_ident = &ast.ident; + let pb_ty = ast.pb_attrs.pb_struct_type()?; + let struct_ident = &ast.ident; - let build_set_pb_fields = ast - .data - .all_fields() - .filter(|f| !f.pb_attrs.skip_pb_serializing()) - .flat_map(|field| se_token_stream_for_field(ast_result, field, false)); + let build_set_pb_fields = ast + .data + .all_fields() + .filter(|f| !f.pb_attrs.skip_pb_serializing()) + .flat_map(|field| se_token_stream_for_field(ast_result, field, false)); - let se_token_stream: TokenStream = quote! { + let se_token_stream: TokenStream = quote! { - impl std::convert::TryInto for #struct_ident { - type Error = ::protobuf::ProtobufError; - fn try_into(self) -> Result { - use protobuf::Message; - let pb: crate::protobuf::#pb_ty = self.into(); - let bytes = pb.write_to_bytes()?; - Ok(bytes::Bytes::from(bytes)) - } - } + impl std::convert::TryInto for #struct_ident { + type Error = ::protobuf::ProtobufError; + fn try_into(self) -> Result { + use protobuf::Message; + let pb: crate::protobuf::#pb_ty = self.into(); + let bytes = pb.write_to_bytes()?; + Ok(bytes::Bytes::from(bytes)) + } + } - impl std::convert::From<#struct_ident> for crate::protobuf::#pb_ty { - fn from(mut o: #struct_ident) -> crate::protobuf::#pb_ty { - let mut pb = crate::protobuf::#pb_ty::new(); - #(#build_set_pb_fields)* - pb - } - } - }; + impl std::convert::From<#struct_ident> for crate::protobuf::#pb_ty { + fn from(mut o: #struct_ident) -> crate::protobuf::#pb_ty { + let mut pb = crate::protobuf::#pb_ty::new(); + #(#build_set_pb_fields)* + pb + } + } + }; - Some(se_token_stream) + Some(se_token_stream) } -fn se_token_stream_for_field(ast_result: &ASTResult, field: &ASTField, _take: bool) -> Option { - if let Some(func) = &field.pb_attrs.serialize_pb_with() { - let member = &field.member; - Some(quote! { pb.#member=o.#func(); }) - } else if field.pb_attrs.is_one_of() { - token_stream_for_one_of(ast_result, field) - } else { - gen_token_stream(ast_result, &field.member, field.ty, false) - } +fn se_token_stream_for_field( + ast_result: &ASTResult, + field: &ASTField, + _take: bool, +) -> Option { + if let Some(func) = &field.pb_attrs.serialize_pb_with() { + let member = &field.member; + Some(quote! { pb.#member=o.#func(); }) + } else if field.pb_attrs.is_one_of() { + token_stream_for_one_of(ast_result, field) + } else { + gen_token_stream(ast_result, &field.member, field.ty, false) + } } fn token_stream_for_one_of(ast_result: &ASTResult, field: &ASTField) -> Option { - let member = &field.member; - let ident = get_member_ident(ast_result, member)?; - let ty_info = match parse_ty(ast_result, field.ty) { - Ok(ty_info) => ty_info, - Err(e) => { - eprintln!("token_stream_for_one_of failed: {:?} with error: {}", member, e); - panic!(); + let member = &field.member; + let ident = get_member_ident(ast_result, member)?; + let ty_info = match parse_ty(ast_result, field.ty) { + Ok(ty_info) => ty_info, + Err(e) => { + eprintln!( + "token_stream_for_one_of failed: {:?} with error: {}", + member, e + ); + panic!(); + }, + }?; + + let bracketed_ty_info = ty_info.bracket_ty_info.as_ref().as_ref(); + + let set_func = format_ident!("set_{}", ident.to_string()); + + match ident_category(bracketed_ty_info.unwrap().ident) { + TypeCategory::Protobuf => Some(quote! { + match o.#member { + Some(s) => { pb.#set_func(s.into()) } + None => {} } - }?; - - let bracketed_ty_info = ty_info.bracket_ty_info.as_ref().as_ref(); - - let set_func = format_ident!("set_{}", ident.to_string()); - - match ident_category(bracketed_ty_info.unwrap().ident) { - TypeCategory::Protobuf => Some(quote! { - match o.#member { - Some(s) => { pb.#set_func(s.into()) } - None => {} - } - }), - TypeCategory::Enum => Some(quote! { - match o.#member { - Some(s) => { pb.#set_func(s.into()) } - None => {} - } - }), - _ => Some(quote! { - match o.#member { - Some(ref s) => { pb.#set_func(s.clone()) } - None => {} - } - }), - } + }), + TypeCategory::Enum => Some(quote! { + match o.#member { + Some(s) => { pb.#set_func(s.into()) } + None => {} + } + }), + _ => Some(quote! { + match o.#member { + Some(ref s) => { pb.#set_func(s.clone()) } + None => {} + } + }), + } } fn gen_token_stream( - ast_result: &ASTResult, - member: &syn::Member, - ty: &syn::Type, - is_option: bool, + ast_result: &ASTResult, + member: &syn::Member, + ty: &syn::Type, + is_option: bool, ) -> Option { - let ty_info = match parse_ty(ast_result, ty) { - Ok(ty_info) => ty_info, - Err(e) => { - eprintln!("gen_token_stream failed: {:?} with error: {}", member, e); - panic!(); - } - }?; - match ident_category(ty_info.ident) { - TypeCategory::Array => token_stream_for_vec(ast_result, member, ty_info.bracket_ty_info.unwrap().ty), - TypeCategory::Map => token_stream_for_map(ast_result, member, ty_info.bracket_ty_info.unwrap().ty), - TypeCategory::Str => { - if is_option { - Some(quote! { - match o.#member { - Some(ref s) => { pb.#member = s.to_string().clone(); } - None => { pb.#member = String::new(); } - } - }) - } else { - Some(quote! { pb.#member = o.#member.clone(); }) + let ty_info = match parse_ty(ast_result, ty) { + Ok(ty_info) => ty_info, + Err(e) => { + eprintln!("gen_token_stream failed: {:?} with error: {}", member, e); + panic!(); + }, + }?; + match ident_category(ty_info.ident) { + TypeCategory::Array => { + token_stream_for_vec(ast_result, member, ty_info.bracket_ty_info.unwrap().ty) + }, + TypeCategory::Map => { + token_stream_for_map(ast_result, member, ty_info.bracket_ty_info.unwrap().ty) + }, + TypeCategory::Str => { + if is_option { + Some(quote! { + match o.#member { + Some(ref s) => { pb.#member = s.to_string().clone(); } + None => { pb.#member = String::new(); } } - } - TypeCategory::Protobuf => Some(quote! { pb.#member = ::protobuf::SingularPtrField::some(o.#member.into()); }), - TypeCategory::Opt => gen_token_stream(ast_result, member, ty_info.bracket_ty_info.unwrap().ty, true), - TypeCategory::Enum => { - // let pb_enum_ident = format_ident!("{}", ty_info.ident.to_string()); - // Some(quote! { - // flowy_protobuf::#pb_enum_ident::from_i32(self.#member.value()).unwrap(); - // }) - Some(quote! { - pb.#member = o.#member.into(); - }) - } - _ => Some(quote! { pb.#member = o.#member; }), - } + }) + } else { + Some(quote! { pb.#member = o.#member.clone(); }) + } + }, + TypeCategory::Protobuf => { + Some(quote! { pb.#member = ::protobuf::SingularPtrField::some(o.#member.into()); }) + }, + TypeCategory::Opt => gen_token_stream( + ast_result, + member, + ty_info.bracket_ty_info.unwrap().ty, + true, + ), + TypeCategory::Enum => { + // let pb_enum_ident = format_ident!("{}", ty_info.ident.to_string()); + // Some(quote! { + // flowy_protobuf::#pb_enum_ident::from_i32(self.#member.value()).unwrap(); + // }) + Some(quote! { + pb.#member = o.#member.into(); + }) + }, + _ => Some(quote! { pb.#member = o.#member; }), + } } // e.g. pub cells: Vec, the member will be cells, ty would be Vec -fn token_stream_for_vec(ast_result: &ASTResult, member: &syn::Member, ty: &syn::Type) -> Option { - let ty_info = match parse_ty(ast_result, ty) { - Ok(ty_info) => ty_info, - Err(e) => { - eprintln!("token_stream_for_vec failed: {:?} with error: {}", member, e); - panic!(); - } - }?; +fn token_stream_for_vec( + ast_result: &ASTResult, + member: &syn::Member, + ty: &syn::Type, +) -> Option { + let ty_info = match parse_ty(ast_result, ty) { + Ok(ty_info) => ty_info, + Err(e) => { + eprintln!( + "token_stream_for_vec failed: {:?} with error: {}", + member, e + ); + panic!(); + }, + }?; - match ident_category(ty_info.ident) { - TypeCategory::Protobuf => Some(quote! { - pb.#member = ::protobuf::RepeatedField::from_vec( - o.#member - .into_iter() - .map(|m| m.into()) - .collect()); - }), - TypeCategory::Bytes => Some(quote! { pb.#member = o.#member.clone(); }), + match ident_category(ty_info.ident) { + TypeCategory::Protobuf => Some(quote! { + pb.#member = ::protobuf::RepeatedField::from_vec( + o.#member + .into_iter() + .map(|m| m.into()) + .collect()); + }), + TypeCategory::Bytes => Some(quote! { pb.#member = o.#member.clone(); }), - _ => Some(quote! { - pb.#member = ::protobuf::RepeatedField::from_vec(o.#member.clone()); - }), - } + _ => Some(quote! { + pb.#member = ::protobuf::RepeatedField::from_vec(o.#member.clone()); + }), + } } // e.g. pub cells: HashMap -fn token_stream_for_map(ast_result: &ASTResult, member: &syn::Member, ty: &syn::Type) -> Option { - // The key of the hashmap must be string - let ty_info = match parse_ty(ast_result, ty) { - Ok(ty_info) => ty_info, - Err(e) => { - eprintln!("token_stream_for_map failed: {:?} with error: {}", member, e); - panic!(); - } - }?; - let value_ty = ty_info.ty; - match ident_category(ty_info.ident) { - TypeCategory::Protobuf => Some(quote! { - let mut m: std::collections::HashMap = std::collections::HashMap::new(); - o.#member.into_iter().for_each(|(k,v)| { - m.insert(k.clone(), v.into()); - }); - pb.#member = m; - }), - _ => Some(quote! { - let mut m: std::collections::HashMap = std::collections::HashMap::new(); - o.#member.iter().for_each(|(k,v)| { - m.insert(k.clone(), v.clone()); - }); - pb.#member = m; - }), - } +fn token_stream_for_map( + ast_result: &ASTResult, + member: &syn::Member, + ty: &syn::Type, +) -> Option { + // The key of the hashmap must be string + let ty_info = match parse_ty(ast_result, ty) { + Ok(ty_info) => ty_info, + Err(e) => { + eprintln!( + "token_stream_for_map failed: {:?} with error: {}", + member, e + ); + panic!(); + }, + }?; + let value_ty = ty_info.ty; + match ident_category(ty_info.ident) { + TypeCategory::Protobuf => Some(quote! { + let mut m: std::collections::HashMap = std::collections::HashMap::new(); + o.#member.into_iter().for_each(|(k,v)| { + m.insert(k.clone(), v.into()); + }); + pb.#member = m; + }), + _ => Some(quote! { + let mut m: std::collections::HashMap = std::collections::HashMap::new(); + o.#member.iter().for_each(|(k,v)| { + m.insert(k.clone(), v.clone()); + }); + pb.#member = m; + }), + } } diff --git a/frontend/rust-lib/flowy-derive/src/proto_buf/util.rs b/frontend/rust-lib/flowy-derive/src/proto_buf/util.rs index 37b841069a..53a0c62dfc 100644 --- a/frontend/rust-lib/flowy-derive/src/proto_buf/util.rs +++ b/frontend/rust-lib/flowy-derive/src/proto_buf/util.rs @@ -8,100 +8,112 @@ use std::sync::atomic::{AtomicBool, Ordering}; use walkdir::WalkDir; pub fn ident_category(ident: &syn::Ident) -> TypeCategory { - let ident_str = ident.to_string(); - category_from_str(ident_str) + let ident_str = ident.to_string(); + category_from_str(ident_str) } -pub(crate) fn get_member_ident<'a>(ast_result: &ASTResult, member: &'a syn::Member) -> Option<&'a syn::Ident> { - if let syn::Member::Named(ref ident) = member { - Some(ident) - } else { - ast_result.error_spanned_by(member, "Unsupported member, shouldn't be self.0".to_string()); - None - } +pub(crate) fn get_member_ident<'a>( + ast_result: &ASTResult, + member: &'a syn::Member, +) -> Option<&'a syn::Ident> { + if let syn::Member::Named(ref ident) = member { + Some(ident) + } else { + ast_result.error_spanned_by( + member, + "Unsupported member, shouldn't be self.0".to_string(), + ); + None + } } pub fn assert_bracket_ty_is_some(ast_result: &ASTResult, ty_info: &TyInfo) { - if ty_info.bracket_ty_info.is_none() { - ast_result.error_spanned_by(ty_info.ty, "Invalid bracketed type when gen de token steam".to_string()); - } + if ty_info.bracket_ty_info.is_none() { + ast_result.error_spanned_by( + ty_info.ty, + "Invalid bracketed type when gen de token steam".to_string(), + ); + } } lazy_static! { - static ref READ_FLAG: DashSet = DashSet::new(); - static ref CACHE_INFO: DashMap> = DashMap::new(); - static ref IS_LOAD: AtomicBool = AtomicBool::new(false); + static ref READ_FLAG: DashSet = DashSet::new(); + static ref CACHE_INFO: DashMap> = DashMap::new(); + static ref IS_LOAD: AtomicBool = AtomicBool::new(false); } #[derive(Eq, Hash, PartialEq)] pub enum TypeCategory { - Array, - Map, - Str, - Protobuf, - Bytes, - Enum, - Opt, - Primitive, + Array, + Map, + Str, + Protobuf, + Bytes, + Enum, + Opt, + Primitive, } // auto generate, do not edit pub fn category_from_str(type_str: String) -> TypeCategory { - if !IS_LOAD.load(Ordering::SeqCst) { - IS_LOAD.store(true, Ordering::SeqCst); - // Dependents on another crate file is not good, just leave it here. - // Maybe find another way to read the .cache in the future. - let cache_dir = format!("{}/../flowy-codegen/.cache", env!("CARGO_MANIFEST_DIR")); - for path in WalkDir::new(cache_dir) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| e.path().file_stem().unwrap().to_str().unwrap() == "proto_cache") - .map(|e| e.path().to_str().unwrap().to_string()) - { - match read_file(&path) { - None => {} - Some(s) => { - let cache: ProtoCache = serde_json::from_str(&s).unwrap(); - CACHE_INFO - .entry(TypeCategory::Protobuf) - .or_default() - .extend(cache.structs); - CACHE_INFO.entry(TypeCategory::Enum).or_default().extend(cache.enums); - } - } - } + if !IS_LOAD.load(Ordering::SeqCst) { + IS_LOAD.store(true, Ordering::SeqCst); + // Dependents on another crate file is not good, just leave it here. + // Maybe find another way to read the .cache in the future. + let cache_dir = format!("{}/../flowy-codegen/.cache", env!("CARGO_MANIFEST_DIR")); + for path in WalkDir::new(cache_dir) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.path().file_stem().unwrap().to_str().unwrap() == "proto_cache") + .map(|e| e.path().to_str().unwrap().to_string()) + { + match read_file(&path) { + None => {}, + Some(s) => { + let cache: ProtoCache = serde_json::from_str(&s).unwrap(); + CACHE_INFO + .entry(TypeCategory::Protobuf) + .or_default() + .extend(cache.structs); + CACHE_INFO + .entry(TypeCategory::Enum) + .or_default() + .extend(cache.enums); + }, + } } + } - if let Some(protobuf_tys) = CACHE_INFO.get(&TypeCategory::Protobuf) { - if protobuf_tys.contains(&type_str) { - return TypeCategory::Protobuf; - } + if let Some(protobuf_tys) = CACHE_INFO.get(&TypeCategory::Protobuf) { + if protobuf_tys.contains(&type_str) { + return TypeCategory::Protobuf; } + } - if let Some(enum_tys) = CACHE_INFO.get(&TypeCategory::Enum) { - if enum_tys.contains(&type_str) { - return TypeCategory::Enum; - } + if let Some(enum_tys) = CACHE_INFO.get(&TypeCategory::Enum) { + if enum_tys.contains(&type_str) { + return TypeCategory::Enum; } + } - match type_str.as_str() { - "Vec" => TypeCategory::Array, - "HashMap" => TypeCategory::Map, - "u8" => TypeCategory::Bytes, - "String" => TypeCategory::Str, - "Option" => TypeCategory::Opt, - _ => TypeCategory::Primitive, - } + match type_str.as_str() { + "Vec" => TypeCategory::Array, + "HashMap" => TypeCategory::Map, + "u8" => TypeCategory::Bytes, + "String" => TypeCategory::Str, + "Option" => TypeCategory::Opt, + _ => TypeCategory::Primitive, + } } fn read_file(path: &str) -> Option { - match File::open(path) { - Ok(mut file) => { - let mut content = String::new(); - match file.read_to_string(&mut content) { - Ok(_) => Some(content), - Err(_) => None, - } - } + match File::open(path) { + Ok(mut file) => { + let mut content = String::new(); + match file.read_to_string(&mut content) { + Ok(_) => Some(content), Err(_) => None, - } + } + }, + Err(_) => None, + } } diff --git a/frontend/rust-lib/flowy-derive/tests/progress.rs b/frontend/rust-lib/flowy-derive/tests/progress.rs index c4380d6778..260bdb4d80 100644 --- a/frontend/rust-lib/flowy-derive/tests/progress.rs +++ b/frontend/rust-lib/flowy-derive/tests/progress.rs @@ -1,5 +1,5 @@ #[tokio::test] async fn tests() { - let _t = trybuild::TestCases::new(); - // t.pass("tests/protobuf_enum.rs"); + let _t = trybuild::TestCases::new(); + // t.pass("tests/protobuf_enum.rs"); } diff --git a/frontend/rust-lib/flowy-document/build.rs b/frontend/rust-lib/flowy-document/build.rs index 508b370b87..06388d2a02 100644 --- a/frontend/rust-lib/flowy-document/build.rs +++ b/frontend/rust-lib/flowy-document/build.rs @@ -1,10 +1,10 @@ fn main() { - let crate_name = env!("CARGO_PKG_NAME"); - flowy_codegen::protobuf_file::gen(crate_name); + let crate_name = env!("CARGO_PKG_NAME"); + flowy_codegen::protobuf_file::gen(crate_name); - #[cfg(feature = "dart")] - flowy_codegen::dart_event::gen(crate_name); + #[cfg(feature = "dart")] + flowy_codegen::dart_event::gen(crate_name); - #[cfg(feature = "ts")] - flowy_codegen::ts_event::gen(crate_name); + #[cfg(feature = "ts")] + flowy_codegen::ts_event::gen(crate_name); } diff --git a/frontend/rust-lib/flowy-document/src/editor/document.rs b/frontend/rust-lib/flowy-document/src/editor/document.rs index 22333051c2..8d6974fa37 100644 --- a/frontend/rust-lib/flowy-document/src/editor/document.rs +++ b/frontend/rust-lib/flowy-document/src/editor/document.rs @@ -1,116 +1,123 @@ use bytes::Bytes; use flowy_error::{FlowyError, FlowyResult}; use flowy_revision::{RevisionMergeable, RevisionObjectDeserializer, RevisionObjectSerializer}; -use lib_ot::core::{Extension, NodeDataBuilder, NodeOperation, NodeTree, NodeTreeContext, Selection, Transaction}; +use lib_ot::core::{ + Extension, NodeDataBuilder, NodeOperation, NodeTree, NodeTreeContext, Selection, Transaction, +}; use lib_ot::text_delta::DeltaTextOperationBuilder; use revision_model::Revision; #[derive(Debug)] pub struct Document { - tree: NodeTree, + tree: NodeTree, } impl Document { - pub fn new(tree: NodeTree) -> Self { - Self { tree } - } + pub fn new(tree: NodeTree) -> Self { + Self { tree } + } - pub fn from_transaction(transaction: Transaction) -> FlowyResult { - let tree = NodeTree::from_operations(transaction.operations, make_tree_context())?; - Ok(Self { tree }) - } + pub fn from_transaction(transaction: Transaction) -> FlowyResult { + let tree = NodeTree::from_operations(transaction.operations, make_tree_context())?; + Ok(Self { tree }) + } - pub fn get_content(&self, pretty: bool) -> FlowyResult { - if pretty { - serde_json::to_string_pretty(self).map_err(|err| FlowyError::serde().context(err)) - } else { - serde_json::to_string(self).map_err(|err| FlowyError::serde().context(err)) - } + pub fn get_content(&self, pretty: bool) -> FlowyResult { + if pretty { + serde_json::to_string_pretty(self).map_err(|err| FlowyError::serde().context(err)) + } else { + serde_json::to_string(self).map_err(|err| FlowyError::serde().context(err)) } + } - pub fn document_md5(&self) -> String { - let bytes = self.tree.to_bytes(); - format!("{:x}", md5::compute(&bytes)) - } + pub fn document_md5(&self) -> String { + let bytes = self.tree.to_bytes(); + format!("{:x}", md5::compute(&bytes)) + } - pub fn get_tree(&self) -> &NodeTree { - &self.tree - } + pub fn get_tree(&self) -> &NodeTree { + &self.tree + } } pub(crate) fn make_tree_context() -> NodeTreeContext { - NodeTreeContext {} + NodeTreeContext {} } pub fn initial_document_content() -> String { - let delta = DeltaTextOperationBuilder::new().insert("").build(); - let node_data = NodeDataBuilder::new("text").insert_delta(delta).build(); - let editor_node = NodeDataBuilder::new("editor").add_node_data(node_data).build(); - let node_operation = NodeOperation::Insert { - path: vec![0].into(), - nodes: vec![editor_node], - }; - let extension = Extension::TextSelection { - before_selection: Selection::default(), - after_selection: Selection::default(), - }; - let transaction = Transaction { - operations: vec![node_operation].into(), - extension, - }; - transaction.to_json().unwrap() + let delta = DeltaTextOperationBuilder::new().insert("").build(); + let node_data = NodeDataBuilder::new("text").insert_delta(delta).build(); + let editor_node = NodeDataBuilder::new("editor") + .add_node_data(node_data) + .build(); + let node_operation = NodeOperation::Insert { + path: vec![0].into(), + nodes: vec![editor_node], + }; + let extension = Extension::TextSelection { + before_selection: Selection::default(), + after_selection: Selection::default(), + }; + let transaction = Transaction { + operations: vec![node_operation].into(), + extension, + }; + transaction.to_json().unwrap() } impl std::ops::Deref for Document { - type Target = NodeTree; + type Target = NodeTree; - fn deref(&self) -> &Self::Target { - &self.tree - } + fn deref(&self) -> &Self::Target { + &self.tree + } } impl std::ops::DerefMut for Document { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.tree - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.tree + } } pub struct DocumentRevisionSerde(); impl RevisionObjectDeserializer for DocumentRevisionSerde { - type Output = Document; + type Output = Document; - fn deserialize_revisions(_object_id: &str, revisions: Vec) -> FlowyResult { - let mut tree = NodeTree::new(make_tree_context()); - let transaction = make_transaction_from_revisions(&revisions)?; - tree.apply_transaction(transaction)?; - let document = Document::new(tree); - Result::::Ok(document) - } + fn deserialize_revisions( + _object_id: &str, + revisions: Vec, + ) -> FlowyResult { + let mut tree = NodeTree::new(make_tree_context()); + let transaction = make_transaction_from_revisions(&revisions)?; + tree.apply_transaction(transaction)?; + let document = Document::new(tree); + Result::::Ok(document) + } - fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { - None - } + fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { + None + } } impl RevisionObjectSerializer for DocumentRevisionSerde { - fn combine_revisions(revisions: Vec) -> FlowyResult { - let transaction = make_transaction_from_revisions(&revisions)?; - Ok(Bytes::from(transaction.to_bytes()?)) - } + fn combine_revisions(revisions: Vec) -> FlowyResult { + let transaction = make_transaction_from_revisions(&revisions)?; + Ok(Bytes::from(transaction.to_bytes()?)) + } } pub(crate) struct DocumentRevisionMergeable(); impl RevisionMergeable for DocumentRevisionMergeable { - fn combine_revisions(&self, revisions: Vec) -> FlowyResult { - DocumentRevisionSerde::combine_revisions(revisions) - } + fn combine_revisions(&self, revisions: Vec) -> FlowyResult { + DocumentRevisionSerde::combine_revisions(revisions) + } } #[tracing::instrument(level = "trace", skip_all, err)] pub fn make_transaction_from_revisions(revisions: &[Revision]) -> FlowyResult { - let mut transaction = Transaction::new(); - for revision in revisions { - transaction.compose(Transaction::from_bytes(&revision.bytes)?)?; - } - Ok(transaction) + let mut transaction = Transaction::new(); + for revision in revisions { + transaction.compose(Transaction::from_bytes(&revision.bytes)?)?; + } + Ok(transaction) } diff --git a/frontend/rust-lib/flowy-document/src/editor/document_serde.rs b/frontend/rust-lib/flowy-document/src/editor/document_serde.rs index fbe17c4880..76452deeea 100644 --- a/frontend/rust-lib/flowy-document/src/editor/document_serde.rs +++ b/frontend/rust-lib/flowy-document/src/editor/document_serde.rs @@ -2,8 +2,8 @@ use crate::editor::document::Document; use bytes::Bytes; use flowy_error::FlowyResult; use lib_ot::core::{ - AttributeHashMap, Body, Changeset, Extension, NodeData, NodeId, NodeOperation, NodeTree, NodeTreeContext, Path, - Selection, Transaction, + AttributeHashMap, Body, Changeset, Extension, NodeData, NodeId, NodeOperation, NodeTree, + NodeTreeContext, Path, Selection, Transaction, }; use lib_ot::text_delta::DeltaTextOperations; use serde::de::{self, MapAccess, Unexpected, Visitor}; @@ -12,376 +12,402 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt; impl Serialize for Document { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut map = serializer.serialize_map(Some(1))?; - map.serialize_key("document")?; - map.serialize_value(&DocumentContentSerializer(self))?; - map.end() - } + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_key("document")?; + map.serialize_value(&DocumentContentSerializer(self))?; + map.end() + } } const FIELDS: &[&str] = &["Document"]; impl<'de> Deserialize<'de> for Document { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct DocumentVisitor(); + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct DocumentVisitor(); - impl<'de> Visitor<'de> for DocumentVisitor { - type Value = Document; + impl<'de> Visitor<'de> for DocumentVisitor { + type Value = Document; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("Expect document tree") - } + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Expect document tree") + } - fn visit_map(self, mut map: M) -> Result - where - M: MapAccess<'de>, - { - let mut document_node = None; - while let Some(key) = map.next_key()? { - match key { - "document" => { - if document_node.is_some() { - return Err(de::Error::duplicate_field("document")); - } - document_node = Some(map.next_value::()?) - } - s => { - return Err(de::Error::unknown_field(s, FIELDS)); - } - } - } - - match document_node { - Some(document_node) => { - match NodeTree::from_node_data(document_node.into(), NodeTreeContext::default()) { - Ok(tree) => Ok(Document::new(tree)), - Err(err) => Err(de::Error::invalid_value(Unexpected::Other(&format!("{}", err)), &"")), - } - } - None => Err(de::Error::missing_field("document")), - } - } + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut document_node = None; + while let Some(key) = map.next_key()? { + match key { + "document" => { + if document_node.is_some() { + return Err(de::Error::duplicate_field("document")); + } + document_node = Some(map.next_value::()?) + }, + s => { + return Err(de::Error::unknown_field(s, FIELDS)); + }, + } } - deserializer.deserialize_any(DocumentVisitor()) + + match document_node { + Some(document_node) => { + match NodeTree::from_node_data(document_node.into(), NodeTreeContext::default()) { + Ok(tree) => Ok(Document::new(tree)), + Err(err) => Err(de::Error::invalid_value( + Unexpected::Other(&format!("{}", err)), + &"", + )), + } + }, + None => Err(de::Error::missing_field("document")), + } + } } + deserializer.deserialize_any(DocumentVisitor()) + } } pub fn make_transaction_from_document_content(content: &str) -> FlowyResult { - let document_node: DocumentNode = serde_json::from_str::(content)?.document; - let document_operation = DocumentOperation::Insert { - path: 0_usize.into(), - nodes: vec![document_node], - }; - let mut document_transaction = DocumentTransaction::default(); - document_transaction.operations.push(document_operation); - Ok(document_transaction.into()) + let document_node: DocumentNode = + serde_json::from_str::(content)?.document; + let document_operation = DocumentOperation::Insert { + path: 0_usize.into(), + nodes: vec![document_node], + }; + let mut document_transaction = DocumentTransaction::default(); + document_transaction.operations.push(document_operation); + Ok(document_transaction.into()) } pub struct DocumentContentSerde {} #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct DocumentTransaction { - #[serde(default)] - operations: Vec, + #[serde(default)] + operations: Vec, - #[serde(default)] - before_selection: Selection, + #[serde(default)] + before_selection: Selection, - #[serde(default)] - after_selection: Selection, + #[serde(default)] + after_selection: Selection, } impl DocumentTransaction { - pub fn to_json(&self) -> FlowyResult { - let json = serde_json::to_string(self)?; - Ok(json) - } + pub fn to_json(&self) -> FlowyResult { + let json = serde_json::to_string(self)?; + Ok(json) + } - pub fn to_bytes(&self) -> FlowyResult { - let data = serde_json::to_vec(&self)?; - Ok(Bytes::from(data)) - } + pub fn to_bytes(&self) -> FlowyResult { + let data = serde_json::to_vec(&self)?; + Ok(Bytes::from(data)) + } - pub fn from_bytes(bytes: Bytes) -> FlowyResult { - let transaction = serde_json::from_slice(&bytes)?; - Ok(transaction) - } + pub fn from_bytes(bytes: Bytes) -> FlowyResult { + let transaction = serde_json::from_slice(&bytes)?; + Ok(transaction) + } } impl std::convert::From for DocumentTransaction { - fn from(transaction: Transaction) -> Self { - let (operations, extension) = transaction.split(); - let (before_selection, after_selection) = match extension { - Extension::Empty => (Selection::default(), Selection::default()), - Extension::TextSelection { - before_selection, - after_selection, - } => (before_selection, after_selection), - }; + fn from(transaction: Transaction) -> Self { + let (operations, extension) = transaction.split(); + let (before_selection, after_selection) = match extension { + Extension::Empty => (Selection::default(), Selection::default()), + Extension::TextSelection { + before_selection, + after_selection, + } => (before_selection, after_selection), + }; - DocumentTransaction { - operations: operations - .into_iter() - .map(|operation| operation.as_ref().into()) - .collect(), - before_selection, - after_selection, - } + DocumentTransaction { + operations: operations + .into_iter() + .map(|operation| operation.as_ref().into()) + .collect(), + before_selection, + after_selection, } + } } impl std::convert::From for Transaction { - fn from(document_transaction: DocumentTransaction) -> Self { - let mut transaction = Transaction::new(); - for document_operation in document_transaction.operations { - transaction.push_operation(document_operation); - } - transaction.extension = Extension::TextSelection { - before_selection: document_transaction.before_selection, - after_selection: document_transaction.after_selection, - }; - transaction + fn from(document_transaction: DocumentTransaction) -> Self { + let mut transaction = Transaction::new(); + for document_operation in document_transaction.operations { + transaction.push_operation(document_operation); } + transaction.extension = Extension::TextSelection { + before_selection: document_transaction.before_selection, + after_selection: document_transaction.after_selection, + }; + transaction + } } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "op")] pub enum DocumentOperation { - #[serde(rename = "insert")] - Insert { path: Path, nodes: Vec }, + #[serde(rename = "insert")] + Insert { + path: Path, + nodes: Vec, + }, - #[serde(rename = "delete")] - Delete { path: Path, nodes: Vec }, + #[serde(rename = "delete")] + Delete { + path: Path, + nodes: Vec, + }, - #[serde(rename = "update")] - Update { - path: Path, - attributes: AttributeHashMap, - #[serde(rename = "oldAttributes")] - old_attributes: AttributeHashMap, - }, + #[serde(rename = "update")] + Update { + path: Path, + attributes: AttributeHashMap, + #[serde(rename = "oldAttributes")] + old_attributes: AttributeHashMap, + }, - #[serde(rename = "update_text")] - UpdateText { - path: Path, - delta: DeltaTextOperations, - inverted: DeltaTextOperations, - }, + #[serde(rename = "update_text")] + UpdateText { + path: Path, + delta: DeltaTextOperations, + inverted: DeltaTextOperations, + }, } impl std::convert::From for NodeOperation { - fn from(document_operation: DocumentOperation) -> Self { - match document_operation { - DocumentOperation::Insert { path, nodes } => NodeOperation::Insert { - path, - nodes: nodes.into_iter().map(|node| node.into()).collect(), - }, - DocumentOperation::Delete { path, nodes } => NodeOperation::Delete { - path, + fn from(document_operation: DocumentOperation) -> Self { + match document_operation { + DocumentOperation::Insert { path, nodes } => NodeOperation::Insert { + path, + nodes: nodes.into_iter().map(|node| node.into()).collect(), + }, + DocumentOperation::Delete { path, nodes } => NodeOperation::Delete { + path, - nodes: nodes.into_iter().map(|node| node.into()).collect(), - }, - DocumentOperation::Update { - path, - attributes, - old_attributes, - } => NodeOperation::Update { - path, - changeset: Changeset::Attributes { - new: attributes, - old: old_attributes, - }, - }, - DocumentOperation::UpdateText { path, delta, inverted } => NodeOperation::Update { - path, - changeset: Changeset::Delta { delta, inverted }, - }, - } + nodes: nodes.into_iter().map(|node| node.into()).collect(), + }, + DocumentOperation::Update { + path, + attributes, + old_attributes, + } => NodeOperation::Update { + path, + changeset: Changeset::Attributes { + new: attributes, + old: old_attributes, + }, + }, + DocumentOperation::UpdateText { + path, + delta, + inverted, + } => NodeOperation::Update { + path, + changeset: Changeset::Delta { delta, inverted }, + }, } + } } impl std::convert::From<&NodeOperation> for DocumentOperation { - fn from(node_operation: &NodeOperation) -> Self { - let node_operation = node_operation.clone(); - match node_operation { - NodeOperation::Insert { path, nodes } => DocumentOperation::Insert { - path, - nodes: nodes.into_iter().map(|node| node.into()).collect(), - }, - NodeOperation::Update { path, changeset } => match changeset { - Changeset::Delta { delta, inverted } => DocumentOperation::UpdateText { path, delta, inverted }, - Changeset::Attributes { new, old } => DocumentOperation::Update { - path, - attributes: new, - old_attributes: old, - }, - }, - NodeOperation::Delete { path, nodes } => DocumentOperation::Delete { - path, - nodes: nodes.into_iter().map(|node| node.into()).collect(), - }, - } + fn from(node_operation: &NodeOperation) -> Self { + let node_operation = node_operation.clone(); + match node_operation { + NodeOperation::Insert { path, nodes } => DocumentOperation::Insert { + path, + nodes: nodes.into_iter().map(|node| node.into()).collect(), + }, + NodeOperation::Update { path, changeset } => match changeset { + Changeset::Delta { delta, inverted } => DocumentOperation::UpdateText { + path, + delta, + inverted, + }, + Changeset::Attributes { new, old } => DocumentOperation::Update { + path, + attributes: new, + old_attributes: old, + }, + }, + NodeOperation::Delete { path, nodes } => DocumentOperation::Delete { + path, + nodes: nodes.into_iter().map(|node| node.into()).collect(), + }, } + } } #[derive(Default, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct DocumentNode { - #[serde(rename = "type")] - pub node_type: String, + #[serde(rename = "type")] + pub node_type: String, - #[serde(skip_serializing_if = "AttributeHashMap::is_empty")] - #[serde(default)] - pub attributes: AttributeHashMap, + #[serde(skip_serializing_if = "AttributeHashMap::is_empty")] + #[serde(default)] + pub attributes: AttributeHashMap, - #[serde(skip_serializing_if = "DeltaTextOperations::is_empty")] - #[serde(default)] - pub delta: DeltaTextOperations, + #[serde(skip_serializing_if = "DeltaTextOperations::is_empty")] + #[serde(default)] + pub delta: DeltaTextOperations, - #[serde(skip_serializing_if = "Vec::is_empty")] - #[serde(default)] - pub children: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default)] + pub children: Vec, } impl DocumentNode { - pub fn new() -> Self { - Self::default() - } + pub fn new() -> Self { + Self::default() + } } impl std::convert::From for DocumentNode { - fn from(node_data: NodeData) -> Self { - let delta = if let Body::Delta(operations) = node_data.body { - operations - } else { - DeltaTextOperations::default() - }; - DocumentNode { - node_type: node_data.node_type, - attributes: node_data.attributes, - delta, - children: node_data.children.into_iter().map(DocumentNode::from).collect(), - } + fn from(node_data: NodeData) -> Self { + let delta = if let Body::Delta(operations) = node_data.body { + operations + } else { + DeltaTextOperations::default() + }; + DocumentNode { + node_type: node_data.node_type, + attributes: node_data.attributes, + delta, + children: node_data + .children + .into_iter() + .map(DocumentNode::from) + .collect(), } + } } impl std::convert::From for NodeData { - fn from(document_node: DocumentNode) -> Self { - NodeData { - node_type: document_node.node_type, - attributes: document_node.attributes, - body: Body::Delta(document_node.delta), - children: document_node.children.into_iter().map(|child| child.into()).collect(), - } + fn from(document_node: DocumentNode) -> Self { + NodeData { + node_type: document_node.node_type, + attributes: document_node.attributes, + body: Body::Delta(document_node.delta), + children: document_node + .children + .into_iter() + .map(|child| child.into()) + .collect(), } + } } #[derive(Debug, Deserialize)] struct DocumentContentDeserializer { - document: DocumentNode, + document: DocumentNode, } #[derive(Debug)] struct DocumentContentSerializer<'a>(pub &'a Document); impl<'a> Serialize for DocumentContentSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let tree = self.0.get_tree(); - let root_node_id = tree.root_node_id(); + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let tree = self.0.get_tree(); + let root_node_id = tree.root_node_id(); - // transform the NodeData to DocumentNodeData - let get_document_node_data = |node_id: NodeId| tree.get_node_data(node_id).map(DocumentNode::from); + // transform the NodeData to DocumentNodeData + let get_document_node_data = + |node_id: NodeId| tree.get_node_data(node_id).map(DocumentNode::from); - let mut children = tree.get_children_ids(root_node_id); - if children.len() == 1 { - let node_id = children.pop().unwrap(); - match get_document_node_data(node_id) { - None => serializer.serialize_str(""), - Some(node_data) => node_data.serialize(serializer), - } - } else { - let mut seq = serializer.serialize_seq(Some(children.len()))?; - for child in children { - if let Some(node_data) = get_document_node_data(child) { - seq.serialize_element(&node_data)?; - } - } - seq.end() + let mut children = tree.get_children_ids(root_node_id); + if children.len() == 1 { + let node_id = children.pop().unwrap(); + match get_document_node_data(node_id) { + None => serializer.serialize_str(""), + Some(node_data) => node_data.serialize(serializer), + } + } else { + let mut seq = serializer.serialize_seq(Some(children.len()))?; + for child in children { + if let Some(node_data) = get_document_node_data(child) { + seq.serialize_element(&node_data)?; } + } + seq.end() } + } } #[cfg(test)] mod tests { - use crate::editor::document::Document; - use crate::editor::document_serde::DocumentTransaction; - use crate::editor::initial_read_me; + use crate::editor::document::Document; + use crate::editor::document_serde::DocumentTransaction; + use crate::editor::initial_read_me; - #[test] - fn load_read_me() { - let _ = initial_read_me(); - } + #[test] + fn load_read_me() { + let _ = initial_read_me(); + } - #[test] - fn transaction_deserialize_update_text_operation_test() { - // bold - let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"retain":3,"attributes":{"bold":true}}],"inverted":[{"retain":3,"attributes":{"bold":null}}]}],"after_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":3}},"before_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":3}}}"#; - let _ = serde_json::from_str::(json).unwrap(); + #[test] + fn transaction_deserialize_update_text_operation_test() { + // bold + let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"retain":3,"attributes":{"bold":true}}],"inverted":[{"retain":3,"attributes":{"bold":null}}]}],"after_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":3}},"before_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":3}}}"#; + let _ = serde_json::from_str::(json).unwrap(); - // delete character - let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"retain":2},{"delete":1}],"inverted":[{"retain":2},{"insert":"C","attributes":{"bold":true}}]}],"after_selection":{"start":{"path":[0],"offset":2},"end":{"path":[0],"offset":2}},"before_selection":{"start":{"path":[0],"offset":3},"end":{"path":[0],"offset":3}}}"#; - let _ = serde_json::from_str::(json).unwrap(); - } + // delete character + let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"retain":2},{"delete":1}],"inverted":[{"retain":2},{"insert":"C","attributes":{"bold":true}}]}],"after_selection":{"start":{"path":[0],"offset":2},"end":{"path":[0],"offset":2}},"before_selection":{"start":{"path":[0],"offset":3},"end":{"path":[0],"offset":3}}}"#; + let _ = serde_json::from_str::(json).unwrap(); + } - #[test] - fn transaction_deserialize_insert_operation_test() { - let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"insert":"a"}],"inverted":[{"delete":1}]}],"after_selection":{"start":{"path":[0],"offset":1},"end":{"path":[0],"offset":1}},"before_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":0}}}"#; - let _ = serde_json::from_str::(json).unwrap(); - } + #[test] + fn transaction_deserialize_insert_operation_test() { + let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"insert":"a"}],"inverted":[{"delete":1}]}],"after_selection":{"start":{"path":[0],"offset":1},"end":{"path":[0],"offset":1}},"before_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":0}}}"#; + let _ = serde_json::from_str::(json).unwrap(); + } - #[test] - fn transaction_deserialize_delete_operation_test() { - let json = r#"{"operations": [{"op":"delete","path":[1],"nodes":[{"type":"text","delta":[]}]}],"after_selection":{"start":{"path":[0],"offset":2},"end":{"path":[0],"offset":2}},"before_selection":{"start":{"path":[1],"offset":0},"end":{"path":[1],"offset":0}}}"#; - let _transaction = serde_json::from_str::(json).unwrap(); - } + #[test] + fn transaction_deserialize_delete_operation_test() { + let json = r#"{"operations": [{"op":"delete","path":[1],"nodes":[{"type":"text","delta":[]}]}],"after_selection":{"start":{"path":[0],"offset":2},"end":{"path":[0],"offset":2}},"before_selection":{"start":{"path":[1],"offset":0},"end":{"path":[1],"offset":0}}}"#; + let _transaction = serde_json::from_str::(json).unwrap(); + } - #[test] - fn transaction_deserialize_update_attribute_operation_test() { - // let json = r#"{"operations":[{"op":"update","path":[0],"attributes":{"retain":3,"attributes":{"bold":true}},"oldAttributes":{"retain":3,"attributes":{"bold":null}}}]}"#; - // let transaction = serde_json::from_str::(&json).unwrap(); + #[test] + fn transaction_deserialize_update_attribute_operation_test() { + // let json = r#"{"operations":[{"op":"update","path":[0],"attributes":{"retain":3,"attributes":{"bold":true}},"oldAttributes":{"retain":3,"attributes":{"bold":null}}}]}"#; + // let transaction = serde_json::from_str::(&json).unwrap(); - let json = - r#"{"operations":[{"op":"update","path":[0],"attributes":{"retain":3},"oldAttributes":{"retain":3}}]}"#; - let _ = serde_json::from_str::(json).unwrap(); - } + let json = r#"{"operations":[{"op":"update","path":[0],"attributes":{"retain":3},"oldAttributes":{"retain":3}}]}"#; + let _ = serde_json::from_str::(json).unwrap(); + } - #[test] - fn document_serde_test() { - let document: Document = serde_json::from_str(EXAMPLE_DOCUMENT).unwrap(); - let _ = serde_json::to_string_pretty(&document).unwrap(); - } + #[test] + fn document_serde_test() { + let document: Document = serde_json::from_str(EXAMPLE_DOCUMENT).unwrap(); + let _ = serde_json::to_string_pretty(&document).unwrap(); + } - // #[test] - // fn document_operation_compose_test() { - // let json = include_str!("./test.json"); - // let transaction: Transaction = Transaction::from_json(json).unwrap(); - // let json = transaction.to_json().unwrap(); - // // let transaction: Transaction = Transaction::from_json(&json).unwrap(); - // let document = Document::from_transaction(transaction).unwrap(); - // let content = document.get_content(false).unwrap(); - // println!("{}", json); - // } + // #[test] + // fn document_operation_compose_test() { + // let json = include_str!("./test.json"); + // let transaction: Transaction = Transaction::from_json(json).unwrap(); + // let json = transaction.to_json().unwrap(); + // // let transaction: Transaction = Transaction::from_json(&json).unwrap(); + // let document = Document::from_transaction(transaction).unwrap(); + // let content = document.get_content(false).unwrap(); + // println!("{}", json); + // } - const EXAMPLE_DOCUMENT: &str = r#"{ + const EXAMPLE_DOCUMENT: &str = r#"{ "document": { "type": "editor", "children": [ diff --git a/frontend/rust-lib/flowy-document/src/editor/editor.rs b/frontend/rust-lib/flowy-document/src/editor/editor.rs index fa5c7e535a..5c7566efe4 100644 --- a/frontend/rust-lib/flowy-document/src/editor/editor.rs +++ b/frontend/rust-lib/flowy-document/src/editor/editor.rs @@ -17,110 +17,110 @@ use tokio::sync::{mpsc, oneshot}; use ws_model::ws_revision::ServerRevisionWSData; pub struct AppFlowyDocumentEditor { - #[allow(dead_code)] - doc_id: String, - command_sender: CommandSender, - rev_manager: Arc>>, + #[allow(dead_code)] + doc_id: String, + command_sender: CommandSender, + rev_manager: Arc>>, } impl AppFlowyDocumentEditor { - pub async fn new( - doc_id: &str, - user: Arc, - mut rev_manager: RevisionManager>, - cloud_service: Arc, - ) -> FlowyResult> { - let document = rev_manager - .initialize::(Some(cloud_service)) - .await?; - let rev_manager = Arc::new(rev_manager); - let command_sender = spawn_edit_queue(user, rev_manager.clone(), document); - let doc_id = doc_id.to_string(); - let editor = Arc::new(Self { - doc_id, - command_sender, - rev_manager, - }); - Ok(editor) - } + pub async fn new( + doc_id: &str, + user: Arc, + mut rev_manager: RevisionManager>, + cloud_service: Arc, + ) -> FlowyResult> { + let document = rev_manager + .initialize::(Some(cloud_service)) + .await?; + let rev_manager = Arc::new(rev_manager); + let command_sender = spawn_edit_queue(user, rev_manager.clone(), document); + let doc_id = doc_id.to_string(); + let editor = Arc::new(Self { + doc_id, + command_sender, + rev_manager, + }); + Ok(editor) + } - pub async fn apply_transaction(&self, transaction: Transaction) -> FlowyResult<()> { - let (ret, rx) = oneshot::channel::>(); - let _ = self - .command_sender - .send(Command::ComposeTransaction { transaction, ret }) - .await; - rx.await.map_err(internal_error)??; - Ok(()) - } + pub async fn apply_transaction(&self, transaction: Transaction) -> FlowyResult<()> { + let (ret, rx) = oneshot::channel::>(); + let _ = self + .command_sender + .send(Command::ComposeTransaction { transaction, ret }) + .await; + rx.await.map_err(internal_error)??; + Ok(()) + } - pub async fn get_content(&self, pretty: bool) -> FlowyResult { - let (ret, rx) = oneshot::channel::>(); - let _ = self - .command_sender - .send(Command::GetDocumentContent { pretty, ret }) - .await; - let content = rx.await.map_err(internal_error)??; - Ok(content) - } + pub async fn get_content(&self, pretty: bool) -> FlowyResult { + let (ret, rx) = oneshot::channel::>(); + let _ = self + .command_sender + .send(Command::GetDocumentContent { pretty, ret }) + .await; + let content = rx.await.map_err(internal_error)??; + Ok(content) + } - pub async fn duplicate_document(&self) -> FlowyResult { - let transaction = self.document_transaction().await?; - let json = transaction.to_json()?; - Ok(json) - } + pub async fn duplicate_document(&self) -> FlowyResult { + let transaction = self.document_transaction().await?; + let json = transaction.to_json()?; + Ok(json) + } - pub async fn document_transaction(&self) -> FlowyResult { - let revisions = self.rev_manager.load_revisions().await?; - make_transaction_from_revisions(&revisions) - } + pub async fn document_transaction(&self) -> FlowyResult { + let revisions = self.rev_manager.load_revisions().await?; + make_transaction_from_revisions(&revisions) + } } fn spawn_edit_queue( - user: Arc, - rev_manager: Arc>>, - document: Document, + user: Arc, + rev_manager: Arc>>, + document: Document, ) -> CommandSender { - let (sender, receiver) = mpsc::channel(1000); - let queue = DocumentQueue::new(user, rev_manager, document, receiver); - tokio::spawn(queue.run()); - sender + let (sender, receiver) = mpsc::channel(1000); + let queue = DocumentQueue::new(user, rev_manager, document, receiver); + tokio::spawn(queue.run()); + sender } #[async_trait] impl DocumentEditor for Arc { - #[tracing::instrument(name = "close document editor", level = "trace", skip_all)] - async fn close(&self) { - self.rev_manager.generate_snapshot().await; - self.rev_manager.close().await; - } + #[tracing::instrument(name = "close document editor", level = "trace", skip_all)] + async fn close(&self) { + self.rev_manager.generate_snapshot().await; + self.rev_manager.close().await; + } - fn export(&self) -> FutureResult { - let this = self.clone(); - FutureResult::new(async move { this.get_content(false).await }) - } + fn export(&self) -> FutureResult { + let this = self.clone(); + FutureResult::new(async move { this.get_content(false).await }) + } - fn duplicate(&self) -> FutureResult { - let this = self.clone(); - FutureResult::new(async move { this.duplicate_document().await }) - } + fn duplicate(&self) -> FutureResult { + let this = self.clone(); + FutureResult::new(async move { this.duplicate_document().await }) + } - fn receive_ws_data(&self, _data: ServerRevisionWSData) -> FutureResult<(), FlowyError> { - FutureResult::new(async move { Ok(()) }) - } + fn receive_ws_data(&self, _data: ServerRevisionWSData) -> FutureResult<(), FlowyError> { + FutureResult::new(async move { Ok(()) }) + } - fn receive_ws_state(&self, _state: &WSConnectState) {} + fn receive_ws_state(&self, _state: &WSConnectState) {} - fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError> { - let this = self.clone(); - FutureResult::new(async move { - let transaction = DocumentTransaction::from_bytes(data)?; - this.apply_transaction(transaction.into()).await?; - Ok(()) - }) - } + fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError> { + let this = self.clone(); + FutureResult::new(async move { + let transaction = DocumentTransaction::from_bytes(data)?; + this.apply_transaction(transaction.into()).await?; + Ok(()) + }) + } - fn as_any(&self) -> &dyn Any { - self - } + fn as_any(&self) -> &dyn Any { + self + } } diff --git a/frontend/rust-lib/flowy-document/src/editor/mod.rs b/frontend/rust-lib/flowy-document/src/editor/mod.rs index 6d1abf4a7a..f401f71984 100644 --- a/frontend/rust-lib/flowy-document/src/editor/mod.rs +++ b/frontend/rust-lib/flowy-document/src/editor/mod.rs @@ -10,7 +10,7 @@ pub use editor::*; #[inline] pub fn initial_read_me() -> String { - let document_content = include_str!("READ_ME.json"); - let transaction = make_transaction_from_document_content(document_content).unwrap(); - transaction.to_json().unwrap() + let document_content = include_str!("READ_ME.json"); + let transaction = make_transaction_from_document_content(document_content).unwrap(); + transaction.to_json().unwrap() } diff --git a/frontend/rust-lib/flowy-document/src/editor/queue.rs b/frontend/rust-lib/flowy-document/src/editor/queue.rs index c2ee48e11c..cd94e099ad 100644 --- a/frontend/rust-lib/flowy-document/src/editor/queue.rs +++ b/frontend/rust-lib/flowy-document/src/editor/queue.rs @@ -13,73 +13,81 @@ use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::{oneshot, RwLock}; pub struct DocumentQueue { - #[allow(dead_code)] - user: Arc, - document: Arc>, - #[allow(dead_code)] - rev_manager: Arc>>, - receiver: Option, + #[allow(dead_code)] + user: Arc, + document: Arc>, + #[allow(dead_code)] + rev_manager: Arc>>, + receiver: Option, } impl DocumentQueue { - pub fn new( - user: Arc, - rev_manager: Arc>>, - document: Document, - receiver: CommandReceiver, - ) -> Self { - let document = Arc::new(RwLock::new(document)); - Self { - user, - document, - rev_manager, - receiver: Some(receiver), - } + pub fn new( + user: Arc, + rev_manager: Arc>>, + document: Document, + receiver: CommandReceiver, + ) -> Self { + let document = Arc::new(RwLock::new(document)); + Self { + user, + document, + rev_manager, + receiver: Some(receiver), } + } - pub async fn run(mut self) { - let mut receiver = self.receiver.take().expect("Only take once"); - let stream = stream! { - loop { - match receiver.recv().await { - Some(msg) => yield msg, - None => break, - } - } - }; - stream - .for_each(|command| async { - match self.handle_command(command).await { - Ok(_) => {} - Err(e) => tracing::debug!("[DocumentQueue]: {}", e), - } - }) - .await; - } - - async fn handle_command(&self, command: Command) -> Result<(), FlowyError> { - match command { - Command::ComposeTransaction { transaction, ret } => { - self.document.write().await.apply_transaction(transaction.clone())?; - let _ = self - .save_local_operations(transaction, self.document.read().await.document_md5()) - .await?; - let _ = ret.send(Ok(())); - } - Command::GetDocumentContent { pretty, ret } => { - let content = self.document.read().await.get_content(pretty)?; - let _ = ret.send(Ok(content)); + pub async fn run(mut self) { + let mut receiver = self.receiver.take().expect("Only take once"); + let stream = stream! { + loop { + match receiver.recv().await { + Some(msg) => yield msg, + None => break, } } - Ok(()) - } + }; + stream + .for_each(|command| async { + match self.handle_command(command).await { + Ok(_) => {}, + Err(e) => tracing::debug!("[DocumentQueue]: {}", e), + } + }) + .await; + } - #[tracing::instrument(level = "trace", skip(self, transaction, md5), err)] - async fn save_local_operations(&self, transaction: Transaction, md5: String) -> Result { - let bytes = Bytes::from(transaction.to_bytes()?); - let rev_id = self.rev_manager.add_local_revision(bytes, md5).await?; - Ok(rev_id) + async fn handle_command(&self, command: Command) -> Result<(), FlowyError> { + match command { + Command::ComposeTransaction { transaction, ret } => { + self + .document + .write() + .await + .apply_transaction(transaction.clone())?; + let _ = self + .save_local_operations(transaction, self.document.read().await.document_md5()) + .await?; + let _ = ret.send(Ok(())); + }, + Command::GetDocumentContent { pretty, ret } => { + let content = self.document.read().await.get_content(pretty)?; + let _ = ret.send(Ok(content)); + }, } + Ok(()) + } + + #[tracing::instrument(level = "trace", skip(self, transaction, md5), err)] + async fn save_local_operations( + &self, + transaction: Transaction, + md5: String, + ) -> Result { + let bytes = Bytes::from(transaction.to_bytes()?); + let rev_id = self.rev_manager.add_local_revision(bytes, md5).await?; + Ok(rev_id) + } } pub(crate) type CommandSender = Sender; @@ -87,6 +95,12 @@ pub(crate) type CommandReceiver = Receiver; pub(crate) type Ret = oneshot::Sender>; pub enum Command { - ComposeTransaction { transaction: Transaction, ret: Ret<()> }, - GetDocumentContent { pretty: bool, ret: Ret }, + ComposeTransaction { + transaction: Transaction, + ret: Ret<()>, + }, + GetDocumentContent { + pretty: bool, + ret: Ret, + }, } diff --git a/frontend/rust-lib/flowy-document/src/entities.rs b/frontend/rust-lib/flowy-document/src/entities.rs index 8fc867be8a..3a8ee308f4 100644 --- a/frontend/rust-lib/flowy-document/src/entities.rs +++ b/frontend/rust-lib/flowy-document/src/entities.rs @@ -4,129 +4,129 @@ use std::convert::TryInto; #[derive(PartialEq, Eq, Debug, ProtoBuf_Enum, Clone)] pub enum ExportType { - Text = 0, - Markdown = 1, - Link = 2, + Text = 0, + Markdown = 1, + Link = 2, } impl Default for ExportType { - fn default() -> Self { - ExportType::Text - } + fn default() -> Self { + ExportType::Text + } } impl From for ExportType { - fn from(val: i32) -> Self { - match val { - 0 => ExportType::Text, - 1 => ExportType::Markdown, - 2 => ExportType::Link, - _ => { - tracing::error!("Invalid export type: {}", val); - ExportType::Text - } - } + fn from(val: i32) -> Self { + match val { + 0 => ExportType::Text, + 1 => ExportType::Markdown, + 2 => ExportType::Link, + _ => { + tracing::error!("Invalid export type: {}", val); + ExportType::Text + }, } + } } #[derive(Default, ProtoBuf)] pub struct EditPayloadPB { - #[pb(index = 1)] - pub doc_id: String, + #[pb(index = 1)] + pub doc_id: String, - // Encode in JSON format - #[pb(index = 2)] - pub operations: String, + // Encode in JSON format + #[pb(index = 2)] + pub operations: String, } #[derive(Default)] pub struct EditParams { - pub doc_id: String, + pub doc_id: String, - // Encode in JSON format - pub operations: String, + // Encode in JSON format + pub operations: String, } impl TryInto for EditPayloadPB { - type Error = ErrorCode; - fn try_into(self) -> Result { - Ok(EditParams { - doc_id: self.doc_id, - operations: self.operations, - }) - } + type Error = ErrorCode; + fn try_into(self) -> Result { + Ok(EditParams { + doc_id: self.doc_id, + operations: self.operations, + }) + } } #[derive(Default, ProtoBuf)] pub struct DocumentDataPB { - #[pb(index = 1)] - pub doc_id: String, + #[pb(index = 1)] + pub doc_id: String, - /// Encode in JSON format - #[pb(index = 2)] - pub content: String, + /// Encode in JSON format + #[pb(index = 2)] + pub content: String, } #[derive(Default, ProtoBuf)] pub struct ExportPayloadPB { - #[pb(index = 1)] - pub view_id: String, + #[pb(index = 1)] + pub view_id: String, - #[pb(index = 2)] - pub export_type: ExportType, + #[pb(index = 2)] + pub export_type: ExportType, - #[pb(index = 3)] - pub document_version: DocumentVersionPB, + #[pb(index = 3)] + pub document_version: DocumentVersionPB, } #[derive(PartialEq, Eq, Debug, ProtoBuf_Enum, Clone)] pub enum DocumentVersionPB { - /// this version's content of the document is build from `Delta`. It uses - /// `DeltaDocumentEditor`. - V0 = 0, - /// this version's content of the document is build from `NodeTree`. It uses - /// `AppFlowyDocumentEditor` - V1 = 1, + /// this version's content of the document is build from `Delta`. It uses + /// `DeltaDocumentEditor`. + V0 = 0, + /// this version's content of the document is build from `NodeTree`. It uses + /// `AppFlowyDocumentEditor` + V1 = 1, } impl std::default::Default for DocumentVersionPB { - fn default() -> Self { - Self::V0 - } + fn default() -> Self { + Self::V0 + } } #[derive(Default, ProtoBuf)] pub struct OpenDocumentPayloadPB { - #[pb(index = 1)] - pub document_id: String, + #[pb(index = 1)] + pub document_id: String, - #[pb(index = 2)] - pub version: DocumentVersionPB, + #[pb(index = 2)] + pub version: DocumentVersionPB, } #[derive(Default, Debug)] pub struct ExportParams { - pub view_id: String, - pub export_type: ExportType, - pub document_version: DocumentVersionPB, + pub view_id: String, + pub export_type: ExportType, + pub document_version: DocumentVersionPB, } impl TryInto for ExportPayloadPB { - type Error = ErrorCode; - fn try_into(self) -> Result { - Ok(ExportParams { - view_id: self.view_id, - export_type: self.export_type, - document_version: self.document_version, - }) - } + type Error = ErrorCode; + fn try_into(self) -> Result { + Ok(ExportParams { + view_id: self.view_id, + export_type: self.export_type, + document_version: self.document_version, + }) + } } #[derive(Default, ProtoBuf)] pub struct ExportDataPB { - #[pb(index = 1)] - pub data: String, + #[pb(index = 1)] + pub data: String, - #[pb(index = 2)] - pub export_type: ExportType, + #[pb(index = 2)] + pub export_type: ExportType, } diff --git a/frontend/rust-lib/flowy-document/src/event_handler.rs b/frontend/rust-lib/flowy-document/src/event_handler.rs index 091d3ed35c..cdc63b399a 100644 --- a/frontend/rust-lib/flowy-document/src/event_handler.rs +++ b/frontend/rust-lib/flowy-document/src/event_handler.rs @@ -1,5 +1,6 @@ use crate::entities::{ - DocumentDataPB, EditParams, EditPayloadPB, ExportDataPB, ExportParams, ExportPayloadPB, OpenDocumentPayloadPB, + DocumentDataPB, EditParams, EditPayloadPB, ExportDataPB, ExportParams, ExportPayloadPB, + OpenDocumentPayloadPB, }; use crate::DocumentManager; use flowy_error::FlowyError; @@ -9,37 +10,37 @@ use std::convert::TryInto; use std::sync::Arc; pub(crate) async fn get_document_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let context: OpenDocumentPayloadPB = data.into_inner(); - let editor = manager.open_document_editor(&context.document_id).await?; - let document_data = editor.export().await?; - data_result(DocumentDataPB { - doc_id: context.document_id, - content: document_data, - }) + let context: OpenDocumentPayloadPB = data.into_inner(); + let editor = manager.open_document_editor(&context.document_id).await?; + let document_data = editor.export().await?; + data_result(DocumentDataPB { + doc_id: context.document_id, + content: document_data, + }) } pub(crate) async fn apply_edit_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> Result<(), FlowyError> { - let params: EditParams = data.into_inner().try_into()?; - manager.apply_edit(params).await?; - Ok(()) + let params: EditParams = data.into_inner().try_into()?; + manager.apply_edit(params).await?; + Ok(()) } #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn export_handler( - data: AFPluginData, - manager: AFPluginState>, + data: AFPluginData, + manager: AFPluginState>, ) -> DataResult { - let params: ExportParams = data.into_inner().try_into()?; - let editor = manager.open_document_editor(¶ms.view_id).await?; - let document_data = editor.export().await?; - data_result(ExportDataPB { - data: document_data, - export_type: params.export_type, - }) + let params: ExportParams = data.into_inner().try_into()?; + let editor = manager.open_document_editor(¶ms.view_id).await?; + let document_data = editor.export().await?; + data_result(ExportDataPB { + data: document_data, + export_type: params.export_type, + }) } diff --git a/frontend/rust-lib/flowy-document/src/event_map.rs b/frontend/rust-lib/flowy-document/src/event_map.rs index fb8dea55c1..71905d5adb 100644 --- a/frontend/rust-lib/flowy-document/src/event_map.rs +++ b/frontend/rust-lib/flowy-document/src/event_map.rs @@ -6,25 +6,27 @@ use std::sync::Arc; use strum_macros::Display; pub fn init(document_manager: Arc) -> AFPlugin { - let mut plugin = AFPlugin::new().name(env!("CARGO_PKG_NAME")).state(document_manager); + let mut plugin = AFPlugin::new() + .name(env!("CARGO_PKG_NAME")) + .state(document_manager); - plugin = plugin - .event(DocumentEvent::GetDocument, get_document_handler) - .event(DocumentEvent::ApplyEdit, apply_edit_handler) - .event(DocumentEvent::ExportDocument, export_handler); + plugin = plugin + .event(DocumentEvent::GetDocument, get_document_handler) + .event(DocumentEvent::ApplyEdit, apply_edit_handler) + .event(DocumentEvent::ExportDocument, export_handler); - plugin + plugin } #[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)] #[event_err = "FlowyError"] pub enum DocumentEvent { - #[event(input = "OpenDocumentPayloadPB", output = "DocumentDataPB")] - GetDocument = 0, + #[event(input = "OpenDocumentPayloadPB", output = "DocumentDataPB")] + GetDocument = 0, - #[event(input = "EditPayloadPB")] - ApplyEdit = 1, + #[event(input = "EditPayloadPB")] + ApplyEdit = 1, - #[event(input = "ExportPayloadPB", output = "ExportDataPB")] - ExportDocument = 2, + #[event(input = "ExportPayloadPB", output = "ExportDataPB")] + ExportDocument = 2, } diff --git a/frontend/rust-lib/flowy-document/src/lib.rs b/frontend/rust-lib/flowy-document/src/lib.rs index 986af3354f..5106885014 100644 --- a/frontend/rust-lib/flowy-document/src/lib.rs +++ b/frontend/rust-lib/flowy-document/src/lib.rs @@ -10,19 +10,33 @@ mod services; pub use manager::*; pub mod errors { - pub use flowy_error::{internal_error, ErrorCode, FlowyError}; + pub use flowy_error::{internal_error, ErrorCode, FlowyError}; } pub const TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS: u64 = 1000; use crate::errors::FlowyError; -use document_model::document::{CreateDocumentParams, DocumentId, DocumentInfo, ResetDocumentParams}; +use document_model::document::{ + CreateDocumentParams, DocumentId, DocumentInfo, ResetDocumentParams, +}; use lib_infra::future::FutureResult; pub trait DocumentCloudService: Send + Sync { - fn create_document(&self, token: &str, params: CreateDocumentParams) -> FutureResult<(), FlowyError>; + fn create_document( + &self, + token: &str, + params: CreateDocumentParams, + ) -> FutureResult<(), FlowyError>; - fn fetch_document(&self, token: &str, params: DocumentId) -> FutureResult, FlowyError>; + fn fetch_document( + &self, + token: &str, + params: DocumentId, + ) -> FutureResult, FlowyError>; - fn update_document_content(&self, token: &str, params: ResetDocumentParams) -> FutureResult<(), FlowyError>; + fn update_document_content( + &self, + token: &str, + params: ResetDocumentParams, + ) -> FutureResult<(), FlowyError>; } diff --git a/frontend/rust-lib/flowy-document/src/manager.rs b/frontend/rust-lib/flowy-document/src/manager.rs index 3471fa66c9..d63b7173df 100644 --- a/frontend/rust-lib/flowy-document/src/manager.rs +++ b/frontend/rust-lib/flowy-document/src/manager.rs @@ -3,8 +3,8 @@ use crate::entities::{DocumentVersionPB, EditParams}; use crate::old_editor::editor::{DeltaDocumentEditor, DeltaDocumentRevisionMergeable}; use crate::old_editor::snapshot::DeltaDocumentSnapshotPersistence; use crate::services::rev_sqlite::{ - SQLiteDeltaDocumentRevisionPersistence, SQLiteDocumentRevisionPersistence, - SQLiteDocumentRevisionSnapshotPersistence, + SQLiteDeltaDocumentRevisionPersistence, SQLiteDocumentRevisionPersistence, + SQLiteDocumentRevisionSnapshotPersistence, }; use crate::services::DocumentPersistence; use crate::{errors::FlowyError, DocumentCloudService}; @@ -13,7 +13,8 @@ use document_model::document::DocumentId; use flowy_client_sync::client_document::initial_delta_document_content; use flowy_error::FlowyResult; use flowy_revision::{ - RevisionCloudService, RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, RevisionWebSocket, + RevisionCloudService, RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, + RevisionWebSocket, }; use flowy_sqlite::ConnectionPool; use lib_infra::async_trait::async_trait; @@ -29,298 +30,326 @@ use tokio::sync::RwLock; use ws_model::ws_revision::ServerRevisionWSData; pub trait DocumentUser: Send + Sync { - fn user_dir(&self) -> Result; - fn user_id(&self) -> Result; - fn token(&self) -> Result; + fn user_dir(&self) -> Result; + fn user_id(&self) -> Result; + fn token(&self) -> Result; } pub trait DocumentDatabase: Send + Sync { - fn db_pool(&self) -> Result, FlowyError>; + fn db_pool(&self) -> Result, FlowyError>; } #[async_trait] pub trait DocumentEditor: Send + Sync { - /// Called when the document get closed - async fn close(&self); + /// Called when the document get closed + async fn close(&self); - /// Exports the document content. The content is encoded in the corresponding - /// editor data format. - fn export(&self) -> FutureResult; + /// Exports the document content. The content is encoded in the corresponding + /// editor data format. + fn export(&self) -> FutureResult; - /// Duplicate the document inner data into String - fn duplicate(&self) -> FutureResult; + /// Duplicate the document inner data into String + fn duplicate(&self) -> FutureResult; - fn receive_ws_data(&self, data: ServerRevisionWSData) -> FutureResult<(), FlowyError>; + fn receive_ws_data(&self, data: ServerRevisionWSData) -> FutureResult<(), FlowyError>; - fn receive_ws_state(&self, state: &WSConnectState); + fn receive_ws_state(&self, state: &WSConnectState); - /// Receives the local operations made by the user input. The operations are encoded - /// in binary format. - fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError>; + /// Receives the local operations made by the user input. The operations are encoded + /// in binary format. + fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError>; - /// Returns the `Any` reference that can be used to downcast back to the original, - /// concrete type. - /// - /// The indirection through `as_any` is because using `downcast_ref` - /// on `Box` *directly* only lets us downcast back to `&A` again. You can take a look at [this](https://stackoverflow.com/questions/33687447/how-to-get-a-reference-to-a-concrete-type-from-a-trait-object) - /// for more information. - /// - /// - fn as_any(&self) -> &dyn Any; + /// Returns the `Any` reference that can be used to downcast back to the original, + /// concrete type. + /// + /// The indirection through `as_any` is because using `downcast_ref` + /// on `Box` *directly* only lets us downcast back to `&A` again. You can take a look at [this](https://stackoverflow.com/questions/33687447/how-to-get-a-reference-to-a-concrete-type-from-a-trait-object) + /// for more information. + /// + /// + fn as_any(&self) -> &dyn Any; } #[derive(Clone, Debug)] pub struct DocumentConfig { - pub version: DocumentVersionPB, + pub version: DocumentVersionPB, } impl std::default::Default for DocumentConfig { - fn default() -> Self { - Self { - version: DocumentVersionPB::V1, - } + fn default() -> Self { + Self { + version: DocumentVersionPB::V1, } + } } pub struct DocumentManager { + cloud_service: Arc, + rev_web_socket: Arc, + editor_map: Arc>>, + user: Arc, + persistence: Arc, + #[allow(dead_code)] + config: DocumentConfig, +} + +impl DocumentManager { + pub fn new( cloud_service: Arc, + document_user: Arc, + database: Arc, rev_web_socket: Arc, - editor_map: Arc>>, - user: Arc, - persistence: Arc, - #[allow(dead_code)] config: DocumentConfig, + ) -> Self { + Self { + cloud_service, + rev_web_socket, + editor_map: Arc::new(RwLock::new(RefCountHashMap::new())), + user: document_user, + persistence: Arc::new(DocumentPersistence::new(database)), + config, + } + } + + /// Called immediately after the application launched with the user sign in/sign up. + #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn initialize(&self, user_id: &str) -> FlowyResult<()> { + self.persistence.initialize(user_id)?; + listen_ws_state_changed(self.rev_web_socket.clone(), self.editor_map.clone()); + Ok(()) + } + + pub async fn initialize_with_new_user(&self, _user_id: &str, _token: &str) -> FlowyResult<()> { + Ok(()) + } + + #[tracing::instrument(level = "trace", skip_all, fields(document_id), err)] + pub async fn open_document_editor>( + &self, + document_id: T, + ) -> Result, FlowyError> { + let document_id = document_id.as_ref(); + tracing::Span::current().record("document_id", document_id); + self.init_document_editor(document_id).await + } + + #[tracing::instrument(level = "trace", skip(self, editor_id), fields(editor_id), err)] + pub async fn close_document_editor>(&self, editor_id: T) -> Result<(), FlowyError> { + let editor_id = editor_id.as_ref(); + tracing::Span::current().record("editor_id", editor_id); + self.editor_map.write().await.remove(editor_id).await; + Ok(()) + } + + pub async fn apply_edit(&self, params: EditParams) -> FlowyResult<()> { + let editor = self.get_document_editor(¶ms.doc_id).await?; + editor + .compose_local_operations(Bytes::from(params.operations)) + .await?; + Ok(()) + } + + pub async fn create_document>( + &self, + doc_id: T, + revisions: Vec, + ) -> FlowyResult<()> { + let doc_id = doc_id.as_ref().to_owned(); + let db_pool = self.persistence.database.db_pool()?; + // Maybe we could save the document to disk without creating the RevisionManager + let rev_manager = self.make_rev_manager(&doc_id, db_pool)?; + rev_manager.reset_object(revisions).await?; + Ok(()) + } + + pub async fn receive_ws_data(&self, data: Bytes) { + let result: Result = + ServerRevisionWSData::try_from(data); + match result { + Ok(data) => match self.editor_map.read().await.get(&data.object_id) { + None => tracing::error!( + "Can't find any source handler for {:?}-{:?}", + data.object_id, + data.payload + ), + Some(handler) => match handler.0.receive_ws_data(data).await { + Ok(_) => {}, + Err(e) => tracing::error!("{}", e), + }, + }, + Err(e) => { + tracing::error!("Document ws data parser failed: {:?}", e); + }, + } + } + + pub fn initial_document_content(&self) -> String { + match self.config.version { + DocumentVersionPB::V0 => initial_delta_document_content(), + DocumentVersionPB::V1 => initial_document_content(), + } + } } impl DocumentManager { - pub fn new( - cloud_service: Arc, - document_user: Arc, - database: Arc, - rev_web_socket: Arc, - config: DocumentConfig, - ) -> Self { - Self { + /// Returns the `DocumentEditor` + /// + /// # Arguments + /// + /// * `doc_id`: the id of the document + /// + /// returns: Result, FlowyError> + /// + async fn get_document_editor(&self, doc_id: &str) -> FlowyResult> { + match self.editor_map.read().await.get(doc_id) { + None => { + // + tracing::warn!("Should call init_document_editor first"); + self.init_document_editor(doc_id).await + }, + Some(handler) => Ok(handler.0.clone()), + } + } + + /// Initializes a document editor with the doc_id + /// + /// # Arguments + /// + /// * `doc_id`: the id of the document + /// * `pool`: sqlite connection pool + /// + /// returns: Result, FlowyError> + /// + #[tracing::instrument(level = "trace", skip(self), err)] + pub async fn init_document_editor( + &self, + doc_id: &str, + ) -> Result, FlowyError> { + let pool = self.persistence.database.db_pool()?; + let user = self.user.clone(); + let token = self.user.token()?; + let cloud_service = Arc::new(DocumentRevisionCloudService { + token, + server: self.cloud_service.clone(), + }); + + match self.config.version { + DocumentVersionPB::V0 => { + let rev_manager = self.make_delta_document_rev_manager(doc_id, pool.clone())?; + let editor: Arc = Arc::new( + DeltaDocumentEditor::new( + doc_id, + user, + rev_manager, + self.rev_web_socket.clone(), cloud_service, - rev_web_socket, - editor_map: Arc::new(RwLock::new(RefCountHashMap::new())), - user: document_user, - persistence: Arc::new(DocumentPersistence::new(database)), - config, - } + ) + .await?, + ); + self + .editor_map + .write() + .await + .insert(doc_id.to_string(), RefCountDocumentHandler(editor.clone())); + Ok(editor) + }, + DocumentVersionPB::V1 => { + let rev_manager = self.make_document_rev_manager(doc_id, pool.clone())?; + let editor: Arc = + Arc::new(AppFlowyDocumentEditor::new(doc_id, user, rev_manager, cloud_service).await?); + self + .editor_map + .write() + .await + .insert(doc_id.to_string(), RefCountDocumentHandler(editor.clone())); + Ok(editor) + }, } + } - /// Called immediately after the application launched with the user sign in/sign up. - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn initialize(&self, user_id: &str) -> FlowyResult<()> { - self.persistence.initialize(user_id)?; - listen_ws_state_changed(self.rev_web_socket.clone(), self.editor_map.clone()); - Ok(()) + fn make_rev_manager( + &self, + doc_id: &str, + pool: Arc, + ) -> Result>, FlowyError> { + match self.config.version { + DocumentVersionPB::V0 => self.make_delta_document_rev_manager(doc_id, pool), + DocumentVersionPB::V1 => self.make_document_rev_manager(doc_id, pool), } + } - pub async fn initialize_with_new_user(&self, _user_id: &str, _token: &str) -> FlowyResult<()> { - Ok(()) - } + fn make_document_rev_manager( + &self, + doc_id: &str, + pool: Arc, + ) -> Result>, FlowyError> { + let user_id = self.user.user_id()?; + let disk_cache = SQLiteDocumentRevisionPersistence::new(&user_id, pool.clone()); + let configuration = RevisionPersistenceConfiguration::new(200, true); + let rev_persistence = RevisionPersistence::new(&user_id, doc_id, disk_cache, configuration); + let snapshot_persistence = SQLiteDocumentRevisionSnapshotPersistence::new(doc_id, pool); + Ok(RevisionManager::new( + &user_id, + doc_id, + rev_persistence, + DocumentRevisionMergeable(), + snapshot_persistence, + )) + } - #[tracing::instrument(level = "trace", skip_all, fields(document_id), err)] - pub async fn open_document_editor>( - &self, - document_id: T, - ) -> Result, FlowyError> { - let document_id = document_id.as_ref(); - tracing::Span::current().record("document_id", document_id); - self.init_document_editor(document_id).await - } - - #[tracing::instrument(level = "trace", skip(self, editor_id), fields(editor_id), err)] - pub async fn close_document_editor>(&self, editor_id: T) -> Result<(), FlowyError> { - let editor_id = editor_id.as_ref(); - tracing::Span::current().record("editor_id", editor_id); - self.editor_map.write().await.remove(editor_id).await; - Ok(()) - } - - pub async fn apply_edit(&self, params: EditParams) -> FlowyResult<()> { - let editor = self.get_document_editor(¶ms.doc_id).await?; - editor.compose_local_operations(Bytes::from(params.operations)).await?; - Ok(()) - } - - pub async fn create_document>(&self, doc_id: T, revisions: Vec) -> FlowyResult<()> { - let doc_id = doc_id.as_ref().to_owned(); - let db_pool = self.persistence.database.db_pool()?; - // Maybe we could save the document to disk without creating the RevisionManager - let rev_manager = self.make_rev_manager(&doc_id, db_pool)?; - rev_manager.reset_object(revisions).await?; - Ok(()) - } - - pub async fn receive_ws_data(&self, data: Bytes) { - let result: Result = ServerRevisionWSData::try_from(data); - match result { - Ok(data) => match self.editor_map.read().await.get(&data.object_id) { - None => tracing::error!( - "Can't find any source handler for {:?}-{:?}", - data.object_id, - data.payload - ), - Some(handler) => match handler.0.receive_ws_data(data).await { - Ok(_) => {} - Err(e) => tracing::error!("{}", e), - }, - }, - Err(e) => { - tracing::error!("Document ws data parser failed: {:?}", e); - } - } - } - - pub fn initial_document_content(&self) -> String { - match self.config.version { - DocumentVersionPB::V0 => initial_delta_document_content(), - DocumentVersionPB::V1 => initial_document_content(), - } - } -} - -impl DocumentManager { - /// Returns the `DocumentEditor` - /// - /// # Arguments - /// - /// * `doc_id`: the id of the document - /// - /// returns: Result, FlowyError> - /// - async fn get_document_editor(&self, doc_id: &str) -> FlowyResult> { - match self.editor_map.read().await.get(doc_id) { - None => { - // - tracing::warn!("Should call init_document_editor first"); - self.init_document_editor(doc_id).await - } - Some(handler) => Ok(handler.0.clone()), - } - } - - /// Initializes a document editor with the doc_id - /// - /// # Arguments - /// - /// * `doc_id`: the id of the document - /// * `pool`: sqlite connection pool - /// - /// returns: Result, FlowyError> - /// - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn init_document_editor(&self, doc_id: &str) -> Result, FlowyError> { - let pool = self.persistence.database.db_pool()?; - let user = self.user.clone(); - let token = self.user.token()?; - let cloud_service = Arc::new(DocumentRevisionCloudService { - token, - server: self.cloud_service.clone(), - }); - - match self.config.version { - DocumentVersionPB::V0 => { - let rev_manager = self.make_delta_document_rev_manager(doc_id, pool.clone())?; - let editor: Arc = Arc::new( - DeltaDocumentEditor::new(doc_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service) - .await?, - ); - self.editor_map - .write() - .await - .insert(doc_id.to_string(), RefCountDocumentHandler(editor.clone())); - Ok(editor) - } - DocumentVersionPB::V1 => { - let rev_manager = self.make_document_rev_manager(doc_id, pool.clone())?; - let editor: Arc = - Arc::new(AppFlowyDocumentEditor::new(doc_id, user, rev_manager, cloud_service).await?); - self.editor_map - .write() - .await - .insert(doc_id.to_string(), RefCountDocumentHandler(editor.clone())); - Ok(editor) - } - } - } - - fn make_rev_manager( - &self, - doc_id: &str, - pool: Arc, - ) -> Result>, FlowyError> { - match self.config.version { - DocumentVersionPB::V0 => self.make_delta_document_rev_manager(doc_id, pool), - DocumentVersionPB::V1 => self.make_document_rev_manager(doc_id, pool), - } - } - - fn make_document_rev_manager( - &self, - doc_id: &str, - pool: Arc, - ) -> Result>, FlowyError> { - let user_id = self.user.user_id()?; - let disk_cache = SQLiteDocumentRevisionPersistence::new(&user_id, pool.clone()); - let configuration = RevisionPersistenceConfiguration::new(200, true); - let rev_persistence = RevisionPersistence::new(&user_id, doc_id, disk_cache, configuration); - let snapshot_persistence = SQLiteDocumentRevisionSnapshotPersistence::new(doc_id, pool); - Ok(RevisionManager::new( - &user_id, - doc_id, - rev_persistence, - DocumentRevisionMergeable(), - snapshot_persistence, - )) - } - - fn make_delta_document_rev_manager( - &self, - doc_id: &str, - pool: Arc, - ) -> Result>, FlowyError> { - let user_id = self.user.user_id()?; - let disk_cache = SQLiteDeltaDocumentRevisionPersistence::new(&user_id, pool); - let configuration = RevisionPersistenceConfiguration::new(100, true); - let rev_persistence = RevisionPersistence::new(&user_id, doc_id, disk_cache, configuration); - Ok(RevisionManager::new( - &user_id, - doc_id, - rev_persistence, - DeltaDocumentRevisionMergeable(), - DeltaDocumentSnapshotPersistence(), - )) - } + fn make_delta_document_rev_manager( + &self, + doc_id: &str, + pool: Arc, + ) -> Result>, FlowyError> { + let user_id = self.user.user_id()?; + let disk_cache = SQLiteDeltaDocumentRevisionPersistence::new(&user_id, pool); + let configuration = RevisionPersistenceConfiguration::new(100, true); + let rev_persistence = RevisionPersistence::new(&user_id, doc_id, disk_cache, configuration); + Ok(RevisionManager::new( + &user_id, + doc_id, + rev_persistence, + DeltaDocumentRevisionMergeable(), + DeltaDocumentSnapshotPersistence(), + )) + } } struct DocumentRevisionCloudService { - token: String, - server: Arc, + token: String, + server: Arc, } impl RevisionCloudService for DocumentRevisionCloudService { - #[tracing::instrument(level = "trace", skip(self))] - fn fetch_object(&self, user_id: &str, object_id: &str) -> FutureResult, FlowyError> { - let params: DocumentId = object_id.to_string().into(); - let server = self.server.clone(); - let token = self.token.clone(); + #[tracing::instrument(level = "trace", skip(self))] + fn fetch_object( + &self, + user_id: &str, + object_id: &str, + ) -> FutureResult, FlowyError> { + let params: DocumentId = object_id.to_string().into(); + let server = self.server.clone(); + let token = self.token.clone(); - FutureResult::new(async move { - match server.fetch_document(&token, params).await? { - None => Err(FlowyError::record_not_found().context("Remote doesn't have this document")), - Some(payload) => { - let bytes = Bytes::from(payload.data.clone()); - let doc_md5 = md5(&bytes); - let revision = Revision::new(&payload.doc_id, payload.base_rev_id, payload.rev_id, bytes, doc_md5); - Ok(vec![revision]) - } - } - }) - } + FutureResult::new(async move { + match server.fetch_document(&token, params).await? { + None => Err(FlowyError::record_not_found().context("Remote doesn't have this document")), + Some(payload) => { + let bytes = Bytes::from(payload.data.clone()); + let doc_md5 = md5(&bytes); + let revision = Revision::new( + &payload.doc_id, + payload.base_rev_id, + payload.rev_id, + bytes, + doc_md5, + ); + Ok(vec![revision]) + }, + } + }) + } } #[derive(Clone)] @@ -328,30 +357,30 @@ struct RefCountDocumentHandler(Arc); #[async_trait] impl RefCountValue for RefCountDocumentHandler { - async fn did_remove(&self) { - self.0.close().await; - } + async fn did_remove(&self) { + self.0.close().await; + } } impl std::ops::Deref for RefCountDocumentHandler { - type Target = Arc; + type Target = Arc; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } #[tracing::instrument(level = "trace", skip(web_socket, handlers))] fn listen_ws_state_changed( - web_socket: Arc, - handlers: Arc>>, + web_socket: Arc, + handlers: Arc>>, ) { - tokio::spawn(async move { - let mut notify = web_socket.subscribe_state_changed().await; - while let Ok(state) = notify.recv().await { - handlers.read().await.values().iter().for_each(|handler| { - handler.receive_ws_state(&state); - }) - } - }); + tokio::spawn(async move { + let mut notify = web_socket.subscribe_state_changed().await; + while let Ok(state) = notify.recv().await { + handlers.read().await.values().iter().for_each(|handler| { + handler.receive_ws_state(&state); + }) + } + }); } diff --git a/frontend/rust-lib/flowy-document/src/old_editor/editor.rs b/frontend/rust-lib/flowy-document/src/old_editor/editor.rs index 72810649f1..998d7f4bd3 100644 --- a/frontend/rust-lib/flowy-document/src/old_editor/editor.rs +++ b/frontend/rust-lib/flowy-document/src/old_editor/editor.rs @@ -9,16 +9,16 @@ use flowy_client_sync::errors::SyncResult; use flowy_client_sync::make_operations_from_revisions; use flowy_error::{internal_error, FlowyResult}; use flowy_revision::{ - RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, RevisionObjectSerializer, - RevisionWebSocket, + RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, + RevisionObjectSerializer, RevisionWebSocket, }; use flowy_sqlite::ConnectionPool; use lib_infra::async_trait::async_trait; use lib_infra::future::FutureResult; use lib_ot::core::{AttributeEntry, AttributeHashMap}; use lib_ot::{ - core::{DeltaOperation, Interval}, - text_delta::DeltaTextOperations, + core::{DeltaOperation, Interval}, + text_delta::DeltaTextOperations, }; use lib_ws::WSConnectState; use revision_model::Revision; @@ -28,273 +28,280 @@ use tokio::sync::{mpsc, oneshot}; use ws_model::ws_revision::ServerRevisionWSData; pub struct DeltaDocumentEditor { - pub doc_id: String, - #[allow(dead_code)] - rev_manager: Arc>>, - #[cfg(feature = "sync")] - ws_manager: Arc, - edit_cmd_tx: EditorCommandSender, + pub doc_id: String, + #[allow(dead_code)] + rev_manager: Arc>>, + #[cfg(feature = "sync")] + ws_manager: Arc, + edit_cmd_tx: EditorCommandSender, } impl DeltaDocumentEditor { - #[allow(unused_variables)] - pub(crate) async fn new( - doc_id: &str, - user: Arc, - mut rev_manager: RevisionManager>, - rev_web_socket: Arc, - cloud_service: Arc, - ) -> FlowyResult> { - let document = rev_manager - .initialize::(Some(cloud_service)) - .await?; - let operations = DeltaTextOperations::from_bytes(&document.data)?; - let rev_manager = Arc::new(rev_manager); - let doc_id = doc_id.to_string(); - let user_id = user.user_id()?; + #[allow(unused_variables)] + pub(crate) async fn new( + doc_id: &str, + user: Arc, + mut rev_manager: RevisionManager>, + rev_web_socket: Arc, + cloud_service: Arc, + ) -> FlowyResult> { + let document = rev_manager + .initialize::(Some(cloud_service)) + .await?; + let operations = DeltaTextOperations::from_bytes(&document.data)?; + let rev_manager = Arc::new(rev_manager); + let doc_id = doc_id.to_string(); + let user_id = user.user_id()?; - let edit_cmd_tx = spawn_edit_queue(user, rev_manager.clone(), operations); - #[cfg(feature = "sync")] - let ws_manager = crate::old_editor::web_socket::make_document_ws_manager( - doc_id.clone(), - user_id.clone(), - edit_cmd_tx.clone(), - rev_manager.clone(), - rev_web_socket, - ) - .await; - let editor = Arc::new(Self { - doc_id, - rev_manager, - #[cfg(feature = "sync")] - ws_manager, - edit_cmd_tx, - }); - Ok(editor) - } + let edit_cmd_tx = spawn_edit_queue(user, rev_manager.clone(), operations); + #[cfg(feature = "sync")] + let ws_manager = crate::old_editor::web_socket::make_document_ws_manager( + doc_id.clone(), + user_id.clone(), + edit_cmd_tx.clone(), + rev_manager.clone(), + rev_web_socket, + ) + .await; + let editor = Arc::new(Self { + doc_id, + rev_manager, + #[cfg(feature = "sync")] + ws_manager, + edit_cmd_tx, + }); + Ok(editor) + } - pub async fn insert(&self, index: usize, data: T) -> Result<(), FlowyError> { - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::Insert { - index, - data: data.to_string(), - ret, - }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.map_err(internal_error)??; - Ok(()) - } + pub async fn insert(&self, index: usize, data: T) -> Result<(), FlowyError> { + let (ret, rx) = oneshot::channel::>(); + let msg = EditorCommand::Insert { + index, + data: data.to_string(), + ret, + }; + let _ = self.edit_cmd_tx.send(msg).await; + rx.await.map_err(internal_error)??; + Ok(()) + } - pub async fn delete(&self, interval: Interval) -> Result<(), FlowyError> { - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::Delete { interval, ret }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.map_err(internal_error)??; - Ok(()) - } + pub async fn delete(&self, interval: Interval) -> Result<(), FlowyError> { + let (ret, rx) = oneshot::channel::>(); + let msg = EditorCommand::Delete { interval, ret }; + let _ = self.edit_cmd_tx.send(msg).await; + rx.await.map_err(internal_error)??; + Ok(()) + } - pub async fn format(&self, interval: Interval, attribute: AttributeEntry) -> Result<(), FlowyError> { - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::Format { - interval, - attribute, - ret, - }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.map_err(internal_error)??; - Ok(()) - } + pub async fn format( + &self, + interval: Interval, + attribute: AttributeEntry, + ) -> Result<(), FlowyError> { + let (ret, rx) = oneshot::channel::>(); + let msg = EditorCommand::Format { + interval, + attribute, + ret, + }; + let _ = self.edit_cmd_tx.send(msg).await; + rx.await.map_err(internal_error)??; + Ok(()) + } - pub async fn replace(&self, interval: Interval, data: T) -> Result<(), FlowyError> { - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::Replace { - interval, - data: data.to_string(), - ret, - }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.map_err(internal_error)??; - Ok(()) - } + pub async fn replace(&self, interval: Interval, data: T) -> Result<(), FlowyError> { + let (ret, rx) = oneshot::channel::>(); + let msg = EditorCommand::Replace { + interval, + data: data.to_string(), + ret, + }; + let _ = self.edit_cmd_tx.send(msg).await; + rx.await.map_err(internal_error)??; + Ok(()) + } - pub async fn can_undo(&self) -> bool { - let (ret, rx) = oneshot::channel::(); - let msg = EditorCommand::CanUndo { ret }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.unwrap_or(false) - } + pub async fn can_undo(&self) -> bool { + let (ret, rx) = oneshot::channel::(); + let msg = EditorCommand::CanUndo { ret }; + let _ = self.edit_cmd_tx.send(msg).await; + rx.await.unwrap_or(false) + } - pub async fn can_redo(&self) -> bool { - let (ret, rx) = oneshot::channel::(); - let msg = EditorCommand::CanRedo { ret }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.unwrap_or(false) - } + pub async fn can_redo(&self) -> bool { + let (ret, rx) = oneshot::channel::(); + let msg = EditorCommand::CanRedo { ret }; + let _ = self.edit_cmd_tx.send(msg).await; + rx.await.unwrap_or(false) + } - pub async fn undo(&self) -> Result<(), FlowyError> { - let (ret, rx) = oneshot::channel(); - let msg = EditorCommand::Undo { ret }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.map_err(internal_error)??; - Ok(()) - } + pub async fn undo(&self) -> Result<(), FlowyError> { + let (ret, rx) = oneshot::channel(); + let msg = EditorCommand::Undo { ret }; + let _ = self.edit_cmd_tx.send(msg).await; + rx.await.map_err(internal_error)??; + Ok(()) + } - pub async fn redo(&self) -> Result<(), FlowyError> { - let (ret, rx) = oneshot::channel(); - let msg = EditorCommand::Redo { ret }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.map_err(internal_error)??; - Ok(()) - } + pub async fn redo(&self) -> Result<(), FlowyError> { + let (ret, rx) = oneshot::channel(); + let msg = EditorCommand::Redo { ret }; + let _ = self.edit_cmd_tx.send(msg).await; + rx.await.map_err(internal_error)??; + Ok(()) + } } #[async_trait] impl DocumentEditor for Arc { - async fn close(&self) { - #[cfg(feature = "sync")] - self.ws_manager.stop(); - } + async fn close(&self) { + #[cfg(feature = "sync")] + self.ws_manager.stop(); + } - fn export(&self) -> FutureResult { - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::GetOperationsString { ret }; - let edit_cmd_tx = self.edit_cmd_tx.clone(); - FutureResult::new(async move { - let _ = edit_cmd_tx.send(msg).await; - let json = rx.await.map_err(internal_error)??; - Ok(json) - }) - } + fn export(&self) -> FutureResult { + let (ret, rx) = oneshot::channel::>(); + let msg = EditorCommand::GetOperationsString { ret }; + let edit_cmd_tx = self.edit_cmd_tx.clone(); + FutureResult::new(async move { + let _ = edit_cmd_tx.send(msg).await; + let json = rx.await.map_err(internal_error)??; + Ok(json) + }) + } - fn duplicate(&self) -> FutureResult { - self.export() - } + fn duplicate(&self) -> FutureResult { + self.export() + } - #[allow(unused_variables)] - fn receive_ws_data(&self, data: ServerRevisionWSData) -> FutureResult<(), FlowyError> { - let cloned_self = self.clone(); - FutureResult::new(async move { - #[cfg(feature = "sync")] - let _ = cloned_self.ws_manager.receive_ws_data(data).await?; + #[allow(unused_variables)] + fn receive_ws_data(&self, data: ServerRevisionWSData) -> FutureResult<(), FlowyError> { + let cloned_self = self.clone(); + FutureResult::new(async move { + #[cfg(feature = "sync")] + let _ = cloned_self.ws_manager.receive_ws_data(data).await?; - Ok(()) - }) - } + Ok(()) + }) + } - #[allow(unused_variables)] - fn receive_ws_state(&self, state: &WSConnectState) { - #[cfg(feature = "sync")] - self.ws_manager.connect_state_changed(state.clone()); - } + #[allow(unused_variables)] + fn receive_ws_state(&self, state: &WSConnectState) { + #[cfg(feature = "sync")] + self.ws_manager.connect_state_changed(state.clone()); + } - fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError> { - let edit_cmd_tx = self.edit_cmd_tx.clone(); - FutureResult::new(async move { - let operations = DeltaTextOperations::from_bytes(&data)?; - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::ComposeLocalOperations { operations, ret }; + fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError> { + let edit_cmd_tx = self.edit_cmd_tx.clone(); + FutureResult::new(async move { + let operations = DeltaTextOperations::from_bytes(&data)?; + let (ret, rx) = oneshot::channel::>(); + let msg = EditorCommand::ComposeLocalOperations { operations, ret }; - let _ = edit_cmd_tx.send(msg).await; - rx.await.map_err(internal_error)??; - Ok(()) - }) - } + let _ = edit_cmd_tx.send(msg).await; + rx.await.map_err(internal_error)??; + Ok(()) + }) + } - fn as_any(&self) -> &dyn Any { - self - } + fn as_any(&self) -> &dyn Any { + self + } } impl std::ops::Drop for DeltaDocumentEditor { - fn drop(&mut self) { - tracing::trace!("{} DocumentEditor was dropped", self.doc_id) - } + fn drop(&mut self) { + tracing::trace!("{} DocumentEditor was dropped", self.doc_id) + } } // The edit queue will exit after the EditorCommandSender was dropped. fn spawn_edit_queue( - user: Arc, - rev_manager: Arc>>, - delta: DeltaTextOperations, + user: Arc, + rev_manager: Arc>>, + delta: DeltaTextOperations, ) -> EditorCommandSender { - let (sender, receiver) = mpsc::channel(1000); - let edit_queue = EditDocumentQueue::new(user, rev_manager, delta, receiver); - // We can use tokio::task::spawn_local here by using tokio::spawn_blocking. - // https://github.com/tokio-rs/tokio/issues/2095 - // tokio::task::spawn_blocking(move || { - // let rt = tokio::runtime::Handle::current(); - // rt.block_on(async { - // let local = tokio::task::LocalSet::new(); - // local.run_until(edit_queue.run()).await; - // }); - // }); - tokio::spawn(edit_queue.run()); - sender + let (sender, receiver) = mpsc::channel(1000); + let edit_queue = EditDocumentQueue::new(user, rev_manager, delta, receiver); + // We can use tokio::task::spawn_local here by using tokio::spawn_blocking. + // https://github.com/tokio-rs/tokio/issues/2095 + // tokio::task::spawn_blocking(move || { + // let rt = tokio::runtime::Handle::current(); + // rt.block_on(async { + // let local = tokio::task::LocalSet::new(); + // local.run_until(edit_queue.run()).await; + // }); + // }); + tokio::spawn(edit_queue.run()); + sender } #[cfg(feature = "flowy_unit_test")] impl DeltaDocumentEditor { - pub async fn document_operations(&self) -> FlowyResult { - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::GetOperations { ret }; - let _ = self.edit_cmd_tx.send(msg).await; - let delta = rx.await.map_err(internal_error)??; - Ok(delta) - } + pub async fn document_operations(&self) -> FlowyResult { + let (ret, rx) = oneshot::channel::>(); + let msg = EditorCommand::GetOperations { ret }; + let _ = self.edit_cmd_tx.send(msg).await; + let delta = rx.await.map_err(internal_error)??; + Ok(delta) + } - pub fn rev_manager(&self) -> Arc>> { - self.rev_manager.clone() - } + pub fn rev_manager(&self) -> Arc>> { + self.rev_manager.clone() + } } pub struct DeltaDocumentRevisionSerde(); impl RevisionObjectDeserializer for DeltaDocumentRevisionSerde { - type Output = DocumentInfo; + type Output = DocumentInfo; - fn deserialize_revisions(object_id: &str, revisions: Vec) -> FlowyResult { - let (base_rev_id, rev_id) = revisions.last().unwrap().pair_rev_id(); - let mut delta = make_operations_from_revisions(revisions)?; - correct_delta(&mut delta); + fn deserialize_revisions(object_id: &str, revisions: Vec) -> FlowyResult { + let (base_rev_id, rev_id) = revisions.last().unwrap().pair_rev_id(); + let mut delta = make_operations_from_revisions(revisions)?; + correct_delta(&mut delta); - Result::::Ok(DocumentInfo { - doc_id: object_id.to_owned(), - data: delta.json_bytes().to_vec(), - rev_id, - base_rev_id, - }) - } + Result::::Ok(DocumentInfo { + doc_id: object_id.to_owned(), + data: delta.json_bytes().to_vec(), + rev_id, + base_rev_id, + }) + } - fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { - None - } + fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { + None + } } impl RevisionObjectSerializer for DeltaDocumentRevisionSerde { - fn combine_revisions(revisions: Vec) -> FlowyResult { - let operations = make_operations_from_revisions::(revisions)?; - Ok(operations.json_bytes()) - } + fn combine_revisions(revisions: Vec) -> FlowyResult { + let operations = make_operations_from_revisions::(revisions)?; + Ok(operations.json_bytes()) + } } pub(crate) struct DeltaDocumentRevisionMergeable(); impl RevisionMergeable for DeltaDocumentRevisionMergeable { - fn combine_revisions(&self, revisions: Vec) -> FlowyResult { - DeltaDocumentRevisionSerde::combine_revisions(revisions) - } + fn combine_revisions(&self, revisions: Vec) -> FlowyResult { + DeltaDocumentRevisionSerde::combine_revisions(revisions) + } } // quill-editor requires the delta should end with '\n' and only contains the // insert operation. The function, correct_delta maybe be removed in the future. fn correct_delta(delta: &mut DeltaTextOperations) { - if let Some(op) = delta.ops.last() { - let op_data = op.get_data(); - if !op_data.ends_with('\n') { - tracing::warn!("The document must end with newline. Correcting it by inserting newline op"); - delta.ops.push(DeltaOperation::Insert("\n".into())); - } + if let Some(op) = delta.ops.last() { + let op_data = op.get_data(); + if !op_data.ends_with('\n') { + tracing::warn!("The document must end with newline. Correcting it by inserting newline op"); + delta.ops.push(DeltaOperation::Insert("\n".into())); } + } - if let Some(op) = delta.ops.iter().find(|op| !op.is_insert()) { - tracing::warn!("The document can only contains insert operations, but found {:?}", op); - delta.ops.retain(|op| op.is_insert()); - } + if let Some(op) = delta.ops.iter().find(|op| !op.is_insert()) { + tracing::warn!( + "The document can only contains insert operations, but found {:?}", + op + ); + delta.ops.retain(|op| op.is_insert()); + } } diff --git a/frontend/rust-lib/flowy-document/src/old_editor/queue.rs b/frontend/rust-lib/flowy-document/src/old_editor/queue.rs index 2bde143ce7..bf2bb3895a 100644 --- a/frontend/rust-lib/flowy-document/src/old_editor/queue.rs +++ b/frontend/rust-lib/flowy-document/src/old_editor/queue.rs @@ -2,8 +2,8 @@ use crate::old_editor::web_socket::DeltaDocumentResolveOperations; use crate::DocumentUser; use async_stream::stream; use flowy_client_sync::{ - client_document::{history::UndoResult, ClientDocument}, - errors::SyncError, + client_document::{history::UndoResult, ClientDocument}, + errors::SyncError, }; use flowy_error::FlowyError; use flowy_revision::{RevisionMD5, RevisionManager, TransformOperations}; @@ -11,8 +11,8 @@ use flowy_sqlite::ConnectionPool; use futures::stream::StreamExt; use lib_ot::core::AttributeEntry; use lib_ot::{ - core::{Interval, OperationTransform}, - text_delta::DeltaTextOperations, + core::{Interval, OperationTransform}, + text_delta::DeltaTextOperations, }; use std::sync::Arc; use tokio::sync::mpsc::{Receiver, Sender}; @@ -21,165 +21,176 @@ use tokio::sync::{oneshot, RwLock}; // The EditorCommandQueue executes each command that will alter the document in // serial. pub(crate) struct EditDocumentQueue { - document: Arc>, - #[allow(dead_code)] - user: Arc, - rev_manager: Arc>>, - receiver: Option, + document: Arc>, + #[allow(dead_code)] + user: Arc, + rev_manager: Arc>>, + receiver: Option, } impl EditDocumentQueue { - pub(crate) fn new( - user: Arc, - rev_manager: Arc>>, - operations: DeltaTextOperations, - receiver: EditorCommandReceiver, - ) -> Self { - let document = Arc::new(RwLock::new(ClientDocument::from_operations(operations))); - Self { - document, - user, - rev_manager, - receiver: Some(receiver), - } + pub(crate) fn new( + user: Arc, + rev_manager: Arc>>, + operations: DeltaTextOperations, + receiver: EditorCommandReceiver, + ) -> Self { + let document = Arc::new(RwLock::new(ClientDocument::from_operations(operations))); + Self { + document, + user, + rev_manager, + receiver: Some(receiver), } + } - pub(crate) async fn run(mut self) { - let mut receiver = self.receiver.take().expect("Should only call once"); - let stream = stream! { - loop { - match receiver.recv().await { - Some(msg) => yield msg, - None => break, - } + pub(crate) async fn run(mut self) { + let mut receiver = self.receiver.take().expect("Should only call once"); + let stream = stream! { + loop { + match receiver.recv().await { + Some(msg) => yield msg, + None => break, } + } + }; + stream + .for_each(|command| async { + match self.handle_command(command).await { + Ok(_) => {}, + Err(e) => tracing::debug!("[EditCommandQueue]: {}", e), + } + }) + .await; + } + + #[tracing::instrument(level = "trace", skip(self), err)] + async fn handle_command(&self, command: EditorCommand) -> Result<(), FlowyError> { + match command { + EditorCommand::ComposeLocalOperations { operations, ret } => { + let mut document = self.document.write().await; + document.compose_operations(operations.clone())?; + let md5 = document.document_md5(); + drop(document); + let _ = self.save_local_operations(operations, md5).await?; + let _ = ret.send(Ok(())); + }, + EditorCommand::ComposeRemoteOperation { + client_operations, + ret, + } => { + let mut document = self.document.write().await; + document.compose_operations(client_operations.clone())?; + let md5 = document.document_md5(); + drop(document); + let _ = ret.send(Ok(md5.into())); + }, + EditorCommand::ResetOperations { operations, ret } => { + let mut document = self.document.write().await; + document.set_operations(operations); + let md5 = document.document_md5(); + drop(document); + let _ = ret.send(Ok(md5.into())); + }, + EditorCommand::TransformOperations { operations, ret } => { + let f = || async { + let read_guard = self.document.read().await; + let mut server_operations: Option = None; + let client_operations: DeltaTextOperations; + + if read_guard.is_empty() { + // Do nothing + client_operations = operations; + } else { + let (s_prime, c_prime) = read_guard.get_operations().transform(&operations)?; + client_operations = c_prime; + server_operations = Some(DeltaDocumentResolveOperations(s_prime)); + } + drop(read_guard); + Ok::(TransformOperations { + client_operations: DeltaDocumentResolveOperations(client_operations), + server_operations, + }) }; - stream - .for_each(|command| async { - match self.handle_command(command).await { - Ok(_) => {} - Err(e) => tracing::debug!("[EditCommandQueue]: {}", e), - } - }) - .await; + let _ = ret.send(f().await); + }, + EditorCommand::Insert { index, data, ret } => { + let mut write_guard = self.document.write().await; + let operations = write_guard.insert(index, data)?; + let md5 = write_guard.document_md5(); + let _ = self.save_local_operations(operations, md5).await?; + let _ = ret.send(Ok(())); + }, + EditorCommand::Delete { interval, ret } => { + let mut write_guard = self.document.write().await; + let operations = write_guard.delete(interval)?; + let md5 = write_guard.document_md5(); + let _ = self.save_local_operations(operations, md5).await?; + let _ = ret.send(Ok(())); + }, + EditorCommand::Format { + interval, + attribute, + ret, + } => { + let mut write_guard = self.document.write().await; + let operations = write_guard.format(interval, attribute)?; + let md5 = write_guard.document_md5(); + let _ = self.save_local_operations(operations, md5).await?; + let _ = ret.send(Ok(())); + }, + EditorCommand::Replace { + interval, + data, + ret, + } => { + let mut write_guard = self.document.write().await; + let operations = write_guard.replace(interval, data)?; + let md5 = write_guard.document_md5(); + let _ = self.save_local_operations(operations, md5).await?; + let _ = ret.send(Ok(())); + }, + EditorCommand::CanUndo { ret } => { + let _ = ret.send(self.document.read().await.can_undo()); + }, + EditorCommand::CanRedo { ret } => { + let _ = ret.send(self.document.read().await.can_redo()); + }, + EditorCommand::Undo { ret } => { + let mut write_guard = self.document.write().await; + let UndoResult { operations } = write_guard.undo()?; + let md5 = write_guard.document_md5(); + let _ = self.save_local_operations(operations, md5).await?; + let _ = ret.send(Ok(())); + }, + EditorCommand::Redo { ret } => { + let mut write_guard = self.document.write().await; + let UndoResult { operations } = write_guard.redo()?; + let md5 = write_guard.document_md5(); + let _ = self.save_local_operations(operations, md5).await?; + let _ = ret.send(Ok(())); + }, + EditorCommand::GetOperationsString { ret } => { + let data = self.document.read().await.get_operations_json(); + let _ = ret.send(Ok(data)); + }, + EditorCommand::GetOperations { ret } => { + let operations = self.document.read().await.get_operations().clone(); + let _ = ret.send(Ok(operations)); + }, } + Ok(()) + } - #[tracing::instrument(level = "trace", skip(self), err)] - async fn handle_command(&self, command: EditorCommand) -> Result<(), FlowyError> { - match command { - EditorCommand::ComposeLocalOperations { operations, ret } => { - let mut document = self.document.write().await; - document.compose_operations(operations.clone())?; - let md5 = document.document_md5(); - drop(document); - let _ = self.save_local_operations(operations, md5).await?; - let _ = ret.send(Ok(())); - } - EditorCommand::ComposeRemoteOperation { client_operations, ret } => { - let mut document = self.document.write().await; - document.compose_operations(client_operations.clone())?; - let md5 = document.document_md5(); - drop(document); - let _ = ret.send(Ok(md5.into())); - } - EditorCommand::ResetOperations { operations, ret } => { - let mut document = self.document.write().await; - document.set_operations(operations); - let md5 = document.document_md5(); - drop(document); - let _ = ret.send(Ok(md5.into())); - } - EditorCommand::TransformOperations { operations, ret } => { - let f = || async { - let read_guard = self.document.read().await; - let mut server_operations: Option = None; - let client_operations: DeltaTextOperations; - - if read_guard.is_empty() { - // Do nothing - client_operations = operations; - } else { - let (s_prime, c_prime) = read_guard.get_operations().transform(&operations)?; - client_operations = c_prime; - server_operations = Some(DeltaDocumentResolveOperations(s_prime)); - } - drop(read_guard); - Ok::(TransformOperations { - client_operations: DeltaDocumentResolveOperations(client_operations), - server_operations, - }) - }; - let _ = ret.send(f().await); - } - EditorCommand::Insert { index, data, ret } => { - let mut write_guard = self.document.write().await; - let operations = write_guard.insert(index, data)?; - let md5 = write_guard.document_md5(); - let _ = self.save_local_operations(operations, md5).await?; - let _ = ret.send(Ok(())); - } - EditorCommand::Delete { interval, ret } => { - let mut write_guard = self.document.write().await; - let operations = write_guard.delete(interval)?; - let md5 = write_guard.document_md5(); - let _ = self.save_local_operations(operations, md5).await?; - let _ = ret.send(Ok(())); - } - EditorCommand::Format { - interval, - attribute, - ret, - } => { - let mut write_guard = self.document.write().await; - let operations = write_guard.format(interval, attribute)?; - let md5 = write_guard.document_md5(); - let _ = self.save_local_operations(operations, md5).await?; - let _ = ret.send(Ok(())); - } - EditorCommand::Replace { interval, data, ret } => { - let mut write_guard = self.document.write().await; - let operations = write_guard.replace(interval, data)?; - let md5 = write_guard.document_md5(); - let _ = self.save_local_operations(operations, md5).await?; - let _ = ret.send(Ok(())); - } - EditorCommand::CanUndo { ret } => { - let _ = ret.send(self.document.read().await.can_undo()); - } - EditorCommand::CanRedo { ret } => { - let _ = ret.send(self.document.read().await.can_redo()); - } - EditorCommand::Undo { ret } => { - let mut write_guard = self.document.write().await; - let UndoResult { operations } = write_guard.undo()?; - let md5 = write_guard.document_md5(); - let _ = self.save_local_operations(operations, md5).await?; - let _ = ret.send(Ok(())); - } - EditorCommand::Redo { ret } => { - let mut write_guard = self.document.write().await; - let UndoResult { operations } = write_guard.redo()?; - let md5 = write_guard.document_md5(); - let _ = self.save_local_operations(operations, md5).await?; - let _ = ret.send(Ok(())); - } - EditorCommand::GetOperationsString { ret } => { - let data = self.document.read().await.get_operations_json(); - let _ = ret.send(Ok(data)); - } - EditorCommand::GetOperations { ret } => { - let operations = self.document.read().await.get_operations().clone(); - let _ = ret.send(Ok(operations)); - } - } - Ok(()) - } - - async fn save_local_operations(&self, operations: DeltaTextOperations, md5: String) -> Result { - let bytes = operations.json_bytes(); - let rev_id = self.rev_manager.add_local_revision(bytes, md5).await?; - Ok(rev_id) - } + async fn save_local_operations( + &self, + operations: DeltaTextOperations, + md5: String, + ) -> Result { + let bytes = operations.json_bytes(); + let rev_id = self.rev_manager.add_local_revision(bytes, md5).await?; + Ok(rev_id) + } } pub type TextTransformOperations = TransformOperations; @@ -188,80 +199,80 @@ pub(crate) type EditorCommandReceiver = Receiver; pub(crate) type Ret = oneshot::Sender>; pub(crate) enum EditorCommand { - ComposeLocalOperations { - operations: DeltaTextOperations, - ret: Ret<()>, - }, - ComposeRemoteOperation { - client_operations: DeltaTextOperations, - ret: Ret, - }, - ResetOperations { - operations: DeltaTextOperations, - ret: Ret, - }, - TransformOperations { - operations: DeltaTextOperations, - ret: Ret, - }, - Insert { - index: usize, - data: String, - ret: Ret<()>, - }, - Delete { - interval: Interval, - ret: Ret<()>, - }, - Format { - interval: Interval, - attribute: AttributeEntry, - ret: Ret<()>, - }, - Replace { - interval: Interval, - data: String, - ret: Ret<()>, - }, - CanUndo { - ret: oneshot::Sender, - }, - CanRedo { - ret: oneshot::Sender, - }, - Undo { - ret: Ret<()>, - }, - Redo { - ret: Ret<()>, - }, - GetOperationsString { - ret: Ret, - }, - #[allow(dead_code)] - GetOperations { - ret: Ret, - }, + ComposeLocalOperations { + operations: DeltaTextOperations, + ret: Ret<()>, + }, + ComposeRemoteOperation { + client_operations: DeltaTextOperations, + ret: Ret, + }, + ResetOperations { + operations: DeltaTextOperations, + ret: Ret, + }, + TransformOperations { + operations: DeltaTextOperations, + ret: Ret, + }, + Insert { + index: usize, + data: String, + ret: Ret<()>, + }, + Delete { + interval: Interval, + ret: Ret<()>, + }, + Format { + interval: Interval, + attribute: AttributeEntry, + ret: Ret<()>, + }, + Replace { + interval: Interval, + data: String, + ret: Ret<()>, + }, + CanUndo { + ret: oneshot::Sender, + }, + CanRedo { + ret: oneshot::Sender, + }, + Undo { + ret: Ret<()>, + }, + Redo { + ret: Ret<()>, + }, + GetOperationsString { + ret: Ret, + }, + #[allow(dead_code)] + GetOperations { + ret: Ret, + }, } impl std::fmt::Debug for EditorCommand { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let s = match self { - EditorCommand::ComposeLocalOperations { .. } => "ComposeLocalOperations", - EditorCommand::ComposeRemoteOperation { .. } => "ComposeRemoteOperation", - EditorCommand::ResetOperations { .. } => "ResetOperations", - EditorCommand::TransformOperations { .. } => "TransformOperations", - EditorCommand::Insert { .. } => "Insert", - EditorCommand::Delete { .. } => "Delete", - EditorCommand::Format { .. } => "Format", - EditorCommand::Replace { .. } => "Replace", - EditorCommand::CanUndo { .. } => "CanUndo", - EditorCommand::CanRedo { .. } => "CanRedo", - EditorCommand::Undo { .. } => "Undo", - EditorCommand::Redo { .. } => "Redo", - EditorCommand::GetOperationsString { .. } => "StringifyOperations", - EditorCommand::GetOperations { .. } => "ReadOperations", - }; - f.write_str(s) - } + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let s = match self { + EditorCommand::ComposeLocalOperations { .. } => "ComposeLocalOperations", + EditorCommand::ComposeRemoteOperation { .. } => "ComposeRemoteOperation", + EditorCommand::ResetOperations { .. } => "ResetOperations", + EditorCommand::TransformOperations { .. } => "TransformOperations", + EditorCommand::Insert { .. } => "Insert", + EditorCommand::Delete { .. } => "Delete", + EditorCommand::Format { .. } => "Format", + EditorCommand::Replace { .. } => "Replace", + EditorCommand::CanUndo { .. } => "CanUndo", + EditorCommand::CanRedo { .. } => "CanRedo", + EditorCommand::Undo { .. } => "Undo", + EditorCommand::Redo { .. } => "Redo", + EditorCommand::GetOperationsString { .. } => "StringifyOperations", + EditorCommand::GetOperations { .. } => "ReadOperations", + }; + f.write_str(s) + } } diff --git a/frontend/rust-lib/flowy-document/src/old_editor/snapshot.rs b/frontend/rust-lib/flowy-document/src/old_editor/snapshot.rs index 883c8b83d9..9269df24a5 100644 --- a/frontend/rust-lib/flowy-document/src/old_editor/snapshot.rs +++ b/frontend/rust-lib/flowy-document/src/old_editor/snapshot.rs @@ -4,15 +4,15 @@ use flowy_revision::{RevisionSnapshotData, RevisionSnapshotPersistence}; pub struct DeltaDocumentSnapshotPersistence(); impl RevisionSnapshotPersistence for DeltaDocumentSnapshotPersistence { - fn write_snapshot(&self, _rev_id: i64, _data: Vec) -> FlowyResult<()> { - Ok(()) - } + fn write_snapshot(&self, _rev_id: i64, _data: Vec) -> FlowyResult<()> { + Ok(()) + } - fn read_snapshot(&self, _rev_id: i64) -> FlowyResult> { - Ok(None) - } + fn read_snapshot(&self, _rev_id: i64) -> FlowyResult> { + Ok(None) + } - fn read_last_snapshot(&self) -> FlowyResult> { - Ok(None) - } + fn read_last_snapshot(&self) -> FlowyResult> { + Ok(None) + } } diff --git a/frontend/rust-lib/flowy-document/src/old_editor/web_socket.rs b/frontend/rust-lib/flowy-document/src/old_editor/web_socket.rs index f047897de2..c3a682ebc9 100644 --- a/frontend/rust-lib/flowy-document/src/old_editor/web_socket.rs +++ b/frontend/rust-lib/flowy-document/src/old_editor/web_socket.rs @@ -18,167 +18,184 @@ use ws_model::ws_revision::{ClientRevisionWSData, NewDocumentUser}; pub struct DeltaDocumentResolveOperations(pub DeltaTextOperations); impl OperationsDeserializer for DeltaDocumentResolveOperations { - fn deserialize_revisions(revisions: Vec) -> FlowyResult { - Ok(DeltaDocumentResolveOperations(make_operations_from_revisions( - revisions, - )?)) - } + fn deserialize_revisions( + revisions: Vec, + ) -> FlowyResult { + Ok(DeltaDocumentResolveOperations( + make_operations_from_revisions(revisions)?, + )) + } } impl OperationsSerializer for DeltaDocumentResolveOperations { - fn serialize_operations(&self) -> Bytes { - self.0.json_bytes() - } + fn serialize_operations(&self) -> Bytes { + self.0.json_bytes() + } } impl DeltaDocumentResolveOperations { - pub fn into_inner(self) -> DeltaTextOperations { - self.0 - } + pub fn into_inner(self) -> DeltaTextOperations { + self.0 + } } -pub type DocumentConflictController = ConflictController>; +pub type DocumentConflictController = + ConflictController>; #[allow(dead_code)] pub(crate) async fn make_document_ws_manager( - doc_id: String, - user_id: String, - edit_cmd_tx: EditorCommandSender, - rev_manager: Arc>>, - rev_web_socket: Arc, + doc_id: String, + user_id: String, + edit_cmd_tx: EditorCommandSender, + rev_manager: Arc>>, + rev_web_socket: Arc, ) -> Arc { - let ws_data_provider = Arc::new(WSDataProvider::new(&doc_id, Arc::new(rev_manager.clone()))); - let resolver = Arc::new(DocumentConflictResolver { edit_cmd_tx }); - let conflict_controller = - DocumentConflictController::new(&user_id, resolver, Arc::new(ws_data_provider.clone()), rev_manager); - let ws_data_stream = Arc::new(DocumentRevisionWSDataStream::new(conflict_controller)); - let ws_data_sink = Arc::new(DocumentWSDataSink(ws_data_provider)); - let ping_duration = Duration::from_millis(TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS); - let ws_manager = Arc::new(RevisionWebSocketManager::new( - "Block", - &doc_id, - rev_web_socket, - ws_data_sink, - ws_data_stream, - ping_duration, - )); - listen_document_ws_state(&user_id, &doc_id, ws_manager.scribe_state()); - ws_manager + let ws_data_provider = Arc::new(WSDataProvider::new(&doc_id, Arc::new(rev_manager.clone()))); + let resolver = Arc::new(DocumentConflictResolver { edit_cmd_tx }); + let conflict_controller = DocumentConflictController::new( + &user_id, + resolver, + Arc::new(ws_data_provider.clone()), + rev_manager, + ); + let ws_data_stream = Arc::new(DocumentRevisionWSDataStream::new(conflict_controller)); + let ws_data_sink = Arc::new(DocumentWSDataSink(ws_data_provider)); + let ping_duration = Duration::from_millis(TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS); + let ws_manager = Arc::new(RevisionWebSocketManager::new( + "Block", + &doc_id, + rev_web_socket, + ws_data_sink, + ws_data_stream, + ping_duration, + )); + listen_document_ws_state(&user_id, &doc_id, ws_manager.scribe_state()); + ws_manager } #[allow(dead_code)] -fn listen_document_ws_state(_user_id: &str, _doc_id: &str, mut subscriber: broadcast::Receiver) { - tokio::spawn(async move { - while let Ok(state) = subscriber.recv().await { - match state { - WSConnectState::Init => {} - WSConnectState::Connecting => {} - WSConnectState::Connected => {} - WSConnectState::Disconnected => {} - } - } - }); +fn listen_document_ws_state( + _user_id: &str, + _doc_id: &str, + mut subscriber: broadcast::Receiver, +) { + tokio::spawn(async move { + while let Ok(state) = subscriber.recv().await { + match state { + WSConnectState::Init => {}, + WSConnectState::Connecting => {}, + WSConnectState::Connected => {}, + WSConnectState::Disconnected => {}, + } + } + }); } pub(crate) struct DocumentRevisionWSDataStream { - conflict_controller: Arc, + conflict_controller: Arc, } impl DocumentRevisionWSDataStream { - #[allow(dead_code)] - pub fn new(conflict_controller: DocumentConflictController) -> Self { - Self { - conflict_controller: Arc::new(conflict_controller), - } + #[allow(dead_code)] + pub fn new(conflict_controller: DocumentConflictController) -> Self { + Self { + conflict_controller: Arc::new(conflict_controller), } + } } impl RevisionWSDataStream for DocumentRevisionWSDataStream { - fn receive_push_revision(&self, revisions: Vec) -> BoxResultFuture<(), FlowyError> { - let resolver = self.conflict_controller.clone(); - Box::pin(async move { resolver.receive_revisions(revisions).await }) - } + fn receive_push_revision(&self, revisions: Vec) -> BoxResultFuture<(), FlowyError> { + let resolver = self.conflict_controller.clone(); + Box::pin(async move { resolver.receive_revisions(revisions).await }) + } - fn receive_ack(&self, rev_id: i64) -> BoxResultFuture<(), FlowyError> { - let resolver = self.conflict_controller.clone(); - Box::pin(async move { resolver.ack_revision(rev_id).await }) - } + fn receive_ack(&self, rev_id: i64) -> BoxResultFuture<(), FlowyError> { + let resolver = self.conflict_controller.clone(); + Box::pin(async move { resolver.ack_revision(rev_id).await }) + } - fn receive_new_user_connect(&self, _new_user: NewDocumentUser) -> BoxResultFuture<(), FlowyError> { - // Do nothing by now, just a placeholder for future extension. - Box::pin(async move { Ok(()) }) - } + fn receive_new_user_connect( + &self, + _new_user: NewDocumentUser, + ) -> BoxResultFuture<(), FlowyError> { + // Do nothing by now, just a placeholder for future extension. + Box::pin(async move { Ok(()) }) + } - fn pull_revisions_in_range(&self, range: RevisionRange) -> BoxResultFuture<(), FlowyError> { - let resolver = self.conflict_controller.clone(); - Box::pin(async move { resolver.send_revisions(range).await }) - } + fn pull_revisions_in_range(&self, range: RevisionRange) -> BoxResultFuture<(), FlowyError> { + let resolver = self.conflict_controller.clone(); + Box::pin(async move { resolver.send_revisions(range).await }) + } } pub(crate) struct DocumentWSDataSink(pub(crate) Arc); impl RevisionWebSocketSink for DocumentWSDataSink { - fn next(&self) -> FutureResult, FlowyError> { - let sink_provider = self.0.clone(); - FutureResult::new(async move { sink_provider.next().await }) - } + fn next(&self) -> FutureResult, FlowyError> { + let sink_provider = self.0.clone(); + FutureResult::new(async move { sink_provider.next().await }) + } } struct DocumentConflictResolver { - edit_cmd_tx: EditorCommandSender, + edit_cmd_tx: EditorCommandSender, } impl ConflictResolver for DocumentConflictResolver { - fn compose_operations( - &self, - operations: DeltaDocumentResolveOperations, - ) -> BoxResultFuture { - let tx = self.edit_cmd_tx.clone(); - let operations = operations.into_inner(); - Box::pin(async move { - let (ret, rx) = oneshot::channel(); - tx.send(EditorCommand::ComposeRemoteOperation { - client_operations: operations, - ret, - }) - .await - .map_err(internal_error)?; - let md5 = rx - .await - .map_err(|e| FlowyError::internal().context(format!("Compose operations failed: {}", e)))??; - Ok(md5) - }) - } + fn compose_operations( + &self, + operations: DeltaDocumentResolveOperations, + ) -> BoxResultFuture { + let tx = self.edit_cmd_tx.clone(); + let operations = operations.into_inner(); + Box::pin(async move { + let (ret, rx) = oneshot::channel(); + tx.send(EditorCommand::ComposeRemoteOperation { + client_operations: operations, + ret, + }) + .await + .map_err(internal_error)?; + let md5 = rx.await.map_err(|e| { + FlowyError::internal().context(format!("Compose operations failed: {}", e)) + })??; + Ok(md5) + }) + } - fn transform_operations( - &self, - operations: DeltaDocumentResolveOperations, - ) -> BoxResultFuture, FlowyError> { - let tx = self.edit_cmd_tx.clone(); - let operations = operations.into_inner(); - Box::pin(async move { - let (ret, rx) = oneshot::channel::>(); - tx.send(EditorCommand::TransformOperations { operations, ret }) - .await - .map_err(internal_error)?; - let transformed_operations = rx - .await - .map_err(|e| FlowyError::internal().context(format!("Transform operations failed: {}", e)))??; - Ok(transformed_operations) - }) - } + fn transform_operations( + &self, + operations: DeltaDocumentResolveOperations, + ) -> BoxResultFuture, FlowyError> { + let tx = self.edit_cmd_tx.clone(); + let operations = operations.into_inner(); + Box::pin(async move { + let (ret, rx) = oneshot::channel::>(); + tx.send(EditorCommand::TransformOperations { operations, ret }) + .await + .map_err(internal_error)?; + let transformed_operations = rx.await.map_err(|e| { + FlowyError::internal().context(format!("Transform operations failed: {}", e)) + })??; + Ok(transformed_operations) + }) + } - fn reset_operations(&self, operations: DeltaDocumentResolveOperations) -> BoxResultFuture { - let tx = self.edit_cmd_tx.clone(); - let operations = operations.into_inner(); - Box::pin(async move { - let (ret, rx) = oneshot::channel(); - tx.send(EditorCommand::ResetOperations { operations, ret }) - .await - .map_err(internal_error)?; - let md5 = rx - .await - .map_err(|e| FlowyError::internal().context(format!("Reset operations failed: {}", e)))??; - Ok(md5) - }) - } + fn reset_operations( + &self, + operations: DeltaDocumentResolveOperations, + ) -> BoxResultFuture { + let tx = self.edit_cmd_tx.clone(); + let operations = operations.into_inner(); + Box::pin(async move { + let (ret, rx) = oneshot::channel(); + tx.send(EditorCommand::ResetOperations { operations, ret }) + .await + .map_err(internal_error)?; + let md5 = rx + .await + .map_err(|e| FlowyError::internal().context(format!("Reset operations failed: {}", e)))??; + Ok(md5) + }) + } } diff --git a/frontend/rust-lib/flowy-document/src/services/migration.rs b/frontend/rust-lib/flowy-document/src/services/migration.rs index 2e86f67710..2570ecbabf 100644 --- a/frontend/rust-lib/flowy-document/src/services/migration.rs +++ b/frontend/rust-lib/flowy-document/src/services/migration.rs @@ -12,62 +12,68 @@ use std::sync::Arc; const V1_MIGRATION: &str = "DOCUMENT_V1_MIGRATION"; pub(crate) struct DocumentMigration { - user_id: String, - database: Arc, + user_id: String, + database: Arc, } impl DocumentMigration { - pub fn new(user_id: &str, database: Arc) -> Self { - let user_id = user_id.to_owned(); - Self { user_id, database } + pub fn new(user_id: &str, database: Arc) -> Self { + let user_id = user_id.to_owned(); + Self { user_id, database } + } + + pub fn run_v1_migration(&self) -> FlowyResult<()> { + let key = migration_flag_key(&self.user_id, V1_MIGRATION); + if KV::get_bool(&key) { + return Ok(()); } - pub fn run_v1_migration(&self) -> FlowyResult<()> { - let key = migration_flag_key(&self.user_id, V1_MIGRATION); - if KV::get_bool(&key) { - return Ok(()); - } + let pool = self.database.db_pool()?; + let conn = &*pool.get()?; + let disk_cache = SQLiteDocumentRevisionPersistence::new(&self.user_id, pool); + let documents = DeltaRevisionSql::read_all_documents(&self.user_id, conn)?; + tracing::debug!( + "[Document Migration]: try migrate {} documents", + documents.len() + ); + for revisions in documents { + if revisions.is_empty() { + continue; + } - let pool = self.database.db_pool()?; - let conn = &*pool.get()?; - let disk_cache = SQLiteDocumentRevisionPersistence::new(&self.user_id, pool); - let documents = DeltaRevisionSql::read_all_documents(&self.user_id, conn)?; - tracing::debug!("[Document Migration]: try migrate {} documents", documents.len()); - for revisions in documents { - if revisions.is_empty() { - continue; - } - - let document_id = revisions.first().unwrap().object_id.clone(); - if let Ok(delta) = make_operations_from_revisions(revisions) { - match DeltaRevisionMigration::run(delta) { - Ok(transaction) => { - let bytes = Bytes::from(transaction.to_bytes()?); - let md5 = format!("{:x}", md5::compute(&bytes)); - let revision = Revision::new(&document_id, 0, 1, bytes, md5); - let record = SyncRecord::new(revision); - match disk_cache.create_revision_records(vec![record]) { - Ok(_) => {} - Err(err) => { - tracing::error!("[Document Migration]: Save revisions to disk failed {:?}", err); - } - } - } - Err(err) => { - tracing::error!( - "[Document Migration]: Migrate revisions to transaction failed {:?}", - err - ); - } - } + let document_id = revisions.first().unwrap().object_id.clone(); + if let Ok(delta) = make_operations_from_revisions(revisions) { + match DeltaRevisionMigration::run(delta) { + Ok(transaction) => { + let bytes = Bytes::from(transaction.to_bytes()?); + let md5 = format!("{:x}", md5::compute(&bytes)); + let revision = Revision::new(&document_id, 0, 1, bytes, md5); + let record = SyncRecord::new(revision); + match disk_cache.create_revision_records(vec![record]) { + Ok(_) => {}, + Err(err) => { + tracing::error!( + "[Document Migration]: Save revisions to disk failed {:?}", + err + ); + }, } + }, + Err(err) => { + tracing::error!( + "[Document Migration]: Migrate revisions to transaction failed {:?}", + err + ); + }, } - // - - KV::set_bool(&key, true); - Ok(()) + } } + // + + KV::set_bool(&key, true); + Ok(()) + } } fn migration_flag_key(user_id: &str, version: &str) -> String { - md5(format!("{}{}", user_id, version,)) + md5(format!("{}{}", user_id, version,)) } diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/delta_migration.rs b/frontend/rust-lib/flowy-document/src/services/persistence/delta_migration.rs index eaa1aad584..ddc932df23 100644 --- a/frontend/rust-lib/flowy-document/src/services/persistence/delta_migration.rs +++ b/frontend/rust-lib/flowy-document/src/services/persistence/delta_migration.rs @@ -7,183 +7,195 @@ use lib_ot::text_delta::{DeltaTextOperation, DeltaTextOperations}; pub struct DeltaRevisionMigration(); impl DeltaRevisionMigration { - pub fn run(delta: DeltaTextOperations) -> FlowyResult { - let migrate_background_attribute = |insert: &mut Insert| { - if let Some(Some(color)) = insert.attributes.get("background").map(|value| value.str_value()) { - insert.attributes.remove_key("background"); - insert.attributes.insert("backgroundColor", color); - } - }; - let migrate_strike_attribute = |insert: &mut Insert| { - if let Some(Some(_)) = insert.attributes.get("strike").map(|value| value.str_value()) { - insert.attributes.remove_key("strike"); - insert.attributes.insert("strikethrough", true); - } - }; + pub fn run(delta: DeltaTextOperations) -> FlowyResult { + let migrate_background_attribute = |insert: &mut Insert| { + if let Some(Some(color)) = insert + .attributes + .get("background") + .map(|value| value.str_value()) + { + insert.attributes.remove_key("background"); + insert.attributes.insert("backgroundColor", color); + } + }; + let migrate_strike_attribute = |insert: &mut Insert| { + if let Some(Some(_)) = insert + .attributes + .get("strike") + .map(|value| value.str_value()) + { + insert.attributes.remove_key("strike"); + insert.attributes.insert("strikethrough", true); + } + }; - let migrate_link_attribute = |insert: &mut Insert| { - if let Some(Some(link)) = insert.attributes.get("link").map(|value| value.str_value()) { - insert.attributes.remove_key("link"); - insert.attributes.insert("href", link); - } - }; + let migrate_link_attribute = |insert: &mut Insert| { + if let Some(Some(link)) = insert.attributes.get("link").map(|value| value.str_value()) { + insert.attributes.remove_key("link"); + insert.attributes.insert("href", link); + } + }; - let migrate_list_attribute = - |attribute_node: &mut DocumentNode, value: &str, number_list_number: &mut usize| { - if value == "unchecked" { - *number_list_number = 0; - attribute_node.attributes.insert("subtype", "checkbox"); - attribute_node.attributes.insert("checkbox", false); - } - if value == "checked" { - *number_list_number = 0; - attribute_node.attributes.insert("subtype", "checkbox"); - attribute_node.attributes.insert("checkbox", true); - } - - if value == "bullet" { - *number_list_number = 0; - attribute_node.attributes.insert("subtype", "bulleted-list"); - } - - if value == "ordered" { - *number_list_number += 1; - attribute_node.attributes.insert("subtype", "number-list"); - attribute_node.attributes.insert("number", *number_list_number); - } - }; - - let generate_new_op_with_double_new_lines = |insert: &mut Insert| { - let pattern = "\n\n"; - let mut new_ops = vec![]; - if insert.s.as_str().contains(pattern) { - let insert_str = insert.s.clone(); - let insert_strings = insert_str.split(pattern).map(|s| s.to_owned()); - for (index, new_s) in insert_strings.enumerate() { - if index == 0 { - insert.s = new_s.into(); - } else { - new_ops.push(DeltaOperation::Insert(Insert { - s: new_s.into(), - attributes: AttributeHashMap::default(), - })); - } - } - } - new_ops - }; - - let create_text_node = |ops: Vec| { - let mut document_node = DocumentNode::new(); - document_node.node_type = "text".to_owned(); - ops.into_iter().for_each(|op| document_node.delta.add(op)); - document_node - }; - - let transform_op = |mut insert: Insert| { - // Rename the attribute name from background to backgroundColor - migrate_background_attribute(&mut insert); - migrate_strike_attribute(&mut insert); - migrate_link_attribute(&mut insert); - - let new_ops = generate_new_op_with_double_new_lines(&mut insert); - (DeltaOperation::Insert(insert), new_ops) - }; - let mut index: usize = 0; - let mut number_list_number = 0; - let mut editor_node = DocumentNode::new(); - editor_node.node_type = "editor".to_owned(); - - let mut transaction = Transaction::new(); - transaction.push_operation(DocumentOperation::Insert { - path: 0.into(), - nodes: vec![editor_node], - }); - - let mut iter = delta.ops.into_iter().enumerate(); - while let Some((_, op)) = iter.next() { - let mut document_node = create_text_node(vec![]); - let mut split_document_nodes = vec![]; - match op { - DeltaOperation::Delete(_) => tracing::warn!("Should not contain delete operation"), - DeltaOperation::Retain(_) => tracing::warn!("Should not contain retain operation"), - DeltaOperation::Insert(insert) => { - if insert.s.as_str() != "\n" { - let (op, new_ops) = transform_op(insert); - document_node.delta.add(op); - if !new_ops.is_empty() { - split_document_nodes.push(create_text_node(new_ops)); - } - } - - while let Some((_, DeltaOperation::Insert(insert))) = iter.next() { - if insert.s.as_str() != "\n" { - let (op, new_ops) = transform_op(insert); - document_node.delta.add(op); - - if !new_ops.is_empty() { - split_document_nodes.push(create_text_node(new_ops)); - } - } else { - let attribute_node = match split_document_nodes.last_mut() { - None => &mut document_node, - Some(split_document_node) => split_document_node, - }; - - if let Some(value) = insert.attributes.get("header") { - attribute_node.attributes.insert("subtype", "heading"); - if let Some(v) = value.int_value() { - number_list_number = 0; - attribute_node.attributes.insert("heading", format!("h{}", v)); - } - } - - if insert.attributes.get("blockquote").is_some() { - attribute_node.attributes.insert("subtype", "quote"); - } - - if let Some(value) = insert.attributes.get("list") { - if let Some(s) = value.str_value() { - migrate_list_attribute(attribute_node, &s, &mut number_list_number); - } - } - break; - } - } - } - } - let mut operations = vec![document_node]; - operations.extend(split_document_nodes); - operations.into_iter().for_each(|node| { - // println!("{}", serde_json::to_string(&node).unwrap()); - let operation = DocumentOperation::Insert { - path: vec![0, index].into(), - nodes: vec![node], - }; - transaction.push_operation(operation); - index += 1; - }); + let migrate_list_attribute = + |attribute_node: &mut DocumentNode, value: &str, number_list_number: &mut usize| { + if value == "unchecked" { + *number_list_number = 0; + attribute_node.attributes.insert("subtype", "checkbox"); + attribute_node.attributes.insert("checkbox", false); } - Ok(transaction) + if value == "checked" { + *number_list_number = 0; + attribute_node.attributes.insert("subtype", "checkbox"); + attribute_node.attributes.insert("checkbox", true); + } + + if value == "bullet" { + *number_list_number = 0; + attribute_node.attributes.insert("subtype", "bulleted-list"); + } + + if value == "ordered" { + *number_list_number += 1; + attribute_node.attributes.insert("subtype", "number-list"); + attribute_node + .attributes + .insert("number", *number_list_number); + } + }; + + let generate_new_op_with_double_new_lines = |insert: &mut Insert| { + let pattern = "\n\n"; + let mut new_ops = vec![]; + if insert.s.as_str().contains(pattern) { + let insert_str = insert.s.clone(); + let insert_strings = insert_str.split(pattern).map(|s| s.to_owned()); + for (index, new_s) in insert_strings.enumerate() { + if index == 0 { + insert.s = new_s.into(); + } else { + new_ops.push(DeltaOperation::Insert(Insert { + s: new_s.into(), + attributes: AttributeHashMap::default(), + })); + } + } + } + new_ops + }; + + let create_text_node = |ops: Vec| { + let mut document_node = DocumentNode::new(); + document_node.node_type = "text".to_owned(); + ops.into_iter().for_each(|op| document_node.delta.add(op)); + document_node + }; + + let transform_op = |mut insert: Insert| { + // Rename the attribute name from background to backgroundColor + migrate_background_attribute(&mut insert); + migrate_strike_attribute(&mut insert); + migrate_link_attribute(&mut insert); + + let new_ops = generate_new_op_with_double_new_lines(&mut insert); + (DeltaOperation::Insert(insert), new_ops) + }; + let mut index: usize = 0; + let mut number_list_number = 0; + let mut editor_node = DocumentNode::new(); + editor_node.node_type = "editor".to_owned(); + + let mut transaction = Transaction::new(); + transaction.push_operation(DocumentOperation::Insert { + path: 0.into(), + nodes: vec![editor_node], + }); + + let mut iter = delta.ops.into_iter().enumerate(); + while let Some((_, op)) = iter.next() { + let mut document_node = create_text_node(vec![]); + let mut split_document_nodes = vec![]; + match op { + DeltaOperation::Delete(_) => tracing::warn!("Should not contain delete operation"), + DeltaOperation::Retain(_) => tracing::warn!("Should not contain retain operation"), + DeltaOperation::Insert(insert) => { + if insert.s.as_str() != "\n" { + let (op, new_ops) = transform_op(insert); + document_node.delta.add(op); + if !new_ops.is_empty() { + split_document_nodes.push(create_text_node(new_ops)); + } + } + + while let Some((_, DeltaOperation::Insert(insert))) = iter.next() { + if insert.s.as_str() != "\n" { + let (op, new_ops) = transform_op(insert); + document_node.delta.add(op); + + if !new_ops.is_empty() { + split_document_nodes.push(create_text_node(new_ops)); + } + } else { + let attribute_node = match split_document_nodes.last_mut() { + None => &mut document_node, + Some(split_document_node) => split_document_node, + }; + + if let Some(value) = insert.attributes.get("header") { + attribute_node.attributes.insert("subtype", "heading"); + if let Some(v) = value.int_value() { + number_list_number = 0; + attribute_node + .attributes + .insert("heading", format!("h{}", v)); + } + } + + if insert.attributes.get("blockquote").is_some() { + attribute_node.attributes.insert("subtype", "quote"); + } + + if let Some(value) = insert.attributes.get("list") { + if let Some(s) = value.str_value() { + migrate_list_attribute(attribute_node, &s, &mut number_list_number); + } + } + break; + } + } + }, + } + let mut operations = vec![document_node]; + operations.extend(split_document_nodes); + operations.into_iter().for_each(|node| { + // println!("{}", serde_json::to_string(&node).unwrap()); + let operation = DocumentOperation::Insert { + path: vec![0, index].into(), + nodes: vec![node], + }; + transaction.push_operation(operation); + index += 1; + }); } + Ok(transaction) + } } #[cfg(test)] mod tests { - use crate::editor::Document; - use crate::services::delta_migration::DeltaRevisionMigration; - use lib_ot::text_delta::DeltaTextOperations; + use crate::editor::Document; + use crate::services::delta_migration::DeltaRevisionMigration; + use lib_ot::text_delta::DeltaTextOperations; - #[test] - fn transform_delta_to_transaction_test() { - let delta = DeltaTextOperations::from_json(DELTA_STR).unwrap(); - let transaction = DeltaRevisionMigration::run(delta).unwrap(); - let document = Document::from_transaction(transaction).unwrap(); - let s = document.get_content(true).unwrap(); - assert!(!s.is_empty()); - } + #[test] + fn transform_delta_to_transaction_test() { + let delta = DeltaTextOperations::from_json(DELTA_STR).unwrap(); + let transaction = DeltaRevisionMigration::run(delta).unwrap(); + let document = Document::from_transaction(transaction).unwrap(); + let s = document.get_content(true).unwrap(); + assert!(!s.is_empty()); + } - const DELTA_STR: &str = r#"[ + const DELTA_STR: &str = r#"[ { "insert": "\n👋 Welcome to AppFlowy!" }, diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-document/src/services/persistence/mod.rs index bedd4fea49..a48b838193 100644 --- a/frontend/rust-lib/flowy-document/src/services/persistence/mod.rs +++ b/frontend/rust-lib/flowy-document/src/services/persistence/mod.rs @@ -7,20 +7,20 @@ use flowy_error::FlowyResult; use std::sync::Arc; pub struct DocumentPersistence { - pub database: Arc, + pub database: Arc, } impl DocumentPersistence { - pub fn new(database: Arc) -> Self { - Self { database } - } + pub fn new(database: Arc) -> Self { + Self { database } + } - #[tracing::instrument(level = "trace", skip_all, err)] - pub fn initialize(&self, user_id: &str) -> FlowyResult<()> { - let migration = DocumentMigration::new(user_id, self.database.clone()); - if let Err(e) = migration.run_v1_migration() { - tracing::error!("[Document Migration]: run v1 migration failed: {:?}", e); - } - Ok(()) + #[tracing::instrument(level = "trace", skip_all, err)] + pub fn initialize(&self, user_id: &str) -> FlowyResult<()> { + let migration = DocumentMigration::new(user_id, self.database.clone()); + if let Err(e) = migration.run_v1_migration() { + tracing::error!("[Document Migration]: run v1 migration failed: {:?}", e); } + Ok(()) + } } diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v0.rs b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v0.rs index 5de1a12db8..ded4fbd160 100644 --- a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v0.rs +++ b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v0.rs @@ -3,10 +3,10 @@ use diesel::{sql_types::Integer, update, SqliteConnection}; use flowy_error::{internal_error, FlowyError, FlowyResult}; use flowy_revision_persistence::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; use flowy_sqlite::{ - impl_sql_integer_expression, insert_or_ignore_into, - prelude::*, - schema::{rev_table, rev_table::dsl}, - ConnectionPool, + impl_sql_integer_expression, insert_or_ignore_into, + prelude::*, + schema::{rev_table, rev_table::dsl}, + ConnectionPool, }; use lib_infra::util::md5; use revision_model::{Revision, RevisionRange}; @@ -14,273 +14,298 @@ use std::collections::HashMap; use std::sync::Arc; pub struct SQLiteDeltaDocumentRevisionPersistence { - user_id: String, - pub(crate) pool: Arc, + user_id: String, + pub(crate) pool: Arc, } impl RevisionDiskCache> for SQLiteDeltaDocumentRevisionPersistence { - type Error = FlowyError; + type Error = FlowyError; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - DeltaRevisionSql::create(revision_records, &conn)?; - Ok(()) - } + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + DeltaRevisionSql::create(revision_records, &conn)?; + Ok(()) + } - fn get_connection(&self) -> Result, Self::Error> { - Ok(self.pool.clone()) - } + fn get_connection(&self) -> Result, Self::Error> { + Ok(self.pool.clone()) + } - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - let records = DeltaRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?; - Ok(records) - } + fn read_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result, Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + let records = DeltaRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?; + Ok(records) + } - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - let revisions = DeltaRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?; - Ok(revisions) - } + fn read_revision_records_with_range( + &self, + object_id: &str, + range: &RevisionRange, + ) -> Result, Self::Error> { + let conn = &*self.pool.get().map_err(internal_error)?; + let revisions = + DeltaRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?; + Ok(revisions) + } - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - let conn = &*self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - for changeset in changesets { - DeltaRevisionSql::update(changeset, conn)?; - } - Ok(()) - })?; - Ok(()) - } + fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { + let conn = &*self.pool.get().map_err(internal_error)?; + conn.immediate_transaction::<_, FlowyError, _>(|| { + for changeset in changesets { + DeltaRevisionSql::update(changeset, conn)?; + } + Ok(()) + })?; + Ok(()) + } - fn delete_revision_records(&self, object_id: &str, rev_ids: Option>) -> Result<(), Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - DeltaRevisionSql::delete(object_id, rev_ids, conn)?; - Ok(()) - } + fn delete_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result<(), Self::Error> { + let conn = &*self.pool.get().map_err(internal_error)?; + DeltaRevisionSql::delete(object_id, rev_ids, conn)?; + Ok(()) + } - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - DeltaRevisionSql::delete(object_id, deleted_rev_ids, &conn)?; - DeltaRevisionSql::create(inserted_records, &conn)?; - Ok(()) - }) - } + fn delete_and_insert_records( + &self, + object_id: &str, + deleted_rev_ids: Option>, + inserted_records: Vec, + ) -> Result<(), Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + conn.immediate_transaction::<_, FlowyError, _>(|| { + DeltaRevisionSql::delete(object_id, deleted_rev_ids, &conn)?; + DeltaRevisionSql::create(inserted_records, &conn)?; + Ok(()) + }) + } } impl SQLiteDeltaDocumentRevisionPersistence { - pub fn new(user_id: &str, pool: Arc) -> Self { - Self { - user_id: user_id.to_owned(), - pool, - } + pub fn new(user_id: &str, pool: Arc) -> Self { + Self { + user_id: user_id.to_owned(), + pool, } + } } pub struct DeltaRevisionSql {} impl DeltaRevisionSql { - fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { - // Batch insert: https://diesel.rs/guides/all-about-inserts.html - let records = revision_records - .into_iter() - .map(|record| { - tracing::trace!( - "[TextRevisionSql] create revision: {}:{:?}", - record.revision.object_id, - record.revision.rev_id - ); - let rev_state: TextRevisionState = record.state.into(); - ( - dsl::doc_id.eq(record.revision.object_id), - dsl::base_rev_id.eq(record.revision.base_rev_id), - dsl::rev_id.eq(record.revision.rev_id), - dsl::data.eq(record.revision.bytes), - dsl::state.eq(rev_state), - dsl::ty.eq(RevTableType::Local), - ) - }) - .collect::>(); - - let _ = insert_or_ignore_into(dsl::rev_table).values(&records).execute(conn)?; - Ok(()) - } - - fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { - let state: TextRevisionState = changeset.state.clone().into(); - let filter = dsl::rev_table - .filter(dsl::rev_id.eq(changeset.rev_id)) - .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 {:?}", - changeset.rev_id, - changeset.state + fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { + // Batch insert: https://diesel.rs/guides/all-about-inserts.html + let records = revision_records + .into_iter() + .map(|record| { + tracing::trace!( + "[TextRevisionSql] create revision: {}:{:?}", + record.revision.object_id, + record.revision.rev_id ); - Ok(()) + let rev_state: TextRevisionState = record.state.into(); + ( + dsl::doc_id.eq(record.revision.object_id), + dsl::base_rev_id.eq(record.revision.base_rev_id), + dsl::rev_id.eq(record.revision.rev_id), + dsl::data.eq(record.revision.bytes), + dsl::state.eq(rev_state), + dsl::ty.eq(RevTableType::Local), + ) + }) + .collect::>(); + + let _ = insert_or_ignore_into(dsl::rev_table) + .values(&records) + .execute(conn)?; + Ok(()) + } + + fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { + let state: TextRevisionState = changeset.state.clone().into(); + let filter = dsl::rev_table + .filter(dsl::rev_id.eq(changeset.rev_id)) + .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 {:?}", + changeset.rev_id, + changeset.state + ); + Ok(()) + } + + fn read( + user_id: &str, + object_id: &str, + rev_ids: Option>, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let mut sql = dsl::rev_table + .filter(dsl::doc_id.eq(object_id)) + .into_boxed(); + if let Some(rev_ids) = rev_ids { + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); + } + let rows = sql.order(dsl::rev_id.asc()).load::(conn)?; + let records = rows + .into_iter() + .map(|row| mk_revision_record_from_table(user_id, row)) + .collect::>(); + + Ok(records) + } + + fn read_with_range( + user_id: &str, + object_id: &str, + range: RevisionRange, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let rev_tables = dsl::rev_table + .filter(dsl::rev_id.ge(range.start)) + .filter(dsl::rev_id.le(range.end)) + .filter(dsl::doc_id.eq(object_id)) + .order(dsl::rev_id.asc()) + .load::(conn)?; + + let revisions = rev_tables + .into_iter() + .map(|table| mk_revision_record_from_table(user_id, table)) + .collect::>(); + Ok(revisions) + } + + fn delete( + object_id: &str, + rev_ids: Option>, + conn: &SqliteConnection, + ) -> Result<(), FlowyError> { + let mut sql = diesel::delete(dsl::rev_table).into_boxed(); + sql = sql.filter(dsl::doc_id.eq(object_id)); + + if let Some(rev_ids) = rev_ids { + tracing::trace!( + "[TextRevisionSql] Delete revision: {}:{:?}", + object_id, + rev_ids + ); + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); } - fn read( - user_id: &str, - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let mut sql = dsl::rev_table.filter(dsl::doc_id.eq(object_id)).into_boxed(); - if let Some(rev_ids) = rev_ids { - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - let rows = sql.order(dsl::rev_id.asc()).load::(conn)?; - let records = rows - .into_iter() - .map(|row| mk_revision_record_from_table(user_id, row)) - .collect::>(); + let affected_row = sql.execute(conn)?; + tracing::trace!("[TextRevisionSql] Delete {} rows", affected_row); + Ok(()) + } - Ok(records) + pub fn read_all_documents( + user_id: &str, + conn: &SqliteConnection, + ) -> Result>, FlowyError> { + let rev_tables = dsl::rev_table + .order(dsl::rev_id.asc()) + .load::(conn)?; + let mut document_map = HashMap::new(); + for rev_table in rev_tables { + document_map + .entry(rev_table.doc_id.clone()) + .or_insert_with(Vec::new) + .push(rev_table); + } + let mut documents = vec![]; + for rev_tables in document_map.into_values() { + let revisions = rev_tables + .into_iter() + .map(|table| { + let record = mk_revision_record_from_table(user_id, table); + record.revision + }) + .collect::>(); + documents.push(revisions); } - fn read_with_range( - user_id: &str, - object_id: &str, - range: RevisionRange, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let rev_tables = dsl::rev_table - .filter(dsl::rev_id.ge(range.start)) - .filter(dsl::rev_id.le(range.end)) - .filter(dsl::doc_id.eq(object_id)) - .order(dsl::rev_id.asc()) - .load::(conn)?; - - let revisions = rev_tables - .into_iter() - .map(|table| mk_revision_record_from_table(user_id, table)) - .collect::>(); - Ok(revisions) - } - - fn delete(object_id: &str, rev_ids: Option>, conn: &SqliteConnection) -> Result<(), FlowyError> { - let mut sql = diesel::delete(dsl::rev_table).into_boxed(); - sql = sql.filter(dsl::doc_id.eq(object_id)); - - if let Some(rev_ids) = rev_ids { - tracing::trace!("[TextRevisionSql] Delete revision: {}:{:?}", object_id, rev_ids); - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - - let affected_row = sql.execute(conn)?; - tracing::trace!("[TextRevisionSql] Delete {} rows", affected_row); - Ok(()) - } - - pub fn read_all_documents(user_id: &str, conn: &SqliteConnection) -> Result>, FlowyError> { - let rev_tables = dsl::rev_table.order(dsl::rev_id.asc()).load::(conn)?; - let mut document_map = HashMap::new(); - for rev_table in rev_tables { - document_map - .entry(rev_table.doc_id.clone()) - .or_insert_with(Vec::new) - .push(rev_table); - } - let mut documents = vec![]; - for rev_tables in document_map.into_values() { - let revisions = rev_tables - .into_iter() - .map(|table| { - let record = mk_revision_record_from_table(user_id, table); - record.revision - }) - .collect::>(); - documents.push(revisions); - } - - Ok(documents) - } + Ok(documents) + } } #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] #[table_name = "rev_table"] struct RevisionTable { - id: i32, - doc_id: String, - base_rev_id: i64, - rev_id: i64, - data: Vec, - state: TextRevisionState, - ty: RevTableType, // Deprecated + id: i32, + doc_id: String, + base_rev_id: i64, + rev_id: i64, + data: Vec, + state: TextRevisionState, + ty: RevTableType, // Deprecated } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] #[repr(i32)] #[sql_type = "Integer"] enum TextRevisionState { - Sync = 0, - Ack = 1, + Sync = 0, + Ack = 1, } impl_sql_integer_expression!(TextRevisionState); impl_rev_state_map!(TextRevisionState); impl std::default::Default for TextRevisionState { - fn default() -> Self { - TextRevisionState::Sync - } + fn default() -> Self { + TextRevisionState::Sync + } } fn mk_revision_record_from_table(_user_id: &str, table: RevisionTable) -> SyncRecord { - let md5 = md5(&table.data); - let revision = Revision::new( - &table.doc_id, - table.base_rev_id, - table.rev_id, - Bytes::from(table.data), - md5, - ); - SyncRecord { - revision, - state: table.state.into(), - write_to_disk: false, - } + let md5 = md5(&table.data); + let revision = Revision::new( + &table.doc_id, + table.base_rev_id, + table.rev_id, + Bytes::from(table.data), + md5, + ); + SyncRecord { + revision, + state: table.state.into(), + write_to_disk: false, + } } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] #[repr(i32)] #[sql_type = "Integer"] pub enum RevTableType { - Local = 0, - Remote = 1, + Local = 0, + Remote = 1, } impl_sql_integer_expression!(RevTableType); impl std::default::Default for RevTableType { - fn default() -> Self { - RevTableType::Local - } + fn default() -> Self { + RevTableType::Local + } } impl std::convert::From for RevTableType { - fn from(value: i32) -> Self { - match value { - 0 => RevTableType::Local, - 1 => RevTableType::Remote, - o => { - tracing::error!("Unsupported rev type {}, fallback to RevTableType::Local", o); - RevTableType::Local - } - } + fn from(value: i32) -> Self { + match value { + 0 => RevTableType::Local, + 1 => RevTableType::Remote, + o => { + tracing::error!( + "Unsupported rev type {}, fallback to RevTableType::Local", + o + ); + RevTableType::Local + }, } + } } diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v1.rs b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v1.rs index 2c38e50aef..569fada510 100644 --- a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v1.rs +++ b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v1.rs @@ -3,233 +3,248 @@ use diesel::{sql_types::Integer, update, SqliteConnection}; use flowy_error::{internal_error, FlowyError, FlowyResult}; use flowy_revision_persistence::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; use flowy_sqlite::{ - impl_sql_integer_expression, insert_or_ignore_into, - prelude::*, - schema::{document_rev_table, document_rev_table::dsl}, - ConnectionPool, + impl_sql_integer_expression, insert_or_ignore_into, + prelude::*, + schema::{document_rev_table, document_rev_table::dsl}, + ConnectionPool, }; use lib_infra::util::md5; use revision_model::{Revision, RevisionRange}; use std::sync::Arc; pub struct SQLiteDocumentRevisionPersistence { - user_id: String, - pub(crate) pool: Arc, + user_id: String, + pub(crate) pool: Arc, } impl RevisionDiskCache> for SQLiteDocumentRevisionPersistence { - type Error = FlowyError; + type Error = FlowyError; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - DocumentRevisionSql::create(revision_records, &conn)?; - Ok(()) - } + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + DocumentRevisionSql::create(revision_records, &conn)?; + Ok(()) + } - fn get_connection(&self) -> Result, Self::Error> { - Ok(self.pool.clone()) - } + fn get_connection(&self) -> Result, Self::Error> { + Ok(self.pool.clone()) + } - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - let records = DocumentRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?; - Ok(records) - } + fn read_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result, Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + let records = DocumentRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?; + Ok(records) + } - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - let revisions = DocumentRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?; - Ok(revisions) - } + fn read_revision_records_with_range( + &self, + object_id: &str, + range: &RevisionRange, + ) -> Result, Self::Error> { + let conn = &*self.pool.get().map_err(internal_error)?; + let revisions = + DocumentRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?; + Ok(revisions) + } - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - let conn = &*self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - for changeset in changesets { - DocumentRevisionSql::update(changeset, conn)?; - } - Ok(()) - })?; - Ok(()) - } + fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { + let conn = &*self.pool.get().map_err(internal_error)?; + conn.immediate_transaction::<_, FlowyError, _>(|| { + for changeset in changesets { + DocumentRevisionSql::update(changeset, conn)?; + } + Ok(()) + })?; + Ok(()) + } - fn delete_revision_records(&self, object_id: &str, rev_ids: Option>) -> Result<(), Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - DocumentRevisionSql::delete(object_id, rev_ids, conn)?; - Ok(()) - } + fn delete_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result<(), Self::Error> { + let conn = &*self.pool.get().map_err(internal_error)?; + DocumentRevisionSql::delete(object_id, rev_ids, conn)?; + Ok(()) + } - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - DocumentRevisionSql::delete(object_id, deleted_rev_ids, &conn)?; - DocumentRevisionSql::create(inserted_records, &conn)?; - Ok(()) - }) - } + fn delete_and_insert_records( + &self, + object_id: &str, + deleted_rev_ids: Option>, + inserted_records: Vec, + ) -> Result<(), Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + conn.immediate_transaction::<_, FlowyError, _>(|| { + DocumentRevisionSql::delete(object_id, deleted_rev_ids, &conn)?; + DocumentRevisionSql::create(inserted_records, &conn)?; + Ok(()) + }) + } } impl SQLiteDocumentRevisionPersistence { - pub fn new(user_id: &str, pool: Arc) -> Self { - Self { - user_id: user_id.to_owned(), - pool, - } + pub fn new(user_id: &str, pool: Arc) -> Self { + Self { + user_id: user_id.to_owned(), + pool, } + } } struct DocumentRevisionSql {} impl DocumentRevisionSql { - fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { - // Batch insert: https://diesel.rs/guides/all-about-inserts.html - let records = revision_records - .into_iter() - .map(|record| { - tracing::trace!( - "[DocumentRevisionSql] create revision: {}:{:?}", - record.revision.object_id, - record.revision.rev_id - ); - let rev_state: DocumentRevisionState = record.state.into(); - ( - dsl::document_id.eq(record.revision.object_id), - dsl::base_rev_id.eq(record.revision.base_rev_id), - dsl::rev_id.eq(record.revision.rev_id), - dsl::data.eq(record.revision.bytes), - dsl::state.eq(rev_state), - ) - }) - .collect::>(); - - let _ = insert_or_ignore_into(dsl::document_rev_table) - .values(&records) - .execute(conn)?; - Ok(()) - } - - fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { - let state: DocumentRevisionState = changeset.state.clone().into(); - let filter = dsl::document_rev_table - .filter(dsl::rev_id.eq(changeset.rev_id)) - .filter(dsl::document_id.eq(changeset.object_id)); - let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?; - tracing::debug!( - "[DocumentRevisionSql] update revision:{} state:to {:?}", - changeset.rev_id, - changeset.state + fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { + // Batch insert: https://diesel.rs/guides/all-about-inserts.html + let records = revision_records + .into_iter() + .map(|record| { + tracing::trace!( + "[DocumentRevisionSql] create revision: {}:{:?}", + record.revision.object_id, + record.revision.rev_id ); - Ok(()) + let rev_state: DocumentRevisionState = record.state.into(); + ( + dsl::document_id.eq(record.revision.object_id), + dsl::base_rev_id.eq(record.revision.base_rev_id), + dsl::rev_id.eq(record.revision.rev_id), + dsl::data.eq(record.revision.bytes), + dsl::state.eq(rev_state), + ) + }) + .collect::>(); + + let _ = insert_or_ignore_into(dsl::document_rev_table) + .values(&records) + .execute(conn)?; + Ok(()) + } + + fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { + let state: DocumentRevisionState = changeset.state.clone().into(); + let filter = dsl::document_rev_table + .filter(dsl::rev_id.eq(changeset.rev_id)) + .filter(dsl::document_id.eq(changeset.object_id)); + let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?; + tracing::debug!( + "[DocumentRevisionSql] update revision:{} state:to {:?}", + changeset.rev_id, + changeset.state + ); + Ok(()) + } + + fn read( + user_id: &str, + object_id: &str, + rev_ids: Option>, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let mut sql = dsl::document_rev_table + .filter(dsl::document_id.eq(object_id)) + .into_boxed(); + if let Some(rev_ids) = rev_ids { + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); + } + let rows = sql + .order(dsl::rev_id.asc()) + .load::(conn)?; + let records = rows + .into_iter() + .map(|row| mk_revision_record_from_table(user_id, row)) + .collect::>(); + + Ok(records) + } + + fn read_with_range( + user_id: &str, + object_id: &str, + range: RevisionRange, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let rev_tables = dsl::document_rev_table + .filter(dsl::rev_id.ge(range.start)) + .filter(dsl::rev_id.le(range.end)) + .filter(dsl::document_id.eq(object_id)) + .order(dsl::rev_id.asc()) + .load::(conn)?; + + let revisions = rev_tables + .into_iter() + .map(|table| mk_revision_record_from_table(user_id, table)) + .collect::>(); + Ok(revisions) + } + + fn delete( + object_id: &str, + rev_ids: Option>, + conn: &SqliteConnection, + ) -> Result<(), FlowyError> { + let mut sql = diesel::delete(dsl::document_rev_table).into_boxed(); + sql = sql.filter(dsl::document_id.eq(object_id)); + + if let Some(rev_ids) = rev_ids { + tracing::trace!( + "[DocumentRevisionSql] Delete revision: {}:{:?}", + object_id, + rev_ids + ); + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); } - fn read( - user_id: &str, - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let mut sql = dsl::document_rev_table - .filter(dsl::document_id.eq(object_id)) - .into_boxed(); - if let Some(rev_ids) = rev_ids { - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - let rows = sql.order(dsl::rev_id.asc()).load::(conn)?; - let records = rows - .into_iter() - .map(|row| mk_revision_record_from_table(user_id, row)) - .collect::>(); - - Ok(records) - } - - fn read_with_range( - user_id: &str, - object_id: &str, - range: RevisionRange, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let rev_tables = dsl::document_rev_table - .filter(dsl::rev_id.ge(range.start)) - .filter(dsl::rev_id.le(range.end)) - .filter(dsl::document_id.eq(object_id)) - .order(dsl::rev_id.asc()) - .load::(conn)?; - - let revisions = rev_tables - .into_iter() - .map(|table| mk_revision_record_from_table(user_id, table)) - .collect::>(); - Ok(revisions) - } - - fn delete(object_id: &str, rev_ids: Option>, conn: &SqliteConnection) -> Result<(), FlowyError> { - let mut sql = diesel::delete(dsl::document_rev_table).into_boxed(); - sql = sql.filter(dsl::document_id.eq(object_id)); - - if let Some(rev_ids) = rev_ids { - tracing::trace!("[DocumentRevisionSql] Delete revision: {}:{:?}", object_id, rev_ids); - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - - let affected_row = sql.execute(conn)?; - tracing::trace!("[DocumentRevisionSql] Delete {} rows", affected_row); - Ok(()) - } + let affected_row = sql.execute(conn)?; + tracing::trace!("[DocumentRevisionSql] Delete {} rows", affected_row); + Ok(()) + } } #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] #[table_name = "document_rev_table"] struct DocumentRevisionTable { - id: i32, - document_id: String, - base_rev_id: i64, - rev_id: i64, - data: Vec, - state: DocumentRevisionState, + id: i32, + document_id: String, + base_rev_id: i64, + rev_id: i64, + data: Vec, + state: DocumentRevisionState, } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] #[repr(i32)] #[sql_type = "Integer"] enum DocumentRevisionState { - Sync = 0, - Ack = 1, + Sync = 0, + Ack = 1, } impl_sql_integer_expression!(DocumentRevisionState); impl_rev_state_map!(DocumentRevisionState); impl std::default::Default for DocumentRevisionState { - fn default() -> Self { - DocumentRevisionState::Sync - } + fn default() -> Self { + DocumentRevisionState::Sync + } } fn mk_revision_record_from_table(_user_id: &str, table: DocumentRevisionTable) -> SyncRecord { - let md5 = md5(&table.data); - let revision = Revision::new( - &table.document_id, - table.base_rev_id, - table.rev_id, - Bytes::from(table.data), - md5, - ); - SyncRecord { - revision, - state: table.state.into(), - write_to_disk: false, - } + let md5 = md5(&table.data); + let revision = Revision::new( + &table.document_id, + table.base_rev_id, + table.rev_id, + Bytes::from(table.data), + md5, + ); + SyncRecord { + revision, + state: table.state.into(), + write_to_disk: false, + } } diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_snapshot.rs b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_snapshot.rs index a42333c914..40ec19eb07 100644 --- a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_snapshot.rs +++ b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_snapshot.rs @@ -2,95 +2,95 @@ use bytes::Bytes; use flowy_error::{internal_error, FlowyResult}; use flowy_revision::{RevisionSnapshotData, RevisionSnapshotPersistence}; use flowy_sqlite::{ - prelude::*, - schema::{document_rev_snapshot, document_rev_snapshot::dsl}, - ConnectionPool, + prelude::*, + schema::{document_rev_snapshot, document_rev_snapshot::dsl}, + ConnectionPool, }; use lib_infra::util::timestamp; use std::sync::Arc; pub struct SQLiteDocumentRevisionSnapshotPersistence { - object_id: String, - pool: Arc, + object_id: String, + pool: Arc, } impl SQLiteDocumentRevisionSnapshotPersistence { - pub fn new(object_id: &str, pool: Arc) -> Self { - Self { - object_id: object_id.to_string(), - pool, - } + pub fn new(object_id: &str, pool: Arc) -> Self { + Self { + object_id: object_id.to_string(), + pool, } + } - fn gen_snapshot_id(&self, rev_id: i64) -> String { - format!("{}:{}", self.object_id, rev_id) - } + fn gen_snapshot_id(&self, rev_id: i64) -> String { + format!("{}:{}", self.object_id, rev_id) + } } impl RevisionSnapshotPersistence for SQLiteDocumentRevisionSnapshotPersistence { - fn should_generate_snapshot_from_range(&self, start_rev_id: i64, current_rev_id: i64) -> bool { - (current_rev_id - start_rev_id) >= 150 - } + fn should_generate_snapshot_from_range(&self, start_rev_id: i64, current_rev_id: i64) -> bool { + (current_rev_id - start_rev_id) >= 150 + } - fn write_snapshot(&self, rev_id: i64, data: Vec) -> FlowyResult<()> { - let conn = self.pool.get().map_err(internal_error)?; - let snapshot_id = self.gen_snapshot_id(rev_id); - let timestamp = timestamp(); - let record = ( - dsl::snapshot_id.eq(&snapshot_id), - dsl::object_id.eq(&self.object_id), - dsl::rev_id.eq(rev_id), - dsl::base_rev_id.eq(rev_id), - dsl::timestamp.eq(timestamp), - dsl::data.eq(data), - ); - let _ = insert_or_ignore_into(dsl::document_rev_snapshot) - .values(record) - .execute(&*conn)?; - Ok(()) - } + fn write_snapshot(&self, rev_id: i64, data: Vec) -> FlowyResult<()> { + let conn = self.pool.get().map_err(internal_error)?; + let snapshot_id = self.gen_snapshot_id(rev_id); + let timestamp = timestamp(); + let record = ( + dsl::snapshot_id.eq(&snapshot_id), + dsl::object_id.eq(&self.object_id), + dsl::rev_id.eq(rev_id), + dsl::base_rev_id.eq(rev_id), + dsl::timestamp.eq(timestamp), + dsl::data.eq(data), + ); + let _ = insert_or_ignore_into(dsl::document_rev_snapshot) + .values(record) + .execute(&*conn)?; + Ok(()) + } - fn read_snapshot(&self, rev_id: i64) -> FlowyResult> { - let conn = self.pool.get().map_err(internal_error)?; - let snapshot_id = self.gen_snapshot_id(rev_id); - let record = dsl::document_rev_snapshot - .filter(dsl::snapshot_id.eq(&snapshot_id)) - .first::(&*conn)?; + fn read_snapshot(&self, rev_id: i64) -> FlowyResult> { + let conn = self.pool.get().map_err(internal_error)?; + let snapshot_id = self.gen_snapshot_id(rev_id); + let record = dsl::document_rev_snapshot + .filter(dsl::snapshot_id.eq(&snapshot_id)) + .first::(&*conn)?; - Ok(Some(record.into())) - } + Ok(Some(record.into())) + } - fn read_last_snapshot(&self) -> FlowyResult> { - let conn = self.pool.get().map_err(internal_error)?; - let latest_record = dsl::document_rev_snapshot + fn read_last_snapshot(&self) -> FlowyResult> { + let conn = self.pool.get().map_err(internal_error)?; + let latest_record = dsl::document_rev_snapshot .filter(dsl::object_id.eq(&self.object_id)) .order(dsl::timestamp.desc()) // .select(max(dsl::rev_id)) // .select((dsl::id, dsl::object_id, dsl::rev_id, dsl::data)) .first::(&*conn)?; - Ok(Some(latest_record.into())) - } + Ok(Some(latest_record.into())) + } } #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] #[table_name = "document_rev_snapshot"] #[primary_key("snapshot_id")] struct DocumentSnapshotRecord { - snapshot_id: String, - object_id: String, - rev_id: i64, - base_rev_id: i64, - timestamp: i64, - data: Vec, + snapshot_id: String, + object_id: String, + rev_id: i64, + base_rev_id: i64, + timestamp: i64, + data: Vec, } impl std::convert::From for RevisionSnapshotData { - fn from(record: DocumentSnapshotRecord) -> Self { - RevisionSnapshotData { - rev_id: record.rev_id, - base_rev_id: record.base_rev_id, - timestamp: record.timestamp, - data: Bytes::from(record.data), - } + fn from(record: DocumentSnapshotRecord) -> Self { + RevisionSnapshotData { + rev_id: record.rev_id, + base_rev_id: record.base_rev_id, + timestamp: record.timestamp, + data: Bytes::from(record.data), } + } } diff --git a/frontend/rust-lib/flowy-document/tests/editor/mod.rs b/frontend/rust-lib/flowy-document/tests/editor/mod.rs index 0bdab884b5..a6eee1be5e 100644 --- a/frontend/rust-lib/flowy-document/tests/editor/mod.rs +++ b/frontend/rust-lib/flowy-document/tests/editor/mod.rs @@ -7,324 +7,340 @@ mod undo_redo_test; use derive_more::Display; use flowy_client_sync::client_document::{ClientDocument, InitialDocument}; use lib_ot::{ - core::*, - text_delta::{BuildInTextAttribute, DeltaTextOperations}, + core::*, + text_delta::{BuildInTextAttribute, DeltaTextOperations}, }; use rand::{prelude::*, Rng as WrappedRng}; use std::{sync::Once, time::Duration}; #[derive(Clone, Debug, Display)] pub enum TestOp { - #[display(fmt = "Insert")] - Insert(usize, &'static str, usize), + #[display(fmt = "Insert")] + Insert(usize, &'static str, usize), - // delta_i, s, start, length, - #[display(fmt = "InsertBold")] - InsertBold(usize, &'static str, Interval), + // delta_i, s, start, length, + #[display(fmt = "InsertBold")] + InsertBold(usize, &'static str, Interval), - // delta_i, start, length, enable - #[display(fmt = "Bold")] - Bold(usize, Interval, bool), + // delta_i, start, length, enable + #[display(fmt = "Bold")] + Bold(usize, Interval, bool), - #[display(fmt = "Delete")] - Delete(usize, Interval), + #[display(fmt = "Delete")] + Delete(usize, Interval), - #[display(fmt = "Replace")] - Replace(usize, Interval, &'static str), + #[display(fmt = "Replace")] + Replace(usize, Interval, &'static str), - #[display(fmt = "Italic")] - Italic(usize, Interval, bool), + #[display(fmt = "Italic")] + Italic(usize, Interval, bool), - #[display(fmt = "Header")] - Header(usize, Interval, usize), + #[display(fmt = "Header")] + Header(usize, Interval, usize), - #[display(fmt = "Link")] - Link(usize, Interval, &'static str), + #[display(fmt = "Link")] + Link(usize, Interval, &'static str), - #[display(fmt = "Bullet")] - Bullet(usize, Interval, bool), + #[display(fmt = "Bullet")] + Bullet(usize, Interval, bool), - #[display(fmt = "Transform")] - Transform(usize, usize), + #[display(fmt = "Transform")] + Transform(usize, usize), - #[display(fmt = "TransformPrime")] - TransformPrime(usize, usize), + #[display(fmt = "TransformPrime")] + TransformPrime(usize, usize), - // invert the delta_a base on the delta_b - #[display(fmt = "Invert")] - Invert(usize, usize), + // invert the delta_a base on the delta_b + #[display(fmt = "Invert")] + Invert(usize, usize), - #[display(fmt = "Undo")] - Undo(usize), + #[display(fmt = "Undo")] + Undo(usize), - #[display(fmt = "Redo")] - Redo(usize), + #[display(fmt = "Redo")] + Redo(usize), - #[display(fmt = "Wait")] - Wait(usize), + #[display(fmt = "Wait")] + Wait(usize), - #[display(fmt = "AssertStr")] - AssertStr(usize, &'static str), + #[display(fmt = "AssertStr")] + AssertStr(usize, &'static str), - #[display(fmt = "AssertDocJson")] - AssertDocJson(usize, &'static str), + #[display(fmt = "AssertDocJson")] + AssertDocJson(usize, &'static str), - #[display(fmt = "AssertPrimeJson")] - AssertPrimeJson(usize, &'static str), + #[display(fmt = "AssertPrimeJson")] + AssertPrimeJson(usize, &'static str), - #[display(fmt = "DocComposeDelta")] - DocComposeDelta(usize, usize), + #[display(fmt = "DocComposeDelta")] + DocComposeDelta(usize, usize), - #[display(fmt = "ApplyPrimeDelta")] - DocComposePrime(usize, usize), + #[display(fmt = "ApplyPrimeDelta")] + DocComposePrime(usize, usize), } pub struct TestBuilder { - documents: Vec, - deltas: Vec>, - primes: Vec>, + documents: Vec, + deltas: Vec>, + primes: Vec>, } impl TestBuilder { - pub fn new() -> Self { - static INIT: Once = Once::new(); - INIT.call_once(|| { - let _ = color_eyre::install(); - // let subscriber = FmtSubscriber::builder().with_max_level(Level::INFO).finish(); - // tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); - }); + pub fn new() -> Self { + static INIT: Once = Once::new(); + INIT.call_once(|| { + let _ = color_eyre::install(); + // let subscriber = FmtSubscriber::builder().with_max_level(Level::INFO).finish(); + // tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); + }); - Self { - documents: vec![], - deltas: vec![], - primes: vec![], - } + Self { + documents: vec![], + deltas: vec![], + primes: vec![], } + } - fn run_op(&mut self, op: &TestOp) { - tracing::trace!("***************** 😈{} *******************", &op); - match op { - TestOp::Insert(delta_i, s, index) => { - let document = &mut self.documents[*delta_i]; - let delta = document.insert(*index, s).unwrap(); - tracing::debug!("Insert delta: {}", delta.json_str()); + fn run_op(&mut self, op: &TestOp) { + tracing::trace!("***************** 😈{} *******************", &op); + match op { + TestOp::Insert(delta_i, s, index) => { + let document = &mut self.documents[*delta_i]; + let delta = document.insert(*index, s).unwrap(); + tracing::debug!("Insert delta: {}", delta.json_str()); - self.deltas.insert(*delta_i, Some(delta)); - } - TestOp::Delete(delta_i, iv) => { - let document = &mut self.documents[*delta_i]; - let delta = document.replace(*iv, "").unwrap(); - tracing::trace!("Delete delta: {}", delta.json_str()); - self.deltas.insert(*delta_i, Some(delta)); - } - TestOp::Replace(delta_i, iv, s) => { - let document = &mut self.documents[*delta_i]; - let delta = document.replace(*iv, s).unwrap(); - tracing::trace!("Replace delta: {}", delta.json_str()); - self.deltas.insert(*delta_i, Some(delta)); - } - TestOp::InsertBold(delta_i, s, iv) => { - let document = &mut self.documents[*delta_i]; - document.insert(iv.start, s).unwrap(); - document.format(*iv, BuildInTextAttribute::Bold(true)).unwrap(); - } - TestOp::Bold(delta_i, iv, enable) => { - let document = &mut self.documents[*delta_i]; - let attribute = BuildInTextAttribute::Bold(*enable); - let delta = document.format(*iv, attribute).unwrap(); - tracing::trace!("Bold delta: {}", delta.json_str()); - self.deltas.insert(*delta_i, Some(delta)); - } - TestOp::Italic(delta_i, iv, enable) => { - let document = &mut self.documents[*delta_i]; - let attribute = match *enable { - true => BuildInTextAttribute::Italic(true), - false => BuildInTextAttribute::Italic(false), - }; - let delta = document.format(*iv, attribute).unwrap(); - tracing::trace!("Italic delta: {}", delta.json_str()); - self.deltas.insert(*delta_i, Some(delta)); - } - TestOp::Header(delta_i, iv, level) => { - let document = &mut self.documents[*delta_i]; - let attribute = BuildInTextAttribute::Header(*level); - let delta = document.format(*iv, attribute).unwrap(); - tracing::trace!("Header delta: {}", delta.json_str()); - self.deltas.insert(*delta_i, Some(delta)); - } - TestOp::Link(delta_i, iv, link) => { - let document = &mut self.documents[*delta_i]; - let attribute = BuildInTextAttribute::Link(link.to_owned()); - let delta = document.format(*iv, attribute).unwrap(); - tracing::trace!("Link delta: {}", delta.json_str()); - self.deltas.insert(*delta_i, Some(delta)); - } - TestOp::Bullet(delta_i, iv, enable) => { - let document = &mut self.documents[*delta_i]; - let attribute = BuildInTextAttribute::Bullet(*enable); - let delta = document.format(*iv, attribute).unwrap(); - tracing::debug!("Bullet delta: {}", delta.json_str()); + self.deltas.insert(*delta_i, Some(delta)); + }, + TestOp::Delete(delta_i, iv) => { + let document = &mut self.documents[*delta_i]; + let delta = document.replace(*iv, "").unwrap(); + tracing::trace!("Delete delta: {}", delta.json_str()); + self.deltas.insert(*delta_i, Some(delta)); + }, + TestOp::Replace(delta_i, iv, s) => { + let document = &mut self.documents[*delta_i]; + let delta = document.replace(*iv, s).unwrap(); + tracing::trace!("Replace delta: {}", delta.json_str()); + self.deltas.insert(*delta_i, Some(delta)); + }, + TestOp::InsertBold(delta_i, s, iv) => { + let document = &mut self.documents[*delta_i]; + document.insert(iv.start, s).unwrap(); + document + .format(*iv, BuildInTextAttribute::Bold(true)) + .unwrap(); + }, + TestOp::Bold(delta_i, iv, enable) => { + let document = &mut self.documents[*delta_i]; + let attribute = BuildInTextAttribute::Bold(*enable); + let delta = document.format(*iv, attribute).unwrap(); + tracing::trace!("Bold delta: {}", delta.json_str()); + self.deltas.insert(*delta_i, Some(delta)); + }, + TestOp::Italic(delta_i, iv, enable) => { + let document = &mut self.documents[*delta_i]; + let attribute = match *enable { + true => BuildInTextAttribute::Italic(true), + false => BuildInTextAttribute::Italic(false), + }; + let delta = document.format(*iv, attribute).unwrap(); + tracing::trace!("Italic delta: {}", delta.json_str()); + self.deltas.insert(*delta_i, Some(delta)); + }, + TestOp::Header(delta_i, iv, level) => { + let document = &mut self.documents[*delta_i]; + let attribute = BuildInTextAttribute::Header(*level); + let delta = document.format(*iv, attribute).unwrap(); + tracing::trace!("Header delta: {}", delta.json_str()); + self.deltas.insert(*delta_i, Some(delta)); + }, + TestOp::Link(delta_i, iv, link) => { + let document = &mut self.documents[*delta_i]; + let attribute = BuildInTextAttribute::Link(link.to_owned()); + let delta = document.format(*iv, attribute).unwrap(); + tracing::trace!("Link delta: {}", delta.json_str()); + self.deltas.insert(*delta_i, Some(delta)); + }, + TestOp::Bullet(delta_i, iv, enable) => { + let document = &mut self.documents[*delta_i]; + let attribute = BuildInTextAttribute::Bullet(*enable); + let delta = document.format(*iv, attribute).unwrap(); + tracing::debug!("Bullet delta: {}", delta.json_str()); - self.deltas.insert(*delta_i, Some(delta)); - } - TestOp::Transform(delta_a_i, delta_b_i) => { - let (a_prime, b_prime) = self.documents[*delta_a_i] - .get_operations() - .transform(self.documents[*delta_b_i].get_operations()) - .unwrap(); - tracing::trace!("a:{:?},b:{:?}", a_prime, b_prime); + self.deltas.insert(*delta_i, Some(delta)); + }, + TestOp::Transform(delta_a_i, delta_b_i) => { + let (a_prime, b_prime) = self.documents[*delta_a_i] + .get_operations() + .transform(self.documents[*delta_b_i].get_operations()) + .unwrap(); + tracing::trace!("a:{:?},b:{:?}", a_prime, b_prime); - let data_left = self.documents[*delta_a_i].get_operations().compose(&b_prime).unwrap(); - let data_right = self.documents[*delta_b_i].get_operations().compose(&a_prime).unwrap(); + let data_left = self.documents[*delta_a_i] + .get_operations() + .compose(&b_prime) + .unwrap(); + let data_right = self.documents[*delta_b_i] + .get_operations() + .compose(&a_prime) + .unwrap(); - self.documents[*delta_a_i].set_operations(data_left); - self.documents[*delta_b_i].set_operations(data_right); - } - TestOp::TransformPrime(a_doc_index, b_doc_index) => { - let (prime_left, prime_right) = self.documents[*a_doc_index] - .get_operations() - .transform(self.documents[*b_doc_index].get_operations()) - .unwrap(); + self.documents[*delta_a_i].set_operations(data_left); + self.documents[*delta_b_i].set_operations(data_right); + }, + TestOp::TransformPrime(a_doc_index, b_doc_index) => { + let (prime_left, prime_right) = self.documents[*a_doc_index] + .get_operations() + .transform(self.documents[*b_doc_index].get_operations()) + .unwrap(); - self.primes.insert(*a_doc_index, Some(prime_left)); - self.primes.insert(*b_doc_index, Some(prime_right)); - } - TestOp::Invert(delta_a_i, delta_b_i) => { - let delta_a = &self.documents[*delta_a_i].get_operations(); - let delta_b = &self.documents[*delta_b_i].get_operations(); - tracing::debug!("Invert: "); - tracing::debug!("a: {}", delta_a.json_str()); - tracing::debug!("b: {}", delta_b.json_str()); + self.primes.insert(*a_doc_index, Some(prime_left)); + self.primes.insert(*b_doc_index, Some(prime_right)); + }, + TestOp::Invert(delta_a_i, delta_b_i) => { + let delta_a = &self.documents[*delta_a_i].get_operations(); + let delta_b = &self.documents[*delta_b_i].get_operations(); + tracing::debug!("Invert: "); + tracing::debug!("a: {}", delta_a.json_str()); + tracing::debug!("b: {}", delta_b.json_str()); - let (_, b_prime) = delta_a.transform(delta_b).unwrap(); - let undo = b_prime.invert(delta_a); + let (_, b_prime) = delta_a.transform(delta_b).unwrap(); + let undo = b_prime.invert(delta_a); - let new_delta = delta_a.compose(&b_prime).unwrap(); - tracing::debug!("new delta: {}", new_delta.json_str()); - tracing::debug!("undo delta: {}", undo.json_str()); + let new_delta = delta_a.compose(&b_prime).unwrap(); + tracing::debug!("new delta: {}", new_delta.json_str()); + tracing::debug!("undo delta: {}", undo.json_str()); - let new_delta_after_undo = new_delta.compose(&undo).unwrap(); + let new_delta_after_undo = new_delta.compose(&undo).unwrap(); - tracing::debug!("inverted delta a: {}", new_delta_after_undo.to_string()); + tracing::debug!("inverted delta a: {}", new_delta_after_undo.to_string()); - assert_eq!(delta_a, &&new_delta_after_undo); + assert_eq!(delta_a, &&new_delta_after_undo); - self.documents[*delta_a_i].set_operations(new_delta_after_undo); - } - TestOp::Undo(delta_i) => { - self.documents[*delta_i].undo().unwrap(); - } - TestOp::Redo(delta_i) => { - self.documents[*delta_i].redo().unwrap(); - } - TestOp::Wait(mills_sec) => { - std::thread::sleep(Duration::from_millis(*mills_sec as u64)); - } - TestOp::AssertStr(delta_i, expected) => { - assert_eq!(&self.documents[*delta_i].to_content(), expected); - } + self.documents[*delta_a_i].set_operations(new_delta_after_undo); + }, + TestOp::Undo(delta_i) => { + self.documents[*delta_i].undo().unwrap(); + }, + TestOp::Redo(delta_i) => { + self.documents[*delta_i].redo().unwrap(); + }, + TestOp::Wait(mills_sec) => { + std::thread::sleep(Duration::from_millis(*mills_sec as u64)); + }, + TestOp::AssertStr(delta_i, expected) => { + assert_eq!(&self.documents[*delta_i].to_content(), expected); + }, - TestOp::AssertDocJson(delta_i, expected) => { - let delta_json = self.documents[*delta_i].get_operations_json(); - let expected_delta: DeltaTextOperations = serde_json::from_str(expected).unwrap(); - let target_delta: DeltaTextOperations = serde_json::from_str(&delta_json).unwrap(); + TestOp::AssertDocJson(delta_i, expected) => { + let delta_json = self.documents[*delta_i].get_operations_json(); + let expected_delta: DeltaTextOperations = serde_json::from_str(expected).unwrap(); + let target_delta: DeltaTextOperations = serde_json::from_str(&delta_json).unwrap(); - if expected_delta != target_delta { - println!("✅ expect: {}", expected,); - println!("❌ receive: {}", delta_json); - } - assert_eq!(target_delta, expected_delta); - } - - TestOp::AssertPrimeJson(doc_i, expected) => { - let prime_json = self.primes[*doc_i].as_ref().unwrap().json_str(); - let expected_prime: DeltaTextOperations = serde_json::from_str(expected).unwrap(); - let target_prime: DeltaTextOperations = serde_json::from_str(&prime_json).unwrap(); - - if expected_prime != target_prime { - tracing::error!("✅ expect prime: {}", expected,); - tracing::error!("❌ receive prime: {}", prime_json); - } - assert_eq!(target_prime, expected_prime); - } - TestOp::DocComposeDelta(doc_index, delta_i) => { - let delta = self.deltas.get(*delta_i).unwrap().as_ref().unwrap(); - self.documents[*doc_index].compose_operations(delta.clone()).unwrap(); - } - TestOp::DocComposePrime(doc_index, prime_i) => { - let delta = self - .primes - .get(*prime_i) - .expect("Must call TransformPrime first") - .as_ref() - .unwrap(); - let new_delta = self.documents[*doc_index].get_operations().compose(delta).unwrap(); - self.documents[*doc_index].set_operations(new_delta); - } + if expected_delta != target_delta { + println!("✅ expect: {}", expected,); + println!("❌ receive: {}", delta_json); } - } + assert_eq!(target_delta, expected_delta); + }, - pub fn run_scripts(mut self, scripts: Vec) { - self.documents = vec![ClientDocument::new::(), ClientDocument::new::()]; - self.primes = vec![None, None]; - self.deltas = vec![None, None]; - for (_i, op) in scripts.iter().enumerate() { - self.run_op(op); + TestOp::AssertPrimeJson(doc_i, expected) => { + let prime_json = self.primes[*doc_i].as_ref().unwrap().json_str(); + let expected_prime: DeltaTextOperations = serde_json::from_str(expected).unwrap(); + let target_prime: DeltaTextOperations = serde_json::from_str(&prime_json).unwrap(); + + if expected_prime != target_prime { + tracing::error!("✅ expect prime: {}", expected,); + tracing::error!("❌ receive prime: {}", prime_json); } + assert_eq!(target_prime, expected_prime); + }, + TestOp::DocComposeDelta(doc_index, delta_i) => { + let delta = self.deltas.get(*delta_i).unwrap().as_ref().unwrap(); + self.documents[*doc_index] + .compose_operations(delta.clone()) + .unwrap(); + }, + TestOp::DocComposePrime(doc_index, prime_i) => { + let delta = self + .primes + .get(*prime_i) + .expect("Must call TransformPrime first") + .as_ref() + .unwrap(); + let new_delta = self.documents[*doc_index] + .get_operations() + .compose(delta) + .unwrap(); + self.documents[*doc_index].set_operations(new_delta); + }, } + } + + pub fn run_scripts(mut self, scripts: Vec) { + self.documents = vec![ClientDocument::new::(), ClientDocument::new::()]; + self.primes = vec![None, None]; + self.deltas = vec![None, None]; + for (_i, op) in scripts.iter().enumerate() { + self.run_op(op); + } + } } pub struct Rng(StdRng); impl Default for Rng { - fn default() -> Self { - Rng(StdRng::from_rng(thread_rng()).unwrap()) - } + fn default() -> Self { + Rng(StdRng::from_rng(thread_rng()).unwrap()) + } } impl Rng { - #[allow(dead_code)] - pub fn from_seed(seed: [u8; 32]) -> Self { - Rng(StdRng::from_seed(seed)) - } + #[allow(dead_code)] + pub fn from_seed(seed: [u8; 32]) -> Self { + Rng(StdRng::from_seed(seed)) + } - pub fn gen_string(&mut self, len: usize) -> String { - (0..len) - .map(|_| { - let c = self.0.gen::(); - format!("{:x}", c as u32) - }) - .collect() - } + pub fn gen_string(&mut self, len: usize) -> String { + (0..len) + .map(|_| { + let c = self.0.gen::(); + format!("{:x}", c as u32) + }) + .collect() + } - pub fn gen_delta(&mut self, s: &str) -> DeltaTextOperations { - let mut delta = DeltaTextOperations::default(); - let s = OTString::from(s); - loop { - let left = s.utf16_len() - delta.utf16_base_len; - if left == 0 { - break; - } - let i = if left == 1 { - 1 - } else { - 1 + self.0.gen_range(0..std::cmp::min(left - 1, 20)) - }; - match self.0.gen_range(0.0..1.0) { - f if f < 0.2 => { - delta.insert(&self.gen_string(i), AttributeHashMap::default()); - } - f if f < 0.4 => { - delta.delete(i); - } - _ => { - delta.retain(i, AttributeHashMap::default()); - } - } - } - if self.0.gen_range(0.0..1.0) < 0.3 { - delta.insert(&("1".to_owned() + &self.gen_string(10)), AttributeHashMap::default()); - } - delta + pub fn gen_delta(&mut self, s: &str) -> DeltaTextOperations { + let mut delta = DeltaTextOperations::default(); + let s = OTString::from(s); + loop { + let left = s.utf16_len() - delta.utf16_base_len; + if left == 0 { + break; + } + let i = if left == 1 { + 1 + } else { + 1 + self.0.gen_range(0..std::cmp::min(left - 1, 20)) + }; + match self.0.gen_range(0.0..1.0) { + f if f < 0.2 => { + delta.insert(&self.gen_string(i), AttributeHashMap::default()); + }, + f if f < 0.4 => { + delta.delete(i); + }, + _ => { + delta.retain(i, AttributeHashMap::default()); + }, + } } + if self.0.gen_range(0.0..1.0) < 0.3 { + delta.insert( + &("1".to_owned() + &self.gen_string(10)), + AttributeHashMap::default(), + ); + } + delta + } } diff --git a/frontend/rust-lib/flowy-document/tests/editor/op_test.rs b/frontend/rust-lib/flowy-document/tests/editor/op_test.rs index c7a900d586..5f2f6f42ba 100644 --- a/frontend/rust-lib/flowy-document/tests/editor/op_test.rs +++ b/frontend/rust-lib/flowy-document/tests/editor/op_test.rs @@ -6,744 +6,783 @@ use lib_ot::{core::Interval, core::*, text_delta::DeltaTextOperations}; #[test] fn attributes_insert_text() { - let ops = vec![ - Insert(0, "123", 0), - Insert(0, "456", 3), - AssertDocJson(0, r#"[{"insert":"123456"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Insert(0, "456", 3), + AssertDocJson(0, r#"[{"insert":"123456"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn attributes_insert_text_at_head() { - let ops = vec![ - Insert(0, "123", 0), - Insert(0, "456", 0), - AssertDocJson(0, r#"[{"insert":"456123"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Insert(0, "456", 0), + AssertDocJson(0, r#"[{"insert":"456123"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn attributes_insert_text_at_middle() { - let ops = vec![ - Insert(0, "123", 0), - Insert(0, "456", 1), - AssertDocJson(0, r#"[{"insert":"145623"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Insert(0, "456", 1), + AssertDocJson(0, r#"[{"insert":"145623"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn delta_get_ops_in_interval_1() { - let delta = DeltaTextOperationBuilder::new().insert("123").insert("4").build(); + let delta = DeltaTextOperationBuilder::new() + .insert("123") + .insert("4") + .build(); - let mut iterator = OperationIterator::from_interval(&delta, Interval::new(0, 4)); - assert_eq!(iterator.ops(), delta.ops); + let mut iterator = OperationIterator::from_interval(&delta, Interval::new(0, 4)); + assert_eq!(iterator.ops(), delta.ops); } #[test] fn delta_get_ops_in_interval_2() { - let mut delta = DeltaTextOperations::default(); - let insert_a = DeltaOperation::insert("123"); - let insert_b = DeltaOperation::insert("4"); - let insert_c = DeltaOperation::insert("5"); - let retain_a = DeltaOperation::retain(3); + let mut delta = DeltaTextOperations::default(); + let insert_a = DeltaOperation::insert("123"); + let insert_b = DeltaOperation::insert("4"); + let insert_c = DeltaOperation::insert("5"); + let retain_a = DeltaOperation::retain(3); - delta.add(insert_a.clone()); - delta.add(retain_a.clone()); - delta.add(insert_b.clone()); - delta.add(insert_c.clone()); + delta.add(insert_a.clone()); + delta.add(retain_a.clone()); + delta.add(insert_b.clone()); + delta.add(insert_c.clone()); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(0, 2)).ops(), - vec![DeltaOperation::insert("12")] - ); + assert_eq!( + OperationIterator::from_interval(&delta, Interval::new(0, 2)).ops(), + vec![DeltaOperation::insert("12")] + ); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(1, 3)).ops(), - vec![DeltaOperation::insert("23")] - ); + assert_eq!( + OperationIterator::from_interval(&delta, Interval::new(1, 3)).ops(), + vec![DeltaOperation::insert("23")] + ); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(0, 3)).ops(), - vec![insert_a.clone()] - ); + assert_eq!( + OperationIterator::from_interval(&delta, Interval::new(0, 3)).ops(), + vec![insert_a.clone()] + ); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(0, 4)).ops(), - vec![insert_a.clone(), DeltaOperation::retain(1)] - ); + assert_eq!( + OperationIterator::from_interval(&delta, Interval::new(0, 4)).ops(), + vec![insert_a.clone(), DeltaOperation::retain(1)] + ); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(0, 6)).ops(), - vec![insert_a.clone(), retain_a.clone()] - ); + assert_eq!( + OperationIterator::from_interval(&delta, Interval::new(0, 6)).ops(), + vec![insert_a.clone(), retain_a.clone()] + ); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(0, 7)).ops(), - vec![insert_a.clone(), retain_a.clone(), insert_b.clone()] - ); + assert_eq!( + OperationIterator::from_interval(&delta, Interval::new(0, 7)).ops(), + vec![insert_a.clone(), retain_a.clone(), insert_b.clone()] + ); } #[test] fn delta_get_ops_in_interval_3() { - let mut delta = DeltaTextOperations::default(); - let insert_a = DeltaOperation::insert("123456"); - delta.add(insert_a.clone()); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(3, 5)).ops(), - vec![DeltaOperation::insert("45")] - ); + let mut delta = DeltaTextOperations::default(); + let insert_a = DeltaOperation::insert("123456"); + delta.add(insert_a.clone()); + assert_eq!( + OperationIterator::from_interval(&delta, Interval::new(3, 5)).ops(), + vec![DeltaOperation::insert("45")] + ); } #[test] fn delta_get_ops_in_interval_4() { - let mut delta = DeltaTextOperations::default(); - let insert_a = DeltaOperation::insert("12"); - let insert_b = DeltaOperation::insert("34"); - let insert_c = DeltaOperation::insert("56"); + let mut delta = DeltaTextOperations::default(); + let insert_a = DeltaOperation::insert("12"); + let insert_b = DeltaOperation::insert("34"); + let insert_c = DeltaOperation::insert("56"); - delta.ops.push(insert_a.clone()); - delta.ops.push(insert_b.clone()); - delta.ops.push(insert_c.clone()); + delta.ops.push(insert_a.clone()); + delta.ops.push(insert_b.clone()); + delta.ops.push(insert_c.clone()); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(0, 2)).ops(), - vec![insert_a] - ); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(2, 4)).ops(), - vec![insert_b] - ); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(4, 6)).ops(), - vec![insert_c] - ); + assert_eq!( + OperationIterator::from_interval(&delta, Interval::new(0, 2)).ops(), + vec![insert_a] + ); + assert_eq!( + OperationIterator::from_interval(&delta, Interval::new(2, 4)).ops(), + vec![insert_b] + ); + assert_eq!( + OperationIterator::from_interval(&delta, Interval::new(4, 6)).ops(), + vec![insert_c] + ); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(2, 5)).ops(), - vec![DeltaOperation::insert("34"), DeltaOperation::insert("5")] - ); + assert_eq!( + OperationIterator::from_interval(&delta, Interval::new(2, 5)).ops(), + vec![DeltaOperation::insert("34"), DeltaOperation::insert("5")] + ); } #[test] fn delta_get_ops_in_interval_5() { - let mut delta = DeltaTextOperations::default(); - let insert_a = DeltaOperation::insert("123456"); - let insert_b = DeltaOperation::insert("789"); - delta.ops.push(insert_a.clone()); - delta.ops.push(insert_b.clone()); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(4, 8)).ops(), - vec![DeltaOperation::insert("56"), DeltaOperation::insert("78")] - ); + let mut delta = DeltaTextOperations::default(); + let insert_a = DeltaOperation::insert("123456"); + let insert_b = DeltaOperation::insert("789"); + delta.ops.push(insert_a.clone()); + delta.ops.push(insert_b.clone()); + assert_eq!( + OperationIterator::from_interval(&delta, Interval::new(4, 8)).ops(), + vec![DeltaOperation::insert("56"), DeltaOperation::insert("78")] + ); - // assert_eq!( - // DeltaIter::from_interval(&delta, Interval::new(8, 9)).ops(), - // vec![Builder::insert("9")] - // ); + // assert_eq!( + // DeltaIter::from_interval(&delta, Interval::new(8, 9)).ops(), + // vec![Builder::insert("9")] + // ); } #[test] fn delta_get_ops_in_interval_6() { - let mut delta = DeltaTextOperations::default(); - let insert_a = DeltaOperation::insert("12345678"); - delta.add(insert_a.clone()); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(4, 6)).ops(), - vec![DeltaOperation::insert("56")] - ); + let mut delta = DeltaTextOperations::default(); + let insert_a = DeltaOperation::insert("12345678"); + delta.add(insert_a.clone()); + assert_eq!( + OperationIterator::from_interval(&delta, Interval::new(4, 6)).ops(), + vec![DeltaOperation::insert("56")] + ); } #[test] fn delta_get_ops_in_interval_7() { - let mut delta = DeltaTextOperations::default(); - let insert_a = DeltaOperation::insert("12345"); - let retain_a = DeltaOperation::retain(3); + let mut delta = DeltaTextOperations::default(); + let insert_a = DeltaOperation::insert("12345"); + let retain_a = DeltaOperation::retain(3); - delta.add(insert_a.clone()); - delta.add(retain_a.clone()); + delta.add(insert_a.clone()); + delta.add(retain_a.clone()); - let mut iter_1 = OperationIterator::from_offset(&delta, 2); - assert_eq!(iter_1.next_op().unwrap(), DeltaOperation::insert("345")); - assert_eq!(iter_1.next_op().unwrap(), DeltaOperation::retain(3)); + let mut iter_1 = OperationIterator::from_offset(&delta, 2); + assert_eq!(iter_1.next_op().unwrap(), DeltaOperation::insert("345")); + assert_eq!(iter_1.next_op().unwrap(), DeltaOperation::retain(3)); - let mut iter_2 = OperationIterator::new(&delta); - assert_eq!(iter_2.next_op_with_len(2).unwrap(), DeltaOperation::insert("12")); - assert_eq!(iter_2.next_op().unwrap(), DeltaOperation::insert("345")); + let mut iter_2 = OperationIterator::new(&delta); + assert_eq!( + iter_2.next_op_with_len(2).unwrap(), + DeltaOperation::insert("12") + ); + assert_eq!(iter_2.next_op().unwrap(), DeltaOperation::insert("345")); - assert_eq!(iter_2.next_op().unwrap(), DeltaOperation::retain(3)); + assert_eq!(iter_2.next_op().unwrap(), DeltaOperation::retain(3)); } #[test] fn delta_op_seek() { - let mut delta = DeltaTextOperations::default(); - let insert_a = DeltaOperation::insert("12345"); - let retain_a = DeltaOperation::retain(3); - delta.add(insert_a.clone()); - delta.add(retain_a.clone()); - let mut iter = OperationIterator::new(&delta); - iter.seek::(1); - assert_eq!(iter.next_op().unwrap(), retain_a); + let mut delta = DeltaTextOperations::default(); + let insert_a = DeltaOperation::insert("12345"); + let retain_a = DeltaOperation::retain(3); + delta.add(insert_a.clone()); + delta.add(retain_a.clone()); + let mut iter = OperationIterator::new(&delta); + iter.seek::(1); + assert_eq!(iter.next_op().unwrap(), retain_a); } #[test] fn delta_utf16_code_unit_seek() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("12345")); + let mut delta = DeltaTextOperations::default(); + delta.add(DeltaOperation::insert("12345")); - let mut iter = OperationIterator::new(&delta); - iter.seek::(3); - assert_eq!(iter.next_op_with_len(2).unwrap(), DeltaOperation::insert("45")); + let mut iter = OperationIterator::new(&delta); + iter.seek::(3); + assert_eq!( + iter.next_op_with_len(2).unwrap(), + DeltaOperation::insert("45") + ); } #[test] fn delta_utf16_code_unit_seek_with_attributes() { - let mut delta = DeltaTextOperations::default(); - let attributes = AttributeBuilder::new() - .insert("bold", true) - .insert("italic", true) - .build(); + let mut delta = DeltaTextOperations::default(); + let attributes = AttributeBuilder::new() + .insert("bold", true) + .insert("italic", true) + .build(); - delta.add(DeltaOperation::insert_with_attributes("1234", attributes.clone())); - delta.add(DeltaOperation::insert("\n")); + delta.add(DeltaOperation::insert_with_attributes( + "1234", + attributes.clone(), + )); + delta.add(DeltaOperation::insert("\n")); - let mut iter = OperationIterator::new(&delta); - iter.seek::(0); + let mut iter = OperationIterator::new(&delta); + iter.seek::(0); - assert_eq!( - iter.next_op_with_len(4).unwrap(), - DeltaOperation::insert_with_attributes("1234", attributes), - ); + assert_eq!( + iter.next_op_with_len(4).unwrap(), + DeltaOperation::insert_with_attributes("1234", attributes), + ); } #[test] fn delta_next_op_len() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("12345")); - let mut iter = OperationIterator::new(&delta); - assert_eq!(iter.next_op_with_len(2).unwrap(), DeltaOperation::insert("12")); - assert_eq!(iter.next_op_with_len(2).unwrap(), DeltaOperation::insert("34")); - assert_eq!(iter.next_op_with_len(2).unwrap(), DeltaOperation::insert("5")); - assert_eq!(iter.next_op_with_len(1), None); + let mut delta = DeltaTextOperations::default(); + delta.add(DeltaOperation::insert("12345")); + let mut iter = OperationIterator::new(&delta); + assert_eq!( + iter.next_op_with_len(2).unwrap(), + DeltaOperation::insert("12") + ); + assert_eq!( + iter.next_op_with_len(2).unwrap(), + DeltaOperation::insert("34") + ); + assert_eq!( + iter.next_op_with_len(2).unwrap(), + DeltaOperation::insert("5") + ); + assert_eq!(iter.next_op_with_len(1), None); } #[test] fn delta_next_op_len_with_chinese() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("你好")); + let mut delta = DeltaTextOperations::default(); + delta.add(DeltaOperation::insert("你好")); - let mut iter = OperationIterator::new(&delta); - assert_eq!(iter.next_op_len().unwrap(), 2); - assert_eq!(iter.next_op_with_len(2).unwrap(), DeltaOperation::insert("你好")); + let mut iter = OperationIterator::new(&delta); + assert_eq!(iter.next_op_len().unwrap(), 2); + assert_eq!( + iter.next_op_with_len(2).unwrap(), + DeltaOperation::insert("你好") + ); } #[test] fn delta_next_op_len_with_english() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("ab")); - let mut iter = OperationIterator::new(&delta); - assert_eq!(iter.next_op_len().unwrap(), 2); - assert_eq!(iter.next_op_with_len(2).unwrap(), DeltaOperation::insert("ab")); + let mut delta = DeltaTextOperations::default(); + delta.add(DeltaOperation::insert("ab")); + let mut iter = OperationIterator::new(&delta); + assert_eq!(iter.next_op_len().unwrap(), 2); + assert_eq!( + iter.next_op_with_len(2).unwrap(), + DeltaOperation::insert("ab") + ); } #[test] fn delta_next_op_len_after_seek() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("12345")); - let mut iter = OperationIterator::new(&delta); - assert_eq!(iter.next_op_len().unwrap(), 5); - iter.seek::(3); - assert_eq!(iter.next_op_len().unwrap(), 2); - assert_eq!(iter.next_op_with_len(1).unwrap(), DeltaOperation::insert("4")); - assert_eq!(iter.next_op_len().unwrap(), 1); - assert_eq!(iter.next_op().unwrap(), DeltaOperation::insert("5")); + let mut delta = DeltaTextOperations::default(); + delta.add(DeltaOperation::insert("12345")); + let mut iter = OperationIterator::new(&delta); + assert_eq!(iter.next_op_len().unwrap(), 5); + iter.seek::(3); + assert_eq!(iter.next_op_len().unwrap(), 2); + assert_eq!( + iter.next_op_with_len(1).unwrap(), + DeltaOperation::insert("4") + ); + assert_eq!(iter.next_op_len().unwrap(), 1); + assert_eq!(iter.next_op().unwrap(), DeltaOperation::insert("5")); } #[test] fn delta_next_op_len_none() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("12345")); - let mut iter = OperationIterator::new(&delta); + let mut delta = DeltaTextOperations::default(); + delta.add(DeltaOperation::insert("12345")); + let mut iter = OperationIterator::new(&delta); - assert_eq!(iter.next_op_len().unwrap(), 5); - assert_eq!(iter.next_op_with_len(5).unwrap(), DeltaOperation::insert("12345")); - assert_eq!(iter.next_op_len(), None); + assert_eq!(iter.next_op_len().unwrap(), 5); + assert_eq!( + iter.next_op_with_len(5).unwrap(), + DeltaOperation::insert("12345") + ); + assert_eq!(iter.next_op_len(), None); } #[test] fn delta_next_op_with_len_zero() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("12345")); - let mut iter = OperationIterator::new(&delta); - assert_eq!(iter.next_op_with_len(0), None,); - assert_eq!(iter.next_op_len().unwrap(), 5); + let mut delta = DeltaTextOperations::default(); + delta.add(DeltaOperation::insert("12345")); + let mut iter = OperationIterator::new(&delta); + assert_eq!(iter.next_op_with_len(0), None,); + assert_eq!(iter.next_op_len().unwrap(), 5); } #[test] fn delta_next_op_with_len_cross_op_return_last() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("12345")); - delta.add(DeltaOperation::retain(1)); - delta.add(DeltaOperation::insert("678")); + let mut delta = DeltaTextOperations::default(); + delta.add(DeltaOperation::insert("12345")); + delta.add(DeltaOperation::retain(1)); + delta.add(DeltaOperation::insert("678")); - let mut iter = OperationIterator::new(&delta); - iter.seek::(4); - assert_eq!(iter.next_op_len().unwrap(), 1); - assert_eq!(iter.next_op_with_len(2).unwrap(), DeltaOperation::retain(1)); + let mut iter = OperationIterator::new(&delta); + iter.seek::(4); + assert_eq!(iter.next_op_len().unwrap(), 1); + assert_eq!(iter.next_op_with_len(2).unwrap(), DeltaOperation::retain(1)); } #[test] fn lengths() { - let mut delta = DeltaTextOperations::default(); - assert_eq!(delta.utf16_base_len, 0); - assert_eq!(delta.utf16_target_len, 0); - delta.retain(5, AttributeHashMap::default()); - assert_eq!(delta.utf16_base_len, 5); - assert_eq!(delta.utf16_target_len, 5); - delta.insert("abc", AttributeHashMap::default()); - assert_eq!(delta.utf16_base_len, 5); - assert_eq!(delta.utf16_target_len, 8); - delta.retain(2, AttributeHashMap::default()); - assert_eq!(delta.utf16_base_len, 7); - assert_eq!(delta.utf16_target_len, 10); - delta.delete(2); - assert_eq!(delta.utf16_base_len, 9); - assert_eq!(delta.utf16_target_len, 10); + let mut delta = DeltaTextOperations::default(); + assert_eq!(delta.utf16_base_len, 0); + assert_eq!(delta.utf16_target_len, 0); + delta.retain(5, AttributeHashMap::default()); + assert_eq!(delta.utf16_base_len, 5); + assert_eq!(delta.utf16_target_len, 5); + delta.insert("abc", AttributeHashMap::default()); + assert_eq!(delta.utf16_base_len, 5); + assert_eq!(delta.utf16_target_len, 8); + delta.retain(2, AttributeHashMap::default()); + assert_eq!(delta.utf16_base_len, 7); + assert_eq!(delta.utf16_target_len, 10); + delta.delete(2); + assert_eq!(delta.utf16_base_len, 9); + assert_eq!(delta.utf16_target_len, 10); } #[test] fn sequence() { - let mut delta = DeltaTextOperations::default(); - delta.retain(5, AttributeHashMap::default()); - delta.retain(0, AttributeHashMap::default()); - delta.insert("appflowy", AttributeHashMap::default()); - delta.insert("", AttributeHashMap::default()); - delta.delete(3); - delta.delete(0); - assert_eq!(delta.ops.len(), 3); + let mut delta = DeltaTextOperations::default(); + delta.retain(5, AttributeHashMap::default()); + delta.retain(0, AttributeHashMap::default()); + delta.insert("appflowy", AttributeHashMap::default()); + delta.insert("", AttributeHashMap::default()); + delta.delete(3); + delta.delete(0); + assert_eq!(delta.ops.len(), 3); } #[test] fn apply_1000() { - for _ in 0..1 { - let mut rng = Rng::default(); - let s: OTString = rng.gen_string(50).into(); - let delta = rng.gen_delta(&s); - assert_eq!(s.utf16_len(), delta.utf16_base_len); - } + for _ in 0..1 { + let mut rng = Rng::default(); + let s: OTString = rng.gen_string(50).into(); + let delta = rng.gen_delta(&s); + assert_eq!(s.utf16_len(), delta.utf16_base_len); + } } #[test] fn apply_test() { - let s = "hello"; - let delta_a = DeltaBuilder::new().insert(s).build(); - let delta_b = DeltaBuilder::new().retain(s.len()).insert(", AppFlowy").build(); + let s = "hello"; + let delta_a = DeltaBuilder::new().insert(s).build(); + let delta_b = DeltaBuilder::new() + .retain(s.len()) + .insert(", AppFlowy") + .build(); - let after_a = delta_a.content().unwrap(); - let after_b = delta_b.apply(&after_a).unwrap(); - assert_eq!("hello, AppFlowy", &after_b); + let after_a = delta_a.content().unwrap(); + let after_b = delta_b.apply(&after_a).unwrap(); + assert_eq!("hello, AppFlowy", &after_b); } #[test] fn base_len_test() { - let mut delta_a = DeltaTextOperations::default(); - delta_a.insert("a", AttributeHashMap::default()); - delta_a.insert("b", AttributeHashMap::default()); - delta_a.insert("c", AttributeHashMap::default()); + let mut delta_a = DeltaTextOperations::default(); + delta_a.insert("a", AttributeHashMap::default()); + delta_a.insert("b", AttributeHashMap::default()); + delta_a.insert("c", AttributeHashMap::default()); - let s = "hello world,".to_owned(); - delta_a.delete(s.len()); - let after_a = delta_a.apply(&s).unwrap(); + let s = "hello world,".to_owned(); + delta_a.delete(s.len()); + let after_a = delta_a.apply(&s).unwrap(); - delta_a.insert("d", AttributeHashMap::default()); - assert_eq!("abc", &after_a); + delta_a.insert("d", AttributeHashMap::default()); + assert_eq!("abc", &after_a); } #[test] fn invert() { - for _ in 0..1000 { - let mut rng = Rng::default(); - let s = rng.gen_string(50); - let delta_a = rng.gen_delta(&s); - let delta_b = delta_a.invert_str(&s); - assert_eq!(delta_a.utf16_base_len, delta_b.utf16_target_len); - assert_eq!(delta_a.utf16_target_len, delta_b.utf16_base_len); - assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s); - } + for _ in 0..1000 { + let mut rng = Rng::default(); + let s = rng.gen_string(50); + let delta_a = rng.gen_delta(&s); + let delta_b = delta_a.invert_str(&s); + assert_eq!(delta_a.utf16_base_len, delta_b.utf16_target_len); + assert_eq!(delta_a.utf16_target_len, delta_b.utf16_base_len); + assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s); + } } #[test] fn invert_test() { - let s = "hello world"; - let delta = DeltaBuilder::new().insert(s).build(); - let invert_delta = delta.invert_str(""); - assert_eq!(delta.utf16_base_len, invert_delta.utf16_target_len); - assert_eq!(delta.utf16_target_len, invert_delta.utf16_base_len); + let s = "hello world"; + let delta = DeltaBuilder::new().insert(s).build(); + let invert_delta = delta.invert_str(""); + assert_eq!(delta.utf16_base_len, invert_delta.utf16_target_len); + assert_eq!(delta.utf16_target_len, invert_delta.utf16_base_len); - assert_eq!(invert_delta.apply(s).unwrap(), "") + assert_eq!(invert_delta.apply(s).unwrap(), "") } #[test] fn empty_ops() { - let mut delta = DeltaTextOperations::default(); - delta.retain(0, AttributeHashMap::default()); - delta.insert("", AttributeHashMap::default()); - delta.delete(0); - assert_eq!(delta.ops.len(), 0); + let mut delta = DeltaTextOperations::default(); + delta.retain(0, AttributeHashMap::default()); + delta.insert("", AttributeHashMap::default()); + delta.delete(0); + assert_eq!(delta.ops.len(), 0); } #[test] fn eq() { - let mut delta_a = DeltaTextOperations::default(); - delta_a.delete(1); - delta_a.insert("lo", AttributeHashMap::default()); - delta_a.retain(2, AttributeHashMap::default()); - delta_a.retain(3, AttributeHashMap::default()); - let mut delta_b = DeltaTextOperations::default(); - delta_b.delete(1); - delta_b.insert("l", AttributeHashMap::default()); - delta_b.insert("o", AttributeHashMap::default()); - delta_b.retain(5, AttributeHashMap::default()); - assert_eq!(delta_a, delta_b); - delta_a.delete(1); - delta_b.retain(1, AttributeHashMap::default()); - assert_ne!(delta_a, delta_b); + let mut delta_a = DeltaTextOperations::default(); + delta_a.delete(1); + delta_a.insert("lo", AttributeHashMap::default()); + delta_a.retain(2, AttributeHashMap::default()); + delta_a.retain(3, AttributeHashMap::default()); + let mut delta_b = DeltaTextOperations::default(); + delta_b.delete(1); + delta_b.insert("l", AttributeHashMap::default()); + delta_b.insert("o", AttributeHashMap::default()); + delta_b.retain(5, AttributeHashMap::default()); + assert_eq!(delta_a, delta_b); + delta_a.delete(1); + delta_b.retain(1, AttributeHashMap::default()); + assert_ne!(delta_a, delta_b); } #[test] fn ops_merging() { - let mut delta = DeltaTextOperations::default(); - assert_eq!(delta.ops.len(), 0); - delta.retain(2, AttributeHashMap::default()); - assert_eq!(delta.ops.len(), 1); - assert_eq!(delta.ops.last(), Some(&DeltaOperation::retain(2))); - delta.retain(3, AttributeHashMap::default()); - assert_eq!(delta.ops.len(), 1); - assert_eq!(delta.ops.last(), Some(&DeltaOperation::retain(5))); - delta.insert("abc", AttributeHashMap::default()); - assert_eq!(delta.ops.len(), 2); - assert_eq!(delta.ops.last(), Some(&DeltaOperation::insert("abc"))); - delta.insert("xyz", AttributeHashMap::default()); - assert_eq!(delta.ops.len(), 2); - assert_eq!(delta.ops.last(), Some(&DeltaOperation::insert("abcxyz"))); - delta.delete(1); - assert_eq!(delta.ops.len(), 3); - assert_eq!(delta.ops.last(), Some(&DeltaOperation::delete(1))); - delta.delete(1); - assert_eq!(delta.ops.len(), 3); - assert_eq!(delta.ops.last(), Some(&DeltaOperation::delete(2))); + let mut delta = DeltaTextOperations::default(); + assert_eq!(delta.ops.len(), 0); + delta.retain(2, AttributeHashMap::default()); + assert_eq!(delta.ops.len(), 1); + assert_eq!(delta.ops.last(), Some(&DeltaOperation::retain(2))); + delta.retain(3, AttributeHashMap::default()); + assert_eq!(delta.ops.len(), 1); + assert_eq!(delta.ops.last(), Some(&DeltaOperation::retain(5))); + delta.insert("abc", AttributeHashMap::default()); + assert_eq!(delta.ops.len(), 2); + assert_eq!(delta.ops.last(), Some(&DeltaOperation::insert("abc"))); + delta.insert("xyz", AttributeHashMap::default()); + assert_eq!(delta.ops.len(), 2); + assert_eq!(delta.ops.last(), Some(&DeltaOperation::insert("abcxyz"))); + delta.delete(1); + assert_eq!(delta.ops.len(), 3); + assert_eq!(delta.ops.last(), Some(&DeltaOperation::delete(1))); + delta.delete(1); + assert_eq!(delta.ops.len(), 3); + assert_eq!(delta.ops.last(), Some(&DeltaOperation::delete(2))); } #[test] fn is_noop() { - let mut delta = DeltaTextOperations::default(); - assert!(delta.is_noop()); - delta.retain(5, AttributeHashMap::default()); - assert!(delta.is_noop()); - delta.retain(3, AttributeHashMap::default()); - assert!(delta.is_noop()); - delta.insert("lorem", AttributeHashMap::default()); - assert!(!delta.is_noop()); + let mut delta = DeltaTextOperations::default(); + assert!(delta.is_noop()); + delta.retain(5, AttributeHashMap::default()); + assert!(delta.is_noop()); + delta.retain(3, AttributeHashMap::default()); + assert!(delta.is_noop()); + delta.insert("lorem", AttributeHashMap::default()); + assert!(!delta.is_noop()); } #[test] fn compose() { - for _ in 0..1000 { - let mut rng = Rng::default(); - let s = rng.gen_string(20); - let a = rng.gen_delta(&s); - let after_a: OTString = a.apply(&s).unwrap().into(); - assert_eq!(a.utf16_target_len, after_a.utf16_len()); + for _ in 0..1000 { + let mut rng = Rng::default(); + let s = rng.gen_string(20); + let a = rng.gen_delta(&s); + let after_a: OTString = a.apply(&s).unwrap().into(); + assert_eq!(a.utf16_target_len, after_a.utf16_len()); - let b = rng.gen_delta(&after_a); - let after_b: OTString = b.apply(&after_a).unwrap().into(); - assert_eq!(b.utf16_target_len, after_b.utf16_len()); + let b = rng.gen_delta(&after_a); + let after_b: OTString = b.apply(&after_a).unwrap().into(); + assert_eq!(b.utf16_target_len, after_b.utf16_len()); - let ab = a.compose(&b).unwrap(); - assert_eq!(ab.utf16_target_len, b.utf16_target_len); - let after_ab: OTString = ab.apply(&s).unwrap().into(); - assert_eq!(after_b, after_ab); - } + let ab = a.compose(&b).unwrap(); + assert_eq!(ab.utf16_target_len, b.utf16_target_len); + let after_ab: OTString = ab.apply(&s).unwrap().into(); + assert_eq!(after_b, after_ab); + } } #[test] fn transform_random_delta() { - for _ in 0..1000 { - let mut rng = Rng::default(); - let s = rng.gen_string(20); - let a = rng.gen_delta(&s); - let b = rng.gen_delta(&s); - let (a_prime, b_prime) = a.transform(&b).unwrap(); - let ab_prime = a.compose(&b_prime).unwrap(); - let ba_prime = b.compose(&a_prime).unwrap(); - assert_eq!(ab_prime, ba_prime); + for _ in 0..1000 { + let mut rng = Rng::default(); + let s = rng.gen_string(20); + let a = rng.gen_delta(&s); + let b = rng.gen_delta(&s); + let (a_prime, b_prime) = a.transform(&b).unwrap(); + let ab_prime = a.compose(&b_prime).unwrap(); + let ba_prime = b.compose(&a_prime).unwrap(); + assert_eq!(ab_prime, ba_prime); - let after_ab_prime = ab_prime.apply(&s).unwrap(); - let after_ba_prime = ba_prime.apply(&s).unwrap(); - assert_eq!(after_ab_prime, after_ba_prime); - } + let after_ab_prime = ab_prime.apply(&s).unwrap(); + let after_ba_prime = ba_prime.apply(&s).unwrap(); + assert_eq!(after_ab_prime, after_ba_prime); + } } #[test] fn transform_with_two_delta() { - let mut a = DeltaTextOperations::default(); - let mut a_s = String::new(); - a.insert("123", AttributeBuilder::new().insert("bold", true).build()); - a_s = a.apply(&a_s).unwrap(); - assert_eq!(&a_s, "123"); + let mut a = DeltaTextOperations::default(); + let mut a_s = String::new(); + a.insert("123", AttributeBuilder::new().insert("bold", true).build()); + a_s = a.apply(&a_s).unwrap(); + assert_eq!(&a_s, "123"); - let mut b = DeltaTextOperations::default(); - let mut b_s = String::new(); - b.insert("456", AttributeHashMap::default()); - b_s = b.apply(&b_s).unwrap(); - assert_eq!(&b_s, "456"); + let mut b = DeltaTextOperations::default(); + let mut b_s = String::new(); + b.insert("456", AttributeHashMap::default()); + b_s = b.apply(&b_s).unwrap(); + assert_eq!(&b_s, "456"); - let (a_prime, b_prime) = a.transform(&b).unwrap(); - assert_eq!( - r#"[{"insert":"123","attributes":{"bold":true}},{"retain":3}]"#, - serde_json::to_string(&a_prime).unwrap() - ); - assert_eq!( - r#"[{"retain":3,"attributes":{"bold":true}},{"insert":"456"}]"#, - serde_json::to_string(&b_prime).unwrap() - ); + let (a_prime, b_prime) = a.transform(&b).unwrap(); + assert_eq!( + r#"[{"insert":"123","attributes":{"bold":true}},{"retain":3}]"#, + serde_json::to_string(&a_prime).unwrap() + ); + assert_eq!( + r#"[{"retain":3,"attributes":{"bold":true}},{"insert":"456"}]"#, + serde_json::to_string(&b_prime).unwrap() + ); - let new_a = a.compose(&b_prime).unwrap(); - let new_b = b.compose(&a_prime).unwrap(); - assert_eq!( - r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"456"}]"#, - serde_json::to_string(&new_a).unwrap() - ); + let new_a = a.compose(&b_prime).unwrap(); + let new_b = b.compose(&a_prime).unwrap(); + assert_eq!( + r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"456"}]"#, + serde_json::to_string(&new_a).unwrap() + ); - assert_eq!( - r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"456"}]"#, - serde_json::to_string(&new_b).unwrap() - ); + assert_eq!( + r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"456"}]"#, + serde_json::to_string(&new_b).unwrap() + ); } #[test] fn transform_two_plain_delta() { - let ops = vec![ - Insert(0, "123", 0), - Insert(1, "456", 0), - Transform(0, 1), - AssertDocJson(0, r#"[{"insert":"123456"}]"#), - AssertDocJson(1, r#"[{"insert":"123456"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Insert(1, "456", 0), + Transform(0, 1), + AssertDocJson(0, r#"[{"insert":"123456"}]"#), + AssertDocJson(1, r#"[{"insert":"123456"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn transform_two_plain_delta2() { - let ops = vec![ - Insert(0, "123", 0), - Insert(1, "456", 0), - TransformPrime(0, 1), - DocComposePrime(0, 1), - DocComposePrime(1, 0), - AssertDocJson(0, r#"[{"insert":"123456"}]"#), - AssertDocJson(1, r#"[{"insert":"123456"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Insert(1, "456", 0), + TransformPrime(0, 1), + DocComposePrime(0, 1), + DocComposePrime(1, 0), + AssertDocJson(0, r#"[{"insert":"123456"}]"#), + AssertDocJson(1, r#"[{"insert":"123456"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn transform_two_non_seq_delta() { - let ops = vec![ - Insert(0, "123", 0), - Insert(1, "456", 0), - TransformPrime(0, 1), - AssertPrimeJson(0, r#"[{"insert":"123"},{"retain":3}]"#), - AssertPrimeJson(1, r#"[{"retain":3},{"insert":"456"}]"#), - DocComposePrime(0, 1), - Insert(1, "78", 3), - Insert(1, "9", 5), - DocComposePrime(1, 0), - AssertDocJson(0, r#"[{"insert":"123456"}]"#), - AssertDocJson(1, r#"[{"insert":"123456789"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Insert(1, "456", 0), + TransformPrime(0, 1), + AssertPrimeJson(0, r#"[{"insert":"123"},{"retain":3}]"#), + AssertPrimeJson(1, r#"[{"retain":3},{"insert":"456"}]"#), + DocComposePrime(0, 1), + Insert(1, "78", 3), + Insert(1, "9", 5), + DocComposePrime(1, 0), + AssertDocJson(0, r#"[{"insert":"123456"}]"#), + AssertDocJson(1, r#"[{"insert":"123456789"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn transform_two_conflict_non_seq_delta() { - let ops = vec![ - Insert(0, "123", 0), - Insert(1, "456", 0), - TransformPrime(0, 1), - DocComposePrime(0, 1), - Insert(1, "78", 0), - DocComposePrime(1, 0), - AssertDocJson(0, r#"[{"insert":"123456"}]"#), - AssertDocJson(1, r#"[{"insert":"12378456"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Insert(1, "456", 0), + TransformPrime(0, 1), + DocComposePrime(0, 1), + Insert(1, "78", 0), + DocComposePrime(1, 0), + AssertDocJson(0, r#"[{"insert":"123456"}]"#), + AssertDocJson(1, r#"[{"insert":"12378456"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn delta_invert_no_attribute_delta() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("123")); + let mut delta = DeltaTextOperations::default(); + delta.add(DeltaOperation::insert("123")); - let mut change = DeltaTextOperations::default(); - change.add(DeltaOperation::retain(3)); - change.add(DeltaOperation::insert("456")); - let undo = change.invert(&delta); + let mut change = DeltaTextOperations::default(); + change.add(DeltaOperation::retain(3)); + change.add(DeltaOperation::insert("456")); + let undo = change.invert(&delta); - let new_delta = delta.compose(&change).unwrap(); - let delta_after_undo = new_delta.compose(&undo).unwrap(); + let new_delta = delta.compose(&change).unwrap(); + let delta_after_undo = new_delta.compose(&undo).unwrap(); - assert_eq!(delta_after_undo, delta); + assert_eq!(delta_after_undo, delta); } #[test] fn delta_invert_no_attribute_delta2() { - let ops = vec![ - Insert(0, "123", 0), - Insert(1, "4567", 0), - Invert(0, 1), - AssertDocJson(0, r#"[{"insert":"123"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Insert(1, "4567", 0), + Invert(0, 1), + AssertDocJson(0, r#"[{"insert":"123"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn delta_invert_attribute_delta_with_no_attribute_delta() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":true}}]"#), - Insert(1, "4567", 0), - Invert(0, 1), - AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":true}}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Bold(0, Interval::new(0, 3), true), + AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":true}}]"#), + Insert(1, "4567", 0), + Invert(0, 1), + AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":true}}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn delta_invert_attribute_delta_with_no_attribute_delta2() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - Insert(0, "456", 3), - AssertDocJson( - 0, - r#"[ + let ops = vec![ + Insert(0, "123", 0), + Bold(0, Interval::new(0, 3), true), + Insert(0, "456", 3), + AssertDocJson( + 0, + r#"[ {"insert":"123456","attributes":{"bold":true}}] "#, - ), - Italic(0, Interval::new(2, 4), true), - AssertDocJson( - 0, - r#"[ + ), + Italic(0, Interval::new(2, 4), true), + AssertDocJson( + 0, + r#"[ {"insert":"12","attributes":{"bold":true}}, {"insert":"34","attributes":{"bold":true,"italic":true}}, {"insert":"56","attributes":{"bold":true}} ]"#, - ), - Insert(1, "abc", 0), - Invert(0, 1), - AssertDocJson( - 0, - r#"[ + ), + Insert(1, "abc", 0), + Invert(0, 1), + AssertDocJson( + 0, + r#"[ {"insert":"12","attributes":{"bold":true}}, {"insert":"34","attributes":{"bold":true,"italic":true}}, {"insert":"56","attributes":{"bold":true}} ]"#, - ), - ]; - TestBuilder::new().run_scripts::(ops); + ), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn delta_invert_no_attribute_delta_with_attribute_delta() { - let ops = vec![ - Insert(0, "123", 0), - Insert(1, "4567", 0), - Bold(1, Interval::new(0, 3), true), - AssertDocJson(1, r#"[{"insert":"456","attributes":{"bold":true}},{"insert":"7"}]"#), - Invert(0, 1), - AssertDocJson(0, r#"[{"insert":"123"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Insert(1, "4567", 0), + Bold(1, Interval::new(0, 3), true), + AssertDocJson( + 1, + r#"[{"insert":"456","attributes":{"bold":true}},{"insert":"7"}]"#, + ), + Invert(0, 1), + AssertDocJson(0, r#"[{"insert":"123"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn delta_invert_no_attribute_delta_with_attribute_delta2() { - let ops = vec![ - Insert(0, "123", 0), - AssertDocJson(0, r#"[{"insert":"123"}]"#), - Insert(1, "abc", 0), - Bold(1, Interval::new(0, 3), true), - Insert(1, "d", 3), - Italic(1, Interval::new(1, 3), true), - AssertDocJson( - 1, - r#"[{"insert":"a","attributes":{"bold":true}},{"insert":"bc","attributes":{"bold":true,"italic":true}},{"insert":"d","attributes":{"bold":true}}]"#, - ), - Invert(0, 1), - AssertDocJson(0, r#"[{"insert":"123"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + AssertDocJson(0, r#"[{"insert":"123"}]"#), + Insert(1, "abc", 0), + Bold(1, Interval::new(0, 3), true), + Insert(1, "d", 3), + Italic(1, Interval::new(1, 3), true), + AssertDocJson( + 1, + r#"[{"insert":"a","attributes":{"bold":true}},{"insert":"bc","attributes":{"bold":true,"italic":true}},{"insert":"d","attributes":{"bold":true}}]"#, + ), + Invert(0, 1), + AssertDocJson(0, r#"[{"insert":"123"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn delta_invert_attribute_delta_with_attribute_delta() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - Insert(0, "456", 3), - AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":true}}]"#), - Italic(0, Interval::new(2, 4), true), - AssertDocJson( - 0, - r#"[ + let ops = vec![ + Insert(0, "123", 0), + Bold(0, Interval::new(0, 3), true), + Insert(0, "456", 3), + AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":true}}]"#), + Italic(0, Interval::new(2, 4), true), + AssertDocJson( + 0, + r#"[ {"insert":"12","attributes":{"bold":true}}, {"insert":"34","attributes":{"bold":true,"italic":true}}, {"insert":"56","attributes":{"bold":true}} ]"#, - ), - Insert(1, "abc", 0), - Bold(1, Interval::new(0, 3), true), - Insert(1, "d", 3), - Italic(1, Interval::new(1, 3), true), - AssertDocJson( - 1, - r#"[ + ), + Insert(1, "abc", 0), + Bold(1, Interval::new(0, 3), true), + Insert(1, "d", 3), + Italic(1, Interval::new(1, 3), true), + AssertDocJson( + 1, + r#"[ {"insert":"a","attributes":{"bold":true}}, {"insert":"bc","attributes":{"bold":true,"italic":true}}, {"insert":"d","attributes":{"bold":true}} ]"#, - ), - Invert(0, 1), - AssertDocJson( - 0, - r#"[ + ), + Invert(0, 1), + AssertDocJson( + 0, + r#"[ {"insert":"12","attributes":{"bold":true}}, {"insert":"34","attributes":{"bold":true,"italic":true}}, {"insert":"56","attributes":{"bold":true}} ]"#, - ), - ]; - TestBuilder::new().run_scripts::(ops); + ), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn delta_compose_str() { - let ops = vec![ - Insert(0, "1", 0), - Insert(0, "2", 1), - AssertDocJson(0, r#"[{"insert":"12\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "1", 0), + Insert(0, "2", 1), + AssertDocJson(0, r#"[{"insert":"12\n"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] #[should_panic] fn delta_compose_with_missing_delta() { - let ops = vec![ - Insert(0, "123", 0), - Insert(0, "4", 3), - DocComposeDelta(1, 0), - AssertDocJson(0, r#"[{"insert":"1234\n"}]"#), - AssertStr(1, r#"4\n"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Insert(0, "4", 3), + DocComposeDelta(1, 0), + AssertDocJson(0, r#"[{"insert":"1234\n"}]"#), + AssertStr(1, r#"4\n"#), + ]; + TestBuilder::new().run_scripts::(ops); } diff --git a/frontend/rust-lib/flowy-document/tests/editor/serde_test.rs b/frontend/rust-lib/flowy-document/tests/editor/serde_test.rs index 9341f7222a..ce38ac7058 100644 --- a/frontend/rust-lib/flowy-document/tests/editor/serde_test.rs +++ b/frontend/rust-lib/flowy-document/tests/editor/serde_test.rs @@ -1,113 +1,118 @@ use flowy_client_sync::client_document::{ClientDocument, EmptyDocument}; use lib_ot::text_delta::DeltaTextOperation; use lib_ot::{ - core::*, - text_delta::{BuildInTextAttribute, DeltaTextOperations}, + core::*, + text_delta::{BuildInTextAttribute, DeltaTextOperations}, }; #[test] fn operation_insert_serialize_test() { - let attributes = AttributeBuilder::new() - .insert("bold", true) - .insert("italic", true) - .build(); - let operation = DeltaOperation::insert_with_attributes("123", attributes); - let json = serde_json::to_string(&operation).unwrap(); - eprintln!("{}", json); + let attributes = AttributeBuilder::new() + .insert("bold", true) + .insert("italic", true) + .build(); + let operation = DeltaOperation::insert_with_attributes("123", attributes); + let json = serde_json::to_string(&operation).unwrap(); + eprintln!("{}", json); - let insert_op: DeltaTextOperation = serde_json::from_str(&json).unwrap(); - assert_eq!(insert_op, operation); + let insert_op: DeltaTextOperation = serde_json::from_str(&json).unwrap(); + assert_eq!(insert_op, operation); } #[test] fn operation_retain_serialize_test() { - let operation = DeltaOperation::Retain(12.into()); - let json = serde_json::to_string(&operation).unwrap(); - eprintln!("{}", json); - let insert_op: DeltaTextOperation = serde_json::from_str(&json).unwrap(); - assert_eq!(insert_op, operation); + let operation = DeltaOperation::Retain(12.into()); + let json = serde_json::to_string(&operation).unwrap(); + eprintln!("{}", json); + let insert_op: DeltaTextOperation = serde_json::from_str(&json).unwrap(); + assert_eq!(insert_op, operation); } #[test] fn operation_delete_serialize_test() { - let operation = DeltaTextOperation::Delete(2); - let json = serde_json::to_string(&operation).unwrap(); - let insert_op: DeltaTextOperation = serde_json::from_str(&json).unwrap(); - assert_eq!(insert_op, operation); + let operation = DeltaTextOperation::Delete(2); + let json = serde_json::to_string(&operation).unwrap(); + let insert_op: DeltaTextOperation = serde_json::from_str(&json).unwrap(); + assert_eq!(insert_op, operation); } #[test] fn attributes_serialize_test() { - let attributes = AttributeBuilder::new() - .insert_entry(BuildInTextAttribute::Bold(true)) - .insert_entry(BuildInTextAttribute::Italic(true)) - .build(); - let retain = DeltaOperation::insert_with_attributes("123", attributes); + let attributes = AttributeBuilder::new() + .insert_entry(BuildInTextAttribute::Bold(true)) + .insert_entry(BuildInTextAttribute::Italic(true)) + .build(); + let retain = DeltaOperation::insert_with_attributes("123", attributes); - let json = serde_json::to_string(&retain).unwrap(); - eprintln!("{}", json); + let json = serde_json::to_string(&retain).unwrap(); + eprintln!("{}", json); } #[test] fn delta_serialize_multi_attribute_test() { - let mut delta = DeltaOperations::default(); + let mut delta = DeltaOperations::default(); - let attributes = AttributeBuilder::new() - .insert_entry(BuildInTextAttribute::Bold(true)) - .insert_entry(BuildInTextAttribute::Italic(true)) - .build(); - let retain = DeltaOperation::insert_with_attributes("123", attributes); + let attributes = AttributeBuilder::new() + .insert_entry(BuildInTextAttribute::Bold(true)) + .insert_entry(BuildInTextAttribute::Italic(true)) + .build(); + let retain = DeltaOperation::insert_with_attributes("123", attributes); - delta.add(retain); - delta.add(DeltaOperation::Retain(5.into())); - delta.add(DeltaOperation::Delete(3)); + delta.add(retain); + delta.add(DeltaOperation::Retain(5.into())); + delta.add(DeltaOperation::Delete(3)); - let json = serde_json::to_string(&delta).unwrap(); - eprintln!("{}", json); + let json = serde_json::to_string(&delta).unwrap(); + eprintln!("{}", json); - let delta_from_json = DeltaOperations::from_json(&json).unwrap(); - assert_eq!(delta_from_json, delta); + let delta_from_json = DeltaOperations::from_json(&json).unwrap(); + assert_eq!(delta_from_json, delta); } #[test] fn delta_deserialize_test() { - let json = r#"[ + let json = r#"[ {"retain":2,"attributes":{"italic":true}}, {"retain":2,"attributes":{"italic":123}}, {"retain":2,"attributes":{"italic":true,"bold":true}}, {"retain":2,"attributes":{"italic":true,"bold":true}} ]"#; - let delta = DeltaTextOperations::from_json(json).unwrap(); - eprintln!("{}", delta); + let delta = DeltaTextOperations::from_json(json).unwrap(); + eprintln!("{}", delta); } #[test] fn delta_deserialize_null_test() { - let json = r#"[ + let json = r#"[ {"retain":7,"attributes":{"bold":null}} ]"#; - let delta1 = DeltaTextOperations::from_json(json).unwrap(); + let delta1 = DeltaTextOperations::from_json(json).unwrap(); - let mut attribute = BuildInTextAttribute::Bold(true); - attribute.clear(); + let mut attribute = BuildInTextAttribute::Bold(true); + attribute.clear(); - let delta2 = DeltaOperationBuilder::new() - .retain_with_attributes(7, attribute.into()) - .build(); + let delta2 = DeltaOperationBuilder::new() + .retain_with_attributes(7, attribute.into()) + .build(); - assert_eq!(delta2.json_str(), r#"[{"retain":7,"attributes":{"bold":null}}]"#); - assert_eq!(delta1, delta2); + assert_eq!( + delta2.json_str(), + r#"[{"retain":7,"attributes":{"bold":null}}]"# + ); + assert_eq!(delta1, delta2); } #[test] fn document_insert_serde_test() { - let mut document = ClientDocument::new::(); - document.insert(0, "\n").unwrap(); - document.insert(0, "123").unwrap(); - let json = document.get_operations_json(); - assert_eq!(r#"[{"insert":"123\n"}]"#, json); - assert_eq!( - r#"[{"insert":"123\n"}]"#, - ClientDocument::from_json(&json).unwrap().get_operations_json() - ); + let mut document = ClientDocument::new::(); + document.insert(0, "\n").unwrap(); + document.insert(0, "123").unwrap(); + let json = document.get_operations_json(); + assert_eq!(r#"[{"insert":"123\n"}]"#, json); + assert_eq!( + r#"[{"insert":"123\n"}]"#, + ClientDocument::from_json(&json) + .unwrap() + .get_operations_json() + ); } diff --git a/frontend/rust-lib/flowy-document/tests/editor/undo_redo_test.rs b/frontend/rust-lib/flowy-document/tests/editor/undo_redo_test.rs index d188daea37..53d957527d 100644 --- a/frontend/rust-lib/flowy-document/tests/editor/undo_redo_test.rs +++ b/frontend/rust-lib/flowy-document/tests/editor/undo_redo_test.rs @@ -4,370 +4,386 @@ use lib_ot::core::{Interval, NEW_LINE, WHITESPACE}; #[test] fn history_insert_undo() { - let ops = vec![Insert(0, "123", 0), Undo(0), AssertDocJson(0, r#"[{"insert":"\n"}]"#)]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Undo(0), + AssertDocJson(0, r#"[{"insert":"\n"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn history_insert_undo_with_lagging() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - Insert(0, "456", 0), - Undo(0), - AssertDocJson(0, r#"[{"insert":"123\n"}]"#), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Wait(RECORD_THRESHOLD), + Insert(0, "456", 0), + Undo(0), + AssertDocJson(0, r#"[{"insert":"123\n"}]"#), + Undo(0), + AssertDocJson(0, r#"[{"insert":"\n"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn history_insert_redo() { - let ops = vec![ - Insert(0, "123", 0), - AssertDocJson(0, r#"[{"insert":"123\n"}]"#), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - Redo(0), - AssertDocJson(0, r#"[{"insert":"123\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + AssertDocJson(0, r#"[{"insert":"123\n"}]"#), + Undo(0), + AssertDocJson(0, r#"[{"insert":"\n"}]"#), + Redo(0), + AssertDocJson(0, r#"[{"insert":"123\n"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn history_insert_redo_with_lagging() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - Insert(0, "456", 3), - Wait(RECORD_THRESHOLD), - AssertStr(0, "123456\n"), - AssertDocJson(0, r#"[{"insert":"123456\n"}]"#), - Undo(0), - AssertDocJson(0, r#"[{"insert":"123\n"}]"#), - Redo(0), - AssertDocJson(0, r#"[{"insert":"123456\n"}]"#), - Undo(0), - AssertDocJson(0, r#"[{"insert":"123\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Wait(RECORD_THRESHOLD), + Insert(0, "456", 3), + Wait(RECORD_THRESHOLD), + AssertStr(0, "123456\n"), + AssertDocJson(0, r#"[{"insert":"123456\n"}]"#), + Undo(0), + AssertDocJson(0, r#"[{"insert":"123\n"}]"#), + Redo(0), + AssertDocJson(0, r#"[{"insert":"123456\n"}]"#), + Undo(0), + AssertDocJson(0, r#"[{"insert":"123\n"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn history_bold_undo() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Bold(0, Interval::new(0, 3), true), + Undo(0), + AssertDocJson(0, r#"[{"insert":"\n"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn history_bold_undo_with_lagging() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - Bold(0, Interval::new(0, 3), true), - Undo(0), - AssertDocJson(0, r#"[{"insert":"123\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Wait(RECORD_THRESHOLD), + Bold(0, Interval::new(0, 3), true), + Undo(0), + AssertDocJson(0, r#"[{"insert":"123\n"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn history_bold_redo() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - Redo(0), - AssertDocJson(0, r#" [{"insert":"123","attributes":{"bold":true}},{"insert":"\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Bold(0, Interval::new(0, 3), true), + Undo(0), + AssertDocJson(0, r#"[{"insert":"\n"}]"#), + Redo(0), + AssertDocJson( + 0, + r#" [{"insert":"123","attributes":{"bold":true}},{"insert":"\n"}]"#, + ), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn history_bold_redo_with_lagging() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - Bold(0, Interval::new(0, 3), true), - Undo(0), - AssertDocJson(0, r#"[{"insert":"123\n"}]"#), - Redo(0), - AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Wait(RECORD_THRESHOLD), + Bold(0, Interval::new(0, 3), true), + Undo(0), + AssertDocJson(0, r#"[{"insert":"123\n"}]"#), + Redo(0), + AssertDocJson( + 0, + r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"\n"}]"#, + ), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn history_delete_undo() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - AssertDocJson(0, r#"[{"insert":"123"}]"#), - Delete(0, Interval::new(0, 3)), - AssertDocJson(0, r#"[]"#), - Undo(0), - AssertDocJson(0, r#"[{"insert":"123"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Wait(RECORD_THRESHOLD), + AssertDocJson(0, r#"[{"insert":"123"}]"#), + Delete(0, Interval::new(0, 3)), + AssertDocJson(0, r#"[]"#), + Undo(0), + AssertDocJson(0, r#"[{"insert":"123"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn history_delete_undo_2() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - Delete(0, Interval::new(0, 1)), - AssertDocJson( - 0, - r#"[ + let ops = vec![ + Insert(0, "123", 0), + Bold(0, Interval::new(0, 3), true), + Delete(0, Interval::new(0, 1)), + AssertDocJson( + 0, + r#"[ {"insert":"23","attributes":{"bold":true}}, {"insert":"\n"}] "#, - ), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + ), + Undo(0), + AssertDocJson(0, r#"[{"insert":"\n"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn history_delete_undo_with_lagging() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - Bold(0, Interval::new(0, 3), true), - Wait(RECORD_THRESHOLD), - Delete(0, Interval::new(0, 1)), - AssertDocJson( - 0, - r#"[ + let ops = vec![ + Insert(0, "123", 0), + Wait(RECORD_THRESHOLD), + Bold(0, Interval::new(0, 3), true), + Wait(RECORD_THRESHOLD), + Delete(0, Interval::new(0, 1)), + AssertDocJson( + 0, + r#"[ {"insert":"23","attributes":{"bold":true}}, {"insert":"\n"}] "#, - ), - Undo(0), - AssertDocJson( - 0, - r#"[ + ), + Undo(0), + AssertDocJson( + 0, + r#"[ {"insert":"123","attributes":{"bold":true}}, {"insert":"\n"}] "#, - ), - ]; - TestBuilder::new().run_scripts::(ops); + ), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn history_delete_redo() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - Delete(0, Interval::new(0, 3)), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - Undo(0), - Redo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + let ops = vec![ + Insert(0, "123", 0), + Wait(RECORD_THRESHOLD), + Delete(0, Interval::new(0, 3)), + AssertDocJson(0, r#"[{"insert":"\n"}]"#), + Undo(0), + Redo(0), + AssertDocJson(0, r#"[{"insert":"\n"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn history_replace_undo() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - Replace(0, Interval::new(0, 2), "ab"), - AssertDocJson( - 0, - r#"[ + let ops = vec![ + Insert(0, "123", 0), + Bold(0, Interval::new(0, 3), true), + Replace(0, Interval::new(0, 2), "ab"), + AssertDocJson( + 0, + r#"[ {"insert":"ab"}, {"insert":"3","attributes":{"bold":true}},{"insert":"\n"}] "#, - ), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + ), + Undo(0), + AssertDocJson(0, r#"[{"insert":"\n"}]"#), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn history_replace_undo_with_lagging() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - Bold(0, Interval::new(0, 3), true), - Wait(RECORD_THRESHOLD), - Replace(0, Interval::new(0, 2), "ab"), - AssertDocJson( - 0, - r#"[ + let ops = vec![ + Insert(0, "123", 0), + Wait(RECORD_THRESHOLD), + Bold(0, Interval::new(0, 3), true), + Wait(RECORD_THRESHOLD), + Replace(0, Interval::new(0, 2), "ab"), + AssertDocJson( + 0, + r#"[ {"insert":"ab"}, {"insert":"3","attributes":{"bold":true}},{"insert":"\n"}] "#, - ), - Undo(0), - AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); + ), + Undo(0), + AssertDocJson( + 0, + r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"\n"}]"#, + ), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn history_replace_redo() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - Replace(0, Interval::new(0, 2), "ab"), - Undo(0), - Redo(0), - AssertDocJson( - 0, - r#"[ + let ops = vec![ + Insert(0, "123", 0), + Bold(0, Interval::new(0, 3), true), + Replace(0, Interval::new(0, 2), "ab"), + Undo(0), + Redo(0), + AssertDocJson( + 0, + r#"[ {"insert":"ab"}, {"insert":"3","attributes":{"bold":true}},{"insert":"\n"}] "#, - ), - ]; - TestBuilder::new().run_scripts::(ops); + ), + ]; + TestBuilder::new().run_scripts::(ops); } #[test] fn history_header_added_undo() { - let ops = vec![ - Insert(0, "123456", 0), - Header(0, Interval::new(0, 6), 1), - Insert(0, "\n", 3), - Insert(0, "\n", 4), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - Redo(0), - AssertDocJson( - 0, - r#"[{"insert":"123"},{"insert":"\n\n","attributes":{"header":1}},{"insert":"456"},{"insert":"\n","attributes":{"header":1}}]"#, - ), - ]; + let ops = vec![ + Insert(0, "123456", 0), + Header(0, Interval::new(0, 6), 1), + Insert(0, "\n", 3), + Insert(0, "\n", 4), + Undo(0), + AssertDocJson(0, r#"[{"insert":"\n"}]"#), + Redo(0), + AssertDocJson( + 0, + r#"[{"insert":"123"},{"insert":"\n\n","attributes":{"header":1}},{"insert":"456"},{"insert":"\n","attributes":{"header":1}}]"#, + ), + ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] fn history_link_added_undo() { - let site = "https://appflowy.io"; - let ops = vec![ - Insert(0, site, 0), - Wait(RECORD_THRESHOLD), - Link(0, Interval::new(0, site.len()), site), - Undo(0), - AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#), - Redo(0), - AssertDocJson( - 0, - r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#, - ), - ]; + let site = "https://appflowy.io"; + let ops = vec![ + Insert(0, site, 0), + Wait(RECORD_THRESHOLD), + Link(0, Interval::new(0, site.len()), site), + Undo(0), + AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#), + Redo(0), + AssertDocJson( + 0, + r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#, + ), + ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] fn history_link_auto_format_undo_with_lagging() { - let site = "https://appflowy.io"; - let ops = vec![ - Insert(0, site, 0), - AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#), - Wait(RECORD_THRESHOLD), - Insert(0, WHITESPACE, site.len()), - AssertDocJson( - 0, - r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io/"}},{"insert":" \n"}]"#, - ), - Undo(0), - AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#), - ]; + let site = "https://appflowy.io"; + let ops = vec![ + Insert(0, site, 0), + AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#), + Wait(RECORD_THRESHOLD), + Insert(0, WHITESPACE, site.len()), + AssertDocJson( + 0, + r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io/"}},{"insert":" \n"}]"#, + ), + Undo(0), + AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#), + ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] fn history_bullet_undo() { - let ops = vec![ - Insert(0, "1", 0), - Bullet(0, Interval::new(0, 1), true), - Insert(0, NEW_LINE, 1), - Insert(0, "2", 2), - AssertDocJson( - 0, - r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - Redo(0), - AssertDocJson( - 0, - r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - ]; + let ops = vec![ + Insert(0, "1", 0), + Bullet(0, Interval::new(0, 1), true), + Insert(0, NEW_LINE, 1), + Insert(0, "2", 2), + AssertDocJson( + 0, + r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, + ), + Undo(0), + AssertDocJson(0, r#"[{"insert":"\n"}]"#), + Redo(0), + AssertDocJson( + 0, + r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, + ), + ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] fn history_bullet_undo_with_lagging() { - let ops = vec![ - Insert(0, "1", 0), - Bullet(0, Interval::new(0, 1), true), - Wait(RECORD_THRESHOLD), - Insert(0, NEW_LINE, 1), - Insert(0, "2", 2), - Wait(RECORD_THRESHOLD), - AssertDocJson( - 0, - r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - Undo(0), - AssertDocJson(0, r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}}]"#), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - Redo(0), - Redo(0), - AssertDocJson( - 0, - r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - ]; + let ops = vec![ + Insert(0, "1", 0), + Bullet(0, Interval::new(0, 1), true), + Wait(RECORD_THRESHOLD), + Insert(0, NEW_LINE, 1), + Insert(0, "2", 2), + Wait(RECORD_THRESHOLD), + AssertDocJson( + 0, + r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, + ), + Undo(0), + AssertDocJson( + 0, + r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, + ), + Undo(0), + AssertDocJson(0, r#"[{"insert":"\n"}]"#), + Redo(0), + Redo(0), + AssertDocJson( + 0, + r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, + ), + ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] fn history_undo_attribute_on_merge_between_line() { - let ops = vec![ - Insert(0, "123456", 0), - Bullet(0, Interval::new(0, 6), true), - Wait(RECORD_THRESHOLD), - Insert(0, NEW_LINE, 3), - Wait(RECORD_THRESHOLD), - AssertDocJson( - 0, - r#"[{"insert":"123"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - Delete(0, Interval::new(3, 4)), // delete the newline - AssertDocJson( - 0, - r#"[{"insert":"123456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - Undo(0), - AssertDocJson( - 0, - r#"[{"insert":"123"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - ]; + let ops = vec![ + Insert(0, "123456", 0), + Bullet(0, Interval::new(0, 6), true), + Wait(RECORD_THRESHOLD), + Insert(0, NEW_LINE, 3), + Wait(RECORD_THRESHOLD), + AssertDocJson( + 0, + r#"[{"insert":"123"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, + ), + Delete(0, Interval::new(3, 4)), // delete the newline + AssertDocJson( + 0, + r#"[{"insert":"123456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, + ), + Undo(0), + AssertDocJson( + 0, + r#"[{"insert":"123"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, + ), + ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } diff --git a/frontend/rust-lib/flowy-document/tests/new_document/document_compose_test.rs b/frontend/rust-lib/flowy-document/tests/new_document/document_compose_test.rs index 4480623fbf..f6070751dd 100644 --- a/frontend/rust-lib/flowy-document/tests/new_document/document_compose_test.rs +++ b/frontend/rust-lib/flowy-document/tests/new_document/document_compose_test.rs @@ -3,22 +3,22 @@ use crate::new_document::script::EditScript::*; #[tokio::test] async fn document_insert_h1_style_test() { - let scripts = vec![ - ComposeTransactionStr { - transaction: r#"{"operations":[{"op":"update_text","path":[0,0],"delta":[{"insert":"/"}],"inverted":[{"delete":1}]}],"after_selection":{"start":{"path":[0,0],"offset":1},"end":{"path":[0,0],"offset":1}},"before_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}}}"#, - }, - AssertContent { - expected: r#"{"document":{"type":"editor","children":[{"type":"text","delta":[{"insert":"/"}]}]}}"#, - }, - ComposeTransactionStr { - transaction: r#"{"operations":[{"op":"update_text","path":[0,0],"delta":[{"delete":1}],"inverted":[{"insert":"/"}]}],"after_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}},"before_selection":{"start":{"path":[0,0],"offset":1},"end":{"path":[0,0],"offset":1}}}"#, - }, - ComposeTransactionStr { - transaction: r#"{"operations":[{"op":"update","path":[0,0],"attributes":{"subtype":"heading","heading":"h1"},"oldAttributes":{"subtype":null,"heading":null}}],"after_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}},"before_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}}}"#, - }, - AssertContent { - expected: r#"{"document":{"type":"editor","children":[{"type":"text","attributes":{"subtype":"heading","heading":"h1"}}]}}"#, - }, - ]; - DocumentEditorTest::new().await.run_scripts(scripts).await; + let scripts = vec![ + ComposeTransactionStr { + transaction: r#"{"operations":[{"op":"update_text","path":[0,0],"delta":[{"insert":"/"}],"inverted":[{"delete":1}]}],"after_selection":{"start":{"path":[0,0],"offset":1},"end":{"path":[0,0],"offset":1}},"before_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}}}"#, + }, + AssertContent { + expected: r#"{"document":{"type":"editor","children":[{"type":"text","delta":[{"insert":"/"}]}]}}"#, + }, + ComposeTransactionStr { + transaction: r#"{"operations":[{"op":"update_text","path":[0,0],"delta":[{"delete":1}],"inverted":[{"insert":"/"}]}],"after_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}},"before_selection":{"start":{"path":[0,0],"offset":1},"end":{"path":[0,0],"offset":1}}}"#, + }, + ComposeTransactionStr { + transaction: r#"{"operations":[{"op":"update","path":[0,0],"attributes":{"subtype":"heading","heading":"h1"},"oldAttributes":{"subtype":null,"heading":null}}],"after_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}},"before_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}}}"#, + }, + AssertContent { + expected: r#"{"document":{"type":"editor","children":[{"type":"text","attributes":{"subtype":"heading","heading":"h1"}}]}}"#, + }, + ]; + DocumentEditorTest::new().await.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-document/tests/new_document/script.rs b/frontend/rust-lib/flowy-document/tests/new_document/script.rs index 90a48e1e5e..d3ae48d64b 100644 --- a/frontend/rust-lib/flowy-document/tests/new_document/script.rs +++ b/frontend/rust-lib/flowy-document/tests/new_document/script.rs @@ -8,109 +8,123 @@ use lib_ot::text_delta::DeltaTextOperations; use std::sync::Arc; pub enum EditScript { - InsertText { - path: Path, - delta: DeltaTextOperations, - }, - UpdateText { - path: Path, - delta: DeltaTextOperations, - }, - #[allow(dead_code)] - ComposeTransaction { - transaction: Transaction, - }, - ComposeTransactionStr { - transaction: &'static str, - }, - Delete { - path: Path, - }, - AssertContent { - expected: &'static str, - }, - AssertPrettyContent { - expected: &'static str, - }, + InsertText { + path: Path, + delta: DeltaTextOperations, + }, + UpdateText { + path: Path, + delta: DeltaTextOperations, + }, + #[allow(dead_code)] + ComposeTransaction { + transaction: Transaction, + }, + ComposeTransactionStr { + transaction: &'static str, + }, + Delete { + path: Path, + }, + AssertContent { + expected: &'static str, + }, + AssertPrettyContent { + expected: &'static str, + }, } pub struct DocumentEditorTest { - pub sdk: FlowySDKTest, - pub editor: Arc, + pub sdk: FlowySDKTest, + pub editor: Arc, } impl DocumentEditorTest { - pub async fn new() -> Self { - let version = DocumentVersionPB::V1; - let sdk = FlowySDKTest::new(version.clone()); - let _ = sdk.init_user().await; + pub async fn new() -> Self { + let version = DocumentVersionPB::V1; + let sdk = FlowySDKTest::new(version.clone()); + let _ = sdk.init_user().await; - let test = ViewTest::new_document_view(&sdk).await; - let document_editor = sdk.document_manager.open_document_editor(&test.view.id).await.unwrap(); - let editor = match document_editor.as_any().downcast_ref::>() { - None => panic!(), - Some(editor) => editor.clone(), + let test = ViewTest::new_document_view(&sdk).await; + let document_editor = sdk + .document_manager + .open_document_editor(&test.view.id) + .await + .unwrap(); + let editor = match document_editor + .as_any() + .downcast_ref::>() + { + None => panic!(), + Some(editor) => editor.clone(), + }; + + Self { sdk, editor } + } + + pub async fn run_scripts(&self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + async fn run_script(&self, script: EditScript) { + match script { + EditScript::InsertText { path, delta } => { + let node_data = NodeDataBuilder::new("text").insert_delta(delta).build(); + let operation = NodeOperation::Insert { + path, + nodes: vec![node_data], }; + self + .editor + .apply_transaction(Transaction::from_operations(vec![operation])) + .await + .unwrap(); + }, + EditScript::UpdateText { path, delta } => { + let inverted = delta.invert_str(""); + let changeset = Changeset::Delta { delta, inverted }; + let operation = NodeOperation::Update { path, changeset }; + self + .editor + .apply_transaction(Transaction::from_operations(vec![operation])) + .await + .unwrap(); + }, + EditScript::ComposeTransaction { transaction } => { + self.editor.apply_transaction(transaction).await.unwrap(); + }, + EditScript::ComposeTransactionStr { transaction } => { + let document_transaction = + serde_json::from_str::(transaction).unwrap(); + let transaction: Transaction = document_transaction.into(); + self.editor.apply_transaction(transaction).await.unwrap(); + }, + EditScript::Delete { path } => { + let operation = NodeOperation::Delete { + path, + nodes: vec![], + }; + self + .editor + .apply_transaction(Transaction::from_operations(vec![operation])) + .await + .unwrap(); + }, + EditScript::AssertContent { expected } => { + // + let content = self.editor.get_content(false).await.unwrap(); + let expected_document: Document = serde_json::from_str(expected).unwrap(); + let expected = serde_json::to_string(&expected_document).unwrap(); - Self { sdk, editor } - } - - pub async fn run_scripts(&self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - async fn run_script(&self, script: EditScript) { - match script { - EditScript::InsertText { path, delta } => { - let node_data = NodeDataBuilder::new("text").insert_delta(delta).build(); - let operation = NodeOperation::Insert { - path, - nodes: vec![node_data], - }; - self.editor - .apply_transaction(Transaction::from_operations(vec![operation])) - .await - .unwrap(); - } - EditScript::UpdateText { path, delta } => { - let inverted = delta.invert_str(""); - let changeset = Changeset::Delta { delta, inverted }; - let operation = NodeOperation::Update { path, changeset }; - self.editor - .apply_transaction(Transaction::from_operations(vec![operation])) - .await - .unwrap(); - } - EditScript::ComposeTransaction { transaction } => { - self.editor.apply_transaction(transaction).await.unwrap(); - } - EditScript::ComposeTransactionStr { transaction } => { - let document_transaction = serde_json::from_str::(transaction).unwrap(); - let transaction: Transaction = document_transaction.into(); - self.editor.apply_transaction(transaction).await.unwrap(); - } - EditScript::Delete { path } => { - let operation = NodeOperation::Delete { path, nodes: vec![] }; - self.editor - .apply_transaction(Transaction::from_operations(vec![operation])) - .await - .unwrap(); - } - EditScript::AssertContent { expected } => { - // - let content = self.editor.get_content(false).await.unwrap(); - let expected_document: Document = serde_json::from_str(expected).unwrap(); - let expected = serde_json::to_string(&expected_document).unwrap(); - - assert_eq!(content, expected); - } - EditScript::AssertPrettyContent { expected } => { - // - let content = self.editor.get_content(true).await.unwrap(); - assert_eq!(content, expected); - } - } + assert_eq!(content, expected); + }, + EditScript::AssertPrettyContent { expected } => { + // + let content = self.editor.get_content(true).await.unwrap(); + assert_eq!(content, expected); + }, } + } } diff --git a/frontend/rust-lib/flowy-document/tests/new_document/test.rs b/frontend/rust-lib/flowy-document/tests/new_document/test.rs index c54c676a8f..593c7a461e 100644 --- a/frontend/rust-lib/flowy-document/tests/new_document/test.rs +++ b/frontend/rust-lib/flowy-document/tests/new_document/test.rs @@ -5,16 +5,18 @@ use lib_ot::text_delta::DeltaTextOperationBuilder; #[tokio::test] async fn document_initialize_test() { - let scripts = vec![AssertContent { - expected: r#"{"document":{"type":"editor","children":[{"type":"text"}]}}"#, - }]; - DocumentEditorTest::new().await.run_scripts(scripts).await; + let scripts = vec![AssertContent { + expected: r#"{"document":{"type":"editor","children":[{"type":"text"}]}}"#, + }]; + DocumentEditorTest::new().await.run_scripts(scripts).await; } #[tokio::test] async fn document_insert_text_test() { - let delta = DeltaTextOperationBuilder::new().insert("Hello world").build(); - let expected = r#"{ + let delta = DeltaTextOperationBuilder::new() + .insert("Hello world") + .build(); + let expected = r#"{ "document": { "type": "editor", "children": [ @@ -32,27 +34,29 @@ async fn document_insert_text_test() { ] } }"#; - let scripts = vec![ - InsertText { - path: vec![0, 0].into(), - delta, - }, - AssertPrettyContent { expected }, - ]; - DocumentEditorTest::new().await.run_scripts(scripts).await; + let scripts = vec![ + InsertText { + path: vec![0, 0].into(), + delta, + }, + AssertPrettyContent { expected }, + ]; + DocumentEditorTest::new().await.run_scripts(scripts).await; } #[tokio::test] async fn document_update_text_test() { - let test = DocumentEditorTest::new().await; - let hello_world = "Hello world".to_string(); - let scripts = vec![ - UpdateText { - path: vec![0, 0].into(), - delta: DeltaTextOperationBuilder::new().insert(&hello_world).build(), - }, - AssertPrettyContent { - expected: r#"{ + let test = DocumentEditorTest::new().await; + let hello_world = "Hello world".to_string(); + let scripts = vec![ + UpdateText { + path: vec![0, 0].into(), + delta: DeltaTextOperationBuilder::new() + .insert(&hello_world) + .build(), + }, + AssertPrettyContent { + expected: r#"{ "document": { "type": "editor", "children": [ @@ -67,21 +71,21 @@ async fn document_update_text_test() { ] } }"#, - }, - ]; + }, + ]; - test.run_scripts(scripts).await; + test.run_scripts(scripts).await; - let scripts = vec![ - UpdateText { - path: vec![0, 0].into(), - delta: DeltaTextOperationBuilder::new() - .retain(hello_world.len()) - .insert(", AppFlowy") - .build(), - }, - AssertPrettyContent { - expected: r#"{ + let scripts = vec![ + UpdateText { + path: vec![0, 0].into(), + delta: DeltaTextOperationBuilder::new() + .retain(hello_world.len()) + .insert(", AppFlowy") + .build(), + }, + AssertPrettyContent { + expected: r#"{ "document": { "type": "editor", "children": [ @@ -96,14 +100,14 @@ async fn document_update_text_test() { ] } }"#, - }, - ]; - test.run_scripts(scripts).await; + }, + ]; + test.run_scripts(scripts).await; } #[tokio::test] async fn document_delete_text_test() { - let expected = r#"{ + let expected = r#"{ "document": { "type": "editor", "children": [ @@ -118,39 +122,43 @@ async fn document_delete_text_test() { ] } }"#; - let hello_world = "Hello world".to_string(); - let scripts = vec![ - UpdateText { - path: vec![0, 0].into(), - delta: DeltaTextOperationBuilder::new().insert(&hello_world).build(), - }, - UpdateText { - path: vec![0, 0].into(), - delta: DeltaTextOperationBuilder::new().retain(5).delete(6).build(), - }, - AssertPrettyContent { expected }, - ]; + let hello_world = "Hello world".to_string(); + let scripts = vec![ + UpdateText { + path: vec![0, 0].into(), + delta: DeltaTextOperationBuilder::new() + .insert(&hello_world) + .build(), + }, + UpdateText { + path: vec![0, 0].into(), + delta: DeltaTextOperationBuilder::new().retain(5).delete(6).build(), + }, + AssertPrettyContent { expected }, + ]; - DocumentEditorTest::new().await.run_scripts(scripts).await; + DocumentEditorTest::new().await.run_scripts(scripts).await; } #[tokio::test] async fn document_delete_node_test() { - let scripts = vec![ - UpdateText { - path: vec![0, 0].into(), - delta: DeltaTextOperationBuilder::new().insert("Hello world").build(), - }, - AssertContent { - expected: r#"{"document":{"type":"editor","children":[{"type":"text","delta":[{"insert":"Hello world"}]}]}}"#, - }, - Delete { - path: vec![0, 0].into(), - }, - AssertContent { - expected: r#"{"document":{"type":"editor"}}"#, - }, - ]; + let scripts = vec![ + UpdateText { + path: vec![0, 0].into(), + delta: DeltaTextOperationBuilder::new() + .insert("Hello world") + .build(), + }, + AssertContent { + expected: r#"{"document":{"type":"editor","children":[{"type":"text","delta":[{"insert":"Hello world"}]}]}}"#, + }, + Delete { + path: vec![0, 0].into(), + }, + AssertContent { + expected: r#"{"document":{"type":"editor"}}"#, + }, + ]; - DocumentEditorTest::new().await.run_scripts(scripts).await; + DocumentEditorTest::new().await.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-document/tests/old_document/old_document_test.rs b/frontend/rust-lib/flowy-document/tests/old_document/old_document_test.rs index f910b2bb2f..6daedc2f8d 100644 --- a/frontend/rust-lib/flowy-document/tests/old_document/old_document_test.rs +++ b/frontend/rust-lib/flowy-document/tests/old_document/old_document_test.rs @@ -4,102 +4,126 @@ use lib_ot::core::{count_utf16_code_units, Interval}; #[tokio::test] async fn text_block_sync_current_rev_id_check() { - let scripts = vec![ - InsertText("1", 0), - AssertCurrentRevId(1), - InsertText("2", 1), - AssertCurrentRevId(2), - InsertText("3", 2), - AssertCurrentRevId(3), - AssertNextSyncRevId(None), - AssertJson(r#"[{"insert":"123\n"}]"#), - ]; - DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; + let scripts = vec![ + InsertText("1", 0), + AssertCurrentRevId(1), + InsertText("2", 1), + AssertCurrentRevId(2), + InsertText("3", 2), + AssertCurrentRevId(3), + AssertNextSyncRevId(None), + AssertJson(r#"[{"insert":"123\n"}]"#), + ]; + DeltaDocumentEditorTest::new() + .await + .run_scripts(scripts) + .await; } #[tokio::test] async fn text_block_sync_state_check() { - let scripts = vec![ - InsertText("1", 0), - InsertText("2", 1), - InsertText("3", 2), - AssertRevisionState(1, RevisionState::Ack), - AssertRevisionState(2, RevisionState::Ack), - AssertRevisionState(3, RevisionState::Ack), - AssertJson(r#"[{"insert":"123\n"}]"#), - ]; - DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; + let scripts = vec![ + InsertText("1", 0), + InsertText("2", 1), + InsertText("3", 2), + AssertRevisionState(1, RevisionState::Ack), + AssertRevisionState(2, RevisionState::Ack), + AssertRevisionState(3, RevisionState::Ack), + AssertJson(r#"[{"insert":"123\n"}]"#), + ]; + DeltaDocumentEditorTest::new() + .await + .run_scripts(scripts) + .await; } #[tokio::test] async fn text_block_sync_insert_test() { - let scripts = vec![ - InsertText("1", 0), - InsertText("2", 1), - InsertText("3", 2), - AssertJson(r#"[{"insert":"123\n"}]"#), - AssertNextSyncRevId(None), - ]; - DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; + let scripts = vec![ + InsertText("1", 0), + InsertText("2", 1), + InsertText("3", 2), + AssertJson(r#"[{"insert":"123\n"}]"#), + AssertNextSyncRevId(None), + ]; + DeltaDocumentEditorTest::new() + .await + .run_scripts(scripts) + .await; } #[tokio::test] async fn text_block_sync_insert_in_chinese() { - let s = "好".to_owned(); - let offset = count_utf16_code_units(&s); - let scripts = vec![ - InsertText("你", 0), - InsertText("好", offset), - AssertJson(r#"[{"insert":"你好\n"}]"#), - ]; - DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; + let s = "好".to_owned(); + let offset = count_utf16_code_units(&s); + let scripts = vec![ + InsertText("你", 0), + InsertText("好", offset), + AssertJson(r#"[{"insert":"你好\n"}]"#), + ]; + DeltaDocumentEditorTest::new() + .await + .run_scripts(scripts) + .await; } #[tokio::test] async fn text_block_sync_insert_with_emoji() { - let s = "😁".to_owned(); - let offset = count_utf16_code_units(&s); - let scripts = vec![ - InsertText("😁", 0), - InsertText("☺️", offset), - AssertJson(r#"[{"insert":"😁☺️\n"}]"#), - ]; - DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; + let s = "😁".to_owned(); + let offset = count_utf16_code_units(&s); + let scripts = vec![ + InsertText("😁", 0), + InsertText("☺️", offset), + AssertJson(r#"[{"insert":"😁☺️\n"}]"#), + ]; + DeltaDocumentEditorTest::new() + .await + .run_scripts(scripts) + .await; } #[tokio::test] async fn text_block_sync_delete_in_english() { - let scripts = vec![ - InsertText("1", 0), - InsertText("2", 1), - InsertText("3", 2), - Delete(Interval::new(0, 2)), - AssertJson(r#"[{"insert":"3\n"}]"#), - ]; - DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; + let scripts = vec![ + InsertText("1", 0), + InsertText("2", 1), + InsertText("3", 2), + Delete(Interval::new(0, 2)), + AssertJson(r#"[{"insert":"3\n"}]"#), + ]; + DeltaDocumentEditorTest::new() + .await + .run_scripts(scripts) + .await; } #[tokio::test] async fn text_block_sync_delete_in_chinese() { - let s = "好".to_owned(); - let offset = count_utf16_code_units(&s); - let scripts = vec![ - InsertText("你", 0), - InsertText("好", offset), - Delete(Interval::new(0, offset)), - AssertJson(r#"[{"insert":"好\n"}]"#), - ]; - DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; + let s = "好".to_owned(); + let offset = count_utf16_code_units(&s); + let scripts = vec![ + InsertText("你", 0), + InsertText("好", offset), + Delete(Interval::new(0, offset)), + AssertJson(r#"[{"insert":"好\n"}]"#), + ]; + DeltaDocumentEditorTest::new() + .await + .run_scripts(scripts) + .await; } #[tokio::test] async fn text_block_sync_replace_test() { - let scripts = vec![ - InsertText("1", 0), - InsertText("2", 1), - InsertText("3", 2), - Replace(Interval::new(0, 3), "abc"), - AssertJson(r#"[{"insert":"abc\n"}]"#), - ]; - DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; + let scripts = vec![ + InsertText("1", 0), + InsertText("2", 1), + InsertText("3", 2), + Replace(Interval::new(0, 3), "abc"), + AssertJson(r#"[{"insert":"abc\n"}]"#), + ]; + DeltaDocumentEditorTest::new() + .await + .run_scripts(scripts) + .await; } diff --git a/frontend/rust-lib/flowy-document/tests/old_document/script.rs b/frontend/rust-lib/flowy-document/tests/old_document/script.rs index e523566da7..49f94e019d 100644 --- a/frontend/rust-lib/flowy-document/tests/old_document/script.rs +++ b/frontend/rust-lib/flowy-document/tests/old_document/script.rs @@ -7,83 +7,90 @@ use std::sync::Arc; use tokio::time::{sleep, Duration}; pub enum EditorScript { - InsertText(&'static str, usize), - Delete(Interval), - Replace(Interval, &'static str), + InsertText(&'static str, usize), + Delete(Interval), + Replace(Interval, &'static str), - AssertRevisionState(i64, RevisionState), - AssertNextSyncRevId(Option), - AssertCurrentRevId(i64), - AssertJson(&'static str), + AssertRevisionState(i64, RevisionState), + AssertNextSyncRevId(Option), + AssertCurrentRevId(i64), + AssertJson(&'static str), } pub struct DeltaDocumentEditorTest { - pub sdk: FlowySDKTest, - pub editor: Arc, + pub sdk: FlowySDKTest, + pub editor: Arc, } impl DeltaDocumentEditorTest { - pub async fn new() -> Self { - let sdk = FlowySDKTest::default(); - let _ = sdk.init_user().await; - let test = ViewTest::new_document_view(&sdk).await; - let document_editor = sdk.document_manager.open_document_editor(&test.view.id).await.unwrap(); - let editor = match document_editor.as_any().downcast_ref::>() { - None => panic!(), - Some(editor) => editor.clone(), - }; - Self { sdk, editor } - } + pub async fn new() -> Self { + let sdk = FlowySDKTest::default(); + let _ = sdk.init_user().await; + let test = ViewTest::new_document_view(&sdk).await; + let document_editor = sdk + .document_manager + .open_document_editor(&test.view.id) + .await + .unwrap(); + let editor = match document_editor + .as_any() + .downcast_ref::>() + { + None => panic!(), + Some(editor) => editor.clone(), + }; + Self { sdk, editor } + } - pub async fn run_scripts(mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; + pub async fn run_scripts(mut self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + async fn run_script(&mut self, script: EditorScript) { + let rev_manager = self.editor.rev_manager(); + let cache = rev_manager.revision_cache().await; + let _user_id = self.sdk.user_session.user_id().unwrap(); + + match script { + EditorScript::InsertText(s, offset) => { + self.editor.insert(offset, s).await.unwrap(); + }, + EditorScript::Delete(interval) => { + self.editor.delete(interval).await.unwrap(); + }, + EditorScript::Replace(interval, s) => { + self.editor.replace(interval, s).await.unwrap(); + }, + EditorScript::AssertRevisionState(rev_id, state) => { + let record = cache.get(rev_id).await.unwrap(); + assert_eq!(record.state, state); + }, + EditorScript::AssertCurrentRevId(rev_id) => { + assert_eq!(self.editor.rev_manager().rev_id(), rev_id); + }, + EditorScript::AssertNextSyncRevId(rev_id) => { + let next_revision = rev_manager.next_sync_revision().await.unwrap(); + if rev_id.is_none() { + assert!(next_revision.is_none(), "Next revision should be None"); + return; } - } - - async fn run_script(&mut self, script: EditorScript) { - let rev_manager = self.editor.rev_manager(); - let cache = rev_manager.revision_cache().await; - let _user_id = self.sdk.user_session.user_id().unwrap(); - - match script { - EditorScript::InsertText(s, offset) => { - self.editor.insert(offset, s).await.unwrap(); - } - EditorScript::Delete(interval) => { - self.editor.delete(interval).await.unwrap(); - } - EditorScript::Replace(interval, s) => { - self.editor.replace(interval, s).await.unwrap(); - } - EditorScript::AssertRevisionState(rev_id, state) => { - let record = cache.get(rev_id).await.unwrap(); - assert_eq!(record.state, state); - } - EditorScript::AssertCurrentRevId(rev_id) => { - assert_eq!(self.editor.rev_manager().rev_id(), rev_id); - } - EditorScript::AssertNextSyncRevId(rev_id) => { - let next_revision = rev_manager.next_sync_revision().await.unwrap(); - if rev_id.is_none() { - assert!(next_revision.is_none(), "Next revision should be None"); - return; - } - let next_revision = next_revision.unwrap(); - let mut notify = rev_manager.ack_notify(); - let _ = notify.recv().await; - assert_eq!(next_revision.rev_id, rev_id.unwrap()); - } - EditorScript::AssertJson(expected) => { - let expected_delta: DeltaTextOperations = serde_json::from_str(expected).unwrap(); - let delta = self.editor.document_operations().await.unwrap(); - if expected_delta != delta { - eprintln!("✅ expect: {}", expected,); - eprintln!("❌ receive: {}", delta.json_str()); - } - assert_eq!(expected_delta, delta); - } + let next_revision = next_revision.unwrap(); + let mut notify = rev_manager.ack_notify(); + let _ = notify.recv().await; + assert_eq!(next_revision.rev_id, rev_id.unwrap()); + }, + EditorScript::AssertJson(expected) => { + let expected_delta: DeltaTextOperations = serde_json::from_str(expected).unwrap(); + let delta = self.editor.document_operations().await.unwrap(); + if expected_delta != delta { + eprintln!("✅ expect: {}", expected,); + eprintln!("❌ receive: {}", delta.json_str()); } - sleep(Duration::from_millis(TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS)).await; + assert_eq!(expected_delta, delta); + }, } + sleep(Duration::from_millis(TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS)).await; + } } diff --git a/frontend/rust-lib/flowy-error/build.rs b/frontend/rust-lib/flowy-error/build.rs index 009e328849..55820d5cbd 100644 --- a/frontend/rust-lib/flowy-error/build.rs +++ b/frontend/rust-lib/flowy-error/build.rs @@ -1,3 +1,3 @@ fn main() { - flowy_codegen::protobuf_file::gen("flowy-error"); + flowy_codegen::protobuf_file::gen("flowy-error"); } diff --git a/frontend/rust-lib/flowy-error/src/code.rs b/frontend/rust-lib/flowy-error/src/code.rs index f93bf9449a..5e7523b2f7 100644 --- a/frontend/rust-lib/flowy-error/src/code.rs +++ b/frontend/rust-lib/flowy-error/src/code.rs @@ -5,189 +5,191 @@ use thiserror::Error; #[derive(Debug, Clone, PartialEq, Eq, Error, Serialize_repr, Deserialize_repr, ProtoBuf_Enum)] #[repr(u8)] pub enum ErrorCode { - #[error("Internal error")] - Internal = 0, + #[error("Internal error")] + Internal = 0, - #[error("Unauthorized user")] - UserUnauthorized = 2, + #[error("Unauthorized user")] + UserUnauthorized = 2, - #[error("Record not found")] - RecordNotFound = 3, + #[error("Record not found")] + RecordNotFound = 3, - #[error("User id is empty")] - UserIdIsEmpty = 4, + #[error("User id is empty")] + UserIdIsEmpty = 4, - #[error("Workspace name can not be empty or whitespace")] - WorkspaceNameInvalid = 5, + #[error("Workspace name can not be empty or whitespace")] + WorkspaceNameInvalid = 5, - #[error("Workspace id can not be empty or whitespace")] - WorkspaceIdInvalid = 6, + #[error("Workspace id can not be empty or whitespace")] + WorkspaceIdInvalid = 6, - #[error("Color style of the App is invalid")] - AppColorStyleInvalid = 7, + #[error("Color style of the App is invalid")] + AppColorStyleInvalid = 7, - #[error("Workspace desc is invalid")] - WorkspaceDescTooLong = 8, + #[error("Workspace desc is invalid")] + WorkspaceDescTooLong = 8, - #[error("Workspace description too long")] - WorkspaceNameTooLong = 9, + #[error("Workspace description too long")] + WorkspaceNameTooLong = 9, - #[error("App id can not be empty or whitespace")] - AppIdInvalid = 10, + #[error("App id can not be empty or whitespace")] + AppIdInvalid = 10, - #[error("App name can not be empty or whitespace")] - AppNameInvalid = 11, + #[error("App name can not be empty or whitespace")] + AppNameInvalid = 11, - #[error("View name can not be empty or whitespace")] - ViewNameInvalid = 12, + #[error("View name can not be empty or whitespace")] + ViewNameInvalid = 12, - #[error("Thumbnail of the view is invalid")] - ViewThumbnailInvalid = 13, + #[error("Thumbnail of the view is invalid")] + ViewThumbnailInvalid = 13, - #[error("View id can not be empty or whitespace")] - ViewIdInvalid = 14, + #[error("View id can not be empty or whitespace")] + ViewIdInvalid = 14, - #[error("View desc too long")] - ViewDescTooLong = 15, + #[error("View desc too long")] + ViewDescTooLong = 15, - #[error("View data is invalid")] - ViewDataInvalid = 16, + #[error("View data is invalid")] + ViewDataInvalid = 16, - #[error("View name too long")] - ViewNameTooLong = 17, + #[error("View name too long")] + ViewNameTooLong = 17, - #[error("Http server connection error")] - HttpServerConnectError = 18, + #[error("Http server connection error")] + HttpServerConnectError = 18, - #[error("Email can not be empty or whitespace")] - EmailIsEmpty = 19, + #[error("Email can not be empty or whitespace")] + EmailIsEmpty = 19, - #[error("Email format is not valid")] - EmailFormatInvalid = 20, + #[error("Email format is not valid")] + EmailFormatInvalid = 20, - #[error("Email already exists")] - EmailAlreadyExists = 21, + #[error("Email already exists")] + EmailAlreadyExists = 21, - #[error("Password can not be empty or whitespace")] - PasswordIsEmpty = 22, + #[error("Password can not be empty or whitespace")] + PasswordIsEmpty = 22, - #[error("Password format too long")] - PasswordTooLong = 23, + #[error("Password format too long")] + PasswordTooLong = 23, - #[error("Password contains forbidden characters.")] - PasswordContainsForbidCharacters = 24, + #[error("Password contains forbidden characters.")] + PasswordContainsForbidCharacters = 24, - #[error("Password should contain a minimum of 6 characters with 1 special 1 letter and 1 numeric")] - PasswordFormatInvalid = 25, + #[error( + "Password should contain a minimum of 6 characters with 1 special 1 letter and 1 numeric" + )] + PasswordFormatInvalid = 25, - #[error("Password not match")] - PasswordNotMatch = 26, + #[error("Password not match")] + PasswordNotMatch = 26, - #[error("User name is too long")] - UserNameTooLong = 27, + #[error("User name is too long")] + UserNameTooLong = 27, - #[error("User name contain forbidden characters")] - UserNameContainForbiddenCharacters = 28, + #[error("User name contain forbidden characters")] + UserNameContainForbiddenCharacters = 28, - #[error("User name can not be empty or whitespace")] - UserNameIsEmpty = 29, + #[error("User name can not be empty or whitespace")] + UserNameIsEmpty = 29, - #[error("user id is empty or whitespace")] - UserIdInvalid = 30, + #[error("user id is empty or whitespace")] + UserIdInvalid = 30, - #[error("User not exist")] - UserNotExist = 31, + #[error("User not exist")] + UserNotExist = 31, - #[error("Text is too long")] - TextTooLong = 32, + #[error("Text is too long")] + TextTooLong = 32, - #[error("Database id is empty")] - DatabaseIdIsEmpty = 33, + #[error("Database id is empty")] + DatabaseIdIsEmpty = 33, - #[error("Grid view id is empty")] - DatabaseViewIdIsEmpty = 34, + #[error("Grid view id is empty")] + DatabaseViewIdIsEmpty = 34, - #[error("Grid block id is empty")] - BlockIdIsEmpty = 35, + #[error("Grid block id is empty")] + BlockIdIsEmpty = 35, - #[error("Row id is empty")] - RowIdIsEmpty = 36, + #[error("Row id is empty")] + RowIdIsEmpty = 36, - #[error("Select option id is empty")] - OptionIdIsEmpty = 37, + #[error("Select option id is empty")] + OptionIdIsEmpty = 37, - #[error("Field id is empty")] - FieldIdIsEmpty = 38, + #[error("Field id is empty")] + FieldIdIsEmpty = 38, - #[error("Field doesn't exist")] - FieldDoesNotExist = 39, + #[error("Field doesn't exist")] + FieldDoesNotExist = 39, - #[error("The name of the option should not be empty")] - SelectOptionNameIsEmpty = 40, + #[error("The name of the option should not be empty")] + SelectOptionNameIsEmpty = 40, - #[error("Field not exists")] - FieldNotExists = 41, + #[error("Field not exists")] + FieldNotExists = 41, - #[error("The operation in this field is invalid")] - FieldInvalidOperation = 42, + #[error("The operation in this field is invalid")] + FieldInvalidOperation = 42, - #[error("Filter id is empty")] - FilterIdIsEmpty = 43, + #[error("Filter id is empty")] + FilterIdIsEmpty = 43, - #[error("Field is not exist")] - FieldRecordNotFound = 44, + #[error("Field is not exist")] + FieldRecordNotFound = 44, - #[error("Field's type-option data should not be empty")] - TypeOptionDataIsEmpty = 45, + #[error("Field's type-option data should not be empty")] + TypeOptionDataIsEmpty = 45, - #[error("Group id is empty")] - GroupIdIsEmpty = 46, + #[error("Group id is empty")] + GroupIdIsEmpty = 46, - #[error("Invalid date time format")] - InvalidDateTimeFormat = 47, + #[error("Invalid date time format")] + InvalidDateTimeFormat = 47, - #[error("The input string is empty or contains invalid characters")] - UnexpectedEmptyString = 48, + #[error("The input string is empty or contains invalid characters")] + UnexpectedEmptyString = 48, - #[error("Invalid data")] - InvalidData = 49, + #[error("Invalid data")] + InvalidData = 49, - #[error("Serde")] - Serde = 50, + #[error("Serde")] + Serde = 50, - #[error("Protobuf serde")] - ProtobufSerde = 51, + #[error("Protobuf serde")] + ProtobufSerde = 51, - #[error("Out of bounds")] - OutOfBounds = 52, + #[error("Out of bounds")] + OutOfBounds = 52, - #[error("Sort id is empty")] - SortIdIsEmpty = 53, + #[error("Sort id is empty")] + SortIdIsEmpty = 53, - #[error("Connect refused")] - ConnectRefused = 54, + #[error("Connect refused")] + ConnectRefused = 54, - #[error("Connection timeout")] - ConnectTimeout = 55, + #[error("Connection timeout")] + ConnectTimeout = 55, - #[error("Connection closed")] - ConnectClose = 56, + #[error("Connection closed")] + ConnectClose = 56, - #[error("Connection canceled")] - ConnectCancel = 57, + #[error("Connection canceled")] + ConnectCancel = 57, - #[error("Sql error")] - SqlError = 58, + #[error("Sql error")] + SqlError = 58, - #[error("Http request error")] - HttpError = 59, + #[error("Http request error")] + HttpError = 59, - #[error("Payload should not be empty")] - UnexpectedEmptyPayload = 60, + #[error("Payload should not be empty")] + UnexpectedEmptyPayload = 60, } impl ErrorCode { - pub fn value(&self) -> i32 { - self.clone() as i32 - } + pub fn value(&self) -> i32 { + self.clone() as i32 + } } diff --git a/frontend/rust-lib/flowy-error/src/errors.rs b/frontend/rust-lib/flowy-error/src/errors.rs index 74739bb705..6425d03ab5 100644 --- a/frontend/rust-lib/flowy-error/src/errors.rs +++ b/frontend/rust-lib/flowy-error/src/errors.rs @@ -9,99 +9,105 @@ pub type FlowyResult = anyhow::Result; #[derive(Debug, Default, Clone, ProtoBuf, Error)] #[error("{code:?}: {msg}")] pub struct FlowyError { - #[pb(index = 1)] - pub code: i32, + #[pb(index = 1)] + pub code: i32, - #[pb(index = 2)] - pub msg: String, + #[pb(index = 2)] + pub msg: String, } macro_rules! static_flowy_error { - ($name:ident, $code:expr) => { - #[allow(non_snake_case, missing_docs)] - pub fn $name() -> FlowyError { - $code.into() - } - }; + ($name:ident, $code:expr) => { + #[allow(non_snake_case, missing_docs)] + pub fn $name() -> FlowyError { + $code.into() + } + }; } impl FlowyError { - pub fn new(code: ErrorCode, msg: &str) -> Self { - Self { - code: code.value() as i32, - msg: msg.to_owned(), - } - } - pub fn context(mut self, error: T) -> Self { - self.msg = format!("{:?}", error); - self + pub fn new(code: ErrorCode, msg: &str) -> Self { + Self { + code: code.value() as i32, + msg: msg.to_owned(), } + } + pub fn context(mut self, error: T) -> Self { + self.msg = format!("{:?}", error); + self + } - pub fn is_record_not_found(&self) -> bool { - self.code == ErrorCode::RecordNotFound.value() - } + pub fn is_record_not_found(&self) -> bool { + self.code == ErrorCode::RecordNotFound.value() + } - static_flowy_error!(internal, ErrorCode::Internal); - static_flowy_error!(record_not_found, ErrorCode::RecordNotFound); - static_flowy_error!(workspace_name, ErrorCode::WorkspaceNameInvalid); - static_flowy_error!(workspace_id, ErrorCode::WorkspaceIdInvalid); - static_flowy_error!(color_style, ErrorCode::AppColorStyleInvalid); - static_flowy_error!(workspace_desc, ErrorCode::WorkspaceDescTooLong); - static_flowy_error!(app_name, ErrorCode::AppNameInvalid); - static_flowy_error!(invalid_app_id, ErrorCode::AppIdInvalid); - static_flowy_error!(view_name, ErrorCode::ViewNameInvalid); - static_flowy_error!(view_thumbnail, ErrorCode::ViewThumbnailInvalid); - static_flowy_error!(invalid_view_id, ErrorCode::ViewIdInvalid); - static_flowy_error!(view_desc, ErrorCode::ViewDescTooLong); - static_flowy_error!(view_data, ErrorCode::ViewDataInvalid); - static_flowy_error!(unauthorized, ErrorCode::UserUnauthorized); - static_flowy_error!(connection, ErrorCode::HttpServerConnectError); - static_flowy_error!(email_empty, ErrorCode::EmailIsEmpty); - static_flowy_error!(email_format, ErrorCode::EmailFormatInvalid); - static_flowy_error!(email_exist, ErrorCode::EmailAlreadyExists); - static_flowy_error!(password_empty, ErrorCode::PasswordIsEmpty); - static_flowy_error!(passworkd_too_long, ErrorCode::PasswordTooLong); - static_flowy_error!(password_forbid_char, ErrorCode::PasswordContainsForbidCharacters); - static_flowy_error!(password_format, ErrorCode::PasswordFormatInvalid); - static_flowy_error!(password_not_match, ErrorCode::PasswordNotMatch); - static_flowy_error!(name_too_long, ErrorCode::UserNameTooLong); - static_flowy_error!(name_forbid_char, ErrorCode::UserNameContainForbiddenCharacters); - static_flowy_error!(name_empty, ErrorCode::UserNameIsEmpty); - static_flowy_error!(user_id, ErrorCode::UserIdInvalid); - static_flowy_error!(user_not_exist, ErrorCode::UserNotExist); - static_flowy_error!(text_too_long, ErrorCode::TextTooLong); - static_flowy_error!(invalid_data, ErrorCode::InvalidData); - static_flowy_error!(out_of_bounds, ErrorCode::OutOfBounds); - static_flowy_error!(serde, ErrorCode::Serde); - static_flowy_error!(field_record_not_found, ErrorCode::FieldRecordNotFound); - static_flowy_error!(payload_none, ErrorCode::UnexpectedEmptyPayload); - static_flowy_error!(http, ErrorCode::HttpError); + static_flowy_error!(internal, ErrorCode::Internal); + static_flowy_error!(record_not_found, ErrorCode::RecordNotFound); + static_flowy_error!(workspace_name, ErrorCode::WorkspaceNameInvalid); + static_flowy_error!(workspace_id, ErrorCode::WorkspaceIdInvalid); + static_flowy_error!(color_style, ErrorCode::AppColorStyleInvalid); + static_flowy_error!(workspace_desc, ErrorCode::WorkspaceDescTooLong); + static_flowy_error!(app_name, ErrorCode::AppNameInvalid); + static_flowy_error!(invalid_app_id, ErrorCode::AppIdInvalid); + static_flowy_error!(view_name, ErrorCode::ViewNameInvalid); + static_flowy_error!(view_thumbnail, ErrorCode::ViewThumbnailInvalid); + static_flowy_error!(invalid_view_id, ErrorCode::ViewIdInvalid); + static_flowy_error!(view_desc, ErrorCode::ViewDescTooLong); + static_flowy_error!(view_data, ErrorCode::ViewDataInvalid); + static_flowy_error!(unauthorized, ErrorCode::UserUnauthorized); + static_flowy_error!(connection, ErrorCode::HttpServerConnectError); + static_flowy_error!(email_empty, ErrorCode::EmailIsEmpty); + static_flowy_error!(email_format, ErrorCode::EmailFormatInvalid); + static_flowy_error!(email_exist, ErrorCode::EmailAlreadyExists); + static_flowy_error!(password_empty, ErrorCode::PasswordIsEmpty); + static_flowy_error!(passworkd_too_long, ErrorCode::PasswordTooLong); + static_flowy_error!( + password_forbid_char, + ErrorCode::PasswordContainsForbidCharacters + ); + static_flowy_error!(password_format, ErrorCode::PasswordFormatInvalid); + static_flowy_error!(password_not_match, ErrorCode::PasswordNotMatch); + static_flowy_error!(name_too_long, ErrorCode::UserNameTooLong); + static_flowy_error!( + name_forbid_char, + ErrorCode::UserNameContainForbiddenCharacters + ); + static_flowy_error!(name_empty, ErrorCode::UserNameIsEmpty); + static_flowy_error!(user_id, ErrorCode::UserIdInvalid); + static_flowy_error!(user_not_exist, ErrorCode::UserNotExist); + static_flowy_error!(text_too_long, ErrorCode::TextTooLong); + static_flowy_error!(invalid_data, ErrorCode::InvalidData); + static_flowy_error!(out_of_bounds, ErrorCode::OutOfBounds); + static_flowy_error!(serde, ErrorCode::Serde); + static_flowy_error!(field_record_not_found, ErrorCode::FieldRecordNotFound); + static_flowy_error!(payload_none, ErrorCode::UnexpectedEmptyPayload); + static_flowy_error!(http, ErrorCode::HttpError); } impl std::convert::From for FlowyError { - fn from(code: ErrorCode) -> Self { - FlowyError { - code: code.value() as i32, - msg: format!("{}", code), - } + fn from(code: ErrorCode) -> Self { + FlowyError { + code: code.value() as i32, + msg: format!("{}", code), } + } } pub fn internal_error(e: T) -> FlowyError where - T: std::fmt::Debug, + T: std::fmt::Debug, { - FlowyError::internal().context(e) + FlowyError::internal().context(e) } impl std::convert::From for FlowyError { - fn from(error: std::io::Error) -> Self { - FlowyError::internal().context(error) - } + fn from(error: std::io::Error) -> Self { + FlowyError::internal().context(error) + } } impl std::convert::From for FlowyError { - fn from(e: protobuf::ProtobufError) -> Self { - FlowyError::internal().context(e) - } + fn from(e: protobuf::ProtobufError) -> Self { + FlowyError::internal().context(e) + } } diff --git a/frontend/rust-lib/flowy-error/src/ext/database.rs b/frontend/rust-lib/flowy-error/src/ext/database.rs index 2a5807421d..b9db62e22d 100644 --- a/frontend/rust-lib/flowy-error/src/ext/database.rs +++ b/frontend/rust-lib/flowy-error/src/ext/database.rs @@ -1,13 +1,13 @@ use crate::FlowyError; impl std::convert::From for FlowyError { - fn from(error: flowy_sqlite::Error) -> Self { - FlowyError::internal().context(error) - } + fn from(error: flowy_sqlite::Error) -> Self { + FlowyError::internal().context(error) + } } impl std::convert::From<::r2d2::Error> for FlowyError { - fn from(error: r2d2::Error) -> Self { - FlowyError::internal().context(error) - } + fn from(error: r2d2::Error) -> Self { + FlowyError::internal().context(error) + } } diff --git a/frontend/rust-lib/flowy-error/src/ext/dispatch.rs b/frontend/rust-lib/flowy-error/src/ext/dispatch.rs index c072931af6..83c733ef5f 100644 --- a/frontend/rust-lib/flowy-error/src/ext/dispatch.rs +++ b/frontend/rust-lib/flowy-error/src/ext/dispatch.rs @@ -3,8 +3,8 @@ use bytes::Bytes; use lib_dispatch::prelude::{AFPluginEventResponse, ResponseBuilder}; use std::convert::TryInto; impl lib_dispatch::Error for FlowyError { - fn as_response(&self) -> AFPluginEventResponse { - let bytes: Bytes = self.clone().try_into().unwrap(); - ResponseBuilder::Err().data(bytes).build() - } + fn as_response(&self) -> AFPluginEventResponse { + let bytes: Bytes = self.clone().try_into().unwrap(); + ResponseBuilder::Err().data(bytes).build() + } } diff --git a/frontend/rust-lib/flowy-error/src/ext/http_server.rs b/frontend/rust-lib/flowy-error/src/ext/http_server.rs index 32f57b1de4..d7fa3355d2 100644 --- a/frontend/rust-lib/flowy-error/src/ext/http_server.rs +++ b/frontend/rust-lib/flowy-error/src/ext/http_server.rs @@ -2,15 +2,15 @@ use crate::code::ErrorCode; use http_error_code::ErrorCode as ServerErrorCode; impl std::convert::From for ErrorCode { - fn from(code: ServerErrorCode) -> Self { - match code { - ServerErrorCode::UserUnauthorized => ErrorCode::UserUnauthorized, - ServerErrorCode::PasswordNotMatch => ErrorCode::PasswordNotMatch, - ServerErrorCode::RecordNotFound => ErrorCode::RecordNotFound, - ServerErrorCode::ConnectRefused | ServerErrorCode::ConnectTimeout | ServerErrorCode::ConnectClose => { - ErrorCode::HttpServerConnectError - } - _ => ErrorCode::Internal, - } + fn from(code: ServerErrorCode) -> Self { + match code { + ServerErrorCode::UserUnauthorized => ErrorCode::UserUnauthorized, + ServerErrorCode::PasswordNotMatch => ErrorCode::PasswordNotMatch, + ServerErrorCode::RecordNotFound => ErrorCode::RecordNotFound, + ServerErrorCode::ConnectRefused + | ServerErrorCode::ConnectTimeout + | ServerErrorCode::ConnectClose => ErrorCode::HttpServerConnectError, + _ => ErrorCode::Internal, } + } } diff --git a/frontend/rust-lib/flowy-error/src/ext/ot.rs b/frontend/rust-lib/flowy-error/src/ext/ot.rs index c9346b835d..65e0354c15 100644 --- a/frontend/rust-lib/flowy-error/src/ext/ot.rs +++ b/frontend/rust-lib/flowy-error/src/ext/ot.rs @@ -1,7 +1,7 @@ use crate::FlowyError; impl std::convert::From for FlowyError { - fn from(error: lib_ot::errors::OTError) -> Self { - FlowyError::internal().context(error.msg) - } + fn from(error: lib_ot::errors::OTError) -> Self { + FlowyError::internal().context(error.msg) + } } diff --git a/frontend/rust-lib/flowy-error/src/ext/reqwest.rs b/frontend/rust-lib/flowy-error/src/ext/reqwest.rs index e573e9b98c..8bc8778b82 100644 --- a/frontend/rust-lib/flowy-error/src/ext/reqwest.rs +++ b/frontend/rust-lib/flowy-error/src/ext/reqwest.rs @@ -2,7 +2,7 @@ use crate::FlowyError; use reqwest::Error; impl std::convert::From for FlowyError { - fn from(error: Error) -> Self { - FlowyError::connection().context(error) - } + fn from(error: Error) -> Self { + FlowyError::connection().context(error) + } } diff --git a/frontend/rust-lib/flowy-error/src/ext/serde.rs b/frontend/rust-lib/flowy-error/src/ext/serde.rs index e8a158f1bb..333171bae1 100644 --- a/frontend/rust-lib/flowy-error/src/ext/serde.rs +++ b/frontend/rust-lib/flowy-error/src/ext/serde.rs @@ -1,7 +1,7 @@ use crate::FlowyError; impl std::convert::From for FlowyError { - fn from(error: serde_json::Error) -> Self { - FlowyError::serde().context(error) - } + fn from(error: serde_json::Error) -> Self { + FlowyError::serde().context(error) + } } diff --git a/frontend/rust-lib/flowy-error/src/ext/sync.rs b/frontend/rust-lib/flowy-error/src/ext/sync.rs index a1f233e1f2..3cae9098df 100644 --- a/frontend/rust-lib/flowy-error/src/ext/sync.rs +++ b/frontend/rust-lib/flowy-error/src/ext/sync.rs @@ -3,10 +3,10 @@ use crate::FlowyError; use flowy_client_sync::errors::ErrorCode; impl std::convert::From for FlowyError { - fn from(error: flowy_client_sync::errors::SyncError) -> Self { - match error.code { - ErrorCode::RecordNotFound => FlowyError::record_not_found().context(error.msg), - _ => FlowyError::internal().context(error.msg), - } + fn from(error: flowy_client_sync::errors::SyncError) -> Self { + match error.code { + ErrorCode::RecordNotFound => FlowyError::record_not_found().context(error.msg), + _ => FlowyError::internal().context(error.msg), } + } } diff --git a/frontend/rust-lib/flowy-error/src/ext/user.rs b/frontend/rust-lib/flowy-error/src/ext/user.rs index bef8f9a28f..0862c6e4fd 100644 --- a/frontend/rust-lib/flowy-error/src/ext/user.rs +++ b/frontend/rust-lib/flowy-error/src/ext/user.rs @@ -2,22 +2,26 @@ use crate::code::ErrorCode; use user_model::errors::UserErrorCode; impl std::convert::From for ErrorCode { - fn from(code: UserErrorCode) -> Self { - match code { - UserErrorCode::Internal => ErrorCode::Internal, - UserErrorCode::WorkspaceIdInvalid => ErrorCode::WorkspaceIdInvalid, - UserErrorCode::EmailIsEmpty => ErrorCode::EmailIsEmpty, - UserErrorCode::EmailFormatInvalid => ErrorCode::EmailFormatInvalid, - UserErrorCode::UserIdInvalid => ErrorCode::UserIdInvalid, - UserErrorCode::UserNameContainForbiddenCharacters => ErrorCode::UserNameContainForbiddenCharacters, - UserErrorCode::UserNameIsEmpty => ErrorCode::UserNameIsEmpty, - UserErrorCode::UserNotExist => ErrorCode::UserNotExist, - UserErrorCode::PasswordIsEmpty => ErrorCode::PasswordIsEmpty, - UserErrorCode::PasswordTooLong => ErrorCode::PasswordTooLong, - UserErrorCode::PasswordContainsForbidCharacters => ErrorCode::PasswordContainsForbidCharacters, - UserErrorCode::PasswordFormatInvalid => ErrorCode::PasswordFormatInvalid, - UserErrorCode::PasswordNotMatch => ErrorCode::PasswordNotMatch, - UserErrorCode::UserNameTooLong => ErrorCode::UserNameTooLong, - } + fn from(code: UserErrorCode) -> Self { + match code { + UserErrorCode::Internal => ErrorCode::Internal, + UserErrorCode::WorkspaceIdInvalid => ErrorCode::WorkspaceIdInvalid, + UserErrorCode::EmailIsEmpty => ErrorCode::EmailIsEmpty, + UserErrorCode::EmailFormatInvalid => ErrorCode::EmailFormatInvalid, + UserErrorCode::UserIdInvalid => ErrorCode::UserIdInvalid, + UserErrorCode::UserNameContainForbiddenCharacters => { + ErrorCode::UserNameContainForbiddenCharacters + }, + UserErrorCode::UserNameIsEmpty => ErrorCode::UserNameIsEmpty, + UserErrorCode::UserNotExist => ErrorCode::UserNotExist, + UserErrorCode::PasswordIsEmpty => ErrorCode::PasswordIsEmpty, + UserErrorCode::PasswordTooLong => ErrorCode::PasswordTooLong, + UserErrorCode::PasswordContainsForbidCharacters => { + ErrorCode::PasswordContainsForbidCharacters + }, + UserErrorCode::PasswordFormatInvalid => ErrorCode::PasswordFormatInvalid, + UserErrorCode::PasswordNotMatch => ErrorCode::PasswordNotMatch, + UserErrorCode::UserNameTooLong => ErrorCode::UserNameTooLong, } + } } diff --git a/frontend/rust-lib/flowy-error/src/ext/ws.rs b/frontend/rust-lib/flowy-error/src/ext/ws.rs index f877ad1763..b790ed2f0e 100644 --- a/frontend/rust-lib/flowy-error/src/ext/ws.rs +++ b/frontend/rust-lib/flowy-error/src/ext/ws.rs @@ -2,9 +2,9 @@ use crate::FlowyError; use flowy_client_ws::WSErrorCode; impl std::convert::From for FlowyError { - fn from(code: WSErrorCode) -> Self { - match code { - WSErrorCode::Internal => FlowyError::internal().context(code), - } + fn from(code: WSErrorCode) -> Self { + match code { + WSErrorCode::Internal => FlowyError::internal().context(code), } + } } diff --git a/frontend/rust-lib/flowy-folder/build.rs b/frontend/rust-lib/flowy-folder/build.rs index 508b370b87..06388d2a02 100644 --- a/frontend/rust-lib/flowy-folder/build.rs +++ b/frontend/rust-lib/flowy-folder/build.rs @@ -1,10 +1,10 @@ fn main() { - let crate_name = env!("CARGO_PKG_NAME"); - flowy_codegen::protobuf_file::gen(crate_name); + let crate_name = env!("CARGO_PKG_NAME"); + flowy_codegen::protobuf_file::gen(crate_name); - #[cfg(feature = "dart")] - flowy_codegen::dart_event::gen(crate_name); + #[cfg(feature = "dart")] + flowy_codegen::dart_event::gen(crate_name); - #[cfg(feature = "ts")] - flowy_codegen::ts_event::gen(crate_name); + #[cfg(feature = "ts")] + flowy_codegen::ts_event::gen(crate_name); } diff --git a/frontend/rust-lib/flowy-folder/src/entities/app.rs b/frontend/rust-lib/flowy-folder/src/entities/app.rs index 203feb9a4e..6b1e3f1f6d 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/app.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/app.rs @@ -1,11 +1,11 @@ use crate::{ - entities::parser::{ - app::{AppColorStyle, AppIdentify, AppName}, - workspace::WorkspaceIdentify, - }, - entities::view::RepeatedViewPB, - errors::ErrorCode, - impl_def_and_def_mut, + entities::parser::{ + app::{AppColorStyle, AppIdentify, AppName}, + workspace::WorkspaceIdentify, + }, + entities::view::RepeatedViewPB, + errors::ErrorCode, + impl_def_and_def_mut, }; use flowy_derive::ProtoBuf; use folder_model::AppRevision; @@ -13,207 +13,210 @@ use std::convert::TryInto; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct AppPB { - #[pb(index = 1)] - pub id: String, + #[pb(index = 1)] + pub id: String, - #[pb(index = 2)] - pub workspace_id: String, + #[pb(index = 2)] + pub workspace_id: String, - #[pb(index = 3)] - pub name: String, + #[pb(index = 3)] + pub name: String, - #[pb(index = 4)] - pub desc: String, + #[pb(index = 4)] + pub desc: String, - #[pb(index = 5)] - pub belongings: RepeatedViewPB, + #[pb(index = 5)] + pub belongings: RepeatedViewPB, - #[pb(index = 6)] - pub version: i64, + #[pb(index = 6)] + pub version: i64, - #[pb(index = 7)] - pub modified_time: i64, + #[pb(index = 7)] + pub modified_time: i64, - #[pb(index = 8)] - pub create_time: i64, + #[pb(index = 8)] + pub create_time: i64, } impl std::convert::From for AppPB { - fn from(app_serde: AppRevision) -> Self { - AppPB { - id: app_serde.id, - workspace_id: app_serde.workspace_id, - name: app_serde.name, - desc: app_serde.desc, - belongings: app_serde.belongings.into(), - version: app_serde.version, - modified_time: app_serde.modified_time, - create_time: app_serde.create_time, - } + fn from(app_serde: AppRevision) -> Self { + AppPB { + id: app_serde.id, + workspace_id: app_serde.workspace_id, + name: app_serde.name, + desc: app_serde.desc, + belongings: app_serde.belongings.into(), + version: app_serde.version, + modified_time: app_serde.modified_time, + create_time: app_serde.create_time, } + } } #[derive(Eq, PartialEq, Debug, Default, ProtoBuf, Clone)] pub struct RepeatedAppPB { - #[pb(index = 1)] - pub items: Vec, + #[pb(index = 1)] + pub items: Vec, } impl_def_and_def_mut!(RepeatedAppPB, AppPB); impl std::convert::From> for RepeatedAppPB { - fn from(values: Vec) -> Self { - let items = values.into_iter().map(|value| value.into()).collect::>(); - RepeatedAppPB { items } - } + fn from(values: Vec) -> Self { + let items = values + .into_iter() + .map(|value| value.into()) + .collect::>(); + RepeatedAppPB { items } + } } #[derive(ProtoBuf, Default)] pub struct CreateAppPayloadPB { - #[pb(index = 1)] - pub workspace_id: String, + #[pb(index = 1)] + pub workspace_id: String, - #[pb(index = 2)] - pub name: String, + #[pb(index = 2)] + pub name: String, - #[pb(index = 3)] - pub desc: String, + #[pb(index = 3)] + pub desc: String, - #[pb(index = 4)] - pub color_style: ColorStylePB, + #[pb(index = 4)] + pub color_style: ColorStylePB, } #[derive(ProtoBuf, Default, Debug, Clone)] pub struct ColorStylePB { - #[pb(index = 1)] - pub theme_color: String, + #[pb(index = 1)] + pub theme_color: String, } #[derive(Debug)] pub struct CreateAppParams { - pub workspace_id: String, - pub name: String, - pub desc: String, - pub color_style: ColorStylePB, + pub workspace_id: String, + pub name: String, + pub desc: String, + pub color_style: ColorStylePB, } impl TryInto for CreateAppPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let name = AppName::parse(self.name)?; - let id = WorkspaceIdentify::parse(self.workspace_id)?; - let color_style = AppColorStyle::parse(self.color_style.theme_color.clone())?; + fn try_into(self) -> Result { + let name = AppName::parse(self.name)?; + let id = WorkspaceIdentify::parse(self.workspace_id)?; + let color_style = AppColorStyle::parse(self.color_style.theme_color.clone())?; - Ok(CreateAppParams { - workspace_id: id.0, - name: name.0, - desc: self.desc, - color_style: color_style.into(), - }) - } + Ok(CreateAppParams { + workspace_id: id.0, + name: name.0, + desc: self.desc, + color_style: color_style.into(), + }) + } } impl std::convert::From for ColorStylePB { - fn from(data: AppColorStyle) -> Self { - ColorStylePB { - theme_color: data.theme_color, - } + fn from(data: AppColorStyle) -> Self { + ColorStylePB { + theme_color: data.theme_color, } + } } #[derive(ProtoBuf, Default, Clone, Debug)] pub struct AppIdPB { - #[pb(index = 1)] - pub value: String, + #[pb(index = 1)] + pub value: String, } impl AppIdPB { - pub fn new(app_id: &str) -> Self { - Self { - value: app_id.to_string(), - } + pub fn new(app_id: &str) -> Self { + Self { + value: app_id.to_string(), } + } } #[derive(ProtoBuf, Default)] pub struct UpdateAppPayloadPB { - #[pb(index = 1)] - pub app_id: String, + #[pb(index = 1)] + pub app_id: String, - #[pb(index = 2, one_of)] - pub name: Option, + #[pb(index = 2, one_of)] + pub name: Option, - #[pb(index = 3, one_of)] - pub desc: Option, + #[pb(index = 3, one_of)] + pub desc: Option, - #[pb(index = 4, one_of)] - pub color_style: Option, + #[pb(index = 4, one_of)] + pub color_style: Option, - #[pb(index = 5, one_of)] - pub is_trash: Option, + #[pb(index = 5, one_of)] + pub is_trash: Option, } #[derive(Debug, Clone)] pub struct UpdateAppParams { - pub app_id: String, + pub app_id: String, - pub name: Option, + pub name: Option, - pub desc: Option, + pub desc: Option, - pub color_style: Option, + pub color_style: Option, - pub is_trash: Option, + pub is_trash: Option, } impl UpdateAppParams { - pub fn new(app_id: &str) -> Self { - Self { - app_id: app_id.to_string(), - name: None, - desc: None, - color_style: None, - is_trash: None, - } + pub fn new(app_id: &str) -> Self { + Self { + app_id: app_id.to_string(), + name: None, + desc: None, + color_style: None, + is_trash: None, } + } - pub fn name(mut self, name: &str) -> Self { - self.name = Some(name.to_string()); - self - } + pub fn name(mut self, name: &str) -> Self { + self.name = Some(name.to_string()); + self + } - pub fn desc(mut self, desc: &str) -> Self { - self.desc = Some(desc.to_string()); - self - } + pub fn desc(mut self, desc: &str) -> Self { + self.desc = Some(desc.to_string()); + self + } - pub fn trash(mut self) -> Self { - self.is_trash = Some(true); - self - } + pub fn trash(mut self) -> Self { + self.is_trash = Some(true); + self + } } impl TryInto for UpdateAppPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let app_id = AppIdentify::parse(self.app_id)?.0; + fn try_into(self) -> Result { + let app_id = AppIdentify::parse(self.app_id)?.0; - let name = match self.name { - None => None, - Some(name) => Some(AppName::parse(name)?.0), - }; + let name = match self.name { + None => None, + Some(name) => Some(AppName::parse(name)?.0), + }; - let color_style = match self.color_style { - None => None, - Some(color_style) => Some(AppColorStyle::parse(color_style.theme_color)?.into()), - }; + let color_style = match self.color_style { + None => None, + Some(color_style) => Some(AppColorStyle::parse(color_style.theme_color)?.into()), + }; - Ok(UpdateAppParams { - app_id, - name, - desc: self.desc, - color_style, - is_trash: self.is_trash, - }) - } + Ok(UpdateAppParams { + app_id, + name, + desc: self.desc, + color_style, + is_trash: self.is_trash, + }) + } } diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_color_style.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_color_style.rs index 3b971f7f87..cad7851b3f 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_color_style.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_color_style.rs @@ -2,12 +2,12 @@ use crate::errors::ErrorCode; #[derive(Debug)] pub struct AppColorStyle { - pub theme_color: String, + pub theme_color: String, } impl AppColorStyle { - pub fn parse(theme_color: String) -> Result { - // TODO: verify the color style format - Ok(AppColorStyle { theme_color }) - } + pub fn parse(theme_color: String) -> Result { + // TODO: verify the color style format + Ok(AppColorStyle { theme_color }) + } } diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_desc.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_desc.rs index 7f8e64808b..24cc2dcab8 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_desc.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_desc.rs @@ -3,18 +3,18 @@ use unicode_segmentation::UnicodeSegmentation; pub struct AppDesc(pub String); impl AppDesc { - #[allow(dead_code)] - pub fn parse(s: String) -> Result { - if s.graphemes(true).count() > 1024 { - return Err("Workspace description too long".to_string()); - } - - Ok(Self(s)) + #[allow(dead_code)] + pub fn parse(s: String) -> Result { + if s.graphemes(true).count() > 1024 { + return Err("Workspace description too long".to_string()); } + + Ok(Self(s)) + } } impl AsRef for AppDesc { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_id.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_id.rs index 5b73bffeee..889c219b3a 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_id.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_id.rs @@ -4,17 +4,17 @@ use crate::errors::ErrorCode; pub struct AppIdentify(pub String); impl AppIdentify { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err(ErrorCode::AppIdInvalid); - } - - Ok(Self(s)) + pub fn parse(s: String) -> Result { + if s.trim().is_empty() { + return Err(ErrorCode::AppIdInvalid); } + + Ok(Self(s)) + } } impl AsRef for AppIdentify { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_name.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_name.rs index 9052316724..c36a43b758 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_name.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_name.rs @@ -4,17 +4,17 @@ use crate::errors::ErrorCode; pub struct AppName(pub String); impl AppName { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err(ErrorCode::AppNameInvalid); - } - - Ok(Self(s)) + pub fn parse(s: String) -> Result { + if s.trim().is_empty() { + return Err(ErrorCode::AppNameInvalid); } + + Ok(Self(s)) + } } impl AsRef for AppName { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/trash/trash_id.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/trash/trash_id.rs index 2b629e33ec..3b6a4c4f6d 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/trash/trash_id.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/parser/trash/trash_id.rs @@ -2,33 +2,33 @@ pub struct TrashIdentify(pub String); impl TrashIdentify { - #[allow(dead_code)] - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err("Trash id can not be empty or whitespace".to_string()); - } - - Ok(Self(s)) + #[allow(dead_code)] + pub fn parse(s: String) -> Result { + if s.trim().is_empty() { + return Err("Trash id can not be empty or whitespace".to_string()); } + + Ok(Self(s)) + } } impl AsRef for TrashIdentify { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } #[derive(Debug)] pub struct TrashIds(pub Vec); impl TrashIds { - #[allow(dead_code)] - pub fn parse(ids: Vec) -> Result { - let mut trash_ids = vec![]; - for id in ids { - let id = TrashIdentify::parse(id)?; - trash_ids.push(id.0); - } - Ok(Self(trash_ids)) + #[allow(dead_code)] + pub fn parse(ids: Vec) -> Result { + let mut trash_ids = vec![]; + for id in ids { + let id = TrashIdentify::parse(id)?; + trash_ids.push(id.0); } + Ok(Self(trash_ids)) + } } diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_desc.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_desc.rs index 5b6b0758ad..7427573c16 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_desc.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_desc.rs @@ -5,17 +5,17 @@ use unicode_segmentation::UnicodeSegmentation; pub struct ViewDesc(pub String); impl ViewDesc { - pub fn parse(s: String) -> Result { - if s.graphemes(true).count() > 1000 { - return Err(ErrorCode::ViewDescTooLong); - } - - Ok(Self(s)) + pub fn parse(s: String) -> Result { + if s.graphemes(true).count() > 1000 { + return Err(ErrorCode::ViewDescTooLong); } + + Ok(Self(s)) + } } impl AsRef for ViewDesc { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_id.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_id.rs index a9a1fffefc..6397e6c3a4 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_id.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_id.rs @@ -4,17 +4,17 @@ use crate::errors::ErrorCode; pub struct ViewIdentify(pub String); impl ViewIdentify { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err(ErrorCode::ViewIdInvalid); - } - - Ok(Self(s)) + pub fn parse(s: String) -> Result { + if s.trim().is_empty() { + return Err(ErrorCode::ViewIdInvalid); } + + Ok(Self(s)) + } } impl AsRef for ViewIdentify { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_name.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_name.rs index e620dd8baa..08f35a47fb 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_name.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_name.rs @@ -5,21 +5,21 @@ use unicode_segmentation::UnicodeSegmentation; pub struct ViewName(pub String); impl ViewName { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err(ErrorCode::ViewNameInvalid); - } - - if s.graphemes(true).count() > 256 { - return Err(ErrorCode::ViewNameTooLong); - } - - Ok(Self(s)) + pub fn parse(s: String) -> Result { + if s.trim().is_empty() { + return Err(ErrorCode::ViewNameInvalid); } + + if s.graphemes(true).count() > 256 { + return Err(ErrorCode::ViewNameTooLong); + } + + Ok(Self(s)) + } } impl AsRef for ViewName { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_thumbnail.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_thumbnail.rs index 93b2153dcc..936a7ae7a1 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_thumbnail.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_thumbnail.rs @@ -4,18 +4,18 @@ use crate::errors::ErrorCode; pub struct ViewThumbnail(pub String); impl ViewThumbnail { - pub fn parse(s: String) -> Result { - // if s.trim().is_empty() { - // return Err(format!("View thumbnail can not be empty or whitespace")); - // } - // TODO: verify the thumbnail url is valid or not + pub fn parse(s: String) -> Result { + // if s.trim().is_empty() { + // return Err(format!("View thumbnail can not be empty or whitespace")); + // } + // TODO: verify the thumbnail url is valid or not - Ok(Self(s)) - } + Ok(Self(s)) + } } impl AsRef for ViewThumbnail { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_desc.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_desc.rs index fa15d1d83e..f90a27fe34 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_desc.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_desc.rs @@ -5,17 +5,17 @@ use unicode_segmentation::UnicodeSegmentation; pub struct WorkspaceDesc(pub String); impl WorkspaceDesc { - pub fn parse(s: String) -> Result { - if s.graphemes(true).count() > 1024 { - return Err(ErrorCode::WorkspaceNameTooLong); - } - - Ok(Self(s)) + pub fn parse(s: String) -> Result { + if s.graphemes(true).count() > 1024 { + return Err(ErrorCode::WorkspaceNameTooLong); } + + Ok(Self(s)) + } } impl AsRef for WorkspaceDesc { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_id.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_id.rs index 3c76e05bf7..f8f5a8ebee 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_id.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_id.rs @@ -4,17 +4,17 @@ use crate::errors::ErrorCode; pub struct WorkspaceIdentify(pub String); impl WorkspaceIdentify { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err(ErrorCode::WorkspaceIdInvalid); - } - - Ok(Self(s)) + pub fn parse(s: String) -> Result { + if s.trim().is_empty() { + return Err(ErrorCode::WorkspaceIdInvalid); } + + Ok(Self(s)) + } } impl AsRef for WorkspaceIdentify { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_name.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_name.rs index 1a796f0779..3028546be0 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_name.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_name.rs @@ -5,21 +5,21 @@ use unicode_segmentation::UnicodeSegmentation; pub struct WorkspaceName(pub String); impl WorkspaceName { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err(ErrorCode::WorkspaceNameInvalid); - } - - if s.graphemes(true).count() > 256 { - return Err(ErrorCode::WorkspaceNameTooLong); - } - - Ok(Self(s)) + pub fn parse(s: String) -> Result { + if s.trim().is_empty() { + return Err(ErrorCode::WorkspaceNameInvalid); } + + if s.graphemes(true).count() > 256 { + return Err(ErrorCode::WorkspaceNameTooLong); + } + + Ok(Self(s)) + } } impl AsRef for WorkspaceName { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } diff --git a/frontend/rust-lib/flowy-folder/src/entities/trash.rs b/frontend/rust-lib/flowy-folder/src/entities/trash.rs index 1846e180f8..b5e866e0ed 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/trash.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/trash.rs @@ -6,178 +6,185 @@ use std::fmt::Formatter; #[derive(Eq, PartialEq, ProtoBuf, Default, Debug, Clone)] pub struct TrashPB { - #[pb(index = 1)] - pub id: String, + #[pb(index = 1)] + pub id: String, - #[pb(index = 2)] - pub name: String, + #[pb(index = 2)] + pub name: String, - #[pb(index = 3)] - pub modified_time: i64, + #[pb(index = 3)] + pub modified_time: i64, - #[pb(index = 4)] - pub create_time: i64, + #[pb(index = 4)] + pub create_time: i64, - #[pb(index = 5)] - pub ty: TrashType, + #[pb(index = 5)] + pub ty: TrashType, } impl std::convert::From for TrashPB { - fn from(trash_rev: TrashRevision) -> Self { - TrashPB { - id: trash_rev.id, - name: trash_rev.name, - modified_time: trash_rev.modified_time, - create_time: trash_rev.create_time, - ty: trash_rev.ty.into(), - } + fn from(trash_rev: TrashRevision) -> Self { + TrashPB { + id: trash_rev.id, + name: trash_rev.name, + modified_time: trash_rev.modified_time, + create_time: trash_rev.create_time, + ty: trash_rev.ty.into(), } + } } impl std::convert::From for TrashRevision { - fn from(trash: TrashPB) -> Self { - TrashRevision { - id: trash.id, - name: trash.name, - modified_time: trash.modified_time, - create_time: trash.create_time, - ty: trash.ty.into(), - } + fn from(trash: TrashPB) -> Self { + TrashRevision { + id: trash.id, + name: trash.name, + modified_time: trash.modified_time, + create_time: trash.create_time, + ty: trash.ty.into(), } + } } #[derive(PartialEq, Eq, Debug, Default, ProtoBuf, Clone)] pub struct RepeatedTrashPB { - #[pb(index = 1)] - pub items: Vec, + #[pb(index = 1)] + pub items: Vec, } impl_def_and_def_mut!(RepeatedTrashPB, TrashPB); impl std::convert::From> for RepeatedTrashPB { - fn from(trash_revs: Vec) -> Self { - let items: Vec = trash_revs.into_iter().map(|trash_rev| trash_rev.into()).collect(); - RepeatedTrashPB { items } - } + fn from(trash_revs: Vec) -> Self { + let items: Vec = trash_revs + .into_iter() + .map(|trash_rev| trash_rev.into()) + .collect(); + RepeatedTrashPB { items } + } } #[derive(Eq, PartialEq, Debug, ProtoBuf_Enum, Clone, Serialize, Deserialize)] pub enum TrashType { - TrashUnknown = 0, - TrashView = 1, - TrashApp = 2, + TrashUnknown = 0, + TrashView = 1, + TrashApp = 2, } impl std::convert::TryFrom for TrashType { - type Error = String; + type Error = String; - fn try_from(value: i32) -> Result { - match value { - 0 => Ok(TrashType::TrashUnknown), - 1 => Ok(TrashType::TrashView), - 2 => Ok(TrashType::TrashApp), - _ => Err(format!("Invalid trash type: {}", value)), - } + fn try_from(value: i32) -> Result { + match value { + 0 => Ok(TrashType::TrashUnknown), + 1 => Ok(TrashType::TrashView), + 2 => Ok(TrashType::TrashApp), + _ => Err(format!("Invalid trash type: {}", value)), } + } } impl std::convert::From for TrashType { - fn from(rev: TrashTypeRevision) -> Self { - match rev { - TrashTypeRevision::Unknown => TrashType::TrashUnknown, - TrashTypeRevision::TrashView => TrashType::TrashView, - TrashTypeRevision::TrashApp => TrashType::TrashApp, - } + fn from(rev: TrashTypeRevision) -> Self { + match rev { + TrashTypeRevision::Unknown => TrashType::TrashUnknown, + TrashTypeRevision::TrashView => TrashType::TrashView, + TrashTypeRevision::TrashApp => TrashType::TrashApp, } + } } impl std::convert::From for TrashTypeRevision { - fn from(rev: TrashType) -> Self { - match rev { - TrashType::TrashUnknown => TrashTypeRevision::Unknown, - TrashType::TrashView => TrashTypeRevision::TrashView, - TrashType::TrashApp => TrashTypeRevision::TrashApp, - } + fn from(rev: TrashType) -> Self { + match rev { + TrashType::TrashUnknown => TrashTypeRevision::Unknown, + TrashType::TrashView => TrashTypeRevision::TrashView, + TrashType::TrashApp => TrashTypeRevision::TrashApp, } + } } impl std::default::Default for TrashType { - fn default() -> Self { - TrashType::TrashUnknown - } + fn default() -> Self { + TrashType::TrashUnknown + } } #[derive(PartialEq, Eq, ProtoBuf, Default, Debug, Clone)] pub struct RepeatedTrashIdPB { - #[pb(index = 1)] - pub items: Vec, + #[pb(index = 1)] + pub items: Vec, - #[pb(index = 2)] - pub delete_all: bool, + #[pb(index = 2)] + pub delete_all: bool, } impl std::fmt::Display for RepeatedTrashIdPB { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&format!( - "{:?}", - &self.items.iter().map(|item| format!("{}", item)).collect::>() - )) - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&format!( + "{:?}", + &self + .items + .iter() + .map(|item| format!("{}", item)) + .collect::>() + )) + } } impl RepeatedTrashIdPB { - pub fn all() -> RepeatedTrashIdPB { - RepeatedTrashIdPB { - items: vec![], - delete_all: true, - } + pub fn all() -> RepeatedTrashIdPB { + RepeatedTrashIdPB { + items: vec![], + delete_all: true, } + } } impl std::convert::From> for RepeatedTrashIdPB { - fn from(items: Vec) -> Self { - RepeatedTrashIdPB { - items, - delete_all: false, - } + fn from(items: Vec) -> Self { + RepeatedTrashIdPB { + items, + delete_all: false, } + } } impl std::convert::From> for RepeatedTrashIdPB { - fn from(trash: Vec) -> Self { - let items = trash - .into_iter() - .map(|t| TrashIdPB { - id: t.id, - ty: t.ty.into(), - }) - .collect::>(); + fn from(trash: Vec) -> Self { + let items = trash + .into_iter() + .map(|t| TrashIdPB { + id: t.id, + ty: t.ty.into(), + }) + .collect::>(); - RepeatedTrashIdPB { - items, - delete_all: false, - } + RepeatedTrashIdPB { + items, + delete_all: false, } + } } #[derive(PartialEq, Eq, ProtoBuf, Default, Debug, Clone)] pub struct TrashIdPB { - #[pb(index = 1)] - pub id: String, + #[pb(index = 1)] + pub id: String, - #[pb(index = 2)] - pub ty: TrashType, + #[pb(index = 2)] + pub ty: TrashType, } impl std::fmt::Display for TrashIdPB { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&format!("{:?}:{}", self.ty, self.id)) - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&format!("{:?}:{}", self.ty, self.id)) + } } impl std::convert::From<&TrashRevision> for TrashIdPB { - fn from(trash: &TrashRevision) -> Self { - TrashIdPB { - id: trash.id.clone(), - ty: trash.ty.clone().into(), - } + fn from(trash: &TrashRevision) -> Self { + TrashIdPB { + id: trash.id.clone(), + ty: trash.ty.clone().into(), } + } } diff --git a/frontend/rust-lib/flowy-folder/src/entities/view.rs b/frontend/rust-lib/flowy-folder/src/entities/view.rs index 01cbb5cf96..7f0752244d 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/view.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/view.rs @@ -1,10 +1,10 @@ use crate::{ - entities::parser::{ - app::AppIdentify, - view::{ViewDesc, ViewIdentify, ViewName, ViewThumbnail}, - }, - errors::ErrorCode, - impl_def_and_def_mut, + entities::parser::{ + app::AppIdentify, + view::{ViewDesc, ViewIdentify, ViewName, ViewThumbnail}, + }, + errors::ErrorCode, + impl_def_and_def_mut, }; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use folder_model::{gen_view_id, ViewDataFormatRevision, ViewLayoutTypeRevision, ViewRevision}; @@ -12,326 +12,329 @@ use std::convert::TryInto; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct ViewPB { - #[pb(index = 1)] - pub id: String, + #[pb(index = 1)] + pub id: String, - #[pb(index = 2)] - pub app_id: String, + #[pb(index = 2)] + pub app_id: String, - #[pb(index = 3)] - pub name: String, + #[pb(index = 3)] + pub name: String, - #[pb(index = 4)] - pub data_format: ViewDataFormatPB, + #[pb(index = 4)] + pub data_format: ViewDataFormatPB, - #[pb(index = 5)] - pub modified_time: i64, + #[pb(index = 5)] + pub modified_time: i64, - #[pb(index = 6)] - pub create_time: i64, + #[pb(index = 6)] + pub create_time: i64, - #[pb(index = 7)] - pub layout: ViewLayoutTypePB, + #[pb(index = 7)] + pub layout: ViewLayoutTypePB, } impl std::convert::From for ViewPB { - fn from(rev: ViewRevision) -> Self { - ViewPB { - id: rev.id, - app_id: rev.app_id, - name: rev.name, - data_format: rev.data_format.into(), - modified_time: rev.modified_time, - create_time: rev.create_time, - layout: rev.layout.into(), - } + fn from(rev: ViewRevision) -> Self { + ViewPB { + id: rev.id, + app_id: rev.app_id, + name: rev.name, + data_format: rev.data_format.into(), + modified_time: rev.modified_time, + create_time: rev.create_time, + layout: rev.layout.into(), } + } } #[derive(Eq, PartialEq, Hash, Debug, ProtoBuf_Enum, Clone)] pub enum ViewDataFormatPB { - /// Indicate this view is using `Delta` for the persistence data format, it's deprecated. - DeltaFormat = 0, - /// Indicate this view is using `Database` for the persistence data format. It is used in AppFlowy database - /// views including Grid,Board, and Calendar. - DatabaseFormat = 1, - /// Indicate this view is using `Node` for the persistence data format. It is used in AppFlowy document - NodeFormat = 2, + /// Indicate this view is using `Delta` for the persistence data format, it's deprecated. + DeltaFormat = 0, + /// Indicate this view is using `Database` for the persistence data format. It is used in AppFlowy database + /// views including Grid,Board, and Calendar. + DatabaseFormat = 1, + /// Indicate this view is using `Node` for the persistence data format. It is used in AppFlowy document + NodeFormat = 2, } impl std::default::Default for ViewDataFormatPB { - fn default() -> Self { - ViewDataFormatRevision::default().into() - } + fn default() -> Self { + ViewDataFormatRevision::default().into() + } } impl std::convert::From for ViewDataFormatPB { - fn from(rev: ViewDataFormatRevision) -> Self { - match rev { - ViewDataFormatRevision::DeltaFormat => ViewDataFormatPB::DeltaFormat, - ViewDataFormatRevision::DatabaseFormat => ViewDataFormatPB::DatabaseFormat, - ViewDataFormatRevision::NodeFormat => ViewDataFormatPB::NodeFormat, - } + fn from(rev: ViewDataFormatRevision) -> Self { + match rev { + ViewDataFormatRevision::DeltaFormat => ViewDataFormatPB::DeltaFormat, + ViewDataFormatRevision::DatabaseFormat => ViewDataFormatPB::DatabaseFormat, + ViewDataFormatRevision::NodeFormat => ViewDataFormatPB::NodeFormat, } + } } impl std::convert::From for ViewDataFormatRevision { - fn from(ty: ViewDataFormatPB) -> Self { - match ty { - ViewDataFormatPB::DeltaFormat => ViewDataFormatRevision::DeltaFormat, - ViewDataFormatPB::DatabaseFormat => ViewDataFormatRevision::DatabaseFormat, - ViewDataFormatPB::NodeFormat => ViewDataFormatRevision::NodeFormat, - } + fn from(ty: ViewDataFormatPB) -> Self { + match ty { + ViewDataFormatPB::DeltaFormat => ViewDataFormatRevision::DeltaFormat, + ViewDataFormatPB::DatabaseFormat => ViewDataFormatRevision::DatabaseFormat, + ViewDataFormatPB::NodeFormat => ViewDataFormatRevision::NodeFormat, } + } } #[derive(Eq, PartialEq, Hash, Debug, ProtoBuf_Enum, Clone)] pub enum ViewLayoutTypePB { - Document = 0, - Grid = 3, - Board = 4, - Calendar = 5, + Document = 0, + Grid = 3, + Board = 4, + Calendar = 5, } impl std::default::Default for ViewLayoutTypePB { - fn default() -> Self { - ViewLayoutTypePB::Grid - } + fn default() -> Self { + ViewLayoutTypePB::Grid + } } impl std::convert::From for ViewLayoutTypePB { - fn from(rev: ViewLayoutTypeRevision) -> Self { - match rev { - ViewLayoutTypeRevision::Grid => ViewLayoutTypePB::Grid, - ViewLayoutTypeRevision::Board => ViewLayoutTypePB::Board, - ViewLayoutTypeRevision::Document => ViewLayoutTypePB::Document, - ViewLayoutTypeRevision::Calendar => ViewLayoutTypePB::Calendar, - } + fn from(rev: ViewLayoutTypeRevision) -> Self { + match rev { + ViewLayoutTypeRevision::Grid => ViewLayoutTypePB::Grid, + ViewLayoutTypeRevision::Board => ViewLayoutTypePB::Board, + ViewLayoutTypeRevision::Document => ViewLayoutTypePB::Document, + ViewLayoutTypeRevision::Calendar => ViewLayoutTypePB::Calendar, } + } } impl std::convert::From for ViewLayoutTypeRevision { - fn from(rev: ViewLayoutTypePB) -> Self { - match rev { - ViewLayoutTypePB::Grid => ViewLayoutTypeRevision::Grid, - ViewLayoutTypePB::Board => ViewLayoutTypeRevision::Board, - ViewLayoutTypePB::Document => ViewLayoutTypeRevision::Document, - ViewLayoutTypePB::Calendar => ViewLayoutTypeRevision::Calendar, - } + fn from(rev: ViewLayoutTypePB) -> Self { + match rev { + ViewLayoutTypePB::Grid => ViewLayoutTypeRevision::Grid, + ViewLayoutTypePB::Board => ViewLayoutTypeRevision::Board, + ViewLayoutTypePB::Document => ViewLayoutTypeRevision::Document, + ViewLayoutTypePB::Calendar => ViewLayoutTypeRevision::Calendar, } + } } #[derive(Eq, PartialEq, Debug, Default, ProtoBuf, Clone)] pub struct RepeatedViewPB { - #[pb(index = 1)] - pub items: Vec, + #[pb(index = 1)] + pub items: Vec, } impl_def_and_def_mut!(RepeatedViewPB, ViewPB); impl std::convert::From> for RepeatedViewPB { - fn from(values: Vec) -> Self { - let items = values.into_iter().map(|value| value.into()).collect::>(); - RepeatedViewPB { items } - } + fn from(values: Vec) -> Self { + let items = values + .into_iter() + .map(|value| value.into()) + .collect::>(); + RepeatedViewPB { items } + } } #[derive(Default, ProtoBuf)] pub struct RepeatedViewIdPB { - #[pb(index = 1)] - pub items: Vec, + #[pb(index = 1)] + pub items: Vec, } #[derive(Default, ProtoBuf)] pub struct CreateViewPayloadPB { - #[pb(index = 1)] - pub belong_to_id: String, + #[pb(index = 1)] + pub belong_to_id: String, - #[pb(index = 2)] - pub name: String, + #[pb(index = 2)] + pub name: String, - #[pb(index = 3)] - pub desc: String, + #[pb(index = 3)] + pub desc: String, - #[pb(index = 4, one_of)] - pub thumbnail: Option, + #[pb(index = 4, one_of)] + pub thumbnail: Option, - #[pb(index = 5)] - pub data_format: ViewDataFormatPB, + #[pb(index = 5)] + pub data_format: ViewDataFormatPB, - #[pb(index = 6)] - pub layout: ViewLayoutTypePB, + #[pb(index = 6)] + pub layout: ViewLayoutTypePB, - #[pb(index = 7)] - pub initial_data: Vec, + #[pb(index = 7)] + pub initial_data: Vec, } #[derive(Debug, Clone)] pub struct CreateViewParams { - pub belong_to_id: String, - pub name: String, - pub desc: String, - pub thumbnail: String, - pub data_format: ViewDataFormatPB, - pub layout: ViewLayoutTypePB, - pub view_id: String, - pub initial_data: Vec, + pub belong_to_id: String, + pub name: String, + pub desc: String, + pub thumbnail: String, + pub data_format: ViewDataFormatPB, + pub layout: ViewLayoutTypePB, + pub view_id: String, + pub initial_data: Vec, } impl TryInto for CreateViewPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let name = ViewName::parse(self.name)?.0; - let belong_to_id = AppIdentify::parse(self.belong_to_id)?.0; - let view_id = gen_view_id(); - let thumbnail = match self.thumbnail { - None => "".to_string(), - Some(thumbnail) => ViewThumbnail::parse(thumbnail)?.0, - }; + fn try_into(self) -> Result { + let name = ViewName::parse(self.name)?.0; + let belong_to_id = AppIdentify::parse(self.belong_to_id)?.0; + let view_id = gen_view_id(); + let thumbnail = match self.thumbnail { + None => "".to_string(), + Some(thumbnail) => ViewThumbnail::parse(thumbnail)?.0, + }; - Ok(CreateViewParams { - belong_to_id, - name, - desc: self.desc, - data_format: self.data_format, - layout: self.layout, - thumbnail, - view_id, - initial_data: self.initial_data, - }) - } + Ok(CreateViewParams { + belong_to_id, + name, + desc: self.desc, + data_format: self.data_format, + layout: self.layout, + thumbnail, + view_id, + initial_data: self.initial_data, + }) + } } #[derive(Default, ProtoBuf, Clone, Debug)] pub struct ViewIdPB { - #[pb(index = 1)] - pub value: String, + #[pb(index = 1)] + pub value: String, } impl std::convert::From<&str> for ViewIdPB { - fn from(value: &str) -> Self { - ViewIdPB { - value: value.to_string(), - } + fn from(value: &str) -> Self { + ViewIdPB { + value: value.to_string(), } + } } #[derive(Default, ProtoBuf, Clone, Debug)] pub struct DeletedViewPB { - #[pb(index = 1)] - pub view_id: String, + #[pb(index = 1)] + pub view_id: String, - #[pb(index = 2, one_of)] - pub index: Option, + #[pb(index = 2, one_of)] + pub index: Option, } impl std::ops::Deref for ViewIdPB { - type Target = str; + type Target = str; - fn deref(&self) -> &Self::Target { - &self.value - } + fn deref(&self) -> &Self::Target { + &self.value + } } #[derive(Default, ProtoBuf)] pub struct UpdateViewPayloadPB { - #[pb(index = 1)] - pub view_id: String, + #[pb(index = 1)] + pub view_id: String, - #[pb(index = 2, one_of)] - pub name: Option, + #[pb(index = 2, one_of)] + pub name: Option, - #[pb(index = 3, one_of)] - pub desc: Option, + #[pb(index = 3, one_of)] + pub desc: Option, - #[pb(index = 4, one_of)] - pub thumbnail: Option, + #[pb(index = 4, one_of)] + pub thumbnail: Option, } #[derive(Clone, Debug)] pub struct UpdateViewParams { - pub view_id: String, - pub name: Option, - pub desc: Option, - pub thumbnail: Option, + pub view_id: String, + pub name: Option, + pub desc: Option, + pub thumbnail: Option, } impl TryInto for UpdateViewPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let view_id = ViewIdentify::parse(self.view_id)?.0; + fn try_into(self) -> Result { + let view_id = ViewIdentify::parse(self.view_id)?.0; - let name = match self.name { - None => None, - Some(name) => Some(ViewName::parse(name)?.0), - }; + let name = match self.name { + None => None, + Some(name) => Some(ViewName::parse(name)?.0), + }; - let desc = match self.desc { - None => None, - Some(desc) => Some(ViewDesc::parse(desc)?.0), - }; + let desc = match self.desc { + None => None, + Some(desc) => Some(ViewDesc::parse(desc)?.0), + }; - let thumbnail = match self.thumbnail { - None => None, - Some(thumbnail) => Some(ViewThumbnail::parse(thumbnail)?.0), - }; + let thumbnail = match self.thumbnail { + None => None, + Some(thumbnail) => Some(ViewThumbnail::parse(thumbnail)?.0), + }; - Ok(UpdateViewParams { - view_id, - name, - desc, - thumbnail, - }) - } + Ok(UpdateViewParams { + view_id, + name, + desc, + thumbnail, + }) + } } #[derive(ProtoBuf_Enum)] pub enum MoveFolderItemType { - MoveApp = 0, - MoveView = 1, + MoveApp = 0, + MoveView = 1, } impl std::default::Default for MoveFolderItemType { - fn default() -> Self { - MoveFolderItemType::MoveApp - } + fn default() -> Self { + MoveFolderItemType::MoveApp + } } #[derive(Default, ProtoBuf)] pub struct MoveFolderItemPayloadPB { - #[pb(index = 1)] - pub item_id: String, + #[pb(index = 1)] + pub item_id: String, - #[pb(index = 2)] - pub from: i32, + #[pb(index = 2)] + pub from: i32, - #[pb(index = 3)] - pub to: i32, + #[pb(index = 3)] + pub to: i32, - #[pb(index = 4)] - pub ty: MoveFolderItemType, + #[pb(index = 4)] + pub ty: MoveFolderItemType, } pub struct MoveFolderItemParams { - pub item_id: String, - pub from: usize, - pub to: usize, - pub ty: MoveFolderItemType, + pub item_id: String, + pub from: usize, + pub to: usize, + pub ty: MoveFolderItemType, } impl TryInto for MoveFolderItemPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let view_id = ViewIdentify::parse(self.item_id)?.0; - Ok(MoveFolderItemParams { - item_id: view_id, - from: self.from as usize, - to: self.to as usize, - ty: self.ty, - }) - } + fn try_into(self) -> Result { + let view_id = ViewIdentify::parse(self.item_id)?.0; + Ok(MoveFolderItemParams { + item_id: view_id, + from: self.from as usize, + to: self.to as usize, + ty: self.ty, + }) + } } // impl<'de> Deserialize<'de> for ViewDataType { diff --git a/frontend/rust-lib/flowy-folder/src/entities/workspace.rs b/frontend/rust-lib/flowy-folder/src/entities/workspace.rs index 63abfd2af3..12c0ceeae1 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/workspace.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/workspace.rs @@ -1,8 +1,8 @@ use crate::{ - entities::parser::workspace::{WorkspaceDesc, WorkspaceIdentify, WorkspaceName}, - entities::{app::RepeatedAppPB, view::ViewPB}, - errors::*, - impl_def_and_def_mut, + entities::parser::workspace::{WorkspaceDesc, WorkspaceIdentify, WorkspaceName}, + entities::{app::RepeatedAppPB, view::ViewPB}, + errors::*, + impl_def_and_def_mut, }; use flowy_derive::ProtoBuf; use folder_model::WorkspaceRevision; @@ -10,129 +10,131 @@ use std::convert::TryInto; #[derive(Eq, PartialEq, ProtoBuf, Default, Debug, Clone)] pub struct WorkspacePB { - #[pb(index = 1)] - pub id: String, + #[pb(index = 1)] + pub id: String, - #[pb(index = 2)] - pub name: String, + #[pb(index = 2)] + pub name: String, - #[pb(index = 3)] - pub desc: String, + #[pb(index = 3)] + pub desc: String, - #[pb(index = 4)] - pub apps: RepeatedAppPB, + #[pb(index = 4)] + pub apps: RepeatedAppPB, - #[pb(index = 5)] - pub modified_time: i64, + #[pb(index = 5)] + pub modified_time: i64, - #[pb(index = 6)] - pub create_time: i64, + #[pb(index = 6)] + pub create_time: i64, } impl std::convert::From for WorkspacePB { - fn from(workspace_serde: WorkspaceRevision) -> Self { - WorkspacePB { - id: workspace_serde.id, - name: workspace_serde.name, - desc: workspace_serde.desc, - apps: workspace_serde.apps.into(), - modified_time: workspace_serde.modified_time, - create_time: workspace_serde.create_time, - } + fn from(workspace_serde: WorkspaceRevision) -> Self { + WorkspacePB { + id: workspace_serde.id, + name: workspace_serde.name, + desc: workspace_serde.desc, + apps: workspace_serde.apps.into(), + modified_time: workspace_serde.modified_time, + create_time: workspace_serde.create_time, } + } } #[derive(PartialEq, Eq, Debug, Default, ProtoBuf)] pub struct RepeatedWorkspacePB { - #[pb(index = 1)] - pub items: Vec, + #[pb(index = 1)] + pub items: Vec, } impl_def_and_def_mut!(RepeatedWorkspacePB, WorkspacePB); #[derive(ProtoBuf, Default)] pub struct CreateWorkspacePayloadPB { - #[pb(index = 1)] - pub name: String, + #[pb(index = 1)] + pub name: String, - #[pb(index = 2)] - pub desc: String, + #[pb(index = 2)] + pub desc: String, } #[derive(Clone, Debug)] pub struct CreateWorkspaceParams { - pub name: String, - pub desc: String, + pub name: String, + pub desc: String, } impl TryInto for CreateWorkspacePayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let name = WorkspaceName::parse(self.name)?; - let desc = WorkspaceDesc::parse(self.desc)?; + fn try_into(self) -> Result { + let name = WorkspaceName::parse(self.name)?; + let desc = WorkspaceDesc::parse(self.desc)?; - Ok(CreateWorkspaceParams { - name: name.0, - desc: desc.0, - }) - } + Ok(CreateWorkspaceParams { + name: name.0, + desc: desc.0, + }) + } } // Read all workspaces if the workspace_id is None #[derive(Clone, ProtoBuf, Default, Debug)] pub struct WorkspaceIdPB { - #[pb(index = 1, one_of)] - pub value: Option, + #[pb(index = 1, one_of)] + pub value: Option, } impl WorkspaceIdPB { - pub fn new(workspace_id: Option) -> Self { - Self { value: workspace_id } + pub fn new(workspace_id: Option) -> Self { + Self { + value: workspace_id, } + } } #[derive(Default, ProtoBuf, Clone)] pub struct WorkspaceSettingPB { - #[pb(index = 1)] - pub workspace: WorkspacePB, + #[pb(index = 1)] + pub workspace: WorkspacePB, - #[pb(index = 2, one_of)] - pub latest_view: Option, + #[pb(index = 2, one_of)] + pub latest_view: Option, } #[derive(ProtoBuf, Default)] pub struct UpdateWorkspacePayloadPB { - #[pb(index = 1)] - pub id: String, + #[pb(index = 1)] + pub id: String, - #[pb(index = 2, one_of)] - pub name: Option, + #[pb(index = 2, one_of)] + pub name: Option, - #[pb(index = 3, one_of)] - pub desc: Option, + #[pb(index = 3, one_of)] + pub desc: Option, } #[derive(Clone, Debug)] pub struct UpdateWorkspaceParams { - pub id: String, - pub name: Option, - pub desc: Option, + pub id: String, + pub name: Option, + pub desc: Option, } impl TryInto for UpdateWorkspacePayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let name = match self.name { - None => None, - Some(name) => Some(WorkspaceName::parse(name)?.0), - }; - let id = WorkspaceIdentify::parse(self.id)?; + fn try_into(self) -> Result { + let name = match self.name { + None => None, + Some(name) => Some(WorkspaceName::parse(name)?.0), + }; + let id = WorkspaceIdentify::parse(self.id)?; - Ok(UpdateWorkspaceParams { - id: id.0, - name, - desc: self.desc, - }) - } + Ok(UpdateWorkspaceParams { + id: id.0, + name, + desc: self.desc, + }) + } } diff --git a/frontend/rust-lib/flowy-folder/src/event_map.rs b/frontend/rust-lib/flowy-folder/src/event_map.rs index 3c8385b925..9790b26f14 100644 --- a/frontend/rust-lib/flowy-folder/src/event_map.rs +++ b/frontend/rust-lib/flowy-folder/src/event_map.rs @@ -1,13 +1,16 @@ use crate::{ - entities::{ - app::{AppIdPB, CreateAppParams, UpdateAppParams}, - trash::RepeatedTrashIdPB, - view::{CreateViewParams, RepeatedViewIdPB, UpdateViewParams, ViewIdPB}, - workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceIdPB}, - }, - errors::FlowyError, - manager::FolderManager, - services::{app::event_handler::*, trash::event_handler::*, view::event_handler::*, workspace::event_handler::*}, + entities::{ + app::{AppIdPB, CreateAppParams, UpdateAppParams}, + trash::RepeatedTrashIdPB, + view::{CreateViewParams, RepeatedViewIdPB, UpdateViewParams, ViewIdPB}, + workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceIdPB}, + }, + errors::FlowyError, + manager::FolderManager, + services::{ + app::event_handler::*, trash::event_handler::*, view::event_handler::*, + workspace::event_handler::*, + }, }; use flowy_derive::{Flowy_Event, ProtoBuf_Enum}; use flowy_sqlite::{ConnectionPool, DBConnection}; @@ -20,204 +23,228 @@ use strum_macros::Display; pub trait WorkspaceDeps: WorkspaceUser + WorkspaceDatabase {} pub trait WorkspaceUser: Send + Sync { - fn user_id(&self) -> Result; - fn token(&self) -> Result; + fn user_id(&self) -> Result; + fn token(&self) -> Result; } pub trait WorkspaceDatabase: Send + Sync { - fn db_pool(&self) -> Result, FlowyError>; + fn db_pool(&self) -> Result, FlowyError>; - fn db_connection(&self) -> Result { - let pool = self.db_pool()?; - let conn = pool.get().map_err(|e| FlowyError::internal().context(e))?; - Ok(conn) - } + fn db_connection(&self) -> Result { + let pool = self.db_pool()?; + let conn = pool.get().map_err(|e| FlowyError::internal().context(e))?; + Ok(conn) + } } pub fn init(folder: Arc) -> AFPlugin { - let mut plugin = AFPlugin::new() - .name("Flowy-Workspace") - .state(folder.workspace_controller.clone()) - .state(folder.app_controller.clone()) - .state(folder.view_controller.clone()) - .state(folder.trash_controller.clone()) - .state(folder.clone()); + let mut plugin = AFPlugin::new() + .name("Flowy-Workspace") + .state(folder.workspace_controller.clone()) + .state(folder.app_controller.clone()) + .state(folder.view_controller.clone()) + .state(folder.trash_controller.clone()) + .state(folder.clone()); - // Workspace - plugin = plugin - .event(FolderEvent::CreateWorkspace, create_workspace_handler) - .event(FolderEvent::ReadCurrentWorkspace, read_cur_workspace_handler) - .event(FolderEvent::ReadWorkspaces, read_workspaces_handler) - .event(FolderEvent::OpenWorkspace, open_workspace_handler) - .event(FolderEvent::ReadWorkspaceApps, read_workspace_apps_handler); + // Workspace + plugin = plugin + .event(FolderEvent::CreateWorkspace, create_workspace_handler) + .event( + FolderEvent::ReadCurrentWorkspace, + read_cur_workspace_handler, + ) + .event(FolderEvent::ReadWorkspaces, read_workspaces_handler) + .event(FolderEvent::OpenWorkspace, open_workspace_handler) + .event(FolderEvent::ReadWorkspaceApps, read_workspace_apps_handler); - // App - plugin = plugin - .event(FolderEvent::CreateApp, create_app_handler) - .event(FolderEvent::ReadApp, read_app_handler) - .event(FolderEvent::UpdateApp, update_app_handler) - .event(FolderEvent::DeleteApp, delete_app_handler); + // App + plugin = plugin + .event(FolderEvent::CreateApp, create_app_handler) + .event(FolderEvent::ReadApp, read_app_handler) + .event(FolderEvent::UpdateApp, update_app_handler) + .event(FolderEvent::DeleteApp, delete_app_handler); - // View - plugin = plugin - .event(FolderEvent::CreateView, create_view_handler) - .event(FolderEvent::ReadView, read_view_handler) - .event(FolderEvent::UpdateView, update_view_handler) - .event(FolderEvent::DeleteView, delete_view_handler) - .event(FolderEvent::DuplicateView, duplicate_view_handler) - .event(FolderEvent::SetLatestView, set_latest_view_handler) - .event(FolderEvent::CloseView, close_view_handler) - .event(FolderEvent::MoveItem, move_item_handler); + // View + plugin = plugin + .event(FolderEvent::CreateView, create_view_handler) + .event(FolderEvent::ReadView, read_view_handler) + .event(FolderEvent::UpdateView, update_view_handler) + .event(FolderEvent::DeleteView, delete_view_handler) + .event(FolderEvent::DuplicateView, duplicate_view_handler) + .event(FolderEvent::SetLatestView, set_latest_view_handler) + .event(FolderEvent::CloseView, close_view_handler) + .event(FolderEvent::MoveItem, move_item_handler); - // Trash - plugin = plugin - .event(FolderEvent::ReadTrash, read_trash_handler) - .event(FolderEvent::PutbackTrash, putback_trash_handler) - .event(FolderEvent::DeleteTrash, delete_trash_handler) - .event(FolderEvent::RestoreAllTrash, restore_all_trash_handler) - .event(FolderEvent::DeleteAllTrash, delete_all_trash_handler); + // Trash + plugin = plugin + .event(FolderEvent::ReadTrash, read_trash_handler) + .event(FolderEvent::PutbackTrash, putback_trash_handler) + .event(FolderEvent::DeleteTrash, delete_trash_handler) + .event(FolderEvent::RestoreAllTrash, restore_all_trash_handler) + .event(FolderEvent::DeleteAllTrash, delete_all_trash_handler); - plugin + plugin } #[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)] #[event_err = "FlowyError"] pub enum FolderEvent { - /// Create a new workspace - #[event(input = "CreateWorkspacePayloadPB", output = "WorkspacePB")] - CreateWorkspace = 0, + /// Create a new workspace + #[event(input = "CreateWorkspacePayloadPB", output = "WorkspacePB")] + CreateWorkspace = 0, - /// Read the current opening workspace - #[event(output = "WorkspaceSettingPB")] - ReadCurrentWorkspace = 1, + /// Read the current opening workspace + #[event(output = "WorkspaceSettingPB")] + ReadCurrentWorkspace = 1, - /// Open the workspace and mark it as the current workspace - #[event(input = "WorkspaceIdPB", output = "RepeatedWorkspacePB")] - ReadWorkspaces = 2, + /// Open the workspace and mark it as the current workspace + #[event(input = "WorkspaceIdPB", output = "RepeatedWorkspacePB")] + ReadWorkspaces = 2, - /// Delete the workspace - #[event(input = "WorkspaceIdPB")] - DeleteWorkspace = 3, + /// Delete the workspace + #[event(input = "WorkspaceIdPB")] + DeleteWorkspace = 3, - /// Open the workspace and mark it as the current workspace - #[event(input = "WorkspaceIdPB", output = "WorkspacePB")] - OpenWorkspace = 4, + /// Open the workspace and mark it as the current workspace + #[event(input = "WorkspaceIdPB", output = "WorkspacePB")] + OpenWorkspace = 4, - /// Return a list of apps that belong to this workspace - #[event(input = "WorkspaceIdPB", output = "RepeatedAppPB")] - ReadWorkspaceApps = 5, + /// Return a list of apps that belong to this workspace + #[event(input = "WorkspaceIdPB", output = "RepeatedAppPB")] + ReadWorkspaceApps = 5, - /// Create a new app - #[event(input = "CreateAppPayloadPB", output = "AppPB")] - CreateApp = 101, + /// Create a new app + #[event(input = "CreateAppPayloadPB", output = "AppPB")] + CreateApp = 101, - /// Delete the app - #[event(input = "AppIdPB")] - DeleteApp = 102, + /// Delete the app + #[event(input = "AppIdPB")] + DeleteApp = 102, - /// Read the app - #[event(input = "AppIdPB", output = "AppPB")] - ReadApp = 103, + /// Read the app + #[event(input = "AppIdPB", output = "AppPB")] + ReadApp = 103, - /// Update the app's properties including the name,description, etc. - #[event(input = "UpdateAppPayloadPB")] - UpdateApp = 104, + /// Update the app's properties including the name,description, etc. + #[event(input = "UpdateAppPayloadPB")] + UpdateApp = 104, - /// Create a new view in the corresponding app - #[event(input = "CreateViewPayloadPB", output = "ViewPB")] - CreateView = 201, + /// Create a new view in the corresponding app + #[event(input = "CreateViewPayloadPB", output = "ViewPB")] + CreateView = 201, - /// Return the view info - #[event(input = "ViewIdPB", output = "ViewPB")] - ReadView = 202, + /// Return the view info + #[event(input = "ViewIdPB", output = "ViewPB")] + ReadView = 202, - /// Update the view's properties including the name,description, etc. - #[event(input = "UpdateViewPayloadPB", output = "ViewPB")] - UpdateView = 203, + /// Update the view's properties including the name,description, etc. + #[event(input = "UpdateViewPayloadPB", output = "ViewPB")] + UpdateView = 203, - /// Move the view to the trash folder - #[event(input = "RepeatedViewIdPB")] - DeleteView = 204, + /// Move the view to the trash folder + #[event(input = "RepeatedViewIdPB")] + DeleteView = 204, - /// Duplicate the view - #[event(input = "ViewPB")] - DuplicateView = 205, + /// Duplicate the view + #[event(input = "ViewPB")] + DuplicateView = 205, - /// Close and release the resources that are used by this view. - /// It should get called when the 'View' page get destroy - #[event(input = "ViewIdPB")] - CloseView = 206, + /// Close and release the resources that are used by this view. + /// It should get called when the 'View' page get destroy + #[event(input = "ViewIdPB")] + CloseView = 206, - #[event()] - CopyLink = 220, + #[event()] + CopyLink = 220, - /// Set the current visiting view - #[event(input = "ViewIdPB")] - SetLatestView = 221, + /// Set the current visiting view + #[event(input = "ViewIdPB")] + SetLatestView = 221, - /// Move the view or app to another place - #[event(input = "MoveFolderItemPayloadPB")] - MoveItem = 230, + /// Move the view or app to another place + #[event(input = "MoveFolderItemPayloadPB")] + MoveItem = 230, - /// Read the trash that was deleted by the user - #[event(output = "RepeatedTrashPB")] - ReadTrash = 300, + /// Read the trash that was deleted by the user + #[event(output = "RepeatedTrashPB")] + ReadTrash = 300, - /// Put back the trash to the origin folder - #[event(input = "TrashIdPB")] - PutbackTrash = 301, + /// Put back the trash to the origin folder + #[event(input = "TrashIdPB")] + PutbackTrash = 301, - /// Delete the trash from the disk - #[event(input = "RepeatedTrashIdPB")] - DeleteTrash = 302, + /// Delete the trash from the disk + #[event(input = "RepeatedTrashIdPB")] + DeleteTrash = 302, - /// Put back all the trash to its original folder - #[event()] - RestoreAllTrash = 303, + /// Put back all the trash to its original folder + #[event()] + RestoreAllTrash = 303, - /// Delete all the trash from the disk - #[event()] - DeleteAllTrash = 304, + /// Delete all the trash from the disk + #[event()] + DeleteAllTrash = 304, } pub trait FolderCouldServiceV1: Send + Sync { - fn init(&self); + fn init(&self); - // Workspace - fn create_workspace( - &self, - token: &str, - params: CreateWorkspaceParams, - ) -> FutureResult; + // Workspace + fn create_workspace( + &self, + token: &str, + params: CreateWorkspaceParams, + ) -> FutureResult; - fn read_workspace(&self, token: &str, params: WorkspaceIdPB) -> FutureResult, FlowyError>; + fn read_workspace( + &self, + token: &str, + params: WorkspaceIdPB, + ) -> FutureResult, FlowyError>; - fn update_workspace(&self, token: &str, params: UpdateWorkspaceParams) -> FutureResult<(), FlowyError>; + fn update_workspace( + &self, + token: &str, + params: UpdateWorkspaceParams, + ) -> FutureResult<(), FlowyError>; - fn delete_workspace(&self, token: &str, params: WorkspaceIdPB) -> FutureResult<(), FlowyError>; + fn delete_workspace(&self, token: &str, params: WorkspaceIdPB) -> FutureResult<(), FlowyError>; - // View - fn create_view(&self, token: &str, params: CreateViewParams) -> FutureResult; + // View + fn create_view( + &self, + token: &str, + params: CreateViewParams, + ) -> FutureResult; - fn read_view(&self, token: &str, params: ViewIdPB) -> FutureResult, FlowyError>; + fn read_view( + &self, + token: &str, + params: ViewIdPB, + ) -> FutureResult, FlowyError>; - fn delete_view(&self, token: &str, params: RepeatedViewIdPB) -> FutureResult<(), FlowyError>; + fn delete_view(&self, token: &str, params: RepeatedViewIdPB) -> FutureResult<(), FlowyError>; - fn update_view(&self, token: &str, params: UpdateViewParams) -> FutureResult<(), FlowyError>; + fn update_view(&self, token: &str, params: UpdateViewParams) -> FutureResult<(), FlowyError>; - // App - fn create_app(&self, token: &str, params: CreateAppParams) -> FutureResult; + // App + fn create_app( + &self, + token: &str, + params: CreateAppParams, + ) -> FutureResult; - fn read_app(&self, token: &str, params: AppIdPB) -> FutureResult, FlowyError>; + fn read_app(&self, token: &str, params: AppIdPB) + -> FutureResult, FlowyError>; - fn update_app(&self, token: &str, params: UpdateAppParams) -> FutureResult<(), FlowyError>; + fn update_app(&self, token: &str, params: UpdateAppParams) -> FutureResult<(), FlowyError>; - fn delete_app(&self, token: &str, params: AppIdPB) -> FutureResult<(), FlowyError>; + fn delete_app(&self, token: &str, params: AppIdPB) -> FutureResult<(), FlowyError>; - // Trash - fn create_trash(&self, token: &str, params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError>; + // Trash + fn create_trash(&self, token: &str, params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError>; - fn delete_trash(&self, token: &str, params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError>; + fn delete_trash(&self, token: &str, params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError>; - fn read_trash(&self, token: &str) -> FutureResult, FlowyError>; + fn read_trash(&self, token: &str) -> FutureResult, FlowyError>; } diff --git a/frontend/rust-lib/flowy-folder/src/lib.rs b/frontend/rust-lib/flowy-folder/src/lib.rs index d34c6a4a2f..9fa68c3360 100644 --- a/frontend/rust-lib/flowy-folder/src/lib.rs +++ b/frontend/rust-lib/flowy-folder/src/lib.rs @@ -14,9 +14,9 @@ pub mod protobuf; mod util; pub mod prelude { - pub use crate::{errors::*, event_map::*}; + pub use crate::{errors::*, event_map::*}; } pub mod errors { - pub use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult}; + pub use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult}; } diff --git a/frontend/rust-lib/flowy-folder/src/macros.rs b/frontend/rust-lib/flowy-folder/src/macros.rs index 11ff6b71f1..217aafe47f 100644 --- a/frontend/rust-lib/flowy-folder/src/macros.rs +++ b/frontend/rust-lib/flowy-folder/src/macros.rs @@ -11,39 +11,39 @@ #[macro_export] macro_rules! impl_def_and_def_mut { - ($target:ident, $item: ident) => { - impl std::ops::Deref for $target { - type Target = Vec<$item>; + ($target:ident, $item: ident) => { + impl std::ops::Deref for $target { + type Target = Vec<$item>; - fn deref(&self) -> &Self::Target { - &self.items - } - } - impl std::ops::DerefMut for $target { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.items - } + fn deref(&self) -> &Self::Target { + &self.items + } + } + impl std::ops::DerefMut for $target { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.items + } + } + + impl $target { + #[allow(dead_code)] + pub fn into_inner(&mut self) -> Vec<$item> { + ::std::mem::take(&mut self.items) + } + + #[allow(dead_code)] + pub fn push(&mut self, item: $item) { + if self.items.contains(&item) { + log::error!("add duplicate item: {:?}", item); + return; } - impl $target { - #[allow(dead_code)] - pub fn into_inner(&mut self) -> Vec<$item> { - ::std::mem::take(&mut self.items) - } + self.items.push(item); + } - #[allow(dead_code)] - pub fn push(&mut self, item: $item) { - if self.items.contains(&item) { - log::error!("add duplicate item: {:?}", item); - return; - } - - self.items.push(item); - } - - pub fn first_or_crash(&self) -> &$item { - self.items.first().unwrap() - } - } - }; + pub fn first_or_crash(&self) -> &$item { + self.items.first().unwrap() + } + } + }; } diff --git a/frontend/rust-lib/flowy-folder/src/manager.rs b/frontend/rust-lib/flowy-folder/src/manager.rs index 036458e966..84307d927e 100644 --- a/frontend/rust-lib/flowy-folder/src/manager.rs +++ b/frontend/rust-lib/flowy-folder/src/manager.rs @@ -2,26 +2,28 @@ use crate::entities::view::ViewDataFormatPB; use crate::entities::{ViewLayoutTypePB, ViewPB}; use crate::services::folder_editor::FolderRevisionMergeable; use crate::{ - entities::workspace::RepeatedWorkspacePB, - errors::FlowyResult, - event_map::{FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser}, - notification::{send_notification, FolderNotification}, - services::{ - folder_editor::FolderEditor, persistence::FolderPersistence, set_current_workspace, AppController, - TrashController, ViewController, WorkspaceController, - }, + entities::workspace::RepeatedWorkspacePB, + errors::FlowyResult, + event_map::{FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser}, + notification::{send_notification, FolderNotification}, + services::{ + folder_editor::FolderEditor, persistence::FolderPersistence, set_current_workspace, + AppController, TrashController, ViewController, WorkspaceController, + }, }; use bytes::Bytes; use flowy_document::editor::initial_read_me; use flowy_error::FlowyError; -use flowy_revision::{RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, RevisionWebSocket}; +use flowy_revision::{ + RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, RevisionWebSocket, +}; use folder_model::user_default; use lazy_static::lazy_static; use lib_infra::future::FutureResult; use crate::services::clear_current_workspace; use crate::services::persistence::rev_sqlite::{ - SQLiteFolderRevisionPersistence, SQLiteFolderRevisionSnapshotPersistence, + SQLiteFolderRevisionPersistence, SQLiteFolderRevisionSnapshotPersistence, }; use flowy_client_sync::client_folder::FolderPad; use std::convert::TryFrom; @@ -29,268 +31,288 @@ use std::{collections::HashMap, fmt::Formatter, sync::Arc}; use tokio::sync::RwLock as TokioRwLock; use ws_model::ws_revision::ServerRevisionWSData; lazy_static! { - static ref INIT_FOLDER_FLAG: TokioRwLock> = TokioRwLock::new(HashMap::new()); + static ref INIT_FOLDER_FLAG: TokioRwLock> = + TokioRwLock::new(HashMap::new()); } const FOLDER_ID: &str = "folder"; const FOLDER_ID_SPLIT: &str = ":"; #[derive(Clone)] pub struct FolderId(String); impl FolderId { - pub fn new(user_id: &str) -> Self { - Self(format!("{}{}{}", user_id, FOLDER_ID_SPLIT, FOLDER_ID)) - } + pub fn new(user_id: &str) -> Self { + Self(format!("{}{}{}", user_id, FOLDER_ID_SPLIT, FOLDER_ID)) + } } impl std::fmt::Display for FolderId { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(FOLDER_ID) - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(FOLDER_ID) + } } impl std::fmt::Debug for FolderId { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(FOLDER_ID) - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(FOLDER_ID) + } } impl AsRef for FolderId { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } pub struct FolderManager { - pub user: Arc, - pub(crate) persistence: Arc, - pub(crate) workspace_controller: Arc, - pub(crate) app_controller: Arc, - pub(crate) view_controller: Arc, - pub(crate) trash_controller: Arc, - web_socket: Arc, - folder_editor: Arc>>>, + pub user: Arc, + pub(crate) persistence: Arc, + pub(crate) workspace_controller: Arc, + pub(crate) app_controller: Arc, + pub(crate) view_controller: Arc, + pub(crate) trash_controller: Arc, + web_socket: Arc, + folder_editor: Arc>>>, } impl FolderManager { - pub async fn new( - user: Arc, - cloud_service: Arc, - database: Arc, - data_processors: ViewDataProcessorMap, - web_socket: Arc, - ) -> Self { - if let Ok(user_id) = user.user_id() { - // Reset the flag if the folder manager gets initialized, otherwise, - // the folder_editor will not be initialized after flutter hot reload. - INIT_FOLDER_FLAG.write().await.insert(user_id.to_owned(), false); - } - - let folder_editor = Arc::new(TokioRwLock::new(None)); - let persistence = Arc::new(FolderPersistence::new(database.clone(), folder_editor.clone())); - - let trash_controller = Arc::new(TrashController::new( - persistence.clone(), - cloud_service.clone(), - user.clone(), - )); - - let view_controller = Arc::new(ViewController::new( - user.clone(), - persistence.clone(), - cloud_service.clone(), - trash_controller.clone(), - data_processors, - )); - - let app_controller = Arc::new(AppController::new( - user.clone(), - persistence.clone(), - trash_controller.clone(), - cloud_service.clone(), - )); - - let workspace_controller = Arc::new(WorkspaceController::new( - user.clone(), - persistence.clone(), - trash_controller.clone(), - cloud_service.clone(), - )); - - Self { - user, - persistence, - workspace_controller, - app_controller, - view_controller, - trash_controller, - web_socket, - folder_editor, - } + pub async fn new( + user: Arc, + cloud_service: Arc, + database: Arc, + data_processors: ViewDataProcessorMap, + web_socket: Arc, + ) -> Self { + if let Ok(user_id) = user.user_id() { + // Reset the flag if the folder manager gets initialized, otherwise, + // the folder_editor will not be initialized after flutter hot reload. + INIT_FOLDER_FLAG + .write() + .await + .insert(user_id.to_owned(), false); } - // pub fn network_state_changed(&self, new_type: NetworkType) { - // match new_type { - // NetworkType::UnknownNetworkType => {}, - // NetworkType::Wifi => {}, - // NetworkType::Cell => {}, - // NetworkType::Ethernet => {}, - // } - // } + let folder_editor = Arc::new(TokioRwLock::new(None)); + let persistence = Arc::new(FolderPersistence::new( + database.clone(), + folder_editor.clone(), + )); - pub async fn did_receive_ws_data(&self, data: Bytes) { - let result = ServerRevisionWSData::try_from(data); - match result { - Ok(data) => match self.folder_editor.read().await.clone() { - None => {} - Some(editor) => match editor.receive_ws_data(data).await { - Ok(_) => {} - Err(e) => tracing::error!("Folder receive data error: {:?}", e), - }, - }, - Err(e) => { - tracing::error!("Folder ws data parser failed: {:?}", e); - } - } + let trash_controller = Arc::new(TrashController::new( + persistence.clone(), + cloud_service.clone(), + user.clone(), + )); + + let view_controller = Arc::new(ViewController::new( + user.clone(), + persistence.clone(), + cloud_service.clone(), + trash_controller.clone(), + data_processors, + )); + + let app_controller = Arc::new(AppController::new( + user.clone(), + persistence.clone(), + trash_controller.clone(), + cloud_service.clone(), + )); + + let workspace_controller = Arc::new(WorkspaceController::new( + user.clone(), + persistence.clone(), + trash_controller.clone(), + cloud_service.clone(), + )); + + Self { + user, + persistence, + workspace_controller, + app_controller, + view_controller, + trash_controller, + web_socket, + folder_editor, } + } - /// Called immediately after the application launched with the user sign in/sign up. - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn initialize(&self, user_id: &str, token: &str) -> FlowyResult<()> { - let mut write_guard = INIT_FOLDER_FLAG.write().await; - if let Some(is_init) = write_guard.get(user_id) { - if *is_init { - return Ok(()); - } - } - tracing::debug!("Initialize folder editor"); - let folder_id = FolderId::new(user_id); - self.persistence.initialize(user_id, &folder_id).await?; + // pub fn network_state_changed(&self, new_type: NetworkType) { + // match new_type { + // NetworkType::UnknownNetworkType => {}, + // NetworkType::Wifi => {}, + // NetworkType::Cell => {}, + // NetworkType::Ethernet => {}, + // } + // } - let pool = self.persistence.db_pool()?; - let object_id = folder_id.as_ref(); - let disk_cache = SQLiteFolderRevisionPersistence::new(user_id, pool.clone()); - let configuration = RevisionPersistenceConfiguration::new(200, false); - let rev_persistence = RevisionPersistence::new(user_id, object_id, disk_cache, configuration); - let rev_compactor = FolderRevisionMergeable(); - - let snapshot_object_id = format!("folder:{}", object_id); - let snapshot_persistence = SQLiteFolderRevisionSnapshotPersistence::new(&snapshot_object_id, pool); - let rev_manager = RevisionManager::new( - user_id, - folder_id.as_ref(), - rev_persistence, - rev_compactor, - snapshot_persistence, - ); - - let folder_editor = FolderEditor::new(user_id, &folder_id, token, rev_manager, self.web_socket.clone()).await?; - *self.folder_editor.write().await = Some(Arc::new(folder_editor)); - - self.app_controller.initialize()?; - self.view_controller.initialize()?; - write_guard.insert(user_id.to_owned(), true); - Ok(()) + pub async fn did_receive_ws_data(&self, data: Bytes) { + let result = ServerRevisionWSData::try_from(data); + match result { + Ok(data) => match self.folder_editor.read().await.clone() { + None => {}, + Some(editor) => match editor.receive_ws_data(data).await { + Ok(_) => {}, + Err(e) => tracing::error!("Folder receive data error: {:?}", e), + }, + }, + Err(e) => { + tracing::error!("Folder ws data parser failed: {:?}", e); + }, } + } - pub async fn initialize_with_new_user( - &self, - user_id: &str, - token: &str, - view_data_format: ViewDataFormatPB, - ) -> FlowyResult<()> { - DefaultFolderBuilder::build( - token, - user_id, - self.persistence.clone(), - self.view_controller.clone(), - || (view_data_format.clone(), Bytes::from(initial_read_me())), - ) - .await?; - self.initialize(user_id, token).await + /// Called immediately after the application launched with the user sign in/sign up. + #[tracing::instrument(level = "trace", skip(self), err)] + pub async fn initialize(&self, user_id: &str, token: &str) -> FlowyResult<()> { + let mut write_guard = INIT_FOLDER_FLAG.write().await; + if let Some(is_init) = write_guard.get(user_id) { + if *is_init { + return Ok(()); + } } + tracing::debug!("Initialize folder editor"); + let folder_id = FolderId::new(user_id); + self.persistence.initialize(user_id, &folder_id).await?; - /// Called when the current user logout - /// - pub async fn clear(&self, user_id: &str) { - self.view_controller.clear_latest_view(); - clear_current_workspace(user_id); - *self.folder_editor.write().await = None; - } + let pool = self.persistence.db_pool()?; + let object_id = folder_id.as_ref(); + let disk_cache = SQLiteFolderRevisionPersistence::new(user_id, pool.clone()); + let configuration = RevisionPersistenceConfiguration::new(200, false); + let rev_persistence = RevisionPersistence::new(user_id, object_id, disk_cache, configuration); + let rev_compactor = FolderRevisionMergeable(); + + let snapshot_object_id = format!("folder:{}", object_id); + let snapshot_persistence = + SQLiteFolderRevisionSnapshotPersistence::new(&snapshot_object_id, pool); + let rev_manager = RevisionManager::new( + user_id, + folder_id.as_ref(), + rev_persistence, + rev_compactor, + snapshot_persistence, + ); + + let folder_editor = FolderEditor::new( + user_id, + &folder_id, + token, + rev_manager, + self.web_socket.clone(), + ) + .await?; + *self.folder_editor.write().await = Some(Arc::new(folder_editor)); + + self.app_controller.initialize()?; + self.view_controller.initialize()?; + write_guard.insert(user_id.to_owned(), true); + Ok(()) + } + + pub async fn initialize_with_new_user( + &self, + user_id: &str, + token: &str, + view_data_format: ViewDataFormatPB, + ) -> FlowyResult<()> { + DefaultFolderBuilder::build( + token, + user_id, + self.persistence.clone(), + self.view_controller.clone(), + || (view_data_format.clone(), Bytes::from(initial_read_me())), + ) + .await?; + self.initialize(user_id, token).await + } + + /// Called when the current user logout + /// + pub async fn clear(&self, user_id: &str) { + self.view_controller.clear_latest_view(); + clear_current_workspace(user_id); + *self.folder_editor.write().await = None; + } } struct DefaultFolderBuilder(); impl DefaultFolderBuilder { - async fn build (ViewDataFormatPB, Bytes)>( - token: &str, - user_id: &str, - persistence: Arc, - view_controller: Arc, - create_view_fn: F, - ) -> FlowyResult<()> { - let workspace_rev = user_default::create_default_workspace(); - tracing::debug!("Create user:{} default workspace:{}", user_id, workspace_rev.id); - set_current_workspace(user_id, &workspace_rev.id); - for app in workspace_rev.apps.iter() { - for (index, view) in app.belongings.iter().enumerate() { - let (view_data_type, view_data) = create_view_fn(); - if index == 0 { - let _ = view_controller.set_latest_view(&view.id); - let layout_type = ViewLayoutTypePB::from(view.layout.clone()); - view_controller - .create_view(&view.id, view_data_type, layout_type, view_data) - .await?; - } - } + async fn build (ViewDataFormatPB, Bytes)>( + token: &str, + user_id: &str, + persistence: Arc, + view_controller: Arc, + create_view_fn: F, + ) -> FlowyResult<()> { + let workspace_rev = user_default::create_default_workspace(); + tracing::debug!( + "Create user:{} default workspace:{}", + user_id, + workspace_rev.id + ); + set_current_workspace(user_id, &workspace_rev.id); + for app in workspace_rev.apps.iter() { + for (index, view) in app.belongings.iter().enumerate() { + let (view_data_type, view_data) = create_view_fn(); + if index == 0 { + let _ = view_controller.set_latest_view(&view.id); + let layout_type = ViewLayoutTypePB::from(view.layout.clone()); + view_controller + .create_view(&view.id, view_data_type, layout_type, view_data) + .await?; } - let folder = FolderPad::new(vec![workspace_rev.clone()], vec![])?; - let folder_id = FolderId::new(user_id); - persistence.save_folder(user_id, &folder_id, folder).await?; - let repeated_workspace = RepeatedWorkspacePB { - items: vec![workspace_rev.into()], - }; - send_notification(token, FolderNotification::DidCreateWorkspace) - .payload(repeated_workspace) - .send(); - Ok(()) + } } + let folder = FolderPad::new(vec![workspace_rev.clone()], vec![])?; + let folder_id = FolderId::new(user_id); + persistence.save_folder(user_id, &folder_id, folder).await?; + let repeated_workspace = RepeatedWorkspacePB { + items: vec![workspace_rev.into()], + }; + send_notification(token, FolderNotification::DidCreateWorkspace) + .payload(repeated_workspace) + .send(); + Ok(()) + } } #[cfg(feature = "flowy_unit_test")] impl FolderManager { - pub async fn folder_editor(&self) -> Arc { - self.folder_editor.read().await.clone().unwrap() - } + pub async fn folder_editor(&self) -> Arc { + self.folder_editor.read().await.clone().unwrap() + } } pub trait ViewDataProcessor { - fn create_view( - &self, - user_id: &str, - view_id: &str, - layout: ViewLayoutTypePB, - view_data: Bytes, - ) -> FutureResult<(), FlowyError>; + fn create_view( + &self, + user_id: &str, + view_id: &str, + layout: ViewLayoutTypePB, + view_data: Bytes, + ) -> FutureResult<(), FlowyError>; - fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError>; + fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError>; - fn get_view_data(&self, view: &ViewPB) -> FutureResult; + fn get_view_data(&self, view: &ViewPB) -> FutureResult; - fn create_default_view( - &self, - user_id: &str, - view_id: &str, - layout: ViewLayoutTypePB, - data_format: ViewDataFormatPB, - ) -> FutureResult; + fn create_default_view( + &self, + user_id: &str, + view_id: &str, + layout: ViewLayoutTypePB, + data_format: ViewDataFormatPB, + ) -> FutureResult; - fn create_view_with_data( - &self, - user_id: &str, - view_id: &str, - data: Vec, - layout: ViewLayoutTypePB, - ) -> FutureResult; + fn create_view_with_data( + &self, + user_id: &str, + view_id: &str, + data: Vec, + layout: ViewLayoutTypePB, + ) -> FutureResult; - fn data_types(&self) -> Vec; + fn data_types(&self) -> Vec; } -pub type ViewDataProcessorMap = Arc>>; +pub type ViewDataProcessorMap = + Arc>>; diff --git a/frontend/rust-lib/flowy-folder/src/notification.rs b/frontend/rust-lib/flowy-folder/src/notification.rs index 903c1ac670..4c9796354f 100644 --- a/frontend/rust-lib/flowy-folder/src/notification.rs +++ b/frontend/rust-lib/flowy-folder/src/notification.rs @@ -4,49 +4,49 @@ const OBSERVABLE_CATEGORY: &str = "Workspace"; #[derive(ProtoBuf_Enum, Debug)] pub(crate) enum FolderNotification { - Unknown = 0, - /// Trigger after creating a workspace - DidCreateWorkspace = 1, - /// Trigger after deleting a workspace - DidDeleteWorkspace = 2, - /// Trigger after updating a workspace - DidUpdateWorkspace = 3, - /// Trigger when the number of apps of the workspace is changed - DidUpdateWorkspaceApps = 4, - /// Trigger when the settings of the workspace are changed. The changes including the latest visiting view, etc - DidUpdateWorkspaceSetting = 5, - /// Trigger when the properties including rename,update description of the app are changed - DidUpdateApp = 20, - /// Trigger when the properties including rename,update description of the view are changed - DidUpdateView = 30, - /// Trigger after deleting the view - DidDeleteView = 31, - /// Trigger when restore the view from trash - DidRestoreView = 32, - /// Trigger after moving the view to trash - DidMoveViewToTrash = 33, - /// Trigger when the number of trash is changed - DidUpdateTrash = 34, + Unknown = 0, + /// Trigger after creating a workspace + DidCreateWorkspace = 1, + /// Trigger after deleting a workspace + DidDeleteWorkspace = 2, + /// Trigger after updating a workspace + DidUpdateWorkspace = 3, + /// Trigger when the number of apps of the workspace is changed + DidUpdateWorkspaceApps = 4, + /// Trigger when the settings of the workspace are changed. The changes including the latest visiting view, etc + DidUpdateWorkspaceSetting = 5, + /// Trigger when the properties including rename,update description of the app are changed + DidUpdateApp = 20, + /// Trigger when the properties including rename,update description of the view are changed + DidUpdateView = 30, + /// Trigger after deleting the view + DidDeleteView = 31, + /// Trigger when restore the view from trash + DidRestoreView = 32, + /// Trigger after moving the view to trash + DidMoveViewToTrash = 33, + /// Trigger when the number of trash is changed + DidUpdateTrash = 34, } impl std::default::Default for FolderNotification { - fn default() -> Self { - FolderNotification::Unknown - } + fn default() -> Self { + FolderNotification::Unknown + } } impl std::convert::From for i32 { - fn from(notification: FolderNotification) -> Self { - notification as i32 - } + fn from(notification: FolderNotification) -> Self { + notification as i32 + } } #[tracing::instrument(level = "trace")] pub(crate) fn send_notification(id: &str, ty: FolderNotification) -> NotificationBuilder { - NotificationBuilder::new(id, ty, OBSERVABLE_CATEGORY) + NotificationBuilder::new(id, ty, OBSERVABLE_CATEGORY) } #[tracing::instrument(level = "trace")] pub(crate) fn send_anonymous_notification(ty: FolderNotification) -> NotificationBuilder { - NotificationBuilder::new("", ty, OBSERVABLE_CATEGORY) + NotificationBuilder::new("", ty, OBSERVABLE_CATEGORY) } diff --git a/frontend/rust-lib/flowy-folder/src/services/app/controller.rs b/frontend/rust-lib/flowy-folder/src/services/app/controller.rs index c6cd030421..af2f0d442b 100644 --- a/frontend/rust-lib/flowy-folder/src/services/app/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/app/controller.rs @@ -1,15 +1,15 @@ use crate::{ - entities::{ - app::{AppPB, CreateAppParams, *}, - trash::TrashType, - }, - errors::*, - event_map::{FolderCouldServiceV1, WorkspaceUser}, - notification::*, - services::{ - persistence::{AppChangeset, FolderPersistence, FolderPersistenceTransaction}, - TrashController, TrashEvent, - }, + entities::{ + app::{AppPB, CreateAppParams, *}, + trash::TrashType, + }, + errors::*, + event_map::{FolderCouldServiceV1, WorkspaceUser}, + notification::*, + services::{ + persistence::{AppChangeset, FolderPersistence, FolderPersistenceTransaction}, + TrashController, TrashEvent, + }, }; use folder_model::AppRevision; @@ -17,219 +17,239 @@ use futures::{FutureExt, StreamExt}; use std::{collections::HashSet, sync::Arc}; pub(crate) struct AppController { + user: Arc, + persistence: Arc, + trash_controller: Arc, + cloud_service: Arc, +} + +impl AppController { + pub(crate) fn new( user: Arc, persistence: Arc, - trash_controller: Arc, + trash_can: Arc, cloud_service: Arc, -} + ) -> Self { + Self { + user, + persistence, + trash_controller: trash_can, + cloud_service, + } + } -impl AppController { - pub(crate) fn new( - user: Arc, - persistence: Arc, - trash_can: Arc, - cloud_service: Arc, - ) -> Self { - Self { - user, - persistence, - trash_controller: trash_can, - cloud_service, + pub fn initialize(&self) -> Result<(), FlowyError> { + self.listen_trash_controller_event(); + Ok(()) + } + + #[tracing::instrument(level = "debug", skip(self, params), fields(name = %params.name) err)] + pub(crate) async fn create_app_from_params( + &self, + params: CreateAppParams, + ) -> Result { + let app = self.create_app_on_server(params).await?; + self.create_app_on_local(app).await + } + + pub(crate) async fn create_app_on_local(&self, app: AppRevision) -> Result { + self + .persistence + .begin_transaction(|transaction| { + transaction.create_app(app.clone())?; + notify_apps_changed( + &app.workspace_id, + self.trash_controller.clone(), + &transaction, + )?; + Ok(()) + }) + .await?; + Ok(app.into()) + } + + pub(crate) async fn read_app(&self, params: AppIdPB) -> Result, FlowyError> { + let app = self + .persistence + .begin_transaction(|transaction| { + let app = transaction.read_app(¶ms.value)?; + let trash_ids = self.trash_controller.read_trash_ids(&transaction)?; + if trash_ids.contains(&app.id) { + return Ok(None); } - } + Ok(Some(app)) + }) + .await?; + Ok(app) + } - pub fn initialize(&self) -> Result<(), FlowyError> { - self.listen_trash_controller_event(); - Ok(()) - } + pub(crate) async fn update_app(&self, params: UpdateAppParams) -> Result<(), FlowyError> { + let changeset = AppChangeset::new(params.clone()); + let app_id = changeset.id.clone(); - #[tracing::instrument(level = "debug", skip(self, params), fields(name = %params.name) err)] - pub(crate) async fn create_app_from_params(&self, params: CreateAppParams) -> Result { - let app = self.create_app_on_server(params).await?; - self.create_app_on_local(app).await - } - - pub(crate) async fn create_app_on_local(&self, app: AppRevision) -> Result { - self.persistence - .begin_transaction(|transaction| { - transaction.create_app(app.clone())?; - notify_apps_changed(&app.workspace_id, self.trash_controller.clone(), &transaction)?; - Ok(()) - }) - .await?; - Ok(app.into()) - } - - pub(crate) async fn read_app(&self, params: AppIdPB) -> Result, FlowyError> { - let app = self - .persistence - .begin_transaction(|transaction| { - let app = transaction.read_app(¶ms.value)?; - let trash_ids = self.trash_controller.read_trash_ids(&transaction)?; - if trash_ids.contains(&app.id) { - return Ok(None); - } - Ok(Some(app)) - }) - .await?; + let app: AppPB = self + .persistence + .begin_transaction(|transaction| { + transaction.update_app(changeset)?; + let app = transaction.read_app(&app_id)?; Ok(app) - } + }) + .await? + .into(); + send_notification(&app_id, FolderNotification::DidUpdateApp) + .payload(app) + .send(); + self.update_app_on_server(params)?; + Ok(()) + } - pub(crate) async fn update_app(&self, params: UpdateAppParams) -> Result<(), FlowyError> { - let changeset = AppChangeset::new(params.clone()); - let app_id = changeset.id.clone(); - - let app: AppPB = self - .persistence - .begin_transaction(|transaction| { - transaction.update_app(changeset)?; - let app = transaction.read_app(&app_id)?; - Ok(app) - }) - .await? - .into(); - send_notification(&app_id, FolderNotification::DidUpdateApp) - .payload(app) - .send(); - self.update_app_on_server(params)?; + pub(crate) async fn move_app(&self, app_id: &str, from: usize, to: usize) -> FlowyResult<()> { + self + .persistence + .begin_transaction(|transaction| { + transaction.move_app(app_id, from, to)?; + let app = transaction.read_app(app_id)?; + notify_apps_changed( + &app.workspace_id, + self.trash_controller.clone(), + &transaction, + )?; Ok(()) - } + }) + .await?; + Ok(()) + } - pub(crate) async fn move_app(&self, app_id: &str, from: usize, to: usize) -> FlowyResult<()> { - self.persistence - .begin_transaction(|transaction| { - transaction.move_app(app_id, from, to)?; - let app = transaction.read_app(app_id)?; - notify_apps_changed(&app.workspace_id, self.trash_controller.clone(), &transaction)?; - Ok(()) - }) - .await?; - Ok(()) - } - - pub(crate) async fn read_local_apps(&self, ids: Vec) -> Result, FlowyError> { - let app_revs = self - .persistence - .begin_transaction(|transaction| { - let mut apps = vec![]; - for id in ids { - apps.push(transaction.read_app(&id)?); - } - Ok(apps) - }) - .await?; - Ok(app_revs) - } + pub(crate) async fn read_local_apps( + &self, + ids: Vec, + ) -> Result, FlowyError> { + let app_revs = self + .persistence + .begin_transaction(|transaction| { + let mut apps = vec![]; + for id in ids { + apps.push(transaction.read_app(&id)?); + } + Ok(apps) + }) + .await?; + Ok(app_revs) + } } impl AppController { - #[tracing::instrument(level = "trace", skip(self), err)] - async fn create_app_on_server(&self, params: CreateAppParams) -> Result { - let token = self.user.token()?; - let app = self.cloud_service.create_app(&token, params).await?; - Ok(app) - } + #[tracing::instrument(level = "trace", skip(self), err)] + async fn create_app_on_server(&self, params: CreateAppParams) -> Result { + let token = self.user.token()?; + let app = self.cloud_service.create_app(&token, params).await?; + Ok(app) + } - #[tracing::instrument(level = "trace", skip(self), err)] - fn update_app_on_server(&self, params: UpdateAppParams) -> Result<(), FlowyError> { - let token = self.user.token()?; - let server = self.cloud_service.clone(); - tokio::spawn(async move { - match server.update_app(&token, params).await { - Ok(_) => {} - Err(e) => { - // TODO: retry? - log::error!("Update app failed: {:?}", e); - } - } - }); - Ok(()) - } + #[tracing::instrument(level = "trace", skip(self), err)] + fn update_app_on_server(&self, params: UpdateAppParams) -> Result<(), FlowyError> { + let token = self.user.token()?; + let server = self.cloud_service.clone(); + tokio::spawn(async move { + match server.update_app(&token, params).await { + Ok(_) => {}, + Err(e) => { + // TODO: retry? + log::error!("Update app failed: {:?}", e); + }, + } + }); + Ok(()) + } - fn listen_trash_controller_event(&self) { - let mut rx = self.trash_controller.subscribe(); - let persistence = self.persistence.clone(); - let trash_controller = self.trash_controller.clone(); - let _ = tokio::spawn(async move { - loop { - let mut stream = Box::pin(rx.recv().into_stream().filter_map(|result| async move { - match result { - Ok(event) => event.select(TrashType::TrashApp), - Err(_e) => None, - } - })); - if let Some(event) = stream.next().await { - handle_trash_event(persistence.clone(), trash_controller.clone(), event).await - } - } - }); - } + fn listen_trash_controller_event(&self) { + let mut rx = self.trash_controller.subscribe(); + let persistence = self.persistence.clone(); + let trash_controller = self.trash_controller.clone(); + let _ = tokio::spawn(async move { + loop { + let mut stream = Box::pin(rx.recv().into_stream().filter_map(|result| async move { + match result { + Ok(event) => event.select(TrashType::TrashApp), + Err(_e) => None, + } + })); + if let Some(event) = stream.next().await { + handle_trash_event(persistence.clone(), trash_controller.clone(), event).await + } + } + }); + } } #[tracing::instrument(level = "trace", skip(persistence, trash_controller))] async fn handle_trash_event( - persistence: Arc, - trash_controller: Arc, - event: TrashEvent, + persistence: Arc, + trash_controller: Arc, + event: TrashEvent, ) { - match event { - TrashEvent::NewTrash(identifiers, ret) | TrashEvent::Putback(identifiers, ret) => { - let result = persistence - .begin_transaction(|transaction| { - for identifier in identifiers.items { - let app = transaction.read_app(&identifier.id)?; - notify_apps_changed(&app.workspace_id, trash_controller.clone(), &transaction)?; - } - Ok(()) - }) - .await; - let _ = ret.send(result).await; - } - TrashEvent::Delete(identifiers, ret) => { - let result = persistence - .begin_transaction(|transaction| { - let mut notify_ids = HashSet::new(); - for identifier in identifiers.items { - let app = transaction.read_app(&identifier.id)?; - let _ = transaction.delete_app(&identifier.id)?; - notify_ids.insert(app.workspace_id); - } + match event { + TrashEvent::NewTrash(identifiers, ret) | TrashEvent::Putback(identifiers, ret) => { + let result = persistence + .begin_transaction(|transaction| { + for identifier in identifiers.items { + let app = transaction.read_app(&identifier.id)?; + notify_apps_changed(&app.workspace_id, trash_controller.clone(), &transaction)?; + } + Ok(()) + }) + .await; + let _ = ret.send(result).await; + }, + TrashEvent::Delete(identifiers, ret) => { + let result = persistence + .begin_transaction(|transaction| { + let mut notify_ids = HashSet::new(); + for identifier in identifiers.items { + let app = transaction.read_app(&identifier.id)?; + let _ = transaction.delete_app(&identifier.id)?; + notify_ids.insert(app.workspace_id); + } - for notify_id in notify_ids { - notify_apps_changed(¬ify_id, trash_controller.clone(), &transaction)?; - } - Ok(()) - }) - .await; - let _ = ret.send(result).await; - } - } + for notify_id in notify_ids { + notify_apps_changed(¬ify_id, trash_controller.clone(), &transaction)?; + } + Ok(()) + }) + .await; + let _ = ret.send(result).await; + }, + } } -#[tracing::instrument(level = "debug", skip(workspace_id, trash_controller, transaction), err)] +#[tracing::instrument( + level = "debug", + skip(workspace_id, trash_controller, transaction), + err +)] fn notify_apps_changed<'a>( - workspace_id: &str, - trash_controller: Arc, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), + workspace_id: &str, + trash_controller: Arc, + transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> FlowyResult<()> { - let items = read_workspace_apps(workspace_id, trash_controller, transaction)? - .into_iter() - .map(|app_rev| app_rev.into()) - .collect(); - let repeated_app = RepeatedAppPB { items }; - send_notification(workspace_id, FolderNotification::DidUpdateWorkspaceApps) - .payload(repeated_app) - .send(); - Ok(()) + let items = read_workspace_apps(workspace_id, trash_controller, transaction)? + .into_iter() + .map(|app_rev| app_rev.into()) + .collect(); + let repeated_app = RepeatedAppPB { items }; + send_notification(workspace_id, FolderNotification::DidUpdateWorkspaceApps) + .payload(repeated_app) + .send(); + Ok(()) } pub fn read_workspace_apps<'a>( - workspace_id: &str, - trash_controller: Arc, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), + workspace_id: &str, + trash_controller: Arc, + transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> Result, FlowyError> { - let mut app_revs = transaction.read_workspace_apps(workspace_id)?; - let trash_ids = trash_controller.read_trash_ids(transaction)?; - app_revs.retain(|app| !trash_ids.contains(&app.id)); - Ok(app_revs) + let mut app_revs = transaction.read_workspace_apps(workspace_id)?; + let trash_ids = trash_controller.read_trash_ids(transaction)?; + app_revs.retain(|app| !trash_ids.contains(&app.id)); + Ok(app_revs) } diff --git a/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs index 8e88dcf6c7..09f88923bf 100644 --- a/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs +++ b/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs @@ -1,60 +1,62 @@ use crate::{ - entities::app::{AppIdPB, AppPB, CreateAppParams, CreateAppPayloadPB, UpdateAppParams, UpdateAppPayloadPB}, - errors::FlowyError, - services::{AppController, TrashController, ViewController}, + entities::app::{ + AppIdPB, AppPB, CreateAppParams, CreateAppPayloadPB, UpdateAppParams, UpdateAppPayloadPB, + }, + errors::FlowyError, + services::{AppController, TrashController, ViewController}, }; use folder_model::TrashRevision; use lib_dispatch::prelude::{data_result, AFPluginData, AFPluginState, DataResult}; use std::{convert::TryInto, sync::Arc}; pub(crate) async fn create_app_handler( - data: AFPluginData, - controller: AFPluginState>, + data: AFPluginData, + controller: AFPluginState>, ) -> DataResult { - let params: CreateAppParams = data.into_inner().try_into()?; - let detail = controller.create_app_from_params(params).await?; + let params: CreateAppParams = data.into_inner().try_into()?; + let detail = controller.create_app_from_params(params).await?; - data_result(detail) + data_result(detail) } pub(crate) async fn delete_app_handler( - data: AFPluginData, - app_controller: AFPluginState>, - trash_controller: AFPluginState>, + data: AFPluginData, + app_controller: AFPluginState>, + trash_controller: AFPluginState>, ) -> Result<(), FlowyError> { - let params: AppIdPB = data.into_inner(); - let trash = app_controller - .read_local_apps(vec![params.value]) - .await? - .into_iter() - .map(|app_rev| app_rev.into()) - .collect::>(); + let params: AppIdPB = data.into_inner(); + let trash = app_controller + .read_local_apps(vec![params.value]) + .await? + .into_iter() + .map(|app_rev| app_rev.into()) + .collect::>(); - trash_controller.add(trash).await?; - Ok(()) + trash_controller.add(trash).await?; + Ok(()) } #[tracing::instrument(level = "trace", skip(data, controller))] pub(crate) async fn update_app_handler( - data: AFPluginData, - controller: AFPluginState>, + data: AFPluginData, + controller: AFPluginState>, ) -> Result<(), FlowyError> { - let params: UpdateAppParams = data.into_inner().try_into()?; - controller.update_app(params).await?; - Ok(()) + let params: UpdateAppParams = data.into_inner().try_into()?; + controller.update_app(params).await?; + Ok(()) } #[tracing::instrument(level = "trace", skip(data, app_controller, view_controller), err)] pub(crate) async fn read_app_handler( - data: AFPluginData, - app_controller: AFPluginState>, - view_controller: AFPluginState>, + data: AFPluginData, + app_controller: AFPluginState>, + view_controller: AFPluginState>, ) -> DataResult { - let params: AppIdPB = data.into_inner(); - if let Some(mut app_rev) = app_controller.read_app(params.clone()).await? { - app_rev.belongings = view_controller.read_views_belong_to(¶ms.value).await?; - data_result(app_rev.into()) - } else { - Err(FlowyError::record_not_found()) - } + let params: AppIdPB = data.into_inner(); + if let Some(mut app_rev) = app_controller.read_app(params.clone()).await? { + app_rev.belongings = view_controller.read_views_belong_to(¶ms.value).await?; + data_result(app_rev.into()) + } else { + Err(FlowyError::record_not_found()) + } } diff --git a/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs b/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs index 93a158217b..eeb322d762 100644 --- a/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs +++ b/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs @@ -5,8 +5,8 @@ use flowy_client_sync::make_operations_from_revisions; use flowy_client_sync::util::recover_operation_from_revisions; use flowy_error::{FlowyError, FlowyResult}; use flowy_revision::{ - RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, RevisionObjectSerializer, - RevisionWebSocket, + RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, + RevisionObjectSerializer, RevisionWebSocket, }; use flowy_sqlite::ConnectionPool; use lib_infra::future::FutureResult; @@ -17,137 +17,154 @@ use std::sync::Arc; use ws_model::ws_revision::ServerRevisionWSData; pub struct FolderEditor { - #[allow(dead_code)] - user_id: String, - #[allow(dead_code)] - folder_id: FolderId, - pub(crate) folder: Arc>, - rev_manager: Arc>>, - #[cfg(feature = "sync")] - ws_manager: Arc, + #[allow(dead_code)] + user_id: String, + #[allow(dead_code)] + folder_id: FolderId, + pub(crate) folder: Arc>, + rev_manager: Arc>>, + #[cfg(feature = "sync")] + ws_manager: Arc, } impl FolderEditor { - #[allow(unused_variables)] - pub async fn new( - user_id: &str, - folder_id: &FolderId, - token: &str, - mut rev_manager: RevisionManager>, - web_socket: Arc, - ) -> FlowyResult { - let cloud = Arc::new(FolderRevisionCloudService { - token: token.to_string(), - }); - let folder = Arc::new(RwLock::new( - rev_manager.initialize::(Some(cloud)).await?, - )); - let rev_manager = Arc::new(rev_manager); - - #[cfg(feature = "sync")] - let ws_manager = crate::services::web_socket::make_folder_ws_manager( - user_id, - folder_id.as_ref(), - rev_manager.clone(), - web_socket, - folder.clone(), - ) - .await; - - let user_id = user_id.to_owned(); - let folder_id = folder_id.to_owned(); - Ok(Self { - user_id, - folder_id, - folder, - rev_manager, - #[cfg(feature = "sync")] - ws_manager, - }) - } + #[allow(unused_variables)] + pub async fn new( + user_id: &str, + folder_id: &FolderId, + token: &str, + mut rev_manager: RevisionManager>, + web_socket: Arc, + ) -> FlowyResult { + let cloud = Arc::new(FolderRevisionCloudService { + token: token.to_string(), + }); + let folder = Arc::new(RwLock::new( + rev_manager + .initialize::(Some(cloud)) + .await?, + )); + let rev_manager = Arc::new(rev_manager); #[cfg(feature = "sync")] - pub async fn receive_ws_data(&self, data: ServerRevisionWSData) -> FlowyResult<()> { - let _ = self.ws_manager.ws_passthrough_tx.send(data).await.map_err(|e| { - let err_msg = format!("{} passthrough error: {}", self.folder_id, e); - FlowyError::internal().context(err_msg) - })?; + let ws_manager = crate::services::web_socket::make_folder_ws_manager( + user_id, + folder_id.as_ref(), + rev_manager.clone(), + web_socket, + folder.clone(), + ) + .await; - Ok(()) - } + let user_id = user_id.to_owned(); + let folder_id = folder_id.to_owned(); + Ok(Self { + user_id, + folder_id, + folder, + rev_manager, + #[cfg(feature = "sync")] + ws_manager, + }) + } - #[cfg(not(feature = "sync"))] - pub async fn receive_ws_data(&self, _data: ServerRevisionWSData) -> FlowyResult<()> { - Ok(()) - } + #[cfg(feature = "sync")] + pub async fn receive_ws_data(&self, data: ServerRevisionWSData) -> FlowyResult<()> { + let _ = self + .ws_manager + .ws_passthrough_tx + .send(data) + .await + .map_err(|e| { + let err_msg = format!("{} passthrough error: {}", self.folder_id, e); + FlowyError::internal().context(err_msg) + })?; - pub(crate) fn apply_change(&self, change: FolderChangeset) -> FlowyResult<()> { - let FolderChangeset { operations: delta, md5 } = change; - let delta_data = delta.json_bytes(); - let rev_manager = self.rev_manager.clone(); - tokio::spawn(async move { - let _ = rev_manager.add_local_revision(delta_data, md5).await; - }); - Ok(()) - } + Ok(()) + } - #[allow(dead_code)] - pub fn folder_json(&self) -> FlowyResult { - let json = self.folder.read().to_json()?; - Ok(json) - } + #[cfg(not(feature = "sync"))] + pub async fn receive_ws_data(&self, _data: ServerRevisionWSData) -> FlowyResult<()> { + Ok(()) + } + + pub(crate) fn apply_change(&self, change: FolderChangeset) -> FlowyResult<()> { + let FolderChangeset { + operations: delta, + md5, + } = change; + let delta_data = delta.json_bytes(); + let rev_manager = self.rev_manager.clone(); + tokio::spawn(async move { + let _ = rev_manager.add_local_revision(delta_data, md5).await; + }); + Ok(()) + } + + #[allow(dead_code)] + pub fn folder_json(&self) -> FlowyResult { + let json = self.folder.read().to_json()?; + Ok(json) + } } struct FolderRevisionSerde(); impl RevisionObjectDeserializer for FolderRevisionSerde { - type Output = FolderPad; + type Output = FolderPad; - fn deserialize_revisions(_object_id: &str, revisions: Vec) -> FlowyResult { - let operations: FolderOperations = make_operations_from_revisions(revisions)?; - Ok(FolderPad::from_operations(operations)?) - } + fn deserialize_revisions( + _object_id: &str, + revisions: Vec, + ) -> FlowyResult { + let operations: FolderOperations = make_operations_from_revisions(revisions)?; + Ok(FolderPad::from_operations(operations)?) + } - fn recover_from_revisions(revisions: Vec) -> Option<(Self::Output, i64)> { - if let Some((operations, rev_id)) = recover_operation_from_revisions(revisions, |operations| { - FolderPad::from_operations(operations.clone()).is_ok() - }) { - if let Ok(pad) = FolderPad::from_operations(operations) { - return Some((pad, rev_id)); - } - } - None + fn recover_from_revisions(revisions: Vec) -> Option<(Self::Output, i64)> { + if let Some((operations, rev_id)) = recover_operation_from_revisions(revisions, |operations| { + FolderPad::from_operations(operations.clone()).is_ok() + }) { + if let Ok(pad) = FolderPad::from_operations(operations) { + return Some((pad, rev_id)); + } } + None + } } impl RevisionObjectSerializer for FolderRevisionSerde { - fn combine_revisions(revisions: Vec) -> FlowyResult { - let operations = make_operations_from_revisions::(revisions)?; - Ok(operations.json_bytes()) - } + fn combine_revisions(revisions: Vec) -> FlowyResult { + let operations = make_operations_from_revisions::(revisions)?; + Ok(operations.json_bytes()) + } } pub struct FolderRevisionMergeable(); impl RevisionMergeable for FolderRevisionMergeable { - fn combine_revisions(&self, revisions: Vec) -> FlowyResult { - FolderRevisionSerde::combine_revisions(revisions) - } + fn combine_revisions(&self, revisions: Vec) -> FlowyResult { + FolderRevisionSerde::combine_revisions(revisions) + } } struct FolderRevisionCloudService { - #[allow(dead_code)] - token: String, + #[allow(dead_code)] + token: String, } impl RevisionCloudService for FolderRevisionCloudService { - #[tracing::instrument(level = "trace", skip(self))] - fn fetch_object(&self, _user_id: &str, _object_id: &str) -> FutureResult, FlowyError> { - FutureResult::new(async move { Ok(vec![]) }) - } + #[tracing::instrument(level = "trace", skip(self))] + fn fetch_object( + &self, + _user_id: &str, + _object_id: &str, + ) -> FutureResult, FlowyError> { + FutureResult::new(async move { Ok(vec![]) }) + } } #[cfg(feature = "flowy_unit_test")] impl FolderEditor { - pub fn rev_manager(&self) -> Arc>> { - self.rev_manager.clone() - } + pub fn rev_manager(&self) -> Arc>> { + self.rev_manager.clone() + } } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs index 7554e8941e..7498e8dbec 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs @@ -1,7 +1,7 @@ use crate::manager::FolderId; use crate::{ - event_map::WorkspaceDatabase, - services::persistence::{AppTableSql, TrashTableSql, ViewTableSql, WorkspaceTableSql}, + event_map::WorkspaceDatabase, + services::persistence::{AppTableSql, TrashTableSql, ViewTableSql, WorkspaceTableSql}, }; use bytes::Bytes; use flowy_client_sync::client_folder::FolderPad; @@ -21,134 +21,137 @@ const V2_MIGRATION: &str = "FOLDER_V2_MIGRATION"; const V3_MIGRATION: &str = "FOLDER_V3_MIGRATION"; pub(crate) struct FolderMigration { - user_id: String, - database: Arc, + user_id: String, + database: Arc, } impl FolderMigration { - pub fn new(user_id: &str, database: Arc) -> Self { - Self { - user_id: user_id.to_owned(), - database, - } + pub fn new(user_id: &str, database: Arc) -> Self { + Self { + user_id: user_id.to_owned(), + database, + } + } + + pub fn run_v1_migration(&self) -> FlowyResult> { + let key = migration_flag_key(&self.user_id, V1_MIGRATION); + if KV::get_bool(&key) { + return Ok(None); } - pub fn run_v1_migration(&self) -> FlowyResult> { - let key = migration_flag_key(&self.user_id, V1_MIGRATION); - if KV::get_bool(&key) { - return Ok(None); + let pool = self.database.db_pool()?; + let conn = &*pool.get()?; + let workspaces = conn.immediate_transaction::<_, FlowyError, _>(|| { + let mut workspaces = WorkspaceTableSql::read_workspaces(&self.user_id, None, conn)? + .into_iter() + .map(WorkspaceRevision::from) + .collect::>(); + + for workspace in workspaces.iter_mut() { + let mut apps = AppTableSql::read_workspace_apps(&workspace.id, conn)? + .into_iter() + .map(AppRevision::from) + .collect::>(); + + for app in apps.iter_mut() { + let views = ViewTableSql::read_views(&app.id, conn)? + .into_iter() + .map(ViewRevision::from) + .collect::>(); + + app.belongings = views; } - let pool = self.database.db_pool()?; - let conn = &*pool.get()?; - let workspaces = conn.immediate_transaction::<_, FlowyError, _>(|| { - let mut workspaces = WorkspaceTableSql::read_workspaces(&self.user_id, None, conn)? - .into_iter() - .map(WorkspaceRevision::from) - .collect::>(); + workspace.apps = apps; + } + Ok(workspaces) + })?; - for workspace in workspaces.iter_mut() { - let mut apps = AppTableSql::read_workspace_apps(&workspace.id, conn)? - .into_iter() - .map(AppRevision::from) - .collect::>(); - - for app in apps.iter_mut() { - let views = ViewTableSql::read_views(&app.id, conn)? - .into_iter() - .map(ViewRevision::from) - .collect::>(); - - app.belongings = views; - } - - workspace.apps = apps; - } - Ok(workspaces) - })?; - - if workspaces.is_empty() { - tracing::trace!("Run folder v1 migration, but workspace is empty"); - KV::set_bool(&key, true); - return Ok(None); - } - - let trash = conn.immediate_transaction::<_, FlowyError, _>(|| { - let trash = TrashTableSql::read_all(conn)?; - Ok(trash) - })?; - - let folder = FolderPad::new(workspaces, trash)?; - KV::set_bool(&key, true); - tracing::info!("Run folder v1 migration"); - Ok(Some(folder)) + if workspaces.is_empty() { + tracing::trace!("Run folder v1 migration, but workspace is empty"); + KV::set_bool(&key, true); + return Ok(None); } - pub async fn run_v2_migration(&self, folder_id: &FolderId) -> FlowyResult<()> { - let key = migration_flag_key(&self.user_id, V2_MIGRATION); - if KV::get_bool(&key) { - return Ok(()); - } - self.migration_folder_rev_struct(folder_id).await?; - KV::set_bool(&key, true); - // tracing::info!("Run folder v2 migration"); - Ok(()) - } + let trash = conn.immediate_transaction::<_, FlowyError, _>(|| { + let trash = TrashTableSql::read_all(conn)?; + Ok(trash) + })?; - pub async fn run_v3_migration(&self, folder_id: &FolderId) -> FlowyResult<()> { - let key = migration_flag_key(&self.user_id, V3_MIGRATION); - if KV::get_bool(&key) { - return Ok(()); - } - self.migration_folder_rev_struct(folder_id).await?; - KV::set_bool(&key, true); - tracing::trace!("Run folder v3 migration"); - Ok(()) - } + let folder = FolderPad::new(workspaces, trash)?; + KV::set_bool(&key, true); + tracing::info!("Run folder v1 migration"); + Ok(Some(folder)) + } - pub async fn migration_folder_rev_struct(&self, folder_id: &FolderId) -> FlowyResult<()> { - let object = FolderRevisionResettable { - folder_id: folder_id.as_ref().to_owned(), - }; - - let pool = self.database.db_pool()?; - let disk_cache = SQLiteFolderRevisionPersistence::new(&self.user_id, pool); - let reset = RevisionStructReset::new(&self.user_id, object, Arc::new(disk_cache)); - reset.run().await + pub async fn run_v2_migration(&self, folder_id: &FolderId) -> FlowyResult<()> { + let key = migration_flag_key(&self.user_id, V2_MIGRATION); + if KV::get_bool(&key) { + return Ok(()); } + self.migration_folder_rev_struct(folder_id).await?; + KV::set_bool(&key, true); + // tracing::info!("Run folder v2 migration"); + Ok(()) + } + + pub async fn run_v3_migration(&self, folder_id: &FolderId) -> FlowyResult<()> { + let key = migration_flag_key(&self.user_id, V3_MIGRATION); + if KV::get_bool(&key) { + return Ok(()); + } + self.migration_folder_rev_struct(folder_id).await?; + KV::set_bool(&key, true); + tracing::trace!("Run folder v3 migration"); + Ok(()) + } + + pub async fn migration_folder_rev_struct(&self, folder_id: &FolderId) -> FlowyResult<()> { + let object = FolderRevisionResettable { + folder_id: folder_id.as_ref().to_owned(), + }; + + let pool = self.database.db_pool()?; + let disk_cache = SQLiteFolderRevisionPersistence::new(&self.user_id, pool); + let reset = RevisionStructReset::new(&self.user_id, object, Arc::new(disk_cache)); + reset.run().await + } } fn migration_flag_key(user_id: &str, version: &str) -> String { - md5(format!("{}{}", user_id, version,)) + md5(format!("{}{}", user_id, version,)) } struct FolderRevisionResettable { - folder_id: String, + folder_id: String, } impl RevisionResettable for FolderRevisionResettable { - fn target_id(&self) -> &str { - &self.folder_id - } + fn target_id(&self) -> &str { + &self.folder_id + } - fn reset_data(&self, revisions: Vec) -> FlowyResult { - let pad = FolderPad::from_revisions(revisions)?; - let json = pad.to_json()?; - let bytes = FolderOperationsBuilder::new().insert(&json).build().json_bytes(); - Ok(bytes) - } + fn reset_data(&self, revisions: Vec) -> FlowyResult { + let pad = FolderPad::from_revisions(revisions)?; + let json = pad.to_json()?; + let bytes = FolderOperationsBuilder::new() + .insert(&json) + .build() + .json_bytes(); + Ok(bytes) + } - fn default_target_rev_str(&self) -> FlowyResult { - let folder = FolderRevision::default(); - let json = make_folder_rev_json_str(&folder)?; - Ok(json) - } + fn default_target_rev_str(&self) -> FlowyResult { + let folder = FolderRevision::default(); + let json = make_folder_rev_json_str(&folder)?; + Ok(json) + } - fn read_record(&self) -> Option { - KV::get_str(self.target_id()) - } + fn read_record(&self) -> Option { + KV::get_str(self.target_id()) + } - fn set_record(&self, record: String) { - KV::set_str(self.target_id(), record); - } + fn set_record(&self, record: String) { + KV::set_str(self.target_id(), record); + } } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs index 43ec69161b..d46ec12951 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs @@ -5,9 +5,9 @@ mod version_2; use crate::services::persistence::rev_sqlite::SQLiteFolderRevisionPersistence; use crate::{ - event_map::WorkspaceDatabase, - manager::FolderId, - services::{folder_editor::FolderEditor, persistence::migration::FolderMigration}, + event_map::WorkspaceDatabase, + manager::FolderId, + services::{folder_editor::FolderEditor, persistence::migration::FolderMigration}, }; use flowy_client_sync::client_folder::{FolderOperationsBuilder, FolderPad}; use flowy_error::{FlowyError, FlowyResult}; @@ -17,114 +17,135 @@ use folder_model::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; use revision_model::Revision; use std::sync::Arc; use tokio::sync::RwLock; -pub use version_1::{app_sql::*, trash_sql::*, v1_impl::V1Transaction, view_sql::*, workspace_sql::*}; +pub use version_1::{ + app_sql::*, trash_sql::*, v1_impl::V1Transaction, view_sql::*, workspace_sql::*, +}; pub trait FolderPersistenceTransaction { - fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()>; - fn read_workspaces(&self, user_id: &str, workspace_id: Option) -> FlowyResult>; - fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()>; - fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()>; + fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()>; + fn read_workspaces( + &self, + user_id: &str, + workspace_id: Option, + ) -> FlowyResult>; + fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()>; + fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()>; - fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()>; - fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()>; - fn read_app(&self, app_id: &str) -> FlowyResult; - fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult>; - fn delete_app(&self, app_id: &str) -> FlowyResult; - fn move_app(&self, app_id: &str, from: usize, to: usize) -> FlowyResult<()>; + fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()>; + fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()>; + fn read_app(&self, app_id: &str) -> FlowyResult; + fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult>; + fn delete_app(&self, app_id: &str) -> FlowyResult; + fn move_app(&self, app_id: &str, from: usize, to: usize) -> FlowyResult<()>; - fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()>; - fn read_view(&self, view_id: &str) -> FlowyResult; - fn read_views(&self, belong_to_id: &str) -> FlowyResult>; - fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()>; - fn delete_view(&self, view_id: &str) -> FlowyResult; - fn move_view(&self, view_id: &str, from: usize, to: usize) -> FlowyResult<()>; + fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()>; + fn read_view(&self, view_id: &str) -> FlowyResult; + fn read_views(&self, belong_to_id: &str) -> FlowyResult>; + fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()>; + fn delete_view(&self, view_id: &str) -> FlowyResult; + fn move_view(&self, view_id: &str, from: usize, to: usize) -> FlowyResult<()>; - fn create_trash(&self, trashes: Vec) -> FlowyResult<()>; - fn read_trash(&self, trash_id: Option) -> FlowyResult>; - fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()>; + fn create_trash(&self, trashes: Vec) -> FlowyResult<()>; + fn read_trash(&self, trash_id: Option) -> FlowyResult>; + fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()>; } pub struct FolderPersistence { - database: Arc, - folder_editor: Arc>>>, + database: Arc, + folder_editor: Arc>>>, } impl FolderPersistence { - pub fn new(database: Arc, folder_editor: Arc>>>) -> Self { - Self { - database, - folder_editor, - } + pub fn new( + database: Arc, + folder_editor: Arc>>>, + ) -> Self { + Self { + database, + folder_editor, + } + } + + #[deprecated( + since = "0.0.3", + note = "please use `begin_transaction` instead, this interface will be removed in the future" + )] + #[allow(dead_code)] + pub fn begin_transaction_v_1(&self, f: F) -> FlowyResult + where + F: for<'a> FnOnce(Box) -> FlowyResult, + { + //[[immediate_transaction]] + // https://sqlite.org/lang_transaction.html + // IMMEDIATE cause the database connection to start a new write immediately, + // without waiting for a write statement. The BEGIN IMMEDIATE might fail + // with SQLITE_BUSY if another write transaction is already active on another + // database connection. + // + // EXCLUSIVE is similar to IMMEDIATE in that a write transaction is started + // immediately. EXCLUSIVE and IMMEDIATE are the same in WAL mode, but in + // other journaling modes, EXCLUSIVE prevents other database connections from + // reading the database while the transaction is underway. + let conn = self.database.db_connection()?; + conn.immediate_transaction::<_, FlowyError, _>(|| f(Box::new(V1Transaction(&conn)))) + } + + pub async fn begin_transaction(&self, f: F) -> FlowyResult + where + F: FnOnce(Arc) -> FlowyResult, + { + match self.folder_editor.read().await.clone() { + None => Err( + FlowyError::internal().context("FolderEditor should be initialized after user login in."), + ), + Some(editor) => f(editor), + } + } + + pub fn db_pool(&self) -> FlowyResult> { + self.database.db_pool() + } + + pub async fn initialize(&self, user_id: &str, folder_id: &FolderId) -> FlowyResult<()> { + let migrations = FolderMigration::new(user_id, self.database.clone()); + if let Some(migrated_folder) = migrations.run_v1_migration()? { + self + .save_folder(user_id, folder_id, migrated_folder) + .await?; } - #[deprecated( - since = "0.0.3", - note = "please use `begin_transaction` instead, this interface will be removed in the future" - )] - #[allow(dead_code)] - pub fn begin_transaction_v_1(&self, f: F) -> FlowyResult - where - F: for<'a> FnOnce(Box) -> FlowyResult, - { - //[[immediate_transaction]] - // https://sqlite.org/lang_transaction.html - // IMMEDIATE cause the database connection to start a new write immediately, - // without waiting for a write statement. The BEGIN IMMEDIATE might fail - // with SQLITE_BUSY if another write transaction is already active on another - // database connection. - // - // EXCLUSIVE is similar to IMMEDIATE in that a write transaction is started - // immediately. EXCLUSIVE and IMMEDIATE are the same in WAL mode, but in - // other journaling modes, EXCLUSIVE prevents other database connections from - // reading the database while the transaction is underway. - let conn = self.database.db_connection()?; - conn.immediate_transaction::<_, FlowyError, _>(|| f(Box::new(V1Transaction(&conn)))) - } + migrations.run_v2_migration(folder_id).await?; + migrations.run_v3_migration(folder_id).await?; + Ok(()) + } - pub async fn begin_transaction(&self, f: F) -> FlowyResult - where - F: FnOnce(Arc) -> FlowyResult, - { - match self.folder_editor.read().await.clone() { - None => Err(FlowyError::internal().context("FolderEditor should be initialized after user login in.")), - Some(editor) => f(editor), - } - } + pub async fn save_folder( + &self, + user_id: &str, + folder_id: &FolderId, + folder: FolderPad, + ) -> FlowyResult<()> { + let pool = self.database.db_pool()?; + let json = folder.to_json()?; + let delta_data = FolderOperationsBuilder::new() + .insert(&json) + .build() + .json_bytes(); + let revision = Revision::initial_revision(folder_id.as_ref(), delta_data); + let record = SyncRecord { + revision, + state: RevisionState::Sync, + write_to_disk: true, + }; - pub fn db_pool(&self) -> FlowyResult> { - self.database.db_pool() - } - - pub async fn initialize(&self, user_id: &str, folder_id: &FolderId) -> FlowyResult<()> { - let migrations = FolderMigration::new(user_id, self.database.clone()); - if let Some(migrated_folder) = migrations.run_v1_migration()? { - self.save_folder(user_id, folder_id, migrated_folder).await?; - } - - migrations.run_v2_migration(folder_id).await?; - migrations.run_v3_migration(folder_id).await?; - Ok(()) - } - - pub async fn save_folder(&self, user_id: &str, folder_id: &FolderId, folder: FolderPad) -> FlowyResult<()> { - let pool = self.database.db_pool()?; - let json = folder.to_json()?; - let delta_data = FolderOperationsBuilder::new().insert(&json).build().json_bytes(); - let revision = Revision::initial_revision(folder_id.as_ref(), delta_data); - let record = SyncRecord { - revision, - state: RevisionState::Sync, - write_to_disk: true, - }; - - let disk_cache = make_folder_revision_disk_cache(user_id, pool); - disk_cache.delete_and_insert_records(folder_id.as_ref(), None, vec![record]) - } + let disk_cache = make_folder_revision_disk_cache(user_id, pool); + disk_cache.delete_and_insert_records(folder_id.as_ref(), None, vec![record]) + } } pub fn make_folder_revision_disk_cache( - user_id: &str, - pool: Arc, + user_id: &str, + pool: Arc, ) -> Arc, Error = FlowyError>> { - Arc::new(SQLiteFolderRevisionPersistence::new(user_id, pool)) + Arc::new(SQLiteFolderRevisionPersistence::new(user_id, pool)) } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_rev_sqlite.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_rev_sqlite.rs index 7b648e785c..59df4f4c2a 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_rev_sqlite.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_rev_sqlite.rs @@ -3,260 +3,280 @@ use diesel::{sql_types::Integer, update, SqliteConnection}; use flowy_error::{internal_error, FlowyError, FlowyResult}; use flowy_revision_persistence::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; use flowy_sqlite::{ - impl_sql_integer_expression, insert_or_ignore_into, - prelude::*, - schema::{rev_table, rev_table::dsl}, - ConnectionPool, + impl_sql_integer_expression, insert_or_ignore_into, + prelude::*, + schema::{rev_table, rev_table::dsl}, + ConnectionPool, }; use lib_infra::util::md5; use revision_model::{Revision, RevisionRange}; use std::sync::Arc; pub struct SQLiteFolderRevisionPersistence { - user_id: String, - pub(crate) pool: Arc, + user_id: String, + pub(crate) pool: Arc, } impl RevisionDiskCache> for SQLiteFolderRevisionPersistence { - type Error = FlowyError; + type Error = FlowyError; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - FolderRevisionSql::create(revision_records, &conn)?; - Ok(()) - } + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + FolderRevisionSql::create(revision_records, &conn)?; + Ok(()) + } - fn get_connection(&self) -> Result, Self::Error> { - Ok(self.pool.clone()) - } + fn get_connection(&self) -> Result, Self::Error> { + Ok(self.pool.clone()) + } - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - let records = FolderRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?; - Ok(records) - } + fn read_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result, Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + let records = FolderRevisionSql::read(&self.user_id, object_id, rev_ids, &conn)?; + Ok(records) + } - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - let revisions = FolderRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?; - Ok(revisions) - } + fn read_revision_records_with_range( + &self, + object_id: &str, + range: &RevisionRange, + ) -> Result, Self::Error> { + let conn = &*self.pool.get().map_err(internal_error)?; + let revisions = + FolderRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?; + Ok(revisions) + } - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - let conn = &*self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - for changeset in changesets { - FolderRevisionSql::update(changeset, conn)?; - } - Ok(()) - })?; - Ok(()) - } + fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { + let conn = &*self.pool.get().map_err(internal_error)?; + conn.immediate_transaction::<_, FlowyError, _>(|| { + for changeset in changesets { + FolderRevisionSql::update(changeset, conn)?; + } + Ok(()) + })?; + Ok(()) + } - fn delete_revision_records(&self, object_id: &str, rev_ids: Option>) -> Result<(), Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - FolderRevisionSql::delete(object_id, rev_ids, conn)?; - Ok(()) - } + fn delete_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result<(), Self::Error> { + let conn = &*self.pool.get().map_err(internal_error)?; + FolderRevisionSql::delete(object_id, rev_ids, conn)?; + Ok(()) + } - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - FolderRevisionSql::delete(object_id, deleted_rev_ids, &conn)?; - FolderRevisionSql::create(inserted_records, &conn)?; - Ok(()) - }) - } + fn delete_and_insert_records( + &self, + object_id: &str, + deleted_rev_ids: Option>, + inserted_records: Vec, + ) -> Result<(), Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + conn.immediate_transaction::<_, FlowyError, _>(|| { + FolderRevisionSql::delete(object_id, deleted_rev_ids, &conn)?; + FolderRevisionSql::create(inserted_records, &conn)?; + Ok(()) + }) + } } impl SQLiteFolderRevisionPersistence { - pub fn new(user_id: &str, pool: Arc) -> Self { - Self { - user_id: user_id.to_owned(), - pool, - } + pub fn new(user_id: &str, pool: Arc) -> Self { + Self { + user_id: user_id.to_owned(), + pool, } + } } struct FolderRevisionSql {} impl FolderRevisionSql { - fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { - // Batch insert: https://diesel.rs/guides/all-about-inserts.html + fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { + // Batch insert: https://diesel.rs/guides/all-about-inserts.html - let records = revision_records - .into_iter() - .map(|record| { - tracing::trace!( - "[TextRevisionSql] create revision: {}:{:?}", - record.revision.object_id, - record.revision.rev_id - ); - let rev_state: TextRevisionState = record.state.into(); - ( - dsl::doc_id.eq(record.revision.object_id), - dsl::base_rev_id.eq(record.revision.base_rev_id), - dsl::rev_id.eq(record.revision.rev_id), - dsl::data.eq(record.revision.bytes), - dsl::state.eq(rev_state), - dsl::ty.eq(RevTableType::Local), - ) - }) - .collect::>(); - - let _ = insert_or_ignore_into(dsl::rev_table).values(&records).execute(conn)?; - Ok(()) - } - - fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { - let state: TextRevisionState = changeset.state.clone().into(); - let filter = dsl::rev_table - .filter(dsl::rev_id.eq(changeset.rev_id)) - .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 {:?}", - changeset.rev_id, - changeset.state + let records = revision_records + .into_iter() + .map(|record| { + tracing::trace!( + "[TextRevisionSql] create revision: {}:{:?}", + record.revision.object_id, + record.revision.rev_id ); - Ok(()) + let rev_state: TextRevisionState = record.state.into(); + ( + dsl::doc_id.eq(record.revision.object_id), + dsl::base_rev_id.eq(record.revision.base_rev_id), + dsl::rev_id.eq(record.revision.rev_id), + dsl::data.eq(record.revision.bytes), + dsl::state.eq(rev_state), + dsl::ty.eq(RevTableType::Local), + ) + }) + .collect::>(); + + let _ = insert_or_ignore_into(dsl::rev_table) + .values(&records) + .execute(conn)?; + Ok(()) + } + + fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { + let state: TextRevisionState = changeset.state.clone().into(); + let filter = dsl::rev_table + .filter(dsl::rev_id.eq(changeset.rev_id)) + .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 {:?}", + changeset.rev_id, + changeset.state + ); + Ok(()) + } + + fn read( + user_id: &str, + object_id: &str, + rev_ids: Option>, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let mut sql = dsl::rev_table + .filter(dsl::doc_id.eq(object_id)) + .into_boxed(); + if let Some(rev_ids) = rev_ids { + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); + } + let rows = sql.order(dsl::rev_id.asc()).load::(conn)?; + let records = rows + .into_iter() + .map(|row| mk_revision_record_from_table(user_id, row)) + .collect::>(); + + Ok(records) + } + + fn read_with_range( + user_id: &str, + object_id: &str, + range: RevisionRange, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let rev_tables = dsl::rev_table + .filter(dsl::rev_id.ge(range.start)) + .filter(dsl::rev_id.le(range.end)) + .filter(dsl::doc_id.eq(object_id)) + .order(dsl::rev_id.asc()) + .load::(conn)?; + + let revisions = rev_tables + .into_iter() + .map(|table| mk_revision_record_from_table(user_id, table)) + .collect::>(); + Ok(revisions) + } + + fn delete( + object_id: &str, + rev_ids: Option>, + conn: &SqliteConnection, + ) -> Result<(), FlowyError> { + let mut sql = diesel::delete(dsl::rev_table).into_boxed(); + sql = sql.filter(dsl::doc_id.eq(object_id)); + + if let Some(rev_ids) = rev_ids { + tracing::trace!( + "[TextRevisionSql] Delete revision: {}:{:?}", + object_id, + rev_ids + ); + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); } - fn read( - user_id: &str, - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let mut sql = dsl::rev_table.filter(dsl::doc_id.eq(object_id)).into_boxed(); - if let Some(rev_ids) = rev_ids { - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - let rows = sql.order(dsl::rev_id.asc()).load::(conn)?; - let records = rows - .into_iter() - .map(|row| mk_revision_record_from_table(user_id, row)) - .collect::>(); - - Ok(records) - } - - fn read_with_range( - user_id: &str, - object_id: &str, - range: RevisionRange, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let rev_tables = dsl::rev_table - .filter(dsl::rev_id.ge(range.start)) - .filter(dsl::rev_id.le(range.end)) - .filter(dsl::doc_id.eq(object_id)) - .order(dsl::rev_id.asc()) - .load::(conn)?; - - let revisions = rev_tables - .into_iter() - .map(|table| mk_revision_record_from_table(user_id, table)) - .collect::>(); - Ok(revisions) - } - - fn delete(object_id: &str, rev_ids: Option>, conn: &SqliteConnection) -> Result<(), FlowyError> { - let mut sql = diesel::delete(dsl::rev_table).into_boxed(); - sql = sql.filter(dsl::doc_id.eq(object_id)); - - if let Some(rev_ids) = rev_ids { - tracing::trace!("[TextRevisionSql] Delete revision: {}:{:?}", object_id, rev_ids); - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - - let affected_row = sql.execute(conn)?; - tracing::trace!("[TextRevisionSql] Delete {} rows", affected_row); - Ok(()) - } + let affected_row = sql.execute(conn)?; + tracing::trace!("[TextRevisionSql] Delete {} rows", affected_row); + Ok(()) + } } #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] #[table_name = "rev_table"] struct RevisionTable { - id: i32, - doc_id: String, - base_rev_id: i64, - rev_id: i64, - data: Vec, - state: TextRevisionState, - ty: RevTableType, // Deprecated + id: i32, + doc_id: String, + base_rev_id: i64, + rev_id: i64, + data: Vec, + state: TextRevisionState, + ty: RevTableType, // Deprecated } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] #[repr(i32)] #[sql_type = "Integer"] enum TextRevisionState { - Sync = 0, - Ack = 1, + Sync = 0, + Ack = 1, } impl_sql_integer_expression!(TextRevisionState); impl_rev_state_map!(TextRevisionState); impl std::default::Default for TextRevisionState { - fn default() -> Self { - TextRevisionState::Sync - } + fn default() -> Self { + TextRevisionState::Sync + } } fn mk_revision_record_from_table(_user_id: &str, table: RevisionTable) -> SyncRecord { - let md5 = md5(&table.data); - let revision = Revision::new( - &table.doc_id, - table.base_rev_id, - table.rev_id, - Bytes::from(table.data), - md5, - ); - SyncRecord { - revision, - state: table.state.into(), - write_to_disk: false, - } + let md5 = md5(&table.data); + let revision = Revision::new( + &table.doc_id, + table.base_rev_id, + table.rev_id, + Bytes::from(table.data), + md5, + ); + SyncRecord { + revision, + state: table.state.into(), + write_to_disk: false, + } } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] #[repr(i32)] #[sql_type = "Integer"] pub enum RevTableType { - Local = 0, - Remote = 1, + Local = 0, + Remote = 1, } impl_sql_integer_expression!(RevTableType); impl std::default::Default for RevTableType { - fn default() -> Self { - RevTableType::Local - } + fn default() -> Self { + RevTableType::Local + } } impl std::convert::From for RevTableType { - fn from(value: i32) -> Self { - match value { - 0 => RevTableType::Local, - 1 => RevTableType::Remote, - o => { - tracing::error!("Unsupported rev type {}, fallback to RevTableType::Local", o); - RevTableType::Local - } - } + fn from(value: i32) -> Self { + match value { + 0 => RevTableType::Local, + 1 => RevTableType::Remote, + o => { + tracing::error!( + "Unsupported rev type {}, fallback to RevTableType::Local", + o + ); + RevTableType::Local + }, } + } } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_snapshot.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_snapshot.rs index d3c13696fb..664818167d 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_snapshot.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_snapshot.rs @@ -3,95 +3,95 @@ use bytes::Bytes; use flowy_error::{internal_error, FlowyResult}; use flowy_revision::{RevisionSnapshotData, RevisionSnapshotPersistence}; use flowy_sqlite::{ - prelude::*, - schema::{folder_rev_snapshot, folder_rev_snapshot::dsl}, - ConnectionPool, + prelude::*, + schema::{folder_rev_snapshot, folder_rev_snapshot::dsl}, + ConnectionPool, }; use lib_infra::util::timestamp; use std::sync::Arc; pub struct SQLiteFolderRevisionSnapshotPersistence { - object_id: String, - pool: Arc, + object_id: String, + pool: Arc, } impl SQLiteFolderRevisionSnapshotPersistence { - pub fn new(object_id: &str, pool: Arc) -> Self { - Self { - object_id: object_id.to_string(), - pool, - } + pub fn new(object_id: &str, pool: Arc) -> Self { + Self { + object_id: object_id.to_string(), + pool, } + } - fn gen_snapshot_id(&self, rev_id: i64) -> String { - format!("{}:{}", self.object_id, rev_id) - } + fn gen_snapshot_id(&self, rev_id: i64) -> String { + format!("{}:{}", self.object_id, rev_id) + } } impl RevisionSnapshotPersistence for SQLiteFolderRevisionSnapshotPersistence { - fn should_generate_snapshot_from_range(&self, start_rev_id: i64, current_rev_id: i64) -> bool { - (current_rev_id - start_rev_id) >= 2 - } + fn should_generate_snapshot_from_range(&self, start_rev_id: i64, current_rev_id: i64) -> bool { + (current_rev_id - start_rev_id) >= 2 + } - fn write_snapshot(&self, rev_id: i64, data: Vec) -> FlowyResult<()> { - let conn = self.pool.get().map_err(internal_error)?; - let snapshot_id = self.gen_snapshot_id(rev_id); - let timestamp = timestamp(); - let record = ( - dsl::snapshot_id.eq(&snapshot_id), - dsl::object_id.eq(&self.object_id), - dsl::rev_id.eq(rev_id), - dsl::base_rev_id.eq(rev_id), - dsl::timestamp.eq(timestamp), - dsl::data.eq(data), - ); - let _ = insert_or_ignore_into(dsl::folder_rev_snapshot) - .values(record) - .execute(&*conn)?; - Ok(()) - } + fn write_snapshot(&self, rev_id: i64, data: Vec) -> FlowyResult<()> { + let conn = self.pool.get().map_err(internal_error)?; + let snapshot_id = self.gen_snapshot_id(rev_id); + let timestamp = timestamp(); + let record = ( + dsl::snapshot_id.eq(&snapshot_id), + dsl::object_id.eq(&self.object_id), + dsl::rev_id.eq(rev_id), + dsl::base_rev_id.eq(rev_id), + dsl::timestamp.eq(timestamp), + dsl::data.eq(data), + ); + let _ = insert_or_ignore_into(dsl::folder_rev_snapshot) + .values(record) + .execute(&*conn)?; + Ok(()) + } - fn read_snapshot(&self, rev_id: i64) -> FlowyResult> { - let conn = self.pool.get().map_err(internal_error)?; - let snapshot_id = self.gen_snapshot_id(rev_id); - let record = dsl::folder_rev_snapshot - .filter(dsl::snapshot_id.eq(&snapshot_id)) - .first::(&*conn)?; + fn read_snapshot(&self, rev_id: i64) -> FlowyResult> { + let conn = self.pool.get().map_err(internal_error)?; + let snapshot_id = self.gen_snapshot_id(rev_id); + let record = dsl::folder_rev_snapshot + .filter(dsl::snapshot_id.eq(&snapshot_id)) + .first::(&*conn)?; - Ok(Some(record.into())) - } + Ok(Some(record.into())) + } - fn read_last_snapshot(&self) -> FlowyResult> { - let conn = self.pool.get().map_err(internal_error)?; - let latest_record = dsl::folder_rev_snapshot + fn read_last_snapshot(&self) -> FlowyResult> { + let conn = self.pool.get().map_err(internal_error)?; + let latest_record = dsl::folder_rev_snapshot .filter(dsl::object_id.eq(&self.object_id)) .order(dsl::timestamp.desc()) // .select(max(dsl::rev_id)) // .select((dsl::id, dsl::object_id, dsl::rev_id, dsl::data)) .first::(&*conn)?; - Ok(Some(latest_record.into())) - } + Ok(Some(latest_record.into())) + } } #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] #[table_name = "folder_rev_snapshot"] #[primary_key("snapshot_id")] struct FolderSnapshotRecord { - snapshot_id: String, - object_id: String, - rev_id: i64, - base_rev_id: i64, - timestamp: i64, - data: Vec, + snapshot_id: String, + object_id: String, + rev_id: i64, + base_rev_id: i64, + timestamp: i64, + data: Vec, } impl std::convert::From for RevisionSnapshotData { - fn from(record: FolderSnapshotRecord) -> Self { - RevisionSnapshotData { - rev_id: record.rev_id, - base_rev_id: record.base_rev_id, - timestamp: record.timestamp, - data: Bytes::from(record.data), - } + fn from(record: FolderSnapshotRecord) -> Self { + RevisionSnapshotData { + rev_id: record.rev_id, + base_rev_id: record.base_rev_id, + timestamp: record.timestamp, + data: Bytes::from(record.data), } + } } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs index 2b28e1dd94..43096ec1b9 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs @@ -1,163 +1,169 @@ use crate::entities::{ - app::UpdateAppParams, - trash::{TrashPB, TrashType}, + app::UpdateAppParams, + trash::{TrashPB, TrashType}, }; use crate::{errors::FlowyError, services::persistence::version_1::workspace_sql::WorkspaceTable}; use flowy_sqlite::{ - prelude::*, - schema::{app_table, app_table::dsl}, - SqliteConnection, + prelude::*, + schema::{app_table, app_table::dsl}, + SqliteConnection, }; use folder_model::AppRevision; pub struct AppTableSql(); impl AppTableSql { - pub(crate) fn create_app(app_rev: AppRevision, conn: &SqliteConnection) -> Result<(), FlowyError> { - let app_table = AppTable::new(app_rev); - match diesel_record_count!(app_table, &app_table.id, conn) { - 0 => diesel_insert_table!(app_table, app_table.clone(), conn), - _ => { - let changeset = AppChangeset::from_table(app_table); - diesel_update_table!(app_table, changeset, conn) - } - } - Ok(()) + pub(crate) fn create_app( + app_rev: AppRevision, + conn: &SqliteConnection, + ) -> Result<(), FlowyError> { + let app_table = AppTable::new(app_rev); + match diesel_record_count!(app_table, &app_table.id, conn) { + 0 => diesel_insert_table!(app_table, app_table.clone(), conn), + _ => { + let changeset = AppChangeset::from_table(app_table); + diesel_update_table!(app_table, changeset, conn) + }, } + Ok(()) + } - pub(crate) fn update_app(changeset: AppChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { - diesel_update_table!(app_table, changeset, conn); - Ok(()) - } + pub(crate) fn update_app( + changeset: AppChangeset, + conn: &SqliteConnection, + ) -> Result<(), FlowyError> { + diesel_update_table!(app_table, changeset, conn); + Ok(()) + } - pub(crate) fn read_app(app_id: &str, conn: &SqliteConnection) -> Result { - let filter = dsl::app_table.filter(app_table::id.eq(app_id)).into_boxed(); - let app_table = filter.first::(conn)?; - Ok(app_table) - } + pub(crate) fn read_app(app_id: &str, conn: &SqliteConnection) -> Result { + let filter = dsl::app_table.filter(app_table::id.eq(app_id)).into_boxed(); + let app_table = filter.first::(conn)?; + Ok(app_table) + } - pub(crate) fn read_workspace_apps( - workspace_id: &str, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let app_table = dsl::app_table - .filter(app_table::workspace_id.eq(workspace_id)) - .order(app_table::create_time.asc()) - .load::(conn)?; + pub(crate) fn read_workspace_apps( + workspace_id: &str, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let app_table = dsl::app_table + .filter(app_table::workspace_id.eq(workspace_id)) + .order(app_table::create_time.asc()) + .load::(conn)?; - Ok(app_table) - } + Ok(app_table) + } - pub(crate) fn delete_app(app_id: &str, conn: &SqliteConnection) -> Result { - let app_table = dsl::app_table - .filter(app_table::id.eq(app_id)) - .first::(conn)?; - diesel_delete_table!(app_table, app_id, conn); - Ok(app_table) - } + pub(crate) fn delete_app(app_id: &str, conn: &SqliteConnection) -> Result { + let app_table = dsl::app_table + .filter(app_table::id.eq(app_id)) + .first::(conn)?; + diesel_delete_table!(app_table, app_id, conn); + Ok(app_table) + } - // pub(crate) fn read_views_belong_to_app( - // &self, - // app_id: &str, - // ) -> Result, FlowyError> { - // let conn = self.database.db_connection()?; - // - // let views = conn.immediate_transaction::<_, FlowyError, _>(|| { - // let app_table: AppTable = dsl::app_table - // .filter(app_table::id.eq(app_id)) - // .first::(&*(conn))?; - // let views = - // ViewTable::belonging_to(&app_table).load::(&*conn)?; - // Ok(views) - // })?; - // - // Ok(views) - // } + // pub(crate) fn read_views_belong_to_app( + // &self, + // app_id: &str, + // ) -> Result, FlowyError> { + // let conn = self.database.db_connection()?; + // + // let views = conn.immediate_transaction::<_, FlowyError, _>(|| { + // let app_table: AppTable = dsl::app_table + // .filter(app_table::id.eq(app_id)) + // .first::(&*(conn))?; + // let views = + // ViewTable::belonging_to(&app_table).load::(&*conn)?; + // Ok(views) + // })?; + // + // Ok(views) + // } } #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] #[belongs_to(WorkspaceTable, foreign_key = "workspace_id")] #[table_name = "app_table"] pub(crate) struct AppTable { - pub id: String, - pub workspace_id: String, // equal to #[belongs_to(Workspace, foreign_key = "workspace_id")]. - pub name: String, - pub desc: String, - pub color_style: Vec, - pub last_view_id: Option, - pub modified_time: i64, - pub create_time: i64, - pub version: i64, - pub is_trash: bool, + pub id: String, + pub workspace_id: String, // equal to #[belongs_to(Workspace, foreign_key = "workspace_id")]. + pub name: String, + pub desc: String, + pub color_style: Vec, + pub last_view_id: Option, + pub modified_time: i64, + pub create_time: i64, + pub version: i64, + pub is_trash: bool, } impl AppTable { - pub fn new(app_rev: AppRevision) -> Self { - Self { - id: app_rev.id, - workspace_id: app_rev.workspace_id, - name: app_rev.name, - desc: app_rev.desc, - color_style: Default::default(), - last_view_id: None, - modified_time: app_rev.modified_time, - create_time: app_rev.create_time, - version: 0, - is_trash: false, - } + pub fn new(app_rev: AppRevision) -> Self { + Self { + id: app_rev.id, + workspace_id: app_rev.workspace_id, + name: app_rev.name, + desc: app_rev.desc, + color_style: Default::default(), + last_view_id: None, + modified_time: app_rev.modified_time, + create_time: app_rev.create_time, + version: 0, + is_trash: false, } + } } impl std::convert::From for TrashPB { - fn from(table: AppTable) -> Self { - TrashPB { - id: table.id, - name: table.name, - modified_time: table.modified_time, - create_time: table.create_time, - ty: TrashType::TrashApp, - } + fn from(table: AppTable) -> Self { + TrashPB { + id: table.id, + name: table.name, + modified_time: table.modified_time, + create_time: table.create_time, + ty: TrashType::TrashApp, } + } } #[derive(AsChangeset, Identifiable, Default, Debug)] #[table_name = "app_table"] pub struct AppChangeset { - pub id: String, - pub name: Option, - pub desc: Option, - pub is_trash: Option, + pub id: String, + pub name: Option, + pub desc: Option, + pub is_trash: Option, } impl AppChangeset { - pub(crate) fn new(params: UpdateAppParams) -> Self { - AppChangeset { - id: params.app_id, - name: params.name, - desc: params.desc, - is_trash: params.is_trash, - } + pub(crate) fn new(params: UpdateAppParams) -> Self { + AppChangeset { + id: params.app_id, + name: params.name, + desc: params.desc, + is_trash: params.is_trash, } + } - pub(crate) fn from_table(table: AppTable) -> Self { - AppChangeset { - id: table.id, - name: Some(table.name), - desc: Some(table.desc), - is_trash: Some(table.is_trash), - } + pub(crate) fn from_table(table: AppTable) -> Self { + AppChangeset { + id: table.id, + name: Some(table.name), + desc: Some(table.desc), + is_trash: Some(table.is_trash), } + } } impl std::convert::From for AppRevision { - fn from(table: AppTable) -> Self { - AppRevision { - id: table.id, - workspace_id: table.workspace_id, - name: table.name, - desc: table.desc, - belongings: vec![], - version: table.version, - modified_time: table.modified_time, - create_time: table.create_time, - } + fn from(table: AppTable) -> Self { + AppRevision { + id: table.id, + workspace_id: table.workspace_id, + name: table.name, + desc: table.desc, + belongings: vec![], + version: table.version, + modified_time: table.modified_time, + create_time: table.create_time, } + } } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/trash_sql.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/trash_sql.rs index 82f7063012..def1ae74a5 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/trash_sql.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/trash_sql.rs @@ -1,65 +1,68 @@ use crate::errors::FlowyError; use diesel::sql_types::Integer; use flowy_sqlite::{ - prelude::*, - schema::{trash_table, trash_table::dsl}, - SqliteConnection, + prelude::*, + schema::{trash_table, trash_table::dsl}, + SqliteConnection, }; use folder_model::{TrashRevision, TrashTypeRevision}; pub struct TrashTableSql(); impl TrashTableSql { - pub(crate) fn create_trash(trashes: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { - for trash_rev in trashes { - let trash_table: TrashTable = trash_rev.into(); - match diesel_record_count!(trash_table, &trash_table.id, conn) { - 0 => diesel_insert_table!(trash_table, trash_table.clone(), conn), - _ => { - let changeset = TrashChangeset::from(trash_table); - diesel_update_table!(trash_table, changeset, conn) - } - } - } - - Ok(()) + pub(crate) fn create_trash( + trashes: Vec, + conn: &SqliteConnection, + ) -> Result<(), FlowyError> { + for trash_rev in trashes { + let trash_table: TrashTable = trash_rev.into(); + match diesel_record_count!(trash_table, &trash_table.id, conn) { + 0 => diesel_insert_table!(trash_table, trash_table.clone(), conn), + _ => { + let changeset = TrashChangeset::from(trash_table); + diesel_update_table!(trash_table, changeset, conn) + }, + } } - pub(crate) fn read_all(conn: &SqliteConnection) -> Result, FlowyError> { - let trash_tables = dsl::trash_table.load::(conn)?; - let items = trash_tables - .into_iter() - .map(TrashRevision::from) - .collect::>(); - Ok(items) - } + Ok(()) + } - pub(crate) fn delete_all(conn: &SqliteConnection) -> Result<(), FlowyError> { - let _ = diesel::delete(dsl::trash_table).execute(conn)?; - Ok(()) - } + pub(crate) fn read_all(conn: &SqliteConnection) -> Result, FlowyError> { + let trash_tables = dsl::trash_table.load::(conn)?; + let items = trash_tables + .into_iter() + .map(TrashRevision::from) + .collect::>(); + Ok(items) + } - pub(crate) fn read(trash_id: &str, conn: &SqliteConnection) -> Result { - let trash_table = dsl::trash_table - .filter(trash_table::id.eq(trash_id)) - .first::(conn)?; - Ok(trash_table) - } + pub(crate) fn delete_all(conn: &SqliteConnection) -> Result<(), FlowyError> { + let _ = diesel::delete(dsl::trash_table).execute(conn)?; + Ok(()) + } - pub(crate) fn delete_trash(trash_id: &str, conn: &SqliteConnection) -> Result<(), FlowyError> { - diesel_delete_table!(trash_table, trash_id, conn); - Ok(()) - } + pub(crate) fn read(trash_id: &str, conn: &SqliteConnection) -> Result { + let trash_table = dsl::trash_table + .filter(trash_table::id.eq(trash_id)) + .first::(conn)?; + Ok(trash_table) + } + + pub(crate) fn delete_trash(trash_id: &str, conn: &SqliteConnection) -> Result<(), FlowyError> { + diesel_delete_table!(trash_table, trash_id, conn); + Ok(()) + } } #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] #[table_name = "trash_table"] pub(crate) struct TrashTable { - pub id: String, - pub name: String, - pub desc: String, - pub modified_time: i64, - pub create_time: i64, - pub ty: SqlTrashType, + pub id: String, + pub name: String, + pub desc: String, + pub modified_time: i64, + pub create_time: i64, + pub ty: SqlTrashType, } // impl std::convert::From for Trash { // fn from(table: TrashTable) -> Self { @@ -74,86 +77,86 @@ pub(crate) struct TrashTable { // } // impl std::convert::From for TrashRevision { - fn from(trash: TrashTable) -> Self { - TrashRevision { - id: trash.id, - name: trash.name, - modified_time: trash.modified_time, - create_time: trash.create_time, - ty: trash.ty.into(), - } + fn from(trash: TrashTable) -> Self { + TrashRevision { + id: trash.id, + name: trash.name, + modified_time: trash.modified_time, + create_time: trash.create_time, + ty: trash.ty.into(), } + } } impl std::convert::From for TrashTable { - fn from(trash_rev: TrashRevision) -> Self { - TrashTable { - id: trash_rev.id, - name: trash_rev.name, - desc: "".to_string(), - modified_time: trash_rev.modified_time, - create_time: trash_rev.create_time, - ty: trash_rev.ty.into(), - } + fn from(trash_rev: TrashRevision) -> Self { + TrashTable { + id: trash_rev.id, + name: trash_rev.name, + desc: "".to_string(), + modified_time: trash_rev.modified_time, + create_time: trash_rev.create_time, + ty: trash_rev.ty.into(), } + } } #[derive(AsChangeset, Identifiable, Clone, Default, Debug)] #[table_name = "trash_table"] pub(crate) struct TrashChangeset { - pub id: String, - pub name: Option, - pub modified_time: i64, + pub id: String, + pub name: Option, + pub modified_time: i64, } impl std::convert::From for TrashChangeset { - fn from(trash: TrashTable) -> Self { - TrashChangeset { - id: trash.id, - name: Some(trash.name), - modified_time: trash.modified_time, - } + fn from(trash: TrashTable) -> Self { + TrashChangeset { + id: trash.id, + name: Some(trash.name), + modified_time: trash.modified_time, } + } } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] #[repr(i32)] #[sql_type = "Integer"] pub(crate) enum SqlTrashType { - Unknown = 0, - View = 1, - App = 2, + Unknown = 0, + View = 1, + App = 2, } impl std::convert::From for SqlTrashType { - fn from(value: i32) -> Self { - match value { - 0 => SqlTrashType::Unknown, - 1 => SqlTrashType::View, - 2 => SqlTrashType::App, - _o => SqlTrashType::Unknown, - } + fn from(value: i32) -> Self { + match value { + 0 => SqlTrashType::Unknown, + 1 => SqlTrashType::View, + 2 => SqlTrashType::App, + _o => SqlTrashType::Unknown, } + } } impl_sql_integer_expression!(SqlTrashType); impl std::convert::From for TrashTypeRevision { - fn from(ty: SqlTrashType) -> Self { - match ty { - SqlTrashType::Unknown => TrashTypeRevision::Unknown, - SqlTrashType::View => TrashTypeRevision::TrashView, - SqlTrashType::App => TrashTypeRevision::TrashApp, - } + fn from(ty: SqlTrashType) -> Self { + match ty { + SqlTrashType::Unknown => TrashTypeRevision::Unknown, + SqlTrashType::View => TrashTypeRevision::TrashView, + SqlTrashType::App => TrashTypeRevision::TrashApp, } + } } impl std::convert::From for SqlTrashType { - fn from(ty: TrashTypeRevision) -> Self { - match ty { - TrashTypeRevision::Unknown => SqlTrashType::Unknown, - TrashTypeRevision::TrashView => SqlTrashType::View, - TrashTypeRevision::TrashApp => SqlTrashType::App, - } + fn from(ty: TrashTypeRevision) -> Self { + match ty { + TrashTypeRevision::Unknown => SqlTrashType::Unknown, + TrashTypeRevision::TrashView => SqlTrashType::View, + TrashTypeRevision::TrashApp => SqlTrashType::App, } + } } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/v1_impl.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/v1_impl.rs index 08c8277f61..1f6c517cfa 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/v1_impl.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/v1_impl.rs @@ -1,10 +1,10 @@ use crate::services::persistence::{ - version_1::{ - app_sql::{AppChangeset, AppTableSql}, - view_sql::{ViewChangeset, ViewTableSql}, - workspace_sql::{WorkspaceChangeset, WorkspaceTableSql}, - }, - FolderPersistenceTransaction, TrashTableSql, + version_1::{ + app_sql::{AppChangeset, AppTableSql}, + view_sql::{ViewChangeset, ViewTableSql}, + workspace_sql::{WorkspaceChangeset, WorkspaceTableSql}, + }, + FolderPersistenceTransaction, TrashTableSql, }; use flowy_error::FlowyResult; use flowy_sqlite::DBConnection; @@ -14,192 +14,209 @@ use folder_model::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; pub struct V1Transaction<'a>(pub &'a DBConnection); impl<'a> FolderPersistenceTransaction for V1Transaction<'a> { - fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> { - WorkspaceTableSql::create_workspace(user_id, workspace_rev, self.0)?; - Ok(()) - } + fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> { + WorkspaceTableSql::create_workspace(user_id, workspace_rev, self.0)?; + Ok(()) + } - fn read_workspaces(&self, user_id: &str, workspace_id: Option) -> FlowyResult> { - let tables = WorkspaceTableSql::read_workspaces(user_id, workspace_id, self.0)?; - let workspaces = tables.into_iter().map(WorkspaceRevision::from).collect::>(); - Ok(workspaces) - } + fn read_workspaces( + &self, + user_id: &str, + workspace_id: Option, + ) -> FlowyResult> { + let tables = WorkspaceTableSql::read_workspaces(user_id, workspace_id, self.0)?; + let workspaces = tables + .into_iter() + .map(WorkspaceRevision::from) + .collect::>(); + Ok(workspaces) + } - fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> { - WorkspaceTableSql::update_workspace(changeset, self.0) - } + fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> { + WorkspaceTableSql::update_workspace(changeset, self.0) + } - fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> { - WorkspaceTableSql::delete_workspace(workspace_id, self.0) - } + fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> { + WorkspaceTableSql::delete_workspace(workspace_id, self.0) + } - fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> { - AppTableSql::create_app(app_rev, self.0)?; - Ok(()) - } + fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> { + AppTableSql::create_app(app_rev, self.0)?; + Ok(()) + } - fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { - AppTableSql::update_app(changeset, self.0)?; - Ok(()) - } + fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { + AppTableSql::update_app(changeset, self.0)?; + Ok(()) + } - fn read_app(&self, app_id: &str) -> FlowyResult { - let app_revision: AppRevision = AppTableSql::read_app(app_id, self.0)?.into(); - Ok(app_revision) - } + fn read_app(&self, app_id: &str) -> FlowyResult { + let app_revision: AppRevision = AppTableSql::read_app(app_id, self.0)?.into(); + Ok(app_revision) + } - fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult> { - let tables = AppTableSql::read_workspace_apps(workspace_id, self.0)?; - let apps = tables.into_iter().map(AppRevision::from).collect::>(); - Ok(apps) - } + fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult> { + let tables = AppTableSql::read_workspace_apps(workspace_id, self.0)?; + let apps = tables + .into_iter() + .map(AppRevision::from) + .collect::>(); + Ok(apps) + } - fn delete_app(&self, app_id: &str) -> FlowyResult { - let app_revision: AppRevision = AppTableSql::delete_app(app_id, self.0)?.into(); - Ok(app_revision) - } + fn delete_app(&self, app_id: &str) -> FlowyResult { + let app_revision: AppRevision = AppTableSql::delete_app(app_id, self.0)?.into(); + Ok(app_revision) + } - fn move_app(&self, _app_id: &str, _from: usize, _to: usize) -> FlowyResult<()> { - Ok(()) - } + fn move_app(&self, _app_id: &str, _from: usize, _to: usize) -> FlowyResult<()> { + Ok(()) + } - fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> { - ViewTableSql::create_view(view_rev, self.0)?; - Ok(()) - } + fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> { + ViewTableSql::create_view(view_rev, self.0)?; + Ok(()) + } - fn read_view(&self, view_id: &str) -> FlowyResult { - let view_revision: ViewRevision = ViewTableSql::read_view(view_id, self.0)?.into(); - Ok(view_revision) - } + fn read_view(&self, view_id: &str) -> FlowyResult { + let view_revision: ViewRevision = ViewTableSql::read_view(view_id, self.0)?.into(); + Ok(view_revision) + } - fn read_views(&self, belong_to_id: &str) -> FlowyResult> { - let tables = ViewTableSql::read_views(belong_to_id, self.0)?; - let views = tables.into_iter().map(ViewRevision::from).collect::>(); - Ok(views) - } + fn read_views(&self, belong_to_id: &str) -> FlowyResult> { + let tables = ViewTableSql::read_views(belong_to_id, self.0)?; + let views = tables + .into_iter() + .map(ViewRevision::from) + .collect::>(); + Ok(views) + } - fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { - ViewTableSql::update_view(changeset, self.0)?; - Ok(()) - } + fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { + ViewTableSql::update_view(changeset, self.0)?; + Ok(()) + } - fn delete_view(&self, view_id: &str) -> FlowyResult { - let view_revision: ViewRevision = ViewTableSql::read_view(view_id, self.0)?.into(); - ViewTableSql::delete_view(view_id, self.0)?; - Ok(view_revision) - } + fn delete_view(&self, view_id: &str) -> FlowyResult { + let view_revision: ViewRevision = ViewTableSql::read_view(view_id, self.0)?.into(); + ViewTableSql::delete_view(view_id, self.0)?; + Ok(view_revision) + } - fn move_view(&self, _view_id: &str, _from: usize, _to: usize) -> FlowyResult<()> { - Ok(()) - } + fn move_view(&self, _view_id: &str, _from: usize, _to: usize) -> FlowyResult<()> { + Ok(()) + } - fn create_trash(&self, trashes: Vec) -> FlowyResult<()> { - TrashTableSql::create_trash(trashes, self.0)?; - Ok(()) - } + fn create_trash(&self, trashes: Vec) -> FlowyResult<()> { + TrashTableSql::create_trash(trashes, self.0)?; + Ok(()) + } - fn read_trash(&self, trash_id: Option) -> FlowyResult> { - match trash_id { - None => TrashTableSql::read_all(self.0), - Some(trash_id) => { - let trash_revision: TrashRevision = TrashTableSql::read(&trash_id, self.0)?.into(); - Ok(vec![trash_revision]) - } - } - } - - fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { - match trash_ids { - None => TrashTableSql::delete_all(self.0), - Some(trash_ids) => { - for trash_id in &trash_ids { - TrashTableSql::delete_trash(trash_id, self.0)?; - } - Ok(()) - } + fn read_trash(&self, trash_id: Option) -> FlowyResult> { + match trash_id { + None => TrashTableSql::read_all(self.0), + Some(trash_id) => { + let trash_revision: TrashRevision = TrashTableSql::read(&trash_id, self.0)?.into(); + Ok(vec![trash_revision]) + }, + } + } + + fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { + match trash_ids { + None => TrashTableSql::delete_all(self.0), + Some(trash_ids) => { + for trash_id in &trash_ids { + TrashTableSql::delete_trash(trash_id, self.0)?; } + Ok(()) + }, } + } } // https://www.reddit.com/r/rust/comments/droxdg/why_arent_traits_impld_for_boxdyn_trait/ impl FolderPersistenceTransaction for Box where - T: FolderPersistenceTransaction + ?Sized, + T: FolderPersistenceTransaction + ?Sized, { - fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> { - (**self).create_workspace(user_id, workspace_rev) - } + fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> { + (**self).create_workspace(user_id, workspace_rev) + } - fn read_workspaces(&self, user_id: &str, workspace_id: Option) -> FlowyResult> { - (**self).read_workspaces(user_id, workspace_id) - } + fn read_workspaces( + &self, + user_id: &str, + workspace_id: Option, + ) -> FlowyResult> { + (**self).read_workspaces(user_id, workspace_id) + } - fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> { - (**self).update_workspace(changeset) - } + fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> { + (**self).update_workspace(changeset) + } - fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> { - (**self).delete_workspace(workspace_id) - } + fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> { + (**self).delete_workspace(workspace_id) + } - fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> { - (**self).create_app(app_rev) - } + fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> { + (**self).create_app(app_rev) + } - fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { - (**self).update_app(changeset) - } + fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { + (**self).update_app(changeset) + } - fn read_app(&self, app_id: &str) -> FlowyResult { - (**self).read_app(app_id) - } + fn read_app(&self, app_id: &str) -> FlowyResult { + (**self).read_app(app_id) + } - fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult> { - (**self).read_workspace_apps(workspace_id) - } + fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult> { + (**self).read_workspace_apps(workspace_id) + } - fn delete_app(&self, app_id: &str) -> FlowyResult { - (**self).delete_app(app_id) - } + fn delete_app(&self, app_id: &str) -> FlowyResult { + (**self).delete_app(app_id) + } - fn move_app(&self, _app_id: &str, _from: usize, _to: usize) -> FlowyResult<()> { - Ok(()) - } + fn move_app(&self, _app_id: &str, _from: usize, _to: usize) -> FlowyResult<()> { + Ok(()) + } - fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> { - (**self).create_view(view_rev) - } + fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> { + (**self).create_view(view_rev) + } - fn read_view(&self, view_id: &str) -> FlowyResult { - (**self).read_view(view_id) - } + fn read_view(&self, view_id: &str) -> FlowyResult { + (**self).read_view(view_id) + } - fn read_views(&self, belong_to_id: &str) -> FlowyResult> { - (**self).read_views(belong_to_id) - } + fn read_views(&self, belong_to_id: &str) -> FlowyResult> { + (**self).read_views(belong_to_id) + } - fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { - (**self).update_view(changeset) - } + fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { + (**self).update_view(changeset) + } - fn delete_view(&self, view_id: &str) -> FlowyResult { - (**self).delete_view(view_id) - } + fn delete_view(&self, view_id: &str) -> FlowyResult { + (**self).delete_view(view_id) + } - fn move_view(&self, _view_id: &str, _from: usize, _to: usize) -> FlowyResult<()> { - Ok(()) - } + fn move_view(&self, _view_id: &str, _from: usize, _to: usize) -> FlowyResult<()> { + Ok(()) + } - fn create_trash(&self, trashes: Vec) -> FlowyResult<()> { - (**self).create_trash(trashes) - } + fn create_trash(&self, trashes: Vec) -> FlowyResult<()> { + (**self).create_trash(trashes) + } - fn read_trash(&self, trash_id: Option) -> FlowyResult> { - (**self).read_trash(trash_id) - } + fn read_trash(&self, trash_id: Option) -> FlowyResult> { + (**self).read_trash(trash_id) + } - fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { - (**self).delete_trash(trash_ids) - } + fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { + (**self).delete_trash(trash_ids) + } } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs index 1f913c6a63..b1180177f2 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs @@ -1,16 +1,16 @@ use crate::{ - entities::{ - trash::{TrashPB, TrashType}, - view::UpdateViewParams, - }, - errors::FlowyError, - services::persistence::version_1::app_sql::AppTable, + entities::{ + trash::{TrashPB, TrashType}, + view::UpdateViewParams, + }, + errors::FlowyError, + services::persistence::version_1::app_sql::AppTable, }; use diesel::sql_types::Integer; use flowy_sqlite::{ - prelude::*, - schema::{view_table, view_table::dsl}, - SqliteConnection, + prelude::*, + schema::{view_table, view_table::dsl}, + SqliteConnection, }; use folder_model::{ViewDataFormatRevision, ViewLayoutTypeRevision, ViewRevision}; @@ -18,200 +18,209 @@ use lib_infra::util::timestamp; pub struct ViewTableSql(); impl ViewTableSql { - pub(crate) fn create_view(view_rev: ViewRevision, conn: &SqliteConnection) -> Result<(), FlowyError> { - let view_table = ViewTable::new(view_rev); - match diesel_record_count!(view_table, &view_table.id, conn) { - 0 => diesel_insert_table!(view_table, view_table.clone(), conn), - _ => { - let changeset = ViewChangeset::from_table(view_table); - diesel_update_table!(view_table, changeset, conn) - } - } - Ok(()) + pub(crate) fn create_view( + view_rev: ViewRevision, + conn: &SqliteConnection, + ) -> Result<(), FlowyError> { + let view_table = ViewTable::new(view_rev); + match diesel_record_count!(view_table, &view_table.id, conn) { + 0 => diesel_insert_table!(view_table, view_table.clone(), conn), + _ => { + let changeset = ViewChangeset::from_table(view_table); + diesel_update_table!(view_table, changeset, conn) + }, } + Ok(()) + } - pub(crate) fn read_view(view_id: &str, conn: &SqliteConnection) -> Result { - // https://docs.diesel.rs/diesel/query_builder/struct.UpdateStatement.html - // let mut filter = - // dsl::view_table.filter(view_table::id.eq(view_id)).into_boxed(); - // if let Some(is_trash) = is_trash { - // filter = filter.filter(view_table::is_trash.eq(is_trash)); - // } - // let repeated_view = filter.first::(conn)?; - let view_table = dsl::view_table - .filter(view_table::id.eq(view_id)) - .first::(conn)?; + pub(crate) fn read_view(view_id: &str, conn: &SqliteConnection) -> Result { + // https://docs.diesel.rs/diesel/query_builder/struct.UpdateStatement.html + // let mut filter = + // dsl::view_table.filter(view_table::id.eq(view_id)).into_boxed(); + // if let Some(is_trash) = is_trash { + // filter = filter.filter(view_table::is_trash.eq(is_trash)); + // } + // let repeated_view = filter.first::(conn)?; + let view_table = dsl::view_table + .filter(view_table::id.eq(view_id)) + .first::(conn)?; - Ok(view_table) - } + Ok(view_table) + } - // belong_to_id will be the app_id or view_id. - pub(crate) fn read_views(belong_to_id: &str, conn: &SqliteConnection) -> Result, FlowyError> { - let view_tables = dsl::view_table - .filter(view_table::belong_to_id.eq(belong_to_id)) - .order(view_table::create_time.asc()) - .into_boxed() - .load::(conn)?; + // belong_to_id will be the app_id or view_id. + pub(crate) fn read_views( + belong_to_id: &str, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let view_tables = dsl::view_table + .filter(view_table::belong_to_id.eq(belong_to_id)) + .order(view_table::create_time.asc()) + .into_boxed() + .load::(conn)?; - Ok(view_tables) - } + Ok(view_tables) + } - pub(crate) fn update_view(changeset: ViewChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { - diesel_update_table!(view_table, changeset, conn); - Ok(()) - } + pub(crate) fn update_view( + changeset: ViewChangeset, + conn: &SqliteConnection, + ) -> Result<(), FlowyError> { + diesel_update_table!(view_table, changeset, conn); + Ok(()) + } - pub(crate) fn delete_view(view_id: &str, conn: &SqliteConnection) -> Result<(), FlowyError> { - diesel_delete_table!(view_table, view_id, conn); - Ok(()) - } + pub(crate) fn delete_view(view_id: &str, conn: &SqliteConnection) -> Result<(), FlowyError> { + diesel_delete_table!(view_table, view_id, conn); + Ok(()) + } } #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] #[belongs_to(AppTable, foreign_key = "belong_to_id")] #[table_name = "view_table"] pub(crate) struct ViewTable { - pub id: String, - pub belong_to_id: String, - pub name: String, - pub desc: String, - pub modified_time: i64, - pub create_time: i64, - pub thumbnail: String, - pub view_type: SqlViewDataFormat, - pub version: i64, - pub is_trash: bool, - pub ext_data: String, + pub id: String, + pub belong_to_id: String, + pub name: String, + pub desc: String, + pub modified_time: i64, + pub create_time: i64, + pub thumbnail: String, + pub view_type: SqlViewDataFormat, + pub version: i64, + pub is_trash: bool, + pub ext_data: String, } impl ViewTable { - pub fn new(view_rev: ViewRevision) -> Self { - let data_type = match view_rev.data_format { - ViewDataFormatRevision::DeltaFormat => SqlViewDataFormat::Delta, - ViewDataFormatRevision::DatabaseFormat => SqlViewDataFormat::Database, - ViewDataFormatRevision::NodeFormat => SqlViewDataFormat::Tree, - }; + pub fn new(view_rev: ViewRevision) -> Self { + let data_type = match view_rev.data_format { + ViewDataFormatRevision::DeltaFormat => SqlViewDataFormat::Delta, + ViewDataFormatRevision::DatabaseFormat => SqlViewDataFormat::Database, + ViewDataFormatRevision::NodeFormat => SqlViewDataFormat::Tree, + }; - ViewTable { - id: view_rev.id, - belong_to_id: view_rev.app_id, - name: view_rev.name, - desc: view_rev.desc, - modified_time: view_rev.modified_time, - create_time: view_rev.create_time, - thumbnail: view_rev.thumbnail, - view_type: data_type, - ext_data: view_rev.ext_data, - version: view_rev.version, - is_trash: false, - } + ViewTable { + id: view_rev.id, + belong_to_id: view_rev.app_id, + name: view_rev.name, + desc: view_rev.desc, + modified_time: view_rev.modified_time, + create_time: view_rev.create_time, + thumbnail: view_rev.thumbnail, + view_type: data_type, + ext_data: view_rev.ext_data, + version: view_rev.version, + is_trash: false, } + } } impl std::convert::From for ViewRevision { - fn from(table: ViewTable) -> Self { - let data_type = match table.view_type { - SqlViewDataFormat::Delta => ViewDataFormatRevision::DeltaFormat, - SqlViewDataFormat::Database => ViewDataFormatRevision::DatabaseFormat, - SqlViewDataFormat::Tree => ViewDataFormatRevision::NodeFormat, - }; + fn from(table: ViewTable) -> Self { + let data_type = match table.view_type { + SqlViewDataFormat::Delta => ViewDataFormatRevision::DeltaFormat, + SqlViewDataFormat::Database => ViewDataFormatRevision::DatabaseFormat, + SqlViewDataFormat::Tree => ViewDataFormatRevision::NodeFormat, + }; - ViewRevision { - id: table.id, - app_id: table.belong_to_id, - name: table.name, - desc: table.desc, - data_format: data_type, - belongings: vec![], - modified_time: table.modified_time, - version: table.version, - create_time: table.create_time, - ext_data: "".to_string(), - thumbnail: table.thumbnail, - // Store the view in ViewTable was deprecated since v0.0.2. - // No need to worry about layout. - layout: ViewLayoutTypeRevision::Document, - } + ViewRevision { + id: table.id, + app_id: table.belong_to_id, + name: table.name, + desc: table.desc, + data_format: data_type, + belongings: vec![], + modified_time: table.modified_time, + version: table.version, + create_time: table.create_time, + ext_data: "".to_string(), + thumbnail: table.thumbnail, + // Store the view in ViewTable was deprecated since v0.0.2. + // No need to worry about layout. + layout: ViewLayoutTypeRevision::Document, } + } } impl std::convert::From for TrashPB { - fn from(table: ViewTable) -> Self { - TrashPB { - id: table.id, - name: table.name, - modified_time: table.modified_time, - create_time: table.create_time, - ty: TrashType::TrashView, - } + fn from(table: ViewTable) -> Self { + TrashPB { + id: table.id, + name: table.name, + modified_time: table.modified_time, + create_time: table.create_time, + ty: TrashType::TrashView, } + } } #[derive(AsChangeset, Identifiable, Clone, Default, Debug)] #[table_name = "view_table"] pub struct ViewChangeset { - pub id: String, - pub name: Option, - pub desc: Option, - pub thumbnail: Option, - pub modified_time: i64, + pub id: String, + pub name: Option, + pub desc: Option, + pub thumbnail: Option, + pub modified_time: i64, } impl ViewChangeset { - pub(crate) fn new(params: UpdateViewParams) -> Self { - ViewChangeset { - id: params.view_id, - name: params.name, - desc: params.desc, - thumbnail: params.thumbnail, - modified_time: timestamp(), - } + pub(crate) fn new(params: UpdateViewParams) -> Self { + ViewChangeset { + id: params.view_id, + name: params.name, + desc: params.desc, + thumbnail: params.thumbnail, + modified_time: timestamp(), } + } - pub(crate) fn from_table(table: ViewTable) -> Self { - ViewChangeset { - id: table.id, - name: Some(table.name), - desc: Some(table.desc), - thumbnail: Some(table.thumbnail), - modified_time: table.modified_time, - } + pub(crate) fn from_table(table: ViewTable) -> Self { + ViewChangeset { + id: table.id, + name: Some(table.name), + desc: Some(table.desc), + thumbnail: Some(table.thumbnail), + modified_time: table.modified_time, } + } } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] #[repr(i32)] #[sql_type = "Integer"] pub enum SqlViewDataFormat { - Delta = 0, - Database = 1, - Tree = 2, + Delta = 0, + Database = 1, + Tree = 2, } impl std::default::Default for SqlViewDataFormat { - fn default() -> Self { - SqlViewDataFormat::Delta - } + fn default() -> Self { + SqlViewDataFormat::Delta + } } impl std::convert::From for SqlViewDataFormat { - fn from(value: i32) -> Self { - match value { - 0 => SqlViewDataFormat::Delta, - 1 => SqlViewDataFormat::Database, - 2 => SqlViewDataFormat::Tree, - o => { - log::error!("Unsupported view type {}, fallback to ViewType::Block", o); - SqlViewDataFormat::Delta - } - } + fn from(value: i32) -> Self { + match value { + 0 => SqlViewDataFormat::Delta, + 1 => SqlViewDataFormat::Database, + 2 => SqlViewDataFormat::Tree, + o => { + log::error!("Unsupported view type {}, fallback to ViewType::Block", o); + SqlViewDataFormat::Delta + }, } + } } impl SqlViewDataFormat { - pub fn value(&self) -> i32 { - *self as i32 - } + pub fn value(&self) -> i32 { + *self as i32 + } } impl_sql_integer_expression!(SqlViewDataFormat); diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/workspace_sql.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/workspace_sql.rs index ea3b0bb3bc..2abb483043 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/workspace_sql.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/workspace_sql.rs @@ -1,123 +1,129 @@ use crate::{entities::workspace::UpdateWorkspaceParams, errors::FlowyError}; use diesel::SqliteConnection; use flowy_sqlite::{ - prelude::*, - schema::{workspace_table, workspace_table::dsl}, + prelude::*, + schema::{workspace_table, workspace_table::dsl}, }; use folder_model::WorkspaceRevision; pub(crate) struct WorkspaceTableSql(); impl WorkspaceTableSql { - pub(crate) fn create_workspace( - user_id: &str, - workspace_rev: WorkspaceRevision, - conn: &SqliteConnection, - ) -> Result<(), FlowyError> { - let table = WorkspaceTable::new(workspace_rev, user_id); - match diesel_record_count!(workspace_table, &table.id, conn) { - 0 => diesel_insert_table!(workspace_table, table.clone(), conn), - _ => { - let changeset = WorkspaceChangeset::from_table(table); - diesel_update_table!(workspace_table, changeset, conn); - } - } - Ok(()) - } - - pub(crate) fn read_workspaces( - user_id: &str, - workspace_id: Option, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let mut filter = dsl::workspace_table - .filter(workspace_table::user_id.eq(user_id)) - .order(workspace_table::create_time.asc()) - .into_boxed(); - - if let Some(workspace_id) = workspace_id { - filter = filter.filter(workspace_table::id.eq(workspace_id)); - }; - - let workspaces = filter.load::(conn)?; - - Ok(workspaces) - } - - #[allow(dead_code)] - pub(crate) fn update_workspace(changeset: WorkspaceChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { + pub(crate) fn create_workspace( + user_id: &str, + workspace_rev: WorkspaceRevision, + conn: &SqliteConnection, + ) -> Result<(), FlowyError> { + let table = WorkspaceTable::new(workspace_rev, user_id); + match diesel_record_count!(workspace_table, &table.id, conn) { + 0 => diesel_insert_table!(workspace_table, table.clone(), conn), + _ => { + let changeset = WorkspaceChangeset::from_table(table); diesel_update_table!(workspace_table, changeset, conn); - Ok(()) + }, } + Ok(()) + } - #[allow(dead_code)] - pub(crate) fn delete_workspace(workspace_id: &str, conn: &SqliteConnection) -> Result<(), FlowyError> { - diesel_delete_table!(workspace_table, workspace_id, conn); - Ok(()) - } + pub(crate) fn read_workspaces( + user_id: &str, + workspace_id: Option, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let mut filter = dsl::workspace_table + .filter(workspace_table::user_id.eq(user_id)) + .order(workspace_table::create_time.asc()) + .into_boxed(); + + if let Some(workspace_id) = workspace_id { + filter = filter.filter(workspace_table::id.eq(workspace_id)); + }; + + let workspaces = filter.load::(conn)?; + + Ok(workspaces) + } + + #[allow(dead_code)] + pub(crate) fn update_workspace( + changeset: WorkspaceChangeset, + conn: &SqliteConnection, + ) -> Result<(), FlowyError> { + diesel_update_table!(workspace_table, changeset, conn); + Ok(()) + } + + #[allow(dead_code)] + pub(crate) fn delete_workspace( + workspace_id: &str, + conn: &SqliteConnection, + ) -> Result<(), FlowyError> { + diesel_delete_table!(workspace_table, workspace_id, conn); + Ok(()) + } } #[derive(PartialEq, Eq, Clone, Debug, Queryable, Identifiable, Insertable)] #[table_name = "workspace_table"] pub struct WorkspaceTable { - pub id: String, - pub name: String, - pub desc: String, - pub modified_time: i64, - pub create_time: i64, - pub user_id: String, - pub version: i64, + pub id: String, + pub name: String, + pub desc: String, + pub modified_time: i64, + pub create_time: i64, + pub user_id: String, + pub version: i64, } impl WorkspaceTable { - #[allow(dead_code)] - pub fn new(workspace_rev: WorkspaceRevision, user_id: &str) -> Self { - WorkspaceTable { - id: workspace_rev.id, - name: workspace_rev.name, - desc: workspace_rev.desc, - modified_time: workspace_rev.modified_time, - create_time: workspace_rev.create_time, - user_id: user_id.to_owned(), - version: 0, - } + #[allow(dead_code)] + pub fn new(workspace_rev: WorkspaceRevision, user_id: &str) -> Self { + WorkspaceTable { + id: workspace_rev.id, + name: workspace_rev.name, + desc: workspace_rev.desc, + modified_time: workspace_rev.modified_time, + create_time: workspace_rev.create_time, + user_id: user_id.to_owned(), + version: 0, } + } } impl std::convert::From for WorkspaceRevision { - fn from(table: WorkspaceTable) -> Self { - WorkspaceRevision { - id: table.id, - name: table.name, - desc: table.desc, - apps: vec![], - modified_time: table.modified_time, - create_time: table.create_time, - } + fn from(table: WorkspaceTable) -> Self { + WorkspaceRevision { + id: table.id, + name: table.name, + desc: table.desc, + apps: vec![], + modified_time: table.modified_time, + create_time: table.create_time, } + } } #[derive(AsChangeset, Identifiable, Clone, Default, Debug)] #[table_name = "workspace_table"] pub struct WorkspaceChangeset { - pub id: String, - pub name: Option, - pub desc: Option, + pub id: String, + pub name: Option, + pub desc: Option, } impl WorkspaceChangeset { - pub fn new(params: UpdateWorkspaceParams) -> Self { - WorkspaceChangeset { - id: params.id, - name: params.name, - desc: params.desc, - } + pub fn new(params: UpdateWorkspaceParams) -> Self { + WorkspaceChangeset { + id: params.id, + name: params.name, + desc: params.desc, } + } - pub(crate) fn from_table(table: WorkspaceTable) -> Self { - WorkspaceChangeset { - id: table.id, - name: Some(table.name), - desc: Some(table.desc), - } + pub(crate) fn from_table(table: WorkspaceTable) -> Self { + WorkspaceChangeset { + id: table.id, + name: Some(table.name), + desc: Some(table.desc), } + } } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs index 57f3b75ea4..61373c61e1 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs @@ -1,230 +1,245 @@ use crate::services::{ - folder_editor::FolderEditor, - persistence::{AppChangeset, FolderPersistenceTransaction, ViewChangeset, WorkspaceChangeset}, + folder_editor::FolderEditor, + persistence::{AppChangeset, FolderPersistenceTransaction, ViewChangeset, WorkspaceChangeset}, }; use flowy_error::{FlowyError, FlowyResult}; use folder_model::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; use std::sync::Arc; impl FolderPersistenceTransaction for FolderEditor { - fn create_workspace(&self, _user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> { - if let Some(change) = self.folder.write().create_workspace(workspace_rev)? { - self.apply_change(change)?; - } - Ok(()) + fn create_workspace(&self, _user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> { + if let Some(change) = self.folder.write().create_workspace(workspace_rev)? { + self.apply_change(change)?; } + Ok(()) + } - fn read_workspaces(&self, _user_id: &str, workspace_id: Option) -> FlowyResult> { - let workspaces = self.folder.read().read_workspaces(workspace_id)?; - Ok(workspaces) - } + fn read_workspaces( + &self, + _user_id: &str, + workspace_id: Option, + ) -> FlowyResult> { + let workspaces = self.folder.read().read_workspaces(workspace_id)?; + Ok(workspaces) + } - fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> { - if let Some(change) = self - .folder - .write() - .update_workspace(&changeset.id, changeset.name, changeset.desc)? - { - self.apply_change(change)?; - } - Ok(()) + fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> { + if let Some(change) = + self + .folder + .write() + .update_workspace(&changeset.id, changeset.name, changeset.desc)? + { + self.apply_change(change)?; } + Ok(()) + } - fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> { - if let Some(change) = self.folder.write().delete_workspace(workspace_id)? { - self.apply_change(change)?; - } - Ok(()) + fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> { + if let Some(change) = self.folder.write().delete_workspace(workspace_id)? { + self.apply_change(change)?; } + Ok(()) + } - fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> { - if let Some(change) = self.folder.write().create_app(app_rev)? { - self.apply_change(change)?; - } - Ok(()) + fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> { + if let Some(change) = self.folder.write().create_app(app_rev)? { + self.apply_change(change)?; } + Ok(()) + } - fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { - if let Some(change) = self - .folder - .write() - .update_app(&changeset.id, changeset.name, changeset.desc)? - { - self.apply_change(change)?; - } - Ok(()) + fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { + if let Some(change) = + self + .folder + .write() + .update_app(&changeset.id, changeset.name, changeset.desc)? + { + self.apply_change(change)?; } + Ok(()) + } - fn read_app(&self, app_id: &str) -> FlowyResult { - let app = self.folder.read().read_app(app_id)?; - Ok(app) - } + fn read_app(&self, app_id: &str) -> FlowyResult { + let app = self.folder.read().read_app(app_id)?; + Ok(app) + } - fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult> { - let workspaces = self.folder.read().read_workspaces(Some(workspace_id.to_owned()))?; - match workspaces.first() { - None => { - Err(FlowyError::record_not_found().context(format!("can't find workspace with id {}", workspace_id))) - } - Some(workspace) => Ok(workspace.apps.clone()), - } + fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult> { + let workspaces = self + .folder + .read() + .read_workspaces(Some(workspace_id.to_owned()))?; + match workspaces.first() { + None => Err( + FlowyError::record_not_found() + .context(format!("can't find workspace with id {}", workspace_id)), + ), + Some(workspace) => Ok(workspace.apps.clone()), } + } - fn delete_app(&self, app_id: &str) -> FlowyResult { - let app = self.folder.read().read_app(app_id)?; - if let Some(change) = self.folder.write().delete_app(app_id)? { - self.apply_change(change)?; - } - Ok(app) + fn delete_app(&self, app_id: &str) -> FlowyResult { + let app = self.folder.read().read_app(app_id)?; + if let Some(change) = self.folder.write().delete_app(app_id)? { + self.apply_change(change)?; } + Ok(app) + } - fn move_app(&self, app_id: &str, from: usize, to: usize) -> FlowyResult<()> { - if let Some(change) = self.folder.write().move_app(app_id, from, to)? { - self.apply_change(change)?; - } - Ok(()) + fn move_app(&self, app_id: &str, from: usize, to: usize) -> FlowyResult<()> { + if let Some(change) = self.folder.write().move_app(app_id, from, to)? { + self.apply_change(change)?; } + Ok(()) + } - fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> { - if let Some(change) = self.folder.write().create_view(view_rev)? { - self.apply_change(change)?; - } - Ok(()) + fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> { + if let Some(change) = self.folder.write().create_view(view_rev)? { + self.apply_change(change)?; } + Ok(()) + } - fn read_view(&self, view_id: &str) -> FlowyResult { - let view = self.folder.read().read_view(view_id)?; - Ok(view) - } + fn read_view(&self, view_id: &str) -> FlowyResult { + let view = self.folder.read().read_view(view_id)?; + Ok(view) + } - fn read_views(&self, belong_to_id: &str) -> FlowyResult> { - let views = self.folder.read().read_views(belong_to_id)?; - Ok(views) - } + fn read_views(&self, belong_to_id: &str) -> FlowyResult> { + let views = self.folder.read().read_views(belong_to_id)?; + Ok(views) + } - fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { - if let Some(change) = - self.folder - .write() - .update_view(&changeset.id, changeset.name, changeset.desc, changeset.modified_time)? - { - self.apply_change(change)?; - } - Ok(()) + fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { + if let Some(change) = self.folder.write().update_view( + &changeset.id, + changeset.name, + changeset.desc, + changeset.modified_time, + )? { + self.apply_change(change)?; } + Ok(()) + } - fn delete_view(&self, view_id: &str) -> FlowyResult { - let view = self.folder.read().read_view(view_id)?; - if let Some(change) = self.folder.write().delete_view(&view.app_id, view_id)? { - self.apply_change(change)?; - } - Ok(view) + fn delete_view(&self, view_id: &str) -> FlowyResult { + let view = self.folder.read().read_view(view_id)?; + if let Some(change) = self.folder.write().delete_view(&view.app_id, view_id)? { + self.apply_change(change)?; } + Ok(view) + } - fn move_view(&self, view_id: &str, from: usize, to: usize) -> FlowyResult<()> { - if let Some(change) = self.folder.write().move_view(view_id, from, to)? { - self.apply_change(change)?; - } - Ok(()) + fn move_view(&self, view_id: &str, from: usize, to: usize) -> FlowyResult<()> { + if let Some(change) = self.folder.write().move_view(view_id, from, to)? { + self.apply_change(change)?; } + Ok(()) + } - fn create_trash(&self, trashes: Vec) -> FlowyResult<()> { - if let Some(change) = self.folder.write().create_trash(trashes)? { - self.apply_change(change)?; - } - Ok(()) + fn create_trash(&self, trashes: Vec) -> FlowyResult<()> { + if let Some(change) = self.folder.write().create_trash(trashes)? { + self.apply_change(change)?; } + Ok(()) + } - fn read_trash(&self, trash_id: Option) -> FlowyResult> { - let trash = self.folder.read().read_trash(trash_id)?; - Ok(trash) - } + fn read_trash(&self, trash_id: Option) -> FlowyResult> { + let trash = self.folder.read().read_trash(trash_id)?; + Ok(trash) + } - fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { - if let Some(change) = self.folder.write().delete_trash(trash_ids)? { - self.apply_change(change)?; - } - Ok(()) + fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { + if let Some(change) = self.folder.write().delete_trash(trash_ids)? { + self.apply_change(change)?; } + Ok(()) + } } impl FolderPersistenceTransaction for Arc where - T: FolderPersistenceTransaction + ?Sized, + T: FolderPersistenceTransaction + ?Sized, { - fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> { - (**self).create_workspace(user_id, workspace_rev) - } + fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> { + (**self).create_workspace(user_id, workspace_rev) + } - fn read_workspaces(&self, user_id: &str, workspace_id: Option) -> FlowyResult> { - (**self).read_workspaces(user_id, workspace_id) - } + fn read_workspaces( + &self, + user_id: &str, + workspace_id: Option, + ) -> FlowyResult> { + (**self).read_workspaces(user_id, workspace_id) + } - fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> { - (**self).update_workspace(changeset) - } + fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> { + (**self).update_workspace(changeset) + } - fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> { - (**self).delete_workspace(workspace_id) - } + fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> { + (**self).delete_workspace(workspace_id) + } - fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> { - (**self).create_app(app_rev) - } + fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> { + (**self).create_app(app_rev) + } - fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { - (**self).update_app(changeset) - } + fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { + (**self).update_app(changeset) + } - fn read_app(&self, app_id: &str) -> FlowyResult { - (**self).read_app(app_id) - } + fn read_app(&self, app_id: &str) -> FlowyResult { + (**self).read_app(app_id) + } - fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult> { - (**self).read_workspace_apps(workspace_id) - } + fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult> { + (**self).read_workspace_apps(workspace_id) + } - fn delete_app(&self, app_id: &str) -> FlowyResult { - (**self).delete_app(app_id) - } + fn delete_app(&self, app_id: &str) -> FlowyResult { + (**self).delete_app(app_id) + } - fn move_app(&self, app_id: &str, from: usize, to: usize) -> FlowyResult<()> { - (**self).move_app(app_id, from, to) - } + fn move_app(&self, app_id: &str, from: usize, to: usize) -> FlowyResult<()> { + (**self).move_app(app_id, from, to) + } - fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> { - (**self).create_view(view_rev) - } + fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> { + (**self).create_view(view_rev) + } - fn read_view(&self, view_id: &str) -> FlowyResult { - (**self).read_view(view_id) - } + fn read_view(&self, view_id: &str) -> FlowyResult { + (**self).read_view(view_id) + } - fn read_views(&self, belong_to_id: &str) -> FlowyResult> { - (**self).read_views(belong_to_id) - } + fn read_views(&self, belong_to_id: &str) -> FlowyResult> { + (**self).read_views(belong_to_id) + } - fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { - (**self).update_view(changeset) - } + fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { + (**self).update_view(changeset) + } - fn delete_view(&self, view_id: &str) -> FlowyResult { - (**self).delete_view(view_id) - } + fn delete_view(&self, view_id: &str) -> FlowyResult { + (**self).delete_view(view_id) + } - fn move_view(&self, view_id: &str, from: usize, to: usize) -> FlowyResult<()> { - (**self).move_view(view_id, from, to) - } + fn move_view(&self, view_id: &str, from: usize, to: usize) -> FlowyResult<()> { + (**self).move_view(view_id, from, to) + } - fn create_trash(&self, trashes: Vec) -> FlowyResult<()> { - (**self).create_trash(trashes) - } + fn create_trash(&self, trashes: Vec) -> FlowyResult<()> { + (**self).create_trash(trashes) + } - fn read_trash(&self, trash_id: Option) -> FlowyResult> { - (**self).read_trash(trash_id) - } + fn read_trash(&self, trash_id: Option) -> FlowyResult> { + (**self).read_trash(trash_id) + } - fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { - (**self).delete_trash(trash_ids) - } + fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { + (**self).delete_trash(trash_ids) + } } diff --git a/frontend/rust-lib/flowy-folder/src/services/trash/controller.rs b/frontend/rust-lib/flowy-folder/src/services/trash/controller.rs index 923ff6de6c..80294678c4 100644 --- a/frontend/rust-lib/flowy-folder/src/services/trash/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/trash/controller.rs @@ -1,9 +1,9 @@ use crate::{ - entities::trash::{RepeatedTrashIdPB, RepeatedTrashPB, TrashIdPB, TrashPB, TrashType}, - errors::{FlowyError, FlowyResult}, - event_map::{FolderCouldServiceV1, WorkspaceUser}, - notification::{send_anonymous_notification, FolderNotification}, - services::persistence::{FolderPersistence, FolderPersistenceTransaction}, + entities::trash::{RepeatedTrashIdPB, RepeatedTrashPB, TrashIdPB, TrashPB, TrashType}, + errors::{FlowyError, FlowyResult}, + event_map::{FolderCouldServiceV1, WorkspaceUser}, + notification::{send_anonymous_notification, FolderNotification}, + services::persistence::{FolderPersistence, FolderPersistenceTransaction}, }; use folder_model::TrashRevision; @@ -11,250 +11,275 @@ use std::{fmt::Formatter, sync::Arc}; use tokio::sync::{broadcast, mpsc}; pub struct TrashController { - persistence: Arc, - notify: broadcast::Sender, - #[allow(dead_code)] - cloud_service: Arc, - #[allow(dead_code)] - user: Arc, + persistence: Arc, + notify: broadcast::Sender, + #[allow(dead_code)] + cloud_service: Arc, + #[allow(dead_code)] + user: Arc, } impl TrashController { - pub fn new( - persistence: Arc, - cloud_service: Arc, - user: Arc, - ) -> Self { - let (tx, _) = broadcast::channel(10); - Self { - persistence, - notify: tx, - cloud_service, - user, + pub fn new( + persistence: Arc, + cloud_service: Arc, + user: Arc, + ) -> Self { + let (tx, _) = broadcast::channel(10); + Self { + persistence, + notify: tx, + cloud_service, + user, + } + } + + #[tracing::instrument(level = "debug", skip(self), fields(putback) err)] + pub async fn putback(&self, trash_id: &str) -> FlowyResult<()> { + let (tx, mut rx) = mpsc::channel::>(1); + let trash = self + .persistence + .begin_transaction(|transaction| { + let mut repeated_trash = transaction.read_trash(Some(trash_id.to_owned()))?; + transaction.delete_trash(Some(vec![trash_id.to_owned()]))?; + notify_trash_changed(transaction.read_trash(None)?); + + if repeated_trash.is_empty() { + return Err(FlowyError::internal().context("Try to put back trash is not exists")); } + Ok(repeated_trash.pop().unwrap()) + }) + .await?; + + let identifier = TrashIdPB { + id: trash.id, + ty: trash.ty.into(), + }; + + tracing::Span::current().record("putback", format!("{:?}", &identifier).as_str()); + let _ = self + .notify + .send(TrashEvent::Putback(vec![identifier].into(), tx)); + rx.recv().await.unwrap()?; + Ok(()) + } + + #[tracing::instrument(level = "debug", skip(self) err)] + pub async fn restore_all_trash(&self) -> FlowyResult<()> { + let trash_identifier: RepeatedTrashIdPB = self + .persistence + .begin_transaction(|transaction| { + let trash = transaction.read_trash(None); + let _ = transaction.delete_trash(None); + trash + }) + .await? + .into(); + + let (tx, mut rx) = mpsc::channel::>(1); + let _ = self.notify.send(TrashEvent::Putback(trash_identifier, tx)); + let _ = rx.recv().await; + + notify_trash_changed(RepeatedTrashPB { items: vec![] }); + Ok(()) + } + + #[tracing::instrument(level = "debug", skip(self), err)] + pub async fn delete_all_trash(&self) -> FlowyResult<()> { + let all_trash_identifiers: RepeatedTrashIdPB = self + .persistence + .begin_transaction(|transaction| transaction.read_trash(None)) + .await? + .into(); + + self.delete_with_identifiers(all_trash_identifiers).await?; + + notify_trash_changed(RepeatedTrashPB { items: vec![] }); + Ok(()) + } + + #[tracing::instrument(level = "debug", skip(self), err)] + pub async fn delete(&self, trash_identifiers: RepeatedTrashIdPB) -> FlowyResult<()> { + self + .delete_with_identifiers(trash_identifiers.clone()) + .await?; + let trash_revs = self + .persistence + .begin_transaction(|transaction| transaction.read_trash(None)) + .await?; + + notify_trash_changed(trash_revs); + + Ok(()) + } + + #[tracing::instrument(level = "debug", skip(self), fields(delete_trash_ids), err)] + pub async fn delete_with_identifiers( + &self, + trash_identifiers: RepeatedTrashIdPB, + ) -> FlowyResult<()> { + let (tx, mut rx) = mpsc::channel::>(1); + tracing::Span::current().record( + "delete_trash_ids", + format!("{}", trash_identifiers).as_str(), + ); + let _ = self + .notify + .send(TrashEvent::Delete(trash_identifiers.clone(), tx)); + + match rx.recv().await { + None => {}, + Some(result) => match result { + Ok(_) => {}, + Err(e) => log::error!("{}", e), + }, } + self + .persistence + .begin_transaction(|transaction| { + let ids = trash_identifiers + .items + .into_iter() + .map(|item| item.id) + .collect::>(); + transaction.delete_trash(Some(ids)) + }) + .await?; - #[tracing::instrument(level = "debug", skip(self), fields(putback) err)] - pub async fn putback(&self, trash_id: &str) -> FlowyResult<()> { - let (tx, mut rx) = mpsc::channel::>(1); - let trash = self - .persistence - .begin_transaction(|transaction| { - let mut repeated_trash = transaction.read_trash(Some(trash_id.to_owned()))?; - transaction.delete_trash(Some(vec![trash_id.to_owned()]))?; - notify_trash_changed(transaction.read_trash(None)?); + Ok(()) + } - if repeated_trash.is_empty() { - return Err(FlowyError::internal().context("Try to put back trash is not exists")); - } - Ok(repeated_trash.pop().unwrap()) - }) - .await?; + // [[ transaction ]] + // https://www.tutlane.com/tutorial/sqlite/sqlite-transactions-begin-commit-rollback + // We can use these commands only when we are performing INSERT, UPDATE, and + // DELETE operations. It’s not possible for us to use these commands to + // CREATE and DROP tables operations because those are auto-commit in the + // database. + #[tracing::instrument( + name = "add_trash", + level = "debug", + skip(self, trash), + fields(trash_ids), + err + )] + pub async fn add>(&self, trash: Vec) -> Result<(), FlowyError> { + let (tx, mut rx) = mpsc::channel::>(1); + let trash_revs: Vec = trash.into_iter().map(|t| t.into()).collect(); + let identifiers = trash_revs + .iter() + .map(|t| t.into()) + .collect::>(); - let identifier = TrashIdPB { - id: trash.id, - ty: trash.ty.into(), - }; + tracing::Span::current().record( + "trash_ids", + format!( + "{:?}", + identifiers + .iter() + .map(|identifier| format!("{:?}:{}", identifier.ty, identifier.id)) + .collect::>() + ) + .as_str(), + ); - tracing::Span::current().record("putback", format!("{:?}", &identifier).as_str()); - let _ = self.notify.send(TrashEvent::Putback(vec![identifier].into(), tx)); - rx.recv().await.unwrap()?; + self + .persistence + .begin_transaction(|transaction| { + transaction.create_trash(trash_revs.clone())?; + notify_trash_changed(transaction.read_trash(None)?); Ok(()) - } + }) + .await?; + let _ = self + .notify + .send(TrashEvent::NewTrash(identifiers.into(), tx)); + rx.recv().await.unwrap()?; - #[tracing::instrument(level = "debug", skip(self) err)] - pub async fn restore_all_trash(&self) -> FlowyResult<()> { - let trash_identifier: RepeatedTrashIdPB = self - .persistence - .begin_transaction(|transaction| { - let trash = transaction.read_trash(None); - let _ = transaction.delete_trash(None); - trash - }) - .await? - .into(); + Ok(()) + } - let (tx, mut rx) = mpsc::channel::>(1); - let _ = self.notify.send(TrashEvent::Putback(trash_identifier, tx)); - let _ = rx.recv().await; + pub fn subscribe(&self) -> broadcast::Receiver { + self.notify.subscribe() + } - notify_trash_changed(RepeatedTrashPB { items: vec![] }); - Ok(()) - } + pub async fn read_trash(&self) -> Result { + let items: Vec = self + .persistence + .begin_transaction(|transaction| transaction.read_trash(None)) + .await? + .into_iter() + .map(|trash_rev| trash_rev.into()) + .collect(); - #[tracing::instrument(level = "debug", skip(self), err)] - pub async fn delete_all_trash(&self) -> FlowyResult<()> { - let all_trash_identifiers: RepeatedTrashIdPB = self - .persistence - .begin_transaction(|transaction| transaction.read_trash(None)) - .await? - .into(); + Ok(RepeatedTrashPB { items }) + } - self.delete_with_identifiers(all_trash_identifiers).await?; - - notify_trash_changed(RepeatedTrashPB { items: vec![] }); - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self), err)] - pub async fn delete(&self, trash_identifiers: RepeatedTrashIdPB) -> FlowyResult<()> { - self.delete_with_identifiers(trash_identifiers.clone()).await?; - let trash_revs = self - .persistence - .begin_transaction(|transaction| transaction.read_trash(None)) - .await?; - - notify_trash_changed(trash_revs); - - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self), fields(delete_trash_ids), err)] - pub async fn delete_with_identifiers(&self, trash_identifiers: RepeatedTrashIdPB) -> FlowyResult<()> { - let (tx, mut rx) = mpsc::channel::>(1); - tracing::Span::current().record("delete_trash_ids", format!("{}", trash_identifiers).as_str()); - let _ = self.notify.send(TrashEvent::Delete(trash_identifiers.clone(), tx)); - - match rx.recv().await { - None => {} - Some(result) => match result { - Ok(_) => {} - Err(e) => log::error!("{}", e), - }, - } - self.persistence - .begin_transaction(|transaction| { - let ids = trash_identifiers - .items - .into_iter() - .map(|item| item.id) - .collect::>(); - transaction.delete_trash(Some(ids)) - }) - .await?; - - Ok(()) - } - - // [[ transaction ]] - // https://www.tutlane.com/tutorial/sqlite/sqlite-transactions-begin-commit-rollback - // We can use these commands only when we are performing INSERT, UPDATE, and - // DELETE operations. It’s not possible for us to use these commands to - // CREATE and DROP tables operations because those are auto-commit in the - // database. - #[tracing::instrument(name = "add_trash", level = "debug", skip(self, trash), fields(trash_ids), err)] - pub async fn add>(&self, trash: Vec) -> Result<(), FlowyError> { - let (tx, mut rx) = mpsc::channel::>(1); - let trash_revs: Vec = trash.into_iter().map(|t| t.into()).collect(); - let identifiers = trash_revs.iter().map(|t| t.into()).collect::>(); - - tracing::Span::current().record( - "trash_ids", - format!( - "{:?}", - identifiers - .iter() - .map(|identifier| format!("{:?}:{}", identifier.ty, identifier.id)) - .collect::>() - ) - .as_str(), - ); - - self.persistence - .begin_transaction(|transaction| { - transaction.create_trash(trash_revs.clone())?; - notify_trash_changed(transaction.read_trash(None)?); - Ok(()) - }) - .await?; - let _ = self.notify.send(TrashEvent::NewTrash(identifiers.into(), tx)); - rx.recv().await.unwrap()?; - - Ok(()) - } - - pub fn subscribe(&self) -> broadcast::Receiver { - self.notify.subscribe() - } - - pub async fn read_trash(&self) -> Result { - let items: Vec = self - .persistence - .begin_transaction(|transaction| transaction.read_trash(None)) - .await? - .into_iter() - .map(|trash_rev| trash_rev.into()) - .collect(); - - Ok(RepeatedTrashPB { items }) - } - - pub fn read_trash_ids<'a>( - &self, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), - ) -> Result, FlowyError> { - let ids = transaction - .read_trash(None)? - .into_iter() - .map(|item| item.id) - .collect::>(); - Ok(ids) - } + pub fn read_trash_ids<'a>( + &self, + transaction: &'a (dyn FolderPersistenceTransaction + 'a), + ) -> Result, FlowyError> { + let ids = transaction + .read_trash(None)? + .into_iter() + .map(|item| item.id) + .collect::>(); + Ok(ids) + } } #[tracing::instrument(level = "debug", skip(repeated_trash), fields(n_trash))] fn notify_trash_changed>(repeated_trash: T) { - let repeated_trash = repeated_trash.into(); - tracing::Span::current().record("n_trash", repeated_trash.len()); - send_anonymous_notification(FolderNotification::DidUpdateTrash) - .payload(repeated_trash) - .send(); + let repeated_trash = repeated_trash.into(); + tracing::Span::current().record("n_trash", repeated_trash.len()); + send_anonymous_notification(FolderNotification::DidUpdateTrash) + .payload(repeated_trash) + .send(); } #[derive(Clone)] pub enum TrashEvent { - NewTrash(RepeatedTrashIdPB, mpsc::Sender>), - Putback(RepeatedTrashIdPB, mpsc::Sender>), - Delete(RepeatedTrashIdPB, mpsc::Sender>), + NewTrash(RepeatedTrashIdPB, mpsc::Sender>), + Putback(RepeatedTrashIdPB, mpsc::Sender>), + Delete(RepeatedTrashIdPB, mpsc::Sender>), } impl std::fmt::Debug for TrashEvent { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - TrashEvent::NewTrash(identifiers, _) => f.write_str(&format!("{:?}", identifiers)), - TrashEvent::Putback(identifiers, _) => f.write_str(&format!("{:?}", identifiers)), - TrashEvent::Delete(identifiers, _) => f.write_str(&format!("{:?}", identifiers)), - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + TrashEvent::NewTrash(identifiers, _) => f.write_str(&format!("{:?}", identifiers)), + TrashEvent::Putback(identifiers, _) => f.write_str(&format!("{:?}", identifiers)), + TrashEvent::Delete(identifiers, _) => f.write_str(&format!("{:?}", identifiers)), } + } } impl TrashEvent { - pub fn select(self, s: TrashType) -> Option { - match self { - TrashEvent::Putback(mut identifiers, sender) => { - identifiers.items.retain(|item| item.ty == s); - if identifiers.items.is_empty() { - None - } else { - Some(TrashEvent::Putback(identifiers, sender)) - } - } - TrashEvent::Delete(mut identifiers, sender) => { - identifiers.items.retain(|item| item.ty == s); - if identifiers.items.is_empty() { - None - } else { - Some(TrashEvent::Delete(identifiers, sender)) - } - } - TrashEvent::NewTrash(mut identifiers, sender) => { - identifiers.items.retain(|item| item.ty == s); - if identifiers.items.is_empty() { - None - } else { - Some(TrashEvent::NewTrash(identifiers, sender)) - } - } + pub fn select(self, s: TrashType) -> Option { + match self { + TrashEvent::Putback(mut identifiers, sender) => { + identifiers.items.retain(|item| item.ty == s); + if identifiers.items.is_empty() { + None + } else { + Some(TrashEvent::Putback(identifiers, sender)) } + }, + TrashEvent::Delete(mut identifiers, sender) => { + identifiers.items.retain(|item| item.ty == s); + if identifiers.items.is_empty() { + None + } else { + Some(TrashEvent::Delete(identifiers, sender)) + } + }, + TrashEvent::NewTrash(mut identifiers, sender) => { + identifiers.items.retain(|item| item.ty == s); + if identifiers.items.is_empty() { + None + } else { + Some(TrashEvent::NewTrash(identifiers, sender)) + } + }, } + } } diff --git a/frontend/rust-lib/flowy-folder/src/services/trash/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/trash/event_handler.rs index 987ceada51..28777da438 100644 --- a/frontend/rust-lib/flowy-folder/src/services/trash/event_handler.rs +++ b/frontend/rust-lib/flowy-folder/src/services/trash/event_handler.rs @@ -1,49 +1,49 @@ use crate::{ - entities::trash::{RepeatedTrashIdPB, RepeatedTrashPB, TrashIdPB}, - errors::FlowyError, - services::TrashController, + entities::trash::{RepeatedTrashIdPB, RepeatedTrashPB, TrashIdPB}, + errors::FlowyError, + services::TrashController, }; use lib_dispatch::prelude::{data_result, AFPluginData, AFPluginState, DataResult}; use std::sync::Arc; #[tracing::instrument(level = "debug", skip(controller), err)] pub(crate) async fn read_trash_handler( - controller: AFPluginState>, + controller: AFPluginState>, ) -> DataResult { - let repeated_trash = controller.read_trash().await?; - data_result(repeated_trash) + let repeated_trash = controller.read_trash().await?; + data_result(repeated_trash) } #[tracing::instrument(level = "debug", skip(identifier, controller), err)] pub(crate) async fn putback_trash_handler( - identifier: AFPluginData, - controller: AFPluginState>, + identifier: AFPluginData, + controller: AFPluginState>, ) -> Result<(), FlowyError> { - controller.putback(&identifier.id).await?; - Ok(()) + controller.putback(&identifier.id).await?; + Ok(()) } #[tracing::instrument(level = "debug", skip(identifiers, controller), err)] pub(crate) async fn delete_trash_handler( - identifiers: AFPluginData, - controller: AFPluginState>, + identifiers: AFPluginData, + controller: AFPluginState>, ) -> Result<(), FlowyError> { - controller.delete(identifiers.into_inner()).await?; - Ok(()) + controller.delete(identifiers.into_inner()).await?; + Ok(()) } #[tracing::instrument(level = "debug", skip(controller), err)] pub(crate) async fn restore_all_trash_handler( - controller: AFPluginState>, + controller: AFPluginState>, ) -> Result<(), FlowyError> { - controller.restore_all_trash().await?; - Ok(()) + controller.restore_all_trash().await?; + Ok(()) } #[tracing::instrument(level = "debug", skip(controller), err)] pub(crate) async fn delete_all_trash_handler( - controller: AFPluginState>, + controller: AFPluginState>, ) -> Result<(), FlowyError> { - controller.delete_all_trash().await?; - Ok(()) + controller.delete_all_trash().await?; + Ok(()) } diff --git a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs b/frontend/rust-lib/flowy-folder/src/services/view/controller.rs index f926025e7a..ab3d244180 100644 --- a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/view/controller.rs @@ -2,17 +2,17 @@ pub use crate::entities::view::ViewDataFormatPB; use crate::entities::{AppPB, DeletedViewPB, ViewLayoutTypePB}; use crate::manager::{ViewDataProcessor, ViewDataProcessorMap}; use crate::{ - entities::{ - trash::{RepeatedTrashIdPB, TrashType}, - view::{CreateViewParams, UpdateViewParams, ViewPB}, - }, - errors::{FlowyError, FlowyResult}, - event_map::{FolderCouldServiceV1, WorkspaceUser}, - notification::{send_notification, FolderNotification}, - services::{ - persistence::{FolderPersistence, FolderPersistenceTransaction, ViewChangeset}, - TrashController, TrashEvent, - }, + entities::{ + trash::{RepeatedTrashIdPB, TrashType}, + view::{CreateViewParams, UpdateViewParams, ViewPB}, + }, + errors::{FlowyError, FlowyResult}, + event_map::{FolderCouldServiceV1, WorkspaceUser}, + notification::{send_notification, FolderNotification}, + services::{ + persistence::{FolderPersistence, FolderPersistenceTransaction, ViewChangeset}, + TrashController, TrashEvent, + }, }; use bytes::Bytes; use flowy_sqlite::kv::KV; @@ -23,475 +23,511 @@ use std::{collections::HashSet, sync::Arc}; const LATEST_VIEW_ID: &str = "latest_view_id"; pub(crate) struct ViewController { + user: Arc, + cloud_service: Arc, + persistence: Arc, + trash_controller: Arc, + data_processors: ViewDataProcessorMap, +} + +impl ViewController { + pub(crate) fn new( user: Arc, - cloud_service: Arc, persistence: Arc, + cloud_service: Arc, trash_controller: Arc, data_processors: ViewDataProcessorMap, -} + ) -> Self { + Self { + user, + cloud_service, + persistence, + trash_controller, + data_processors, + } + } -impl ViewController { - pub(crate) fn new( - user: Arc, - persistence: Arc, - cloud_service: Arc, - trash_controller: Arc, - data_processors: ViewDataProcessorMap, - ) -> Self { - Self { - user, - cloud_service, - persistence, - trash_controller, - data_processors, + pub(crate) fn initialize(&self) -> Result<(), FlowyError> { + self.listen_trash_can_event(); + Ok(()) + } + + #[tracing::instrument(level = "trace", skip(self, params), fields(name = %params.name), err)] + pub(crate) async fn create_view_from_params( + &self, + mut params: CreateViewParams, + ) -> Result { + let processor = self.get_data_processor(params.data_format.clone())?; + let user_id = self.user.user_id()?; + if params.initial_data.is_empty() { + tracing::trace!("Create view with build-in data"); + let view_data = processor + .create_default_view( + &user_id, + ¶ms.view_id, + params.layout.clone(), + params.data_format.clone(), + ) + .await?; + params.initial_data = view_data.to_vec(); + } else { + tracing::trace!("Create view with view data"); + let view_data = processor + .create_view_with_data( + &user_id, + ¶ms.view_id, + params.initial_data.clone(), + params.layout.clone(), + ) + .await?; + self + .create_view( + ¶ms.view_id, + params.data_format.clone(), + params.layout.clone(), + view_data, + ) + .await?; + }; + + let view_rev = self.create_view_on_server(params).await?; + self.create_view_on_local(view_rev.clone()).await?; + Ok(view_rev) + } + + #[tracing::instrument(level = "debug", skip(self, view_id, view_data), err)] + pub(crate) async fn create_view( + &self, + view_id: &str, + data_type: ViewDataFormatPB, + layout_type: ViewLayoutTypePB, + view_data: Bytes, + ) -> Result<(), FlowyError> { + if view_data.is_empty() { + return Err(FlowyError::internal().context("The content of the view should not be empty")); + } + let user_id = self.user.user_id()?; + let processor = self.get_data_processor(data_type)?; + processor + .create_view(&user_id, view_id, layout_type, view_data) + .await?; + Ok(()) + } + + pub(crate) async fn create_view_on_local( + &self, + view_rev: ViewRevision, + ) -> Result<(), FlowyError> { + let trash_controller = self.trash_controller.clone(); + self + .persistence + .begin_transaction(|transaction| { + let belong_to_id = view_rev.app_id.clone(); + transaction.create_view(view_rev)?; + notify_views_changed(&belong_to_id, trash_controller, &transaction)?; + Ok(()) + }) + .await + } + + #[tracing::instrument(level = "debug", skip(self, view_id), err)] + pub(crate) async fn read_view(&self, view_id: &str) -> Result { + let view_rev = self + .persistence + .begin_transaction(|transaction| { + let view = transaction.read_view(view_id)?; + let trash_ids = self.trash_controller.read_trash_ids(&transaction)?; + if trash_ids.contains(&view.id) { + return Err(FlowyError::record_not_found()); } - } + Ok(view) + }) + .await?; + Ok(view_rev) + } - pub(crate) fn initialize(&self) -> Result<(), FlowyError> { - self.listen_trash_can_event(); - Ok(()) - } - - #[tracing::instrument(level = "trace", skip(self, params), fields(name = %params.name), err)] - pub(crate) async fn create_view_from_params( - &self, - mut params: CreateViewParams, - ) -> Result { - let processor = self.get_data_processor(params.data_format.clone())?; - let user_id = self.user.user_id()?; - if params.initial_data.is_empty() { - tracing::trace!("Create view with build-in data"); - let view_data = processor - .create_default_view( - &user_id, - ¶ms.view_id, - params.layout.clone(), - params.data_format.clone(), - ) - .await?; - params.initial_data = view_data.to_vec(); - } else { - tracing::trace!("Create view with view data"); - let view_data = processor - .create_view_with_data( - &user_id, - ¶ms.view_id, - params.initial_data.clone(), - params.layout.clone(), - ) - .await?; - self.create_view( - ¶ms.view_id, - params.data_format.clone(), - params.layout.clone(), - view_data, - ) - .await?; - }; - - let view_rev = self.create_view_on_server(params).await?; - self.create_view_on_local(view_rev.clone()).await?; - Ok(view_rev) - } - - #[tracing::instrument(level = "debug", skip(self, view_id, view_data), err)] - pub(crate) async fn create_view( - &self, - view_id: &str, - data_type: ViewDataFormatPB, - layout_type: ViewLayoutTypePB, - view_data: Bytes, - ) -> Result<(), FlowyError> { - if view_data.is_empty() { - return Err(FlowyError::internal().context("The content of the view should not be empty")); + pub(crate) async fn read_local_views( + &self, + ids: Vec, + ) -> Result, FlowyError> { + self + .persistence + .begin_transaction(|transaction| { + let mut views = vec![]; + for view_id in ids { + views.push(transaction.read_view(&view_id)?); } - let user_id = self.user.user_id()?; - let processor = self.get_data_processor(data_type)?; - processor.create_view(&user_id, view_id, layout_type, view_data).await?; - Ok(()) - } + Ok(views) + }) + .await + } - pub(crate) async fn create_view_on_local(&self, view_rev: ViewRevision) -> Result<(), FlowyError> { - let trash_controller = self.trash_controller.clone(); - self.persistence - .begin_transaction(|transaction| { - let belong_to_id = view_rev.app_id.clone(); - transaction.create_view(view_rev)?; - notify_views_changed(&belong_to_id, trash_controller, &transaction)?; - Ok(()) - }) - .await - } + #[tracing::instrument(level = "trace", skip(self), err)] + pub(crate) fn set_latest_view(&self, view_id: &str) -> Result<(), FlowyError> { + KV::set_str(LATEST_VIEW_ID, view_id.to_owned()); + Ok(()) + } - #[tracing::instrument(level = "debug", skip(self, view_id), err)] - pub(crate) async fn read_view(&self, view_id: &str) -> Result { - let view_rev = self - .persistence - .begin_transaction(|transaction| { - let view = transaction.read_view(view_id)?; - let trash_ids = self.trash_controller.read_trash_ids(&transaction)?; - if trash_ids.contains(&view.id) { - return Err(FlowyError::record_not_found()); - } - Ok(view) - }) - .await?; - Ok(view_rev) - } + #[tracing::instrument(level = "trace", skip(self))] + pub(crate) fn clear_latest_view(&self) { + let _ = KV::remove(LATEST_VIEW_ID); + } - pub(crate) async fn read_local_views(&self, ids: Vec) -> Result, FlowyError> { - self.persistence - .begin_transaction(|transaction| { - let mut views = vec![]; - for view_id in ids { - views.push(transaction.read_view(&view_id)?); - } - Ok(views) - }) - .await - } + #[tracing::instrument(level = "debug", skip(self), err)] + pub(crate) async fn close_view(&self, view_id: &str) -> Result<(), FlowyError> { + let processor = self.get_data_processor_from_view_id(view_id).await?; + processor.close_view(view_id).await?; + Ok(()) + } - #[tracing::instrument(level = "trace", skip(self), err)] - pub(crate) fn set_latest_view(&self, view_id: &str) -> Result<(), FlowyError> { - KV::set_str(LATEST_VIEW_ID, view_id.to_owned()); - Ok(()) - } - - #[tracing::instrument(level = "trace", skip(self))] - pub(crate) fn clear_latest_view(&self) { + #[tracing::instrument(level = "debug", skip(self), err)] + pub(crate) async fn move_view_to_trash(&self, view_id: &str) -> Result<(), FlowyError> { + if let Some(latest_view_id) = KV::get_str(LATEST_VIEW_ID) { + if latest_view_id == view_id { let _ = KV::remove(LATEST_VIEW_ID); + } } - #[tracing::instrument(level = "debug", skip(self), err)] - pub(crate) async fn close_view(&self, view_id: &str) -> Result<(), FlowyError> { - let processor = self.get_data_processor_from_view_id(view_id).await?; - processor.close_view(view_id).await?; + let deleted_view = self + .persistence + .begin_transaction(|transaction| { + let view = transaction.read_view(view_id)?; + let views = + read_belonging_views_on_local(&view.app_id, self.trash_controller.clone(), &transaction)?; + + let index = views + .iter() + .position(|view| view.id == view_id) + .map(|index| index as i32); + Ok(DeletedViewPB { + view_id: view_id.to_owned(), + index, + }) + }) + .await?; + + send_notification(view_id, FolderNotification::DidMoveViewToTrash) + .payload(deleted_view) + .send(); + + let processor = self.get_data_processor_from_view_id(view_id).await?; + processor.close_view(view_id).await?; + Ok(()) + } + + #[tracing::instrument(level = "debug", skip(self), err)] + pub(crate) async fn move_view( + &self, + view_id: &str, + from: usize, + to: usize, + ) -> Result<(), FlowyError> { + self + .persistence + .begin_transaction(|transaction| { + transaction.move_view(view_id, from, to)?; + let view = transaction.read_view(view_id)?; + notify_views_changed(&view.app_id, self.trash_controller.clone(), &transaction)?; Ok(()) - } + }) + .await?; + Ok(()) + } - #[tracing::instrument(level = "debug", skip(self), err)] - pub(crate) async fn move_view_to_trash(&self, view_id: &str) -> Result<(), FlowyError> { - if let Some(latest_view_id) = KV::get_str(LATEST_VIEW_ID) { - if latest_view_id == view_id { - let _ = KV::remove(LATEST_VIEW_ID); - } - } + #[tracing::instrument(level = "debug", skip(self), err)] + pub(crate) async fn duplicate_view(&self, view: ViewPB) -> Result<(), FlowyError> { + let view_rev = self + .persistence + .begin_transaction(|transaction| transaction.read_view(&view.id)) + .await?; - let deleted_view = self - .persistence - .begin_transaction(|transaction| { - let view = transaction.read_view(view_id)?; - let views = read_belonging_views_on_local(&view.app_id, self.trash_controller.clone(), &transaction)?; + let processor = self.get_data_processor(view_rev.data_format.clone())?; + let view_data = processor.get_view_data(&view).await?; + let duplicate_params = CreateViewParams { + belong_to_id: view_rev.app_id.clone(), + name: format!("{} (copy)", &view_rev.name), + desc: view_rev.desc, + thumbnail: view_rev.thumbnail, + data_format: view_rev.data_format.into(), + layout: view_rev.layout.into(), + initial_data: view_data.to_vec(), + view_id: gen_view_id(), + }; - let index = views - .iter() - .position(|view| view.id == view_id) - .map(|index| index as i32); - Ok(DeletedViewPB { - view_id: view_id.to_owned(), - index, - }) - }) - .await?; + let _ = self.create_view_from_params(duplicate_params).await?; + Ok(()) + } - send_notification(view_id, FolderNotification::DidMoveViewToTrash) - .payload(deleted_view) - .send(); + // belong_to_id will be the app_id or view_id. + #[tracing::instrument(level = "trace", skip(self), err)] + pub(crate) async fn read_views_belong_to( + &self, + belong_to_id: &str, + ) -> Result, FlowyError> { + self + .persistence + .begin_transaction(|transaction| { + read_belonging_views_on_local(belong_to_id, self.trash_controller.clone(), &transaction) + }) + .await + } - let processor = self.get_data_processor_from_view_id(view_id).await?; - processor.close_view(view_id).await?; - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self), err)] - pub(crate) async fn move_view(&self, view_id: &str, from: usize, to: usize) -> Result<(), FlowyError> { - self.persistence - .begin_transaction(|transaction| { - transaction.move_view(view_id, from, to)?; - let view = transaction.read_view(view_id)?; - notify_views_changed(&view.app_id, self.trash_controller.clone(), &transaction)?; - Ok(()) - }) - .await?; - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self), err)] - pub(crate) async fn duplicate_view(&self, view: ViewPB) -> Result<(), FlowyError> { - let view_rev = self - .persistence - .begin_transaction(|transaction| transaction.read_view(&view.id)) - .await?; - - let processor = self.get_data_processor(view_rev.data_format.clone())?; - let view_data = processor.get_view_data(&view).await?; - let duplicate_params = CreateViewParams { - belong_to_id: view_rev.app_id.clone(), - name: format!("{} (copy)", &view_rev.name), - desc: view_rev.desc, - thumbnail: view_rev.thumbnail, - data_format: view_rev.data_format.into(), - layout: view_rev.layout.into(), - initial_data: view_data.to_vec(), - view_id: gen_view_id(), - }; - - let _ = self.create_view_from_params(duplicate_params).await?; - Ok(()) - } - - // belong_to_id will be the app_id or view_id. - #[tracing::instrument(level = "trace", skip(self), err)] - pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result, FlowyError> { - self.persistence - .begin_transaction(|transaction| { - read_belonging_views_on_local(belong_to_id, self.trash_controller.clone(), &transaction) - }) - .await - } - - #[tracing::instrument(level = "debug", skip(self, params), err)] - pub(crate) async fn update_view(&self, params: UpdateViewParams) -> Result { - let changeset = ViewChangeset::new(params.clone()); - let view_id = changeset.id.clone(); - let view_rev = self - .persistence - .begin_transaction(|transaction| { - transaction.update_view(changeset)?; - let view_rev = transaction.read_view(&view_id)?; - let view: ViewPB = view_rev.clone().into(); - send_notification(&view_id, FolderNotification::DidUpdateView) - .payload(view) - .send(); - notify_views_changed(&view_rev.app_id, self.trash_controller.clone(), &transaction)?; - Ok(view_rev) - }) - .await?; - - let _ = self.update_view_on_server(params); + #[tracing::instrument(level = "debug", skip(self, params), err)] + pub(crate) async fn update_view( + &self, + params: UpdateViewParams, + ) -> Result { + let changeset = ViewChangeset::new(params.clone()); + let view_id = changeset.id.clone(); + let view_rev = self + .persistence + .begin_transaction(|transaction| { + transaction.update_view(changeset)?; + let view_rev = transaction.read_view(&view_id)?; + let view: ViewPB = view_rev.clone().into(); + send_notification(&view_id, FolderNotification::DidUpdateView) + .payload(view) + .send(); + notify_views_changed( + &view_rev.app_id, + self.trash_controller.clone(), + &transaction, + )?; Ok(view_rev) - } + }) + .await?; - pub(crate) async fn latest_visit_view(&self) -> FlowyResult> { - match KV::get_str(LATEST_VIEW_ID) { - None => Ok(None), - Some(view_id) => { - let view_rev = self - .persistence - .begin_transaction(|transaction| transaction.read_view(&view_id)) - .await?; - Ok(Some(view_rev)) - } - } + let _ = self.update_view_on_server(params); + Ok(view_rev) + } + + pub(crate) async fn latest_visit_view(&self) -> FlowyResult> { + match KV::get_str(LATEST_VIEW_ID) { + None => Ok(None), + Some(view_id) => { + let view_rev = self + .persistence + .begin_transaction(|transaction| transaction.read_view(&view_id)) + .await?; + Ok(Some(view_rev)) + }, } + } } impl ViewController { - #[tracing::instrument(level = "debug", skip(self, params), err)] - async fn create_view_on_server(&self, params: CreateViewParams) -> Result { - let token = self.user.token()?; - let view_rev = self.cloud_service.create_view(&token, params).await?; - Ok(view_rev) - } + #[tracing::instrument(level = "debug", skip(self, params), err)] + async fn create_view_on_server( + &self, + params: CreateViewParams, + ) -> Result { + let token = self.user.token()?; + let view_rev = self.cloud_service.create_view(&token, params).await?; + Ok(view_rev) + } - #[tracing::instrument(level = "debug", skip(self), err)] - fn update_view_on_server(&self, params: UpdateViewParams) -> Result<(), FlowyError> { - let token = self.user.token()?; - let server = self.cloud_service.clone(); - tokio::spawn(async move { - match server.update_view(&token, params).await { - Ok(_) => {} - Err(e) => { - // TODO: retry? - log::error!("Update view failed: {:?}", e); - } - } - }); - Ok(()) - } + #[tracing::instrument(level = "debug", skip(self), err)] + fn update_view_on_server(&self, params: UpdateViewParams) -> Result<(), FlowyError> { + let token = self.user.token()?; + let server = self.cloud_service.clone(); + tokio::spawn(async move { + match server.update_view(&token, params).await { + Ok(_) => {}, + Err(e) => { + // TODO: retry? + log::error!("Update view failed: {:?}", e); + }, + } + }); + Ok(()) + } - fn listen_trash_can_event(&self) { - let mut rx = self.trash_controller.subscribe(); - let persistence = self.persistence.clone(); - let data_processors = self.data_processors.clone(); - let trash_controller = self.trash_controller.clone(); - let _ = tokio::spawn(async move { - loop { - let mut stream = Box::pin(rx.recv().into_stream().filter_map(|result| async move { - match result { - Ok(event) => event.select(TrashType::TrashView), - Err(_e) => None, - } - })); + fn listen_trash_can_event(&self) { + let mut rx = self.trash_controller.subscribe(); + let persistence = self.persistence.clone(); + let data_processors = self.data_processors.clone(); + let trash_controller = self.trash_controller.clone(); + let _ = tokio::spawn(async move { + loop { + let mut stream = Box::pin(rx.recv().into_stream().filter_map(|result| async move { + match result { + Ok(event) => event.select(TrashType::TrashView), + Err(_e) => None, + } + })); - if let Some(event) = stream.next().await { - handle_trash_event( - persistence.clone(), - data_processors.clone(), - trash_controller.clone(), - event, - ) - .await - } - } - }); - } - - async fn get_data_processor_from_view_id( - &self, - view_id: &str, - ) -> FlowyResult> { - let view = self - .persistence - .begin_transaction(|transaction| transaction.read_view(view_id)) - .await?; - self.get_data_processor(view.data_format) - } - - #[inline] - fn get_data_processor>( - &self, - data_type: T, - ) -> FlowyResult> { - let data_type = data_type.into(); - match self.data_processors.get(&data_type) { - None => Err(FlowyError::internal().context(format!( - "Get data processor failed. Unknown view data type: {:?}", - data_type - ))), - Some(processor) => Ok(processor.clone()), + if let Some(event) = stream.next().await { + handle_trash_event( + persistence.clone(), + data_processors.clone(), + trash_controller.clone(), + event, + ) + .await } + } + }); + } + + async fn get_data_processor_from_view_id( + &self, + view_id: &str, + ) -> FlowyResult> { + let view = self + .persistence + .begin_transaction(|transaction| transaction.read_view(view_id)) + .await?; + self.get_data_processor(view.data_format) + } + + #[inline] + fn get_data_processor>( + &self, + data_type: T, + ) -> FlowyResult> { + let data_type = data_type.into(); + match self.data_processors.get(&data_type) { + None => Err(FlowyError::internal().context(format!( + "Get data processor failed. Unknown view data type: {:?}", + data_type + ))), + Some(processor) => Ok(processor.clone()), } + } } #[tracing::instrument(level = "trace", skip(persistence, data_processors, trash_can))] async fn handle_trash_event( - persistence: Arc, - data_processors: ViewDataProcessorMap, - trash_can: Arc, - event: TrashEvent, + persistence: Arc, + data_processors: ViewDataProcessorMap, + trash_can: Arc, + event: TrashEvent, ) { - match event { - TrashEvent::NewTrash(identifiers, ret) => { - let result = persistence - .begin_transaction(|transaction| { - let view_revs = read_local_views_with_transaction(identifiers, &transaction)?; - for view_rev in view_revs { - notify_views_changed(&view_rev.app_id, trash_can.clone(), &transaction)?; - notify_dart(view_rev.into(), FolderNotification::DidDeleteView); - } - Ok(()) - }) - .await; - let _ = ret.send(result).await; - } - TrashEvent::Putback(identifiers, ret) => { - let result = persistence - .begin_transaction(|transaction| { - let view_revs = read_local_views_with_transaction(identifiers, &transaction)?; - for view_rev in view_revs { - notify_views_changed(&view_rev.app_id, trash_can.clone(), &transaction)?; - notify_dart(view_rev.into(), FolderNotification::DidRestoreView); - } - Ok(()) - }) - .await; - let _ = ret.send(result).await; - } - TrashEvent::Delete(identifiers, ret) => { - let result = || async { - let views = persistence - .begin_transaction(|transaction| { - let mut notify_ids = HashSet::new(); - let mut views = vec![]; - for identifier in identifiers.items { - if let Ok(view_rev) = transaction.delete_view(&identifier.id) { - notify_ids.insert(view_rev.app_id.clone()); - views.push(view_rev); - } - } - for notify_id in notify_ids { - notify_views_changed(¬ify_id, trash_can.clone(), &transaction)?; - } - Ok(views) - }) - .await?; + match event { + TrashEvent::NewTrash(identifiers, ret) => { + let result = persistence + .begin_transaction(|transaction| { + let view_revs = read_local_views_with_transaction(identifiers, &transaction)?; + for view_rev in view_revs { + notify_views_changed(&view_rev.app_id, trash_can.clone(), &transaction)?; + notify_dart(view_rev.into(), FolderNotification::DidDeleteView); + } + Ok(()) + }) + .await; + let _ = ret.send(result).await; + }, + TrashEvent::Putback(identifiers, ret) => { + let result = persistence + .begin_transaction(|transaction| { + let view_revs = read_local_views_with_transaction(identifiers, &transaction)?; + for view_rev in view_revs { + notify_views_changed(&view_rev.app_id, trash_can.clone(), &transaction)?; + notify_dart(view_rev.into(), FolderNotification::DidRestoreView); + } + Ok(()) + }) + .await; + let _ = ret.send(result).await; + }, + TrashEvent::Delete(identifiers, ret) => { + let result = || async { + let views = persistence + .begin_transaction(|transaction| { + let mut notify_ids = HashSet::new(); + let mut views = vec![]; + for identifier in identifiers.items { + if let Ok(view_rev) = transaction.delete_view(&identifier.id) { + notify_ids.insert(view_rev.app_id.clone()); + views.push(view_rev); + } + } + for notify_id in notify_ids { + notify_views_changed(¬ify_id, trash_can.clone(), &transaction)?; + } + Ok(views) + }) + .await?; - for view in views { - let data_type = view.data_format.clone().into(); - match get_data_processor(data_processors.clone(), &data_type) { - Ok(processor) => { - processor.close_view(&view.id).await?; - } - Err(e) => tracing::error!("{}", e), - } - } - Ok(()) - }; - let _ = ret.send(result().await).await; + for view in views { + let data_type = view.data_format.clone().into(); + match get_data_processor(data_processors.clone(), &data_type) { + Ok(processor) => { + processor.close_view(&view.id).await?; + }, + Err(e) => tracing::error!("{}", e), + } } - } + Ok(()) + }; + let _ = ret.send(result().await).await; + }, + } } fn get_data_processor( - data_processors: ViewDataProcessorMap, - data_type: &ViewDataFormatPB, + data_processors: ViewDataProcessorMap, + data_type: &ViewDataFormatPB, ) -> FlowyResult> { - match data_processors.get(data_type) { - None => Err(FlowyError::internal().context(format!( - "Get data processor failed. Unknown view data type: {:?}", - data_type - ))), - Some(processor) => Ok(processor.clone()), - } + match data_processors.get(data_type) { + None => Err(FlowyError::internal().context(format!( + "Get data processor failed. Unknown view data type: {:?}", + data_type + ))), + Some(processor) => Ok(processor.clone()), + } } fn read_local_views_with_transaction<'a>( - identifiers: RepeatedTrashIdPB, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), + identifiers: RepeatedTrashIdPB, + transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> Result, FlowyError> { - let mut view_revs = vec![]; - for identifier in identifiers.items { - view_revs.push(transaction.read_view(&identifier.id)?); - } - Ok(view_revs) + let mut view_revs = vec![]; + for identifier in identifiers.items { + view_revs.push(transaction.read_view(&identifier.id)?); + } + Ok(view_revs) } fn notify_dart(view: ViewPB, notification: FolderNotification) { - send_notification(&view.id, notification).payload(view).send(); + send_notification(&view.id, notification) + .payload(view) + .send(); } #[tracing::instrument( - level = "debug", - skip(belong_to_id, trash_controller, transaction), - fields(view_count), - err + level = "debug", + skip(belong_to_id, trash_controller, transaction), + fields(view_count), + err )] fn notify_views_changed<'a>( - belong_to_id: &str, - trash_controller: Arc, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), + belong_to_id: &str, + trash_controller: Arc, + transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> FlowyResult<()> { - let mut app_rev = transaction.read_app(belong_to_id)?; - let trash_ids = trash_controller.read_trash_ids(transaction)?; - app_rev.belongings.retain(|view| !trash_ids.contains(&view.id)); - let app: AppPB = app_rev.into(); + let mut app_rev = transaction.read_app(belong_to_id)?; + let trash_ids = trash_controller.read_trash_ids(transaction)?; + app_rev + .belongings + .retain(|view| !trash_ids.contains(&view.id)); + let app: AppPB = app_rev.into(); - send_notification(belong_to_id, FolderNotification::DidUpdateApp) - .payload(app) - .send(); + send_notification(belong_to_id, FolderNotification::DidUpdateApp) + .payload(app) + .send(); - Ok(()) + Ok(()) } fn read_belonging_views_on_local<'a>( - belong_to_id: &str, - trash_controller: Arc, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), + belong_to_id: &str, + trash_controller: Arc, + transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> FlowyResult> { - let mut view_revs = transaction.read_views(belong_to_id)?; - let trash_ids = trash_controller.read_trash_ids(transaction)?; - view_revs.retain(|view_table| !trash_ids.contains(&view_table.id)); + let mut view_revs = transaction.read_views(belong_to_id)?; + let trash_ids = trash_controller.read_trash_ids(transaction)?; + view_revs.retain(|view_table| !trash_ids.contains(&view_table.id)); - Ok(view_revs) + Ok(view_revs) } diff --git a/frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs index 03948b0e59..dc7f8843b8 100644 --- a/frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs +++ b/frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs @@ -2,119 +2,121 @@ use crate::entities::view::{MoveFolderItemParams, MoveFolderItemPayloadPB, MoveF use crate::manager::FolderManager; use crate::services::{notify_workspace_setting_did_change, AppController}; use crate::{ - entities::{ - trash::TrashPB, - view::{ - CreateViewParams, CreateViewPayloadPB, RepeatedViewIdPB, UpdateViewParams, UpdateViewPayloadPB, ViewIdPB, - ViewPB, - }, + entities::{ + trash::TrashPB, + view::{ + CreateViewParams, CreateViewPayloadPB, RepeatedViewIdPB, UpdateViewParams, + UpdateViewPayloadPB, ViewIdPB, ViewPB, }, - errors::FlowyError, - services::{TrashController, ViewController}, + }, + errors::FlowyError, + services::{TrashController, ViewController}, }; use folder_model::TrashRevision; use lib_dispatch::prelude::{data_result, AFPluginData, AFPluginState, DataResult}; use std::{convert::TryInto, sync::Arc}; pub(crate) async fn create_view_handler( - data: AFPluginData, - controller: AFPluginState>, + data: AFPluginData, + controller: AFPluginState>, ) -> DataResult { - let params: CreateViewParams = data.into_inner().try_into()?; - let view_rev = controller.create_view_from_params(params).await?; - data_result(view_rev.into()) + let params: CreateViewParams = data.into_inner().try_into()?; + let view_rev = controller.create_view_from_params(params).await?; + data_result(view_rev.into()) } pub(crate) async fn read_view_handler( - data: AFPluginData, - controller: AFPluginState>, + data: AFPluginData, + controller: AFPluginState>, ) -> DataResult { - let view_id: ViewIdPB = data.into_inner(); - let view_rev = controller.read_view(&view_id.value).await?; - data_result(view_rev.into()) + let view_id: ViewIdPB = data.into_inner(); + let view_rev = controller.read_view(&view_id.value).await?; + data_result(view_rev.into()) } #[tracing::instrument(level = "debug", skip(data, controller), err)] pub(crate) async fn update_view_handler( - data: AFPluginData, - controller: AFPluginState>, + data: AFPluginData, + controller: AFPluginState>, ) -> Result<(), FlowyError> { - let params: UpdateViewParams = data.into_inner().try_into()?; - let _ = controller.update_view(params).await?; + let params: UpdateViewParams = data.into_inner().try_into()?; + let _ = controller.update_view(params).await?; - Ok(()) + Ok(()) } pub(crate) async fn delete_view_handler( - data: AFPluginData, - view_controller: AFPluginState>, - trash_controller: AFPluginState>, + data: AFPluginData, + view_controller: AFPluginState>, + trash_controller: AFPluginState>, ) -> Result<(), FlowyError> { - let params: RepeatedViewIdPB = data.into_inner(); - for view_id in ¶ms.items { - let _ = view_controller.move_view_to_trash(view_id).await; - } + let params: RepeatedViewIdPB = data.into_inner(); + for view_id in ¶ms.items { + let _ = view_controller.move_view_to_trash(view_id).await; + } - let trash = view_controller - .read_local_views(params.items) - .await? - .into_iter() - .map(|view| { - let trash_rev: TrashRevision = view.into(); - trash_rev.into() - }) - .collect::>(); + let trash = view_controller + .read_local_views(params.items) + .await? + .into_iter() + .map(|view| { + let trash_rev: TrashRevision = view.into(); + trash_rev.into() + }) + .collect::>(); - trash_controller.add(trash).await?; - Ok(()) + trash_controller.add(trash).await?; + Ok(()) } pub(crate) async fn set_latest_view_handler( - data: AFPluginData, - folder: AFPluginState>, - controller: AFPluginState>, + data: AFPluginData, + folder: AFPluginState>, + controller: AFPluginState>, ) -> Result<(), FlowyError> { - let view_id: ViewIdPB = data.into_inner(); - controller.set_latest_view(&view_id.value)?; - notify_workspace_setting_did_change(&folder, &view_id).await?; - Ok(()) + let view_id: ViewIdPB = data.into_inner(); + controller.set_latest_view(&view_id.value)?; + notify_workspace_setting_did_change(&folder, &view_id).await?; + Ok(()) } pub(crate) async fn close_view_handler( - data: AFPluginData, - controller: AFPluginState>, + data: AFPluginData, + controller: AFPluginState>, ) -> Result<(), FlowyError> { - let view_id: ViewIdPB = data.into_inner(); - controller.close_view(&view_id.value).await?; - Ok(()) + let view_id: ViewIdPB = data.into_inner(); + controller.close_view(&view_id.value).await?; + Ok(()) } #[tracing::instrument(level = "debug", skip_all, err)] pub(crate) async fn move_item_handler( - data: AFPluginData, - view_controller: AFPluginState>, - app_controller: AFPluginState>, + data: AFPluginData, + view_controller: AFPluginState>, + app_controller: AFPluginState>, ) -> Result<(), FlowyError> { - let params: MoveFolderItemParams = data.into_inner().try_into()?; - match params.ty { - MoveFolderItemType::MoveApp => { - app_controller.move_app(¶ms.item_id, params.from, params.to).await?; - } - MoveFolderItemType::MoveView => { - view_controller - .move_view(¶ms.item_id, params.from, params.to) - .await?; - } - } - Ok(()) + let params: MoveFolderItemParams = data.into_inner().try_into()?; + match params.ty { + MoveFolderItemType::MoveApp => { + app_controller + .move_app(¶ms.item_id, params.from, params.to) + .await?; + }, + MoveFolderItemType::MoveView => { + view_controller + .move_view(¶ms.item_id, params.from, params.to) + .await?; + }, + } + Ok(()) } #[tracing::instrument(level = "debug", skip(data, controller), err)] pub(crate) async fn duplicate_view_handler( - data: AFPluginData, - controller: AFPluginState>, + data: AFPluginData, + controller: AFPluginState>, ) -> Result<(), FlowyError> { - let view: ViewPB = data.into_inner(); - controller.duplicate_view(view).await?; - Ok(()) + let view: ViewPB = data.into_inner(); + controller.duplicate_view(view).await?; + Ok(()) } diff --git a/frontend/rust-lib/flowy-folder/src/services/web_socket.rs b/frontend/rust-lib/flowy-folder/src/services/web_socket.rs index 3a059f48e4..6a4eb4a463 100644 --- a/frontend/rust-lib/flowy-folder/src/services/web_socket.rs +++ b/frontend/rust-lib/flowy-folder/src/services/web_socket.rs @@ -15,137 +15,156 @@ use ws_model::ws_revision::{ClientRevisionWSData, NewDocumentUser}; #[derive(Clone)] pub struct FolderResolveOperations(pub FolderOperations); impl OperationsDeserializer for FolderResolveOperations { - fn deserialize_revisions(revisions: Vec) -> FlowyResult { - Ok(FolderResolveOperations(make_operations_from_revisions(revisions)?)) - } + fn deserialize_revisions(revisions: Vec) -> FlowyResult { + Ok(FolderResolveOperations(make_operations_from_revisions( + revisions, + )?)) + } } impl OperationsSerializer for FolderResolveOperations { - fn serialize_operations(&self) -> Bytes { - self.0.json_bytes() - } + fn serialize_operations(&self) -> Bytes { + self.0.json_bytes() + } } impl FolderResolveOperations { - pub fn into_inner(self) -> FolderOperations { - self.0 - } + pub fn into_inner(self) -> FolderOperations { + self.0 + } } -pub type FolderConflictController = ConflictController>; +pub type FolderConflictController = + ConflictController>; #[allow(dead_code)] pub(crate) async fn make_folder_ws_manager( - user_id: &str, - folder_id: &str, - rev_manager: Arc>>, - web_socket: Arc, - folder_pad: Arc>, + user_id: &str, + folder_id: &str, + rev_manager: Arc>>, + web_socket: Arc, + folder_pad: Arc>, ) -> Arc { - let ws_data_provider = Arc::new(WSDataProvider::new(folder_id, Arc::new(rev_manager.clone()))); - let resolver = Arc::new(FolderConflictResolver { folder_pad }); - let conflict_controller = - FolderConflictController::new(user_id, resolver, Arc::new(ws_data_provider.clone()), rev_manager); - let ws_data_stream = Arc::new(FolderRevisionWSDataStream::new(conflict_controller)); - let ws_data_sink = Arc::new(FolderWSDataSink(ws_data_provider)); - let ping_duration = Duration::from_millis(FOLDER_SYNC_INTERVAL_IN_MILLIS); - Arc::new(RevisionWebSocketManager::new( - "Folder", - folder_id, - web_socket, - ws_data_sink, - ws_data_stream, - ping_duration, - )) + let ws_data_provider = Arc::new(WSDataProvider::new( + folder_id, + Arc::new(rev_manager.clone()), + )); + let resolver = Arc::new(FolderConflictResolver { folder_pad }); + let conflict_controller = FolderConflictController::new( + user_id, + resolver, + Arc::new(ws_data_provider.clone()), + rev_manager, + ); + let ws_data_stream = Arc::new(FolderRevisionWSDataStream::new(conflict_controller)); + let ws_data_sink = Arc::new(FolderWSDataSink(ws_data_provider)); + let ping_duration = Duration::from_millis(FOLDER_SYNC_INTERVAL_IN_MILLIS); + Arc::new(RevisionWebSocketManager::new( + "Folder", + folder_id, + web_socket, + ws_data_sink, + ws_data_stream, + ping_duration, + )) } pub(crate) struct FolderWSDataSink(Arc); impl RevisionWebSocketSink for FolderWSDataSink { - fn next(&self) -> FutureResult, FlowyError> { - let sink_provider = self.0.clone(); - FutureResult::new(async move { sink_provider.next().await }) - } + fn next(&self) -> FutureResult, FlowyError> { + let sink_provider = self.0.clone(); + FutureResult::new(async move { sink_provider.next().await }) + } } struct FolderConflictResolver { - folder_pad: Arc>, + folder_pad: Arc>, } impl ConflictResolver for FolderConflictResolver { - fn compose_operations(&self, operations: FolderResolveOperations) -> BoxResultFuture { - let operations = operations.into_inner(); - let folder_pad = self.folder_pad.clone(); - Box::pin(async move { - let md5 = folder_pad.write().compose_remote_operations(operations)?; - Ok(md5.into()) - }) - } + fn compose_operations( + &self, + operations: FolderResolveOperations, + ) -> BoxResultFuture { + let operations = operations.into_inner(); + let folder_pad = self.folder_pad.clone(); + Box::pin(async move { + let md5 = folder_pad.write().compose_remote_operations(operations)?; + Ok(md5.into()) + }) + } - fn transform_operations( - &self, - operations: FolderResolveOperations, - ) -> BoxResultFuture, FlowyError> { - let folder_pad = self.folder_pad.clone(); - let operations = operations.into_inner(); - Box::pin(async move { - let read_guard = folder_pad.read(); - let mut server_operations: Option = None; - let client_operations: FolderResolveOperations; - if read_guard.is_empty() { - // Do nothing - client_operations = FolderResolveOperations(operations); - } else { - let (s_prime, c_prime) = read_guard.get_operations().transform(&operations)?; - client_operations = FolderResolveOperations(c_prime); - server_operations = Some(FolderResolveOperations(s_prime)); - } - drop(read_guard); - Ok(TransformOperations { - client_operations, - server_operations, - }) - }) - } + fn transform_operations( + &self, + operations: FolderResolveOperations, + ) -> BoxResultFuture, FlowyError> { + let folder_pad = self.folder_pad.clone(); + let operations = operations.into_inner(); + Box::pin(async move { + let read_guard = folder_pad.read(); + let mut server_operations: Option = None; + let client_operations: FolderResolveOperations; + if read_guard.is_empty() { + // Do nothing + client_operations = FolderResolveOperations(operations); + } else { + let (s_prime, c_prime) = read_guard.get_operations().transform(&operations)?; + client_operations = FolderResolveOperations(c_prime); + server_operations = Some(FolderResolveOperations(s_prime)); + } + drop(read_guard); + Ok(TransformOperations { + client_operations, + server_operations, + }) + }) + } - fn reset_operations(&self, operations: FolderResolveOperations) -> BoxResultFuture { - let folder_pad = self.folder_pad.clone(); - Box::pin(async move { - let md5 = folder_pad.write().reset_folder(operations.into_inner())?; - Ok(md5.into()) - }) - } + fn reset_operations( + &self, + operations: FolderResolveOperations, + ) -> BoxResultFuture { + let folder_pad = self.folder_pad.clone(); + Box::pin(async move { + let md5 = folder_pad.write().reset_folder(operations.into_inner())?; + Ok(md5.into()) + }) + } } struct FolderRevisionWSDataStream { - conflict_controller: Arc, + conflict_controller: Arc, } impl FolderRevisionWSDataStream { - pub fn new(conflict_controller: FolderConflictController) -> Self { - Self { - conflict_controller: Arc::new(conflict_controller), - } + pub fn new(conflict_controller: FolderConflictController) -> Self { + Self { + conflict_controller: Arc::new(conflict_controller), } + } } impl RevisionWSDataStream for FolderRevisionWSDataStream { - fn receive_push_revision(&self, revisions: Vec) -> BoxResultFuture<(), FlowyError> { - let resolver = self.conflict_controller.clone(); - Box::pin(async move { resolver.receive_revisions(revisions).await }) - } + fn receive_push_revision(&self, revisions: Vec) -> BoxResultFuture<(), FlowyError> { + let resolver = self.conflict_controller.clone(); + Box::pin(async move { resolver.receive_revisions(revisions).await }) + } - fn receive_ack(&self, rev_id: i64) -> BoxResultFuture<(), FlowyError> { - let resolver = self.conflict_controller.clone(); - Box::pin(async move { resolver.ack_revision(rev_id).await }) - } + fn receive_ack(&self, rev_id: i64) -> BoxResultFuture<(), FlowyError> { + let resolver = self.conflict_controller.clone(); + Box::pin(async move { resolver.ack_revision(rev_id).await }) + } - fn receive_new_user_connect(&self, _new_user: NewDocumentUser) -> BoxResultFuture<(), FlowyError> { - // Do nothing by now, just a placeholder for future extension. - Box::pin(async move { Ok(()) }) - } + fn receive_new_user_connect( + &self, + _new_user: NewDocumentUser, + ) -> BoxResultFuture<(), FlowyError> { + // Do nothing by now, just a placeholder for future extension. + Box::pin(async move { Ok(()) }) + } - fn pull_revisions_in_range(&self, range: RevisionRange) -> BoxResultFuture<(), FlowyError> { - let resolver = self.conflict_controller.clone(); - Box::pin(async move { resolver.send_revisions(range).await }) - } + fn pull_revisions_in_range(&self, range: RevisionRange) -> BoxResultFuture<(), FlowyError> { + let resolver = self.conflict_controller.clone(); + Box::pin(async move { resolver.send_revisions(range).await }) + } } diff --git a/frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs b/frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs index 9179591396..710a6bf8ae 100644 --- a/frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs @@ -1,263 +1,281 @@ use crate::entities::workspace::*; use crate::manager::FolderManager; use crate::{ - errors::*, - event_map::{FolderCouldServiceV1, WorkspaceUser}, - notification::*, - services::{ - persistence::{FolderPersistence, FolderPersistenceTransaction, WorkspaceChangeset}, - read_workspace_apps, TrashController, - }, + errors::*, + event_map::{FolderCouldServiceV1, WorkspaceUser}, + notification::*, + services::{ + persistence::{FolderPersistence, FolderPersistenceTransaction, WorkspaceChangeset}, + read_workspace_apps, TrashController, + }, }; use flowy_sqlite::kv::KV; use folder_model::{AppRevision, WorkspaceRevision}; use std::sync::Arc; pub struct WorkspaceController { - pub user: Arc, + pub user: Arc, + persistence: Arc, + pub(crate) trash_controller: Arc, + cloud_service: Arc, +} + +impl WorkspaceController { + pub(crate) fn new( + user: Arc, persistence: Arc, - pub(crate) trash_controller: Arc, + trash_can: Arc, cloud_service: Arc, + ) -> Self { + Self { + user, + persistence, + trash_controller: trash_can, + cloud_service, + } + } + + pub(crate) async fn create_workspace_from_params( + &self, + params: CreateWorkspaceParams, + ) -> Result { + let workspace = self.create_workspace_on_server(params.clone()).await?; + let user_id = self.user.user_id()?; + let token = self.user.token()?; + let workspaces = self + .persistence + .begin_transaction(|transaction| { + transaction.create_workspace(&user_id, workspace.clone())?; + transaction.read_workspaces(&user_id, None) + }) + .await? + .into_iter() + .map(|workspace_rev| workspace_rev.into()) + .collect(); + let repeated_workspace = RepeatedWorkspacePB { items: workspaces }; + send_notification(&token, FolderNotification::DidCreateWorkspace) + .payload(repeated_workspace) + .send(); + set_current_workspace(&user_id, &workspace.id); + Ok(workspace) + } + + #[allow(dead_code)] + pub(crate) async fn update_workspace( + &self, + params: UpdateWorkspaceParams, + ) -> Result<(), FlowyError> { + let changeset = WorkspaceChangeset::new(params.clone()); + let workspace_id = changeset.id.clone(); + let workspace = self + .persistence + .begin_transaction(|transaction| { + transaction.update_workspace(changeset)?; + let user_id = self.user.user_id()?; + self.read_workspace(workspace_id.clone(), &user_id, &transaction) + }) + .await?; + + send_notification(&workspace_id, FolderNotification::DidUpdateWorkspace) + .payload(workspace) + .send(); + self.update_workspace_on_server(params)?; + + Ok(()) + } + + #[allow(dead_code)] + pub(crate) async fn delete_workspace(&self, workspace_id: &str) -> Result<(), FlowyError> { + let user_id = self.user.user_id()?; + let token = self.user.token()?; + let repeated_workspace = self + .persistence + .begin_transaction(|transaction| { + transaction.delete_workspace(workspace_id)?; + self.read_workspaces(None, &user_id, &transaction) + }) + .await?; + send_notification(&token, FolderNotification::DidDeleteWorkspace) + .payload(repeated_workspace) + .send(); + self.delete_workspace_on_server(workspace_id)?; + Ok(()) + } + + pub(crate) async fn open_workspace( + &self, + params: WorkspaceIdPB, + ) -> Result { + let user_id = self.user.user_id()?; + if let Some(workspace_id) = params.value { + let workspace = self + .persistence + .begin_transaction(|transaction| self.read_workspace(workspace_id, &user_id, &transaction)) + .await?; + set_current_workspace(&user_id, &workspace.id); + Ok(workspace) + } else { + Err(FlowyError::workspace_id().context("Opened workspace id should not be empty")) + } + } + + pub(crate) async fn read_current_workspace_apps(&self) -> Result, FlowyError> { + let user_id = self.user.user_id()?; + let workspace_id = get_current_workspace(&user_id)?; + let app_revs = self + .persistence + .begin_transaction(|transaction| { + read_workspace_apps(&workspace_id, self.trash_controller.clone(), &transaction) + }) + .await?; + // TODO: read from server + Ok(app_revs) + } + + #[tracing::instrument(level = "debug", skip(self, transaction), err)] + pub(crate) fn read_workspaces<'a>( + &self, + workspace_id: Option, + user_id: &str, + transaction: &'a (dyn FolderPersistenceTransaction + 'a), + ) -> Result { + let workspace_id = workspace_id.to_owned(); + let trash_ids = self.trash_controller.read_trash_ids(transaction)?; + let workspaces = transaction + .read_workspaces(user_id, workspace_id)? + .into_iter() + .map(|mut workspace_rev| { + workspace_rev + .apps + .retain(|app_rev| !trash_ids.contains(&app_rev.id)); + workspace_rev.into() + }) + .collect(); + Ok(RepeatedWorkspacePB { items: workspaces }) + } + + pub(crate) fn read_workspace<'a>( + &self, + workspace_id: String, + user_id: &str, + transaction: &'a (dyn FolderPersistenceTransaction + 'a), + ) -> Result { + let mut workspaces = self + .read_workspaces(Some(workspace_id.clone()), user_id, transaction)? + .items; + if workspaces.is_empty() { + return Err( + FlowyError::record_not_found().context(format!("{} workspace not found", workspace_id)), + ); + } + debug_assert_eq!(workspaces.len(), 1); + let workspace = workspaces + .drain(..1) + .collect::>() + .pop() + .unwrap(); + Ok(workspace) + } } impl WorkspaceController { - pub(crate) fn new( - user: Arc, - persistence: Arc, - trash_can: Arc, - cloud_service: Arc, - ) -> Self { - Self { - user, - persistence, - trash_controller: trash_can, - cloud_service, - } - } + #[tracing::instrument(level = "trace", skip(self), err)] + async fn create_workspace_on_server( + &self, + params: CreateWorkspaceParams, + ) -> Result { + let token = self.user.token()?; + self.cloud_service.create_workspace(&token, params).await + } - pub(crate) async fn create_workspace_from_params( - &self, - params: CreateWorkspaceParams, - ) -> Result { - let workspace = self.create_workspace_on_server(params.clone()).await?; - let user_id = self.user.user_id()?; - let token = self.user.token()?; - let workspaces = self - .persistence - .begin_transaction(|transaction| { - transaction.create_workspace(&user_id, workspace.clone())?; - transaction.read_workspaces(&user_id, None) - }) - .await? - .into_iter() - .map(|workspace_rev| workspace_rev.into()) - .collect(); - let repeated_workspace = RepeatedWorkspacePB { items: workspaces }; - send_notification(&token, FolderNotification::DidCreateWorkspace) - .payload(repeated_workspace) - .send(); - set_current_workspace(&user_id, &workspace.id); - Ok(workspace) - } + #[tracing::instrument(level = "trace", skip(self), err)] + fn update_workspace_on_server(&self, params: UpdateWorkspaceParams) -> Result<(), FlowyError> { + let (token, server) = (self.user.token()?, self.cloud_service.clone()); + tokio::spawn(async move { + match server.update_workspace(&token, params).await { + Ok(_) => {}, + Err(e) => { + // TODO: retry? + log::error!("Update workspace failed: {:?}", e); + }, + } + }); + Ok(()) + } - #[allow(dead_code)] - pub(crate) async fn update_workspace(&self, params: UpdateWorkspaceParams) -> Result<(), FlowyError> { - let changeset = WorkspaceChangeset::new(params.clone()); - let workspace_id = changeset.id.clone(); - let workspace = self - .persistence - .begin_transaction(|transaction| { - transaction.update_workspace(changeset)?; - let user_id = self.user.user_id()?; - self.read_workspace(workspace_id.clone(), &user_id, &transaction) - }) - .await?; - - send_notification(&workspace_id, FolderNotification::DidUpdateWorkspace) - .payload(workspace) - .send(); - self.update_workspace_on_server(params)?; - - Ok(()) - } - - #[allow(dead_code)] - pub(crate) async fn delete_workspace(&self, workspace_id: &str) -> Result<(), FlowyError> { - let user_id = self.user.user_id()?; - let token = self.user.token()?; - let repeated_workspace = self - .persistence - .begin_transaction(|transaction| { - transaction.delete_workspace(workspace_id)?; - self.read_workspaces(None, &user_id, &transaction) - }) - .await?; - send_notification(&token, FolderNotification::DidDeleteWorkspace) - .payload(repeated_workspace) - .send(); - self.delete_workspace_on_server(workspace_id)?; - Ok(()) - } - - pub(crate) async fn open_workspace(&self, params: WorkspaceIdPB) -> Result { - let user_id = self.user.user_id()?; - if let Some(workspace_id) = params.value { - let workspace = self - .persistence - .begin_transaction(|transaction| self.read_workspace(workspace_id, &user_id, &transaction)) - .await?; - set_current_workspace(&user_id, &workspace.id); - Ok(workspace) - } else { - Err(FlowyError::workspace_id().context("Opened workspace id should not be empty")) - } - } - - pub(crate) async fn read_current_workspace_apps(&self) -> Result, FlowyError> { - let user_id = self.user.user_id()?; - let workspace_id = get_current_workspace(&user_id)?; - let app_revs = self - .persistence - .begin_transaction(|transaction| { - read_workspace_apps(&workspace_id, self.trash_controller.clone(), &transaction) - }) - .await?; - // TODO: read from server - Ok(app_revs) - } - - #[tracing::instrument(level = "debug", skip(self, transaction), err)] - pub(crate) fn read_workspaces<'a>( - &self, - workspace_id: Option, - user_id: &str, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), - ) -> Result { - let workspace_id = workspace_id.to_owned(); - let trash_ids = self.trash_controller.read_trash_ids(transaction)?; - let workspaces = transaction - .read_workspaces(user_id, workspace_id)? - .into_iter() - .map(|mut workspace_rev| { - workspace_rev.apps.retain(|app_rev| !trash_ids.contains(&app_rev.id)); - workspace_rev.into() - }) - .collect(); - Ok(RepeatedWorkspacePB { items: workspaces }) - } - - pub(crate) fn read_workspace<'a>( - &self, - workspace_id: String, - user_id: &str, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), - ) -> Result { - let mut workspaces = self - .read_workspaces(Some(workspace_id.clone()), user_id, transaction)? - .items; - if workspaces.is_empty() { - return Err(FlowyError::record_not_found().context(format!("{} workspace not found", workspace_id))); - } - debug_assert_eq!(workspaces.len(), 1); - let workspace = workspaces.drain(..1).collect::>().pop().unwrap(); - Ok(workspace) - } -} - -impl WorkspaceController { - #[tracing::instrument(level = "trace", skip(self), err)] - async fn create_workspace_on_server(&self, params: CreateWorkspaceParams) -> Result { - let token = self.user.token()?; - self.cloud_service.create_workspace(&token, params).await - } - - #[tracing::instrument(level = "trace", skip(self), err)] - fn update_workspace_on_server(&self, params: UpdateWorkspaceParams) -> Result<(), FlowyError> { - let (token, server) = (self.user.token()?, self.cloud_service.clone()); - tokio::spawn(async move { - match server.update_workspace(&token, params).await { - Ok(_) => {} - Err(e) => { - // TODO: retry? - log::error!("Update workspace failed: {:?}", e); - } - } - }); - Ok(()) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - fn delete_workspace_on_server(&self, workspace_id: &str) -> Result<(), FlowyError> { - let params = WorkspaceIdPB { - value: Some(workspace_id.to_string()), - }; - let (token, server) = (self.user.token()?, self.cloud_service.clone()); - tokio::spawn(async move { - match server.delete_workspace(&token, params).await { - Ok(_) => {} - Err(e) => { - // TODO: retry? - log::error!("Delete workspace failed: {:?}", e); - } - } - }); - Ok(()) - } + #[tracing::instrument(level = "trace", skip(self), err)] + fn delete_workspace_on_server(&self, workspace_id: &str) -> Result<(), FlowyError> { + let params = WorkspaceIdPB { + value: Some(workspace_id.to_string()), + }; + let (token, server) = (self.user.token()?, self.cloud_service.clone()); + tokio::spawn(async move { + match server.delete_workspace(&token, params).await { + Ok(_) => {}, + Err(e) => { + // TODO: retry? + log::error!("Delete workspace failed: {:?}", e); + }, + } + }); + Ok(()) + } } pub async fn notify_workspace_setting_did_change( - folder_manager: &Arc, - view_id: &str, + folder_manager: &Arc, + view_id: &str, ) -> FlowyResult<()> { - let user_id = folder_manager.user.user_id()?; - let token = folder_manager.user.token()?; - let workspace_id = get_current_workspace(&user_id)?; + let user_id = folder_manager.user.user_id()?; + let token = folder_manager.user.token()?; + let workspace_id = get_current_workspace(&user_id)?; - let workspace_setting = folder_manager - .persistence - .begin_transaction(|transaction| { - let workspace = - folder_manager - .workspace_controller - .read_workspace(workspace_id.clone(), &user_id, &transaction)?; + let workspace_setting = folder_manager + .persistence + .begin_transaction(|transaction| { + let workspace = folder_manager.workspace_controller.read_workspace( + workspace_id.clone(), + &user_id, + &transaction, + )?; - let setting = match transaction.read_view(view_id) { - Ok(latest_view) => WorkspaceSettingPB { - workspace, - latest_view: Some(latest_view.into()), - }, - Err(_) => WorkspaceSettingPB { - workspace, - latest_view: None, - }, - }; + let setting = match transaction.read_view(view_id) { + Ok(latest_view) => WorkspaceSettingPB { + workspace, + latest_view: Some(latest_view.into()), + }, + Err(_) => WorkspaceSettingPB { + workspace, + latest_view: None, + }, + }; - Ok(setting) - }) - .await?; + Ok(setting) + }) + .await?; - send_notification(&token, FolderNotification::DidUpdateWorkspaceSetting) - .payload(workspace_setting) - .send(); - Ok(()) + send_notification(&token, FolderNotification::DidUpdateWorkspaceSetting) + .payload(workspace_setting) + .send(); + Ok(()) } const CURRENT_WORKSPACE_ID: &str = "current_workspace_id"; pub fn set_current_workspace(_user_id: &str, workspace_id: &str) { - KV::set_str(CURRENT_WORKSPACE_ID, workspace_id.to_owned()); + KV::set_str(CURRENT_WORKSPACE_ID, workspace_id.to_owned()); } pub fn clear_current_workspace(_user_id: &str) { - let _ = KV::remove(CURRENT_WORKSPACE_ID); + let _ = KV::remove(CURRENT_WORKSPACE_ID); } pub fn get_current_workspace(_user_id: &str) -> Result { - match KV::get_str(CURRENT_WORKSPACE_ID) { - None => { - Err(FlowyError::record_not_found() - .context("Current workspace not found or should call open workspace first")) - } - Some(workspace_id) => Ok(workspace_id), - } + match KV::get_str(CURRENT_WORKSPACE_ID) { + None => Err( + FlowyError::record_not_found() + .context("Current workspace not found or should call open workspace first"), + ), + Some(workspace_id) => Ok(workspace_id), + } } diff --git a/frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs index 796608d716..6bfbd224c4 100644 --- a/frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs +++ b/frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs @@ -1,99 +1,103 @@ use crate::entities::{ - app::RepeatedAppPB, - view::ViewPB, - workspace::{RepeatedWorkspacePB, WorkspaceIdPB, WorkspaceSettingPB, *}, + app::RepeatedAppPB, + view::ViewPB, + workspace::{RepeatedWorkspacePB, WorkspaceIdPB, WorkspaceSettingPB, *}, }; use crate::{ - errors::FlowyError, - manager::FolderManager, - services::{get_current_workspace, read_workspace_apps, WorkspaceController}, + errors::FlowyError, + manager::FolderManager, + services::{get_current_workspace, read_workspace_apps, WorkspaceController}, }; use lib_dispatch::prelude::{data_result, AFPluginData, AFPluginState, DataResult}; use std::{convert::TryInto, sync::Arc}; #[tracing::instrument(level = "debug", skip(data, controller), err)] pub(crate) async fn create_workspace_handler( - data: AFPluginData, - controller: AFPluginState>, + data: AFPluginData, + controller: AFPluginState>, ) -> DataResult { - let controller = controller.get_ref().clone(); - let params: CreateWorkspaceParams = data.into_inner().try_into()?; - let workspace_rev = controller.create_workspace_from_params(params).await?; - data_result(workspace_rev.into()) + let controller = controller.get_ref().clone(); + let params: CreateWorkspaceParams = data.into_inner().try_into()?; + let workspace_rev = controller.create_workspace_from_params(params).await?; + data_result(workspace_rev.into()) } #[tracing::instrument(level = "debug", skip(controller), err)] pub(crate) async fn read_workspace_apps_handler( - controller: AFPluginState>, + controller: AFPluginState>, ) -> DataResult { - let items = controller - .read_current_workspace_apps() - .await? - .into_iter() - .map(|app_rev| app_rev.into()) - .collect(); - let repeated_app = RepeatedAppPB { items }; - data_result(repeated_app) + let items = controller + .read_current_workspace_apps() + .await? + .into_iter() + .map(|app_rev| app_rev.into()) + .collect(); + let repeated_app = RepeatedAppPB { items }; + data_result(repeated_app) } #[tracing::instrument(level = "debug", skip(data, controller), err)] pub(crate) async fn open_workspace_handler( - data: AFPluginData, - controller: AFPluginState>, + data: AFPluginData, + controller: AFPluginState>, ) -> DataResult { - let params: WorkspaceIdPB = data.into_inner(); - let workspaces = controller.open_workspace(params).await?; - data_result(workspaces) + let params: WorkspaceIdPB = data.into_inner(); + let workspaces = controller.open_workspace(params).await?; + data_result(workspaces) } #[tracing::instrument(level = "debug", skip(data, folder), err)] pub(crate) async fn read_workspaces_handler( - data: AFPluginData, - folder: AFPluginState>, + data: AFPluginData, + folder: AFPluginState>, ) -> DataResult { - let params: WorkspaceIdPB = data.into_inner(); - let user_id = folder.user.user_id()?; - let workspace_controller = folder.workspace_controller.clone(); + let params: WorkspaceIdPB = data.into_inner(); + let user_id = folder.user.user_id()?; + let workspace_controller = folder.workspace_controller.clone(); - let trash_controller = folder.trash_controller.clone(); - let workspaces = folder - .persistence - .begin_transaction(|transaction| { - let mut workspaces = workspace_controller.read_workspaces(params.value.clone(), &user_id, &transaction)?; - for workspace in workspaces.iter_mut() { - let apps = read_workspace_apps(&workspace.id, trash_controller.clone(), &transaction)? - .into_iter() - .map(|app_rev| app_rev.into()) - .collect(); - workspace.apps.items = apps; - } - Ok(workspaces) - }) - .await?; - data_result(workspaces) + let trash_controller = folder.trash_controller.clone(); + let workspaces = folder + .persistence + .begin_transaction(|transaction| { + let mut workspaces = + workspace_controller.read_workspaces(params.value.clone(), &user_id, &transaction)?; + for workspace in workspaces.iter_mut() { + let apps = read_workspace_apps(&workspace.id, trash_controller.clone(), &transaction)? + .into_iter() + .map(|app_rev| app_rev.into()) + .collect(); + workspace.apps.items = apps; + } + Ok(workspaces) + }) + .await?; + data_result(workspaces) } #[tracing::instrument(level = "debug", skip(folder), err)] pub async fn read_cur_workspace_handler( - folder: AFPluginState>, + folder: AFPluginState>, ) -> DataResult { - let user_id = folder.user.user_id()?; - let workspace_id = get_current_workspace(&user_id)?; - let workspace = folder - .persistence - .begin_transaction(|transaction| { - folder - .workspace_controller - .read_workspace(workspace_id, &user_id, &transaction) - }) - .await?; + let user_id = folder.user.user_id()?; + let workspace_id = get_current_workspace(&user_id)?; + let workspace = folder + .persistence + .begin_transaction(|transaction| { + folder + .workspace_controller + .read_workspace(workspace_id, &user_id, &transaction) + }) + .await?; - let latest_view: Option = folder - .view_controller - .latest_visit_view() - .await - .unwrap_or(None) - .map(|view_rev| view_rev.into()); - let setting = WorkspaceSettingPB { workspace, latest_view }; - data_result(setting) + let latest_view: Option = folder + .view_controller + .latest_visit_view() + .await + .unwrap_or(None) + .map(|view_rev| view_rev.into()); + let setting = WorkspaceSettingPB { + workspace, + latest_view, + }; + data_result(setting) } diff --git a/frontend/rust-lib/flowy-folder/src/util.rs b/frontend/rust-lib/flowy-folder/src/util.rs index 9356b50852..62439713b3 100644 --- a/frontend/rust-lib/flowy-folder/src/util.rs +++ b/frontend/rust-lib/flowy-folder/src/util.rs @@ -3,73 +3,78 @@ use crate::event_map::{FolderCouldServiceV1, WorkspaceUser}; use lib_infra::retry::Action; use pin_project::pin_project; use std::{ - future::Future, - marker::PhantomData, - pin::Pin, - sync::Arc, - task::{Context, Poll}, + future::Future, + marker::PhantomData, + pin::Pin, + sync::Arc, + task::{Context, Poll}, }; -pub(crate) type Builder = Box) -> Fut + Send + Sync>; +pub(crate) type Builder = + Box) -> Fut + Send + Sync>; #[allow(dead_code)] pub(crate) struct RetryAction { - token: String, - cloud_service: Arc, - user: Arc, - builder: Builder, - phantom: PhantomData<(T, E)>, + token: String, + cloud_service: Arc, + user: Arc, + builder: Builder, + phantom: PhantomData<(T, E)>, } impl RetryAction { - #[allow(dead_code)] - pub(crate) fn new(cloud_service: Arc, user: Arc, builder: F) -> Self - where - Fut: Future> + Send + Sync + 'static, - F: Fn(String, Arc) -> Fut + Send + Sync + 'static, - { - let token = user.token().unwrap_or_else(|_| "".to_owned()); - Self { - token, - cloud_service, - user, - builder: Box::new(builder), - phantom: PhantomData, - } + #[allow(dead_code)] + pub(crate) fn new( + cloud_service: Arc, + user: Arc, + builder: F, + ) -> Self + where + Fut: Future> + Send + Sync + 'static, + F: Fn(String, Arc) -> Fut + Send + Sync + 'static, + { + let token = user.token().unwrap_or_else(|_| "".to_owned()); + Self { + token, + cloud_service, + user, + builder: Box::new(builder), + phantom: PhantomData, } + } } impl Action for RetryAction where - Fut: Future> + Send + Sync + 'static, - T: Send + Sync + 'static, - E: Send + Sync + 'static, + Fut: Future> + Send + Sync + 'static, + T: Send + Sync + 'static, + E: Send + Sync + 'static, { - type Future = Pin> + Send + Sync>>; - type Item = T; - type Error = E; + type Future = Pin> + Send + Sync>>; + type Item = T; + type Error = E; - fn run(&mut self) -> Self::Future { - let fut = (self.builder)(self.token.clone(), self.cloud_service.clone()); - Box::pin(RetryActionFut { fut: Box::pin(fut) }) - } + fn run(&mut self) -> Self::Future { + let fut = (self.builder)(self.token.clone(), self.cloud_service.clone()); + Box::pin(RetryActionFut { fut: Box::pin(fut) }) + } } #[pin_project] struct RetryActionFut { - #[pin] - fut: Pin> + Send + Sync>>, + #[pin] + fut: Pin> + Send + Sync>>, } impl Future for RetryActionFut where - T: Send + Sync + 'static, - E: Send + Sync + 'static, + T: Send + Sync + 'static, + E: Send + Sync + 'static, { - type Output = Result; + type Output = Result; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.project(); - this.fut.as_mut().poll(cx) - } + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + this.fut.as_mut().poll(cx) + } } diff --git a/frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs b/frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs index ef42afaf67..15588b5c55 100644 --- a/frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs +++ b/frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs @@ -6,288 +6,310 @@ use flowy_test::{event_builder::*, FlowySDKTest}; #[tokio::test] async fn workspace_read_all() { - let mut test = FolderTest::new().await; - test.run_scripts(vec![ReadAllWorkspaces]).await; - assert!(!test.all_workspace.is_empty()); + let mut test = FolderTest::new().await; + test.run_scripts(vec![ReadAllWorkspaces]).await; + assert!(!test.all_workspace.is_empty()); } #[tokio::test] async fn workspace_create() { - let mut test = FolderTest::new().await; - let name = "My new workspace".to_owned(); - let desc = "Daily routines".to_owned(); - test.run_scripts(vec![CreateWorkspace { - name: name.clone(), - desc: desc.clone(), + let mut test = FolderTest::new().await; + let name = "My new workspace".to_owned(); + let desc = "Daily routines".to_owned(); + test + .run_scripts(vec![CreateWorkspace { + name: name.clone(), + desc: desc.clone(), }]) .await; - let workspace = test.workspace.clone(); - assert_eq!(workspace.name, name); - assert_eq!(workspace.desc, desc); + let workspace = test.workspace.clone(); + assert_eq!(workspace.name, name); + assert_eq!(workspace.desc, desc); - test.run_scripts(vec![ - ReadWorkspace(Some(workspace.id.clone())), - AssertWorkspace(workspace), + test + .run_scripts(vec![ + ReadWorkspace(Some(workspace.id.clone())), + AssertWorkspace(workspace), ]) .await; } #[tokio::test] async fn workspace_read() { - let mut test = FolderTest::new().await; - let workspace = test.workspace.clone(); + let mut test = FolderTest::new().await; + let workspace = test.workspace.clone(); - test.run_scripts(vec![ - ReadWorkspace(Some(workspace.id.clone())), - AssertWorkspace(workspace), + test + .run_scripts(vec![ + ReadWorkspace(Some(workspace.id.clone())), + AssertWorkspace(workspace), ]) .await; } #[tokio::test] async fn workspace_create_with_apps() { - let mut test = FolderTest::new().await; - test.run_scripts(vec![CreateApp { - name: "App".to_string(), - desc: "App description".to_string(), + let mut test = FolderTest::new().await; + test + .run_scripts(vec![CreateApp { + name: "App".to_string(), + desc: "App description".to_string(), }]) .await; - let app = test.app.clone(); - test.run_scripts(vec![ReadApp(app.id)]).await; + let app = test.app.clone(); + test.run_scripts(vec![ReadApp(app.id)]).await; } #[tokio::test] async fn workspace_create_with_invalid_name() { - for (name, code) in invalid_workspace_name_test_case() { - let sdk = FlowySDKTest::default(); - let request = CreateWorkspacePayloadPB { - name, - desc: "".to_owned(), - }; - assert_eq!( - FolderEventBuilder::new(sdk) - .event(flowy_folder::event_map::FolderEvent::CreateWorkspace) - .payload(request) - .async_send() - .await - .error() - .code, - code.value() - ) - } + for (name, code) in invalid_workspace_name_test_case() { + let sdk = FlowySDKTest::default(); + let request = CreateWorkspacePayloadPB { + name, + desc: "".to_owned(), + }; + assert_eq!( + FolderEventBuilder::new(sdk) + .event(flowy_folder::event_map::FolderEvent::CreateWorkspace) + .payload(request) + .async_send() + .await + .error() + .code, + code.value() + ) + } } #[tokio::test] #[should_panic] async fn app_delete() { - let mut test = FolderTest::new().await; - let app = test.app.clone(); - test.run_scripts(vec![DeleteApp, ReadApp(app.id)]).await; + let mut test = FolderTest::new().await; + let app = test.app.clone(); + test.run_scripts(vec![DeleteApp, ReadApp(app.id)]).await; } #[tokio::test] async fn app_delete_then_restore() { - let mut test = FolderTest::new().await; - let app = test.app.clone(); - test.run_scripts(vec![ - DeleteApp, - RestoreAppFromTrash, - ReadApp(app.id.clone()), - AssertApp(app), + let mut test = FolderTest::new().await; + let app = test.app.clone(); + test + .run_scripts(vec![ + DeleteApp, + RestoreAppFromTrash, + ReadApp(app.id.clone()), + AssertApp(app), ]) .await; } #[tokio::test] async fn app_read() { - let mut test = FolderTest::new().await; - let app = test.app.clone(); - test.run_scripts(vec![ReadApp(app.id.clone()), AssertApp(app)]).await; + let mut test = FolderTest::new().await; + let app = test.app.clone(); + test + .run_scripts(vec![ReadApp(app.id.clone()), AssertApp(app)]) + .await; } #[tokio::test] async fn app_update() { - let mut test = FolderTest::new().await; - let app = test.app.clone(); - let new_name = "😁 hell world".to_owned(); - assert_ne!(app.name, new_name); + let mut test = FolderTest::new().await; + let app = test.app.clone(); + let new_name = "😁 hell world".to_owned(); + assert_ne!(app.name, new_name); - test.run_scripts(vec![ - UpdateApp { - name: Some(new_name.clone()), - desc: None, - }, - ReadApp(app.id), + test + .run_scripts(vec![ + UpdateApp { + name: Some(new_name.clone()), + desc: None, + }, + ReadApp(app.id), ]) .await; - assert_eq!(test.app.name, new_name); + assert_eq!(test.app.name, new_name); } #[tokio::test] async fn app_create_with_view() { - let mut test = FolderTest::new().await; - let mut app = test.app.clone(); - test.run_scripts(vec![ - CreateView { - name: "View A".to_owned(), - desc: "View A description".to_owned(), - data_type: ViewDataFormatPB::DeltaFormat, - }, - CreateView { - name: "Grid".to_owned(), - desc: "Grid description".to_owned(), - data_type: ViewDataFormatPB::DatabaseFormat, - }, - ReadApp(app.id), + let mut test = FolderTest::new().await; + let mut app = test.app.clone(); + test + .run_scripts(vec![ + CreateView { + name: "View A".to_owned(), + desc: "View A description".to_owned(), + data_type: ViewDataFormatPB::DeltaFormat, + }, + CreateView { + name: "Grid".to_owned(), + desc: "Grid description".to_owned(), + data_type: ViewDataFormatPB::DatabaseFormat, + }, + ReadApp(app.id), ]) .await; - app = test.app.clone(); - assert_eq!(app.belongings.len(), 3); - assert_eq!(app.belongings[1].name, "View A"); - assert_eq!(app.belongings[2].name, "Grid") + app = test.app.clone(); + assert_eq!(app.belongings.len(), 3); + assert_eq!(app.belongings[1].name, "View A"); + assert_eq!(app.belongings[2].name, "Grid") } #[tokio::test] async fn view_update() { - let mut test = FolderTest::new().await; - let view = test.view.clone(); - let new_name = "😁 123".to_owned(); - assert_ne!(view.name, new_name); + let mut test = FolderTest::new().await; + let view = test.view.clone(); + let new_name = "😁 123".to_owned(); + assert_ne!(view.name, new_name); - test.run_scripts(vec![ - UpdateView { - name: Some(new_name.clone()), - desc: None, - }, - ReadView(view.id), + test + .run_scripts(vec![ + UpdateView { + name: Some(new_name.clone()), + desc: None, + }, + ReadView(view.id), ]) .await; - assert_eq!(test.view.name, new_name); + assert_eq!(test.view.name, new_name); } #[tokio::test] #[should_panic] async fn view_delete() { - let mut test = FolderTest::new().await; - let view = test.view.clone(); - test.run_scripts(vec![DeleteView, ReadView(view.id)]).await; + let mut test = FolderTest::new().await; + let view = test.view.clone(); + test.run_scripts(vec![DeleteView, ReadView(view.id)]).await; } #[tokio::test] async fn view_delete_then_restore() { - let mut test = FolderTest::new().await; - let view = test.view.clone(); - test.run_scripts(vec![ - DeleteView, - RestoreViewFromTrash, - ReadView(view.id.clone()), - AssertView(view), + let mut test = FolderTest::new().await; + let view = test.view.clone(); + test + .run_scripts(vec![ + DeleteView, + RestoreViewFromTrash, + ReadView(view.id.clone()), + AssertView(view), ]) .await; } #[tokio::test] async fn view_delete_all() { - let mut test = FolderTest::new().await; - let app = test.app.clone(); - test.run_scripts(vec![ - CreateView { - name: "View A".to_owned(), - desc: "View A description".to_owned(), - data_type: ViewDataFormatPB::DeltaFormat, - }, - CreateView { - name: "Grid".to_owned(), - desc: "Grid description".to_owned(), - data_type: ViewDataFormatPB::DatabaseFormat, - }, - ReadApp(app.id.clone()), + let mut test = FolderTest::new().await; + let app = test.app.clone(); + test + .run_scripts(vec![ + CreateView { + name: "View A".to_owned(), + desc: "View A description".to_owned(), + data_type: ViewDataFormatPB::DeltaFormat, + }, + CreateView { + name: "Grid".to_owned(), + desc: "Grid description".to_owned(), + data_type: ViewDataFormatPB::DatabaseFormat, + }, + ReadApp(app.id.clone()), ]) .await; - assert_eq!(test.app.belongings.len(), 3); - let view_ids = test - .app - .belongings - .iter() - .map(|view| view.id.clone()) - .collect::>(); - test.run_scripts(vec![DeleteViews(view_ids), ReadApp(app.id), ReadTrash]) - .await; + assert_eq!(test.app.belongings.len(), 3); + let view_ids = test + .app + .belongings + .iter() + .map(|view| view.id.clone()) + .collect::>(); + test + .run_scripts(vec![DeleteViews(view_ids), ReadApp(app.id), ReadTrash]) + .await; - assert_eq!(test.app.belongings.len(), 0); - assert_eq!(test.trash.len(), 3); + assert_eq!(test.app.belongings.len(), 0); + assert_eq!(test.trash.len(), 3); } #[tokio::test] async fn view_delete_all_permanent() { - let mut test = FolderTest::new().await; - let app = test.app.clone(); - test.run_scripts(vec![ - CreateView { - name: "View A".to_owned(), - desc: "View A description".to_owned(), - data_type: ViewDataFormatPB::DeltaFormat, - }, - ReadApp(app.id.clone()), + let mut test = FolderTest::new().await; + let app = test.app.clone(); + test + .run_scripts(vec![ + CreateView { + name: "View A".to_owned(), + desc: "View A description".to_owned(), + data_type: ViewDataFormatPB::DeltaFormat, + }, + ReadApp(app.id.clone()), ]) .await; - let view_ids = test - .app - .belongings - .iter() - .map(|view| view.id.clone()) - .collect::>(); - test.run_scripts(vec![DeleteViews(view_ids), ReadApp(app.id), DeleteAllTrash, ReadTrash]) - .await; + let view_ids = test + .app + .belongings + .iter() + .map(|view| view.id.clone()) + .collect::>(); + test + .run_scripts(vec![ + DeleteViews(view_ids), + ReadApp(app.id), + DeleteAllTrash, + ReadTrash, + ]) + .await; - assert_eq!(test.app.belongings.len(), 0); - assert_eq!(test.trash.len(), 0); + assert_eq!(test.app.belongings.len(), 0); + assert_eq!(test.trash.len(), 0); } #[tokio::test] async fn folder_sync_revision_state() { - let mut test = FolderTest::new().await; - test.run_scripts(vec![ - AssertRevisionState { - rev_id: 1, - state: RevisionState::Sync, - }, - AssertNextSyncRevId(Some(1)), - AssertRevisionState { - rev_id: 1, - state: RevisionState::Ack, - }, + let mut test = FolderTest::new().await; + test + .run_scripts(vec![ + AssertRevisionState { + rev_id: 1, + state: RevisionState::Sync, + }, + AssertNextSyncRevId(Some(1)), + AssertRevisionState { + rev_id: 1, + state: RevisionState::Ack, + }, ]) .await; } #[tokio::test] async fn folder_sync_revision_seq() { - let mut test = FolderTest::new().await; - test.run_scripts(vec![ - AssertRevisionState { - rev_id: 1, - state: RevisionState::Sync, - }, - AssertRevisionState { - rev_id: 2, - state: RevisionState::Sync, - }, - AssertNextSyncRevId(Some(1)), - AssertNextSyncRevId(Some(2)), - AssertRevisionState { - rev_id: 1, - state: RevisionState::Ack, - }, - AssertRevisionState { - rev_id: 2, - state: RevisionState::Ack, - }, + let mut test = FolderTest::new().await; + test + .run_scripts(vec![ + AssertRevisionState { + rev_id: 1, + state: RevisionState::Sync, + }, + AssertRevisionState { + rev_id: 2, + state: RevisionState::Sync, + }, + AssertNextSyncRevId(Some(1)), + AssertNextSyncRevId(Some(2)), + AssertRevisionState { + rev_id: 1, + state: RevisionState::Ack, + }, + AssertRevisionState { + rev_id: 2, + state: RevisionState::Ack, + }, ]) .await; } diff --git a/frontend/rust-lib/flowy-folder/tests/workspace/script.rs b/frontend/rust-lib/flowy-folder/tests/workspace/script.rs index 5cce7e4207..638c2d5516 100644 --- a/frontend/rust-lib/flowy-folder/tests/workspace/script.rs +++ b/frontend/rust-lib/flowy-folder/tests/workspace/script.rs @@ -1,17 +1,17 @@ use flowy_folder::entities::view::{RepeatedViewIdPB, ViewIdPB}; use flowy_folder::entities::workspace::WorkspaceIdPB; use flowy_folder::entities::{ - app::{AppIdPB, CreateAppPayloadPB, UpdateAppPayloadPB}, - trash::{RepeatedTrashPB, TrashIdPB, TrashType}, - view::{CreateViewPayloadPB, UpdateViewPayloadPB}, - workspace::{CreateWorkspacePayloadPB, RepeatedWorkspacePB}, - ViewLayoutTypePB, + app::{AppIdPB, CreateAppPayloadPB, UpdateAppPayloadPB}, + trash::{RepeatedTrashPB, TrashIdPB, TrashType}, + view::{CreateViewPayloadPB, UpdateViewPayloadPB}, + workspace::{CreateWorkspacePayloadPB, RepeatedWorkspacePB}, + ViewLayoutTypePB, }; use flowy_folder::entities::{ - app::{AppPB, RepeatedAppPB}, - trash::TrashPB, - view::{RepeatedViewPB, ViewDataFormatPB, ViewPB}, - workspace::WorkspacePB, + app::{AppPB, RepeatedAppPB}, + trash::TrashPB, + view::{RepeatedViewPB, ViewDataFormatPB, ViewPB}, + workspace::WorkspacePB, }; use flowy_folder::event_map::FolderEvent::*; use flowy_folder::{errors::ErrorCode, services::folder_editor::FolderEditor}; @@ -22,425 +22,449 @@ use std::{sync::Arc, time::Duration}; use tokio::time::sleep; pub enum FolderScript { - // Workspace - ReadAllWorkspaces, - CreateWorkspace { - name: String, - desc: String, - }, - // AssertWorkspaceRevisionJson(String), - AssertWorkspace(WorkspacePB), - ReadWorkspace(Option), + // Workspace + ReadAllWorkspaces, + CreateWorkspace { + name: String, + desc: String, + }, + // AssertWorkspaceRevisionJson(String), + AssertWorkspace(WorkspacePB), + ReadWorkspace(Option), - // App - CreateApp { - name: String, - desc: String, - }, - // AssertAppRevisionJson(String), - AssertApp(AppPB), - ReadApp(String), - UpdateApp { - name: Option, - desc: Option, - }, - DeleteApp, + // App + CreateApp { + name: String, + desc: String, + }, + // AssertAppRevisionJson(String), + AssertApp(AppPB), + ReadApp(String), + UpdateApp { + name: Option, + desc: Option, + }, + DeleteApp, - // View - CreateView { - name: String, - desc: String, - data_type: ViewDataFormatPB, - }, - AssertView(ViewPB), - ReadView(String), - UpdateView { - name: Option, - desc: Option, - }, - DeleteView, - DeleteViews(Vec), + // View + CreateView { + name: String, + desc: String, + data_type: ViewDataFormatPB, + }, + AssertView(ViewPB), + ReadView(String), + UpdateView { + name: Option, + desc: Option, + }, + DeleteView, + DeleteViews(Vec), - // Trash - RestoreAppFromTrash, - RestoreViewFromTrash, - ReadTrash, - DeleteAllTrash, + // Trash + RestoreAppFromTrash, + RestoreViewFromTrash, + ReadTrash, + DeleteAllTrash, - // Sync - #[allow(dead_code)] - AssertCurrentRevId(i64), - AssertNextSyncRevId(Option), - AssertRevisionState { - rev_id: i64, - state: RevisionState, - }, + // Sync + #[allow(dead_code)] + AssertCurrentRevId(i64), + AssertNextSyncRevId(Option), + AssertRevisionState { + rev_id: i64, + state: RevisionState, + }, } pub struct FolderTest { - pub sdk: FlowySDKTest, - pub all_workspace: Vec, - pub workspace: WorkspacePB, - pub app: AppPB, - pub view: ViewPB, - pub trash: Vec, - // pub folder_editor: + pub sdk: FlowySDKTest, + pub all_workspace: Vec, + pub workspace: WorkspacePB, + pub app: AppPB, + pub view: ViewPB, + pub trash: Vec, + // pub folder_editor: } impl FolderTest { - pub async fn new() -> Self { - let sdk = FlowySDKTest::default(); - let _ = sdk.init_user().await; - let mut workspace = create_workspace(&sdk, "FolderWorkspace", "Folder test workspace").await; - let mut app = create_app(&sdk, &workspace.id, "Folder App", "Folder test app").await; - let view = create_view( - &sdk, - &app.id, - "Folder View", - "Folder test view", - ViewDataFormatPB::DeltaFormat, - ViewLayoutTypePB::Document, - ) - .await; - app.belongings = RepeatedViewPB { - items: vec![view.clone()], + pub async fn new() -> Self { + let sdk = FlowySDKTest::default(); + let _ = sdk.init_user().await; + let mut workspace = create_workspace(&sdk, "FolderWorkspace", "Folder test workspace").await; + let mut app = create_app(&sdk, &workspace.id, "Folder App", "Folder test app").await; + let view = create_view( + &sdk, + &app.id, + "Folder View", + "Folder test view", + ViewDataFormatPB::DeltaFormat, + ViewLayoutTypePB::Document, + ) + .await; + app.belongings = RepeatedViewPB { + items: vec![view.clone()], + }; + + workspace.apps = RepeatedAppPB { + items: vec![app.clone()], + }; + Self { + sdk, + all_workspace: vec![], + workspace, + app, + view, + trash: vec![], + } + } + + pub async fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + pub async fn run_script(&mut self, script: FolderScript) { + let sdk = &self.sdk; + let folder_editor: Arc = sdk.folder_manager.folder_editor().await; + let rev_manager = folder_editor.rev_manager(); + let cache = rev_manager.revision_cache().await; + + match script { + FolderScript::ReadAllWorkspaces => { + let all_workspace = read_workspace(sdk, None).await; + self.all_workspace = all_workspace; + }, + FolderScript::CreateWorkspace { name, desc } => { + let workspace = create_workspace(sdk, &name, &desc).await; + self.workspace = workspace; + }, + // FolderScript::AssertWorkspaceRevisionJson(expected_json) => { + // let workspace = read_workspace(sdk, Some(self.workspace.id.clone())) + // .await + // .pop() + // .unwrap(); + // let workspace_revision: WorkspaceRevision = workspace.into(); + // let json = serde_json::to_string(&workspace_revision).unwrap(); + // assert_eq!(json, expected_json); + // } + FolderScript::AssertWorkspace(workspace) => { + assert_eq!(self.workspace, workspace, "Workspace not equal"); + }, + FolderScript::ReadWorkspace(workspace_id) => { + let workspace = read_workspace(sdk, workspace_id).await.pop().unwrap(); + self.workspace = workspace; + }, + FolderScript::CreateApp { name, desc } => { + let app = create_app(sdk, &self.workspace.id, &name, &desc).await; + self.app = app; + }, + // FolderScript::AssertAppRevisionJson(expected_json) => { + // let app_revision: AppRevision = self.app.clone().into(); + // let json = serde_json::to_string(&app_revision).unwrap(); + // assert_eq!(json, expected_json); + // } + FolderScript::AssertApp(app) => { + assert_eq!(self.app, app, "App not equal"); + }, + FolderScript::ReadApp(app_id) => { + let app = read_app(sdk, &app_id).await; + self.app = app; + }, + FolderScript::UpdateApp { name, desc } => { + update_app(sdk, &self.app.id, name, desc).await; + }, + FolderScript::DeleteApp => { + delete_app(sdk, &self.app.id).await; + }, + + FolderScript::CreateView { + name, + desc, + data_type, + } => { + let layout = match data_type { + ViewDataFormatPB::DeltaFormat => ViewLayoutTypePB::Document, + ViewDataFormatPB::NodeFormat => ViewLayoutTypePB::Document, + ViewDataFormatPB::DatabaseFormat => ViewLayoutTypePB::Grid, }; - - workspace.apps = RepeatedAppPB { - items: vec![app.clone()], - }; - Self { - sdk, - all_workspace: vec![], - workspace, - app, - view, - trash: vec![], + let view = create_view(sdk, &self.app.id, &name, &desc, data_type, layout).await; + self.view = view; + }, + FolderScript::AssertView(view) => { + assert_eq!(self.view, view, "View not equal"); + }, + FolderScript::ReadView(view_id) => { + let view = read_view(sdk, &view_id).await; + self.view = view; + }, + FolderScript::UpdateView { name, desc } => { + update_view(sdk, &self.view.id, name, desc).await; + }, + FolderScript::DeleteView => { + delete_view(sdk, vec![self.view.id.clone()]).await; + }, + FolderScript::DeleteViews(view_ids) => { + delete_view(sdk, view_ids).await; + }, + FolderScript::RestoreAppFromTrash => { + restore_app_from_trash(sdk, &self.app.id).await; + }, + FolderScript::RestoreViewFromTrash => { + restore_view_from_trash(sdk, &self.view.id).await; + }, + FolderScript::ReadTrash => { + let mut trash = read_trash(sdk).await; + self.trash = trash.into_inner(); + }, + FolderScript::DeleteAllTrash => { + delete_all_trash(sdk).await; + self.trash = vec![]; + }, + FolderScript::AssertRevisionState { rev_id, state } => { + let record = cache.get(rev_id).await.unwrap(); + assert_eq!(record.state, state, "Revision state is not match"); + if let RevisionState::Ack = state { + // There is a defer action that writes the revisions to disk, so we wait here. + // Make sure everything is written. + sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await; } - } - - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - pub async fn run_script(&mut self, script: FolderScript) { - let sdk = &self.sdk; - let folder_editor: Arc = sdk.folder_manager.folder_editor().await; - let rev_manager = folder_editor.rev_manager(); - let cache = rev_manager.revision_cache().await; - - match script { - FolderScript::ReadAllWorkspaces => { - let all_workspace = read_workspace(sdk, None).await; - self.all_workspace = all_workspace; - } - FolderScript::CreateWorkspace { name, desc } => { - let workspace = create_workspace(sdk, &name, &desc).await; - self.workspace = workspace; - } - // FolderScript::AssertWorkspaceRevisionJson(expected_json) => { - // let workspace = read_workspace(sdk, Some(self.workspace.id.clone())) - // .await - // .pop() - // .unwrap(); - // let workspace_revision: WorkspaceRevision = workspace.into(); - // let json = serde_json::to_string(&workspace_revision).unwrap(); - // assert_eq!(json, expected_json); - // } - FolderScript::AssertWorkspace(workspace) => { - assert_eq!(self.workspace, workspace, "Workspace not equal"); - } - FolderScript::ReadWorkspace(workspace_id) => { - let workspace = read_workspace(sdk, workspace_id).await.pop().unwrap(); - self.workspace = workspace; - } - FolderScript::CreateApp { name, desc } => { - let app = create_app(sdk, &self.workspace.id, &name, &desc).await; - self.app = app; - } - // FolderScript::AssertAppRevisionJson(expected_json) => { - // let app_revision: AppRevision = self.app.clone().into(); - // let json = serde_json::to_string(&app_revision).unwrap(); - // assert_eq!(json, expected_json); - // } - FolderScript::AssertApp(app) => { - assert_eq!(self.app, app, "App not equal"); - } - FolderScript::ReadApp(app_id) => { - let app = read_app(sdk, &app_id).await; - self.app = app; - } - FolderScript::UpdateApp { name, desc } => { - update_app(sdk, &self.app.id, name, desc).await; - } - FolderScript::DeleteApp => { - delete_app(sdk, &self.app.id).await; - } - - FolderScript::CreateView { name, desc, data_type } => { - let layout = match data_type { - ViewDataFormatPB::DeltaFormat => ViewLayoutTypePB::Document, - ViewDataFormatPB::NodeFormat => ViewLayoutTypePB::Document, - ViewDataFormatPB::DatabaseFormat => ViewLayoutTypePB::Grid, - }; - let view = create_view(sdk, &self.app.id, &name, &desc, data_type, layout).await; - self.view = view; - } - FolderScript::AssertView(view) => { - assert_eq!(self.view, view, "View not equal"); - } - FolderScript::ReadView(view_id) => { - let view = read_view(sdk, &view_id).await; - self.view = view; - } - FolderScript::UpdateView { name, desc } => { - update_view(sdk, &self.view.id, name, desc).await; - } - FolderScript::DeleteView => { - delete_view(sdk, vec![self.view.id.clone()]).await; - } - FolderScript::DeleteViews(view_ids) => { - delete_view(sdk, view_ids).await; - } - FolderScript::RestoreAppFromTrash => { - restore_app_from_trash(sdk, &self.app.id).await; - } - FolderScript::RestoreViewFromTrash => { - restore_view_from_trash(sdk, &self.view.id).await; - } - FolderScript::ReadTrash => { - let mut trash = read_trash(sdk).await; - self.trash = trash.into_inner(); - } - FolderScript::DeleteAllTrash => { - delete_all_trash(sdk).await; - self.trash = vec![]; - } - FolderScript::AssertRevisionState { rev_id, state } => { - let record = cache.get(rev_id).await.unwrap(); - assert_eq!(record.state, state, "Revision state is not match"); - if let RevisionState::Ack = state { - // There is a defer action that writes the revisions to disk, so we wait here. - // Make sure everything is written. - sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await; - } - } - FolderScript::AssertCurrentRevId(rev_id) => { - assert_eq!(rev_manager.rev_id(), rev_id, "Current rev_id is not match"); - } - FolderScript::AssertNextSyncRevId(rev_id) => { - let next_revision = rev_manager.next_sync_revision().await.unwrap(); - if rev_id.is_none() { - assert!(next_revision.is_none(), "Next revision should be None"); - return; - } - let next_revision = next_revision - .unwrap_or_else(|| panic!("Expected Next revision is {}, but receive None", rev_id.unwrap())); - let mut notify = rev_manager.ack_notify(); - let _ = notify.recv().await; - assert_eq!(next_revision.rev_id, rev_id.unwrap(), "Revision id not match"); - } + }, + FolderScript::AssertCurrentRevId(rev_id) => { + assert_eq!(rev_manager.rev_id(), rev_id, "Current rev_id is not match"); + }, + FolderScript::AssertNextSyncRevId(rev_id) => { + let next_revision = rev_manager.next_sync_revision().await.unwrap(); + if rev_id.is_none() { + assert!(next_revision.is_none(), "Next revision should be None"); + return; } + let next_revision = next_revision.unwrap_or_else(|| { + panic!( + "Expected Next revision is {}, but receive None", + rev_id.unwrap() + ) + }); + let mut notify = rev_manager.ack_notify(); + let _ = notify.recv().await; + assert_eq!( + next_revision.rev_id, + rev_id.unwrap(), + "Revision id not match" + ); + }, } + } } pub fn invalid_workspace_name_test_case() -> Vec<(String, ErrorCode)> { - vec![ - ("".to_owned(), ErrorCode::WorkspaceNameInvalid), - ("1234".repeat(100), ErrorCode::WorkspaceNameTooLong), - ] + vec![ + ("".to_owned(), ErrorCode::WorkspaceNameInvalid), + ("1234".repeat(100), ErrorCode::WorkspaceNameTooLong), + ] } pub async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> WorkspacePB { - let request = CreateWorkspacePayloadPB { - name: name.to_owned(), - desc: desc.to_owned(), - }; + let request = CreateWorkspacePayloadPB { + name: name.to_owned(), + desc: desc.to_owned(), + }; - FolderEventBuilder::new(sdk.clone()) - .event(CreateWorkspace) - .payload(request) - .async_send() - .await - .parse::() + FolderEventBuilder::new(sdk.clone()) + .event(CreateWorkspace) + .payload(request) + .async_send() + .await + .parse::() } pub async fn read_workspace(sdk: &FlowySDKTest, workspace_id: Option) -> Vec { - let request = WorkspaceIdPB { value: workspace_id }; - let mut repeated_workspace = FolderEventBuilder::new(sdk.clone()) - .event(ReadWorkspaces) - .payload(request.clone()) - .async_send() - .await - .parse::(); + let request = WorkspaceIdPB { + value: workspace_id, + }; + let mut repeated_workspace = FolderEventBuilder::new(sdk.clone()) + .event(ReadWorkspaces) + .payload(request.clone()) + .async_send() + .await + .parse::(); - let workspaces; - if let Some(workspace_id) = &request.value { - workspaces = repeated_workspace - .into_inner() - .into_iter() - .filter(|workspace| &workspace.id == workspace_id) - .collect::>(); - debug_assert_eq!(workspaces.len(), 1); - } else { - workspaces = repeated_workspace.items; - } + let workspaces; + if let Some(workspace_id) = &request.value { + workspaces = repeated_workspace + .into_inner() + .into_iter() + .filter(|workspace| &workspace.id == workspace_id) + .collect::>(); + debug_assert_eq!(workspaces.len(), 1); + } else { + workspaces = repeated_workspace.items; + } - workspaces + workspaces } pub async fn create_app(sdk: &FlowySDKTest, workspace_id: &str, name: &str, desc: &str) -> AppPB { - let create_app_request = CreateAppPayloadPB { - workspace_id: workspace_id.to_owned(), - name: name.to_string(), - desc: desc.to_string(), - color_style: Default::default(), - }; + let create_app_request = CreateAppPayloadPB { + workspace_id: workspace_id.to_owned(), + name: name.to_string(), + desc: desc.to_string(), + color_style: Default::default(), + }; - FolderEventBuilder::new(sdk.clone()) - .event(CreateApp) - .payload(create_app_request) - .async_send() - .await - .parse::() + FolderEventBuilder::new(sdk.clone()) + .event(CreateApp) + .payload(create_app_request) + .async_send() + .await + .parse::() } pub async fn read_app(sdk: &FlowySDKTest, app_id: &str) -> AppPB { - let request = AppIdPB { - value: app_id.to_owned(), - }; + let request = AppIdPB { + value: app_id.to_owned(), + }; - FolderEventBuilder::new(sdk.clone()) - .event(ReadApp) - .payload(request) - .async_send() - .await - .parse::() + FolderEventBuilder::new(sdk.clone()) + .event(ReadApp) + .payload(request) + .async_send() + .await + .parse::() } -pub async fn update_app(sdk: &FlowySDKTest, app_id: &str, name: Option, desc: Option) { - let request = UpdateAppPayloadPB { - app_id: app_id.to_string(), - name, - desc, - color_style: None, - is_trash: None, - }; +pub async fn update_app( + sdk: &FlowySDKTest, + app_id: &str, + name: Option, + desc: Option, +) { + let request = UpdateAppPayloadPB { + app_id: app_id.to_string(), + name, + desc, + color_style: None, + is_trash: None, + }; - FolderEventBuilder::new(sdk.clone()) - .event(UpdateApp) - .payload(request) - .async_send() - .await; + FolderEventBuilder::new(sdk.clone()) + .event(UpdateApp) + .payload(request) + .async_send() + .await; } pub async fn delete_app(sdk: &FlowySDKTest, app_id: &str) { - let request = AppIdPB { - value: app_id.to_string(), - }; + let request = AppIdPB { + value: app_id.to_string(), + }; - FolderEventBuilder::new(sdk.clone()) - .event(DeleteApp) - .payload(request) - .async_send() - .await; + FolderEventBuilder::new(sdk.clone()) + .event(DeleteApp) + .payload(request) + .async_send() + .await; } pub async fn create_view( - sdk: &FlowySDKTest, - app_id: &str, - name: &str, - desc: &str, - data_type: ViewDataFormatPB, - layout: ViewLayoutTypePB, + sdk: &FlowySDKTest, + app_id: &str, + name: &str, + desc: &str, + data_type: ViewDataFormatPB, + layout: ViewLayoutTypePB, ) -> ViewPB { - let request = CreateViewPayloadPB { - belong_to_id: app_id.to_string(), - name: name.to_string(), - desc: desc.to_string(), - thumbnail: None, - data_format: data_type, - layout, - initial_data: vec![], - }; - FolderEventBuilder::new(sdk.clone()) - .event(CreateView) - .payload(request) - .async_send() - .await - .parse::() + let request = CreateViewPayloadPB { + belong_to_id: app_id.to_string(), + name: name.to_string(), + desc: desc.to_string(), + thumbnail: None, + data_format: data_type, + layout, + initial_data: vec![], + }; + FolderEventBuilder::new(sdk.clone()) + .event(CreateView) + .payload(request) + .async_send() + .await + .parse::() } pub async fn read_view(sdk: &FlowySDKTest, view_id: &str) -> ViewPB { - let view_id: ViewIdPB = view_id.into(); - FolderEventBuilder::new(sdk.clone()) - .event(ReadView) - .payload(view_id) - .async_send() - .await - .parse::() + let view_id: ViewIdPB = view_id.into(); + FolderEventBuilder::new(sdk.clone()) + .event(ReadView) + .payload(view_id) + .async_send() + .await + .parse::() } -pub async fn update_view(sdk: &FlowySDKTest, view_id: &str, name: Option, desc: Option) { - let request = UpdateViewPayloadPB { - view_id: view_id.to_string(), - name, - desc, - thumbnail: None, - }; - FolderEventBuilder::new(sdk.clone()) - .event(UpdateView) - .payload(request) - .async_send() - .await; +pub async fn update_view( + sdk: &FlowySDKTest, + view_id: &str, + name: Option, + desc: Option, +) { + let request = UpdateViewPayloadPB { + view_id: view_id.to_string(), + name, + desc, + thumbnail: None, + }; + FolderEventBuilder::new(sdk.clone()) + .event(UpdateView) + .payload(request) + .async_send() + .await; } pub async fn delete_view(sdk: &FlowySDKTest, view_ids: Vec) { - let request = RepeatedViewIdPB { items: view_ids }; - FolderEventBuilder::new(sdk.clone()) - .event(DeleteView) - .payload(request) - .async_send() - .await; + let request = RepeatedViewIdPB { items: view_ids }; + FolderEventBuilder::new(sdk.clone()) + .event(DeleteView) + .payload(request) + .async_send() + .await; } pub async fn read_trash(sdk: &FlowySDKTest) -> RepeatedTrashPB { - FolderEventBuilder::new(sdk.clone()) - .event(ReadTrash) - .async_send() - .await - .parse::() + FolderEventBuilder::new(sdk.clone()) + .event(ReadTrash) + .async_send() + .await + .parse::() } pub async fn restore_app_from_trash(sdk: &FlowySDKTest, app_id: &str) { - let id = TrashIdPB { - id: app_id.to_owned(), - ty: TrashType::TrashApp, - }; - FolderEventBuilder::new(sdk.clone()) - .event(PutbackTrash) - .payload(id) - .async_send() - .await; + let id = TrashIdPB { + id: app_id.to_owned(), + ty: TrashType::TrashApp, + }; + FolderEventBuilder::new(sdk.clone()) + .event(PutbackTrash) + .payload(id) + .async_send() + .await; } pub async fn restore_view_from_trash(sdk: &FlowySDKTest, view_id: &str) { - let id = TrashIdPB { - id: view_id.to_owned(), - ty: TrashType::TrashView, - }; - FolderEventBuilder::new(sdk.clone()) - .event(PutbackTrash) - .payload(id) - .async_send() - .await; + let id = TrashIdPB { + id: view_id.to_owned(), + ty: TrashType::TrashView, + }; + FolderEventBuilder::new(sdk.clone()) + .event(PutbackTrash) + .payload(id) + .async_send() + .await; } pub async fn delete_all_trash(sdk: &FlowySDKTest) { - FolderEventBuilder::new(sdk.clone()) - .event(DeleteAllTrash) - .async_send() - .await; + FolderEventBuilder::new(sdk.clone()) + .event(DeleteAllTrash) + .async_send() + .await; } diff --git a/frontend/rust-lib/flowy-net/build.rs b/frontend/rust-lib/flowy-net/build.rs index 508b370b87..06388d2a02 100644 --- a/frontend/rust-lib/flowy-net/build.rs +++ b/frontend/rust-lib/flowy-net/build.rs @@ -1,10 +1,10 @@ fn main() { - let crate_name = env!("CARGO_PKG_NAME"); - flowy_codegen::protobuf_file::gen(crate_name); + let crate_name = env!("CARGO_PKG_NAME"); + flowy_codegen::protobuf_file::gen(crate_name); - #[cfg(feature = "dart")] - flowy_codegen::dart_event::gen(crate_name); + #[cfg(feature = "dart")] + flowy_codegen::dart_event::gen(crate_name); - #[cfg(feature = "ts")] - flowy_codegen::ts_event::gen(crate_name); + #[cfg(feature = "ts")] + flowy_codegen::ts_event::gen(crate_name); } diff --git a/frontend/rust-lib/flowy-net/src/entities/network_state.rs b/frontend/rust-lib/flowy-net/src/entities/network_state.rs index 868417e089..34ea01c511 100644 --- a/frontend/rust-lib/flowy-net/src/entities/network_state.rs +++ b/frontend/rust-lib/flowy-net/src/entities/network_state.rs @@ -3,44 +3,46 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; #[derive(ProtoBuf_Enum, Debug, Clone, Eq, PartialEq)] pub enum NetworkTypePB { - Unknown = 0, - Wifi = 1, - Cell = 2, - Ethernet = 3, - Bluetooth = 4, - VPN = 5, + Unknown = 0, + Wifi = 1, + Cell = 2, + Ethernet = 3, + Bluetooth = 4, + VPN = 5, } impl NetworkTypePB { - pub fn is_connect(&self) -> bool { - match self { - NetworkTypePB::Unknown | NetworkTypePB::Bluetooth => false, - NetworkTypePB::Wifi | NetworkTypePB::Cell | NetworkTypePB::Ethernet | NetworkTypePB::VPN => true, - } + pub fn is_connect(&self) -> bool { + match self { + NetworkTypePB::Unknown | NetworkTypePB::Bluetooth => false, + NetworkTypePB::Wifi | NetworkTypePB::Cell | NetworkTypePB::Ethernet | NetworkTypePB::VPN => { + true + }, } + } } impl std::default::Default for NetworkTypePB { - fn default() -> Self { - NetworkTypePB::Unknown - } + fn default() -> Self { + NetworkTypePB::Unknown + } } impl std::convert::From for NetworkType { - fn from(ty: NetworkTypePB) -> Self { - match ty { - NetworkTypePB::Unknown => NetworkType::Unknown, - NetworkTypePB::Wifi => NetworkType::Wifi, - NetworkTypePB::Cell => NetworkType::Cell, - NetworkTypePB::Ethernet => NetworkType::Ethernet, - NetworkTypePB::Bluetooth => NetworkType::Bluetooth, - NetworkTypePB::VPN => NetworkType::VPN, - } + fn from(ty: NetworkTypePB) -> Self { + match ty { + NetworkTypePB::Unknown => NetworkType::Unknown, + NetworkTypePB::Wifi => NetworkType::Wifi, + NetworkTypePB::Cell => NetworkType::Cell, + NetworkTypePB::Ethernet => NetworkType::Ethernet, + NetworkTypePB::Bluetooth => NetworkType::Bluetooth, + NetworkTypePB::VPN => NetworkType::VPN, } + } } #[derive(ProtoBuf, Debug, Default, Clone)] pub struct NetworkStatePB { - #[pb(index = 1)] - pub ty: NetworkTypePB, + #[pb(index = 1)] + pub ty: NetworkTypePB, } diff --git a/frontend/rust-lib/flowy-net/src/event_map.rs b/frontend/rust-lib/flowy-net/src/event_map.rs index fc6155ae76..1d314d1497 100644 --- a/frontend/rust-lib/flowy-net/src/event_map.rs +++ b/frontend/rust-lib/flowy-net/src/event_map.rs @@ -6,15 +6,15 @@ use std::sync::Arc; use strum_macros::Display; pub fn init(ws_conn: Arc) -> AFPlugin { - AFPlugin::new() - .name("Flowy-Network") - .state(ws_conn) - .event(NetworkEvent::UpdateNetworkType, update_network_ty) + AFPlugin::new() + .name("Flowy-Network") + .state(ws_conn) + .event(NetworkEvent::UpdateNetworkType, update_network_ty) } #[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)] #[event_err = "FlowyError"] pub enum NetworkEvent { - #[event(input = "NetworkStatePB")] - UpdateNetworkType = 0, + #[event(input = "NetworkStatePB")] + UpdateNetworkType = 0, } diff --git a/frontend/rust-lib/flowy-net/src/handlers/mod.rs b/frontend/rust-lib/flowy-net/src/handlers/mod.rs index c4d279e4b0..05c0d788c6 100644 --- a/frontend/rust-lib/flowy-net/src/handlers/mod.rs +++ b/frontend/rust-lib/flowy-net/src/handlers/mod.rs @@ -6,10 +6,10 @@ use std::sync::Arc; #[tracing::instrument(level = "debug", skip(data, ws_manager))] pub async fn update_network_ty( - data: AFPluginData, - ws_manager: AFPluginState>, + data: AFPluginData, + ws_manager: AFPluginState>, ) -> Result<(), FlowyError> { - let network_type: NetworkType = data.into_inner().ty.into(); - ws_manager.update_network_type(network_type); - Ok(()) + let network_type: NetworkType = data.into_inner().ty.into(); + ws_manager.update_network_type(network_type); + Ok(()) } diff --git a/frontend/rust-lib/flowy-net/src/http_server/document.rs b/frontend/rust-lib/flowy-net/src/http_server/document.rs index a8f9cacf55..63c27ebbb4 100644 --- a/frontend/rust-lib/flowy-net/src/http_server/document.rs +++ b/frontend/rust-lib/flowy-net/src/http_server/document.rs @@ -1,6 +1,8 @@ use crate::request::{HttpRequestBuilder, ResponseMiddleware}; use crate::response::HttpResponse; -use document_model::document::{CreateDocumentParams, DocumentId, DocumentInfo, ResetDocumentParams}; +use document_model::document::{ + CreateDocumentParams, DocumentId, DocumentInfo, ResetDocumentParams, +}; use flowy_client_network_config::{ClientServerConfiguration, HEADER_TOKEN}; use flowy_document::DocumentCloudService; use flowy_error::FlowyError; @@ -9,96 +11,117 @@ use lib_infra::future::FutureResult; use std::sync::Arc; pub struct DocumentCloudServiceImpl { - config: ClientServerConfiguration, + config: ClientServerConfiguration, } impl DocumentCloudServiceImpl { - pub fn new(config: ClientServerConfiguration) -> Self { - Self { config } - } + pub fn new(config: ClientServerConfiguration) -> Self { + Self { config } + } } impl DocumentCloudService for DocumentCloudServiceImpl { - fn create_document(&self, token: &str, params: CreateDocumentParams) -> FutureResult<(), FlowyError> { - let token = token.to_owned(); - let url = self.config.doc_url(); - FutureResult::new(async move { create_document_request(&token, params, &url).await }) - } + fn create_document( + &self, + token: &str, + params: CreateDocumentParams, + ) -> FutureResult<(), FlowyError> { + let token = token.to_owned(); + let url = self.config.doc_url(); + FutureResult::new(async move { create_document_request(&token, params, &url).await }) + } - fn fetch_document(&self, token: &str, params: DocumentId) -> FutureResult, FlowyError> { - let token = token.to_owned(); - let url = self.config.doc_url(); - FutureResult::new(async move { read_document_request(&token, params, &url).await }) - } + fn fetch_document( + &self, + token: &str, + params: DocumentId, + ) -> FutureResult, FlowyError> { + let token = token.to_owned(); + let url = self.config.doc_url(); + FutureResult::new(async move { read_document_request(&token, params, &url).await }) + } - fn update_document_content(&self, token: &str, params: ResetDocumentParams) -> FutureResult<(), FlowyError> { - let token = token.to_owned(); - let url = self.config.doc_url(); - FutureResult::new(async move { reset_doc_request(&token, params, &url).await }) - } + fn update_document_content( + &self, + token: &str, + params: ResetDocumentParams, + ) -> FutureResult<(), FlowyError> { + let token = token.to_owned(); + let url = self.config.doc_url(); + FutureResult::new(async move { reset_doc_request(&token, params, &url).await }) + } } -pub async fn create_document_request(token: &str, params: CreateDocumentParams, url: &str) -> Result<(), FlowyError> { - request_builder() - .post(url) - .header(HEADER_TOKEN, token) - .json(params)? - .send() - .await?; - Ok(()) +pub async fn create_document_request( + token: &str, + params: CreateDocumentParams, + url: &str, +) -> Result<(), FlowyError> { + request_builder() + .post(url) + .header(HEADER_TOKEN, token) + .json(params)? + .send() + .await?; + Ok(()) } pub async fn read_document_request( - token: &str, - params: DocumentId, - url: &str, + token: &str, + params: DocumentId, + url: &str, ) -> Result, FlowyError> { - let doc = request_builder() - .get(url) - .header(HEADER_TOKEN, token) - .json(params)? - .option_json_response() - .await?; + let doc = request_builder() + .get(url) + .header(HEADER_TOKEN, token) + .json(params)? + .option_json_response() + .await?; - Ok(doc) + Ok(doc) } -pub async fn reset_doc_request(token: &str, params: ResetDocumentParams, url: &str) -> Result<(), FlowyError> { - request_builder() - .patch(url) - .header(HEADER_TOKEN, token) - .json(params)? - .send() - .await?; - Ok(()) +pub async fn reset_doc_request( + token: &str, + params: ResetDocumentParams, + url: &str, +) -> Result<(), FlowyError> { + request_builder() + .patch(url) + .header(HEADER_TOKEN, token) + .json(params)? + .send() + .await?; + Ok(()) } fn request_builder() -> HttpRequestBuilder { - HttpRequestBuilder::new().middleware(MIDDLEWARE.clone()) + HttpRequestBuilder::new().middleware(MIDDLEWARE.clone()) } lazy_static! { - pub(crate) static ref MIDDLEWARE: Arc = Arc::new(DocumentResponseMiddleware {}); + pub(crate) static ref MIDDLEWARE: Arc = + Arc::new(DocumentResponseMiddleware {}); } pub(crate) struct DocumentResponseMiddleware {} impl ResponseMiddleware for DocumentResponseMiddleware { - fn receive_response(&self, token: &Option, response: &HttpResponse) { - if let Some(error) = &response.error { - if error.is_unauthorized() { - tracing::error!("document user is unauthorized"); + fn receive_response(&self, token: &Option, response: &HttpResponse) { + if let Some(error) = &response.error { + if error.is_unauthorized() { + tracing::error!("document user is unauthorized"); - match token { - None => {} - Some(_token) => { - // let error = - // FlowyError::new(ErrorCode::UserUnauthorized, ""); - // observable(token, - // WorkspaceObservable::UserUnauthorized).error(error). - // build() - } - } - } + match token { + None => {}, + Some(_token) => { + // let error = + // FlowyError::new(ErrorCode::UserUnauthorized, ""); + // observable(token, + // WorkspaceObservable::UserUnauthorized).error(error). + // build() + }, } + } } + } } diff --git a/frontend/rust-lib/flowy-net/src/http_server/folder.rs b/frontend/rust-lib/flowy-net/src/http_server/folder.rs index 9e6862ef38..4694136444 100644 --- a/frontend/rust-lib/flowy-net/src/http_server/folder.rs +++ b/frontend/rust-lib/flowy-net/src/http_server/folder.rs @@ -3,10 +3,10 @@ use crate::response::HttpResponse; use flowy_client_network_config::ClientServerConfiguration; use flowy_error::FlowyError; use flowy_folder::entities::{ - trash::RepeatedTrashIdPB, - view::{CreateViewParams, RepeatedViewIdPB, UpdateViewParams, ViewIdPB}, - workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceIdPB}, - {AppIdPB, CreateAppParams, UpdateAppParams}, + trash::RepeatedTrashIdPB, + view::{CreateViewParams, RepeatedViewIdPB, UpdateViewParams, ViewIdPB}, + workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceIdPB}, + {AppIdPB, CreateAppParams, UpdateAppParams}, }; use flowy_folder::event_map::FolderCouldServiceV1; use folder_model::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; @@ -16,379 +16,442 @@ use std::sync::Arc; use tokio::sync::broadcast; pub struct FolderHttpCloudService { - config: ClientServerConfiguration, + config: ClientServerConfiguration, } impl FolderHttpCloudService { - pub fn new(config: ClientServerConfiguration) -> FolderHttpCloudService { - Self { config } - } + pub fn new(config: ClientServerConfiguration) -> FolderHttpCloudService { + Self { config } + } } impl FolderCouldServiceV1 for FolderHttpCloudService { - fn init(&self) {} + fn init(&self) {} - fn create_workspace( - &self, - token: &str, - params: CreateWorkspaceParams, - ) -> FutureResult { - let token = token.to_owned(); - let url = self.config.workspace_url(); - FutureResult::new(async move { - let workspace = create_workspace_request(&token, params, &url).await?; - Ok(workspace) - }) - } + fn create_workspace( + &self, + token: &str, + params: CreateWorkspaceParams, + ) -> FutureResult { + let token = token.to_owned(); + let url = self.config.workspace_url(); + FutureResult::new(async move { + let workspace = create_workspace_request(&token, params, &url).await?; + Ok(workspace) + }) + } - fn read_workspace(&self, token: &str, params: WorkspaceIdPB) -> FutureResult, FlowyError> { - let token = token.to_owned(); - let url = self.config.workspace_url(); - FutureResult::new(async move { - let workspace_revs = read_workspaces_request(&token, params, &url).await?; - Ok(workspace_revs) - }) - } + fn read_workspace( + &self, + token: &str, + params: WorkspaceIdPB, + ) -> FutureResult, FlowyError> { + let token = token.to_owned(); + let url = self.config.workspace_url(); + FutureResult::new(async move { + let workspace_revs = read_workspaces_request(&token, params, &url).await?; + Ok(workspace_revs) + }) + } - fn update_workspace(&self, token: &str, params: UpdateWorkspaceParams) -> FutureResult<(), FlowyError> { - let token = token.to_owned(); - let url = self.config.workspace_url(); - FutureResult::new(async move { - update_workspace_request(&token, params, &url).await?; - Ok(()) - }) - } + fn update_workspace( + &self, + token: &str, + params: UpdateWorkspaceParams, + ) -> FutureResult<(), FlowyError> { + let token = token.to_owned(); + let url = self.config.workspace_url(); + FutureResult::new(async move { + update_workspace_request(&token, params, &url).await?; + Ok(()) + }) + } - fn delete_workspace(&self, token: &str, params: WorkspaceIdPB) -> FutureResult<(), FlowyError> { - let token = token.to_owned(); - let url = self.config.workspace_url(); - FutureResult::new(async move { - delete_workspace_request(&token, params, &url).await?; - Ok(()) - }) - } + fn delete_workspace(&self, token: &str, params: WorkspaceIdPB) -> FutureResult<(), FlowyError> { + let token = token.to_owned(); + let url = self.config.workspace_url(); + FutureResult::new(async move { + delete_workspace_request(&token, params, &url).await?; + Ok(()) + }) + } - fn create_view(&self, token: &str, params: CreateViewParams) -> FutureResult { - let token = token.to_owned(); - let url = self.config.view_url(); - FutureResult::new(async move { - let view = create_view_request(&token, params, &url).await?; - Ok(view) - }) - } + fn create_view( + &self, + token: &str, + params: CreateViewParams, + ) -> FutureResult { + let token = token.to_owned(); + let url = self.config.view_url(); + FutureResult::new(async move { + let view = create_view_request(&token, params, &url).await?; + Ok(view) + }) + } - fn read_view(&self, token: &str, params: ViewIdPB) -> FutureResult, FlowyError> { - let token = token.to_owned(); - let url = self.config.view_url(); - FutureResult::new(async move { - let view_rev = read_view_request(&token, params, &url).await?; - Ok(view_rev) - }) - } + fn read_view( + &self, + token: &str, + params: ViewIdPB, + ) -> FutureResult, FlowyError> { + let token = token.to_owned(); + let url = self.config.view_url(); + FutureResult::new(async move { + let view_rev = read_view_request(&token, params, &url).await?; + Ok(view_rev) + }) + } - fn delete_view(&self, token: &str, params: RepeatedViewIdPB) -> FutureResult<(), FlowyError> { - let token = token.to_owned(); - let url = self.config.view_url(); - FutureResult::new(async move { - delete_view_request(&token, params, &url).await?; - Ok(()) - }) - } + fn delete_view(&self, token: &str, params: RepeatedViewIdPB) -> FutureResult<(), FlowyError> { + let token = token.to_owned(); + let url = self.config.view_url(); + FutureResult::new(async move { + delete_view_request(&token, params, &url).await?; + Ok(()) + }) + } - fn update_view(&self, token: &str, params: UpdateViewParams) -> FutureResult<(), FlowyError> { - let token = token.to_owned(); - let url = self.config.view_url(); - FutureResult::new(async move { - update_view_request(&token, params, &url).await?; - Ok(()) - }) - } + fn update_view(&self, token: &str, params: UpdateViewParams) -> FutureResult<(), FlowyError> { + let token = token.to_owned(); + let url = self.config.view_url(); + FutureResult::new(async move { + update_view_request(&token, params, &url).await?; + Ok(()) + }) + } - fn create_app(&self, token: &str, params: CreateAppParams) -> FutureResult { - let token = token.to_owned(); - let url = self.config.app_url(); - FutureResult::new(async move { - let app = create_app_request(&token, params, &url).await?; - Ok(app) - }) - } + fn create_app( + &self, + token: &str, + params: CreateAppParams, + ) -> FutureResult { + let token = token.to_owned(); + let url = self.config.app_url(); + FutureResult::new(async move { + let app = create_app_request(&token, params, &url).await?; + Ok(app) + }) + } - fn read_app(&self, token: &str, params: AppIdPB) -> FutureResult, FlowyError> { - let token = token.to_owned(); - let url = self.config.app_url(); - FutureResult::new(async move { - let app_rev = read_app_request(&token, params, &url).await?; - Ok(app_rev) - }) - } + fn read_app( + &self, + token: &str, + params: AppIdPB, + ) -> FutureResult, FlowyError> { + let token = token.to_owned(); + let url = self.config.app_url(); + FutureResult::new(async move { + let app_rev = read_app_request(&token, params, &url).await?; + Ok(app_rev) + }) + } - fn update_app(&self, token: &str, params: UpdateAppParams) -> FutureResult<(), FlowyError> { - let token = token.to_owned(); - let url = self.config.app_url(); - FutureResult::new(async move { - update_app_request(&token, params, &url).await?; - Ok(()) - }) - } + fn update_app(&self, token: &str, params: UpdateAppParams) -> FutureResult<(), FlowyError> { + let token = token.to_owned(); + let url = self.config.app_url(); + FutureResult::new(async move { + update_app_request(&token, params, &url).await?; + Ok(()) + }) + } - fn delete_app(&self, token: &str, params: AppIdPB) -> FutureResult<(), FlowyError> { - let token = token.to_owned(); - let url = self.config.app_url(); - FutureResult::new(async move { - delete_app_request(&token, params, &url).await?; - Ok(()) - }) - } + fn delete_app(&self, token: &str, params: AppIdPB) -> FutureResult<(), FlowyError> { + let token = token.to_owned(); + let url = self.config.app_url(); + FutureResult::new(async move { + delete_app_request(&token, params, &url).await?; + Ok(()) + }) + } - fn create_trash(&self, token: &str, params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError> { - let token = token.to_owned(); - let url = self.config.trash_url(); - FutureResult::new(async move { - create_trash_request(&token, params, &url).await?; - Ok(()) - }) - } + fn create_trash(&self, token: &str, params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError> { + let token = token.to_owned(); + let url = self.config.trash_url(); + FutureResult::new(async move { + create_trash_request(&token, params, &url).await?; + Ok(()) + }) + } - fn delete_trash(&self, token: &str, params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError> { - let token = token.to_owned(); - let url = self.config.trash_url(); - FutureResult::new(async move { - delete_trash_request(&token, params, &url).await?; - Ok(()) - }) - } + fn delete_trash(&self, token: &str, params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError> { + let token = token.to_owned(); + let url = self.config.trash_url(); + FutureResult::new(async move { + delete_trash_request(&token, params, &url).await?; + Ok(()) + }) + } - fn read_trash(&self, token: &str) -> FutureResult, FlowyError> { - let token = token.to_owned(); - let url = self.config.trash_url(); - FutureResult::new(async move { - let repeated_trash = read_trash_request(&token, &url).await?; - Ok(repeated_trash) - }) - } + fn read_trash(&self, token: &str) -> FutureResult, FlowyError> { + let token = token.to_owned(); + let url = self.config.trash_url(); + FutureResult::new(async move { + let repeated_trash = read_trash_request(&token, &url).await?; + Ok(repeated_trash) + }) + } } #[allow(dead_code)] fn request_builder() -> HttpRequestBuilder { - HttpRequestBuilder::new().middleware(MIDDLEWARE.clone()) + HttpRequestBuilder::new().middleware(MIDDLEWARE.clone()) } pub async fn create_workspace_request( - _token: &str, - _params: CreateWorkspaceParams, - _url: &str, + _token: &str, + _params: CreateWorkspaceParams, + _url: &str, ) -> Result { - // let workspace = request_builder() - // .post(&url.to_owned()) - // .header(HEADER_TOKEN, token) - // .protobuf(params)? - // .response() - // .await?; - // Ok(workspace) - unimplemented!() + // let workspace = request_builder() + // .post(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .response() + // .await?; + // Ok(workspace) + unimplemented!() } pub async fn read_workspaces_request( - _token: &str, - _params: WorkspaceIdPB, - _url: &str, + _token: &str, + _params: WorkspaceIdPB, + _url: &str, ) -> Result, FlowyError> { - // let repeated_workspace = request_builder() - // .get(&url.to_owned()) - // .header(HEADER_TOKEN, token) - // .protobuf(params)? - // .response::() - // .await?; - // - // Ok(repeated_workspace) - unimplemented!() + // let repeated_workspace = request_builder() + // .get(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .response::() + // .await?; + // + // Ok(repeated_workspace) + unimplemented!() } pub async fn update_workspace_request( - _token: &str, - _params: UpdateWorkspaceParams, - _url: &str, + _token: &str, + _params: UpdateWorkspaceParams, + _url: &str, ) -> Result<(), FlowyError> { - // let _ = request_builder() - // .patch(&url.to_owned()) - // .header(HEADER_TOKEN, token) - // .protobuf(params)? - // .send() - // .await?; - Ok(()) + // let _ = request_builder() + // .patch(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; + Ok(()) } -pub async fn delete_workspace_request(_token: &str, _params: WorkspaceIdPB, _url: &str) -> Result<(), FlowyError> { - // let _ = request_builder() - // .delete(url) - // .header(HEADER_TOKEN, token) - // .protobuf(params)? - // .send() - // .await?; - Ok(()) +pub async fn delete_workspace_request( + _token: &str, + _params: WorkspaceIdPB, + _url: &str, +) -> Result<(), FlowyError> { + // let _ = request_builder() + // .delete(url) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; + Ok(()) } // App -pub async fn create_app_request(_token: &str, _params: CreateAppParams, _url: &str) -> Result { - // let app = request_builder() - // .post(&url.to_owned()) - // .header(HEADER_TOKEN, token) - // .protobuf(params)? - // .response() - // .await?; - // Ok(app) - unimplemented!() +pub async fn create_app_request( + _token: &str, + _params: CreateAppParams, + _url: &str, +) -> Result { + // let app = request_builder() + // .post(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .response() + // .await?; + // Ok(app) + unimplemented!() } -pub async fn read_app_request(_token: &str, _params: AppIdPB, _url: &str) -> Result, FlowyError> { - // let app = request_builder() - // .get(&url.to_owned()) - // .header(HEADER_TOKEN, token) - // .protobuf(params)? - // .option_response() - // .await?; - // Ok(app) +pub async fn read_app_request( + _token: &str, + _params: AppIdPB, + _url: &str, +) -> Result, FlowyError> { + // let app = request_builder() + // .get(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .option_response() + // .await?; + // Ok(app) - unimplemented!() + unimplemented!() } -pub async fn update_app_request(_token: &str, _params: UpdateAppParams, _url: &str) -> Result<(), FlowyError> { - // let _ = request_builder() - // .patch(&url.to_owned()) - // .header(HEADER_TOKEN, token) - // .protobuf(params)? - // .send() - // .await?; - Ok(()) +pub async fn update_app_request( + _token: &str, + _params: UpdateAppParams, + _url: &str, +) -> Result<(), FlowyError> { + // let _ = request_builder() + // .patch(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; + Ok(()) } -pub async fn delete_app_request(_token: &str, _params: AppIdPB, _url: &str) -> Result<(), FlowyError> { - // let _ = request_builder() - // .delete(&url.to_owned()) - // .header(HEADER_TOKEN, token) - // .protobuf(params)? - // .send() - // .await?; - Ok(()) +pub async fn delete_app_request( + _token: &str, + _params: AppIdPB, + _url: &str, +) -> Result<(), FlowyError> { + // let _ = request_builder() + // .delete(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; + Ok(()) } // View pub async fn create_view_request( - _token: &str, - _params: CreateViewParams, - _url: &str, + _token: &str, + _params: CreateViewParams, + _url: &str, ) -> Result { - // let view = request_builder() - // .post(&url.to_owned()) - // .header(HEADER_TOKEN, token) - // .protobuf(params)? - // .response() - // .await?; - // Ok(view) - unimplemented!() + // let view = request_builder() + // .post(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .response() + // .await?; + // Ok(view) + unimplemented!() } pub async fn read_view_request( - _token: &str, - _params: ViewIdPB, - _url: &str, + _token: &str, + _params: ViewIdPB, + _url: &str, ) -> Result, FlowyError> { - // let view = request_builder() - // .get(&url.to_owned()) - // .header(HEADER_TOKEN, token) - // .protobuf(params)? - // .option_response() - // .await?; - // - // Ok(view) - unimplemented!() + // let view = request_builder() + // .get(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .option_response() + // .await?; + // + // Ok(view) + unimplemented!() } -pub async fn update_view_request(_token: &str, _params: UpdateViewParams, _url: &str) -> Result<(), FlowyError> { - // let _ = request_builder() - // .patch(&url.to_owned()) - // .header(HEADER_TOKEN, token) - // .protobuf(params)? - // .send() - // .await?; - Ok(()) +pub async fn update_view_request( + _token: &str, + _params: UpdateViewParams, + _url: &str, +) -> Result<(), FlowyError> { + // let _ = request_builder() + // .patch(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; + Ok(()) } -pub async fn delete_view_request(_token: &str, _params: RepeatedViewIdPB, _url: &str) -> Result<(), FlowyError> { - // let _ = request_builder() - // .delete(&url.to_owned()) - // .header(HEADER_TOKEN, token) - // .protobuf(params)? - // .send() - // .await?; - Ok(()) +pub async fn delete_view_request( + _token: &str, + _params: RepeatedViewIdPB, + _url: &str, +) -> Result<(), FlowyError> { + // let _ = request_builder() + // .delete(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; + Ok(()) } -pub async fn create_trash_request(_token: &str, _params: RepeatedTrashIdPB, _url: &str) -> Result<(), FlowyError> { - // let _ = request_builder() - // .post(&url.to_owned()) - // .header(HEADER_TOKEN, token) - // .protobuf(params)? - // .send() - // .await?; - Ok(()) +pub async fn create_trash_request( + _token: &str, + _params: RepeatedTrashIdPB, + _url: &str, +) -> Result<(), FlowyError> { + // let _ = request_builder() + // .post(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; + Ok(()) } -pub async fn delete_trash_request(_token: &str, _params: RepeatedTrashIdPB, _url: &str) -> Result<(), FlowyError> { - // let _ = request_builder() - // .delete(&url.to_owned()) - // .header(HEADER_TOKEN, token) - // .protobuf(params)? - // .send() - // .await?; - Ok(()) +pub async fn delete_trash_request( + _token: &str, + _params: RepeatedTrashIdPB, + _url: &str, +) -> Result<(), FlowyError> { + // let _ = request_builder() + // .delete(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .protobuf(params)? + // .send() + // .await?; + Ok(()) } -pub async fn read_trash_request(_token: &str, _url: &str) -> Result, FlowyError> { - // let repeated_trash = request_builder() - // .get(&url.to_owned()) - // .header(HEADER_TOKEN, token) - // .response::() - // .await?; - // Ok(repeated_trash) - unimplemented!() +pub async fn read_trash_request( + _token: &str, + _url: &str, +) -> Result, FlowyError> { + // let repeated_trash = request_builder() + // .get(&url.to_owned()) + // .header(HEADER_TOKEN, token) + // .response::() + // .await?; + // Ok(repeated_trash) + unimplemented!() } lazy_static! { - static ref MIDDLEWARE: Arc = Arc::new(FolderResponseMiddleware::new()); + static ref MIDDLEWARE: Arc = Arc::new(FolderResponseMiddleware::new()); } pub struct FolderResponseMiddleware { - invalid_token_sender: broadcast::Sender, + invalid_token_sender: broadcast::Sender, } impl FolderResponseMiddleware { - fn new() -> Self { - let (sender, _) = broadcast::channel(10); - FolderResponseMiddleware { - invalid_token_sender: sender, - } + fn new() -> Self { + let (sender, _) = broadcast::channel(10); + FolderResponseMiddleware { + invalid_token_sender: sender, } + } - #[allow(dead_code)] - fn invalid_token_subscribe(&self) -> broadcast::Receiver { - self.invalid_token_sender.subscribe() - } + #[allow(dead_code)] + fn invalid_token_subscribe(&self) -> broadcast::Receiver { + self.invalid_token_sender.subscribe() + } } impl ResponseMiddleware for FolderResponseMiddleware { - fn receive_response(&self, token: &Option, response: &HttpResponse) { - if let Some(error) = &response.error { - if error.is_unauthorized() { - tracing::error!("user is unauthorized"); - match token { - None => {} - Some(token) => match self.invalid_token_sender.send(token.clone()) { - Ok(_) => {} - Err(e) => tracing::error!("{:?}", e), - }, - } - } + fn receive_response(&self, token: &Option, response: &HttpResponse) { + if let Some(error) = &response.error { + if error.is_unauthorized() { + tracing::error!("user is unauthorized"); + match token { + None => {}, + Some(token) => match self.invalid_token_sender.send(token.clone()) { + Ok(_) => {}, + Err(e) => tracing::error!("{:?}", e), + }, } + } } + } } diff --git a/frontend/rust-lib/flowy-net/src/http_server/user.rs b/frontend/rust-lib/flowy-net/src/http_server/user.rs index 3b9407eeb7..c619a847d8 100644 --- a/frontend/rust-lib/flowy-net/src/http_server/user.rs +++ b/frontend/rust-lib/flowy-net/src/http_server/user.rs @@ -7,101 +7,125 @@ use lib_infra::future::FutureResult; use user_model::*; pub struct UserHttpCloudService { - config: ClientServerConfiguration, + config: ClientServerConfiguration, } impl UserHttpCloudService { - pub fn new(config: &ClientServerConfiguration) -> Self { - Self { config: config.clone() } + pub fn new(config: &ClientServerConfiguration) -> Self { + Self { + config: config.clone(), } + } } impl UserCloudService for UserHttpCloudService { - fn sign_up(&self, params: SignUpParams) -> FutureResult { - let url = self.config.sign_up_url(); - FutureResult::new(async move { - let resp = user_sign_up_request(params, &url).await?; - Ok(resp) - }) - } + fn sign_up(&self, params: SignUpParams) -> FutureResult { + let url = self.config.sign_up_url(); + FutureResult::new(async move { + let resp = user_sign_up_request(params, &url).await?; + Ok(resp) + }) + } - fn sign_in(&self, params: SignInParams) -> FutureResult { - let url = self.config.sign_in_url(); - FutureResult::new(async move { - let resp = user_sign_in_request(params, &url).await?; - Ok(resp) - }) - } + fn sign_in(&self, params: SignInParams) -> FutureResult { + let url = self.config.sign_in_url(); + FutureResult::new(async move { + let resp = user_sign_in_request(params, &url).await?; + Ok(resp) + }) + } - fn sign_out(&self, token: &str) -> FutureResult<(), FlowyError> { - let token = token.to_owned(); - let url = self.config.sign_out_url(); - FutureResult::new(async move { - let _ = user_sign_out_request(&token, &url).await; - Ok(()) - }) - } + fn sign_out(&self, token: &str) -> FutureResult<(), FlowyError> { + let token = token.to_owned(); + let url = self.config.sign_out_url(); + FutureResult::new(async move { + let _ = user_sign_out_request(&token, &url).await; + Ok(()) + }) + } - fn update_user(&self, token: &str, params: UpdateUserProfileParams) -> FutureResult<(), FlowyError> { - let token = token.to_owned(); - let url = self.config.user_profile_url(); - FutureResult::new(async move { - update_user_profile_request(&token, params, &url).await?; - Ok(()) - }) - } + fn update_user( + &self, + token: &str, + params: UpdateUserProfileParams, + ) -> FutureResult<(), FlowyError> { + let token = token.to_owned(); + let url = self.config.user_profile_url(); + FutureResult::new(async move { + update_user_profile_request(&token, params, &url).await?; + Ok(()) + }) + } - fn get_user(&self, token: &str) -> FutureResult { - let token = token.to_owned(); - let url = self.config.user_profile_url(); - FutureResult::new(async move { - let profile = get_user_profile_request(&token, &url).await?; - Ok(profile) - }) - } + fn get_user(&self, token: &str) -> FutureResult { + let token = token.to_owned(); + let url = self.config.user_profile_url(); + FutureResult::new(async move { + let profile = get_user_profile_request(&token, &url).await?; + Ok(profile) + }) + } - fn ws_addr(&self) -> String { - self.config.ws_addr() - } + fn ws_addr(&self) -> String { + self.config.ws_addr() + } } -pub async fn user_sign_up_request(params: SignUpParams, url: &str) -> Result { - let response = request_builder().post(url).json(params)?.json_response().await?; - Ok(response) +pub async fn user_sign_up_request( + params: SignUpParams, + url: &str, +) -> Result { + let response = request_builder() + .post(url) + .json(params)? + .json_response() + .await?; + Ok(response) } -pub async fn user_sign_in_request(params: SignInParams, url: &str) -> Result { - let response = request_builder().post(url).json(params)?.json_response().await?; - Ok(response) +pub async fn user_sign_in_request( + params: SignInParams, + url: &str, +) -> Result { + let response = request_builder() + .post(url) + .json(params)? + .json_response() + .await?; + Ok(response) } pub async fn user_sign_out_request(token: &str, url: &str) -> Result<(), FlowyError> { - request_builder().delete(url).header(HEADER_TOKEN, token).send().await?; - Ok(()) + request_builder() + .delete(url) + .header(HEADER_TOKEN, token) + .send() + .await?; + Ok(()) } pub async fn get_user_profile_request(token: &str, url: &str) -> Result { - let user_profile = request_builder() - .get(url) - .header(HEADER_TOKEN, token) - .response() - .await?; - Ok(user_profile) + let user_profile = request_builder() + .get(url) + .header(HEADER_TOKEN, token) + .response() + .await?; + Ok(user_profile) } pub async fn update_user_profile_request( - token: &str, - params: UpdateUserProfileParams, - url: &str, + token: &str, + params: UpdateUserProfileParams, + url: &str, ) -> Result<(), FlowyError> { - request_builder() - .patch(url) - .header(HEADER_TOKEN, token) - .json(params)? - .send() - .await?; - Ok(()) + request_builder() + .patch(url) + .header(HEADER_TOKEN, token) + .json(params)? + .send() + .await?; + Ok(()) } fn request_builder() -> HttpRequestBuilder { - HttpRequestBuilder::new() + HttpRequestBuilder::new() } diff --git a/frontend/rust-lib/flowy-net/src/local_server/mod.rs b/frontend/rust-lib/flowy-net/src/local_server/mod.rs index 516de4f02a..a36b2ef9e7 100644 --- a/frontend/rust-lib/flowy-net/src/local_server/mod.rs +++ b/frontend/rust-lib/flowy-net/src/local_server/mod.rs @@ -9,19 +9,22 @@ pub use server::*; pub use ws::*; pub struct LocalServerContext { - pub local_ws: LocalWebSocket, - pub local_server: LocalServer, + pub local_ws: LocalWebSocket, + pub local_server: LocalServer, } pub fn build_server(_config: &ClientServerConfiguration) -> LocalServerContext { - let (client_ws_sender, server_ws_receiver) = mpsc::unbounded_channel(); - let (server_ws_sender, _) = broadcast::channel(16); + let (client_ws_sender, server_ws_receiver) = mpsc::unbounded_channel(); + let (server_ws_sender, _) = broadcast::channel(16); - // server_ws_sender -> client_ws_receiver - // server_ws_receiver <- client_ws_sender - let local_ws = LocalWebSocket::new(server_ws_receiver, server_ws_sender.clone()); - let client_ws_receiver = server_ws_sender; - let local_server = LocalServer::new(client_ws_sender, client_ws_receiver); + // server_ws_sender -> client_ws_receiver + // server_ws_receiver <- client_ws_sender + let local_ws = LocalWebSocket::new(server_ws_receiver, server_ws_sender.clone()); + let client_ws_receiver = server_ws_sender; + let local_server = LocalServer::new(client_ws_sender, client_ws_receiver); - LocalServerContext { local_ws, local_server } + LocalServerContext { + local_ws, + local_server, + } } diff --git a/frontend/rust-lib/flowy-net/src/local_server/persistence.rs b/frontend/rust-lib/flowy-net/src/local_server/persistence.rs index d412da38af..5d032860df 100644 --- a/frontend/rust-lib/flowy-net/src/local_server/persistence.rs +++ b/frontend/rust-lib/flowy-net/src/local_server/persistence.rs @@ -6,157 +6,181 @@ use folder_model::folder::FolderInfo; use lib_infra::future::BoxResultFuture; use revision_model::Revision; use std::{ - fmt::{Debug, Formatter}, - sync::Arc, + fmt::{Debug, Formatter}, + sync::Arc, }; // For the moment, we use memory to cache the data, it will be implemented with // other storage. Like the Firestore,Dropbox.etc. pub trait RevisionCloudStorage: Send + Sync { - fn set_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError>; - fn get_revisions(&self, object_id: &str, rev_ids: Option>) -> BoxResultFuture, SyncError>; - fn reset_object(&self, object_id: &str, revisions: Vec) -> BoxResultFuture<(), SyncError>; + fn set_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError>; + fn get_revisions( + &self, + object_id: &str, + rev_ids: Option>, + ) -> BoxResultFuture, SyncError>; + fn reset_object( + &self, + object_id: &str, + revisions: Vec, + ) -> BoxResultFuture<(), SyncError>; } pub(crate) struct LocalDocumentCloudPersistence { - storage: Arc, + storage: Arc, } impl Debug for LocalDocumentCloudPersistence { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str("LocalRevisionCloudPersistence") - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str("LocalRevisionCloudPersistence") + } } impl std::default::Default for LocalDocumentCloudPersistence { - fn default() -> Self { - LocalDocumentCloudPersistence { - storage: Arc::new(MemoryDocumentCloudStorage::default()), - } + fn default() -> Self { + LocalDocumentCloudPersistence { + storage: Arc::new(MemoryDocumentCloudStorage::default()), } + } } impl FolderCloudPersistence for LocalDocumentCloudPersistence { - fn read_folder(&self, _user_id: &str, folder_id: &str) -> BoxResultFuture { - let storage = self.storage.clone(); - let folder_id = folder_id.to_owned(); - Box::pin(async move { - let revisions = storage.get_revisions(&folder_id, None).await?; - match make_folder_from_revisions(&folder_id, revisions)? { - Some(folder_info) => Ok(folder_info), - None => Err(SyncError::record_not_found()), - } - }) - } + fn read_folder(&self, _user_id: &str, folder_id: &str) -> BoxResultFuture { + let storage = self.storage.clone(); + let folder_id = folder_id.to_owned(); + Box::pin(async move { + let revisions = storage.get_revisions(&folder_id, None).await?; + match make_folder_from_revisions(&folder_id, revisions)? { + Some(folder_info) => Ok(folder_info), + None => Err(SyncError::record_not_found()), + } + }) + } - fn create_folder( - &self, - _user_id: &str, - folder_id: &str, - revisions: Vec, - ) -> BoxResultFuture, SyncError> { - let folder_id = folder_id.to_owned(); - let storage = self.storage.clone(); - Box::pin(async move { - storage.set_revisions(revisions.clone()).await?; - make_folder_from_revisions(&folder_id, revisions) - }) - } + fn create_folder( + &self, + _user_id: &str, + folder_id: &str, + revisions: Vec, + ) -> BoxResultFuture, SyncError> { + let folder_id = folder_id.to_owned(); + let storage = self.storage.clone(); + Box::pin(async move { + storage.set_revisions(revisions.clone()).await?; + make_folder_from_revisions(&folder_id, revisions) + }) + } - fn save_folder_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError> { - let storage = self.storage.clone(); - Box::pin(async move { - storage.set_revisions(revisions).await?; - Ok(()) - }) - } + fn save_folder_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError> { + let storage = self.storage.clone(); + Box::pin(async move { + storage.set_revisions(revisions).await?; + Ok(()) + }) + } - fn read_folder_revisions( - &self, - folder_id: &str, - rev_ids: Option>, - ) -> BoxResultFuture, SyncError> { - let folder_id = folder_id.to_owned(); - let storage = self.storage.clone(); - Box::pin(async move { storage.get_revisions(&folder_id, rev_ids).await }) - } + fn read_folder_revisions( + &self, + folder_id: &str, + rev_ids: Option>, + ) -> BoxResultFuture, SyncError> { + let folder_id = folder_id.to_owned(); + let storage = self.storage.clone(); + Box::pin(async move { storage.get_revisions(&folder_id, rev_ids).await }) + } - fn reset_folder(&self, folder_id: &str, revisions: Vec) -> BoxResultFuture<(), SyncError> { - let storage = self.storage.clone(); - let folder_id = folder_id.to_owned(); - Box::pin(async move { - storage.reset_object(&folder_id, revisions).await?; - Ok(()) - }) - } + fn reset_folder( + &self, + folder_id: &str, + revisions: Vec, + ) -> BoxResultFuture<(), SyncError> { + let storage = self.storage.clone(); + let folder_id = folder_id.to_owned(); + Box::pin(async move { + storage.reset_object(&folder_id, revisions).await?; + Ok(()) + }) + } } impl DocumentCloudPersistence for LocalDocumentCloudPersistence { - fn read_document(&self, doc_id: &str) -> BoxResultFuture { - let storage = self.storage.clone(); - let doc_id = doc_id.to_owned(); - Box::pin(async move { - let repeated_revision = storage.get_revisions(&doc_id, None).await?; - match make_document_info_from_revisions(&doc_id, repeated_revision)? { - Some(document_info) => Ok(document_info), - None => Err(SyncError::record_not_found()), - } - }) - } + fn read_document(&self, doc_id: &str) -> BoxResultFuture { + let storage = self.storage.clone(); + let doc_id = doc_id.to_owned(); + Box::pin(async move { + let repeated_revision = storage.get_revisions(&doc_id, None).await?; + match make_document_info_from_revisions(&doc_id, repeated_revision)? { + Some(document_info) => Ok(document_info), + None => Err(SyncError::record_not_found()), + } + }) + } - fn create_document( - &self, - doc_id: &str, - revisions: Vec, - ) -> BoxResultFuture, SyncError> { - let doc_id = doc_id.to_owned(); - let storage = self.storage.clone(); - Box::pin(async move { - storage.set_revisions(revisions.clone()).await?; - make_document_info_from_revisions(&doc_id, revisions) - }) - } + fn create_document( + &self, + doc_id: &str, + revisions: Vec, + ) -> BoxResultFuture, SyncError> { + let doc_id = doc_id.to_owned(); + let storage = self.storage.clone(); + Box::pin(async move { + storage.set_revisions(revisions.clone()).await?; + make_document_info_from_revisions(&doc_id, revisions) + }) + } - fn read_document_revisions( - &self, - doc_id: &str, - rev_ids: Option>, - ) -> BoxResultFuture, SyncError> { - let doc_id = doc_id.to_owned(); - let storage = self.storage.clone(); - Box::pin(async move { storage.get_revisions(&doc_id, rev_ids).await }) - } + fn read_document_revisions( + &self, + doc_id: &str, + rev_ids: Option>, + ) -> BoxResultFuture, SyncError> { + let doc_id = doc_id.to_owned(); + let storage = self.storage.clone(); + Box::pin(async move { storage.get_revisions(&doc_id, rev_ids).await }) + } - fn save_document_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError> { - let storage = self.storage.clone(); - Box::pin(async move { - storage.set_revisions(revisions).await?; - Ok(()) - }) - } + fn save_document_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError> { + let storage = self.storage.clone(); + Box::pin(async move { + storage.set_revisions(revisions).await?; + Ok(()) + }) + } - fn reset_document(&self, doc_id: &str, revisions: Vec) -> BoxResultFuture<(), SyncError> { - let storage = self.storage.clone(); - let doc_id = doc_id.to_owned(); - Box::pin(async move { - storage.reset_object(&doc_id, revisions).await?; - Ok(()) - }) - } + fn reset_document( + &self, + doc_id: &str, + revisions: Vec, + ) -> BoxResultFuture<(), SyncError> { + let storage = self.storage.clone(); + let doc_id = doc_id.to_owned(); + Box::pin(async move { + storage.reset_object(&doc_id, revisions).await?; + Ok(()) + }) + } } #[derive(Default)] struct MemoryDocumentCloudStorage {} impl RevisionCloudStorage for MemoryDocumentCloudStorage { - fn set_revisions(&self, _revisions: Vec) -> BoxResultFuture<(), SyncError> { - Box::pin(async move { Ok(()) }) - } + fn set_revisions(&self, _revisions: Vec) -> BoxResultFuture<(), SyncError> { + Box::pin(async move { Ok(()) }) + } - fn get_revisions(&self, _object_id: &str, _rev_ids: Option>) -> BoxResultFuture, SyncError> { - Box::pin(async move { Ok(vec![]) }) - } + fn get_revisions( + &self, + _object_id: &str, + _rev_ids: Option>, + ) -> BoxResultFuture, SyncError> { + Box::pin(async move { Ok(vec![]) }) + } - fn reset_object(&self, _object_id: &str, _revisions: Vec) -> BoxResultFuture<(), SyncError> { - Box::pin(async move { Ok(()) }) - } + fn reset_object( + &self, + _object_id: &str, + _revisions: Vec, + ) -> BoxResultFuture<(), SyncError> { + Box::pin(async move { Ok(()) }) + } } diff --git a/frontend/rust-lib/flowy-net/src/local_server/server.rs b/frontend/rust-lib/flowy-net/src/local_server/server.rs index 15573b2c43..43f8806faa 100644 --- a/frontend/rust-lib/flowy-net/src/local_server/server.rs +++ b/frontend/rust-lib/flowy-net/src/local_server/server.rs @@ -1,15 +1,17 @@ use crate::local_server::persistence::LocalDocumentCloudPersistence; use async_stream::stream; use bytes::Bytes; -use document_model::document::{CreateDocumentParams, DocumentId, DocumentInfo, ResetDocumentParams}; +use document_model::document::{ + CreateDocumentParams, DocumentId, DocumentInfo, ResetDocumentParams, +}; use flowy_client_sync::errors::SyncError; use flowy_document::DocumentCloudService; use flowy_error::{internal_error, FlowyError}; use flowy_folder::entities::{ - app::{AppIdPB, CreateAppParams, UpdateAppParams}, - trash::RepeatedTrashIdPB, - view::{CreateViewParams, RepeatedViewIdPB, UpdateViewParams, ViewIdPB}, - workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceIdPB}, + app::{AppIdPB, CreateAppParams, UpdateAppParams}, + trash::RepeatedTrashIdPB, + view::{CreateViewParams, RepeatedViewIdPB, UpdateViewParams, ViewIdPB}, + workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceIdPB}, }; use flowy_folder::event_map::FolderCouldServiceV1; use flowy_server_sync::server_document::ServerDocumentManager; @@ -17,393 +19,454 @@ use flowy_server_sync::server_folder::ServerFolderManager; use flowy_sync::{RevisionSyncResponse, RevisionUser}; use flowy_user::entities::UserProfilePB; use flowy_user::event_map::UserCloudService; -use folder_model::{gen_app_id, gen_workspace_id, AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; +use folder_model::{ + gen_app_id, gen_workspace_id, AppRevision, TrashRevision, ViewRevision, WorkspaceRevision, +}; use futures_util::stream::StreamExt; use lib_infra::{future::FutureResult, util::timestamp}; use lib_ws::{WSChannel, WebSocketRawMessage}; use nanoid::nanoid; use parking_lot::RwLock; use std::{ - convert::{TryFrom, TryInto}, - fmt::Debug, - sync::Arc, + convert::{TryFrom, TryInto}, + fmt::Debug, + sync::Arc, }; use tokio::sync::{broadcast, mpsc, mpsc::UnboundedSender}; use user_model::*; use ws_model::ws_revision::{ClientRevisionWSData, ClientRevisionWSDataType}; pub struct LocalServer { - doc_manager: Arc, - folder_manager: Arc, - stop_tx: RwLock>>, - client_ws_sender: mpsc::UnboundedSender, - client_ws_receiver: broadcast::Sender, + doc_manager: Arc, + folder_manager: Arc, + stop_tx: RwLock>>, + client_ws_sender: mpsc::UnboundedSender, + client_ws_receiver: broadcast::Sender, } impl LocalServer { - pub fn new( - client_ws_sender: mpsc::UnboundedSender, - client_ws_receiver: broadcast::Sender, - ) -> Self { - let persistence = Arc::new(LocalDocumentCloudPersistence::default()); - let doc_manager = Arc::new(ServerDocumentManager::new(persistence.clone())); - let folder_manager = Arc::new(ServerFolderManager::new(persistence)); - let stop_tx = RwLock::new(None); + pub fn new( + client_ws_sender: mpsc::UnboundedSender, + client_ws_receiver: broadcast::Sender, + ) -> Self { + let persistence = Arc::new(LocalDocumentCloudPersistence::default()); + let doc_manager = Arc::new(ServerDocumentManager::new(persistence.clone())); + let folder_manager = Arc::new(ServerFolderManager::new(persistence)); + let stop_tx = RwLock::new(None); - LocalServer { - doc_manager, - folder_manager, - stop_tx, - client_ws_sender, - client_ws_receiver, - } + LocalServer { + doc_manager, + folder_manager, + stop_tx, + client_ws_sender, + client_ws_receiver, } + } - pub async fn stop(&self) { - let sender = self.stop_tx.read().clone(); - if let Some(stop_tx) = sender { - let _ = stop_tx.send(()).await; - } + pub async fn stop(&self) { + let sender = self.stop_tx.read().clone(); + if let Some(stop_tx) = sender { + let _ = stop_tx.send(()).await; } + } - pub fn run(&self) { - let (stop_tx, stop_rx) = mpsc::channel(1); - *self.stop_tx.write() = Some(stop_tx); - let runner = LocalWebSocketRunner { - doc_manager: self.doc_manager.clone(), - folder_manager: self.folder_manager.clone(), - stop_rx: Some(stop_rx), - client_ws_sender: self.client_ws_sender.clone(), - client_ws_receiver: Some(self.client_ws_receiver.subscribe()), - }; - tokio::spawn(runner.run()); - } + pub fn run(&self) { + let (stop_tx, stop_rx) = mpsc::channel(1); + *self.stop_tx.write() = Some(stop_tx); + let runner = LocalWebSocketRunner { + doc_manager: self.doc_manager.clone(), + folder_manager: self.folder_manager.clone(), + stop_rx: Some(stop_rx), + client_ws_sender: self.client_ws_sender.clone(), + client_ws_receiver: Some(self.client_ws_receiver.subscribe()), + }; + tokio::spawn(runner.run()); + } } struct LocalWebSocketRunner { - doc_manager: Arc, - folder_manager: Arc, - stop_rx: Option>, - client_ws_sender: mpsc::UnboundedSender, - client_ws_receiver: Option>, + doc_manager: Arc, + folder_manager: Arc, + stop_rx: Option>, + client_ws_sender: mpsc::UnboundedSender, + client_ws_receiver: Option>, } impl LocalWebSocketRunner { - pub async fn run(mut self) { - let mut stop_rx = self.stop_rx.take().expect("Only run once"); - let mut client_ws_receiver = self.client_ws_receiver.take().expect("Only run once"); - let stream = stream! { - loop { - tokio::select! { - result = client_ws_receiver.recv() => { - match result { - Ok(msg) => yield msg, - Err(_e) => {}, - } - }, - _ = stop_rx.recv() => { - tracing::trace!("[LocalWebSocketRunner] stop"); - break - }, - }; - } - }; - stream - .for_each(|message| async { - match self.handle_message(message).await { - Ok(_) => {} - Err(e) => tracing::error!("[LocalWebSocketRunner]: {}", e), - } - }) - .await; - } - - async fn handle_message(&self, message: WebSocketRawMessage) -> Result<(), FlowyError> { - let bytes = Bytes::from(message.data); - let client_data = ClientRevisionWSData::try_from(bytes).map_err(internal_error)?; - match message.channel { - WSChannel::Document => { - self.handle_document_client_data(client_data, "".to_owned()).await?; - Ok(()) - } - WSChannel::Folder => { - self.handle_folder_client_data(client_data, "".to_owned()).await?; - Ok(()) - } - WSChannel::Database => { - todo!("Implement database web socket channel") - } + pub async fn run(mut self) { + let mut stop_rx = self.stop_rx.take().expect("Only run once"); + let mut client_ws_receiver = self.client_ws_receiver.take().expect("Only run once"); + let stream = stream! { + loop { + tokio::select! { + result = client_ws_receiver.recv() => { + match result { + Ok(msg) => yield msg, + Err(_e) => {}, + } + }, + _ = stop_rx.recv() => { + tracing::trace!("[LocalWebSocketRunner] stop"); + break + }, + }; } - } - - pub async fn handle_folder_client_data( - &self, - client_data: ClientRevisionWSData, - user_id: String, - ) -> Result<(), SyncError> { - tracing::trace!( - "[LocalFolderServer] receive: {}:{}-{:?} ", - client_data.object_id, - client_data.rev_id, - client_data.ty, - ); - let client_ws_sender = self.client_ws_sender.clone(); - let user = Arc::new(LocalRevisionUser { - user_id, - client_ws_sender, - channel: WSChannel::Folder, - }); - let ty = client_data.ty.clone(); - match ty { - ClientRevisionWSDataType::ClientPushRev => { - self.folder_manager.handle_client_revisions(user, client_data).await?; - } - ClientRevisionWSDataType::ClientPing => { - self.folder_manager.handle_client_ping(user, client_data).await?; - } + }; + stream + .for_each(|message| async { + match self.handle_message(message).await { + Ok(_) => {}, + Err(e) => tracing::error!("[LocalWebSocketRunner]: {}", e), } + }) + .await; + } + + async fn handle_message(&self, message: WebSocketRawMessage) -> Result<(), FlowyError> { + let bytes = Bytes::from(message.data); + let client_data = ClientRevisionWSData::try_from(bytes).map_err(internal_error)?; + match message.channel { + WSChannel::Document => { + self + .handle_document_client_data(client_data, "".to_owned()) + .await?; Ok(()) - } - - pub async fn handle_document_client_data( - &self, - client_data: ClientRevisionWSData, - user_id: String, - ) -> Result<(), SyncError> { - tracing::trace!( - "[LocalDocumentServer] receive: {}:{}-{:?} ", - client_data.object_id, - client_data.rev_id, - client_data.ty, - ); - let client_ws_sender = self.client_ws_sender.clone(); - let user = Arc::new(LocalRevisionUser { - user_id, - client_ws_sender, - channel: WSChannel::Document, - }); - let ty = client_data.ty.clone(); - match ty { - ClientRevisionWSDataType::ClientPushRev => { - self.doc_manager.handle_client_revisions(user, client_data).await?; - } - ClientRevisionWSDataType::ClientPing => { - self.doc_manager.handle_client_ping(user, client_data).await?; - } - } + }, + WSChannel::Folder => { + self + .handle_folder_client_data(client_data, "".to_owned()) + .await?; Ok(()) + }, + WSChannel::Database => { + todo!("Implement database web socket channel") + }, } + } + + pub async fn handle_folder_client_data( + &self, + client_data: ClientRevisionWSData, + user_id: String, + ) -> Result<(), SyncError> { + tracing::trace!( + "[LocalFolderServer] receive: {}:{}-{:?} ", + client_data.object_id, + client_data.rev_id, + client_data.ty, + ); + let client_ws_sender = self.client_ws_sender.clone(); + let user = Arc::new(LocalRevisionUser { + user_id, + client_ws_sender, + channel: WSChannel::Folder, + }); + let ty = client_data.ty.clone(); + match ty { + ClientRevisionWSDataType::ClientPushRev => { + self + .folder_manager + .handle_client_revisions(user, client_data) + .await?; + }, + ClientRevisionWSDataType::ClientPing => { + self + .folder_manager + .handle_client_ping(user, client_data) + .await?; + }, + } + Ok(()) + } + + pub async fn handle_document_client_data( + &self, + client_data: ClientRevisionWSData, + user_id: String, + ) -> Result<(), SyncError> { + tracing::trace!( + "[LocalDocumentServer] receive: {}:{}-{:?} ", + client_data.object_id, + client_data.rev_id, + client_data.ty, + ); + let client_ws_sender = self.client_ws_sender.clone(); + let user = Arc::new(LocalRevisionUser { + user_id, + client_ws_sender, + channel: WSChannel::Document, + }); + let ty = client_data.ty.clone(); + match ty { + ClientRevisionWSDataType::ClientPushRev => { + self + .doc_manager + .handle_client_revisions(user, client_data) + .await?; + }, + ClientRevisionWSDataType::ClientPing => { + self + .doc_manager + .handle_client_ping(user, client_data) + .await?; + }, + } + Ok(()) + } } #[derive(Debug)] struct LocalRevisionUser { - user_id: String, - client_ws_sender: mpsc::UnboundedSender, - channel: WSChannel, + user_id: String, + client_ws_sender: mpsc::UnboundedSender, + channel: WSChannel, } impl RevisionUser for LocalRevisionUser { - fn user_id(&self) -> String { - self.user_id.clone() - } + fn user_id(&self) -> String { + self.user_id.clone() + } - fn receive(&self, resp: RevisionSyncResponse) { - let sender = self.client_ws_sender.clone(); - let send_fn = |sender: UnboundedSender, msg: WebSocketRawMessage| match sender.send(msg) { - Ok(_) => {} - Err(e) => { - tracing::error!("LocalDocumentUser send message failed: {}", e); - } - }; - let channel = self.channel.clone(); + fn receive(&self, resp: RevisionSyncResponse) { + let sender = self.client_ws_sender.clone(); + let send_fn = + |sender: UnboundedSender, msg: WebSocketRawMessage| match sender + .send(msg) + { + Ok(_) => {}, + Err(e) => { + tracing::error!("LocalDocumentUser send message failed: {}", e); + }, + }; + let channel = self.channel.clone(); - tokio::spawn(async move { - match resp { - RevisionSyncResponse::Pull(data) => { - let bytes: Bytes = data.try_into().unwrap(); - let msg = WebSocketRawMessage { - channel, - data: bytes.to_vec(), - }; - send_fn(sender, msg); - } - RevisionSyncResponse::Push(data) => { - let bytes: Bytes = data.try_into().unwrap(); - let msg = WebSocketRawMessage { - channel, - data: bytes.to_vec(), - }; - send_fn(sender, msg); - } - RevisionSyncResponse::Ack(data) => { - let bytes: Bytes = data.try_into().unwrap(); - let msg = WebSocketRawMessage { - channel, - data: bytes.to_vec(), - }; - send_fn(sender, msg); - } - } - }); - } + tokio::spawn(async move { + match resp { + RevisionSyncResponse::Pull(data) => { + let bytes: Bytes = data.try_into().unwrap(); + let msg = WebSocketRawMessage { + channel, + data: bytes.to_vec(), + }; + send_fn(sender, msg); + }, + RevisionSyncResponse::Push(data) => { + let bytes: Bytes = data.try_into().unwrap(); + let msg = WebSocketRawMessage { + channel, + data: bytes.to_vec(), + }; + send_fn(sender, msg); + }, + RevisionSyncResponse::Ack(data) => { + let bytes: Bytes = data.try_into().unwrap(); + let msg = WebSocketRawMessage { + channel, + data: bytes.to_vec(), + }; + send_fn(sender, msg); + }, + } + }); + } } impl FolderCouldServiceV1 for LocalServer { - fn init(&self) {} + fn init(&self) {} - fn create_workspace( - &self, - _token: &str, - params: CreateWorkspaceParams, - ) -> FutureResult { - let time = timestamp(); - let workspace = WorkspaceRevision { - id: gen_workspace_id(), - name: params.name, - desc: params.desc, - apps: vec![], - modified_time: time, - create_time: time, - }; + fn create_workspace( + &self, + _token: &str, + params: CreateWorkspaceParams, + ) -> FutureResult { + let time = timestamp(); + let workspace = WorkspaceRevision { + id: gen_workspace_id(), + name: params.name, + desc: params.desc, + apps: vec![], + modified_time: time, + create_time: time, + }; - FutureResult::new(async { Ok(workspace) }) - } + FutureResult::new(async { Ok(workspace) }) + } - fn read_workspace(&self, _token: &str, _params: WorkspaceIdPB) -> FutureResult, FlowyError> { - FutureResult::new(async { Ok(vec![]) }) - } + fn read_workspace( + &self, + _token: &str, + _params: WorkspaceIdPB, + ) -> FutureResult, FlowyError> { + FutureResult::new(async { Ok(vec![]) }) + } - fn update_workspace(&self, _token: &str, _params: UpdateWorkspaceParams) -> FutureResult<(), FlowyError> { - FutureResult::new(async { Ok(()) }) - } + fn update_workspace( + &self, + _token: &str, + _params: UpdateWorkspaceParams, + ) -> FutureResult<(), FlowyError> { + FutureResult::new(async { Ok(()) }) + } - fn delete_workspace(&self, _token: &str, _params: WorkspaceIdPB) -> FutureResult<(), FlowyError> { - FutureResult::new(async { Ok(()) }) - } + fn delete_workspace(&self, _token: &str, _params: WorkspaceIdPB) -> FutureResult<(), FlowyError> { + FutureResult::new(async { Ok(()) }) + } - fn create_view(&self, _token: &str, params: CreateViewParams) -> FutureResult { - let time = timestamp(); - let view = ViewRevision { - id: params.view_id, - app_id: params.belong_to_id, - name: params.name, - desc: params.desc, - data_format: params.data_format.into(), - version: 0, - belongings: vec![], - modified_time: time, - create_time: time, - ext_data: "".to_string(), - thumbnail: params.thumbnail, - layout: params.layout.into(), - }; - FutureResult::new(async { Ok(view) }) - } + fn create_view( + &self, + _token: &str, + params: CreateViewParams, + ) -> FutureResult { + let time = timestamp(); + let view = ViewRevision { + id: params.view_id, + app_id: params.belong_to_id, + name: params.name, + desc: params.desc, + data_format: params.data_format.into(), + version: 0, + belongings: vec![], + modified_time: time, + create_time: time, + ext_data: "".to_string(), + thumbnail: params.thumbnail, + layout: params.layout.into(), + }; + FutureResult::new(async { Ok(view) }) + } - fn read_view(&self, _token: &str, _params: ViewIdPB) -> FutureResult, FlowyError> { - FutureResult::new(async { Ok(None) }) - } + fn read_view( + &self, + _token: &str, + _params: ViewIdPB, + ) -> FutureResult, FlowyError> { + FutureResult::new(async { Ok(None) }) + } - fn delete_view(&self, _token: &str, _params: RepeatedViewIdPB) -> FutureResult<(), FlowyError> { - FutureResult::new(async { Ok(()) }) - } + fn delete_view(&self, _token: &str, _params: RepeatedViewIdPB) -> FutureResult<(), FlowyError> { + FutureResult::new(async { Ok(()) }) + } - fn update_view(&self, _token: &str, _params: UpdateViewParams) -> FutureResult<(), FlowyError> { - FutureResult::new(async { Ok(()) }) - } + fn update_view(&self, _token: &str, _params: UpdateViewParams) -> FutureResult<(), FlowyError> { + FutureResult::new(async { Ok(()) }) + } - fn create_app(&self, _token: &str, params: CreateAppParams) -> FutureResult { - let time = timestamp(); - let app = AppRevision { - id: gen_app_id(), - workspace_id: params.workspace_id, - name: params.name, - desc: params.desc, - belongings: vec![], - version: 0, - modified_time: time, - create_time: time, - }; - FutureResult::new(async { Ok(app) }) - } + fn create_app( + &self, + _token: &str, + params: CreateAppParams, + ) -> FutureResult { + let time = timestamp(); + let app = AppRevision { + id: gen_app_id(), + workspace_id: params.workspace_id, + name: params.name, + desc: params.desc, + belongings: vec![], + version: 0, + modified_time: time, + create_time: time, + }; + FutureResult::new(async { Ok(app) }) + } - fn read_app(&self, _token: &str, _params: AppIdPB) -> FutureResult, FlowyError> { - FutureResult::new(async { Ok(None) }) - } + fn read_app( + &self, + _token: &str, + _params: AppIdPB, + ) -> FutureResult, FlowyError> { + FutureResult::new(async { Ok(None) }) + } - fn update_app(&self, _token: &str, _params: UpdateAppParams) -> FutureResult<(), FlowyError> { - FutureResult::new(async { Ok(()) }) - } + fn update_app(&self, _token: &str, _params: UpdateAppParams) -> FutureResult<(), FlowyError> { + FutureResult::new(async { Ok(()) }) + } - fn delete_app(&self, _token: &str, _params: AppIdPB) -> FutureResult<(), FlowyError> { - FutureResult::new(async { Ok(()) }) - } + fn delete_app(&self, _token: &str, _params: AppIdPB) -> FutureResult<(), FlowyError> { + FutureResult::new(async { Ok(()) }) + } - fn create_trash(&self, _token: &str, _params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError> { - FutureResult::new(async { Ok(()) }) - } + fn create_trash(&self, _token: &str, _params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError> { + FutureResult::new(async { Ok(()) }) + } - fn delete_trash(&self, _token: &str, _params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError> { - FutureResult::new(async { Ok(()) }) - } + fn delete_trash(&self, _token: &str, _params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError> { + FutureResult::new(async { Ok(()) }) + } - fn read_trash(&self, _token: &str) -> FutureResult, FlowyError> { - FutureResult::new(async { Ok(vec![]) }) - } + fn read_trash(&self, _token: &str) -> FutureResult, FlowyError> { + FutureResult::new(async { Ok(vec![]) }) + } } impl UserCloudService for LocalServer { - fn sign_up(&self, params: SignUpParams) -> FutureResult { - let uid = nanoid!(20); - FutureResult::new(async move { - Ok(SignUpResponse { - user_id: uid.clone(), - name: params.name, - email: params.email, - token: uid, - }) - }) - } + fn sign_up(&self, params: SignUpParams) -> FutureResult { + let uid = nanoid!(20); + FutureResult::new(async move { + Ok(SignUpResponse { + user_id: uid.clone(), + name: params.name, + email: params.email, + token: uid, + }) + }) + } - fn sign_in(&self, params: SignInParams) -> FutureResult { - let user_id = nanoid!(20); - FutureResult::new(async { - Ok(SignInResponse { - user_id: user_id.clone(), - name: params.name, - email: params.email, - token: user_id, - }) - }) - } + fn sign_in(&self, params: SignInParams) -> FutureResult { + let user_id = nanoid!(20); + FutureResult::new(async { + Ok(SignInResponse { + user_id: user_id.clone(), + name: params.name, + email: params.email, + token: user_id, + }) + }) + } - fn sign_out(&self, _token: &str) -> FutureResult<(), FlowyError> { - FutureResult::new(async { Ok(()) }) - } + fn sign_out(&self, _token: &str) -> FutureResult<(), FlowyError> { + FutureResult::new(async { Ok(()) }) + } - fn update_user(&self, _token: &str, _params: UpdateUserProfileParams) -> FutureResult<(), FlowyError> { - FutureResult::new(async { Ok(()) }) - } + fn update_user( + &self, + _token: &str, + _params: UpdateUserProfileParams, + ) -> FutureResult<(), FlowyError> { + FutureResult::new(async { Ok(()) }) + } - fn get_user(&self, _token: &str) -> FutureResult { - FutureResult::new(async { Ok(UserProfilePB::default()) }) - } + fn get_user(&self, _token: &str) -> FutureResult { + FutureResult::new(async { Ok(UserProfilePB::default()) }) + } - fn ws_addr(&self) -> String { - "ws://localhost:8000/ws/".to_owned() - } + fn ws_addr(&self) -> String { + "ws://localhost:8000/ws/".to_owned() + } } impl DocumentCloudService for LocalServer { - fn create_document(&self, _token: &str, _params: CreateDocumentParams) -> FutureResult<(), FlowyError> { - FutureResult::new(async { Ok(()) }) - } + fn create_document( + &self, + _token: &str, + _params: CreateDocumentParams, + ) -> FutureResult<(), FlowyError> { + FutureResult::new(async { Ok(()) }) + } - fn fetch_document(&self, _token: &str, _params: DocumentId) -> FutureResult, FlowyError> { - FutureResult::new(async { Ok(None) }) - } + fn fetch_document( + &self, + _token: &str, + _params: DocumentId, + ) -> FutureResult, FlowyError> { + FutureResult::new(async { Ok(None) }) + } - fn update_document_content(&self, _token: &str, _params: ResetDocumentParams) -> FutureResult<(), FlowyError> { - FutureResult::new(async { Ok(()) }) - } + fn update_document_content( + &self, + _token: &str, + _params: ResetDocumentParams, + ) -> FutureResult<(), FlowyError> { + FutureResult::new(async { Ok(()) }) + } } diff --git a/frontend/rust-lib/flowy-net/src/local_server/ws.rs b/frontend/rust-lib/flowy-net/src/local_server/ws.rs index 42d943b396..733a2632c0 100644 --- a/frontend/rust-lib/flowy-net/src/local_server/ws.rs +++ b/frontend/rust-lib/flowy-net/src/local_server/ws.rs @@ -8,83 +8,88 @@ use std::sync::Arc; use tokio::sync::{broadcast, broadcast::Receiver, mpsc::UnboundedReceiver}; pub struct LocalWebSocket { - user_id: Arc>>, - receivers: Arc>>, - state_sender: broadcast::Sender, - server_ws_receiver: RwLock>>, - server_ws_sender: broadcast::Sender, + user_id: Arc>>, + receivers: Arc>>, + state_sender: broadcast::Sender, + server_ws_receiver: RwLock>>, + server_ws_sender: broadcast::Sender, } impl LocalWebSocket { - pub fn new( - server_ws_receiver: UnboundedReceiver, - server_ws_sender: broadcast::Sender, - ) -> Self { - let user_id = Arc::new(RwLock::new(None)); - let receivers = Arc::new(DashMap::new()); - let server_ws_receiver = RwLock::new(Some(server_ws_receiver)); - let (state_sender, _) = broadcast::channel(16); - LocalWebSocket { - user_id, - receivers, - state_sender, - server_ws_receiver, - server_ws_sender, - } + pub fn new( + server_ws_receiver: UnboundedReceiver, + server_ws_sender: broadcast::Sender, + ) -> Self { + let user_id = Arc::new(RwLock::new(None)); + let receivers = Arc::new(DashMap::new()); + let server_ws_receiver = RwLock::new(Some(server_ws_receiver)); + let (state_sender, _) = broadcast::channel(16); + LocalWebSocket { + user_id, + receivers, + state_sender, + server_ws_receiver, + server_ws_sender, } + } } impl FlowyRawWebSocket for LocalWebSocket { - fn initialize(&self) -> FutureResult<(), WSErrorCode> { - let mut server_ws_receiver = self.server_ws_receiver.write().take().expect("Only take once"); - let receivers = self.receivers.clone(); - tokio::spawn(async move { - while let Some(message) = server_ws_receiver.recv().await { - match receivers.get(&message.channel) { - None => tracing::error!("Can't find any handler for message: {:?}", message), - Some(receiver) => receiver.receive_message(message.clone()), - } - } - }); - FutureResult::new(async { Ok(()) }) - } + fn initialize(&self) -> FutureResult<(), WSErrorCode> { + let mut server_ws_receiver = self + .server_ws_receiver + .write() + .take() + .expect("Only take once"); + let receivers = self.receivers.clone(); + tokio::spawn(async move { + while let Some(message) = server_ws_receiver.recv().await { + match receivers.get(&message.channel) { + None => tracing::error!("Can't find any handler for message: {:?}", message), + Some(receiver) => receiver.receive_message(message.clone()), + } + } + }); + FutureResult::new(async { Ok(()) }) + } - fn start_connect(&self, _addr: String, user_id: String) -> FutureResult<(), WSErrorCode> { - *self.user_id.write() = Some(user_id); - FutureResult::new(async { Ok(()) }) - } + fn start_connect(&self, _addr: String, user_id: String) -> FutureResult<(), WSErrorCode> { + *self.user_id.write() = Some(user_id); + FutureResult::new(async { Ok(()) }) + } - fn stop_connect(&self) -> FutureResult<(), WSErrorCode> { - FutureResult::new(async { Ok(()) }) - } + fn stop_connect(&self) -> FutureResult<(), WSErrorCode> { + FutureResult::new(async { Ok(()) }) + } - fn subscribe_connect_state(&self) -> BoxFuture> { - let subscribe = self.state_sender.subscribe(); - Box::pin(async move { subscribe }) - } + fn subscribe_connect_state(&self) -> BoxFuture> { + let subscribe = self.state_sender.subscribe(); + Box::pin(async move { subscribe }) + } - fn reconnect(&self, _count: usize) -> FutureResult<(), WSErrorCode> { - FutureResult::new(async { Ok(()) }) - } + fn reconnect(&self, _count: usize) -> FutureResult<(), WSErrorCode> { + FutureResult::new(async { Ok(()) }) + } - fn add_msg_receiver(&self, receiver: Arc) -> Result<(), WSErrorCode> { - tracing::trace!("Local web socket add ws receiver: {:?}", receiver.source()); - self.receivers.insert(receiver.source(), receiver); - Ok(()) - } + fn add_msg_receiver(&self, receiver: Arc) -> Result<(), WSErrorCode> { + tracing::trace!("Local web socket add ws receiver: {:?}", receiver.source()); + self.receivers.insert(receiver.source(), receiver); + Ok(()) + } - fn ws_msg_sender(&self) -> FutureResult>, WSErrorCode> { - let ws: Arc = Arc::new(LocalWebSocketAdaptor(self.server_ws_sender.clone())); - FutureResult::new(async move { Ok(Some(ws)) }) - } + fn ws_msg_sender(&self) -> FutureResult>, WSErrorCode> { + let ws: Arc = + Arc::new(LocalWebSocketAdaptor(self.server_ws_sender.clone())); + FutureResult::new(async move { Ok(Some(ws)) }) + } } #[derive(Clone)] struct LocalWebSocketAdaptor(broadcast::Sender); impl FlowyWebSocket for LocalWebSocketAdaptor { - fn send(&self, msg: WebSocketRawMessage) -> Result<(), WSErrorCode> { - let _ = self.0.send(msg); - Ok(()) - } + fn send(&self, msg: WebSocketRawMessage) -> Result<(), WSErrorCode> { + let _ = self.0.send(msg); + Ok(()) + } } diff --git a/frontend/rust-lib/flowy-net/src/request.rs b/frontend/rust-lib/flowy-net/src/request.rs index f0617619ec..3452aecc61 100644 --- a/frontend/rust-lib/flowy-net/src/request.rs +++ b/frontend/rust-lib/flowy-net/src/request.rs @@ -6,249 +6,249 @@ use hyper::http; use protobuf::ProtobufError; use reqwest::{header::HeaderMap, Client, Method, Response}; use std::{ - convert::{TryFrom, TryInto}, - sync::Arc, - time::Duration, + convert::{TryFrom, TryInto}, + sync::Arc, + time::Duration, }; use tokio::sync::oneshot; pub trait ResponseMiddleware { - fn receive_response(&self, token: &Option, response: &HttpResponse); + fn receive_response(&self, token: &Option, response: &HttpResponse); } pub struct HttpRequestBuilder { - url: String, - body: Option, - response: Option, - headers: HeaderMap, - method: Method, - middleware: Vec>, + url: String, + body: Option, + response: Option, + headers: HeaderMap, + method: Method, + middleware: Vec>, } impl std::default::Default for HttpRequestBuilder { - fn default() -> Self { - Self { - url: "".to_owned(), - body: None, - response: None, - headers: HeaderMap::new(), - method: Method::GET, - middleware: Vec::new(), - } + fn default() -> Self { + Self { + url: "".to_owned(), + body: None, + response: None, + headers: HeaderMap::new(), + method: Method::GET, + middleware: Vec::new(), } + } } impl HttpRequestBuilder { - pub fn new() -> Self { - HttpRequestBuilder::default() - } + pub fn new() -> Self { + HttpRequestBuilder::default() + } - pub fn middleware(mut self, middleware: Arc) -> Self - where - T: 'static + ResponseMiddleware + Send + Sync, - { - self.middleware.push(middleware); - self - } + pub fn middleware(mut self, middleware: Arc) -> Self + where + T: 'static + ResponseMiddleware + Send + Sync, + { + self.middleware.push(middleware); + self + } - pub fn get(mut self, url: &str) -> Self { - self.url = url.to_owned(); - self.method = Method::GET; - self - } + pub fn get(mut self, url: &str) -> Self { + self.url = url.to_owned(); + self.method = Method::GET; + self + } - pub fn post(mut self, url: &str) -> Self { - self.url = url.to_owned(); - self.method = Method::POST; - self - } + pub fn post(mut self, url: &str) -> Self { + self.url = url.to_owned(); + self.method = Method::POST; + self + } - pub fn patch(mut self, url: &str) -> Self { - self.url = url.to_owned(); - self.method = Method::PATCH; - self - } + pub fn patch(mut self, url: &str) -> Self { + self.url = url.to_owned(); + self.method = Method::PATCH; + self + } - pub fn delete(mut self, url: &str) -> Self { - self.url = url.to_owned(); - self.method = Method::DELETE; - self - } + pub fn delete(mut self, url: &str) -> Self { + self.url = url.to_owned(); + self.method = Method::DELETE; + self + } - pub fn header(mut self, key: &'static str, value: &str) -> Self { - self.headers.insert(key, value.parse().unwrap()); - self - } + pub fn header(mut self, key: &'static str, value: &str) -> Self { + self.headers.insert(key, value.parse().unwrap()); + self + } - #[allow(dead_code)] - pub fn protobuf(self, body: T) -> Result - where - T: TryInto, - { - let body: Bytes = body.try_into()?; - self.bytes(body) - } + #[allow(dead_code)] + pub fn protobuf(self, body: T) -> Result + where + T: TryInto, + { + let body: Bytes = body.try_into()?; + self.bytes(body) + } - pub fn json(self, body: T) -> Result - where - T: serde::Serialize, - { - let bytes = Bytes::from(serde_json::to_vec(&body)?); - self.bytes(bytes) - } + pub fn json(self, body: T) -> Result + where + T: serde::Serialize, + { + let bytes = Bytes::from(serde_json::to_vec(&body)?); + self.bytes(bytes) + } - pub fn bytes(mut self, body: Bytes) -> Result { - self.body = Some(body); + pub fn bytes(mut self, body: Bytes) -> Result { + self.body = Some(body); + Ok(self) + } + + pub async fn send(self) -> Result<(), FlowyError> { + let _ = self.inner_send().await?; + Ok(()) + } + + pub async fn response(self) -> Result + where + T: TryFrom, + { + let builder = self.inner_send().await?; + match builder.response { + None => Err(unexpected_empty_payload(&builder.url)), + Some(data) => Ok(T::try_from(data)?), + } + } + + pub async fn json_response(self) -> Result + where + T: serde::de::DeserializeOwned, + { + let builder = self.inner_send().await?; + match builder.response { + None => Err(unexpected_empty_payload(&builder.url)), + Some(data) => Ok(serde_json::from_slice(&data)?), + } + } + + #[allow(dead_code)] + pub async fn option_protobuf_response(self) -> Result, FlowyError> + where + T: TryFrom, + { + let result = self.inner_send().await; + match result { + Ok(builder) => match builder.response { + None => Err(unexpected_empty_payload(&builder.url)), + Some(data) => Ok(Some(T::try_from(data)?)), + }, + Err(error) => match error.is_record_not_found() { + true => Ok(None), + false => Err(error), + }, + } + } + + pub async fn option_json_response(self) -> Result, FlowyError> + where + T: serde::de::DeserializeOwned + 'static, + { + let result = self.inner_send().await; + match result { + Ok(builder) => match builder.response { + None => Err(unexpected_empty_payload(&builder.url)), + Some(data) => Ok(Some(serde_json::from_slice(&data)?)), + }, + Err(error) => match error.is_record_not_found() { + true => Ok(None), + false => Err(error), + }, + } + } + + fn token(&self) -> Option { + match self.headers.get(HEADER_TOKEN) { + None => None, + Some(header) => match header.to_str() { + Ok(val) => Some(val.to_owned()), + Err(_) => None, + }, + } + } + + async fn inner_send(mut self) -> Result { + let (tx, rx) = oneshot::channel::>(); + let url = self.url.clone(); + let body = self.body.take(); + let method = self.method.clone(); + let headers = self.headers.clone(); + + // reqwest client is not 'Sync' but channel is. + tokio::spawn(async move { + let client = default_client(); + let mut builder = client.request(method.clone(), url).headers(headers); + if let Some(body) = body { + builder = builder.body(body); + } + + let response = builder.send().await; + let _ = tx.send(response); + }); + + let response = rx.await.map_err(|e| { + let mag = format!("Receive http response channel error: {}", e); + FlowyError::internal().context(mag) + })??; + tracing::trace!("Http Response: {:?}", response); + let flowy_response = flowy_response_from(response).await?; + let token = self.token(); + self.middleware.iter().for_each(|middleware| { + middleware.receive_response(&token, &flowy_response); + }); + match flowy_response.error { + None => { + self.response = Some(flowy_response.data); Ok(self) + }, + Some(error) => Err(FlowyError::new(error.code, &error.msg)), } - - pub async fn send(self) -> Result<(), FlowyError> { - let _ = self.inner_send().await?; - Ok(()) - } - - pub async fn response(self) -> Result - where - T: TryFrom, - { - let builder = self.inner_send().await?; - match builder.response { - None => Err(unexpected_empty_payload(&builder.url)), - Some(data) => Ok(T::try_from(data)?), - } - } - - pub async fn json_response(self) -> Result - where - T: serde::de::DeserializeOwned, - { - let builder = self.inner_send().await?; - match builder.response { - None => Err(unexpected_empty_payload(&builder.url)), - Some(data) => Ok(serde_json::from_slice(&data)?), - } - } - - #[allow(dead_code)] - pub async fn option_protobuf_response(self) -> Result, FlowyError> - where - T: TryFrom, - { - let result = self.inner_send().await; - match result { - Ok(builder) => match builder.response { - None => Err(unexpected_empty_payload(&builder.url)), - Some(data) => Ok(Some(T::try_from(data)?)), - }, - Err(error) => match error.is_record_not_found() { - true => Ok(None), - false => Err(error), - }, - } - } - - pub async fn option_json_response(self) -> Result, FlowyError> - where - T: serde::de::DeserializeOwned + 'static, - { - let result = self.inner_send().await; - match result { - Ok(builder) => match builder.response { - None => Err(unexpected_empty_payload(&builder.url)), - Some(data) => Ok(Some(serde_json::from_slice(&data)?)), - }, - Err(error) => match error.is_record_not_found() { - true => Ok(None), - false => Err(error), - }, - } - } - - fn token(&self) -> Option { - match self.headers.get(HEADER_TOKEN) { - None => None, - Some(header) => match header.to_str() { - Ok(val) => Some(val.to_owned()), - Err(_) => None, - }, - } - } - - async fn inner_send(mut self) -> Result { - let (tx, rx) = oneshot::channel::>(); - let url = self.url.clone(); - let body = self.body.take(); - let method = self.method.clone(); - let headers = self.headers.clone(); - - // reqwest client is not 'Sync' but channel is. - tokio::spawn(async move { - let client = default_client(); - let mut builder = client.request(method.clone(), url).headers(headers); - if let Some(body) = body { - builder = builder.body(body); - } - - let response = builder.send().await; - let _ = tx.send(response); - }); - - let response = rx.await.map_err(|e| { - let mag = format!("Receive http response channel error: {}", e); - FlowyError::internal().context(mag) - })??; - tracing::trace!("Http Response: {:?}", response); - let flowy_response = flowy_response_from(response).await?; - let token = self.token(); - self.middleware.iter().for_each(|middleware| { - middleware.receive_response(&token, &flowy_response); - }); - match flowy_response.error { - None => { - self.response = Some(flowy_response.data); - Ok(self) - } - Some(error) => Err(FlowyError::new(error.code, &error.msg)), - } - } + } } fn unexpected_empty_payload(url: &str) -> FlowyError { - let msg = format!("Request: {} receives unexpected empty payload", url); - FlowyError::payload_none().context(msg) + let msg = format!("Request: {} receives unexpected empty payload", url); + FlowyError::payload_none().context(msg) } async fn flowy_response_from(original: Response) -> Result { - let bytes = original.bytes().await?; - let response: HttpResponse = serde_json::from_slice(&bytes)?; - Ok(response) + let bytes = original.bytes().await?; + let response: HttpResponse = serde_json::from_slice(&bytes)?; + Ok(response) } #[allow(dead_code)] async fn get_response_data(original: Response) -> Result { - if original.status() == http::StatusCode::OK { - let bytes = original.bytes().await?; - let response: HttpResponse = serde_json::from_slice(&bytes)?; - match response.error { - None => Ok(response.data), - Some(error) => Err(FlowyError::new(error.code, &error.msg)), - } - } else { - Err(FlowyError::http().context(original)) + if original.status() == http::StatusCode::OK { + let bytes = original.bytes().await?; + let response: HttpResponse = serde_json::from_slice(&bytes)?; + match response.error { + None => Ok(response.data), + Some(error) => Err(FlowyError::new(error.code, &error.msg)), } + } else { + Err(FlowyError::http().context(original)) + } } fn default_client() -> Client { - let result = reqwest::Client::builder() - .connect_timeout(Duration::from_millis(500)) - .timeout(Duration::from_secs(5)) - .build(); + let result = reqwest::Client::builder() + .connect_timeout(Duration::from_millis(500)) + .timeout(Duration::from_secs(5)) + .build(); - match result { - Ok(client) => client, - Err(e) => { - tracing::error!("Create reqwest client failed: {}", e); - reqwest::Client::new() - } - } + match result { + Ok(client) => client, + Err(e) => { + tracing::error!("Create reqwest client failed: {}", e); + reqwest::Client::new() + }, + } } diff --git a/frontend/rust-lib/flowy-net/src/response.rs b/frontend/rust-lib/flowy-net/src/response.rs index fdf4ec72fa..8e84d493b0 100644 --- a/frontend/rust-lib/flowy-net/src/response.rs +++ b/frontend/rust-lib/flowy-net/src/response.rs @@ -5,25 +5,25 @@ use std::fmt; #[derive(Debug, Serialize, Deserialize)] pub struct HttpResponse { - pub data: Bytes, - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, + pub data: Bytes, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, } #[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone)] pub struct HttpError { - pub code: ErrorCode, - pub msg: String, + pub code: ErrorCode, + pub msg: String, } impl HttpError { - pub fn is_unauthorized(&self) -> bool { - self.code == ErrorCode::UserUnauthorized - } + pub fn is_unauthorized(&self) -> bool { + self.code == ErrorCode::UserUnauthorized + } } impl fmt::Display for HttpError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}: {}", self.code, self.msg) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}: {}", self.code, self.msg) + } } diff --git a/frontend/rust-lib/flowy-notification/build.rs b/frontend/rust-lib/flowy-notification/build.rs index 90fa29ba4c..d43384c1ae 100644 --- a/frontend/rust-lib/flowy-notification/build.rs +++ b/frontend/rust-lib/flowy-notification/build.rs @@ -1,3 +1,3 @@ fn main() { - flowy_codegen::protobuf_file::gen(env!("CARGO_PKG_NAME")); + flowy_codegen::protobuf_file::gen(env!("CARGO_PKG_NAME")); } diff --git a/frontend/rust-lib/flowy-notification/src/entities/subject.rs b/frontend/rust-lib/flowy-notification/src/entities/subject.rs index 3f9b71b0e6..79fba0e53f 100644 --- a/frontend/rust-lib/flowy-notification/src/entities/subject.rs +++ b/frontend/rust-lib/flowy-notification/src/entities/subject.rs @@ -3,45 +3,45 @@ use std::{fmt, fmt::Formatter}; #[derive(Debug, Clone, ProtoBuf, serde::Serialize)] pub struct SubscribeObject { - #[pb(index = 1)] - pub source: String, + #[pb(index = 1)] + pub source: String, - #[pb(index = 2)] - pub ty: i32, + #[pb(index = 2)] + pub ty: i32, - #[pb(index = 3)] - pub id: String, + #[pb(index = 3)] + pub id: String, - #[pb(index = 4, one_of)] - pub payload: Option>, + #[pb(index = 4, one_of)] + pub payload: Option>, - #[pb(index = 5, one_of)] - pub error: Option>, + #[pb(index = 5, one_of)] + pub error: Option>, } impl std::fmt::Display for SubscribeObject { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(&format!("{} changed: ", &self.source))?; - if let Some(payload) = &self.payload { - f.write_str(&format!("send {} payload", payload.len()))?; - } - - if let Some(payload) = &self.error { - f.write_str(&format!("receive {} error", payload.len()))?; - } - - Ok(()) + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(&format!("{} changed: ", &self.source))?; + if let Some(payload) = &self.payload { + f.write_str(&format!("send {} payload", payload.len()))?; } + + if let Some(payload) = &self.error { + f.write_str(&format!("receive {} error", payload.len()))?; + } + + Ok(()) + } } impl std::default::Default for SubscribeObject { - fn default() -> Self { - Self { - source: "".to_string(), - ty: 0, - id: "".to_string(), - payload: None, - error: None, - } + fn default() -> Self { + Self { + source: "".to_string(), + ty: 0, + id: "".to_string(), + payload: None, + error: None, } + } } diff --git a/frontend/rust-lib/flowy-notification/src/lib.rs b/frontend/rust-lib/flowy-notification/src/lib.rs index c005b84e02..1b29026408 100644 --- a/frontend/rust-lib/flowy-notification/src/lib.rs +++ b/frontend/rust-lib/flowy-notification/src/lib.rs @@ -8,87 +8,87 @@ use lib_dispatch::prelude::ToBytes; use std::sync::RwLock; lazy_static! { - static ref NOTIFICATION_SENDER: RwLock>> = RwLock::new(vec![]); + static ref NOTIFICATION_SENDER: RwLock>> = RwLock::new(vec![]); } pub fn register_notification_sender(sender: T) { - let box_sender = Box::new(sender); - match NOTIFICATION_SENDER.write() { - Ok(mut write_guard) => write_guard.push(box_sender), - Err(err) => tracing::error!("Failed to push notification sender: {:?}", err), - } + let box_sender = Box::new(sender); + match NOTIFICATION_SENDER.write() { + Ok(mut write_guard) => write_guard.push(box_sender), + Err(err) => tracing::error!("Failed to push notification sender: {:?}", err), + } } pub trait NotificationSender: Send + Sync + 'static { - fn send_subject(&self, subject: SubscribeObject) -> Result<(), String>; + fn send_subject(&self, subject: SubscribeObject) -> Result<(), String>; } pub struct NotificationBuilder { - id: String, - payload: Option, - error: Option, - source: String, - ty: i32, + id: String, + payload: Option, + error: Option, + source: String, + ty: i32, } impl NotificationBuilder { - pub fn new>(id: &str, ty: T, source: &str) -> Self { - Self { - id: id.to_owned(), - ty: ty.into(), - payload: None, - error: None, - source: source.to_owned(), - } + pub fn new>(id: &str, ty: T, source: &str) -> Self { + Self { + id: id.to_owned(), + ty: ty.into(), + payload: None, + error: None, + source: source.to_owned(), + } + } + + pub fn payload(mut self, payload: T) -> Self + where + T: ToBytes, + { + match payload.into_bytes() { + Ok(bytes) => self.payload = Some(bytes), + Err(e) => { + tracing::error!("Set observable payload failed: {:?}", e); + }, } - pub fn payload(mut self, payload: T) -> Self - where - T: ToBytes, - { - match payload.into_bytes() { - Ok(bytes) => self.payload = Some(bytes), - Err(e) => { - tracing::error!("Set observable payload failed: {:?}", e); - } - } + self + } - self + pub fn error(mut self, error: T) -> Self + where + T: ToBytes, + { + match error.into_bytes() { + Ok(bytes) => self.error = Some(bytes), + Err(e) => { + tracing::error!("Set observable error failed: {:?}", e); + }, } + self + } - pub fn error(mut self, error: T) -> Self - where - T: ToBytes, - { - match error.into_bytes() { - Ok(bytes) => self.error = Some(bytes), - Err(e) => { - tracing::error!("Set observable error failed: {:?}", e); - } - } - self - } - - pub fn send(self) { - let payload = self.payload.map(|bytes| bytes.to_vec()); - let error = self.error.map(|bytes| bytes.to_vec()); - let subject = SubscribeObject { - source: self.source, - ty: self.ty, - id: self.id, - payload, - error, - }; - - match NOTIFICATION_SENDER.read() { - Ok(read_guard) => read_guard.iter().for_each(|sender| { - if let Err(e) = sender.send_subject(subject.clone()) { - tracing::error!("Post notification failed: {}", e); - } - }), - Err(err) => { - tracing::error!("Read notification sender failed: {}", err); - } + pub fn send(self) { + let payload = self.payload.map(|bytes| bytes.to_vec()); + let error = self.error.map(|bytes| bytes.to_vec()); + let subject = SubscribeObject { + source: self.source, + ty: self.ty, + id: self.id, + payload, + error, + }; + + match NOTIFICATION_SENDER.read() { + Ok(read_guard) => read_guard.iter().for_each(|sender| { + if let Err(e) = sender.send_subject(subject.clone()) { + tracing::error!("Post notification failed: {}", e); } + }), + Err(err) => { + tracing::error!("Read notification sender failed: {}", err); + }, } + } } diff --git a/frontend/rust-lib/flowy-revision-persistence/src/disk_cache_impl/file_persistence.rs b/frontend/rust-lib/flowy-revision-persistence/src/disk_cache_impl/file_persistence.rs index 1cb3001b99..6d0ae839b0 100644 --- a/frontend/rust-lib/flowy-revision-persistence/src/disk_cache_impl/file_persistence.rs +++ b/frontend/rust-lib/flowy-revision-persistence/src/disk_cache_impl/file_persistence.rs @@ -3,52 +3,56 @@ use flowy_error::FlowyResult; use revision_model::RevisionRange; pub struct FileRevisionDiskCache { - path: String, + path: String, } pub type FileRevisionDiskCacheConnection = (); impl RevisionDiskCache for FileRevisionDiskCache { - type Error = (); + type Error = (); - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - Ok(()) - } + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + Ok(()) + } - fn get_connection(&self) -> Result { - return Ok(()); - } + fn get_connection(&self) -> Result { + return Ok(()); + } - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - Ok(vec![]) - } + fn read_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result, Self::Error> { + Ok(vec![]) + } - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - Ok(vec![]) - } + fn read_revision_records_with_range( + &self, + object_id: &str, + range: &RevisionRange, + ) -> Result, Self::Error> { + Ok(vec![]) + } - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - Ok(()) - } + fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { + Ok(()) + } - fn delete_revision_records(&self, object_id: &str, rev_ids: Option>) -> Result<(), Self::Error> { - Ok(()) - } + fn delete_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result<(), Self::Error> { + Ok(()) + } - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error> { - todo!() - } + fn delete_and_insert_records( + &self, + object_id: &str, + deleted_rev_ids: Option>, + inserted_records: Vec, + ) -> Result<(), Self::Error> { + todo!() + } } diff --git a/frontend/rust-lib/flowy-revision-persistence/src/lib.rs b/frontend/rust-lib/flowy-revision-persistence/src/lib.rs index d6006af9fc..a3cc3d689a 100644 --- a/frontend/rust-lib/flowy-revision-persistence/src/lib.rs +++ b/frontend/rust-lib/flowy-revision-persistence/src/lib.rs @@ -6,131 +6,142 @@ use std::fmt::Debug; use std::sync::Arc; pub trait RevisionDiskCache: Sync + Send { - type Error: Debug; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error>; + type Error: Debug; + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error>; - fn get_connection(&self) -> Result; + fn get_connection(&self) -> Result; - // Read all the records if the rev_ids is None - fn read_revision_records(&self, object_id: &str, rev_ids: Option>) - -> Result, Self::Error>; + // Read all the records if the rev_ids is None + fn read_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result, Self::Error>; - // Read the revision which rev_id >= range.start && rev_id <= range.end - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error>; + // Read the revision which rev_id >= range.start && rev_id <= range.end + fn read_revision_records_with_range( + &self, + object_id: &str, + range: &RevisionRange, + ) -> Result, Self::Error>; - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()>; + fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()>; - // Delete all the records if the rev_ids is None - fn delete_revision_records(&self, object_id: &str, rev_ids: Option>) -> Result<(), Self::Error>; + // Delete all the records if the rev_ids is None + fn delete_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result<(), Self::Error>; - // Delete and insert will be executed in the same transaction. - // It deletes all the records if the deleted_rev_ids is None and then insert the new records - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error>; + // Delete and insert will be executed in the same transaction. + // It deletes all the records if the deleted_rev_ids is None and then insert the new records + fn delete_and_insert_records( + &self, + object_id: &str, + deleted_rev_ids: Option>, + inserted_records: Vec, + ) -> Result<(), Self::Error>; } impl RevisionDiskCache for Arc where - T: RevisionDiskCache, + T: RevisionDiskCache, { - type Error = FlowyError; + type Error = FlowyError; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - (**self).create_revision_records(revision_records) - } + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + (**self).create_revision_records(revision_records) + } - fn get_connection(&self) -> Result { - (**self).get_connection() - } + fn get_connection(&self) -> Result { + (**self).get_connection() + } - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - (**self).read_revision_records(object_id, rev_ids) - } + fn read_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result, Self::Error> { + (**self).read_revision_records(object_id, rev_ids) + } - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - (**self).read_revision_records_with_range(object_id, range) - } + fn read_revision_records_with_range( + &self, + object_id: &str, + range: &RevisionRange, + ) -> Result, Self::Error> { + (**self).read_revision_records_with_range(object_id, range) + } - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - (**self).update_revision_record(changesets) - } + fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { + (**self).update_revision_record(changesets) + } - fn delete_revision_records(&self, object_id: &str, rev_ids: Option>) -> Result<(), Self::Error> { - (**self).delete_revision_records(object_id, rev_ids) - } + fn delete_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result<(), Self::Error> { + (**self).delete_revision_records(object_id, rev_ids) + } - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error> { - (**self).delete_and_insert_records(object_id, deleted_rev_ids, inserted_records) - } + fn delete_and_insert_records( + &self, + object_id: &str, + deleted_rev_ids: Option>, + inserted_records: Vec, + ) -> Result<(), Self::Error> { + (**self).delete_and_insert_records(object_id, deleted_rev_ids, inserted_records) + } } #[derive(Clone, Debug)] pub struct SyncRecord { - pub revision: Revision, - pub state: RevisionState, - pub write_to_disk: bool, + pub revision: Revision, + pub state: RevisionState, + pub write_to_disk: bool, } impl SyncRecord { - pub fn new(revision: Revision) -> Self { - Self { - revision, - state: RevisionState::Sync, - write_to_disk: true, - } + pub fn new(revision: Revision) -> Self { + Self { + revision, + state: RevisionState::Sync, + write_to_disk: true, } + } - pub fn ack(&mut self) { - self.state = RevisionState::Ack; - } + pub fn ack(&mut self) { + self.state = RevisionState::Ack; + } } pub struct RevisionChangeset { - pub object_id: String, - pub rev_id: i64, - pub state: RevisionState, + pub object_id: String, + pub rev_id: i64, + pub state: RevisionState, } /// Sync: revision is not synced to the server /// Ack: revision is synced to the server #[derive(Debug, Clone, Eq, PartialEq)] pub enum RevisionState { - Sync = 0, - Ack = 1, + Sync = 0, + Ack = 1, } impl RevisionState { - pub fn is_need_sync(&self) -> bool { - match self { - RevisionState::Sync => true, - RevisionState::Ack => false, - } + pub fn is_need_sync(&self) -> bool { + match self { + RevisionState::Sync => true, + RevisionState::Ack => false, } + } } impl AsRef for RevisionState { - fn as_ref(&self) -> &RevisionState { - self - } + fn as_ref(&self) -> &RevisionState { + self + } } diff --git a/frontend/rust-lib/flowy-revision/src/cache/memory.rs b/frontend/rust-lib/flowy-revision/src/cache/memory.rs index 6942f71e27..9eaff2dbb6 100644 --- a/frontend/rust-lib/flowy-revision/src/cache/memory.rs +++ b/frontend/rust-lib/flowy-revision/src/cache/memory.rs @@ -7,143 +7,148 @@ use std::{borrow::Cow, sync::Arc, time::Duration}; use tokio::{sync::RwLock, task::JoinHandle}; pub(crate) trait RevisionMemoryCacheDelegate: Send + Sync { - fn send_sync(&self, records: Vec) -> FlowyResult<()>; - fn receive_ack(&self, object_id: &str, rev_id: i64); + fn send_sync(&self, records: Vec) -> FlowyResult<()>; + fn receive_ack(&self, object_id: &str, rev_id: i64); } pub(crate) struct RevisionMemoryCache { - object_id: String, - revs_map: Arc>, - delegate: Arc, - defer_write_revs: Arc>>, - defer_save: RwLock>>, + object_id: String, + revs_map: Arc>, + delegate: Arc, + defer_write_revs: Arc>>, + defer_save: RwLock>>, } impl RevisionMemoryCache { - pub(crate) fn new(object_id: &str, delegate: Arc) -> Self { - RevisionMemoryCache { - object_id: object_id.to_owned(), - revs_map: Arc::new(DashMap::new()), - delegate, - defer_write_revs: Arc::new(RwLock::new(vec![])), - defer_save: RwLock::new(None), - } + pub(crate) fn new(object_id: &str, delegate: Arc) -> Self { + RevisionMemoryCache { + object_id: object_id.to_owned(), + revs_map: Arc::new(DashMap::new()), + delegate, + defer_write_revs: Arc::new(RwLock::new(vec![])), + defer_save: RwLock::new(None), + } + } + + pub(crate) fn contains(&self, rev_id: &i64) -> bool { + self.revs_map.contains_key(rev_id) + } + + pub(crate) async fn add<'a>(&'a self, record: Cow<'a, SyncRecord>) { + let record = match record { + Cow::Borrowed(record) => record.clone(), + Cow::Owned(record) => record, + }; + + let rev_id = record.revision.rev_id; + self.revs_map.insert(rev_id, record); + + let mut write_guard = self.defer_write_revs.write().await; + if !write_guard.contains(&rev_id) { + write_guard.push(rev_id); + drop(write_guard); + self.tick_checkpoint().await; + } + } + + pub(crate) async fn ack(&self, rev_id: &i64) { + match self.revs_map.get_mut(rev_id) { + None => {}, + Some(mut record) => record.ack(), } - pub(crate) fn contains(&self, rev_id: &i64) -> bool { - self.revs_map.contains_key(rev_id) + if self.defer_write_revs.read().await.contains(rev_id) { + self.tick_checkpoint().await; + } else { + // The revision must be saved on disk if the pending_write_revs + // doesn't contains the rev_id. + self.delegate.receive_ack(&self.object_id, *rev_id); + } + } + + pub(crate) async fn get(&self, rev_id: &i64) -> Option { + self.revs_map.get(rev_id).map(|r| r.value().clone()) + } + + pub(crate) fn remove(&self, rev_id: &i64) { + let _ = self.revs_map.remove(rev_id); + } + + pub(crate) fn remove_with_range(&self, range: &RevisionRange) { + for rev_id in range.iter() { + self.remove(&rev_id); + } + } + + pub(crate) async fn get_with_range( + &self, + range: &RevisionRange, + ) -> Result, FlowyError> { + let revs = range + .iter() + .flat_map(|rev_id| self.revs_map.get(&rev_id).map(|record| record.clone())) + .collect::>(); + Ok(revs) + } + + pub(crate) fn number_of_sync_records(&self) -> usize { + self.revs_map.len() + } + + pub(crate) async fn reset_with_revisions(&self, revision_records: Vec) { + self.revs_map.clear(); + if let Some(handler) = self.defer_save.write().await.take() { + handler.abort(); } - pub(crate) async fn add<'a>(&'a self, record: Cow<'a, SyncRecord>) { - let record = match record { - Cow::Borrowed(record) => record.clone(), - Cow::Owned(record) => record, - }; + let mut write_guard = self.defer_write_revs.write().await; + write_guard.clear(); + for record in revision_records { + write_guard.push(record.revision.rev_id); + self.revs_map.insert(record.revision.rev_id, record); + } + drop(write_guard); - let rev_id = record.revision.rev_id; - self.revs_map.insert(rev_id, record); + self.tick_checkpoint().await; + } - let mut write_guard = self.defer_write_revs.write().await; - if !write_guard.contains(&rev_id) { - write_guard.push(rev_id); - drop(write_guard); - self.tick_checkpoint().await; - } + async fn tick_checkpoint(&self) { + // https://github.com/async-graphql/async-graphql/blob/ed8449beec3d9c54b94da39bab33cec809903953/src/dataloader/mod.rs#L362 + if let Some(handler) = self.defer_save.write().await.take() { + handler.abort(); } - pub(crate) async fn ack(&self, rev_id: &i64) { - match self.revs_map.get_mut(rev_id) { - None => {} - Some(mut record) => record.ack(), - } - - if self.defer_write_revs.read().await.contains(rev_id) { - self.tick_checkpoint().await; - } else { - // The revision must be saved on disk if the pending_write_revs - // doesn't contains the rev_id. - self.delegate.receive_ack(&self.object_id, *rev_id); - } + if self.defer_write_revs.read().await.is_empty() { + return; } - pub(crate) async fn get(&self, rev_id: &i64) -> Option { - self.revs_map.get(rev_id).map(|r| r.value().clone()) - } + let rev_map = self.revs_map.clone(); + let pending_write_revs = self.defer_write_revs.clone(); + let delegate = self.delegate.clone(); - pub(crate) fn remove(&self, rev_id: &i64) { - let _ = self.revs_map.remove(rev_id); - } + *self.defer_save.write().await = Some(tokio::spawn(async move { + tokio::time::sleep(Duration::from_millis(REVISION_WRITE_INTERVAL_IN_MILLIS)).await; + let mut revs_write_guard = pending_write_revs.write().await; + // It may cause performance issues because we hold the write lock of the + // rev_order and the lock will be released after the checkpoint has been written + // to the disk. + // + // Use saturating_sub and split_off ? + // https://stackoverflow.com/questions/28952411/what-is-the-idiomatic-way-to-pop-the-last-n-elements-in-a-mutable-vec + let mut save_records: Vec = vec![]; + revs_write_guard + .iter() + .for_each(|rev_id| match rev_map.get(rev_id) { + None => {}, + Some(value) => { + save_records.push(value.value().clone()); + }, + }); - pub(crate) fn remove_with_range(&self, range: &RevisionRange) { - for rev_id in range.iter() { - self.remove(&rev_id); - } - } - - pub(crate) async fn get_with_range(&self, range: &RevisionRange) -> Result, FlowyError> { - let revs = range - .iter() - .flat_map(|rev_id| self.revs_map.get(&rev_id).map(|record| record.clone())) - .collect::>(); - Ok(revs) - } - - pub(crate) fn number_of_sync_records(&self) -> usize { - self.revs_map.len() - } - - pub(crate) async fn reset_with_revisions(&self, revision_records: Vec) { - self.revs_map.clear(); - if let Some(handler) = self.defer_save.write().await.take() { - handler.abort(); - } - - let mut write_guard = self.defer_write_revs.write().await; - write_guard.clear(); - for record in revision_records { - write_guard.push(record.revision.rev_id); - self.revs_map.insert(record.revision.rev_id, record); - } - drop(write_guard); - - self.tick_checkpoint().await; - } - - async fn tick_checkpoint(&self) { - // https://github.com/async-graphql/async-graphql/blob/ed8449beec3d9c54b94da39bab33cec809903953/src/dataloader/mod.rs#L362 - if let Some(handler) = self.defer_save.write().await.take() { - handler.abort(); - } - - if self.defer_write_revs.read().await.is_empty() { - return; - } - - let rev_map = self.revs_map.clone(); - let pending_write_revs = self.defer_write_revs.clone(); - let delegate = self.delegate.clone(); - - *self.defer_save.write().await = Some(tokio::spawn(async move { - tokio::time::sleep(Duration::from_millis(REVISION_WRITE_INTERVAL_IN_MILLIS)).await; - let mut revs_write_guard = pending_write_revs.write().await; - // It may cause performance issues because we hold the write lock of the - // rev_order and the lock will be released after the checkpoint has been written - // to the disk. - // - // Use saturating_sub and split_off ? - // https://stackoverflow.com/questions/28952411/what-is-the-idiomatic-way-to-pop-the-last-n-elements-in-a-mutable-vec - let mut save_records: Vec = vec![]; - revs_write_guard.iter().for_each(|rev_id| match rev_map.get(rev_id) { - None => {} - Some(value) => { - save_records.push(value.value().clone()); - } - }); - - if delegate.send_sync(save_records).is_ok() { - revs_write_guard.clear(); - drop(revs_write_guard); - } - })); - } + if delegate.send_sync(save_records).is_ok() { + revs_write_guard.clear(); + drop(revs_write_guard); + } + })); + } } diff --git a/frontend/rust-lib/flowy-revision/src/cache/reset.rs b/frontend/rust-lib/flowy-revision/src/cache/reset.rs index ee5971b30d..ba29870ec3 100644 --- a/frontend/rust-lib/flowy-revision/src/cache/reset.rs +++ b/frontend/rust-lib/flowy-revision/src/cache/reset.rs @@ -8,113 +8,118 @@ use std::str::FromStr; use std::sync::Arc; pub trait RevisionResettable { - fn target_id(&self) -> &str; + fn target_id(&self) -> &str; - // String in json format - fn reset_data(&self, revisions: Vec) -> FlowyResult; + // String in json format + fn reset_data(&self, revisions: Vec) -> FlowyResult; - // String in json format - fn default_target_rev_str(&self) -> FlowyResult; + // String in json format + fn default_target_rev_str(&self) -> FlowyResult; - fn read_record(&self) -> Option; + fn read_record(&self) -> Option; - fn set_record(&self, record: String); + fn set_record(&self, record: String); } pub struct RevisionStructReset { - user_id: String, - target: T, - disk_cache: Arc>, + user_id: String, + target: T, + disk_cache: Arc>, } impl RevisionStructReset where - T: RevisionResettable, - C: 'static, + T: RevisionResettable, + C: 'static, { - pub fn new(user_id: &str, object: T, disk_cache: Arc>) -> Self { - Self { - user_id: user_id.to_owned(), - target: object, - disk_cache, - } + pub fn new( + user_id: &str, + object: T, + disk_cache: Arc>, + ) -> Self { + Self { + user_id: user_id.to_owned(), + target: object, + disk_cache, } + } - pub async fn run(&self) -> FlowyResult<()> { - match self.target.read_record() { - None => { - self.reset_object().await?; - self.save_migrate_record()?; - } - Some(s) => { - let mut record = MigrationObjectRecord::from_str(&s).map_err(|e| FlowyError::serde().context(e))?; - let rev_str = self.target.default_target_rev_str()?; - if record.len < rev_str.len() { - self.reset_object().await?; - record.len = rev_str.len(); - self.target.set_record(record.to_string()); - } - } - } - Ok(()) - } - - async fn reset_object(&self) -> FlowyResult<()> { - let configuration = RevisionPersistenceConfiguration::new(2, false); - let rev_persistence = Arc::new(RevisionPersistence::from_disk_cache( - &self.user_id, - self.target.target_id(), - self.disk_cache.clone(), - configuration, - )); - let revisions = RevisionLoader { - object_id: self.target.target_id().to_owned(), - user_id: self.user_id.clone(), - cloud: None, - rev_persistence, - } - .load_revisions() - .await?; - - let bytes = self.target.reset_data(revisions)?; - let revision = Revision::initial_revision(self.target.target_id(), bytes); - let record = SyncRecord::new(revision); - - tracing::trace!("Reset {} revision record object", self.target.target_id()); - let _ = self - .disk_cache - .delete_and_insert_records(self.target.target_id(), None, vec![record]); - - Ok(()) - } - - fn save_migrate_record(&self) -> FlowyResult<()> { + pub async fn run(&self) -> FlowyResult<()> { + match self.target.read_record() { + None => { + self.reset_object().await?; + self.save_migrate_record()?; + }, + Some(s) => { + let mut record = + MigrationObjectRecord::from_str(&s).map_err(|e| FlowyError::serde().context(e))?; let rev_str = self.target.default_target_rev_str()?; - let record = MigrationObjectRecord { - object_id: self.target.target_id().to_owned(), - len: rev_str.len(), - }; - self.target.set_record(record.to_string()); - Ok(()) + if record.len < rev_str.len() { + self.reset_object().await?; + record.len = rev_str.len(); + self.target.set_record(record.to_string()); + } + }, } + Ok(()) + } + + async fn reset_object(&self) -> FlowyResult<()> { + let configuration = RevisionPersistenceConfiguration::new(2, false); + let rev_persistence = Arc::new(RevisionPersistence::from_disk_cache( + &self.user_id, + self.target.target_id(), + self.disk_cache.clone(), + configuration, + )); + let revisions = RevisionLoader { + object_id: self.target.target_id().to_owned(), + user_id: self.user_id.clone(), + cloud: None, + rev_persistence, + } + .load_revisions() + .await?; + + let bytes = self.target.reset_data(revisions)?; + let revision = Revision::initial_revision(self.target.target_id(), bytes); + let record = SyncRecord::new(revision); + + tracing::trace!("Reset {} revision record object", self.target.target_id()); + let _ = self + .disk_cache + .delete_and_insert_records(self.target.target_id(), None, vec![record]); + + Ok(()) + } + + fn save_migrate_record(&self) -> FlowyResult<()> { + let rev_str = self.target.default_target_rev_str()?; + let record = MigrationObjectRecord { + object_id: self.target.target_id().to_owned(), + len: rev_str.len(), + }; + self.target.set_record(record.to_string()); + Ok(()) + } } #[derive(Serialize, Deserialize)] struct MigrationObjectRecord { - object_id: String, - len: usize, + object_id: String, + len: usize, } impl FromStr for MigrationObjectRecord { - type Err = serde_json::Error; + type Err = serde_json::Error; - fn from_str(s: &str) -> Result { - serde_json::from_str::(s) - } + fn from_str(s: &str) -> Result { + serde_json::from_str::(s) + } } impl ToString for MigrationObjectRecord { - fn to_string(&self) -> String { - serde_json::to_string(self).unwrap_or_else(|_| "".to_string()) - } + fn to_string(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|_| "".to_string()) + } } diff --git a/frontend/rust-lib/flowy-revision/src/conflict_resolve.rs b/frontend/rust-lib/flowy-revision/src/conflict_resolve.rs index 887b49dfbc..4d1c182078 100644 --- a/frontend/rust-lib/flowy-revision/src/conflict_resolve.rs +++ b/frontend/rust-lib/flowy-revision/src/conflict_resolve.rs @@ -6,166 +6,178 @@ use revision_model::{Revision, RevisionRange}; use std::sync::Arc; pub struct TransformOperations { - pub client_operations: Operations, - pub server_operations: Option, + pub client_operations: Operations, + pub server_operations: Option, } pub trait OperationsDeserializer: Send + Sync { - fn deserialize_revisions(revisions: Vec) -> FlowyResult; + fn deserialize_revisions(revisions: Vec) -> FlowyResult; } pub trait OperationsSerializer: Send + Sync { - fn serialize_operations(&self) -> Bytes; + fn serialize_operations(&self) -> Bytes; } pub struct ConflictOperations(T); pub trait ConflictResolver where - Operations: Send + Sync, + Operations: Send + Sync, { - fn compose_operations(&self, operations: Operations) -> BoxResultFuture; - fn transform_operations( - &self, - operations: Operations, - ) -> BoxResultFuture, FlowyError>; - fn reset_operations(&self, operations: Operations) -> BoxResultFuture; + fn compose_operations(&self, operations: Operations) -> BoxResultFuture; + fn transform_operations( + &self, + operations: Operations, + ) -> BoxResultFuture, FlowyError>; + fn reset_operations(&self, operations: Operations) -> BoxResultFuture; } pub trait ConflictRevisionSink: Send + Sync + 'static { - fn send(&self, revisions: Vec) -> BoxResultFuture<(), FlowyError>; - fn ack(&self, rev_id: i64) -> BoxResultFuture<(), FlowyError>; + fn send(&self, revisions: Vec) -> BoxResultFuture<(), FlowyError>; + fn ack(&self, rev_id: i64) -> BoxResultFuture<(), FlowyError>; } pub struct ConflictController where - Operations: Send + Sync, + Operations: Send + Sync, { - user_id: String, + user_id: String, + resolver: Arc + Send + Sync>, + rev_sink: Arc, + rev_manager: Arc>, +} + +impl ConflictController +where + Operations: Clone + Send + Sync, + Connection: 'static, +{ + pub fn new( + user_id: &str, resolver: Arc + Send + Sync>, rev_sink: Arc, rev_manager: Arc>, -} - -impl ConflictController -where - Operations: Clone + Send + Sync, - Connection: 'static, -{ - pub fn new( - user_id: &str, - resolver: Arc + Send + Sync>, - rev_sink: Arc, - rev_manager: Arc>, - ) -> Self { - let user_id = user_id.to_owned(); - Self { - user_id, - resolver, - rev_sink, - rev_manager, - } + ) -> Self { + let user_id = user_id.to_owned(); + Self { + user_id, + resolver, + rev_sink, + rev_manager, } + } } impl ConflictController where - Operations: OperationsSerializer + OperationsDeserializer + Clone + Send + Sync, - Connection: Send + Sync + 'static, + Operations: OperationsSerializer + OperationsDeserializer + Clone + Send + Sync, + Connection: Send + Sync + 'static, { - pub async fn receive_revisions(&self, revisions: Vec) -> FlowyResult<()> { + pub async fn receive_revisions(&self, revisions: Vec) -> FlowyResult<()> { + if revisions.is_empty() { + return Ok(()); + } + + match self.handle_revision(revisions).await? { + None => {}, + Some(server_revision) => { + self.rev_sink.send(vec![server_revision]).await?; + }, + } + Ok(()) + } + + pub async fn ack_revision(&self, rev_id: i64) -> FlowyResult<()> { + self.rev_sink.ack(rev_id).await?; + Ok(()) + } + + pub async fn send_revisions(&self, range: RevisionRange) -> FlowyResult<()> { + let revisions = self.rev_manager.get_revisions_in_range(range).await?; + self.rev_sink.send(revisions).await?; + Ok(()) + } + + async fn handle_revision(&self, mut revisions: Vec) -> FlowyResult> { + let first_revision = revisions.first().unwrap(); + if let Some(local_revision) = self.rev_manager.get_revision(first_revision.rev_id).await { + if local_revision.md5 == first_revision.md5 { + // The local revision is equal to the pushed revision. Just ignore it. + revisions = revisions.split_off(1); if revisions.is_empty() { - return Ok(()); + return Ok(None); } - - match self.handle_revision(revisions).await? { - None => {} - Some(server_revision) => { - self.rev_sink.send(vec![server_revision]).await?; - } - } - Ok(()) + } else { + return Ok(None); + } } - pub async fn ack_revision(&self, rev_id: i64) -> FlowyResult<()> { - self.rev_sink.ack(rev_id).await?; - Ok(()) - } + let new_operations = Operations::deserialize_revisions(revisions.clone())?; + let TransformOperations { + client_operations, + server_operations, + } = self.resolver.transform_operations(new_operations).await?; - pub async fn send_revisions(&self, range: RevisionRange) -> FlowyResult<()> { - let revisions = self.rev_manager.get_revisions_in_range(range).await?; - self.rev_sink.send(revisions).await?; - Ok(()) - } - - async fn handle_revision(&self, mut revisions: Vec) -> FlowyResult> { - let first_revision = revisions.first().unwrap(); - if let Some(local_revision) = self.rev_manager.get_revision(first_revision.rev_id).await { - if local_revision.md5 == first_revision.md5 { - // The local revision is equal to the pushed revision. Just ignore it. - revisions = revisions.split_off(1); - if revisions.is_empty() { - return Ok(None); - } - } else { - return Ok(None); - } - } - - let new_operations = Operations::deserialize_revisions(revisions.clone())?; - let TransformOperations { - client_operations, - server_operations, - } = self.resolver.transform_operations(new_operations).await?; - - match server_operations { - None => { - // The server_prime is None means the client local revisions conflict with the - // // server, and it needs to override the client delta. - let md5 = self.resolver.reset_operations(client_operations).await?; - debug_assert!(md5.is_equal(&revisions.last().unwrap().md5)); - self.rev_manager.reset_object(revisions).await?; - Ok(None) - } - Some(server_operations) => { - let md5 = self.resolver.compose_operations(client_operations.clone()).await?; - for revision in &revisions { - self.rev_manager.add_remote_revision(revision).await?; - } - let (client_revision, server_revision) = make_client_and_server_revision( - &self.user_id, - &self.rev_manager, - client_operations, - Some(server_operations), - md5, - ); - self.rev_manager.add_remote_revision(&client_revision).await?; - Ok(server_revision) - } + match server_operations { + None => { + // The server_prime is None means the client local revisions conflict with the + // // server, and it needs to override the client delta. + let md5 = self.resolver.reset_operations(client_operations).await?; + debug_assert!(md5.is_equal(&revisions.last().unwrap().md5)); + self.rev_manager.reset_object(revisions).await?; + Ok(None) + }, + Some(server_operations) => { + let md5 = self + .resolver + .compose_operations(client_operations.clone()) + .await?; + for revision in &revisions { + self.rev_manager.add_remote_revision(revision).await?; } + let (client_revision, server_revision) = make_client_and_server_revision( + &self.user_id, + &self.rev_manager, + client_operations, + Some(server_operations), + md5, + ); + self + .rev_manager + .add_remote_revision(&client_revision) + .await?; + Ok(server_revision) + }, } + } } fn make_client_and_server_revision( - _user_id: &str, - rev_manager: &Arc>, - client_operations: Operations, - server_operations: Option, - md5: RevisionMD5, + _user_id: &str, + rev_manager: &Arc>, + client_operations: Operations, + server_operations: Option, + md5: RevisionMD5, ) -> (Revision, Option) where - Operations: OperationsSerializer, - Connection: 'static, + Operations: OperationsSerializer, + Connection: 'static, { - let (base_rev_id, rev_id) = rev_manager.next_rev_id_pair(); - let bytes = client_operations.serialize_operations(); - let client_revision = Revision::new(&rev_manager.object_id, base_rev_id, rev_id, bytes, md5.clone()); + let (base_rev_id, rev_id) = rev_manager.next_rev_id_pair(); + let bytes = client_operations.serialize_operations(); + let client_revision = Revision::new( + &rev_manager.object_id, + base_rev_id, + rev_id, + bytes, + md5.clone(), + ); - match server_operations { - None => (client_revision, None), - Some(operations) => { - let bytes = operations.serialize_operations(); - let server_revision = Revision::new(&rev_manager.object_id, base_rev_id, rev_id, bytes, md5); - (client_revision, Some(server_revision)) - } - } + match server_operations { + None => (client_revision, None), + Some(operations) => { + let bytes = operations.serialize_operations(); + let server_revision = Revision::new(&rev_manager.object_id, base_rev_id, rev_id, bytes, md5); + (client_revision, Some(server_revision)) + }, + } } diff --git a/frontend/rust-lib/flowy-revision/src/rev_manager.rs b/frontend/rust-lib/flowy-revision/src/rev_manager.rs index 5f5f894d74..fbde49273b 100644 --- a/frontend/rust-lib/flowy-revision/src/rev_manager.rs +++ b/frontend/rust-lib/flowy-revision/src/rev_manager.rs @@ -1,7 +1,7 @@ use crate::rev_queue::{RevCommand, RevCommandSender, RevQueue}; use crate::{ - RevisionPersistence, RevisionSnapshotController, RevisionSnapshotData, RevisionSnapshotPersistence, - WSDataProviderDataSource, + RevisionPersistence, RevisionSnapshotController, RevisionSnapshotData, + RevisionSnapshotPersistence, WSDataProviderDataSource, }; use bytes::Bytes; use flowy_error::{internal_error, FlowyError, FlowyResult}; @@ -14,317 +14,363 @@ use std::sync::Arc; use tokio::sync::{mpsc, oneshot}; pub trait RevisionCloudService: Send + Sync { - /// Read the object's revision from remote - /// Returns a list of revisions that used to build the object - /// # Arguments - /// - /// * `user_id`: the id of the user - /// * `object_id`: the id of the object - /// - fn fetch_object(&self, user_id: &str, object_id: &str) -> FutureResult, FlowyError>; + /// Read the object's revision from remote + /// Returns a list of revisions that used to build the object + /// # Arguments + /// + /// * `user_id`: the id of the user + /// * `object_id`: the id of the object + /// + fn fetch_object(&self, user_id: &str, object_id: &str) + -> FutureResult, FlowyError>; } pub trait RevisionObjectDeserializer: Send + Sync { - type Output; - /// Deserialize the list of revisions into an concrete object type. - /// - /// # Arguments - /// - /// * `object_id`: the id of the object - /// * `revisions`: a list of revisions that represent the object - /// - fn deserialize_revisions(object_id: &str, revisions: Vec) -> FlowyResult; + type Output; + /// Deserialize the list of revisions into an concrete object type. + /// + /// # Arguments + /// + /// * `object_id`: the id of the object + /// * `revisions`: a list of revisions that represent the object + /// + fn deserialize_revisions(object_id: &str, revisions: Vec) -> FlowyResult; - fn recover_from_revisions(revisions: Vec) -> Option<(Self::Output, i64)>; + fn recover_from_revisions(revisions: Vec) -> Option<(Self::Output, i64)>; } pub trait RevisionObjectSerializer: Send + Sync { - /// Serialize a list of revisions into one in `Bytes` format - /// - /// * `revisions`: a list of revisions will be serialized to `Bytes` - /// - fn combine_revisions(revisions: Vec) -> FlowyResult; + /// Serialize a list of revisions into one in `Bytes` format + /// + /// * `revisions`: a list of revisions will be serialized to `Bytes` + /// + fn combine_revisions(revisions: Vec) -> FlowyResult; } /// `RevisionCompress` is used to compress multiple revisions into one revision /// pub trait RevisionMergeable: Send + Sync { - fn merge_revisions(&self, _user_id: &str, object_id: &str, mut revisions: Vec) -> FlowyResult { - if revisions.is_empty() { - return Err(FlowyError::internal().context("Can't compact the empty revisions")); - } - - if revisions.len() == 1 { - return Ok(revisions.pop().unwrap()); - } - - // Select the last version, making sure version numbers don't overlap - let last_revision = revisions.last().unwrap(); - let (base_rev_id, rev_id) = last_revision.pair_rev_id(); - let md5 = last_revision.md5.clone(); - let bytes = self.combine_revisions(revisions)?; - Ok(Revision::new(object_id, base_rev_id, rev_id, bytes, md5)) + fn merge_revisions( + &self, + _user_id: &str, + object_id: &str, + mut revisions: Vec, + ) -> FlowyResult { + if revisions.is_empty() { + return Err(FlowyError::internal().context("Can't compact the empty revisions")); } - fn combine_revisions(&self, revisions: Vec) -> FlowyResult; + if revisions.len() == 1 { + return Ok(revisions.pop().unwrap()); + } + + // Select the last version, making sure version numbers don't overlap + let last_revision = revisions.last().unwrap(); + let (base_rev_id, rev_id) = last_revision.pair_rev_id(); + let md5 = last_revision.md5.clone(); + let bytes = self.combine_revisions(revisions)?; + Ok(Revision::new(object_id, base_rev_id, rev_id, bytes, md5)) + } + + fn combine_revisions(&self, revisions: Vec) -> FlowyResult; } pub struct RevisionManager { - pub object_id: String, - user_id: String, - rev_id_counter: Arc, - rev_persistence: Arc>, - rev_snapshot: Arc>, - rev_compress: Arc, - #[cfg(feature = "flowy_unit_test")] - rev_ack_notifier: tokio::sync::broadcast::Sender, - rev_queue: RevCommandSender, + pub object_id: String, + user_id: String, + rev_id_counter: Arc, + rev_persistence: Arc>, + rev_snapshot: Arc>, + rev_compress: Arc, + #[cfg(feature = "flowy_unit_test")] + rev_ack_notifier: tokio::sync::broadcast::Sender, + rev_queue: RevCommandSender, } impl RevisionManager { - pub fn new( - user_id: &str, - object_id: &str, - rev_persistence: RevisionPersistence, - rev_compress: Compress, - snapshot_persistence: Snapshot, - ) -> Self - where - Snapshot: 'static + RevisionSnapshotPersistence, - Compress: 'static + RevisionMergeable, - { - let rev_id_counter = Arc::new(RevIdCounter::new(0)); - let rev_compress = Arc::new(rev_compress); - let rev_persistence = Arc::new(rev_persistence); - let rev_snapshot = RevisionSnapshotController::new( - user_id, - object_id, - snapshot_persistence, - rev_id_counter.clone(), - rev_persistence.clone(), - rev_compress.clone(), - ); - let (rev_queue, receiver) = mpsc::channel(1000); - let queue = RevQueue::new( - object_id.to_owned(), - rev_id_counter.clone(), - rev_persistence.clone(), - rev_compress.clone(), - receiver, - ); - tokio::spawn(queue.run()); - Self { - object_id: object_id.to_string(), - user_id: user_id.to_owned(), - rev_id_counter, - rev_persistence, - rev_snapshot: Arc::new(rev_snapshot), - rev_compress, - #[cfg(feature = "flowy_unit_test")] - rev_ack_notifier: tokio::sync::broadcast::channel(1).0, - rev_queue, - } + pub fn new( + user_id: &str, + object_id: &str, + rev_persistence: RevisionPersistence, + rev_compress: Compress, + snapshot_persistence: Snapshot, + ) -> Self + where + Snapshot: 'static + RevisionSnapshotPersistence, + Compress: 'static + RevisionMergeable, + { + let rev_id_counter = Arc::new(RevIdCounter::new(0)); + let rev_compress = Arc::new(rev_compress); + let rev_persistence = Arc::new(rev_persistence); + let rev_snapshot = RevisionSnapshotController::new( + user_id, + object_id, + snapshot_persistence, + rev_id_counter.clone(), + rev_persistence.clone(), + rev_compress.clone(), + ); + let (rev_queue, receiver) = mpsc::channel(1000); + let queue = RevQueue::new( + object_id.to_owned(), + rev_id_counter.clone(), + rev_persistence.clone(), + rev_compress.clone(), + receiver, + ); + tokio::spawn(queue.run()); + Self { + object_id: object_id.to_string(), + user_id: user_id.to_owned(), + rev_id_counter, + rev_persistence, + rev_snapshot: Arc::new(rev_snapshot), + rev_compress, + #[cfg(feature = "flowy_unit_test")] + rev_ack_notifier: tokio::sync::broadcast::channel(1).0, + rev_queue, + } + } + + #[tracing::instrument(name = "revision_manager_initialize", level = "trace", skip_all, fields(deserializer, object_id, deserialize_revisions) err)] + pub async fn initialize( + &mut self, + _cloud: Option>, + ) -> FlowyResult + where + De: RevisionObjectDeserializer, + { + let revision_records = self.rev_persistence.load_all_records(&self.object_id)?; + tracing::Span::current().record("object_id", self.object_id.as_str()); + tracing::Span::current().record("deserializer", std::any::type_name::()); + let revisions: Vec = revision_records + .iter() + .map(|record| record.revision.clone()) + .collect(); + tracing::Span::current().record("deserialize_revisions", revisions.len()); + let last_rev_id = revisions + .last() + .as_ref() + .map(|revision| revision.rev_id) + .unwrap_or(0); + match De::deserialize_revisions(&self.object_id, revisions.clone()) { + Ok(object) => { + self + .rev_persistence + .sync_revision_records(&revision_records) + .await?; + self.rev_id_counter.set(last_rev_id); + Ok(object) + }, + Err(e) => match self.rev_snapshot.restore_from_snapshot::(last_rev_id) { + None => { + tracing::info!("[Restore] iterate restore from each revision"); + let (output, recover_rev_id) = De::recover_from_revisions(revisions).ok_or(e)?; + tracing::info!( + "[Restore] last_rev_id:{}, recover_rev_id: {}", + last_rev_id, + recover_rev_id + ); + self.rev_id_counter.set(recover_rev_id); + // delete the revisions whose rev_id is greater than recover_rev_id + if recover_rev_id < last_rev_id { + let range = RevisionRange { + start: recover_rev_id + 1, + end: last_rev_id, + }; + tracing::info!("[Restore] delete revisions in range: {}", range); + let _ = self.rev_persistence.delete_revisions_from_range(range); + } + Ok(output) + }, + Some((object, snapshot_rev)) => { + let snapshot_rev_id = snapshot_rev.rev_id; + let _ = self.rev_persistence.reset(vec![snapshot_rev]).await; + // revision_records.retain(|record| record.revision.rev_id <= snapshot_rev_id); + // let _ = self.rev_persistence.sync_revision_records(&revision_records).await?; + self.rev_id_counter.set(snapshot_rev_id); + Ok(object) + }, + }, + } + } + + pub async fn close(&self) { + let _ = self + .rev_persistence + .merge_lagging_revisions(&self.rev_compress) + .await; + } + + pub async fn generate_snapshot(&self) { + self.rev_snapshot.generate_snapshot().await; + } + + pub async fn read_snapshot( + &self, + rev_id: Option, + ) -> FlowyResult> { + match rev_id { + None => self.rev_snapshot.read_last_snapshot(), + Some(rev_id) => self.rev_snapshot.read_snapshot(rev_id), + } + } + + pub async fn load_revisions(&self) -> FlowyResult> { + let revisions = RevisionLoader { + object_id: self.object_id.clone(), + user_id: self.user_id.clone(), + cloud: None, + rev_persistence: self.rev_persistence.clone(), + } + .load_revisions() + .await?; + Ok(revisions) + } + + #[tracing::instrument(level = "debug", skip(self, revisions), err)] + pub async fn reset_object(&self, revisions: Vec) -> FlowyResult<()> { + let rev_id = pair_rev_id_from_revisions(&revisions).1; + self.rev_persistence.reset(revisions).await?; + self.rev_id_counter.set(rev_id); + Ok(()) + } + + #[tracing::instrument(level = "debug", skip(self, revision), err)] + pub async fn add_remote_revision(&self, revision: &Revision) -> Result<(), FlowyError> { + if revision.bytes.is_empty() { + return Err(FlowyError::internal().context("Remote revisions is empty")); } - #[tracing::instrument(name = "revision_manager_initialize", level = "trace", skip_all, fields(deserializer, object_id, deserialize_revisions) err)] - pub async fn initialize(&mut self, _cloud: Option>) -> FlowyResult - where - De: RevisionObjectDeserializer, - { - let revision_records = self.rev_persistence.load_all_records(&self.object_id)?; - tracing::Span::current().record("object_id", self.object_id.as_str()); - tracing::Span::current().record("deserializer", std::any::type_name::()); - let revisions: Vec = revision_records.iter().map(|record| record.revision.clone()).collect(); - tracing::Span::current().record("deserialize_revisions", revisions.len()); - let last_rev_id = revisions.last().as_ref().map(|revision| revision.rev_id).unwrap_or(0); - match De::deserialize_revisions(&self.object_id, revisions.clone()) { - Ok(object) => { - self.rev_persistence.sync_revision_records(&revision_records).await?; - self.rev_id_counter.set(last_rev_id); - Ok(object) - } - Err(e) => match self.rev_snapshot.restore_from_snapshot::(last_rev_id) { - None => { - tracing::info!("[Restore] iterate restore from each revision"); - let (output, recover_rev_id) = De::recover_from_revisions(revisions).ok_or(e)?; - tracing::info!( - "[Restore] last_rev_id:{}, recover_rev_id: {}", - last_rev_id, - recover_rev_id - ); - self.rev_id_counter.set(recover_rev_id); - // delete the revisions whose rev_id is greater than recover_rev_id - if recover_rev_id < last_rev_id { - let range = RevisionRange { - start: recover_rev_id + 1, - end: last_rev_id, - }; - tracing::info!("[Restore] delete revisions in range: {}", range); - let _ = self.rev_persistence.delete_revisions_from_range(range); - } - Ok(output) - } - Some((object, snapshot_rev)) => { - let snapshot_rev_id = snapshot_rev.rev_id; - let _ = self.rev_persistence.reset(vec![snapshot_rev]).await; - // revision_records.retain(|record| record.revision.rev_id <= snapshot_rev_id); - // let _ = self.rev_persistence.sync_revision_records(&revision_records).await?; - self.rev_id_counter.set(snapshot_rev_id); - Ok(object) - } - }, - } - } + self.rev_persistence.add_ack_revision(revision).await?; + self.rev_id_counter.set(revision.rev_id); + Ok(()) + } - pub async fn close(&self) { - let _ = self.rev_persistence.merge_lagging_revisions(&self.rev_compress).await; + /// Adds the revision that generated by user editing + // #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn add_local_revision( + &self, + data: Bytes, + object_md5: String, + ) -> Result { + if data.is_empty() { + return Err(FlowyError::internal().context("The data of the revisions is empty")); } + self.rev_snapshot.generate_snapshot_if_need(); + let (ret, rx) = oneshot::channel(); + self + .rev_queue + .send(RevCommand::RevisionData { + data, + object_md5, + ret, + }) + .await + .map_err(internal_error)?; + rx.await.map_err(internal_error)? + } - pub async fn generate_snapshot(&self) { - self.rev_snapshot.generate_snapshot().await; + #[tracing::instrument(level = "debug", skip(self), err)] + pub async fn ack_revision(&self, rev_id: i64) -> Result<(), FlowyError> { + if self.rev_persistence.ack_revision(rev_id).await.is_ok() { + #[cfg(feature = "flowy_unit_test")] + let _ = self.rev_ack_notifier.send(rev_id); } + Ok(()) + } - pub async fn read_snapshot(&self, rev_id: Option) -> FlowyResult> { - match rev_id { - None => self.rev_snapshot.read_last_snapshot(), - Some(rev_id) => self.rev_snapshot.read_snapshot(rev_id), - } - } + /// Returns the current revision id + pub fn rev_id(&self) -> i64 { + self.rev_id_counter.value() + } - pub async fn load_revisions(&self) -> FlowyResult> { - let revisions = RevisionLoader { - object_id: self.object_id.clone(), - user_id: self.user_id.clone(), - cloud: None, - rev_persistence: self.rev_persistence.clone(), - } - .load_revisions() - .await?; - Ok(revisions) - } + pub async fn next_sync_rev_id(&self) -> Option { + self.rev_persistence.next_sync_rev_id().await + } - #[tracing::instrument(level = "debug", skip(self, revisions), err)] - pub async fn reset_object(&self, revisions: Vec) -> FlowyResult<()> { - let rev_id = pair_rev_id_from_revisions(&revisions).1; - self.rev_persistence.reset(revisions).await?; - self.rev_id_counter.set(rev_id); - Ok(()) - } + pub fn next_rev_id_pair(&self) -> (i64, i64) { + let cur = self.rev_id_counter.value(); + let next = self.rev_id_counter.next_id(); + (cur, next) + } - #[tracing::instrument(level = "debug", skip(self, revision), err)] - pub async fn add_remote_revision(&self, revision: &Revision) -> Result<(), FlowyError> { - if revision.bytes.is_empty() { - return Err(FlowyError::internal().context("Remote revisions is empty")); - } + pub fn number_of_sync_revisions(&self) -> usize { + self.rev_persistence.number_of_sync_records() + } - self.rev_persistence.add_ack_revision(revision).await?; - self.rev_id_counter.set(revision.rev_id); - Ok(()) - } + pub fn number_of_revisions_in_disk(&self) -> usize { + self.rev_persistence.number_of_records_in_disk() + } - /// Adds the revision that generated by user editing - // #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn add_local_revision(&self, data: Bytes, object_md5: String) -> Result { - if data.is_empty() { - return Err(FlowyError::internal().context("The data of the revisions is empty")); - } - self.rev_snapshot.generate_snapshot_if_need(); - let (ret, rx) = oneshot::channel(); - self.rev_queue - .send(RevCommand::RevisionData { data, object_md5, ret }) - .await - .map_err(internal_error)?; - rx.await.map_err(internal_error)? - } + pub async fn get_revisions_in_range( + &self, + range: RevisionRange, + ) -> Result, FlowyError> { + let revisions = self.rev_persistence.revisions_in_range(&range).await?; + Ok(revisions) + } - #[tracing::instrument(level = "debug", skip(self), err)] - pub async fn ack_revision(&self, rev_id: i64) -> Result<(), FlowyError> { - if self.rev_persistence.ack_revision(rev_id).await.is_ok() { - #[cfg(feature = "flowy_unit_test")] - let _ = self.rev_ack_notifier.send(rev_id); - } - Ok(()) - } + pub async fn next_sync_revision(&self) -> FlowyResult> { + self.rev_persistence.next_sync_revision().await + } - /// Returns the current revision id - pub fn rev_id(&self) -> i64 { - self.rev_id_counter.value() - } - - pub async fn next_sync_rev_id(&self) -> Option { - self.rev_persistence.next_sync_rev_id().await - } - - pub fn next_rev_id_pair(&self) -> (i64, i64) { - let cur = self.rev_id_counter.value(); - let next = self.rev_id_counter.next_id(); - (cur, next) - } - - pub fn number_of_sync_revisions(&self) -> usize { - self.rev_persistence.number_of_sync_records() - } - - pub fn number_of_revisions_in_disk(&self) -> usize { - self.rev_persistence.number_of_records_in_disk() - } - - pub async fn get_revisions_in_range(&self, range: RevisionRange) -> Result, FlowyError> { - let revisions = self.rev_persistence.revisions_in_range(&range).await?; - Ok(revisions) - } - - pub async fn next_sync_revision(&self) -> FlowyResult> { - self.rev_persistence.next_sync_revision().await - } - - pub async fn get_revision(&self, rev_id: i64) -> Option { - self.rev_persistence.get(rev_id).await.map(|record| record.revision) - } + pub async fn get_revision(&self, rev_id: i64) -> Option { + self + .rev_persistence + .get(rev_id) + .await + .map(|record| record.revision) + } } impl WSDataProviderDataSource for Arc> { - fn next_revision(&self) -> FutureResult, FlowyError> { - let rev_manager = self.clone(); - FutureResult::new(async move { rev_manager.next_sync_revision().await }) - } + fn next_revision(&self) -> FutureResult, FlowyError> { + let rev_manager = self.clone(); + FutureResult::new(async move { rev_manager.next_sync_revision().await }) + } - fn ack_revision(&self, rev_id: i64) -> FutureResult<(), FlowyError> { - let rev_manager = self.clone(); - FutureResult::new(async move { (*rev_manager).ack_revision(rev_id).await }) - } + fn ack_revision(&self, rev_id: i64) -> FutureResult<(), FlowyError> { + let rev_manager = self.clone(); + FutureResult::new(async move { (*rev_manager).ack_revision(rev_id).await }) + } - fn current_rev_id(&self) -> i64 { - self.rev_id() - } + fn current_rev_id(&self) -> i64 { + self.rev_id() + } } #[cfg(feature = "flowy_unit_test")] impl RevisionManager { - pub async fn revision_cache(&self) -> Arc> { - self.rev_persistence.clone() - } - pub fn ack_notify(&self) -> tokio::sync::broadcast::Receiver { - self.rev_ack_notifier.subscribe() - } - pub fn get_all_revision_records(&self) -> FlowyResult> { - self.rev_persistence.load_all_records(&self.object_id) - } + pub async fn revision_cache(&self) -> Arc> { + self.rev_persistence.clone() + } + pub fn ack_notify(&self) -> tokio::sync::broadcast::Receiver { + self.rev_ack_notifier.subscribe() + } + pub fn get_all_revision_records( + &self, + ) -> FlowyResult> { + self.rev_persistence.load_all_records(&self.object_id) + } } pub struct RevisionLoader { - pub object_id: String, - pub user_id: String, - pub cloud: Option>, - pub rev_persistence: Arc>, + pub object_id: String, + pub user_id: String, + pub cloud: Option>, + pub rev_persistence: Arc>, } impl RevisionLoader { - pub async fn load_revisions(&self) -> Result, FlowyError> { - let records = self.rev_persistence.load_all_records(&self.object_id)?; - let revisions = records.into_iter().map(|record| record.revision).collect::<_>(); - Ok(revisions) - } + pub async fn load_revisions(&self) -> Result, FlowyError> { + let records = self.rev_persistence.load_all_records(&self.object_id)?; + let revisions = records + .into_iter() + .map(|record| record.revision) + .collect::<_>(); + Ok(revisions) + } } /// Represents as the md5 of the revision object after applying the @@ -334,85 +380,85 @@ impl RevisionLoader { pub struct RevisionMD5(String); impl RevisionMD5 { - pub fn from_bytes>(bytes: T) -> Result { - Ok(RevisionMD5(md5(bytes))) - } + pub fn from_bytes>(bytes: T) -> Result { + Ok(RevisionMD5(md5(bytes))) + } - pub fn into_inner(self) -> String { - self.0 - } + pub fn into_inner(self) -> String { + self.0 + } - pub fn is_equal(&self, s: &str) -> bool { - self.0 == s - } + pub fn is_equal(&self, s: &str) -> bool { + self.0 == s + } } impl std::convert::From for String { - fn from(md5: RevisionMD5) -> Self { - md5.0 - } + fn from(md5: RevisionMD5) -> Self { + md5.0 + } } impl std::convert::From<&str> for RevisionMD5 { - fn from(s: &str) -> Self { - Self(s.to_owned()) - } + fn from(s: &str) -> Self { + Self(s.to_owned()) + } } impl std::convert::From for RevisionMD5 { - fn from(s: String) -> Self { - Self(s) - } + fn from(s: String) -> Self { + Self(s) + } } impl std::ops::Deref for RevisionMD5 { - type Target = String; + type Target = String; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } impl PartialEq for RevisionMD5 { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } } impl std::cmp::Eq for RevisionMD5 {} fn pair_rev_id_from_revisions(revisions: &[Revision]) -> (i64, i64) { - let mut rev_id = 0; - revisions.iter().for_each(|revision| { - if rev_id < revision.rev_id { - rev_id = revision.rev_id; - } - }); - - if rev_id > 0 { - (rev_id - 1, rev_id) - } else { - (0, rev_id) + let mut rev_id = 0; + revisions.iter().for_each(|revision| { + if rev_id < revision.rev_id { + rev_id = revision.rev_id; } + }); + + if rev_id > 0 { + (rev_id - 1, rev_id) + } else { + (0, rev_id) + } } #[derive(Debug)] pub struct RevIdCounter(pub AtomicI64); impl RevIdCounter { - pub fn new(n: i64) -> Self { - Self(AtomicI64::new(n)) - } + pub fn new(n: i64) -> Self { + Self(AtomicI64::new(n)) + } - pub fn next_id(&self) -> i64 { - let _ = self.0.fetch_add(1, SeqCst); - self.value() - } + pub fn next_id(&self) -> i64 { + let _ = self.0.fetch_add(1, SeqCst); + self.value() + } - pub fn value(&self) -> i64 { - self.0.load(SeqCst) - } + pub fn value(&self) -> i64 { + self.0.load(SeqCst) + } - pub fn set(&self, n: i64) { - let _ = self.0.fetch_update(SeqCst, SeqCst, |_| Some(n)); - } + pub fn set(&self, n: i64) { + let _ = self.0.fetch_update(SeqCst, SeqCst, |_| Some(n)); + } } diff --git a/frontend/rust-lib/flowy-revision/src/rev_persistence.rs b/frontend/rust-lib/flowy-revision/src/rev_persistence.rs index 4ee43922da..21ba5c2f47 100644 --- a/frontend/rust-lib/flowy-revision/src/rev_persistence.rs +++ b/frontend/rust-lib/flowy-revision/src/rev_persistence.rs @@ -14,443 +14,472 @@ pub const REVISION_WRITE_INTERVAL_IN_MILLIS: u64 = 600; #[derive(Clone)] pub struct RevisionPersistenceConfiguration { - // If the number of revisions that didn't sync to the server greater than the max_merge_len - // then these revisions will be merged into one revision. - max_merge_len: usize, + // If the number of revisions that didn't sync to the server greater than the max_merge_len + // then these revisions will be merged into one revision. + max_merge_len: usize, - /// Indicates that the revisions that didn't sync to the server can be merged into one when - /// `merge_lagging_revisions` get called. - merge_lagging: bool, + /// Indicates that the revisions that didn't sync to the server can be merged into one when + /// `merge_lagging_revisions` get called. + merge_lagging: bool, } impl RevisionPersistenceConfiguration { - pub fn new(merge_max_length: usize, merge_lagging: bool) -> Self { - debug_assert!(merge_max_length > 1); - if merge_max_length > 1 { - Self { - max_merge_len: merge_max_length, - merge_lagging, - } - } else { - Self { - max_merge_len: 100, - merge_lagging, - } - } + pub fn new(merge_max_length: usize, merge_lagging: bool) -> Self { + debug_assert!(merge_max_length > 1); + if merge_max_length > 1 { + Self { + max_merge_len: merge_max_length, + merge_lagging, + } + } else { + Self { + max_merge_len: 100, + merge_lagging, + } } + } } impl std::default::Default for RevisionPersistenceConfiguration { - fn default() -> Self { - Self { - max_merge_len: 100, - merge_lagging: false, - } + fn default() -> Self { + Self { + max_merge_len: 100, + merge_lagging: false, } + } } /// Represents as the persistence of revisions including memory or disk cache. /// The generic parameter, `Connection`, represents as the disk backend's connection. /// If the backend is SQLite, then the Connect will be SQLiteConnect. pub struct RevisionPersistence { - user_id: String, - object_id: String, - disk_cache: Arc>, - memory_cache: Arc, - sync_seq: RwLock, - configuration: RevisionPersistenceConfiguration, + user_id: String, + object_id: String, + disk_cache: Arc>, + memory_cache: Arc, + sync_seq: RwLock, + configuration: RevisionPersistenceConfiguration, } impl RevisionPersistence where - Connection: 'static, + Connection: 'static, { - pub fn new( - user_id: &str, - object_id: &str, - disk_cache: C, - configuration: RevisionPersistenceConfiguration, - ) -> RevisionPersistence - where - C: 'static + RevisionDiskCache, - { - let disk_cache = Arc::new(disk_cache) as Arc>; - Self::from_disk_cache(user_id, object_id, disk_cache, configuration) + pub fn new( + user_id: &str, + object_id: &str, + disk_cache: C, + configuration: RevisionPersistenceConfiguration, + ) -> RevisionPersistence + where + C: 'static + RevisionDiskCache, + { + let disk_cache = + Arc::new(disk_cache) as Arc>; + Self::from_disk_cache(user_id, object_id, disk_cache, configuration) + } + + pub fn from_disk_cache( + user_id: &str, + object_id: &str, + disk_cache: Arc>, + configuration: RevisionPersistenceConfiguration, + ) -> RevisionPersistence { + let object_id = object_id.to_owned(); + let user_id = user_id.to_owned(); + let sync_seq = RwLock::new(DeferSyncSequence::new()); + let memory_cache = Arc::new(RevisionMemoryCache::new( + &object_id, + Arc::new(disk_cache.clone()), + )); + Self { + user_id, + object_id, + disk_cache, + memory_cache, + sync_seq, + configuration, + } + } + + /// Save the revision that comes from remote to disk. + #[tracing::instrument(level = "trace", skip(self, revision), fields(rev_id, object_id=%self.object_id), err)] + pub(crate) async fn add_ack_revision(&self, revision: &Revision) -> FlowyResult<()> { + tracing::Span::current().record("rev_id", revision.rev_id); + self.add(revision.clone(), RevisionState::Ack, true).await + } + + #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn merge_lagging_revisions<'a>( + &'a self, + rev_compress: &Arc, + ) -> FlowyResult<()> { + if !self.configuration.merge_lagging { + return Ok(()); } - pub fn from_disk_cache( - user_id: &str, - object_id: &str, - disk_cache: Arc>, - configuration: RevisionPersistenceConfiguration, - ) -> RevisionPersistence { - let object_id = object_id.to_owned(); - let user_id = user_id.to_owned(); - let sync_seq = RwLock::new(DeferSyncSequence::new()); - let memory_cache = Arc::new(RevisionMemoryCache::new(&object_id, Arc::new(disk_cache.clone()))); - Self { - user_id, - object_id, - disk_cache, - memory_cache, - sync_seq, - configuration, - } + let mut sync_seq = self.sync_seq.write().await; + let compact_seq = sync_seq.pop_merge_rev_ids(); + if !compact_seq.is_empty() { + let range = RevisionRange { + start: *compact_seq.front().unwrap(), + end: *compact_seq.back().unwrap(), + }; + + let revisions = self.revisions_in_range(&range).await?; + let rev_ids = range.to_rev_ids(); + debug_assert_eq!(range.len() as usize, revisions.len()); + // compact multiple revisions into one + let merged_revision = + rev_compress.merge_revisions(&self.user_id, &self.object_id, revisions)?; + tracing::Span::current().record("rev_id", merged_revision.rev_id); + + let record = SyncRecord { + revision: merged_revision, + state: RevisionState::Sync, + write_to_disk: true, + }; + self + .disk_cache + .delete_and_insert_records(&self.object_id, Some(rev_ids), vec![record])?; } + Ok(()) + } - /// Save the revision that comes from remote to disk. - #[tracing::instrument(level = "trace", skip(self, revision), fields(rev_id, object_id=%self.object_id), err)] - pub(crate) async fn add_ack_revision(&self, revision: &Revision) -> FlowyResult<()> { - tracing::Span::current().record("rev_id", revision.rev_id); - self.add(revision.clone(), RevisionState::Ack, true).await + /// Sync the each records' revisions to remote if its state is `RevisionState::Sync`. + /// + pub(crate) async fn sync_revision_records(&self, records: &[SyncRecord]) -> FlowyResult<()> { + let mut sync_seq = self.sync_seq.write().await; + for record in records { + if record.state == RevisionState::Sync { + self + .add(record.revision.clone(), RevisionState::Sync, false) + .await?; + sync_seq.recv(record.revision.rev_id)?; // Sync the records if their state is RevisionState::Sync. + } } + Ok(()) + } - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn merge_lagging_revisions<'a>( - &'a self, - rev_compress: &Arc, - ) -> FlowyResult<()> { - if !self.configuration.merge_lagging { - return Ok(()); - } + /// Save the revision to disk and append it to the end of the sync sequence. + /// The returned value,rev_id, will be different with the passed-in revision's rev_id if + /// multiple revisions are merged into one. + #[tracing::instrument(level = "trace", skip_all, fields(rev_id, compact_range, object_id=%self.object_id), err)] + pub(crate) async fn add_local_revision<'a>( + &'a self, + new_revision: Revision, + rev_compress: &Arc, + ) -> FlowyResult { + let mut sync_seq = self.sync_seq.write().await; - let mut sync_seq = self.sync_seq.write().await; - let compact_seq = sync_seq.pop_merge_rev_ids(); - if !compact_seq.is_empty() { - let range = RevisionRange { - start: *compact_seq.front().unwrap(), - end: *compact_seq.back().unwrap(), - }; - - let revisions = self.revisions_in_range(&range).await?; - let rev_ids = range.to_rev_ids(); - debug_assert_eq!(range.len() as usize, revisions.len()); - // compact multiple revisions into one - let merged_revision = rev_compress.merge_revisions(&self.user_id, &self.object_id, revisions)?; - tracing::Span::current().record("rev_id", merged_revision.rev_id); - - let record = SyncRecord { - revision: merged_revision, - state: RevisionState::Sync, - write_to_disk: true, - }; - self.disk_cache - .delete_and_insert_records(&self.object_id, Some(rev_ids), vec![record])?; - } - Ok(()) + // Before the new_revision is pushed into the sync_seq, we check if the current `merge_length` of the + // sync_seq is less equal to or greater than the `merge_max_length`. If yes, it's needs to merged + // with the new_revision into one revision. + let mut merge_rev_ids = VecDeque::default(); + // tracing::info!("{}", compact_seq) + if sync_seq.merge_length >= self.configuration.max_merge_len - 1 { + merge_rev_ids.extend(sync_seq.pop_merge_rev_ids()); } + if !merge_rev_ids.is_empty() { + let range = RevisionRange { + start: *merge_rev_ids.front().unwrap(), + end: *merge_rev_ids.back().unwrap(), + }; - /// Sync the each records' revisions to remote if its state is `RevisionState::Sync`. - /// - pub(crate) async fn sync_revision_records(&self, records: &[SyncRecord]) -> FlowyResult<()> { - let mut sync_seq = self.sync_seq.write().await; - for record in records { - if record.state == RevisionState::Sync { - self.add(record.revision.clone(), RevisionState::Sync, false).await?; - sync_seq.recv(record.revision.rev_id)?; // Sync the records if their state is RevisionState::Sync. - } - } - Ok(()) + tracing::Span::current().record("compact_range", format!("{}", range).as_str()); + let mut revisions = self.revisions_in_range(&range).await?; + debug_assert_eq!(range.len() as usize, revisions.len()); + // append the new revision + revisions.push(new_revision); + + // compact multiple revisions into one + let merged_revision = + rev_compress.merge_revisions(&self.user_id, &self.object_id, revisions)?; + let new_rev_id = merged_revision.rev_id; + tracing::Span::current().record("rev_id", merged_revision.rev_id); + sync_seq.recv(new_rev_id)?; + + // replace the revisions in range with compact revision + self.compact(&range, merged_revision).await?; + Ok(new_rev_id) + } else { + let rev_id = new_revision.rev_id; + tracing::Span::current().record("rev_id", rev_id); + self.add(new_revision, RevisionState::Sync, true).await?; + sync_seq.merge_recv(rev_id)?; + Ok(rev_id) } + } - /// Save the revision to disk and append it to the end of the sync sequence. - /// The returned value,rev_id, will be different with the passed-in revision's rev_id if - /// multiple revisions are merged into one. - #[tracing::instrument(level = "trace", skip_all, fields(rev_id, compact_range, object_id=%self.object_id), err)] - pub(crate) async fn add_local_revision<'a>( - &'a self, - new_revision: Revision, - rev_compress: &Arc, - ) -> FlowyResult { - let mut sync_seq = self.sync_seq.write().await; - - // Before the new_revision is pushed into the sync_seq, we check if the current `merge_length` of the - // sync_seq is less equal to or greater than the `merge_max_length`. If yes, it's needs to merged - // with the new_revision into one revision. - let mut merge_rev_ids = VecDeque::default(); - // tracing::info!("{}", compact_seq) - if sync_seq.merge_length >= self.configuration.max_merge_len - 1 { - merge_rev_ids.extend(sync_seq.pop_merge_rev_ids()); - } - if !merge_rev_ids.is_empty() { - let range = RevisionRange { - start: *merge_rev_ids.front().unwrap(), - end: *merge_rev_ids.back().unwrap(), - }; - - tracing::Span::current().record("compact_range", format!("{}", range).as_str()); - let mut revisions = self.revisions_in_range(&range).await?; - debug_assert_eq!(range.len() as usize, revisions.len()); - // append the new revision - revisions.push(new_revision); - - // compact multiple revisions into one - let merged_revision = rev_compress.merge_revisions(&self.user_id, &self.object_id, revisions)?; - let new_rev_id = merged_revision.rev_id; - tracing::Span::current().record("rev_id", merged_revision.rev_id); - sync_seq.recv(new_rev_id)?; - - // replace the revisions in range with compact revision - self.compact(&range, merged_revision).await?; - Ok(new_rev_id) - } else { - let rev_id = new_revision.rev_id; - tracing::Span::current().record("rev_id", rev_id); - self.add(new_revision, RevisionState::Sync, true).await?; - sync_seq.merge_recv(rev_id)?; - Ok(rev_id) - } + /// Remove the revision with rev_id from the sync sequence. + pub(crate) async fn ack_revision(&self, rev_id: i64) -> FlowyResult<()> { + if self.sync_seq.write().await.ack(&rev_id).is_ok() { + self.memory_cache.ack(&rev_id).await; } + Ok(()) + } - /// Remove the revision with rev_id from the sync sequence. - pub(crate) async fn ack_revision(&self, rev_id: i64) -> FlowyResult<()> { - if self.sync_seq.write().await.ack(&rev_id).is_ok() { - self.memory_cache.ack(&rev_id).await; - } - Ok(()) + pub(crate) async fn next_sync_revision(&self) -> FlowyResult> { + match self.sync_seq.read().await.next_rev_id() { + None => Ok(None), + Some(rev_id) => Ok(self.get(rev_id).await.map(|record| record.revision)), } + } - pub(crate) async fn next_sync_revision(&self) -> FlowyResult> { - match self.sync_seq.read().await.next_rev_id() { - None => Ok(None), - Some(rev_id) => Ok(self.get(rev_id).await.map(|record| record.revision)), - } + pub(crate) async fn next_sync_rev_id(&self) -> Option { + self.sync_seq.read().await.next_rev_id() + } + + pub(crate) fn number_of_sync_records(&self) -> usize { + self.memory_cache.number_of_sync_records() + } + + pub(crate) fn number_of_records_in_disk(&self) -> usize { + match self.disk_cache.read_revision_records(&self.object_id, None) { + Ok(records) => records.len(), + Err(e) => { + tracing::error!("Read revision records failed: {:?}", e); + 0 + }, } + } - pub(crate) async fn next_sync_rev_id(&self) -> Option { - self.sync_seq.read().await.next_rev_id() + /// The cache gets reset while it conflicts with the remote revisions. + #[tracing::instrument(level = "trace", skip(self, revisions), err)] + pub(crate) async fn reset(&self, revisions: Vec) -> FlowyResult<()> { + let records = revisions + .into_iter() + .map(|revision| SyncRecord { + revision, + state: RevisionState::Sync, + write_to_disk: false, + }) + .collect::>(); + + self + .disk_cache + .delete_and_insert_records(&self.object_id, None, records.clone())?; + self.memory_cache.reset_with_revisions(records).await; + self.sync_seq.write().await.clear(); + Ok(()) + } + + async fn add( + &self, + revision: Revision, + state: RevisionState, + write_to_disk: bool, + ) -> FlowyResult<()> { + if self.memory_cache.contains(&revision.rev_id) { + tracing::warn!( + "Duplicate revision: {}:{}-{:?}", + self.object_id, + revision.rev_id, + state + ); + return Ok(()); } + let record = SyncRecord { + revision, + state, + write_to_disk, + }; - pub(crate) fn number_of_sync_records(&self) -> usize { - self.memory_cache.number_of_sync_records() + self.memory_cache.add(Cow::Owned(record)).await; + Ok(()) + } + + async fn compact(&self, range: &RevisionRange, new_revision: Revision) -> FlowyResult<()> { + self.memory_cache.remove_with_range(range); + let rev_ids = range.to_rev_ids(); + self + .disk_cache + .delete_revision_records(&self.object_id, Some(rev_ids))?; + self.add(new_revision, RevisionState::Sync, true).await?; + Ok(()) + } + + pub async fn get(&self, rev_id: i64) -> Option { + match self.memory_cache.get(&rev_id).await { + None => match self + .disk_cache + .read_revision_records(&self.object_id, Some(vec![rev_id])) + { + Ok(mut records) => { + let record = records.pop()?; + assert!(records.is_empty()); + Some(record) + }, + Err(e) => { + tracing::error!("{}", e); + None + }, + }, + Some(revision) => Some(revision), } + } - pub(crate) fn number_of_records_in_disk(&self) -> usize { - match self.disk_cache.read_revision_records(&self.object_id, None) { - Ok(records) => records.len(), - Err(e) => { - tracing::error!("Read revision records failed: {:?}", e); - 0 - } - } + pub fn load_all_records(&self, object_id: &str) -> FlowyResult> { + let mut record_ids = HashMap::new(); + let mut records = vec![]; + for record in self.disk_cache.read_revision_records(object_id, None)? { + let rev_id = record.revision.rev_id; + if record_ids.get(&rev_id).is_none() { + records.push(record); + } + record_ids.insert(rev_id, rev_id); } + Ok(records) + } - /// The cache gets reset while it conflicts with the remote revisions. - #[tracing::instrument(level = "trace", skip(self, revisions), err)] - pub(crate) async fn reset(&self, revisions: Vec) -> FlowyResult<()> { - let records = revisions - .into_iter() - .map(|revision| SyncRecord { - revision, - state: RevisionState::Sync, - write_to_disk: false, - }) - .collect::>(); + // Read the revision which rev_id >= range.start && rev_id <= range.end + pub async fn revisions_in_range(&self, range: &RevisionRange) -> FlowyResult> { + let range = range.clone(); + let mut records = self.memory_cache.get_with_range(&range).await?; + let range_len = range.len() as usize; + if records.len() != range_len { + let disk_cache = self.disk_cache.clone(); + let object_id = self.object_id.clone(); + records = + spawn_blocking(move || disk_cache.read_revision_records_with_range(&object_id, &range)) + .await + .map_err(internal_error)??; - self.disk_cache - .delete_and_insert_records(&self.object_id, None, records.clone())?; - self.memory_cache.reset_with_revisions(records).await; - self.sync_seq.write().await.clear(); - Ok(()) + if records.len() != range_len { + tracing::error!( + "Expect revision len {},but receive {}", + range_len, + records.len() + ); + } } + Ok( + records + .into_iter() + .map(|record| record.revision) + .collect::>(), + ) + } - async fn add(&self, revision: Revision, state: RevisionState, write_to_disk: bool) -> FlowyResult<()> { - if self.memory_cache.contains(&revision.rev_id) { - tracing::warn!("Duplicate revision: {}:{}-{:?}", self.object_id, revision.rev_id, state); - return Ok(()); - } - let record = SyncRecord { - revision, - state, - write_to_disk, - }; - - self.memory_cache.add(Cow::Owned(record)).await; - Ok(()) - } - - async fn compact(&self, range: &RevisionRange, new_revision: Revision) -> FlowyResult<()> { - self.memory_cache.remove_with_range(range); - let rev_ids = range.to_rev_ids(); - self.disk_cache - .delete_revision_records(&self.object_id, Some(rev_ids))?; - self.add(new_revision, RevisionState::Sync, true).await?; - Ok(()) - } - - pub async fn get(&self, rev_id: i64) -> Option { - match self.memory_cache.get(&rev_id).await { - None => match self - .disk_cache - .read_revision_records(&self.object_id, Some(vec![rev_id])) - { - Ok(mut records) => { - let record = records.pop()?; - assert!(records.is_empty()); - Some(record) - } - Err(e) => { - tracing::error!("{}", e); - None - } - }, - Some(revision) => Some(revision), - } - } - - pub fn load_all_records(&self, object_id: &str) -> FlowyResult> { - let mut record_ids = HashMap::new(); - let mut records = vec![]; - for record in self.disk_cache.read_revision_records(object_id, None)? { - let rev_id = record.revision.rev_id; - if record_ids.get(&rev_id).is_none() { - records.push(record); - } - record_ids.insert(rev_id, rev_id); - } - Ok(records) - } - - // Read the revision which rev_id >= range.start && rev_id <= range.end - pub async fn revisions_in_range(&self, range: &RevisionRange) -> FlowyResult> { - let range = range.clone(); - let mut records = self.memory_cache.get_with_range(&range).await?; - let range_len = range.len() as usize; - if records.len() != range_len { - let disk_cache = self.disk_cache.clone(); - let object_id = self.object_id.clone(); - records = spawn_blocking(move || disk_cache.read_revision_records_with_range(&object_id, &range)) - .await - .map_err(internal_error)??; - - if records.len() != range_len { - tracing::error!("Expect revision len {},but receive {}", range_len, records.len()); - } - } - Ok(records - .into_iter() - .map(|record| record.revision) - .collect::>()) - } - - pub fn delete_revisions_from_range(&self, range: RevisionRange) -> FlowyResult<()> { - self.disk_cache - .delete_revision_records(&self.object_id, Some(range.to_rev_ids()))?; - Ok(()) - } + pub fn delete_revisions_from_range(&self, range: RevisionRange) -> FlowyResult<()> { + self + .disk_cache + .delete_revision_records(&self.object_id, Some(range.to_rev_ids()))?; + Ok(()) + } } impl RevisionMemoryCacheDelegate for Arc> { - fn send_sync(&self, mut records: Vec) -> FlowyResult<()> { - records.retain(|record| record.write_to_disk); - if !records.is_empty() { - tracing::Span::current().record( - "checkpoint_result", - format!("{} records were saved", records.len()).as_str(), - ); - self.create_revision_records(records)?; - } - Ok(()) + fn send_sync(&self, mut records: Vec) -> FlowyResult<()> { + records.retain(|record| record.write_to_disk); + if !records.is_empty() { + tracing::Span::current().record( + "checkpoint_result", + format!("{} records were saved", records.len()).as_str(), + ); + self.create_revision_records(records)?; } + Ok(()) + } - fn receive_ack(&self, object_id: &str, rev_id: i64) { - let changeset = RevisionChangeset { - object_id: object_id.to_string(), - rev_id, - state: RevisionState::Ack, - }; - match self.update_revision_record(vec![changeset]) { - Ok(_) => {} - Err(e) => tracing::error!("{}", e), - } + fn receive_ack(&self, object_id: &str, rev_id: i64) { + let changeset = RevisionChangeset { + object_id: object_id.to_string(), + rev_id, + state: RevisionState::Ack, + }; + match self.update_revision_record(vec![changeset]) { + Ok(_) => {}, + Err(e) => tracing::error!("{}", e), } + } } #[derive(Default)] struct DeferSyncSequence { - rev_ids: VecDeque, - merge_start: Option, - merge_length: usize, + rev_ids: VecDeque, + merge_start: Option, + merge_length: usize, } impl DeferSyncSequence { - fn new() -> Self { - DeferSyncSequence::default() + fn new() -> Self { + DeferSyncSequence::default() + } + + /// Pushes the new_rev_id to the end of the list and marks this new_rev_id is mergeable. + /// + fn merge_recv(&mut self, new_rev_id: i64) -> FlowyResult<()> { + self.recv(new_rev_id)?; + + self.merge_length += 1; + if self.merge_start.is_none() && !self.rev_ids.is_empty() { + self.merge_start = Some(self.rev_ids.len() - 1); } + Ok(()) + } - /// Pushes the new_rev_id to the end of the list and marks this new_rev_id is mergeable. - /// - fn merge_recv(&mut self, new_rev_id: i64) -> FlowyResult<()> { - self.recv(new_rev_id)?; + /// Pushes the new_rev_id to the end of the list. + fn recv(&mut self, new_rev_id: i64) -> FlowyResult<()> { + // The last revision's rev_id must be greater than the new one. + if let Some(rev_id) = self.rev_ids.back() { + if *rev_id >= new_rev_id { + tracing::error!("The new revision's id must be greater than {}", rev_id); + return Ok(()); + } + } + self.rev_ids.push_back(new_rev_id); + Ok(()) + } - self.merge_length += 1; - if self.merge_start.is_none() && !self.rev_ids.is_empty() { - self.merge_start = Some(self.rev_ids.len() - 1); + /// Removes the rev_id from the list + fn ack(&mut self, rev_id: &i64) -> FlowyResult<()> { + let cur_rev_id = self.rev_ids.front().cloned(); + if let Some(pop_rev_id) = cur_rev_id { + if &pop_rev_id != rev_id { + let desc = format!( + "The ack rev_id:{} is not equal to the current rev_id:{}", + rev_id, pop_rev_id + ); + return Err(FlowyError::internal().context(desc)); + } + + let mut compact_rev_id = None; + if let Some(compact_index) = self.merge_start { + compact_rev_id = self.rev_ids.get(compact_index).cloned(); + } + + let pop_rev_id = self.rev_ids.pop_front(); + if let (Some(compact_rev_id), Some(pop_rev_id)) = (compact_rev_id, pop_rev_id) { + if compact_rev_id <= pop_rev_id && self.merge_length > 0 { + self.merge_length -= 1; } - Ok(()) + } } + Ok(()) + } - /// Pushes the new_rev_id to the end of the list. - fn recv(&mut self, new_rev_id: i64) -> FlowyResult<()> { - // The last revision's rev_id must be greater than the new one. - if let Some(rev_id) = self.rev_ids.back() { - if *rev_id >= new_rev_id { - tracing::error!("The new revision's id must be greater than {}", rev_id); - return Ok(()); - } - } - self.rev_ids.push_back(new_rev_id); - Ok(()) - } - - /// Removes the rev_id from the list - fn ack(&mut self, rev_id: &i64) -> FlowyResult<()> { - let cur_rev_id = self.rev_ids.front().cloned(); - if let Some(pop_rev_id) = cur_rev_id { - if &pop_rev_id != rev_id { - let desc = format!( - "The ack rev_id:{} is not equal to the current rev_id:{}", - rev_id, pop_rev_id - ); - return Err(FlowyError::internal().context(desc)); - } - - let mut compact_rev_id = None; - if let Some(compact_index) = self.merge_start { - compact_rev_id = self.rev_ids.get(compact_index).cloned(); - } - - let pop_rev_id = self.rev_ids.pop_front(); - if let (Some(compact_rev_id), Some(pop_rev_id)) = (compact_rev_id, pop_rev_id) { - if compact_rev_id <= pop_rev_id && self.merge_length > 0 { - self.merge_length -= 1; - } - } - } - Ok(()) - } - - fn next_rev_id(&self) -> Option { - self.rev_ids.front().cloned() - } - - fn clear(&mut self) { - self.merge_start = None; - self.merge_length = 0; - self.rev_ids.clear(); - } - - // Returns the rev_ids into one except the current synchronizing rev_id. - fn pop_merge_rev_ids(&mut self) -> VecDeque { - let mut compact_seq = VecDeque::with_capacity(self.rev_ids.len()); - if let Some(start) = self.merge_start { - if start < self.rev_ids.len() { - let seq = self.rev_ids.split_off(start); - compact_seq.extend(seq); - } - } - self.merge_start = None; - self.merge_length = 0; - compact_seq + fn next_rev_id(&self) -> Option { + self.rev_ids.front().cloned() + } + + fn clear(&mut self) { + self.merge_start = None; + self.merge_length = 0; + self.rev_ids.clear(); + } + + // Returns the rev_ids into one except the current synchronizing rev_id. + fn pop_merge_rev_ids(&mut self) -> VecDeque { + let mut compact_seq = VecDeque::with_capacity(self.rev_ids.len()); + if let Some(start) = self.merge_start { + if start < self.rev_ids.len() { + let seq = self.rev_ids.split_off(start); + compact_seq.extend(seq); + } } + self.merge_start = None; + self.merge_length = 0; + compact_seq + } } diff --git a/frontend/rust-lib/flowy-revision/src/rev_queue.rs b/frontend/rust-lib/flowy-revision/src/rev_queue.rs index 64f790af05..f430e9aabb 100644 --- a/frontend/rust-lib/flowy-revision/src/rev_queue.rs +++ b/frontend/rust-lib/flowy-revision/src/rev_queue.rs @@ -10,87 +10,87 @@ use tokio::sync::oneshot; #[derive(Debug)] pub(crate) enum RevCommand { - RevisionData { - data: Bytes, - object_md5: String, - ret: Ret, - }, + RevisionData { + data: Bytes, + object_md5: String, + ret: Ret, + }, } pub(crate) struct RevQueue { - object_id: String, - rev_id_counter: Arc, - rev_persistence: Arc>, - rev_compress: Arc, - receiver: Option, + object_id: String, + rev_id_counter: Arc, + rev_persistence: Arc>, + rev_compress: Arc, + receiver: Option, } impl RevQueue where - Connection: 'static, + Connection: 'static, { - pub fn new( - object_id: String, - rev_id_counter: Arc, - rev_persistence: Arc>, - rev_compress: Arc, - receiver: RevCommandReceiver, - ) -> Self { - Self { - object_id, - rev_id_counter, - rev_persistence, - rev_compress, - receiver: Some(receiver), - } + pub fn new( + object_id: String, + rev_id_counter: Arc, + rev_persistence: Arc>, + rev_compress: Arc, + receiver: RevCommandReceiver, + ) -> Self { + Self { + object_id, + rev_id_counter, + rev_persistence, + rev_compress, + receiver: Some(receiver), } + } - pub async fn run(mut self) { - let mut receiver = self.receiver.take().expect("Only take once"); - let object_id = self.object_id.clone(); - let stream = stream! { - loop { - match receiver.recv().await { - Some(msg) => yield msg, - None => { - tracing::trace!("{}'s RevQueue exist", &object_id); - break - }, - } - } - }; - stream - .for_each(|command| async { - match self.handle_command(command).await { - Ok(_) => {} - Err(e) => tracing::error!("[RevQueue]: {}", e), - } - }) - .await; - } - - async fn handle_command(&self, command: RevCommand) -> Result<(), FlowyError> { - match command { - RevCommand::RevisionData { - data, - object_md5: data_md5, - ret, - } => { - let base_rev_id = self.rev_id_counter.value(); - let rev_id = self.rev_id_counter.next_id(); - let revision = Revision::new(&self.object_id, base_rev_id, rev_id, data, data_md5); - - let new_rev_id = self - .rev_persistence - .add_local_revision(revision, &self.rev_compress) - .await?; - - self.rev_id_counter.set(new_rev_id); - let _ = ret.send(Ok(new_rev_id)); + pub async fn run(mut self) { + let mut receiver = self.receiver.take().expect("Only take once"); + let object_id = self.object_id.clone(); + let stream = stream! { + loop { + match receiver.recv().await { + Some(msg) => yield msg, + None => { + tracing::trace!("{}'s RevQueue exist", &object_id); + break + }, } } - Ok(()) + }; + stream + .for_each(|command| async { + match self.handle_command(command).await { + Ok(_) => {}, + Err(e) => tracing::error!("[RevQueue]: {}", e), + } + }) + .await; + } + + async fn handle_command(&self, command: RevCommand) -> Result<(), FlowyError> { + match command { + RevCommand::RevisionData { + data, + object_md5: data_md5, + ret, + } => { + let base_rev_id = self.rev_id_counter.value(); + let rev_id = self.rev_id_counter.next_id(); + let revision = Revision::new(&self.object_id, base_rev_id, rev_id, data, data_md5); + + let new_rev_id = self + .rev_persistence + .add_local_revision(revision, &self.rev_compress) + .await?; + + self.rev_id_counter.set(new_rev_id); + let _ = ret.send(Ok(new_rev_id)); + }, } + Ok(()) + } } pub(crate) type RevCommandSender = Sender; diff --git a/frontend/rust-lib/flowy-revision/src/rev_snapshot.rs b/frontend/rust-lib/flowy-revision/src/rev_snapshot.rs index 70a3c97591..a6079295be 100644 --- a/frontend/rust-lib/flowy-revision/src/rev_snapshot.rs +++ b/frontend/rust-lib/flowy-revision/src/rev_snapshot.rs @@ -10,167 +10,175 @@ use std::sync::atomic::Ordering::SeqCst; use std::sync::Arc; pub trait RevisionSnapshotPersistence: Send + Sync { - fn should_generate_snapshot_from_range(&self, start_rev_id: i64, current_rev_id: i64) -> bool { - (current_rev_id - start_rev_id) >= AUTO_GEN_SNAPSHOT_PER_10_REVISION - } + fn should_generate_snapshot_from_range(&self, start_rev_id: i64, current_rev_id: i64) -> bool { + (current_rev_id - start_rev_id) >= AUTO_GEN_SNAPSHOT_PER_10_REVISION + } - fn write_snapshot(&self, rev_id: i64, data: Vec) -> FlowyResult<()>; + fn write_snapshot(&self, rev_id: i64, data: Vec) -> FlowyResult<()>; - fn read_snapshot(&self, rev_id: i64) -> FlowyResult>; + fn read_snapshot(&self, rev_id: i64) -> FlowyResult>; - fn read_last_snapshot(&self) -> FlowyResult>; + fn read_last_snapshot(&self) -> FlowyResult>; } pub trait RevisionSnapshotDataGenerator: Send + Sync { - fn generate_snapshot_data(&self) -> Option; + fn generate_snapshot_data(&self) -> Option; } const AUTO_GEN_SNAPSHOT_PER_10_REVISION: i64 = 10; pub struct RevisionSnapshotController { - user_id: String, - object_id: String, - rev_snapshot_persistence: Arc, - rev_snapshot_data: Option>, - rev_id_counter: Arc, - rev_persistence: Arc>, - rev_compress: Arc, - start_rev_id: AtomicI64, + user_id: String, + object_id: String, + rev_snapshot_persistence: Arc, + rev_snapshot_data: Option>, + rev_id_counter: Arc, + rev_persistence: Arc>, + rev_compress: Arc, + start_rev_id: AtomicI64, } impl RevisionSnapshotController where - Connection: 'static, + Connection: 'static, { - pub fn new( - user_id: &str, - object_id: &str, - disk_cache: D, - rev_id_counter: Arc, - revision_persistence: Arc>, - revision_compress: Arc, - ) -> Self - where - D: RevisionSnapshotPersistence + 'static, + pub fn new( + user_id: &str, + object_id: &str, + disk_cache: D, + rev_id_counter: Arc, + revision_persistence: Arc>, + revision_compress: Arc, + ) -> Self + where + D: RevisionSnapshotPersistence + 'static, + { + let rev_snapshot_persistence = Arc::new(disk_cache); + Self { + user_id: user_id.to_string(), + object_id: object_id.to_string(), + rev_snapshot_persistence, + rev_id_counter, + start_rev_id: AtomicI64::new(0), + rev_snapshot_data: None, + rev_persistence: revision_persistence, + rev_compress: revision_compress, + } + } + + pub async fn set_snapshot_data_generator( + &mut self, + generator: Arc, + ) { + self.rev_snapshot_data = Some(generator); + } + + pub async fn generate_snapshot(&self) { + if let Some((rev_id, bytes)) = self.generate_snapshot_data() { + if let Err(e) = self + .rev_snapshot_persistence + .write_snapshot(rev_id, bytes.to_vec()) + { + tracing::error!("Save snapshot failed: {}", e); + } + } + } + + /// Find the nearest revision base on the passed-in rev_id + #[tracing::instrument(level = "trace", skip_all)] + pub fn restore_from_snapshot(&self, rev_id: i64) -> Option<(B::Output, Revision)> + where + B: RevisionObjectDeserializer, + { + tracing::info!("[Restore] Try to find if {} has snapshot", self.object_id); + let snapshot = self.rev_snapshot_persistence.read_last_snapshot().ok()??; + let snapshot_rev_id = snapshot.rev_id; + let revision = Revision::new( + &self.object_id, + snapshot.base_rev_id, + snapshot.rev_id, + snapshot.data, + "".to_owned(), + ); + tracing::info!( + "[Restore] Try to restore from snapshot: {}, {}", + snapshot.base_rev_id, + snapshot.rev_id + ); + let object = B::deserialize_revisions(&self.object_id, vec![revision.clone()]).ok()?; + tracing::info!( + "[Restore] Restore {} from snapshot with rev_id: {}", + self.object_id, + snapshot_rev_id + ); + + Some((object, revision)) + } + + pub fn generate_snapshot_if_need(&self) { + let current_rev_id = self.rev_id_counter.value(); + let start_rev_id = self.get_start_rev_id(); + if current_rev_id <= start_rev_id { + return; + } + if self + .rev_snapshot_persistence + .should_generate_snapshot_from_range(start_rev_id, current_rev_id) { - let rev_snapshot_persistence = Arc::new(disk_cache); - Self { - user_id: user_id.to_string(), - object_id: object_id.to_string(), - rev_snapshot_persistence, - rev_id_counter, - start_rev_id: AtomicI64::new(0), - rev_snapshot_data: None, - rev_persistence: revision_persistence, - rev_compress: revision_compress, - } + if let Some((rev_id, bytes)) = self.generate_snapshot_data() { + let disk_cache = self.rev_snapshot_persistence.clone(); + tokio::spawn(async move { + let _ = disk_cache.write_snapshot(rev_id, bytes.to_vec()); + }); + } + self.set_start_rev_id(current_rev_id); + } + } + + fn generate_snapshot_data(&self) -> Option<(i64, Bytes)> { + let revisions = self + .rev_persistence + .load_all_records(&self.object_id) + .map(|records| { + records + .into_iter() + .map(|record| record.revision) + .collect::>() + }) + .ok()?; + + if revisions.is_empty() { + return None; } - pub async fn set_snapshot_data_generator(&mut self, generator: Arc) { - self.rev_snapshot_data = Some(generator); - } + let data = self.rev_compress.combine_revisions(revisions).ok()?; + let rev_id = self.rev_id_counter.value(); + Some((rev_id, data)) + } - pub async fn generate_snapshot(&self) { - if let Some((rev_id, bytes)) = self.generate_snapshot_data() { - if let Err(e) = self.rev_snapshot_persistence.write_snapshot(rev_id, bytes.to_vec()) { - tracing::error!("Save snapshot failed: {}", e); - } - } - } + fn get_start_rev_id(&self) -> i64 { + self.start_rev_id.load(SeqCst) + } - /// Find the nearest revision base on the passed-in rev_id - #[tracing::instrument(level = "trace", skip_all)] - pub fn restore_from_snapshot(&self, rev_id: i64) -> Option<(B::Output, Revision)> - where - B: RevisionObjectDeserializer, - { - tracing::info!("[Restore] Try to find if {} has snapshot", self.object_id); - let snapshot = self.rev_snapshot_persistence.read_last_snapshot().ok()??; - let snapshot_rev_id = snapshot.rev_id; - let revision = Revision::new( - &self.object_id, - snapshot.base_rev_id, - snapshot.rev_id, - snapshot.data, - "".to_owned(), - ); - tracing::info!( - "[Restore] Try to restore from snapshot: {}, {}", - snapshot.base_rev_id, - snapshot.rev_id - ); - let object = B::deserialize_revisions(&self.object_id, vec![revision.clone()]).ok()?; - tracing::info!( - "[Restore] Restore {} from snapshot with rev_id: {}", - self.object_id, - snapshot_rev_id - ); - - Some((object, revision)) - } - - pub fn generate_snapshot_if_need(&self) { - let current_rev_id = self.rev_id_counter.value(); - let start_rev_id = self.get_start_rev_id(); - if current_rev_id <= start_rev_id { - return; - } - if self - .rev_snapshot_persistence - .should_generate_snapshot_from_range(start_rev_id, current_rev_id) - { - if let Some((rev_id, bytes)) = self.generate_snapshot_data() { - let disk_cache = self.rev_snapshot_persistence.clone(); - tokio::spawn(async move { - let _ = disk_cache.write_snapshot(rev_id, bytes.to_vec()); - }); - } - self.set_start_rev_id(current_rev_id); - } - } - - fn generate_snapshot_data(&self) -> Option<(i64, Bytes)> { - let revisions = self - .rev_persistence - .load_all_records(&self.object_id) - .map(|records| { - records - .into_iter() - .map(|record| record.revision) - .collect::>() - }) - .ok()?; - - if revisions.is_empty() { - return None; - } - - let data = self.rev_compress.combine_revisions(revisions).ok()?; - let rev_id = self.rev_id_counter.value(); - Some((rev_id, data)) - } - - fn get_start_rev_id(&self) -> i64 { - self.start_rev_id.load(SeqCst) - } - - fn set_start_rev_id(&self, rev_id: i64) { - let _ = self.start_rev_id.fetch_update(SeqCst, SeqCst, |_| Some(rev_id)); - } + fn set_start_rev_id(&self, rev_id: i64) { + let _ = self + .start_rev_id + .fetch_update(SeqCst, SeqCst, |_| Some(rev_id)); + } } impl std::ops::Deref for RevisionSnapshotController { - type Target = Arc; + type Target = Arc; - fn deref(&self) -> &Self::Target { - &self.rev_snapshot_persistence - } + fn deref(&self) -> &Self::Target { + &self.rev_snapshot_persistence + } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct RevisionSnapshotData { - pub rev_id: i64, - pub base_rev_id: i64, - pub timestamp: i64, - pub data: Bytes, + pub rev_id: i64, + pub base_rev_id: i64, + pub timestamp: i64, + pub data: Bytes, } diff --git a/frontend/rust-lib/flowy-revision/src/ws_manager.rs b/frontend/rust-lib/flowy-revision/src/ws_manager.rs index e11ae35406..208b85bfe9 100644 --- a/frontend/rust-lib/flowy-revision/src/ws_manager.rs +++ b/frontend/rust-lib/flowy-revision/src/ws_manager.rs @@ -8,429 +8,448 @@ use lib_ws::WSConnectState; use revision_model::{Revision, RevisionRange}; use std::{collections::VecDeque, fmt::Formatter, sync::Arc}; use tokio::{ - sync::{ - broadcast, mpsc, - mpsc::{Receiver, Sender}, - RwLock, - }, - time::{interval, Duration}, + sync::{ + broadcast, mpsc, + mpsc::{Receiver, Sender}, + RwLock, + }, + time::{interval, Duration}, +}; +use ws_model::ws_revision::{ + ClientRevisionWSData, NewDocumentUser, ServerRevisionWSData, WSRevisionPayload, }; -use ws_model::ws_revision::{ClientRevisionWSData, NewDocumentUser, ServerRevisionWSData, WSRevisionPayload}; // The consumer consumes the messages pushed by the web socket. pub trait RevisionWSDataStream: Send + Sync { - fn receive_push_revision(&self, revisions: Vec) -> BoxResultFuture<(), FlowyError>; - fn receive_ack(&self, rev_id: i64) -> BoxResultFuture<(), FlowyError>; - fn receive_new_user_connect(&self, new_user: NewDocumentUser) -> BoxResultFuture<(), FlowyError>; - fn pull_revisions_in_range(&self, range: RevisionRange) -> BoxResultFuture<(), FlowyError>; + fn receive_push_revision(&self, revisions: Vec) -> BoxResultFuture<(), FlowyError>; + fn receive_ack(&self, rev_id: i64) -> BoxResultFuture<(), FlowyError>; + fn receive_new_user_connect(&self, new_user: NewDocumentUser) -> BoxResultFuture<(), FlowyError>; + fn pull_revisions_in_range(&self, range: RevisionRange) -> BoxResultFuture<(), FlowyError>; } // The sink provides the data that will be sent through the web socket to the // server. pub trait RevisionWebSocketSink: Send + Sync { - fn next(&self) -> FutureResult, FlowyError>; + fn next(&self) -> FutureResult, FlowyError>; } pub type WSStateReceiver = tokio::sync::broadcast::Receiver; pub trait RevisionWebSocket: Send + Sync + 'static { - fn send(&self, data: ClientRevisionWSData) -> BoxResultFuture<(), FlowyError>; - fn subscribe_state_changed(&self) -> BoxFuture; + fn send(&self, data: ClientRevisionWSData) -> BoxResultFuture<(), FlowyError>; + fn subscribe_state_changed(&self) -> BoxFuture; } pub struct RevisionWebSocketManager { - pub object_name: String, - pub object_id: String, - ws_data_sink: Arc, - ws_data_stream: Arc, - rev_web_socket: Arc, - pub ws_passthrough_tx: Sender, - ws_passthrough_rx: Option>, - pub state_passthrough_tx: broadcast::Sender, - stop_sync_tx: SinkStopTx, + pub object_name: String, + pub object_id: String, + ws_data_sink: Arc, + ws_data_stream: Arc, + rev_web_socket: Arc, + pub ws_passthrough_tx: Sender, + ws_passthrough_rx: Option>, + pub state_passthrough_tx: broadcast::Sender, + stop_sync_tx: SinkStopTx, } impl std::fmt::Display for RevisionWebSocketManager { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{}RevisionWebSocketManager", self.object_name)) - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}RevisionWebSocketManager", self.object_name)) + } } impl RevisionWebSocketManager { - pub fn new( - object_name: &str, - object_id: &str, - rev_web_socket: Arc, - ws_data_sink: Arc, - ws_data_stream: Arc, - ping_duration: Duration, - ) -> Self { - let (ws_passthrough_tx, ws_passthrough_rx) = mpsc::channel(1000); - let (stop_sync_tx, _) = tokio::sync::broadcast::channel(2); - let object_id = object_id.to_string(); - let object_name = object_name.to_string(); - let (state_passthrough_tx, _) = broadcast::channel(2); - let mut manager = RevisionWebSocketManager { - object_id, - object_name, - ws_data_sink, - ws_data_stream, - rev_web_socket, - ws_passthrough_tx, - ws_passthrough_rx: Some(ws_passthrough_rx), - state_passthrough_tx, - stop_sync_tx, - }; - manager.run(ping_duration); - manager - } + pub fn new( + object_name: &str, + object_id: &str, + rev_web_socket: Arc, + ws_data_sink: Arc, + ws_data_stream: Arc, + ping_duration: Duration, + ) -> Self { + let (ws_passthrough_tx, ws_passthrough_rx) = mpsc::channel(1000); + let (stop_sync_tx, _) = tokio::sync::broadcast::channel(2); + let object_id = object_id.to_string(); + let object_name = object_name.to_string(); + let (state_passthrough_tx, _) = broadcast::channel(2); + let mut manager = RevisionWebSocketManager { + object_id, + object_name, + ws_data_sink, + ws_data_stream, + rev_web_socket, + ws_passthrough_tx, + ws_passthrough_rx: Some(ws_passthrough_rx), + state_passthrough_tx, + stop_sync_tx, + }; + manager.run(ping_duration); + manager + } - fn run(&mut self, ping_duration: Duration) { - let ws_passthrough_rx = self.ws_passthrough_rx.take().expect("Only take once"); - let sink = RevisionWSSink::new( - &self.object_id, - &self.object_name, - self.ws_data_sink.clone(), - self.rev_web_socket.clone(), - self.stop_sync_tx.subscribe(), - ping_duration, - ); - let stream = RevisionWSStream::new( - &self.object_name, - &self.object_id, - self.ws_data_stream.clone(), - ws_passthrough_rx, - self.stop_sync_tx.subscribe(), - ); - tokio::spawn(sink.run()); - tokio::spawn(stream.run()); - } + fn run(&mut self, ping_duration: Duration) { + let ws_passthrough_rx = self.ws_passthrough_rx.take().expect("Only take once"); + let sink = RevisionWSSink::new( + &self.object_id, + &self.object_name, + self.ws_data_sink.clone(), + self.rev_web_socket.clone(), + self.stop_sync_tx.subscribe(), + ping_duration, + ); + let stream = RevisionWSStream::new( + &self.object_name, + &self.object_id, + self.ws_data_stream.clone(), + ws_passthrough_rx, + self.stop_sync_tx.subscribe(), + ); + tokio::spawn(sink.run()); + tokio::spawn(stream.run()); + } - pub fn scribe_state(&self) -> broadcast::Receiver { - self.state_passthrough_tx.subscribe() - } + pub fn scribe_state(&self) -> broadcast::Receiver { + self.state_passthrough_tx.subscribe() + } - pub fn stop(&self) { - if self.stop_sync_tx.send(()).is_ok() { - tracing::trace!("{} stop sync", self.object_id) - } + pub fn stop(&self) { + if self.stop_sync_tx.send(()).is_ok() { + tracing::trace!("{} stop sync", self.object_id) } + } - #[tracing::instrument(level = "debug", skip(self, data), err)] - pub async fn receive_ws_data(&self, data: ServerRevisionWSData) -> Result<(), FlowyError> { - self.ws_passthrough_tx.send(data).await.map_err(|e| { - let err_msg = format!("{} passthrough error: {}", self.object_id, e); - FlowyError::internal().context(err_msg) - })?; - Ok(()) - } + #[tracing::instrument(level = "debug", skip(self, data), err)] + pub async fn receive_ws_data(&self, data: ServerRevisionWSData) -> Result<(), FlowyError> { + self.ws_passthrough_tx.send(data).await.map_err(|e| { + let err_msg = format!("{} passthrough error: {}", self.object_id, e); + FlowyError::internal().context(err_msg) + })?; + Ok(()) + } - pub fn connect_state_changed(&self, state: WSConnectState) { - match self.state_passthrough_tx.send(state) { - Ok(_) => {} - Err(e) => tracing::error!("{}", e), - } + pub fn connect_state_changed(&self, state: WSConnectState) { + match self.state_passthrough_tx.send(state) { + Ok(_) => {}, + Err(e) => tracing::error!("{}", e), } + } } impl std::ops::Drop for RevisionWebSocketManager { - fn drop(&mut self) { - tracing::trace!("{} was dropped", self) - } + fn drop(&mut self) { + tracing::trace!("{} was dropped", self) + } } pub struct RevisionWSStream { - object_name: String, - object_id: String, - consumer: Arc, - ws_msg_rx: Option>, - stop_rx: Option, + object_name: String, + object_id: String, + consumer: Arc, + ws_msg_rx: Option>, + stop_rx: Option, } impl std::fmt::Display for RevisionWSStream { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{}RevisionWSStream", self.object_name)) - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}RevisionWSStream", self.object_name)) + } } impl std::ops::Drop for RevisionWSStream { - fn drop(&mut self) { - tracing::trace!("{} was dropped", self) - } + fn drop(&mut self) { + tracing::trace!("{} was dropped", self) + } } impl RevisionWSStream { - pub fn new( - object_name: &str, - object_id: &str, - consumer: Arc, - ws_msg_rx: mpsc::Receiver, - stop_rx: SinkStopRx, - ) -> Self { - RevisionWSStream { - object_name: object_name.to_string(), - object_id: object_id.to_owned(), - consumer, - ws_msg_rx: Some(ws_msg_rx), - stop_rx: Some(stop_rx), + pub fn new( + object_name: &str, + object_id: &str, + consumer: Arc, + ws_msg_rx: mpsc::Receiver, + stop_rx: SinkStopRx, + ) -> Self { + RevisionWSStream { + object_name: object_name.to_string(), + object_id: object_id.to_owned(), + consumer, + ws_msg_rx: Some(ws_msg_rx), + stop_rx: Some(stop_rx), + } + } + + pub async fn run(mut self) { + let mut receiver = self.ws_msg_rx.take().expect("Only take once"); + let mut stop_rx = self.stop_rx.take().expect("Only take once"); + let object_id = self.object_id.clone(); + let name = format!("{}", &self); + let stream = stream! { + loop { + tokio::select! { + result = receiver.recv() => { + match result { + Some(msg) => { + yield msg + }, + None => { + tracing::debug!("[{}]:{} loop exit", name, object_id); + break; + }, + } + }, + _ = stop_rx.recv() => { + tracing::debug!("[{}]:{} loop exit", name, object_id); + break + }, + }; } - } + }; - pub async fn run(mut self) { - let mut receiver = self.ws_msg_rx.take().expect("Only take once"); - let mut stop_rx = self.stop_rx.take().expect("Only take once"); - let object_id = self.object_id.clone(); - let name = format!("{}", &self); - let stream = stream! { - loop { - tokio::select! { - result = receiver.recv() => { - match result { - Some(msg) => { - yield msg - }, - None => { - tracing::debug!("[{}]:{} loop exit", name, object_id); - break; - }, - } - }, - _ = stop_rx.recv() => { - tracing::debug!("[{}]:{} loop exit", name, object_id); - break - }, - }; - } - }; - - stream - .for_each(|msg| async { - match self.handle_message(msg).await { - Ok(_) => {} - Err(e) => tracing::error!("[{}]:{} error: {}", &self, self.object_id, e), - } - }) - .await; - } - - async fn handle_message(&self, msg: ServerRevisionWSData) -> FlowyResult<()> { - let ServerRevisionWSData { object_id, payload } = msg; - match payload { - WSRevisionPayload::ServerPushRev { revisions } => { - tracing::trace!("[{}]: new push revision: {}", self, object_id); - self.consumer.receive_push_revision(revisions).await?; - } - WSRevisionPayload::ServerPullRev { range } => { - tracing::trace!("[{}]: new pull: {}:{:?}", self, object_id, range); - self.consumer.pull_revisions_in_range(range).await?; - } - WSRevisionPayload::ServerAck { rev_id } => { - tracing::trace!("[{}]: new ack: {}:{}", self, object_id, rev_id); - let _ = self.consumer.receive_ack(rev_id).await; - } - WSRevisionPayload::UserConnect { user } => { - let _ = self.consumer.receive_new_user_connect(user).await; - } + stream + .for_each(|msg| async { + match self.handle_message(msg).await { + Ok(_) => {}, + Err(e) => tracing::error!("[{}]:{} error: {}", &self, self.object_id, e), } - Ok(()) + }) + .await; + } + + async fn handle_message(&self, msg: ServerRevisionWSData) -> FlowyResult<()> { + let ServerRevisionWSData { object_id, payload } = msg; + match payload { + WSRevisionPayload::ServerPushRev { revisions } => { + tracing::trace!("[{}]: new push revision: {}", self, object_id); + self.consumer.receive_push_revision(revisions).await?; + }, + WSRevisionPayload::ServerPullRev { range } => { + tracing::trace!("[{}]: new pull: {}:{:?}", self, object_id, range); + self.consumer.pull_revisions_in_range(range).await?; + }, + WSRevisionPayload::ServerAck { rev_id } => { + tracing::trace!("[{}]: new ack: {}:{}", self, object_id, rev_id); + let _ = self.consumer.receive_ack(rev_id).await; + }, + WSRevisionPayload::UserConnect { user } => { + let _ = self.consumer.receive_new_user_connect(user).await; + }, } + Ok(()) + } } type SinkStopRx = broadcast::Receiver<()>; type SinkStopTx = broadcast::Sender<()>; pub struct RevisionWSSink { - object_id: String, - object_name: String, - provider: Arc, - rev_web_socket: Arc, - stop_rx: Option, - ping_duration: Duration, + object_id: String, + object_name: String, + provider: Arc, + rev_web_socket: Arc, + stop_rx: Option, + ping_duration: Duration, } impl RevisionWSSink { - pub fn new( - object_id: &str, - object_name: &str, - provider: Arc, - rev_web_socket: Arc, - stop_rx: SinkStopRx, - ping_duration: Duration, - ) -> Self { - Self { - object_id: object_id.to_owned(), - object_name: object_name.to_owned(), - provider, - rev_web_socket, - stop_rx: Some(stop_rx), - ping_duration, - } + pub fn new( + object_id: &str, + object_name: &str, + provider: Arc, + rev_web_socket: Arc, + stop_rx: SinkStopRx, + ping_duration: Duration, + ) -> Self { + Self { + object_id: object_id.to_owned(), + object_name: object_name.to_owned(), + provider, + rev_web_socket, + stop_rx: Some(stop_rx), + ping_duration, } + } - pub async fn run(mut self) { - let (tx, mut rx) = mpsc::channel(1); - let mut stop_rx = self.stop_rx.take().expect("Only take once"); - let object_id = self.object_id.clone(); - tokio::spawn(tick(tx, self.ping_duration)); - let name = format!("{}", self); - let stream = stream! { - loop { - tokio::select! { - result = rx.recv() => { - match result { - Some(msg) => yield msg, - None => break, - } - }, - _ = stop_rx.recv() => { - tracing::trace!("[{}]:{} loop exit", name, object_id); - break - }, - }; - } - }; - stream - .for_each(|_| async { - match self.send_next_revision().await { - Ok(_) => {} - Err(e) => tracing::error!("[{}] send failed, {:?}", self, e), - } - }) - .await; - } - - async fn send_next_revision(&self) -> FlowyResult<()> { - match self.provider.next().await? { - None => { - tracing::trace!("[{}]: Finish synchronizing revisions", self); - Ok(()) - } - Some(data) => { - tracing::trace!("[{}]: send {}:{}-{:?}", self, data.object_id, data.rev_id, data.ty); - self.rev_web_socket.send(data).await - } + pub async fn run(mut self) { + let (tx, mut rx) = mpsc::channel(1); + let mut stop_rx = self.stop_rx.take().expect("Only take once"); + let object_id = self.object_id.clone(); + tokio::spawn(tick(tx, self.ping_duration)); + let name = format!("{}", self); + let stream = stream! { + loop { + tokio::select! { + result = rx.recv() => { + match result { + Some(msg) => yield msg, + None => break, + } + }, + _ = stop_rx.recv() => { + tracing::trace!("[{}]:{} loop exit", name, object_id); + break + }, + }; } + }; + stream + .for_each(|_| async { + match self.send_next_revision().await { + Ok(_) => {}, + Err(e) => tracing::error!("[{}] send failed, {:?}", self, e), + } + }) + .await; + } + + async fn send_next_revision(&self) -> FlowyResult<()> { + match self.provider.next().await? { + None => { + tracing::trace!("[{}]: Finish synchronizing revisions", self); + Ok(()) + }, + Some(data) => { + tracing::trace!( + "[{}]: send {}:{}-{:?}", + self, + data.object_id, + data.rev_id, + data.ty + ); + self.rev_web_socket.send(data).await + }, } + } } async fn tick(sender: mpsc::Sender<()>, duration: Duration) { - let mut interval = interval(duration); - while sender.send(()).await.is_ok() { - interval.tick().await; - } + let mut interval = interval(duration); + while sender.send(()).await.is_ok() { + interval.tick().await; + } } impl std::fmt::Display for RevisionWSSink { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{}RevisionWSSink", self.object_name)) - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}RevisionWSSink", self.object_name)) + } } impl std::ops::Drop for RevisionWSSink { - fn drop(&mut self) { - tracing::trace!("{} was dropped", self) - } + fn drop(&mut self) { + tracing::trace!("{} was dropped", self) + } } #[derive(Clone)] enum Source { - Custom, - Revision, + Custom, + Revision, } pub trait WSDataProviderDataSource: Send + Sync { - fn next_revision(&self) -> FutureResult, FlowyError>; - fn ack_revision(&self, rev_id: i64) -> FutureResult<(), FlowyError>; - fn current_rev_id(&self) -> i64; + fn next_revision(&self) -> FutureResult, FlowyError>; + fn ack_revision(&self, rev_id: i64) -> FutureResult<(), FlowyError>; + fn current_rev_id(&self) -> i64; } #[derive(Clone)] pub struct WSDataProvider { - object_id: String, - rev_ws_data_list: Arc>>, - data_source: Arc, - current_source: Arc>, + object_id: String, + rev_ws_data_list: Arc>>, + data_source: Arc, + current_source: Arc>, } impl WSDataProvider { - pub fn new(object_id: &str, data_source: Arc) -> Self { - WSDataProvider { - object_id: object_id.to_owned(), - rev_ws_data_list: Arc::new(RwLock::new(VecDeque::new())), - data_source, - current_source: Arc::new(RwLock::new(Source::Custom)), + pub fn new(object_id: &str, data_source: Arc) -> Self { + WSDataProvider { + object_id: object_id.to_owned(), + rev_ws_data_list: Arc::new(RwLock::new(VecDeque::new())), + data_source, + current_source: Arc::new(RwLock::new(Source::Custom)), + } + } + + pub async fn push_data(&self, data: ClientRevisionWSData) { + self.rev_ws_data_list.write().await.push_back(data); + } + + pub async fn next(&self) -> FlowyResult> { + let source = self.current_source.read().await.clone(); + let data = match source { + Source::Custom => match self.rev_ws_data_list.read().await.front() { + None => { + *self.current_source.write().await = Source::Revision; + Ok(None) + }, + Some(data) => Ok(Some(data.clone())), + }, + Source::Revision => { + if !self.rev_ws_data_list.read().await.is_empty() { + *self.current_source.write().await = Source::Custom; + return Ok(None); } - } - pub async fn push_data(&self, data: ClientRevisionWSData) { - self.rev_ws_data_list.write().await.push_back(data); - } + match self.data_source.next_revision().await? { + Some(rev) => Ok(Some(ClientRevisionWSData::from_revisions( + &self.object_id, + vec![rev], + ))), + None => Ok(Some(ClientRevisionWSData::ping( + &self.object_id, + self.data_source.current_rev_id(), + ))), + } + }, + }; + data + } - pub async fn next(&self) -> FlowyResult> { - let source = self.current_source.read().await.clone(); - let data = match source { - Source::Custom => match self.rev_ws_data_list.read().await.front() { - None => { - *self.current_source.write().await = Source::Revision; - Ok(None) - } - Some(data) => Ok(Some(data.clone())), - }, - Source::Revision => { - if !self.rev_ws_data_list.read().await.is_empty() { - *self.current_source.write().await = Source::Custom; - return Ok(None); - } - - match self.data_source.next_revision().await? { - Some(rev) => Ok(Some(ClientRevisionWSData::from_revisions(&self.object_id, vec![rev]))), - None => Ok(Some(ClientRevisionWSData::ping( - &self.object_id, - self.data_source.current_rev_id(), - ))), - } + pub async fn ack_data(&self, rev_id: i64) -> FlowyResult<()> { + let source = self.current_source.read().await.clone(); + match source { + Source::Custom => { + let should_pop = match self.rev_ws_data_list.read().await.front() { + None => false, + Some(val) => { + if val.rev_id == rev_id { + true + } else { + tracing::error!( + "The front element's {} is not equal to the {}", + val.rev_id, + rev_id + ); + false } + }, }; - data - } - - pub async fn ack_data(&self, rev_id: i64) -> FlowyResult<()> { - let source = self.current_source.read().await.clone(); - match source { - Source::Custom => { - let should_pop = match self.rev_ws_data_list.read().await.front() { - None => false, - Some(val) => { - if val.rev_id == rev_id { - true - } else { - tracing::error!("The front element's {} is not equal to the {}", val.rev_id, rev_id); - false - } - } - }; - if should_pop { - let _ = self.rev_ws_data_list.write().await.pop_front(); - } - Ok(()) - } - Source::Revision => { - self.data_source.ack_revision(rev_id).await?; - Ok::<(), FlowyError>(()) - } + if should_pop { + let _ = self.rev_ws_data_list.write().await.pop_front(); } + Ok(()) + }, + Source::Revision => { + self.data_source.ack_revision(rev_id).await?; + Ok::<(), FlowyError>(()) + }, } + } } impl ConflictRevisionSink for Arc { - fn send(&self, revisions: Vec) -> BoxResultFuture<(), FlowyError> { - let sink = self.clone(); - Box::pin(async move { - sink.push_data(ClientRevisionWSData::from_revisions(&sink.object_id, revisions)) - .await; - Ok(()) - }) - } + fn send(&self, revisions: Vec) -> BoxResultFuture<(), FlowyError> { + let sink = self.clone(); + Box::pin(async move { + sink + .push_data(ClientRevisionWSData::from_revisions( + &sink.object_id, + revisions, + )) + .await; + Ok(()) + }) + } - fn ack(&self, rev_id: i64) -> BoxResultFuture<(), FlowyError> { - let sink = self.clone(); - Box::pin(async move { sink.ack_data(rev_id).await }) - } + fn ack(&self, rev_id: i64) -> BoxResultFuture<(), FlowyError> { + let sink = self.clone(); + Box::pin(async move { sink.ack_data(rev_id).await }) + } } diff --git a/frontend/rust-lib/flowy-revision/tests/revision_test/local_revision_test.rs b/frontend/rust-lib/flowy-revision/tests/revision_test/local_revision_test.rs index 152b0dca43..7bda4525dd 100644 --- a/frontend/rust-lib/flowy-revision/tests/revision_test/local_revision_test.rs +++ b/frontend/rust-lib/flowy-revision/tests/revision_test/local_revision_test.rs @@ -2,255 +2,298 @@ use crate::revision_test::script::{RevisionScript::*, RevisionTest}; #[tokio::test] async fn revision_sync_test() { - let test = RevisionTest::new().await; - let rev_id = 1; - test.run_script(AddLocalRevision { - content: "123".to_string(), + let test = RevisionTest::new().await; + let rev_id = 1; + test + .run_script(AddLocalRevision { + content: "123".to_string(), }) .await; - test.run_script(AssertNextSyncRevisionId { rev_id: Some(rev_id) }).await; - test.run_script(AckRevision { rev_id }).await; - test.run_script(AssertNextSyncRevisionId { rev_id: None }).await; + test + .run_script(AssertNextSyncRevisionId { + rev_id: Some(rev_id), + }) + .await; + test.run_script(AckRevision { rev_id }).await; + test + .run_script(AssertNextSyncRevisionId { rev_id: None }) + .await; } #[tokio::test] async fn revision_compress_2_revisions_with_2_threshold_test() { - let test = RevisionTest::new_with_configuration(2).await; - test.run_script(AddLocalRevision2 { - content: "123".to_string(), + let test = RevisionTest::new_with_configuration(2).await; + test + .run_script(AddLocalRevision2 { + content: "123".to_string(), }) .await; - test.run_script(AddLocalRevision2 { - content: "456".to_string(), + test + .run_script(AddLocalRevision2 { + content: "456".to_string(), }) .await; - test.run_scripts(vec![ - AssertNextSyncRevisionId { rev_id: Some(2) }, - AssertNextSyncRevisionContent { - expected: "123456".to_string(), - }, - AckRevision { rev_id: 2 }, - AssertNextSyncRevisionId { rev_id: None }, + test + .run_scripts(vec![ + AssertNextSyncRevisionId { rev_id: Some(2) }, + AssertNextSyncRevisionContent { + expected: "123456".to_string(), + }, + AckRevision { rev_id: 2 }, + AssertNextSyncRevisionId { rev_id: None }, ]) .await; } #[tokio::test] async fn revision_compress_4_revisions_with_threshold_2_test() { - let test = RevisionTest::new_with_configuration(2).await; - test.run_script(AddLocalRevision { - content: "1".to_string(), + let test = RevisionTest::new_with_configuration(2).await; + test + .run_script(AddLocalRevision { + content: "1".to_string(), }) .await; - test.run_script(AddLocalRevision { - content: "2".to_string(), + test + .run_script(AddLocalRevision { + content: "2".to_string(), }) .await; - test.run_script(AddLocalRevision { - content: "3".to_string(), + test + .run_script(AddLocalRevision { + content: "3".to_string(), }) .await; - test.run_script(AddLocalRevision { - content: "4".to_string(), + test + .run_script(AddLocalRevision { + content: "4".to_string(), }) .await; - test.run_scripts(vec![ - AssertNumberOfSyncRevisions { num: 2 }, - AssertNextSyncRevisionId { rev_id: Some(2) }, - AssertNextSyncRevisionContent { - expected: "12".to_string(), - }, - AckRevision { rev_id: 2 }, - AssertNextSyncRevisionId { rev_id: Some(4) }, - AssertNextSyncRevisionContent { - expected: "34".to_string(), - }, + test + .run_scripts(vec![ + AssertNumberOfSyncRevisions { num: 2 }, + AssertNextSyncRevisionId { rev_id: Some(2) }, + AssertNextSyncRevisionContent { + expected: "12".to_string(), + }, + AckRevision { rev_id: 2 }, + AssertNextSyncRevisionId { rev_id: Some(4) }, + AssertNextSyncRevisionContent { + expected: "34".to_string(), + }, ]) .await; } #[tokio::test] async fn revision_compress_8_revisions_with_threshold_4_test() { - let merge_len = 4; - let test = RevisionTest::new_with_configuration(merge_len).await; - test.run_script(AddLocalRevision { - content: "1".to_string(), + let merge_len = 4; + let test = RevisionTest::new_with_configuration(merge_len).await; + test + .run_script(AddLocalRevision { + content: "1".to_string(), }) .await; - test.run_script(AddLocalRevision { - content: "2".to_string(), + test + .run_script(AddLocalRevision { + content: "2".to_string(), }) .await; - test.run_script(AddLocalRevision { - content: "3".to_string(), + test + .run_script(AddLocalRevision { + content: "3".to_string(), }) .await; - test.run_script(AddLocalRevision { - content: "4".to_string(), + test + .run_script(AddLocalRevision { + content: "4".to_string(), }) .await; - test.run_script(AddLocalRevision { - content: "a".to_string(), + test + .run_script(AddLocalRevision { + content: "a".to_string(), }) .await; - test.run_script(AddLocalRevision { - content: "b".to_string(), + test + .run_script(AddLocalRevision { + content: "b".to_string(), }) .await; - test.run_script(AddLocalRevision { - content: "c".to_string(), + test + .run_script(AddLocalRevision { + content: "c".to_string(), }) .await; - test.run_script(AddLocalRevision { - content: "d".to_string(), + test + .run_script(AddLocalRevision { + content: "d".to_string(), }) .await; - test.run_scripts(vec![ - AssertNumberOfSyncRevisions { num: 2 }, - AssertNextSyncRevisionId { - rev_id: Some(merge_len), - }, - AssertNextSyncRevisionContent { - expected: "1234".to_string(), - }, - AckRevision { rev_id: merge_len }, - AssertNextSyncRevisionId { - rev_id: Some(merge_len * 2), - }, - AssertNextSyncRevisionContent { - expected: "abcd".to_string(), - }, - AckRevision { rev_id: merge_len * 2 }, - AssertNextSyncRevisionId { rev_id: None }, + test + .run_scripts(vec![ + AssertNumberOfSyncRevisions { num: 2 }, + AssertNextSyncRevisionId { + rev_id: Some(merge_len), + }, + AssertNextSyncRevisionContent { + expected: "1234".to_string(), + }, + AckRevision { rev_id: merge_len }, + AssertNextSyncRevisionId { + rev_id: Some(merge_len * 2), + }, + AssertNextSyncRevisionContent { + expected: "abcd".to_string(), + }, + AckRevision { + rev_id: merge_len * 2, + }, + AssertNextSyncRevisionId { rev_id: None }, ]) .await; } #[tokio::test] async fn revision_merge_per_5_revision_test() { - let merge_len = 5; - let test = RevisionTest::new_with_configuration(merge_len).await; - for i in 0..20 { - let content = format!("{}", i); - test.run_script(AddLocalRevision { content }).await; - } + let merge_len = 5; + let test = RevisionTest::new_with_configuration(merge_len).await; + for i in 0..20 { + let content = format!("{}", i); + test.run_script(AddLocalRevision { content }).await; + } - test.run_scripts(vec![ - AssertNumberOfSyncRevisions { num: 4 }, - AssertNextSyncRevisionContent { - expected: "01234".to_string(), - }, - AckRevision { rev_id: merge_len }, - AssertNextSyncRevisionContent { - expected: "56789".to_string(), - }, - AckRevision { rev_id: merge_len * 2 }, - AssertNextSyncRevisionContent { - expected: "1011121314".to_string(), - }, - AckRevision { rev_id: merge_len * 3 }, - AssertNextSyncRevisionContent { - expected: "1516171819".to_string(), - }, - AckRevision { rev_id: merge_len * 4 }, - AssertNextSyncRevisionId { rev_id: None }, + test + .run_scripts(vec![ + AssertNumberOfSyncRevisions { num: 4 }, + AssertNextSyncRevisionContent { + expected: "01234".to_string(), + }, + AckRevision { rev_id: merge_len }, + AssertNextSyncRevisionContent { + expected: "56789".to_string(), + }, + AckRevision { + rev_id: merge_len * 2, + }, + AssertNextSyncRevisionContent { + expected: "1011121314".to_string(), + }, + AckRevision { + rev_id: merge_len * 3, + }, + AssertNextSyncRevisionContent { + expected: "1516171819".to_string(), + }, + AckRevision { + rev_id: merge_len * 4, + }, + AssertNextSyncRevisionId { rev_id: None }, ]) .await; } #[tokio::test] async fn revision_merge_per_100_revision_test() { - let test = RevisionTest::new_with_configuration(100).await; - for i in 0..1000 { - let content = format!("{}", i); - test.run_script(AddLocalRevision { content }).await; - } + let test = RevisionTest::new_with_configuration(100).await; + for i in 0..1000 { + let content = format!("{}", i); + test.run_script(AddLocalRevision { content }).await; + } - test.run_scripts(vec![AssertNumberOfSyncRevisions { num: 10 }]).await; + test + .run_scripts(vec![AssertNumberOfSyncRevisions { num: 10 }]) + .await; } #[tokio::test] async fn revision_merge_per_100_revision_test2() { - let test = RevisionTest::new_with_configuration(100).await; - for i in 0..50 { - test.run_script(AddLocalRevision { - content: format!("{}", i), - }) - .await; - } + let test = RevisionTest::new_with_configuration(100).await; + for i in 0..50 { + test + .run_script(AddLocalRevision { + content: format!("{}", i), + }) + .await; + } - test.run_scripts(vec![AssertNumberOfSyncRevisions { num: 50 }]).await; + test + .run_scripts(vec![AssertNumberOfSyncRevisions { num: 50 }]) + .await; } #[tokio::test] async fn revision_merge_per_1000_revision_test() { - let test = RevisionTest::new_with_configuration(1000).await; - for i in 0..100000 { - test.run_script(AddLocalRevision { - content: format!("{}", i), - }) - .await; - } + let test = RevisionTest::new_with_configuration(1000).await; + for i in 0..100000 { + test + .run_script(AddLocalRevision { + content: format!("{}", i), + }) + .await; + } - test.run_scripts(vec![AssertNumberOfSyncRevisions { num: 100 }]).await; + test + .run_scripts(vec![AssertNumberOfSyncRevisions { num: 100 }]) + .await; } #[tokio::test] async fn revision_compress_revision_test() { - let test = RevisionTest::new_with_configuration(2).await; - test.run_scripts(vec![ - AddLocalRevision2 { - content: "1".to_string(), - }, - AddLocalRevision2 { - content: "2".to_string(), - }, - AddLocalRevision2 { - content: "3".to_string(), - }, - AddLocalRevision2 { - content: "4".to_string(), - }, - AssertNumberOfSyncRevisions { num: 2 }, + let test = RevisionTest::new_with_configuration(2).await; + test + .run_scripts(vec![ + AddLocalRevision2 { + content: "1".to_string(), + }, + AddLocalRevision2 { + content: "2".to_string(), + }, + AddLocalRevision2 { + content: "3".to_string(), + }, + AddLocalRevision2 { + content: "4".to_string(), + }, + AssertNumberOfSyncRevisions { num: 2 }, ]) .await; } #[tokio::test] async fn revision_compress_revision_while_recv_ack_test() { - let test = RevisionTest::new_with_configuration(2).await; - test.run_scripts(vec![ - AddLocalRevision2 { - content: "1".to_string(), - }, - AckRevision { rev_id: 1 }, - AddLocalRevision2 { - content: "2".to_string(), - }, - AckRevision { rev_id: 2 }, - AddLocalRevision2 { - content: "3".to_string(), - }, - AckRevision { rev_id: 3 }, - AddLocalRevision2 { - content: "4".to_string(), - }, - AssertNumberOfSyncRevisions { num: 4 }, + let test = RevisionTest::new_with_configuration(2).await; + test + .run_scripts(vec![ + AddLocalRevision2 { + content: "1".to_string(), + }, + AckRevision { rev_id: 1 }, + AddLocalRevision2 { + content: "2".to_string(), + }, + AckRevision { rev_id: 2 }, + AddLocalRevision2 { + content: "3".to_string(), + }, + AckRevision { rev_id: 3 }, + AddLocalRevision2 { + content: "4".to_string(), + }, + AssertNumberOfSyncRevisions { num: 4 }, ]) .await; } diff --git a/frontend/rust-lib/flowy-revision/tests/revision_test/revision_disk_test.rs b/frontend/rust-lib/flowy-revision/tests/revision_test/revision_disk_test.rs index 4d27aa7592..e0bc66b00a 100644 --- a/frontend/rust-lib/flowy-revision/tests/revision_test/revision_disk_test.rs +++ b/frontend/rust-lib/flowy-revision/tests/revision_test/revision_disk_test.rs @@ -3,83 +3,92 @@ use crate::revision_test::script::{InvalidRevisionObject, RevisionTest}; #[tokio::test] async fn revision_write_to_disk_test() { - let test = RevisionTest::new_with_configuration(2).await; - test.run_script(AddLocalRevision { - content: "123".to_string(), + let test = RevisionTest::new_with_configuration(2).await; + test + .run_script(AddLocalRevision { + content: "123".to_string(), }) .await; - test.run_scripts(vec![ - AssertNumberOfRevisionsInDisk { num: 0 }, - WaitWhenWriteToDisk, - AssertNumberOfRevisionsInDisk { num: 1 }, + test + .run_scripts(vec![ + AssertNumberOfRevisionsInDisk { num: 0 }, + WaitWhenWriteToDisk, + AssertNumberOfRevisionsInDisk { num: 1 }, ]) .await; } #[tokio::test] async fn revision_write_to_disk_with_merge_test() { - let test = RevisionTest::new_with_configuration(100).await; - for i in 0..1000 { - test.run_script(AddLocalRevision { - content: format!("{}", i), - }) - .await; - } + let test = RevisionTest::new_with_configuration(100).await; + for i in 0..1000 { + test + .run_script(AddLocalRevision { + content: format!("{}", i), + }) + .await; + } - test.run_scripts(vec![ - AssertNumberOfRevisionsInDisk { num: 0 }, - AssertNumberOfSyncRevisions { num: 10 }, - WaitWhenWriteToDisk, - AssertNumberOfRevisionsInDisk { num: 10 }, + test + .run_scripts(vec![ + AssertNumberOfRevisionsInDisk { num: 0 }, + AssertNumberOfSyncRevisions { num: 10 }, + WaitWhenWriteToDisk, + AssertNumberOfRevisionsInDisk { num: 10 }, ]) .await; } #[tokio::test] async fn revision_read_from_disk_test() { - let test = RevisionTest::new_with_configuration(2).await; - test.run_scripts(vec![ - AddLocalRevision { - content: "123".to_string(), - }, - AssertNumberOfRevisionsInDisk { num: 0 }, - WaitWhenWriteToDisk, - AssertNumberOfRevisionsInDisk { num: 1 }, + let test = RevisionTest::new_with_configuration(2).await; + test + .run_scripts(vec![ + AddLocalRevision { + content: "123".to_string(), + }, + AssertNumberOfRevisionsInDisk { num: 0 }, + WaitWhenWriteToDisk, + AssertNumberOfRevisionsInDisk { num: 1 }, ]) .await; - let test = RevisionTest::new_with_other(test).await; - test.run_scripts(vec![ - AssertNextSyncRevisionId { rev_id: Some(1) }, - AddLocalRevision { - content: "456".to_string(), - }, - AckRevision { rev_id: 1 }, - AssertNextSyncRevisionId { rev_id: Some(2) }, + let test = RevisionTest::new_with_other(test).await; + test + .run_scripts(vec![ + AssertNextSyncRevisionId { rev_id: Some(1) }, + AddLocalRevision { + content: "456".to_string(), + }, + AckRevision { rev_id: 1 }, + AssertNextSyncRevisionId { rev_id: Some(2) }, ]) .await; } #[tokio::test] async fn revision_read_from_disk_with_invalid_record_test() { - let test = RevisionTest::new_with_configuration(2).await; - test.run_scripts(vec![AddLocalRevision { - content: "123".to_string(), + let test = RevisionTest::new_with_configuration(2).await; + test + .run_scripts(vec![AddLocalRevision { + content: "123".to_string(), }]) .await; - test.run_scripts(vec![ - AddInvalidLocalRevision { - bytes: InvalidRevisionObject::new().to_bytes(), - }, - WaitWhenWriteToDisk, + test + .run_scripts(vec![ + AddInvalidLocalRevision { + bytes: InvalidRevisionObject::new().to_bytes(), + }, + WaitWhenWriteToDisk, ]) .await; - let test = RevisionTest::new_with_other(test).await; - test.run_scripts(vec![AssertNextSyncRevisionContent { - expected: "123".to_string(), + let test = RevisionTest::new_with_other(test).await; + test + .run_scripts(vec![AssertNextSyncRevisionContent { + expected: "123".to_string(), }]) .await; } diff --git a/frontend/rust-lib/flowy-revision/tests/revision_test/script.rs b/frontend/rust-lib/flowy-revision/tests/revision_test/script.rs index 841270eef4..ec285a0cc6 100644 --- a/frontend/rust-lib/flowy-revision/tests/revision_test/script.rs +++ b/frontend/rust-lib/flowy-revision/tests/revision_test/script.rs @@ -1,9 +1,9 @@ use bytes::Bytes; use flowy_error::{internal_error, FlowyError, FlowyResult}; use flowy_revision::{ - RevisionManager, RevisionMergeable, RevisionObjectDeserializer, RevisionPersistence, - RevisionPersistenceConfiguration, RevisionSnapshotData, RevisionSnapshotPersistence, - REVISION_WRITE_INTERVAL_IN_MILLIS, + RevisionManager, RevisionMergeable, RevisionObjectDeserializer, RevisionPersistence, + RevisionPersistenceConfiguration, RevisionSnapshotData, RevisionSnapshotPersistence, + REVISION_WRITE_INTERVAL_IN_MILLIS, }; use flowy_revision_persistence::{RevisionChangeset, RevisionDiskCache, SyncRecord}; @@ -16,325 +16,354 @@ use std::sync::Arc; use std::time::Duration; pub enum RevisionScript { - AddLocalRevision { content: String }, - AddLocalRevision2 { content: String }, - AddInvalidLocalRevision { bytes: Vec }, - AckRevision { rev_id: i64 }, - AssertNextSyncRevisionId { rev_id: Option }, - AssertNumberOfSyncRevisions { num: usize }, - AssertNumberOfRevisionsInDisk { num: usize }, - AssertNextSyncRevisionContent { expected: String }, - WaitWhenWriteToDisk, + AddLocalRevision { content: String }, + AddLocalRevision2 { content: String }, + AddInvalidLocalRevision { bytes: Vec }, + AckRevision { rev_id: i64 }, + AssertNextSyncRevisionId { rev_id: Option }, + AssertNumberOfSyncRevisions { num: usize }, + AssertNumberOfRevisionsInDisk { num: usize }, + AssertNextSyncRevisionContent { expected: String }, + WaitWhenWriteToDisk, } pub struct RevisionTest { - user_id: String, - object_id: String, - configuration: RevisionPersistenceConfiguration, - rev_manager: Arc>, + user_id: String, + object_id: String, + configuration: RevisionPersistenceConfiguration, + rev_manager: Arc>, } impl RevisionTest { - pub async fn new() -> Self { - Self::new_with_configuration(2).await - } + pub async fn new() -> Self { + Self::new_with_configuration(2).await + } - pub async fn new_with_configuration(max_merge_len: i64) -> Self { - let user_id = nanoid!(10); - let object_id = nanoid!(6); - let configuration = RevisionPersistenceConfiguration::new(max_merge_len as usize, false); - let disk_cache = RevisionDiskCacheMock::new(vec![]); - let persistence = RevisionPersistence::new(&user_id, &object_id, disk_cache, configuration.clone()); - let compress = RevisionMergeableMock {}; - let snapshot = RevisionSnapshotMock {}; - let mut rev_manager = RevisionManager::new(&user_id, &object_id, persistence, compress, snapshot); - rev_manager.initialize::(None).await.unwrap(); - Self { - user_id, - object_id, - configuration, - rev_manager: Arc::new(rev_manager), - } + pub async fn new_with_configuration(max_merge_len: i64) -> Self { + let user_id = nanoid!(10); + let object_id = nanoid!(6); + let configuration = RevisionPersistenceConfiguration::new(max_merge_len as usize, false); + let disk_cache = RevisionDiskCacheMock::new(vec![]); + let persistence = + RevisionPersistence::new(&user_id, &object_id, disk_cache, configuration.clone()); + let compress = RevisionMergeableMock {}; + let snapshot = RevisionSnapshotMock {}; + let mut rev_manager = + RevisionManager::new(&user_id, &object_id, persistence, compress, snapshot); + rev_manager + .initialize::(None) + .await + .unwrap(); + Self { + user_id, + object_id, + configuration, + rev_manager: Arc::new(rev_manager), } + } - pub async fn new_with_other(old_test: RevisionTest) -> Self { - let records = old_test.rev_manager.get_all_revision_records().unwrap(); - let disk_cache = RevisionDiskCacheMock::new(records); - let configuration = old_test.configuration; - let persistence = RevisionPersistence::new( - &old_test.user_id, - &old_test.object_id, - disk_cache, - configuration.clone(), - ); + pub async fn new_with_other(old_test: RevisionTest) -> Self { + let records = old_test.rev_manager.get_all_revision_records().unwrap(); + let disk_cache = RevisionDiskCacheMock::new(records); + let configuration = old_test.configuration; + let persistence = RevisionPersistence::new( + &old_test.user_id, + &old_test.object_id, + disk_cache, + configuration.clone(), + ); - let compress = RevisionMergeableMock {}; - let snapshot = RevisionSnapshotMock {}; - let mut rev_manager = - RevisionManager::new(&old_test.user_id, &old_test.object_id, persistence, compress, snapshot); - rev_manager.initialize::(None).await.unwrap(); - Self { - user_id: old_test.user_id, - object_id: old_test.object_id, - configuration, - rev_manager: Arc::new(rev_manager), - } + let compress = RevisionMergeableMock {}; + let snapshot = RevisionSnapshotMock {}; + let mut rev_manager = RevisionManager::new( + &old_test.user_id, + &old_test.object_id, + persistence, + compress, + snapshot, + ); + rev_manager + .initialize::(None) + .await + .unwrap(); + Self { + user_id: old_test.user_id, + object_id: old_test.object_id, + configuration, + rev_manager: Arc::new(rev_manager), } - pub async fn run_scripts(&self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } + } + pub async fn run_scripts(&self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; } + } - pub async fn run_script(&self, script: RevisionScript) { - match script { - RevisionScript::AddLocalRevision { content } => { - let object = RevisionObjectMock::new(&content); - let bytes = object.to_bytes(); - let md5 = md5(&bytes); - self.rev_manager - .add_local_revision(Bytes::from(bytes), md5) - .await - .unwrap(); - } - RevisionScript::AddLocalRevision2 { content } => { - let object = RevisionObjectMock::new(&content); - let bytes = object.to_bytes(); - let md5 = md5(&bytes); - self.rev_manager - .add_local_revision(Bytes::from(bytes), md5) - .await - .unwrap(); - } - RevisionScript::AddInvalidLocalRevision { bytes } => { - let md5 = md5(&bytes); - self.rev_manager - .add_local_revision(Bytes::from(bytes), md5) - .await - .unwrap(); - } - RevisionScript::AckRevision { rev_id } => { - // - self.rev_manager.ack_revision(rev_id).await.unwrap() - } - RevisionScript::AssertNextSyncRevisionId { rev_id } => { - assert_eq!(self.rev_manager.next_sync_rev_id().await, rev_id) - } - RevisionScript::AssertNumberOfSyncRevisions { num } => { - assert_eq!(self.rev_manager.number_of_sync_revisions(), num) - } - RevisionScript::AssertNumberOfRevisionsInDisk { num } => { - assert_eq!(self.rev_manager.number_of_revisions_in_disk(), num) - } - RevisionScript::AssertNextSyncRevisionContent { expected } => { - // - let rev_id = self.rev_manager.next_sync_rev_id().await.unwrap(); - let revision = self.rev_manager.get_revision(rev_id).await.unwrap(); - let object = RevisionObjectMock::from_bytes(&revision.bytes).unwrap(); - assert_eq!(object.content, expected); - } - RevisionScript::WaitWhenWriteToDisk => { - let milliseconds = 2 * REVISION_WRITE_INTERVAL_IN_MILLIS; - tokio::time::sleep(Duration::from_millis(milliseconds)).await; - } - } + pub async fn run_script(&self, script: RevisionScript) { + match script { + RevisionScript::AddLocalRevision { content } => { + let object = RevisionObjectMock::new(&content); + let bytes = object.to_bytes(); + let md5 = md5(&bytes); + self + .rev_manager + .add_local_revision(Bytes::from(bytes), md5) + .await + .unwrap(); + }, + RevisionScript::AddLocalRevision2 { content } => { + let object = RevisionObjectMock::new(&content); + let bytes = object.to_bytes(); + let md5 = md5(&bytes); + self + .rev_manager + .add_local_revision(Bytes::from(bytes), md5) + .await + .unwrap(); + }, + RevisionScript::AddInvalidLocalRevision { bytes } => { + let md5 = md5(&bytes); + self + .rev_manager + .add_local_revision(Bytes::from(bytes), md5) + .await + .unwrap(); + }, + RevisionScript::AckRevision { rev_id } => { + // + self.rev_manager.ack_revision(rev_id).await.unwrap() + }, + RevisionScript::AssertNextSyncRevisionId { rev_id } => { + assert_eq!(self.rev_manager.next_sync_rev_id().await, rev_id) + }, + RevisionScript::AssertNumberOfSyncRevisions { num } => { + assert_eq!(self.rev_manager.number_of_sync_revisions(), num) + }, + RevisionScript::AssertNumberOfRevisionsInDisk { num } => { + assert_eq!(self.rev_manager.number_of_revisions_in_disk(), num) + }, + RevisionScript::AssertNextSyncRevisionContent { expected } => { + // + let rev_id = self.rev_manager.next_sync_rev_id().await.unwrap(); + let revision = self.rev_manager.get_revision(rev_id).await.unwrap(); + let object = RevisionObjectMock::from_bytes(&revision.bytes).unwrap(); + assert_eq!(object.content, expected); + }, + RevisionScript::WaitWhenWriteToDisk => { + let milliseconds = 2 * REVISION_WRITE_INTERVAL_IN_MILLIS; + tokio::time::sleep(Duration::from_millis(milliseconds)).await; + }, } + } } pub struct RevisionDiskCacheMock { - records: RwLock>, + records: RwLock>, } impl RevisionDiskCacheMock { - pub fn new(records: Vec) -> Self { - Self { - records: RwLock::new(records), - } + pub fn new(records: Vec) -> Self { + Self { + records: RwLock::new(records), } + } } impl RevisionDiskCache for RevisionDiskCacheMock { - type Error = FlowyError; + type Error = FlowyError; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - self.records.write().extend(revision_records); - Ok(()) + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + self.records.write().extend(revision_records); + Ok(()) + } + + fn get_connection(&self) -> Result { + todo!() + } + + fn read_revision_records( + &self, + _object_id: &str, + rev_ids: Option>, + ) -> Result, Self::Error> { + match rev_ids { + None => Ok(self.records.read().clone()), + Some(rev_ids) => Ok( + self + .records + .read() + .iter() + .filter(|record| rev_ids.contains(&record.revision.rev_id)) + .cloned() + .collect::>(), + ), } + } - fn get_connection(&self) -> Result { - todo!() + fn read_revision_records_with_range( + &self, + _object_id: &str, + range: &RevisionRange, + ) -> Result, Self::Error> { + let read_guard = self.records.read(); + let records = range + .iter() + .flat_map(|rev_id| { + read_guard + .iter() + .find(|record| record.revision.rev_id == rev_id) + .cloned() + }) + .collect::>(); + Ok(records) + } + + fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { + for changeset in changesets { + if let Some(record) = self + .records + .write() + .iter_mut() + .find(|record| record.revision.rev_id == changeset.rev_id) + { + record.state = changeset.state; + } } + Ok(()) + } - fn read_revision_records( - &self, - _object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - match rev_ids { - None => Ok(self.records.read().clone()), - Some(rev_ids) => Ok(self - .records - .read() - .iter() - .filter(|record| rev_ids.contains(&record.revision.rev_id)) - .cloned() - .collect::>()), - } - } - - fn read_revision_records_with_range( - &self, - _object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - let read_guard = self.records.read(); - let records = range + fn delete_revision_records( + &self, + _object_id: &str, + rev_ids: Option>, + ) -> Result<(), Self::Error> { + match rev_ids { + None => {}, + Some(rev_ids) => { + for rev_id in rev_ids { + if let Some(index) = self + .records + .read() .iter() - .flat_map(|rev_id| { - read_guard - .iter() - .find(|record| record.revision.rev_id == rev_id) - .cloned() - }) - .collect::>(); - Ok(records) - } - - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - for changeset in changesets { - if let Some(record) = self - .records - .write() - .iter_mut() - .find(|record| record.revision.rev_id == changeset.rev_id) - { - record.state = changeset.state; - } + .position(|record| record.revision.rev_id == rev_id) + { + self.records.write().remove(index); + } } - Ok(()) + }, } + Ok(()) + } - fn delete_revision_records(&self, _object_id: &str, rev_ids: Option>) -> Result<(), Self::Error> { - match rev_ids { - None => {} - Some(rev_ids) => { - for rev_id in rev_ids { - if let Some(index) = self - .records - .read() - .iter() - .position(|record| record.revision.rev_id == rev_id) - { - self.records.write().remove(index); - } - } - } - } - Ok(()) - } - - fn delete_and_insert_records( - &self, - _object_id: &str, - _deleted_rev_ids: Option>, - _inserted_records: Vec, - ) -> Result<(), Self::Error> { - todo!() - } + fn delete_and_insert_records( + &self, + _object_id: &str, + _deleted_rev_ids: Option>, + _inserted_records: Vec, + ) -> Result<(), Self::Error> { + todo!() + } } pub struct RevisionConnectionMock {} pub struct RevisionSnapshotMock {} impl RevisionSnapshotPersistence for RevisionSnapshotMock { - fn write_snapshot(&self, _rev_id: i64, _data: Vec) -> FlowyResult<()> { - Ok(()) - } + fn write_snapshot(&self, _rev_id: i64, _data: Vec) -> FlowyResult<()> { + Ok(()) + } - fn read_snapshot(&self, _rev_id: i64) -> FlowyResult> { - Ok(None) - } + fn read_snapshot(&self, _rev_id: i64) -> FlowyResult> { + Ok(None) + } - fn read_last_snapshot(&self) -> FlowyResult> { - Ok(None) - } + fn read_last_snapshot(&self) -> FlowyResult> { + Ok(None) + } } pub struct RevisionMergeableMock {} impl RevisionMergeable for RevisionMergeableMock { - fn combine_revisions(&self, revisions: Vec) -> FlowyResult { - let mut object = RevisionObjectMock::new(""); - for revision in revisions { - if let Ok(other) = RevisionObjectMock::from_bytes(&revision.bytes) { - object.compose(other)?; - } - } - Ok(Bytes::from(object.to_bytes())) + fn combine_revisions(&self, revisions: Vec) -> FlowyResult { + let mut object = RevisionObjectMock::new(""); + for revision in revisions { + if let Ok(other) = RevisionObjectMock::from_bytes(&revision.bytes) { + object.compose(other)?; + } } + Ok(Bytes::from(object.to_bytes())) + } } #[derive(Serialize, Deserialize)] pub struct InvalidRevisionObject { - data: String, + data: String, } impl InvalidRevisionObject { - pub fn new() -> Self { - InvalidRevisionObject { data: "".to_string() } - } - pub(crate) fn to_bytes(&self) -> Vec { - serde_json::to_vec(self).unwrap() + pub fn new() -> Self { + InvalidRevisionObject { + data: "".to_string(), } + } + pub(crate) fn to_bytes(&self) -> Vec { + serde_json::to_vec(self).unwrap() + } - // fn from_bytes(bytes: &[u8]) -> Self { - // serde_json::from_slice(bytes).unwrap() - // } + // fn from_bytes(bytes: &[u8]) -> Self { + // serde_json::from_slice(bytes).unwrap() + // } } #[derive(Serialize, Deserialize)] pub struct RevisionObjectMock { - content: String, + content: String, } impl RevisionObjectMock { - pub fn new(s: &str) -> Self { - Self { content: s.to_owned() } + pub fn new(s: &str) -> Self { + Self { + content: s.to_owned(), } + } - pub fn compose(&mut self, other: RevisionObjectMock) -> FlowyResult<()> { - self.content.push_str(other.content.as_str()); - Ok(()) - } + pub fn compose(&mut self, other: RevisionObjectMock) -> FlowyResult<()> { + self.content.push_str(other.content.as_str()); + Ok(()) + } - pub fn to_bytes(&self) -> Vec { - serde_json::to_vec(self).unwrap() - } + pub fn to_bytes(&self) -> Vec { + serde_json::to_vec(self).unwrap() + } - pub fn from_bytes(bytes: &[u8]) -> FlowyResult { - serde_json::from_slice(bytes).map_err(internal_error) - } + pub fn from_bytes(bytes: &[u8]) -> FlowyResult { + serde_json::from_slice(bytes).map_err(internal_error) + } } pub struct RevisionObjectMockSerde(); impl RevisionObjectDeserializer for RevisionObjectMockSerde { - type Output = RevisionObjectMock; + type Output = RevisionObjectMock; - fn deserialize_revisions(_object_id: &str, revisions: Vec) -> FlowyResult { - let mut object = RevisionObjectMock::new(""); - if revisions.is_empty() { - return Ok(object); - } - - for revision in revisions { - if let Ok(revision_object) = RevisionObjectMock::from_bytes(&revision.bytes) { - object.compose(revision_object)?; - } - } - - Ok(object) + fn deserialize_revisions( + _object_id: &str, + revisions: Vec, + ) -> FlowyResult { + let mut object = RevisionObjectMock::new(""); + if revisions.is_empty() { + return Ok(object); } - fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { - None + for revision in revisions { + if let Ok(revision_object) = RevisionObjectMock::from_bytes(&revision.bytes) { + object.compose(revision_object)?; + } } + + Ok(object) + } + + fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { + None + } } diff --git a/frontend/rust-lib/flowy-sqlite/src/kv/kv.rs b/frontend/rust-lib/flowy-sqlite/src/kv/kv.rs index 7c48f51d01..402d9db929 100644 --- a/frontend/rust-lib/flowy-sqlite/src/kv/kv.rs +++ b/frontend/rust-lib/flowy-sqlite/src/kv/kv.rs @@ -6,182 +6,182 @@ use lazy_static::lazy_static; use std::{path::Path, sync::RwLock}; macro_rules! impl_get_func { - ( + ( $func_name:ident, $get_method:ident=>$target:ident ) => { - #[allow(dead_code)] - pub fn $func_name(k: &str) -> Option<$target> { - match KV::get(k) { - Ok(item) => item.$get_method, - Err(_) => None, - } - } - }; + #[allow(dead_code)] + pub fn $func_name(k: &str) -> Option<$target> { + match KV::get(k) { + Ok(item) => item.$get_method, + Err(_) => None, + } + } + }; } macro_rules! impl_set_func { - ($func_name:ident,$set_method:ident,$key_type:ident) => { - #[allow(dead_code)] - pub fn $func_name(key: &str, value: $key_type) { - let mut item = KeyValue::new(key); - item.$set_method = Some(value); - match KV::set(item) { - Ok(_) => {} - Err(e) => { - tracing::error!("{:?}", e) - } - }; - } - }; + ($func_name:ident,$set_method:ident,$key_type:ident) => { + #[allow(dead_code)] + pub fn $func_name(key: &str, value: $key_type) { + let mut item = KeyValue::new(key); + item.$set_method = Some(value); + match KV::set(item) { + Ok(_) => {}, + Err(e) => { + tracing::error!("{:?}", e) + }, + }; + } + }; } const DB_NAME: &str = "kv.db"; lazy_static! { - static ref KV_HOLDER: RwLock = RwLock::new(KV::new()); + static ref KV_HOLDER: RwLock = RwLock::new(KV::new()); } pub struct KV { - database: Option, + database: Option, } impl KV { - fn new() -> Self { - KV { database: None } + fn new() -> Self { + KV { database: None } + } + + fn set(value: KeyValue) -> Result<(), String> { + // tracing::trace!("[KV]: set value: {:?}", value); + let _ = diesel::replace_into(kv_table::table) + .values(&value) + .execute(&*(get_connection()?)) + .map_err(|e| format!("KV set error: {:?}", e))?; + + Ok(()) + } + + fn get(key: &str) -> Result { + let conn = get_connection()?; + let value = dsl::kv_table + .filter(kv_table::key.eq(key)) + .first::(&*conn) + .map_err(|e| format!("KV get error: {:?}", e))?; + + Ok(value) + } + + #[allow(dead_code)] + pub fn remove(key: &str) -> Result<(), String> { + // tracing::debug!("remove key: {}", key); + let conn = get_connection()?; + let sql = dsl::kv_table.filter(kv_table::key.eq(key)); + let _ = diesel::delete(sql) + .execute(&*conn) + .map_err(|e| format!("KV remove error: {:?}", e))?; + Ok(()) + } + + #[tracing::instrument(level = "trace", err)] + pub fn init(root: &str) -> Result<(), String> { + if !Path::new(root).exists() { + return Err(format!("Init KVStore failed. {} not exists", root)); } - fn set(value: KeyValue) -> Result<(), String> { - // tracing::trace!("[KV]: set value: {:?}", value); - let _ = diesel::replace_into(kv_table::table) - .values(&value) - .execute(&*(get_connection()?)) - .map_err(|e| format!("KV set error: {:?}", e))?; + let pool_config = PoolConfig::default(); + let database = Database::new(root, DB_NAME, pool_config).unwrap(); + let conn = database.get_connection().unwrap(); + SqliteConnection::execute(&*conn, KV_SQL).unwrap(); - Ok(()) + let mut store = KV_HOLDER + .write() + .map_err(|e| format!("KVStore write failed: {:?}", e))?; + tracing::trace!("Init kv with path: {}", root); + store.database = Some(database); + + Ok(()) + } + + pub fn get_bool(key: &str) -> bool { + match KV::get(key) { + Ok(item) => item.bool_value.unwrap_or(false), + Err(_) => false, } + } - fn get(key: &str) -> Result { - let conn = get_connection()?; - let value = dsl::kv_table - .filter(kv_table::key.eq(key)) - .first::(&*conn) - .map_err(|e| format!("KV get error: {:?}", e))?; + impl_set_func!(set_str, str_value, String); - Ok(value) - } + impl_set_func!(set_bool, bool_value, bool); - #[allow(dead_code)] - pub fn remove(key: &str) -> Result<(), String> { - // tracing::debug!("remove key: {}", key); - let conn = get_connection()?; - let sql = dsl::kv_table.filter(kv_table::key.eq(key)); - let _ = diesel::delete(sql) - .execute(&*conn) - .map_err(|e| format!("KV remove error: {:?}", e))?; - Ok(()) - } + impl_set_func!(set_int, int_value, i64); - #[tracing::instrument(level = "trace", err)] - pub fn init(root: &str) -> Result<(), String> { - if !Path::new(root).exists() { - return Err(format!("Init KVStore failed. {} not exists", root)); - } + impl_set_func!(set_float, float_value, f64); - let pool_config = PoolConfig::default(); - let database = Database::new(root, DB_NAME, pool_config).unwrap(); - let conn = database.get_connection().unwrap(); - SqliteConnection::execute(&*conn, KV_SQL).unwrap(); + impl_get_func!(get_str,str_value=>String); - let mut store = KV_HOLDER - .write() - .map_err(|e| format!("KVStore write failed: {:?}", e))?; - tracing::trace!("Init kv with path: {}", root); - store.database = Some(database); + impl_get_func!(get_int,int_value=>i64); - Ok(()) - } - - pub fn get_bool(key: &str) -> bool { - match KV::get(key) { - Ok(item) => item.bool_value.unwrap_or(false), - Err(_) => false, - } - } - - impl_set_func!(set_str, str_value, String); - - impl_set_func!(set_bool, bool_value, bool); - - impl_set_func!(set_int, int_value, i64); - - impl_set_func!(set_float, float_value, f64); - - impl_get_func!(get_str,str_value=>String); - - impl_get_func!(get_int,int_value=>i64); - - impl_get_func!(get_float,float_value=>f64); + impl_get_func!(get_float,float_value=>f64); } fn get_connection() -> Result { - match KV_HOLDER.read() { - Ok(store) => { - let conn = store - .database - .as_ref() - .expect("KVStore is not init") - .get_connection() - .map_err(|e| format!("KVStore error: {:?}", e))?; - Ok(conn) - } - Err(e) => { - let msg = format!("KVStore get connection failed: {:?}", e); - tracing::error!("{:?}", msg); - Err(msg) - } - } + match KV_HOLDER.read() { + Ok(store) => { + let conn = store + .database + .as_ref() + .expect("KVStore is not init") + .get_connection() + .map_err(|e| format!("KVStore error: {:?}", e))?; + Ok(conn) + }, + Err(e) => { + let msg = format!("KVStore get connection failed: {:?}", e); + tracing::error!("{:?}", msg); + Err(msg) + }, + } } #[derive(Clone, Debug, Default, Queryable, Identifiable, Insertable, AsChangeset)] #[table_name = "kv_table"] #[primary_key(key)] pub struct KeyValue { - pub key: String, - pub str_value: Option, - pub int_value: Option, - pub float_value: Option, - pub bool_value: Option, + pub key: String, + pub str_value: Option, + pub int_value: Option, + pub float_value: Option, + pub bool_value: Option, } impl KeyValue { - pub fn new(key: &str) -> Self { - KeyValue { - key: key.to_string(), - ..Default::default() - } + pub fn new(key: &str) -> Self { + KeyValue { + key: key.to_string(), + ..Default::default() } + } } #[cfg(test)] mod tests { - use crate::kv::KV; + use crate::kv::KV; - #[test] - fn kv_store_test() { - let dir = "./temp/"; - if !std::path::Path::new(dir).exists() { - std::fs::create_dir_all(dir).unwrap(); - } - - KV::init(dir).unwrap(); - - KV::set_str("1", "hello".to_string()); - assert_eq!(KV::get_str("1").unwrap(), "hello"); - - assert_eq!(KV::get_str("2"), None); - - KV::set_bool("1", true); - assert!(KV::get_bool("1")); - - assert!(!KV::get_bool("2")); + #[test] + fn kv_store_test() { + let dir = "./temp/"; + if !std::path::Path::new(dir).exists() { + std::fs::create_dir_all(dir).unwrap(); } + + KV::init(dir).unwrap(); + + KV::set_str("1", "hello".to_string()); + assert_eq!(KV::get_str("1").unwrap(), "hello"); + + assert_eq!(KV::get_str("2"), None); + + KV::set_bool("1", true); + assert!(KV::get_bool("1")); + + assert!(!KV::get_bool("2")); + } } diff --git a/frontend/rust-lib/flowy-sqlite/src/lib.rs b/frontend/rust-lib/flowy-sqlite/src/lib.rs index 62fd3d57b1..2d24006f99 100644 --- a/frontend/rust-lib/flowy-sqlite/src/lib.rs +++ b/frontend/rust-lib/flowy-sqlite/src/lib.rs @@ -22,34 +22,34 @@ extern crate diesel_migrations; pub type Error = diesel::result::Error; pub mod prelude { - pub use super::UserDatabaseConnection; - pub use crate::*; - pub use diesel::SqliteConnection; - pub use diesel::{query_dsl::*, BelongingToDsl, ExpressionMethods, RunQueryDsl}; + pub use super::UserDatabaseConnection; + pub use crate::*; + pub use diesel::SqliteConnection; + pub use diesel::{query_dsl::*, BelongingToDsl, ExpressionMethods, RunQueryDsl}; } embed_migrations!("../flowy-sqlite/migrations/"); pub const DB_NAME: &str = "flowy-database.db"; pub fn init(storage_path: &str) -> Result { - if !Path::new(storage_path).exists() { - std::fs::create_dir_all(storage_path)?; - } - let pool_config = PoolConfig::default(); - let database = Database::new(storage_path, DB_NAME, pool_config).map_err(as_io_error)?; - let conn = database.get_connection().map_err(as_io_error)?; - embedded_migrations::run(&*conn).map_err(as_io_error)?; - Ok(database) + if !Path::new(storage_path).exists() { + std::fs::create_dir_all(storage_path)?; + } + let pool_config = PoolConfig::default(); + let database = Database::new(storage_path, DB_NAME, pool_config).map_err(as_io_error)?; + let conn = database.get_connection().map_err(as_io_error)?; + embedded_migrations::run(&*conn).map_err(as_io_error)?; + Ok(database) } fn as_io_error(e: E) -> io::Error where - E: Into + Debug, + E: Into + Debug, { - let msg = format!("{:?}", e); - io::Error::new(io::ErrorKind::NotConnected, msg) + let msg = format!("{:?}", e); + io::Error::new(io::ErrorKind::NotConnected, msg) } pub trait UserDatabaseConnection: Send + Sync { - fn get_connection(&self) -> Result; + fn get_connection(&self) -> Result; } diff --git a/frontend/rust-lib/flowy-sqlite/src/macros.rs b/frontend/rust-lib/flowy-sqlite/src/macros.rs index bee1958a9b..ad6d3b377a 100644 --- a/frontend/rust-lib/flowy-sqlite/src/macros.rs +++ b/frontend/rust-lib/flowy-sqlite/src/macros.rs @@ -48,166 +48,173 @@ macro_rules! diesel_insert_table { #[macro_export] macro_rules! diesel_record_count { - ( + ( $table_name:ident, $id:expr, $connection:expr ) => { - $table_name::dsl::$table_name - .filter($table_name::dsl::id.eq($id.clone())) - .count() - .get_result($connection) - .unwrap_or(0); - }; + $table_name::dsl::$table_name + .filter($table_name::dsl::id.eq($id.clone())) + .count() + .get_result($connection) + .unwrap_or(0); + }; } #[macro_export] macro_rules! diesel_revision_record_count { - ( + ( $table_name:expr, $filter:expr, $connection:expr ) => { - $table_name - .filter($table_name::dsl::id.eq($id)) - .count() - .get_result($connection) - .unwrap_or(0); - }; + $table_name + .filter($table_name::dsl::id.eq($id)) + .count() + .get_result($connection) + .unwrap_or(0); + }; } #[macro_export] macro_rules! diesel_update_table { - ( + ( $table_name:ident, $changeset:expr, $connection:expr ) => {{ - let filter = $table_name::dsl::$table_name.filter($table_name::dsl::id.eq($changeset.id.clone())); - let affected_row = diesel::update(filter).set($changeset).execute($connection)?; - debug_assert_eq!(affected_row, 1); - }}; + let filter = + $table_name::dsl::$table_name.filter($table_name::dsl::id.eq($changeset.id.clone())); + let affected_row = diesel::update(filter) + .set($changeset) + .execute($connection)?; + debug_assert_eq!(affected_row, 1); + }}; } #[macro_export] macro_rules! diesel_delete_table { - ( + ( $table_name:ident, $id:ident, $connection:ident ) => { - let filter = $table_name::dsl::$table_name.filter($table_name::dsl::id.eq($id)); - let affected_row = diesel::delete(filter).execute(&*$connection)?; - debug_assert_eq!(affected_row, 1); - }; + let filter = $table_name::dsl::$table_name.filter($table_name::dsl::id.eq($id)); + let affected_row = diesel::delete(filter).execute(&*$connection)?; + debug_assert_eq!(affected_row, 1); + }; } #[macro_export] macro_rules! impl_sql_binary_expression { - ($target:ident) => { - impl diesel::serialize::ToSql for $target { - fn to_sql( - &self, - out: &mut diesel::serialize::Output, - ) -> diesel::serialize::Result { - let bytes: Vec = self.try_into().map_err(|e| format!("{:?}", e))?; - diesel::serialize::ToSql::::to_sql(&bytes, out) - } - } - // https://docs.diesel.rs/src/diesel/sqlite/types/mod.rs.html#30-33 - // impl FromSql for *const [u8] { - // fn from_sql(bytes: Option<&SqliteValue>) -> deserialize::Result { - // let bytes = not_none!(bytes).read_blob(); - // Ok(bytes as *const _) - // } - // } - impl diesel::deserialize::FromSql for $target - where - DB: diesel::backend::Backend, - *const [u8]: diesel::deserialize::FromSql, - { - fn from_sql(bytes: Option<&DB::RawValue>) -> diesel::deserialize::Result { - let slice_ptr = - <*const [u8] as diesel::deserialize::FromSql>::from_sql(bytes)?; - let bytes = unsafe { &*slice_ptr }; + ($target:ident) => { + impl diesel::serialize::ToSql for $target { + fn to_sql( + &self, + out: &mut diesel::serialize::Output, + ) -> diesel::serialize::Result { + let bytes: Vec = self.try_into().map_err(|e| format!("{:?}", e))?; + diesel::serialize::ToSql::::to_sql( + &bytes, out, + ) + } + } + // https://docs.diesel.rs/src/diesel/sqlite/types/mod.rs.html#30-33 + // impl FromSql for *const [u8] { + // fn from_sql(bytes: Option<&SqliteValue>) -> deserialize::Result { + // let bytes = not_none!(bytes).read_blob(); + // Ok(bytes as *const _) + // } + // } + impl diesel::deserialize::FromSql for $target + where + DB: diesel::backend::Backend, + *const [u8]: diesel::deserialize::FromSql, + { + fn from_sql(bytes: Option<&DB::RawValue>) -> diesel::deserialize::Result { + let slice_ptr = <*const [u8] as diesel::deserialize::FromSql< + diesel::sql_types::Binary, + DB, + >>::from_sql(bytes)?; + let bytes = unsafe { &*slice_ptr }; - match $target::try_from(bytes) { - Ok(object) => Ok(object), - Err(e) => { - log::error!( - "{:?} deserialize from bytes fail. {:?}", - std::any::type_name::<$target>(), - e - ); - panic!(); - } - } - } + match $target::try_from(bytes) { + Ok(object) => Ok(object), + Err(e) => { + log::error!( + "{:?} deserialize from bytes fail. {:?}", + std::any::type_name::<$target>(), + e + ); + panic!(); + }, } - }; + } + } + }; } #[macro_export] macro_rules! impl_sql_integer_expression { - ($target:ident) => { - impl diesel::serialize::ToSql for $target - where - DB: diesel::backend::Backend, - i32: diesel::serialize::ToSql, - { - fn to_sql( - &self, - out: &mut diesel::serialize::Output, - ) -> diesel::serialize::Result { - (*self as i32).to_sql(out) - } - } + ($target:ident) => { + impl diesel::serialize::ToSql for $target + where + DB: diesel::backend::Backend, + i32: diesel::serialize::ToSql, + { + fn to_sql( + &self, + out: &mut diesel::serialize::Output, + ) -> diesel::serialize::Result { + (*self as i32).to_sql(out) + } + } - impl diesel::deserialize::FromSql for $target - where - DB: diesel::backend::Backend, - i32: diesel::deserialize::FromSql, - { - fn from_sql(bytes: Option<&DB::RawValue>) -> diesel::deserialize::Result { - let smaill_int = i32::from_sql(bytes)?; - Ok($target::from(smaill_int)) - } - } - }; + impl diesel::deserialize::FromSql for $target + where + DB: diesel::backend::Backend, + i32: diesel::deserialize::FromSql, + { + fn from_sql(bytes: Option<&DB::RawValue>) -> diesel::deserialize::Result { + let smaill_int = i32::from_sql(bytes)?; + Ok($target::from(smaill_int)) + } + } + }; } #[macro_export] macro_rules! impl_rev_state_map { - ($target:ident) => { - impl std::convert::From for $target { - fn from(value: i32) -> Self { - match value { - 0 => $target::Sync, - 1 => $target::Ack, - o => { - tracing::error!("Unsupported rev state {}, fallback to RevState::Local", o); - $target::Sync - } - } - } + ($target:ident) => { + impl std::convert::From for $target { + fn from(value: i32) -> Self { + match value { + 0 => $target::Sync, + 1 => $target::Ack, + o => { + tracing::error!("Unsupported rev state {}, fallback to RevState::Local", o); + $target::Sync + }, } + } + } - impl std::convert::From<$target> for RevisionState { - fn from(s: $target) -> Self { - match s { - $target::Sync => RevisionState::Sync, - $target::Ack => RevisionState::Ack, - } - } + impl std::convert::From<$target> for RevisionState { + fn from(s: $target) -> Self { + match s { + $target::Sync => RevisionState::Sync, + $target::Ack => RevisionState::Ack, } + } + } - impl std::convert::From for $target { - fn from(s: RevisionState) -> Self { - match s { - RevisionState::Sync => $target::Sync, - RevisionState::Ack => $target::Ack, - } - } + impl std::convert::From for $target { + fn from(s: RevisionState) -> Self { + match s { + RevisionState::Sync => $target::Sync, + RevisionState::Ack => $target::Ack, } - }; + } + } + }; } diff --git a/frontend/rust-lib/flowy-sqlite/src/schema.rs b/frontend/rust-lib/flowy-sqlite/src/schema.rs index 148dd2556a..9ab84552b2 100644 --- a/frontend/rust-lib/flowy-sqlite/src/schema.rs +++ b/frontend/rust-lib/flowy-sqlite/src/schema.rs @@ -178,20 +178,20 @@ diesel::table! { } diesel::allow_tables_to_appear_in_same_query!( - app_table, - document_rev_snapshot, - document_rev_table, - folder_rev_snapshot, - grid_block_index_table, - grid_meta_rev_table, - grid_rev_snapshot, - grid_rev_table, - grid_view_rev_table, - kv_table, - rev_snapshot, - rev_table, - trash_table, - user_table, - view_table, - workspace_table, + app_table, + document_rev_snapshot, + document_rev_table, + folder_rev_snapshot, + grid_block_index_table, + grid_meta_rev_table, + grid_rev_snapshot, + grid_rev_table, + grid_view_rev_table, + kv_table, + rev_snapshot, + rev_table, + trash_table, + user_table, + view_table, + workspace_table, ); diff --git a/frontend/rust-lib/flowy-sqlite/src/sqlite/conn_ext.rs b/frontend/rust-lib/flowy-sqlite/src/sqlite/conn_ext.rs index e784c4dcbe..31bfd109a1 100644 --- a/frontend/rust-lib/flowy-sqlite/src/sqlite/conn_ext.rs +++ b/frontend/rust-lib/flowy-sqlite/src/sqlite/conn_ext.rs @@ -1,23 +1,25 @@ use crate::sqlite::errors::*; -use diesel::{dsl::sql, expression::SqlLiteral, query_dsl::LoadQuery, Connection, RunQueryDsl, SqliteConnection}; +use diesel::{ + dsl::sql, expression::SqlLiteral, query_dsl::LoadQuery, Connection, RunQueryDsl, SqliteConnection, +}; pub trait ConnectionExtension: Connection { - fn query(&self, query: &str) -> Result - where - SqlLiteral: LoadQuery; + fn query(&self, query: &str) -> Result + where + SqlLiteral: LoadQuery; - fn exec(&self, query: impl AsRef) -> Result; + fn exec(&self, query: impl AsRef) -> Result; } impl ConnectionExtension for SqliteConnection { - fn query(&self, query: &str) -> Result - where - SqlLiteral: LoadQuery, - { - Ok(sql::(query).get_result(self)?) - } + fn query(&self, query: &str) -> Result + where + SqlLiteral: LoadQuery, + { + Ok(sql::(query).get_result(self)?) + } - fn exec(&self, query: impl AsRef) -> Result { - Ok(SqliteConnection::execute(self, query.as_ref())?) - } + fn exec(&self, query: impl AsRef) -> Result { + Ok(SqliteConnection::execute(self, query.as_ref())?) + } } diff --git a/frontend/rust-lib/flowy-sqlite/src/sqlite/database.rs b/frontend/rust-lib/flowy-sqlite/src/sqlite/database.rs index 160bc887cc..a78e8a48b5 100644 --- a/frontend/rust-lib/flowy-sqlite/src/sqlite/database.rs +++ b/frontend/rust-lib/flowy-sqlite/src/sqlite/database.rs @@ -1,53 +1,53 @@ use crate::sqlite::{ - errors::*, - pool::{ConnectionManager, ConnectionPool, PoolConfig}, + errors::*, + pool::{ConnectionManager, ConnectionPool, PoolConfig}, }; use r2d2::PooledConnection; use std::sync::Arc; pub struct Database { - uri: String, - pool: Arc, + uri: String, + pool: Arc, } pub type DBConnection = PooledConnection; impl Database { - pub fn new(dir: &str, name: &str, pool_config: PoolConfig) -> Result { - let uri = db_file_uri(dir, name); + pub fn new(dir: &str, name: &str, pool_config: PoolConfig) -> Result { + let uri = db_file_uri(dir, name); - if !std::path::PathBuf::from(dir).exists() { - tracing::error!("Create database failed. {} not exists", &dir); - } - - let pool = ConnectionPool::new(pool_config, &uri)?; - Ok(Self { - uri, - pool: Arc::new(pool), - }) + if !std::path::PathBuf::from(dir).exists() { + tracing::error!("Create database failed. {} not exists", &dir); } - pub fn get_uri(&self) -> &str { - &self.uri - } + let pool = ConnectionPool::new(pool_config, &uri)?; + Ok(Self { + uri, + pool: Arc::new(pool), + }) + } - pub fn get_connection(&self) -> Result { - let conn = self.pool.get()?; - Ok(conn) - } + pub fn get_uri(&self) -> &str { + &self.uri + } - pub fn get_pool(&self) -> Arc { - self.pool.clone() - } + pub fn get_connection(&self) -> Result { + let conn = self.pool.get()?; + Ok(conn) + } + + pub fn get_pool(&self) -> Arc { + self.pool.clone() + } } pub fn db_file_uri(dir: &str, name: &str) -> String { - use std::path::MAIN_SEPARATOR; + use std::path::MAIN_SEPARATOR; - let mut uri = dir.to_owned(); - if !uri.ends_with(MAIN_SEPARATOR) { - uri.push(MAIN_SEPARATOR); - } - uri.push_str(name); - uri + let mut uri = dir.to_owned(); + if !uri.ends_with(MAIN_SEPARATOR) { + uri.push(MAIN_SEPARATOR); + } + uri.push_str(name); + uri } diff --git a/frontend/rust-lib/flowy-sqlite/src/sqlite/errors.rs b/frontend/rust-lib/flowy-sqlite/src/sqlite/errors.rs index 3a678aa356..031fe8b303 100644 --- a/frontend/rust-lib/flowy-sqlite/src/sqlite/errors.rs +++ b/frontend/rust-lib/flowy-sqlite/src/sqlite/errors.rs @@ -1,5 +1,6 @@ use error_chain::{ - error_chain, error_chain_processing, impl_error_chain_kind, impl_error_chain_processed, impl_extract_backtrace, + error_chain, error_chain_processing, impl_error_chain_kind, impl_error_chain_processed, + impl_extract_backtrace, }; error_chain! { diff --git a/frontend/rust-lib/flowy-sqlite/src/sqlite/pool.rs b/frontend/rust-lib/flowy-sqlite/src/sqlite/pool.rs index 50dbcc4bc9..8ab53ac979 100644 --- a/frontend/rust-lib/flowy-sqlite/src/sqlite/pool.rs +++ b/frontend/rust-lib/flowy-sqlite/src/sqlite/pool.rs @@ -11,144 +11,144 @@ lazy_static::lazy_static! { } pub struct ConnectionPool { - pub(crate) inner: Pool, + pub(crate) inner: Pool, } impl std::ops::Deref for ConnectionPool { - type Target = Pool; + type Target = Pool; - fn deref(&self) -> &Self::Target { - &self.inner - } + fn deref(&self) -> &Self::Target { + &self.inner + } } impl ConnectionPool { - pub fn new(config: PoolConfig, uri: T) -> Result - where - T: Into, - { - let manager = ConnectionManager::new(uri); - let thread_pool = DB_POOL.clone(); - let config = Arc::new(config); - let customizer_config = DatabaseCustomizerConfig::default(); + pub fn new(config: PoolConfig, uri: T) -> Result + where + T: Into, + { + let manager = ConnectionManager::new(uri); + let thread_pool = DB_POOL.clone(); + let config = Arc::new(config); + let customizer_config = DatabaseCustomizerConfig::default(); - let pool = r2d2::Pool::builder() - .thread_pool(thread_pool) - .min_idle(Some(config.min_idle)) - .connection_customizer(Box::new(DatabaseCustomizer::new(customizer_config))) - .max_size(config.max_size) - .max_lifetime(None) - .connection_timeout(config.connection_timeout) - .idle_timeout(Some(config.idle_timeout)) - .build_unchecked(manager); - Ok(ConnectionPool { inner: pool }) - } + let pool = r2d2::Pool::builder() + .thread_pool(thread_pool) + .min_idle(Some(config.min_idle)) + .connection_customizer(Box::new(DatabaseCustomizer::new(customizer_config))) + .max_size(config.max_size) + .max_lifetime(None) + .connection_timeout(config.connection_timeout) + .idle_timeout(Some(config.idle_timeout)) + .build_unchecked(manager); + Ok(ConnectionPool { inner: pool }) + } } #[allow(dead_code)] pub type OnExecFunc = Box Box + Send + Sync>; pub struct PoolConfig { - min_idle: u32, - max_size: u32, - connection_timeout: Duration, - idle_timeout: Duration, + min_idle: u32, + max_size: u32, + connection_timeout: Duration, + idle_timeout: Duration, } impl Default for PoolConfig { - fn default() -> Self { - Self { - min_idle: 1, - max_size: 10, - connection_timeout: Duration::from_secs(10), - idle_timeout: Duration::from_secs(5 * 60), - } + fn default() -> Self { + Self { + min_idle: 1, + max_size: 10, + connection_timeout: Duration::from_secs(10), + idle_timeout: Duration::from_secs(5 * 60), } + } } impl PoolConfig { - #[allow(dead_code)] - pub fn min_idle(mut self, min_idle: u32) -> Self { - self.min_idle = min_idle; - self - } + #[allow(dead_code)] + pub fn min_idle(mut self, min_idle: u32) -> Self { + self.min_idle = min_idle; + self + } - #[allow(dead_code)] - pub fn max_size(mut self, max_size: u32) -> Self { - self.max_size = max_size; - self - } + #[allow(dead_code)] + pub fn max_size(mut self, max_size: u32) -> Self { + self.max_size = max_size; + self + } } pub struct ConnectionManager { - db_uri: String, + db_uri: String, } impl ManageConnection for ConnectionManager { - type Connection = SqliteConnection; - type Error = crate::sqlite::Error; + type Connection = SqliteConnection; + type Error = crate::sqlite::Error; - fn connect(&self) -> Result { - Ok(SqliteConnection::establish(&self.db_uri)?) - } + fn connect(&self) -> Result { + Ok(SqliteConnection::establish(&self.db_uri)?) + } - fn is_valid(&self, conn: &mut Self::Connection) -> Result<()> { - Ok(conn.execute("SELECT 1").map(|_| ())?) - } + fn is_valid(&self, conn: &mut Self::Connection) -> Result<()> { + Ok(conn.execute("SELECT 1").map(|_| ())?) + } - fn has_broken(&self, _conn: &mut Self::Connection) -> bool { - false - } + fn has_broken(&self, _conn: &mut Self::Connection) -> bool { + false + } } impl ConnectionManager { - pub fn new>(uri: S) -> Self { - ConnectionManager { db_uri: uri.into() } - } + pub fn new>(uri: S) -> Self { + ConnectionManager { db_uri: uri.into() } + } } #[derive(Debug)] pub struct DatabaseCustomizerConfig { - pub(crate) journal_mode: SQLiteJournalMode, - pub(crate) synchronous: SQLiteSynchronous, - pub(crate) busy_timeout: i32, - #[allow(dead_code)] - pub(crate) secure_delete: bool, + pub(crate) journal_mode: SQLiteJournalMode, + pub(crate) synchronous: SQLiteSynchronous, + pub(crate) busy_timeout: i32, + #[allow(dead_code)] + pub(crate) secure_delete: bool, } impl Default for DatabaseCustomizerConfig { - fn default() -> Self { - Self { - journal_mode: SQLiteJournalMode::WAL, - synchronous: SQLiteSynchronous::NORMAL, - busy_timeout: 5000, - secure_delete: true, - } + fn default() -> Self { + Self { + journal_mode: SQLiteJournalMode::WAL, + synchronous: SQLiteSynchronous::NORMAL, + busy_timeout: 5000, + secure_delete: true, } + } } #[derive(Debug)] struct DatabaseCustomizer { - config: DatabaseCustomizerConfig, + config: DatabaseCustomizerConfig, } impl DatabaseCustomizer { - fn new(config: DatabaseCustomizerConfig) -> Self - where - Self: Sized, - { - Self { config } - } + fn new(config: DatabaseCustomizerConfig) -> Self + where + Self: Sized, + { + Self { config } + } } impl CustomizeConnection for DatabaseCustomizer { - fn on_acquire(&self, conn: &mut SqliteConnection) -> Result<()> { - conn.pragma_set_busy_timeout(self.config.busy_timeout)?; - if self.config.journal_mode != SQLiteJournalMode::WAL { - conn.pragma_set_journal_mode(self.config.journal_mode, None)?; - } - conn.pragma_set_synchronous(self.config.synchronous, None)?; - - Ok(()) + fn on_acquire(&self, conn: &mut SqliteConnection) -> Result<()> { + conn.pragma_set_busy_timeout(self.config.busy_timeout)?; + if self.config.journal_mode != SQLiteJournalMode::WAL { + conn.pragma_set_journal_mode(self.config.journal_mode, None)?; } + conn.pragma_set_synchronous(self.config.synchronous, None)?; + + Ok(()) + } } diff --git a/frontend/rust-lib/flowy-sqlite/src/sqlite/pragma.rs b/frontend/rust-lib/flowy-sqlite/src/sqlite/pragma.rs index 566c0c540d..d773d2d25e 100644 --- a/frontend/rust-lib/flowy-sqlite/src/sqlite/pragma.rs +++ b/frontend/rust-lib/flowy-sqlite/src/sqlite/pragma.rs @@ -1,170 +1,183 @@ #![allow(clippy::upper_case_acronyms)] use crate::sqlite::errors::{Error, Result}; use diesel::{ - expression::SqlLiteral, - query_dsl::load_dsl::LoadQuery, - sql_types::{Integer, Text}, - SqliteConnection, + expression::SqlLiteral, + query_dsl::load_dsl::LoadQuery, + sql_types::{Integer, Text}, + SqliteConnection, }; use crate::sqlite::conn_ext::ConnectionExtension; use std::{ - convert::{TryFrom, TryInto}, - fmt, - str::FromStr, + convert::{TryFrom, TryInto}, + fmt, + str::FromStr, }; pub trait PragmaExtension: ConnectionExtension { - fn pragma(&self, key: &str, val: D, schema: Option<&str>) -> Result<()> { - let query = match schema { - Some(schema) => format!("PRAGMA {}.{} = '{}'", schema, key, val), - None => format!("PRAGMA {} = '{}'", key, val), - }; - tracing::trace!("SQLITE {}", query); - self.exec(&query)?; - Ok(()) - } + fn pragma(&self, key: &str, val: D, schema: Option<&str>) -> Result<()> { + let query = match schema { + Some(schema) => format!("PRAGMA {}.{} = '{}'", schema, key, val), + None => format!("PRAGMA {} = '{}'", key, val), + }; + tracing::trace!("SQLITE {}", query); + self.exec(&query)?; + Ok(()) + } - fn pragma_ret(&self, key: &str, val: D, schema: Option<&str>) -> Result - where - SqlLiteral: LoadQuery, - { - let query = match schema { - Some(schema) => format!("PRAGMA {}.{} = '{}'", schema, key, val), - None => format!("PRAGMA {} = '{}'", key, val), - }; - tracing::trace!("SQLITE {}", query); - self.query::(&query) - } + fn pragma_ret( + &self, + key: &str, + val: D, + schema: Option<&str>, + ) -> Result + where + SqlLiteral: LoadQuery, + { + let query = match schema { + Some(schema) => format!("PRAGMA {}.{} = '{}'", schema, key, val), + None => format!("PRAGMA {} = '{}'", key, val), + }; + tracing::trace!("SQLITE {}", query); + self.query::(&query) + } - fn pragma_get(&self, key: &str, schema: Option<&str>) -> Result - where - SqlLiteral: LoadQuery, - { - let query = match schema { - Some(schema) => format!("PRAGMA {}.{}", schema, key), - None => format!("PRAGMA {}", key), - }; - tracing::trace!("SQLITE {}", query); - self.query::(&query) - } + fn pragma_get(&self, key: &str, schema: Option<&str>) -> Result + where + SqlLiteral: LoadQuery, + { + let query = match schema { + Some(schema) => format!("PRAGMA {}.{}", schema, key), + None => format!("PRAGMA {}", key), + }; + tracing::trace!("SQLITE {}", query); + self.query::(&query) + } - fn pragma_set_busy_timeout(&self, timeout_ms: i32) -> Result { - self.pragma_ret::("busy_timeout", timeout_ms, None) - } + fn pragma_set_busy_timeout(&self, timeout_ms: i32) -> Result { + self.pragma_ret::("busy_timeout", timeout_ms, None) + } - fn pragma_get_busy_timeout(&self) -> Result { - self.pragma_get::("busy_timeout", None) - } + fn pragma_get_busy_timeout(&self) -> Result { + self.pragma_get::("busy_timeout", None) + } - fn pragma_set_journal_mode(&self, mode: SQLiteJournalMode, schema: Option<&str>) -> Result { - self.pragma_ret::("journal_mode", mode, schema) - } + fn pragma_set_journal_mode(&self, mode: SQLiteJournalMode, schema: Option<&str>) -> Result { + self.pragma_ret::("journal_mode", mode, schema) + } - fn pragma_get_journal_mode(&self, schema: Option<&str>) -> Result { - self.pragma_get::("journal_mode", schema)?.parse() - } + fn pragma_get_journal_mode(&self, schema: Option<&str>) -> Result { + self + .pragma_get::("journal_mode", schema)? + .parse() + } - fn pragma_set_synchronous(&self, synchronous: SQLiteSynchronous, schema: Option<&str>) -> Result<()> { - self.pragma("synchronous", synchronous as u8, schema) - } + fn pragma_set_synchronous( + &self, + synchronous: SQLiteSynchronous, + schema: Option<&str>, + ) -> Result<()> { + self.pragma("synchronous", synchronous as u8, schema) + } - fn pragma_get_synchronous(&self, schema: Option<&str>) -> Result { - self.pragma_get::("synchronous", schema)?.try_into() - } + fn pragma_get_synchronous(&self, schema: Option<&str>) -> Result { + self + .pragma_get::("synchronous", schema)? + .try_into() + } } impl PragmaExtension for SqliteConnection {} #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum SQLiteJournalMode { - DELETE, - TRUNCATE, - PERSIST, - MEMORY, - WAL, - OFF, + DELETE, + TRUNCATE, + PERSIST, + MEMORY, + WAL, + OFF, } impl fmt::Display for SQLiteJournalMode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - Self::DELETE => "DELETE", - Self::TRUNCATE => "TRUNCATE", - Self::PERSIST => "PERSIST", - Self::MEMORY => "MEMORY", - Self::WAL => "WAL", - Self::OFF => "OFF", - } - ) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + Self::DELETE => "DELETE", + Self::TRUNCATE => "TRUNCATE", + Self::PERSIST => "PERSIST", + Self::MEMORY => "MEMORY", + Self::WAL => "WAL", + Self::OFF => "OFF", + } + ) + } } impl FromStr for SQLiteJournalMode { - type Err = Error; + type Err = Error; - fn from_str(s: &str) -> Result { - match s.to_uppercase().as_ref() { - "DELETE" => Ok(Self::DELETE), - "TRUNCATE" => Ok(Self::TRUNCATE), - "PERSIST" => Ok(Self::PERSIST), - "MEMORY" => Ok(Self::MEMORY), - "WAL" => Ok(Self::WAL), - "OFF" => Ok(Self::OFF), - _ => Err(format!("Unknown value {} for JournalMode", s).into()), - } + fn from_str(s: &str) -> Result { + match s.to_uppercase().as_ref() { + "DELETE" => Ok(Self::DELETE), + "TRUNCATE" => Ok(Self::TRUNCATE), + "PERSIST" => Ok(Self::PERSIST), + "MEMORY" => Ok(Self::MEMORY), + "WAL" => Ok(Self::WAL), + "OFF" => Ok(Self::OFF), + _ => Err(format!("Unknown value {} for JournalMode", s).into()), } + } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum SQLiteSynchronous { - EXTRA = 3, - FULL = 2, - NORMAL = 1, - OFF = 0, + EXTRA = 3, + FULL = 2, + NORMAL = 1, + OFF = 0, } impl fmt::Display for SQLiteSynchronous { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match self { - Self::OFF => "OFF", - Self::NORMAL => "NORMAL", - Self::FULL => "FULL", - Self::EXTRA => "EXTRA", - } - ) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + Self::OFF => "OFF", + Self::NORMAL => "NORMAL", + Self::FULL => "FULL", + Self::EXTRA => "EXTRA", + } + ) + } } impl TryFrom for SQLiteSynchronous { - type Error = Error; + type Error = Error; - fn try_from(v: i32) -> Result { - match v { - 0 => Ok(Self::OFF), - 1 => Ok(Self::NORMAL), - 2 => Ok(Self::FULL), - 3 => Ok(Self::EXTRA), - _ => Err(format!("Unknown value {} for Synchronous", v).into()), - } + fn try_from(v: i32) -> Result { + match v { + 0 => Ok(Self::OFF), + 1 => Ok(Self::NORMAL), + 2 => Ok(Self::FULL), + 3 => Ok(Self::EXTRA), + _ => Err(format!("Unknown value {} for Synchronous", v).into()), } + } } impl FromStr for SQLiteSynchronous { - type Err = Error; + type Err = Error; - fn from_str(s: &str) -> Result { - match s.to_uppercase().as_ref() { - "0" | "OFF" => Ok(Self::OFF), - "1" | "NORMAL" => Ok(Self::NORMAL), - "2" | "FULL" => Ok(Self::FULL), - "3" | "EXTRA" => Ok(Self::EXTRA), - _ => Err(format!("Unknown value {} for Synchronous", s).into()), - } + fn from_str(s: &str) -> Result { + match s.to_uppercase().as_ref() { + "0" | "OFF" => Ok(Self::OFF), + "1" | "NORMAL" => Ok(Self::NORMAL), + "2" | "FULL" => Ok(Self::FULL), + "3" | "EXTRA" => Ok(Self::EXTRA), + _ => Err(format!("Unknown value {} for Synchronous", s).into()), } + } } diff --git a/frontend/rust-lib/flowy-task/src/queue.rs b/frontend/rust-lib/flowy-task/src/queue.rs index 135dded3a8..5ec47dde79 100644 --- a/frontend/rust-lib/flowy-task/src/queue.rs +++ b/frontend/rust-lib/flowy-task/src/queue.rs @@ -8,117 +8,123 @@ use std::sync::Arc; #[derive(Default)] pub(crate) struct TaskQueue { - // index_tasks for quick access - index_tasks: HashMap>>, - queue: BinaryHeap>>, + // index_tasks for quick access + index_tasks: HashMap>>, + queue: BinaryHeap>>, } impl TaskQueue { - pub(crate) fn new() -> Self { - Self::default() + pub(crate) fn new() -> Self { + Self::default() + } + + pub(crate) fn push(&mut self, task: &Task) { + if task.content.is_none() { + tracing::warn!( + "The task:{} with empty content will be not executed", + task.id + ); + return; } - pub(crate) fn push(&mut self, task: &Task) { - if task.content.is_none() { - tracing::warn!("The task:{} with empty content will be not executed", task.id); - return; - } - - let pending_task = PendingTask { - qos: task.qos, - id: task.id, - }; - match self.index_tasks.entry(task.handler_id.clone()) { - Entry::Occupied(entry) => { - let mut list = entry.get().borrow_mut(); - assert!(list.peek().map(|old_id| pending_task.id >= old_id.id).unwrap_or(true)); - list.push(pending_task); - } - Entry::Vacant(entry) => { - let mut task_list = TaskList::new(entry.key()); - task_list.push(pending_task); - let task_list = Arc::new(AtomicRefCell::new(task_list)); - entry.insert(task_list.clone()); - self.queue.push(task_list); - } - } + let pending_task = PendingTask { + qos: task.qos, + id: task.id, + }; + match self.index_tasks.entry(task.handler_id.clone()) { + Entry::Occupied(entry) => { + let mut list = entry.get().borrow_mut(); + assert!(list + .peek() + .map(|old_id| pending_task.id >= old_id.id) + .unwrap_or(true)); + list.push(pending_task); + }, + Entry::Vacant(entry) => { + let mut task_list = TaskList::new(entry.key()); + task_list.push(pending_task); + let task_list = Arc::new(AtomicRefCell::new(task_list)); + entry.insert(task_list.clone()); + self.queue.push(task_list); + }, } + } - #[allow(dead_code)] - pub(crate) fn clear(&mut self) { - self.queue.clear(); - } + #[allow(dead_code)] + pub(crate) fn clear(&mut self) { + self.queue.clear(); + } - pub(crate) fn mut_head(&mut self, mut f: F) -> Option - where - F: FnMut(&mut TaskList) -> Option, - { - let head = self.queue.pop()?; - let result = { - let mut ref_head = head.borrow_mut(); - f(&mut ref_head) - }; - if !head.borrow().tasks.is_empty() { - self.queue.push(head); - } else { - self.index_tasks.remove(&head.borrow().id); - } - result + pub(crate) fn mut_head(&mut self, mut f: F) -> Option + where + F: FnMut(&mut TaskList) -> Option, + { + let head = self.queue.pop()?; + let result = { + let mut ref_head = head.borrow_mut(); + f(&mut ref_head) + }; + if !head.borrow().tasks.is_empty() { + self.queue.push(head); + } else { + self.index_tasks.remove(&head.borrow().id); } + result + } } pub type TaskHandlerId = String; #[derive(Debug)] pub(crate) struct TaskList { - pub(crate) id: TaskHandlerId, - tasks: BinaryHeap, + pub(crate) id: TaskHandlerId, + tasks: BinaryHeap, } impl Deref for TaskList { - type Target = BinaryHeap; + type Target = BinaryHeap; - fn deref(&self) -> &Self::Target { - &self.tasks - } + fn deref(&self) -> &Self::Target { + &self.tasks + } } impl DerefMut for TaskList { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.tasks - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.tasks + } } impl TaskList { - fn new(id: &str) -> Self { - Self { - id: id.to_owned(), - tasks: BinaryHeap::new(), - } + fn new(id: &str) -> Self { + Self { + id: id.to_owned(), + tasks: BinaryHeap::new(), } + } } impl PartialEq for TaskList { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } } impl Eq for TaskList {} impl Ord for TaskList { - fn cmp(&self, other: &Self) -> Ordering { - match (self.peek(), other.peek()) { - (None, None) => Ordering::Equal, - (None, Some(_)) => Ordering::Less, - (Some(_), None) => Ordering::Greater, - (Some(lhs), Some(rhs)) => lhs.cmp(rhs), - } + fn cmp(&self, other: &Self) -> Ordering { + match (self.peek(), other.peek()) { + (None, None) => Ordering::Equal, + (None, Some(_)) => Ordering::Less, + (Some(_), None) => Ordering::Greater, + (Some(lhs), Some(rhs)) => lhs.cmp(rhs), } + } } impl PartialOrd for TaskList { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } } diff --git a/frontend/rust-lib/flowy-task/src/scheduler.rs b/frontend/rust-lib/flowy-task/src/scheduler.rs index 2cd70f7a08..ee9e4d979a 100644 --- a/frontend/rust-lib/flowy-task/src/scheduler.rs +++ b/frontend/rust-lib/flowy-task/src/scheduler.rs @@ -12,192 +12,203 @@ use tokio::sync::{watch, RwLock}; use tokio::time::interval; pub struct TaskDispatcher { - queue: TaskQueue, - store: TaskStore, - timeout: Duration, - handlers: RefCountHashMap, + queue: TaskQueue, + store: TaskStore, + timeout: Duration, + handlers: RefCountHashMap, - notifier: watch::Sender, - pub(crate) notifier_rx: Option>, + notifier: watch::Sender, + pub(crate) notifier_rx: Option>, } impl TaskDispatcher { - pub fn new(timeout: Duration) -> Self { - let (notifier, notifier_rx) = watch::channel(false); - Self { - queue: TaskQueue::new(), - store: TaskStore::new(), - timeout, - handlers: RefCountHashMap::new(), - notifier, - notifier_rx: Some(notifier_rx), - } + pub fn new(timeout: Duration) -> Self { + let (notifier, notifier_rx) = watch::channel(false); + Self { + queue: TaskQueue::new(), + store: TaskStore::new(), + timeout, + handlers: RefCountHashMap::new(), + notifier, + notifier_rx: Some(notifier_rx), + } + } + + pub fn register_handler(&mut self, handler: T) + where + T: TaskHandler, + { + let handler_id = handler.handler_id().to_owned(); + self + .handlers + .insert(handler_id, RefCountTaskHandler(Arc::new(handler))); + } + + pub async fn unregister_handler>(&mut self, handler_id: T) { + self.handlers.remove(handler_id.as_ref()).await; + } + + pub fn stop(&mut self) { + let _ = self.notifier.send(true); + self.queue.clear(); + self.store.clear(); + } + + 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)?; + let ret = task.ret.take()?; + + // Do not execute the task if the task was cancelled. + if task.state().is_cancel() { + let _ = ret.send(task.into()); + self.notify(); + return None; } - pub fn register_handler(&mut self, handler: T) - where - T: TaskHandler, - { - let handler_id = handler.handler_id().to_owned(); - self.handlers.insert(handler_id, RefCountTaskHandler(Arc::new(handler))); + 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 + ); + match tokio::time::timeout(self.timeout, handler.run(content)).await { + Ok(result) => match result { + Ok(_) => task.set_state(TaskState::Done), + Err(e) => { + tracing::error!("Process {} task failed: {:?}", handler.handler_name(), e); + task.set_state(TaskState::Failure); + }, + }, + Err(e) => { + tracing::error!("Process {} task timeout: {:?}", handler.handler_name(), e); + task.set_state(TaskState::Timeout); + }, + } + } else { + task.set_state(TaskState::Cancel); + } + let _ = ret.send(task.into()); + self.notify(); + None + } + + pub fn add_task(&mut self, task: Task) { + debug_assert!(!task.state().is_done()); + if task.state().is_done() { + tracing::warn!("Should not add a task which state is done"); + return; } - pub async fn unregister_handler>(&mut self, handler_id: T) { - self.handlers.remove(handler_id.as_ref()).await; + self.queue.push(&task); + self.store.insert_task(task); + self.notify(); + } + + pub fn read_task(&self, task_id: &TaskId) -> Option<&Task> { + self.store.read_task(task_id) + } + + pub fn cancel_task(&mut self, task_id: TaskId) { + if let Some(task) = self.store.mut_task(&task_id) { + task.set_state(TaskState::Cancel); } + } - pub fn stop(&mut self) { - let _ = self.notifier.send(true); - self.queue.clear(); - self.store.clear(); - } + pub fn next_task_id(&self) -> TaskId { + self.store.next_task_id() + } - 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)?; - let ret = task.ret.take()?; - - // Do not execute the task if the task was cancelled. - if task.state().is_cancel() { - let _ = ret.send(task.into()); - self.notify(); - return None; - } - - 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); - match tokio::time::timeout(self.timeout, handler.run(content)).await { - Ok(result) => match result { - Ok(_) => task.set_state(TaskState::Done), - Err(e) => { - tracing::error!("Process {} task failed: {:?}", handler.handler_name(), e); - task.set_state(TaskState::Failure); - } - }, - Err(e) => { - tracing::error!("Process {} task timeout: {:?}", handler.handler_name(), e); - task.set_state(TaskState::Timeout); - } - } - } else { - task.set_state(TaskState::Cancel); - } - let _ = ret.send(task.into()); - self.notify(); - None - } - - pub fn add_task(&mut self, task: Task) { - debug_assert!(!task.state().is_done()); - if task.state().is_done() { - tracing::warn!("Should not add a task which state is done"); - return; - } - - self.queue.push(&task); - self.store.insert_task(task); - self.notify(); - } - - pub fn read_task(&self, task_id: &TaskId) -> Option<&Task> { - self.store.read_task(task_id) - } - - pub fn cancel_task(&mut self, task_id: TaskId) { - if let Some(task) = self.store.mut_task(&task_id) { - task.set_state(TaskState::Cancel); - } - } - - pub fn next_task_id(&self) -> TaskId { - self.store.next_task_id() - } - - pub(crate) fn notify(&self) { - let _ = self.notifier.send(false); - } + pub(crate) fn notify(&self) { + let _ = self.notifier.send(false); + } } pub struct TaskRunner(); impl TaskRunner { - pub async fn run(dispatcher: Arc>) { - dispatcher.read().await.notify(); - let debounce_duration = Duration::from_millis(300); - let mut notifier = dispatcher.write().await.notifier_rx.take().expect("Only take once"); - loop { - // stops the runner if the notifier was closed. - if notifier.changed().await.is_err() { - break; - } + pub async fn run(dispatcher: Arc>) { + dispatcher.read().await.notify(); + let debounce_duration = Duration::from_millis(300); + let mut notifier = dispatcher + .write() + .await + .notifier_rx + .take() + .expect("Only take once"); + loop { + // stops the runner if the notifier was closed. + if notifier.changed().await.is_err() { + break; + } - // stops the runner if the value of notifier is `true` - if *notifier.borrow() { - break; - } + // stops the runner if the value of notifier is `true` + if *notifier.borrow() { + break; + } - let mut interval = interval(debounce_duration); - interval.tick().await; - let _ = dispatcher.write().await.process_next_task().await; - } + let mut interval = interval(debounce_duration); + interval.tick().await; + let _ = dispatcher.write().await.process_next_task().await; } + } } pub trait TaskHandler: Send + Sync + 'static { - fn handler_id(&self) -> &str; + fn handler_id(&self) -> &str; - fn handler_name(&self) -> &str { - "" - } + fn handler_name(&self) -> &str { + "" + } - fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error>; + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error>; } impl TaskHandler for Box where - T: TaskHandler, + T: TaskHandler, { - fn handler_id(&self) -> &str { - (**self).handler_id() - } + fn handler_id(&self) -> &str { + (**self).handler_id() + } - fn handler_name(&self) -> &str { - (**self).handler_name() - } + fn handler_name(&self) -> &str { + (**self).handler_name() + } - fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { - (**self).run(content) - } + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { + (**self).run(content) + } } impl TaskHandler for Arc where - T: TaskHandler, + T: TaskHandler, { - fn handler_id(&self) -> &str { - (**self).handler_id() - } + fn handler_id(&self) -> &str { + (**self).handler_id() + } - fn handler_name(&self) -> &str { - (**self).handler_name() - } + fn handler_name(&self) -> &str { + (**self).handler_name() + } - fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { - (**self).run(content) - } + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { + (**self).run(content) + } } #[derive(Clone)] struct RefCountTaskHandler(Arc); #[async_trait] impl RefCountValue for RefCountTaskHandler { - async fn did_remove(&self) {} + async fn did_remove(&self) {} } impl std::ops::Deref for RefCountTaskHandler { - type Target = Arc; + type Target = Arc; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/frontend/rust-lib/flowy-task/src/store.rs b/frontend/rust-lib/flowy-task/src/store.rs index 800b189f21..287b91b0db 100644 --- a/frontend/rust-lib/flowy-task/src/store.rs +++ b/frontend/rust-lib/flowy-task/src/store.rs @@ -5,47 +5,47 @@ use std::sync::atomic::AtomicU32; use std::sync::atomic::Ordering::SeqCst; pub(crate) struct TaskStore { - tasks: HashMap, - task_id_counter: AtomicU32, + tasks: HashMap, + task_id_counter: AtomicU32, } impl TaskStore { - pub fn new() -> Self { - Self { - tasks: HashMap::new(), - task_id_counter: AtomicU32::new(0), - } + pub fn new() -> Self { + Self { + tasks: HashMap::new(), + task_id_counter: AtomicU32::new(0), } + } - pub(crate) fn insert_task(&mut self, task: Task) { - self.tasks.insert(task.id, task); - } + pub(crate) fn insert_task(&mut self, task: Task) { + self.tasks.insert(task.id, task); + } - pub(crate) fn remove_task(&mut self, task_id: &TaskId) -> Option { - self.tasks.remove(task_id) - } + pub(crate) fn remove_task(&mut self, task_id: &TaskId) -> Option { + self.tasks.remove(task_id) + } - pub(crate) fn mut_task(&mut self, task_id: &TaskId) -> Option<&mut Task> { - self.tasks.get_mut(task_id) - } + pub(crate) fn mut_task(&mut self, task_id: &TaskId) -> Option<&mut Task> { + self.tasks.get_mut(task_id) + } - pub(crate) fn read_task(&self, task_id: &TaskId) -> Option<&Task> { - self.tasks.get(task_id) - } + pub(crate) fn read_task(&self, task_id: &TaskId) -> Option<&Task> { + self.tasks.get(task_id) + } - pub(crate) fn clear(&mut self) { - let tasks = mem::take(&mut self.tasks); - tasks.into_values().for_each(|mut task| { - if task.ret.is_some() { - let ret = task.ret.take().unwrap(); - task.set_state(TaskState::Cancel); - let _ = ret.send(task.into()); - } - }); - } + pub(crate) fn clear(&mut self) { + let tasks = mem::take(&mut self.tasks); + tasks.into_values().for_each(|mut task| { + if task.ret.is_some() { + let ret = task.ret.take().unwrap(); + task.set_state(TaskState::Cancel); + let _ = ret.send(task.into()); + } + }); + } - pub(crate) fn next_task_id(&self) -> TaskId { - let _ = self.task_id_counter.fetch_add(1, SeqCst); - self.task_id_counter.load(SeqCst) - } + pub(crate) fn next_task_id(&self) -> TaskId { + let _ = self.task_id_counter.fetch_add(1, SeqCst); + self.task_id_counter.load(SeqCst) + } } diff --git a/frontend/rust-lib/flowy-task/src/task.rs b/frontend/rust-lib/flowy-task/src/task.rs index 43d32813ab..68c2ae2425 100644 --- a/frontend/rust-lib/flowy-task/src/task.rs +++ b/frontend/rust-lib/flowy-task/src/task.rs @@ -4,140 +4,142 @@ use tokio::sync::oneshot::{Receiver, Sender}; #[derive(Eq, Debug, Clone, Copy)] pub enum QualityOfService { - Background, - UserInteractive, + Background, + UserInteractive, } impl PartialEq for QualityOfService { - fn eq(&self, other: &Self) -> bool { - matches!( - (self, other), - (Self::Background, Self::Background) | (Self::UserInteractive, Self::UserInteractive) - ) - } + fn eq(&self, other: &Self) -> bool { + matches!( + (self, other), + (Self::Background, Self::Background) | (Self::UserInteractive, Self::UserInteractive) + ) + } } pub type TaskId = u32; #[derive(Eq, Debug, Clone, Copy)] pub struct PendingTask { - pub qos: QualityOfService, - pub id: TaskId, + pub qos: QualityOfService, + pub id: TaskId, } impl PartialEq for PendingTask { - fn eq(&self, other: &Self) -> bool { - self.id.eq(&other.id) - } + fn eq(&self, other: &Self) -> bool { + self.id.eq(&other.id) + } } impl PartialOrd for PendingTask { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } } impl Ord for PendingTask { - fn cmp(&self, other: &Self) -> Ordering { - match (self.qos, other.qos) { - // User interactive - (QualityOfService::UserInteractive, QualityOfService::UserInteractive) => self.id.cmp(&other.id), - (QualityOfService::UserInteractive, _) => Ordering::Greater, - (_, QualityOfService::UserInteractive) => Ordering::Less, - // background - (QualityOfService::Background, QualityOfService::Background) => self.id.cmp(&other.id), - } + fn cmp(&self, other: &Self) -> Ordering { + match (self.qos, other.qos) { + // User interactive + (QualityOfService::UserInteractive, QualityOfService::UserInteractive) => { + self.id.cmp(&other.id) + }, + (QualityOfService::UserInteractive, _) => Ordering::Greater, + (_, QualityOfService::UserInteractive) => Ordering::Less, + // background + (QualityOfService::Background, QualityOfService::Background) => self.id.cmp(&other.id), } + } } #[derive(Debug, Clone)] pub enum TaskContent { - Text(String), - Blob(Vec), + Text(String), + Blob(Vec), } #[derive(Debug, Eq, PartialEq, Clone)] pub enum TaskState { - Pending, - Processing, - Done, - Failure, - Cancel, - Timeout, + Pending, + Processing, + Done, + Failure, + Cancel, + Timeout, } impl TaskState { - pub fn is_pending(&self) -> bool { - matches!(self, TaskState::Pending) - } - pub fn is_done(&self) -> bool { - matches!(self, TaskState::Done) - } - pub fn is_cancel(&self) -> bool { - matches!(self, TaskState::Cancel) - } + pub fn is_pending(&self) -> bool { + matches!(self, TaskState::Pending) + } + pub fn is_done(&self) -> bool { + matches!(self, TaskState::Done) + } + pub fn is_cancel(&self) -> bool { + matches!(self, TaskState::Cancel) + } - pub fn is_processing(&self) -> bool { - matches!(self, TaskState::Processing) - } + pub fn is_processing(&self) -> bool { + matches!(self, TaskState::Processing) + } - pub fn is_failed(&self) -> bool { - matches!(self, TaskState::Failure) - } + pub fn is_failed(&self) -> bool { + matches!(self, TaskState::Failure) + } } pub struct Task { - pub id: TaskId, - pub handler_id: TaskHandlerId, - pub content: Option, - pub qos: QualityOfService, - state: TaskState, - pub ret: Option>, - pub recv: Option>, + pub id: TaskId, + pub handler_id: TaskHandlerId, + pub content: Option, + pub qos: QualityOfService, + state: TaskState, + pub ret: Option>, + pub recv: Option>, } impl Task { - pub fn background(handler_id: &str, id: TaskId, content: TaskContent) -> Self { - Self::new(handler_id, id, content, QualityOfService::Background) - } + pub fn background(handler_id: &str, id: TaskId, content: TaskContent) -> Self { + Self::new(handler_id, id, content, QualityOfService::Background) + } - pub fn user_interactive(handler_id: &str, id: TaskId, content: TaskContent) -> Self { - Self::new(handler_id, id, content, QualityOfService::UserInteractive) - } + pub fn user_interactive(handler_id: &str, id: TaskId, content: TaskContent) -> Self { + Self::new(handler_id, id, content, QualityOfService::UserInteractive) + } - pub fn new(handler_id: &str, id: TaskId, content: TaskContent, qos: QualityOfService) -> Self { - let handler_id = handler_id.to_owned(); - let (ret, recv) = tokio::sync::oneshot::channel(); - Self { - handler_id, - id, - content: Some(content), - qos, - ret: Some(ret), - recv: Some(recv), - state: TaskState::Pending, - } + pub fn new(handler_id: &str, id: TaskId, content: TaskContent, qos: QualityOfService) -> Self { + let handler_id = handler_id.to_owned(); + let (ret, recv) = tokio::sync::oneshot::channel(); + Self { + handler_id, + id, + content: Some(content), + qos, + ret: Some(ret), + recv: Some(recv), + state: TaskState::Pending, } + } - pub fn state(&self) -> &TaskState { - &self.state - } + pub fn state(&self) -> &TaskState { + &self.state + } - pub(crate) fn set_state(&mut self, status: TaskState) { - self.state = status; - } + pub(crate) fn set_state(&mut self, status: TaskState) { + self.state = status; + } } pub struct TaskResult { - pub id: TaskId, - pub state: TaskState, + pub id: TaskId, + pub state: TaskState, } impl std::convert::From for TaskResult { - fn from(task: Task) -> Self { - TaskResult { - id: task.id, - state: task.state().clone(), - } + fn from(task: Task) -> Self { + TaskResult { + id: task.id, + state: task.state().clone(), } + } } diff --git a/frontend/rust-lib/flowy-task/tests/task_test/script.rs b/frontend/rust-lib/flowy-task/tests/task_test/script.rs index 312ea9acbd..58c315daf1 100644 --- a/frontend/rust-lib/flowy-task/tests/task_test/script.rs +++ b/frontend/rust-lib/flowy-task/tests/task_test/script.rs @@ -1,5 +1,7 @@ use anyhow::Error; -use flowy_task::{Task, TaskContent, TaskDispatcher, TaskHandler, TaskId, TaskResult, TaskRunner, TaskState}; +use flowy_task::{ + Task, TaskContent, TaskDispatcher, TaskHandler, TaskId, TaskResult, TaskRunner, TaskState, +}; use futures::stream::FuturesUnordered; use futures::StreamExt; use lib_infra::async_trait::async_trait; @@ -12,188 +14,203 @@ use tokio::sync::oneshot::Receiver; use tokio::sync::RwLock; pub enum SearchScript { - AddTask { - task: Task, - }, - AddTasks { - tasks: Vec, - }, - #[allow(dead_code)] - Wait { - millisecond: u64, - }, - CancelTask { - task_id: TaskId, - }, - UnregisterHandler { - handler_id: String, - }, - AssertTaskStatus { - task_id: TaskId, - expected_status: TaskState, - }, - AssertExecuteOrder { - execute_order: Vec, - rets: Vec>, - }, + AddTask { + task: Task, + }, + AddTasks { + tasks: Vec, + }, + #[allow(dead_code)] + Wait { + millisecond: u64, + }, + CancelTask { + task_id: TaskId, + }, + UnregisterHandler { + handler_id: String, + }, + AssertTaskStatus { + task_id: TaskId, + expected_status: TaskState, + }, + AssertExecuteOrder { + execute_order: Vec, + rets: Vec>, + }, } pub struct SearchTest { - scheduler: Arc>, + scheduler: Arc>, } impl SearchTest { - pub async fn new() -> Self { - let duration = Duration::from_millis(1000); - let mut scheduler = TaskDispatcher::new(duration); - scheduler.register_handler(Arc::new(MockTextTaskHandler())); - scheduler.register_handler(Arc::new(MockBlobTaskHandler())); - scheduler.register_handler(Arc::new(MockTimeoutTaskHandler())); + pub async fn new() -> Self { + let duration = Duration::from_millis(1000); + let mut scheduler = TaskDispatcher::new(duration); + scheduler.register_handler(Arc::new(MockTextTaskHandler())); + scheduler.register_handler(Arc::new(MockBlobTaskHandler())); + scheduler.register_handler(Arc::new(MockTimeoutTaskHandler())); - let scheduler = Arc::new(RwLock::new(scheduler)); - tokio::spawn(TaskRunner::run(scheduler.clone())); + let scheduler = Arc::new(RwLock::new(scheduler)); + tokio::spawn(TaskRunner::run(scheduler.clone())); - Self { scheduler } + Self { scheduler } + } + + pub async fn next_task_id(&self) -> TaskId { + self.scheduler.read().await.next_task_id() + } + + pub async fn run_scripts(&self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; } + } - pub async fn next_task_id(&self) -> TaskId { - self.scheduler.read().await.next_task_id() - } - - pub async fn run_scripts(&self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; + pub async fn run_script(&self, script: SearchScript) { + match script { + SearchScript::AddTask { task } => { + self.scheduler.write().await.add_task(task); + }, + SearchScript::CancelTask { task_id } => { + self.scheduler.write().await.cancel_task(task_id); + }, + SearchScript::AddTasks { tasks } => { + let mut scheduler = self.scheduler.write().await; + for task in tasks { + scheduler.add_task(task); } - } - - pub async fn run_script(&self, script: SearchScript) { - match script { - SearchScript::AddTask { task } => { - self.scheduler.write().await.add_task(task); - } - SearchScript::CancelTask { task_id } => { - self.scheduler.write().await.cancel_task(task_id); - } - SearchScript::AddTasks { tasks } => { - let mut scheduler = self.scheduler.write().await; - for task in tasks { - scheduler.add_task(task); - } - } - SearchScript::Wait { millisecond } => { - tokio::time::sleep(Duration::from_millis(millisecond)).await; - } - SearchScript::UnregisterHandler { handler_id } => { - self.scheduler.write().await.unregister_handler(handler_id).await; - } - SearchScript::AssertTaskStatus { - task_id, - expected_status, - } => { - let status = self.scheduler.read().await.read_task(&task_id).unwrap().state().clone(); - assert_eq!(status, expected_status); - } - SearchScript::AssertExecuteOrder { execute_order, rets } => { - let mut futures = FuturesUnordered::new(); - for ret in rets { - futures.push(ret); - } - let mut orders = vec![]; - while let Some(Ok(result)) = futures.next().await { - orders.push(result.id); - assert!(result.state.is_done()); - } - assert_eq!(execute_order, orders); - } + }, + SearchScript::Wait { millisecond } => { + tokio::time::sleep(Duration::from_millis(millisecond)).await; + }, + SearchScript::UnregisterHandler { handler_id } => { + self + .scheduler + .write() + .await + .unregister_handler(handler_id) + .await; + }, + SearchScript::AssertTaskStatus { + task_id, + expected_status, + } => { + let status = self + .scheduler + .read() + .await + .read_task(&task_id) + .unwrap() + .state() + .clone(); + assert_eq!(status, expected_status); + }, + SearchScript::AssertExecuteOrder { + execute_order, + rets, + } => { + let mut futures = FuturesUnordered::new(); + for ret in rets { + futures.push(ret); } + let mut orders = vec![]; + while let Some(Ok(result)) = futures.next().await { + orders.push(result.id); + assert!(result.state.is_done()); + } + assert_eq!(execute_order, orders); + }, } + } } pub struct MockTextTaskHandler(); #[async_trait] impl RefCountValue for MockTextTaskHandler { - async fn did_remove(&self) {} + async fn did_remove(&self) {} } impl TaskHandler for MockTextTaskHandler { - fn handler_id(&self) -> &str { - "1" - } + fn handler_id(&self) -> &str { + "1" + } - fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { - let mut rng = rand::thread_rng(); - let millisecond = rng.gen_range(1..50); - Box::pin(async move { - match content { - TaskContent::Text(_s) => { - tokio::time::sleep(Duration::from_millis(millisecond)).await; - } - TaskContent::Blob(_) => panic!("Only support text"), - } - Ok(()) - }) - } + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { + let mut rng = rand::thread_rng(); + let millisecond = rng.gen_range(1..50); + Box::pin(async move { + match content { + TaskContent::Text(_s) => { + tokio::time::sleep(Duration::from_millis(millisecond)).await; + }, + TaskContent::Blob(_) => panic!("Only support text"), + } + Ok(()) + }) + } } pub fn make_text_background_task(task_id: TaskId, s: &str) -> (Task, Receiver) { - let mut task = Task::background("1", task_id, TaskContent::Text(s.to_owned())); - let recv = task.recv.take().unwrap(); - (task, recv) + let mut task = Task::background("1", task_id, TaskContent::Text(s.to_owned())); + let recv = task.recv.take().unwrap(); + (task, recv) } pub fn make_text_user_interactive_task(task_id: TaskId, s: &str) -> (Task, Receiver) { - let mut task = Task::user_interactive("1", task_id, TaskContent::Text(s.to_owned())); - let recv = task.recv.take().unwrap(); - (task, recv) + let mut task = Task::user_interactive("1", task_id, TaskContent::Text(s.to_owned())); + let recv = task.recv.take().unwrap(); + (task, recv) } pub struct MockBlobTaskHandler(); #[async_trait] impl RefCountValue for MockBlobTaskHandler { - async fn did_remove(&self) {} + async fn did_remove(&self) {} } impl TaskHandler for MockBlobTaskHandler { - fn handler_id(&self) -> &str { - "2" - } + fn handler_id(&self) -> &str { + "2" + } - fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { - Box::pin(async move { - match content { - TaskContent::Text(_) => panic!("Only support blob"), - TaskContent::Blob(bytes) => { - let _msg = String::from_utf8(bytes).unwrap(); - tokio::time::sleep(Duration::from_millis(20)).await; - } - } - Ok(()) - }) - } + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { + Box::pin(async move { + match content { + TaskContent::Text(_) => panic!("Only support blob"), + TaskContent::Blob(bytes) => { + let _msg = String::from_utf8(bytes).unwrap(); + tokio::time::sleep(Duration::from_millis(20)).await; + }, + } + Ok(()) + }) + } } pub struct MockTimeoutTaskHandler(); impl TaskHandler for MockTimeoutTaskHandler { - fn handler_id(&self) -> &str { - "3" - } + fn handler_id(&self) -> &str { + "3" + } - fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { - Box::pin(async move { - match content { - TaskContent::Text(_) => panic!("Only support blob"), - TaskContent::Blob(_bytes) => { - tokio::time::sleep(Duration::from_millis(2000)).await; - } - } - Ok(()) - }) - } + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { + Box::pin(async move { + match content { + TaskContent::Text(_) => panic!("Only support blob"), + TaskContent::Blob(_bytes) => { + tokio::time::sleep(Duration::from_millis(2000)).await; + }, + } + Ok(()) + }) + } } pub fn make_timeout_task(task_id: TaskId) -> (Task, Receiver) { - let mut task = Task::background("3", task_id, TaskContent::Blob(vec![])); - let recv = task.recv.take().unwrap(); - (task, recv) + let mut task = Task::background("3", task_id, TaskContent::Blob(vec![])); + let recv = task.recv.take().unwrap(); + (task, recv) } diff --git a/frontend/rust-lib/flowy-task/tests/task_test/task_cancel_test.rs b/frontend/rust-lib/flowy-task/tests/task_test/task_cancel_test.rs index f4a9422d86..a86eb38949 100644 --- a/frontend/rust-lib/flowy-task/tests/task_test/task_cancel_test.rs +++ b/frontend/rust-lib/flowy-task/tests/task_test/task_cancel_test.rs @@ -4,85 +4,88 @@ use flowy_task::{QualityOfService, Task, TaskContent, TaskState}; #[tokio::test] async fn task_cancel_background_task_test() { - let test = SearchTest::new().await; - let (task_1, ret_1) = make_text_background_task(test.next_task_id().await, "Hello world"); - let (task_2, ret_2) = make_text_background_task(test.next_task_id().await, ""); - test.run_scripts(vec![ - AddTask { task: task_1 }, - AddTask { task: task_2 }, - AssertTaskStatus { - task_id: 1, - expected_status: TaskState::Pending, - }, - AssertTaskStatus { - task_id: 2, - expected_status: TaskState::Pending, - }, - CancelTask { task_id: 2 }, - AssertTaskStatus { - task_id: 2, - expected_status: TaskState::Cancel, - }, + let test = SearchTest::new().await; + let (task_1, ret_1) = make_text_background_task(test.next_task_id().await, "Hello world"); + let (task_2, ret_2) = make_text_background_task(test.next_task_id().await, ""); + test + .run_scripts(vec![ + AddTask { task: task_1 }, + AddTask { task: task_2 }, + AssertTaskStatus { + task_id: 1, + expected_status: TaskState::Pending, + }, + AssertTaskStatus { + task_id: 2, + expected_status: TaskState::Pending, + }, + CancelTask { task_id: 2 }, + AssertTaskStatus { + task_id: 2, + expected_status: TaskState::Cancel, + }, ]) .await; - let result = ret_1.await.unwrap(); - assert_eq!(result.state, TaskState::Done); + let result = ret_1.await.unwrap(); + assert_eq!(result.state, TaskState::Done); - let result = ret_2.await.unwrap(); - assert_eq!(result.state, TaskState::Cancel); + let result = ret_2.await.unwrap(); + assert_eq!(result.state, TaskState::Cancel); } #[tokio::test] async fn task_with_empty_handler_id_test() { - let test = SearchTest::new().await; - let mut task = Task::new( - "", - test.next_task_id().await, - TaskContent::Text("".to_owned()), - QualityOfService::Background, - ); - let ret = task.recv.take().unwrap(); - test.run_scripts(vec![AddTask { task }]).await; + let test = SearchTest::new().await; + let mut task = Task::new( + "", + test.next_task_id().await, + TaskContent::Text("".to_owned()), + QualityOfService::Background, + ); + let ret = task.recv.take().unwrap(); + test.run_scripts(vec![AddTask { task }]).await; - let result = ret.await.unwrap(); - assert_eq!(result.state, TaskState::Cancel); + let result = ret.await.unwrap(); + assert_eq!(result.state, TaskState::Cancel); } #[tokio::test] async fn task_can_not_find_handler_test() { - let test = SearchTest::new().await; - let (task, ret) = make_text_background_task(test.next_task_id().await, "Hello world"); - let handler_id = task.handler_id.clone(); - test.run_scripts(vec![UnregisterHandler { handler_id }, AddTask { task }]) - .await; + let test = SearchTest::new().await; + let (task, ret) = make_text_background_task(test.next_task_id().await, "Hello world"); + let handler_id = task.handler_id.clone(); + test + .run_scripts(vec![UnregisterHandler { handler_id }, AddTask { task }]) + .await; - let result = ret.await.unwrap(); - assert_eq!(result.state, TaskState::Cancel); + let result = ret.await.unwrap(); + assert_eq!(result.state, TaskState::Cancel); } #[tokio::test] async fn task_can_not_find_handler_test2() { - let test = SearchTest::new().await; - let mut tasks = vec![]; - let mut rets = vec![]; - let handler_id = "1".to_owned(); - for _i in 1..10000 { - let (task, ret) = make_text_background_task(test.next_task_id().await, ""); - tasks.push(task); - rets.push(ret); - } + let test = SearchTest::new().await; + let mut tasks = vec![]; + let mut rets = vec![]; + let handler_id = "1".to_owned(); + for _i in 1..10000 { + let (task, ret) = make_text_background_task(test.next_task_id().await, ""); + tasks.push(task); + rets.push(ret); + } - test.run_scripts(vec![UnregisterHandler { handler_id }, AddTasks { tasks }]) - .await; + test + .run_scripts(vec![UnregisterHandler { handler_id }, AddTasks { tasks }]) + .await; } #[tokio::test] async fn task_run_timeout_test() { - let test = SearchTest::new().await; - let (task, ret) = make_timeout_task(test.next_task_id().await); - test.run_scripts(vec![AddTask { task }]).await; + let test = SearchTest::new().await; + let (task, ret) = make_timeout_task(test.next_task_id().await); + test.run_scripts(vec![AddTask { task }]).await; - let result = ret.await.unwrap(); - assert_eq!(result.state, TaskState::Timeout); + let result = ret.await.unwrap(); + assert_eq!(result.state, TaskState::Timeout); } diff --git a/frontend/rust-lib/flowy-task/tests/task_test/task_order_test.rs b/frontend/rust-lib/flowy-task/tests/task_test/task_order_test.rs index a2c084a42c..54f66a727e 100644 --- a/frontend/rust-lib/flowy-task/tests/task_test/task_order_test.rs +++ b/frontend/rust-lib/flowy-task/tests/task_test/task_order_test.rs @@ -1,92 +1,96 @@ use crate::task_test::script::{ - make_text_background_task, make_text_user_interactive_task, SearchScript::*, SearchTest, + make_text_background_task, make_text_user_interactive_task, SearchScript::*, SearchTest, }; #[tokio::test] async fn task_add_single_background_task_test() { - let test = SearchTest::new().await; - let (task, ret) = make_text_background_task(test.next_task_id().await, ""); - test.run_scripts(vec![AddTask { task }]).await; + let test = SearchTest::new().await; + let (task, ret) = make_text_background_task(test.next_task_id().await, ""); + test.run_scripts(vec![AddTask { task }]).await; - let result = ret.await.unwrap(); - assert!(result.state.is_done()) + let result = ret.await.unwrap(); + assert!(result.state.is_done()) } #[tokio::test] async fn task_add_multiple_background_tasks_test() { - let test = SearchTest::new().await; - let (task_1, ret_1) = make_text_background_task(test.next_task_id().await, ""); - let (task_2, ret_2) = make_text_background_task(test.next_task_id().await, ""); - let (task_3, ret_3) = make_text_background_task(test.next_task_id().await, ""); - test.run_scripts(vec![ - AddTask { task: task_1 }, - AddTask { task: task_2 }, - AddTask { task: task_3 }, - AssertExecuteOrder { - execute_order: vec![3, 2, 1], - rets: vec![ret_1, ret_2, ret_3], - }, + let test = SearchTest::new().await; + let (task_1, ret_1) = make_text_background_task(test.next_task_id().await, ""); + let (task_2, ret_2) = make_text_background_task(test.next_task_id().await, ""); + let (task_3, ret_3) = make_text_background_task(test.next_task_id().await, ""); + test + .run_scripts(vec![ + AddTask { task: task_1 }, + AddTask { task: task_2 }, + AddTask { task: task_3 }, + AssertExecuteOrder { + execute_order: vec![3, 2, 1], + rets: vec![ret_1, ret_2, ret_3], + }, ]) .await; } #[tokio::test] async fn task_add_multiple_user_interactive_tasks_test() { - let test = SearchTest::new().await; - let (task_1, ret_1) = make_text_user_interactive_task(test.next_task_id().await, ""); - let (task_2, ret_2) = make_text_user_interactive_task(test.next_task_id().await, ""); - let (task_3, ret_3) = make_text_user_interactive_task(test.next_task_id().await, ""); - test.run_scripts(vec![ - AddTask { task: task_1 }, - AddTask { task: task_2 }, - AddTask { task: task_3 }, - AssertExecuteOrder { - execute_order: vec![3, 2, 1], - rets: vec![ret_1, ret_2, ret_3], - }, + let test = SearchTest::new().await; + let (task_1, ret_1) = make_text_user_interactive_task(test.next_task_id().await, ""); + let (task_2, ret_2) = make_text_user_interactive_task(test.next_task_id().await, ""); + let (task_3, ret_3) = make_text_user_interactive_task(test.next_task_id().await, ""); + test + .run_scripts(vec![ + AddTask { task: task_1 }, + AddTask { task: task_2 }, + AddTask { task: task_3 }, + AssertExecuteOrder { + execute_order: vec![3, 2, 1], + rets: vec![ret_1, ret_2, ret_3], + }, ]) .await; } #[tokio::test] async fn task_add_multiple_different_kind_tasks_test() { - let test = SearchTest::new().await; - let (task_1, ret_1) = make_text_background_task(test.next_task_id().await, ""); - let (task_2, ret_2) = make_text_user_interactive_task(test.next_task_id().await, ""); - let (task_3, ret_3) = make_text_background_task(test.next_task_id().await, ""); - test.run_scripts(vec![ - AddTask { task: task_1 }, - AddTask { task: task_2 }, - AddTask { task: task_3 }, - AssertExecuteOrder { - execute_order: vec![2, 3, 1], - rets: vec![ret_1, ret_2, ret_3], - }, + let test = SearchTest::new().await; + let (task_1, ret_1) = make_text_background_task(test.next_task_id().await, ""); + let (task_2, ret_2) = make_text_user_interactive_task(test.next_task_id().await, ""); + let (task_3, ret_3) = make_text_background_task(test.next_task_id().await, ""); + test + .run_scripts(vec![ + AddTask { task: task_1 }, + AddTask { task: task_2 }, + AddTask { task: task_3 }, + AssertExecuteOrder { + execute_order: vec![2, 3, 1], + rets: vec![ret_1, ret_2, ret_3], + }, ]) .await; } #[tokio::test] async fn task_add_multiple_different_kind_tasks_test2() { - let test = SearchTest::new().await; - let mut tasks = vec![]; - let mut rets = vec![]; + let test = SearchTest::new().await; + let mut tasks = vec![]; + let mut rets = vec![]; - for i in 0..10 { - let (task, ret) = if i % 2 == 0 { - make_text_background_task(test.next_task_id().await, "") - } else { - make_text_user_interactive_task(test.next_task_id().await, "") - }; - tasks.push(task); - rets.push(ret); - } + for i in 0..10 { + let (task, ret) = if i % 2 == 0 { + make_text_background_task(test.next_task_id().await, "") + } else { + make_text_user_interactive_task(test.next_task_id().await, "") + }; + tasks.push(task); + rets.push(ret); + } - test.run_scripts(vec![ - AddTasks { tasks }, - AssertExecuteOrder { - execute_order: vec![10, 8, 6, 4, 2, 9, 7, 5, 3, 1], - rets, - }, + test + .run_scripts(vec![ + AddTasks { tasks }, + AssertExecuteOrder { + execute_order: vec![10, 8, 6, 4, 2, 9, 7, 5, 3, 1], + rets, + }, ]) .await; } diff --git a/frontend/rust-lib/flowy-test/src/event_builder.rs b/frontend/rust-lib/flowy-test/src/event_builder.rs index f86e733a7e..f6083838a2 100644 --- a/frontend/rust-lib/flowy-test/src/event_builder.rs +++ b/frontend/rust-lib/flowy-test/src/event_builder.rs @@ -1,155 +1,159 @@ use crate::FlowySDKTest; use flowy_user::{entities::UserProfilePB, errors::FlowyError}; use lib_dispatch::prelude::{ - AFPluginDispatcher, AFPluginEventResponse, AFPluginFromBytes, AFPluginRequest, StatusCode, ToBytes, *, + AFPluginDispatcher, AFPluginEventResponse, AFPluginFromBytes, AFPluginRequest, StatusCode, + ToBytes, *, }; use std::{ - convert::TryFrom, - fmt::{Debug, Display}, - hash::Hash, - marker::PhantomData, - sync::Arc, + convert::TryFrom, + fmt::{Debug, Display}, + hash::Hash, + marker::PhantomData, + sync::Arc, }; pub type FolderEventBuilder = EventBuilder; impl FolderEventBuilder { - pub fn new(sdk: FlowySDKTest) -> Self { - EventBuilder::test(TestContext::new(sdk)) - } - pub fn user_profile(&self) -> &Option { - &self.user_profile - } + pub fn new(sdk: FlowySDKTest) -> Self { + EventBuilder::test(TestContext::new(sdk)) + } + pub fn user_profile(&self) -> &Option { + &self.user_profile + } } pub type UserModuleEventBuilder = FolderEventBuilder; #[derive(Clone)] pub struct EventBuilder { - context: TestContext, - user_profile: Option, - err_phantom: PhantomData, + context: TestContext, + user_profile: Option, + err_phantom: PhantomData, } impl EventBuilder where - E: AFPluginFromBytes + Debug, + E: AFPluginFromBytes + Debug, { - fn test(context: TestContext) -> Self { - Self { - context, - user_profile: None, - err_phantom: PhantomData, - } + fn test(context: TestContext) -> Self { + Self { + context, + user_profile: None, + err_phantom: PhantomData, } + } - pub fn payload

(mut self, payload: P) -> Self - where - P: ToBytes, - { - match payload.into_bytes() { - Ok(bytes) => { - let module_request = self.get_request(); - self.context.request = Some(module_request.payload(bytes)) - } - Err(e) => { - log::error!("Set payload failed: {:?}", e); - } - } - self + pub fn payload

(mut self, payload: P) -> Self + where + P: ToBytes, + { + match payload.into_bytes() { + Ok(bytes) => { + let module_request = self.get_request(); + self.context.request = Some(module_request.payload(bytes)) + }, + Err(e) => { + log::error!("Set payload failed: {:?}", e); + }, } + self + } - pub fn event(mut self, event: Event) -> Self - where - Event: Eq + Hash + Debug + Clone + Display, - { - self.context.request = Some(AFPluginRequest::new(event)); - self - } + pub fn event(mut self, event: Event) -> Self + where + Event: Eq + Hash + Debug + Clone + Display, + { + self.context.request = Some(AFPluginRequest::new(event)); + self + } - pub fn sync_send(mut self) -> Self { - let request = self.get_request(); - let resp = AFPluginDispatcher::sync_send(self.dispatch(), request); - self.context.response = Some(resp); - self - } + pub fn sync_send(mut self) -> Self { + let request = self.get_request(); + let resp = AFPluginDispatcher::sync_send(self.dispatch(), request); + self.context.response = Some(resp); + self + } - pub async fn async_send(mut self) -> Self { - let request = self.get_request(); - let resp = AFPluginDispatcher::async_send(self.dispatch(), request).await; - self.context.response = Some(resp); - self - } + pub async fn async_send(mut self) -> Self { + let request = self.get_request(); + let resp = AFPluginDispatcher::async_send(self.dispatch(), request).await; + self.context.response = Some(resp); + self + } - pub fn parse(self) -> R - where - R: AFPluginFromBytes, - { - let response = self.get_response(); - match response.clone().parse::() { - Ok(Ok(data)) => data, - Ok(Err(e)) => { - panic!( - "Parser {:?} failed: {:?}, response {:?}", - std::any::type_name::(), - e, - response - ) - } - Err(e) => panic!( - "Dispatch {:?} failed: {:?}, response {:?}", - std::any::type_name::(), - e, - response - ), - } + pub fn parse(self) -> R + where + R: AFPluginFromBytes, + { + let response = self.get_response(); + match response.clone().parse::() { + Ok(Ok(data)) => data, + Ok(Err(e)) => { + panic!( + "Parser {:?} failed: {:?}, response {:?}", + std::any::type_name::(), + e, + response + ) + }, + Err(e) => panic!( + "Dispatch {:?} failed: {:?}, response {:?}", + std::any::type_name::(), + e, + response + ), } + } - pub fn error(self) -> E { - let response = self.get_response(); - assert_eq!(response.status_code, StatusCode::Err); - >::try_from(response.payload).unwrap().into_inner() - } + pub fn error(self) -> E { + let response = self.get_response(); + assert_eq!(response.status_code, StatusCode::Err); + >::try_from(response.payload) + .unwrap() + .into_inner() + } - pub fn assert_error(self) -> Self { - // self.context.assert_error(); - self - } + pub fn assert_error(self) -> Self { + // self.context.assert_error(); + self + } - pub fn assert_success(self) -> Self { - // self.context.assert_success(); - self - } + pub fn assert_success(self) -> Self { + // self.context.assert_success(); + self + } - fn dispatch(&self) -> Arc { - self.context.sdk.dispatcher() - } + fn dispatch(&self) -> Arc { + self.context.sdk.dispatcher() + } - fn get_response(&self) -> AFPluginEventResponse { - self.context - .response - .as_ref() - .expect("must call sync_send first") - .clone() - } + fn get_response(&self) -> AFPluginEventResponse { + self + .context + .response + .as_ref() + .expect("must call sync_send first") + .clone() + } - fn get_request(&mut self) -> AFPluginRequest { - self.context.request.take().expect("must call event first") - } + fn get_request(&mut self) -> AFPluginRequest { + self.context.request.take().expect("must call event first") + } } #[derive(Clone)] pub struct TestContext { - pub sdk: FlowySDKTest, - request: Option, - response: Option, + pub sdk: FlowySDKTest, + request: Option, + response: Option, } impl TestContext { - pub fn new(sdk: FlowySDKTest) -> Self { - Self { - sdk, - request: None, - response: None, - } + pub fn new(sdk: FlowySDKTest) -> Self { + Self { + sdk, + request: None, + response: None, } + } } diff --git a/frontend/rust-lib/flowy-test/src/helper.rs b/frontend/rust-lib/flowy-test/src/helper.rs index be94162d54..3b426450ae 100644 --- a/frontend/rust-lib/flowy-test/src/helper.rs +++ b/frontend/rust-lib/flowy-test/src/helper.rs @@ -1,231 +1,255 @@ use crate::prelude::*; use flowy_folder::entities::WorkspaceIdPB; use flowy_folder::{ - entities::{ - app::*, - view::*, - workspace::{CreateWorkspacePayloadPB, WorkspacePB}, - }, - event_map::FolderEvent::{CreateWorkspace, OpenWorkspace, *}, + entities::{ + app::*, + view::*, + workspace::{CreateWorkspacePayloadPB, WorkspacePB}, + }, + event_map::FolderEvent::{CreateWorkspace, OpenWorkspace, *}, }; use flowy_user::{ - entities::{SignInPayloadPB, SignUpPayloadPB, UserProfilePB}, - errors::FlowyError, - event_map::UserEvent::{InitUser, SignIn, SignOut, SignUp}, + entities::{SignInPayloadPB, SignUpPayloadPB, UserProfilePB}, + errors::FlowyError, + event_map::UserEvent::{InitUser, SignIn, SignOut, SignUp}, }; use lib_dispatch::prelude::{AFPluginDispatcher, AFPluginRequest, ToBytes}; use std::{fs, path::PathBuf, sync::Arc}; pub struct ViewTest { - pub sdk: FlowySDKTest, - pub workspace: WorkspacePB, - pub app: AppPB, - pub view: ViewPB, + pub sdk: FlowySDKTest, + pub workspace: WorkspacePB, + pub app: AppPB, + pub view: ViewPB, } impl ViewTest { - #[allow(dead_code)] - pub async fn new( - sdk: &FlowySDKTest, - data_format: ViewDataFormatPB, - layout: ViewLayoutTypePB, - data: Vec, - ) -> Self { - let workspace = create_workspace(sdk, "Workspace", "").await; - open_workspace(sdk, &workspace.id).await; - let app = create_app(sdk, "App", "AppFlowy GitHub Project", &workspace.id).await; - let view = create_view(sdk, &app.id, data_format, layout, data).await; - Self { - sdk: sdk.clone(), - workspace, - app, - view, - } - } - - pub async fn new_grid_view(sdk: &FlowySDKTest, data: Vec) -> Self { - Self::new(sdk, ViewDataFormatPB::DatabaseFormat, ViewLayoutTypePB::Grid, data).await - } - - pub async fn new_board_view(sdk: &FlowySDKTest, data: Vec) -> Self { - Self::new(sdk, ViewDataFormatPB::DatabaseFormat, ViewLayoutTypePB::Board, data).await - } - - pub async fn new_calendar_view(sdk: &FlowySDKTest, data: Vec) -> Self { - Self::new(sdk, ViewDataFormatPB::DatabaseFormat, ViewLayoutTypePB::Calendar, data).await - } - - pub async fn new_document_view(sdk: &FlowySDKTest) -> Self { - let view_data_format = match sdk.document_version() { - DocumentVersionPB::V0 => ViewDataFormatPB::DeltaFormat, - DocumentVersionPB::V1 => ViewDataFormatPB::NodeFormat, - }; - Self::new(sdk, view_data_format, ViewLayoutTypePB::Document, vec![]).await - } -} - -async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> WorkspacePB { - let request = CreateWorkspacePayloadPB { - name: name.to_owned(), - desc: desc.to_owned(), - }; - - FolderEventBuilder::new(sdk.clone()) - .event(CreateWorkspace) - .payload(request) - .async_send() - .await - .parse::() -} - -async fn open_workspace(sdk: &FlowySDKTest, workspace_id: &str) { - let payload = WorkspaceIdPB { - value: Some(workspace_id.to_owned()), - }; - let _ = FolderEventBuilder::new(sdk.clone()) - .event(OpenWorkspace) - .payload(payload) - .async_send() - .await; -} - -async fn create_app(sdk: &FlowySDKTest, name: &str, desc: &str, workspace_id: &str) -> AppPB { - let create_app_request = CreateAppPayloadPB { - workspace_id: workspace_id.to_owned(), - name: name.to_string(), - desc: desc.to_string(), - color_style: Default::default(), - }; - - FolderEventBuilder::new(sdk.clone()) - .event(CreateApp) - .payload(create_app_request) - .async_send() - .await - .parse::() -} - -async fn create_view( + #[allow(dead_code)] + pub async fn new( sdk: &FlowySDKTest, - app_id: &str, data_format: ViewDataFormatPB, layout: ViewLayoutTypePB, data: Vec, -) -> ViewPB { - let request = CreateViewPayloadPB { - belong_to_id: app_id.to_string(), - name: "View A".to_string(), - desc: "".to_string(), - thumbnail: Some("http://1.png".to_string()), - data_format, - layout, - initial_data: data, - }; + ) -> Self { + let workspace = create_workspace(sdk, "Workspace", "").await; + open_workspace(sdk, &workspace.id).await; + let app = create_app(sdk, "App", "AppFlowy GitHub Project", &workspace.id).await; + let view = create_view(sdk, &app.id, data_format, layout, data).await; + Self { + sdk: sdk.clone(), + workspace, + app, + view, + } + } - FolderEventBuilder::new(sdk.clone()) - .event(CreateView) - .payload(request) - .async_send() - .await - .parse::() + pub async fn new_grid_view(sdk: &FlowySDKTest, data: Vec) -> Self { + Self::new( + sdk, + ViewDataFormatPB::DatabaseFormat, + ViewLayoutTypePB::Grid, + data, + ) + .await + } + + pub async fn new_board_view(sdk: &FlowySDKTest, data: Vec) -> Self { + Self::new( + sdk, + ViewDataFormatPB::DatabaseFormat, + ViewLayoutTypePB::Board, + data, + ) + .await + } + + pub async fn new_calendar_view(sdk: &FlowySDKTest, data: Vec) -> Self { + Self::new( + sdk, + ViewDataFormatPB::DatabaseFormat, + ViewLayoutTypePB::Calendar, + data, + ) + .await + } + + pub async fn new_document_view(sdk: &FlowySDKTest) -> Self { + let view_data_format = match sdk.document_version() { + DocumentVersionPB::V0 => ViewDataFormatPB::DeltaFormat, + DocumentVersionPB::V1 => ViewDataFormatPB::NodeFormat, + }; + Self::new(sdk, view_data_format, ViewLayoutTypePB::Document, vec![]).await + } +} + +async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> WorkspacePB { + let request = CreateWorkspacePayloadPB { + name: name.to_owned(), + desc: desc.to_owned(), + }; + + FolderEventBuilder::new(sdk.clone()) + .event(CreateWorkspace) + .payload(request) + .async_send() + .await + .parse::() +} + +async fn open_workspace(sdk: &FlowySDKTest, workspace_id: &str) { + let payload = WorkspaceIdPB { + value: Some(workspace_id.to_owned()), + }; + let _ = FolderEventBuilder::new(sdk.clone()) + .event(OpenWorkspace) + .payload(payload) + .async_send() + .await; +} + +async fn create_app(sdk: &FlowySDKTest, name: &str, desc: &str, workspace_id: &str) -> AppPB { + let create_app_request = CreateAppPayloadPB { + workspace_id: workspace_id.to_owned(), + name: name.to_string(), + desc: desc.to_string(), + color_style: Default::default(), + }; + + FolderEventBuilder::new(sdk.clone()) + .event(CreateApp) + .payload(create_app_request) + .async_send() + .await + .parse::() +} + +async fn create_view( + sdk: &FlowySDKTest, + app_id: &str, + data_format: ViewDataFormatPB, + layout: ViewLayoutTypePB, + data: Vec, +) -> ViewPB { + let request = CreateViewPayloadPB { + belong_to_id: app_id.to_string(), + name: "View A".to_string(), + desc: "".to_string(), + thumbnail: Some("http://1.png".to_string()), + data_format, + layout, + initial_data: data, + }; + + FolderEventBuilder::new(sdk.clone()) + .event(CreateView) + .payload(request) + .async_send() + .await + .parse::() } pub fn root_dir() -> String { - // https://doc.rust-lang.org/cargo/reference/environment-variables.html - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| "./".to_owned()); - let mut path_buf = fs::canonicalize(&PathBuf::from(&manifest_dir)).unwrap(); - path_buf.pop(); // rust-lib - path_buf.push("temp"); - path_buf.push("flowy"); + // https://doc.rust-lang.org/cargo/reference/environment-variables.html + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| "./".to_owned()); + let mut path_buf = fs::canonicalize(&PathBuf::from(&manifest_dir)).unwrap(); + path_buf.pop(); // rust-lib + path_buf.push("temp"); + path_buf.push("flowy"); - let root_dir = path_buf.to_str().unwrap().to_string(); - if !std::path::Path::new(&root_dir).exists() { - std::fs::create_dir_all(&root_dir).unwrap(); - } - root_dir + let root_dir = path_buf.to_str().unwrap().to_string(); + if !std::path::Path::new(&root_dir).exists() { + std::fs::create_dir_all(&root_dir).unwrap(); + } + root_dir } pub fn random_email() -> String { - format!("{}@appflowy.io", nanoid!(20)) + format!("{}@appflowy.io", nanoid!(20)) } pub fn login_email() -> String { - "annie2@appflowy.io".to_string() + "annie2@appflowy.io".to_string() } pub fn login_password() -> String { - "HelloWorld!123".to_string() + "HelloWorld!123".to_string() } pub struct SignUpContext { - pub user_profile: UserProfilePB, - pub password: String, + pub user_profile: UserProfilePB, + pub password: String, } pub fn sign_up(dispatch: Arc) -> SignUpContext { - let password = login_password(); - let payload = SignUpPayloadPB { - email: random_email(), - name: "app flowy".to_string(), - password: password.clone(), - } - .into_bytes() + let password = login_password(); + let payload = SignUpPayloadPB { + email: random_email(), + name: "app flowy".to_string(), + password: password.clone(), + } + .into_bytes() + .unwrap(); + + let request = AFPluginRequest::new(SignUp).payload(payload); + let user_profile = AFPluginDispatcher::sync_send(dispatch, request) + .parse::() + .unwrap() .unwrap(); - let request = AFPluginRequest::new(SignUp).payload(payload); - let user_profile = AFPluginDispatcher::sync_send(dispatch, request) - .parse::() - .unwrap() - .unwrap(); - - SignUpContext { user_profile, password } + SignUpContext { + user_profile, + password, + } } pub async fn async_sign_up(dispatch: Arc) -> SignUpContext { - let password = login_password(); - let email = random_email(); - let payload = SignUpPayloadPB { - email, - name: "app flowy".to_string(), - password: password.clone(), - } - .into_bytes() + let password = login_password(); + let email = random_email(); + let payload = SignUpPayloadPB { + email, + name: "app flowy".to_string(), + password: password.clone(), + } + .into_bytes() + .unwrap(); + + let request = AFPluginRequest::new(SignUp).payload(payload); + let user_profile = AFPluginDispatcher::async_send(dispatch.clone(), request) + .await + .parse::() + .unwrap() .unwrap(); - let request = AFPluginRequest::new(SignUp).payload(payload); - let user_profile = AFPluginDispatcher::async_send(dispatch.clone(), request) - .await - .parse::() - .unwrap() - .unwrap(); - - // let _ = create_default_workspace_if_need(dispatch.clone(), &user_profile.id); - SignUpContext { user_profile, password } + // let _ = create_default_workspace_if_need(dispatch.clone(), &user_profile.id); + SignUpContext { + user_profile, + password, + } } pub async fn init_user_setting(dispatch: Arc) { - let request = AFPluginRequest::new(InitUser); - let _ = AFPluginDispatcher::async_send(dispatch.clone(), request).await; + let request = AFPluginRequest::new(InitUser); + let _ = AFPluginDispatcher::async_send(dispatch.clone(), request).await; } #[allow(dead_code)] fn sign_in(dispatch: Arc) -> UserProfilePB { - let payload = SignInPayloadPB { - email: login_email(), - password: login_password(), - name: "rust".to_owned(), - } - .into_bytes() - .unwrap(); + let payload = SignInPayloadPB { + email: login_email(), + password: login_password(), + name: "rust".to_owned(), + } + .into_bytes() + .unwrap(); - let request = AFPluginRequest::new(SignIn).payload(payload); - AFPluginDispatcher::sync_send(dispatch, request) - .parse::() - .unwrap() - .unwrap() + let request = AFPluginRequest::new(SignIn).payload(payload); + AFPluginDispatcher::sync_send(dispatch, request) + .parse::() + .unwrap() + .unwrap() } #[allow(dead_code)] fn logout(dispatch: Arc) { - let _ = AFPluginDispatcher::sync_send(dispatch, AFPluginRequest::new(SignOut)); + let _ = AFPluginDispatcher::sync_send(dispatch, AFPluginRequest::new(SignOut)); } diff --git a/frontend/rust-lib/flowy-test/src/lib.rs b/frontend/rust-lib/flowy-test/src/lib.rs index c85544dfd2..29e8a1a2b9 100644 --- a/frontend/rust-lib/flowy-test/src/lib.rs +++ b/frontend/rust-lib/flowy-test/src/lib.rs @@ -10,51 +10,53 @@ use flowy_user::entities::UserProfilePB; use nanoid::nanoid; pub mod prelude { - pub use crate::{event_builder::*, helper::*, *}; - pub use lib_dispatch::prelude::*; + pub use crate::{event_builder::*, helper::*, *}; + pub use lib_dispatch::prelude::*; } #[derive(Clone)] pub struct FlowySDKTest { - pub inner: AppFlowyCore, + pub inner: AppFlowyCore, } impl std::ops::Deref for FlowySDKTest { - type Target = AppFlowyCore; + type Target = AppFlowyCore; - fn deref(&self) -> &Self::Target { - &self.inner - } + fn deref(&self) -> &Self::Target { + &self.inner + } } impl std::default::Default for FlowySDKTest { - fn default() -> Self { - Self::new(DocumentVersionPB::V0) - } + fn default() -> Self { + Self::new(DocumentVersionPB::V0) + } } impl FlowySDKTest { - pub fn new(document_version: DocumentVersionPB) -> Self { - let server_config = get_client_server_configuration().unwrap(); - let config = AppFlowyCoreConfig::new(&root_dir(), nanoid!(6), server_config) - .with_document_version(document_version) - .log_filter("info", vec![]); - let sdk = std::thread::spawn(|| AppFlowyCore::new(config)).join().unwrap(); - std::mem::forget(sdk.dispatcher()); - Self { inner: sdk } - } + pub fn new(document_version: DocumentVersionPB) -> Self { + let server_config = get_client_server_configuration().unwrap(); + let config = AppFlowyCoreConfig::new(&root_dir(), nanoid!(6), server_config) + .with_document_version(document_version) + .log_filter("info", vec![]); + let sdk = std::thread::spawn(|| AppFlowyCore::new(config)) + .join() + .unwrap(); + std::mem::forget(sdk.dispatcher()); + Self { inner: sdk } + } - pub async fn sign_up(&self) -> SignUpContext { - async_sign_up(self.inner.dispatcher()).await - } + pub async fn sign_up(&self) -> SignUpContext { + async_sign_up(self.inner.dispatcher()).await + } - pub async fn init_user(&self) -> UserProfilePB { - let context = async_sign_up(self.inner.dispatcher()).await; - init_user_setting(self.inner.dispatcher()).await; - context.user_profile - } + pub async fn init_user(&self) -> UserProfilePB { + let context = async_sign_up(self.inner.dispatcher()).await; + init_user_setting(self.inner.dispatcher()).await; + context.user_profile + } - pub fn document_version(&self) -> DocumentVersionPB { - self.inner.config.document.version.clone() - } + pub fn document_version(&self) -> DocumentVersionPB { + self.inner.config.document.version.clone() + } } diff --git a/frontend/rust-lib/flowy-user/build.rs b/frontend/rust-lib/flowy-user/build.rs index 508b370b87..06388d2a02 100644 --- a/frontend/rust-lib/flowy-user/build.rs +++ b/frontend/rust-lib/flowy-user/build.rs @@ -1,10 +1,10 @@ fn main() { - let crate_name = env!("CARGO_PKG_NAME"); - flowy_codegen::protobuf_file::gen(crate_name); + let crate_name = env!("CARGO_PKG_NAME"); + flowy_codegen::protobuf_file::gen(crate_name); - #[cfg(feature = "dart")] - flowy_codegen::dart_event::gen(crate_name); + #[cfg(feature = "dart")] + flowy_codegen::dart_event::gen(crate_name); - #[cfg(feature = "ts")] - flowy_codegen::ts_event::gen(crate_name); + #[cfg(feature = "ts")] + flowy_codegen::ts_event::gen(crate_name); } diff --git a/frontend/rust-lib/flowy-user/src/entities/auth.rs b/frontend/rust-lib/flowy-user/src/entities/auth.rs index 1ead6b6410..1478676c83 100644 --- a/frontend/rust-lib/flowy-user/src/entities/auth.rs +++ b/frontend/rust-lib/flowy-user/src/entities/auth.rs @@ -5,54 +5,54 @@ use user_model::{SignInParams, SignUpParams, UserEmail, UserName, UserPassword}; #[derive(ProtoBuf, Default)] pub struct SignInPayloadPB { - #[pb(index = 1)] - pub email: String, + #[pb(index = 1)] + pub email: String, - #[pb(index = 2)] - pub password: String, + #[pb(index = 2)] + pub password: String, - #[pb(index = 3)] - pub name: String, + #[pb(index = 3)] + pub name: String, } impl TryInto for SignInPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let email = UserEmail::parse(self.email)?; - let password = UserPassword::parse(self.password)?; + fn try_into(self) -> Result { + let email = UserEmail::parse(self.email)?; + let password = UserPassword::parse(self.password)?; - Ok(SignInParams { - email: email.0, - password: password.0, - name: self.name, - }) - } + Ok(SignInParams { + email: email.0, + password: password.0, + name: self.name, + }) + } } #[derive(ProtoBuf, Default)] pub struct SignUpPayloadPB { - #[pb(index = 1)] - pub email: String, + #[pb(index = 1)] + pub email: String, - #[pb(index = 2)] - pub name: String, + #[pb(index = 2)] + pub name: String, - #[pb(index = 3)] - pub password: String, + #[pb(index = 3)] + pub password: String, } impl TryInto for SignUpPayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let email = UserEmail::parse(self.email)?; - let password = UserPassword::parse(self.password)?; - let name = UserName::parse(self.name)?; + fn try_into(self) -> Result { + let email = UserEmail::parse(self.email)?; + let password = UserPassword::parse(self.password)?; + let name = UserName::parse(self.name)?; - Ok(SignUpParams { - email: email.0, - name: name.0, - password: password.0, - }) - } + Ok(SignUpParams { + email: email.0, + name: name.0, + password: password.0, + }) + } } diff --git a/frontend/rust-lib/flowy-user/src/entities/user_profile.rs b/frontend/rust-lib/flowy-user/src/entities/user_profile.rs index ba5acbf4a4..2ef29beb13 100644 --- a/frontend/rust-lib/flowy-user/src/entities/user_profile.rs +++ b/frontend/rust-lib/flowy-user/src/entities/user_profile.rs @@ -1,129 +1,131 @@ use crate::errors::ErrorCode; use flowy_derive::ProtoBuf; use std::convert::TryInto; -use user_model::{UpdateUserProfileParams, UserEmail, UserIcon, UserId, UserName, UserPassword, UserProfile}; +use user_model::{ + UpdateUserProfileParams, UserEmail, UserIcon, UserId, UserName, UserPassword, UserProfile, +}; #[derive(Default, ProtoBuf)] pub struct UserTokenPB { - #[pb(index = 1)] - pub token: String, + #[pb(index = 1)] + pub token: String, } #[derive(ProtoBuf, Default, Clone)] pub struct UserSettingPB { - #[pb(index = 1)] - pub(crate) user_folder: String, + #[pb(index = 1)] + pub(crate) user_folder: String, } #[derive(ProtoBuf, Default, Debug, PartialEq, Eq, Clone)] pub struct UserProfilePB { - #[pb(index = 1)] - pub id: String, + #[pb(index = 1)] + pub id: String, - #[pb(index = 2)] - pub email: String, + #[pb(index = 2)] + pub email: String, - #[pb(index = 3)] - pub name: String, + #[pb(index = 3)] + pub name: String, - #[pb(index = 4)] - pub token: String, + #[pb(index = 4)] + pub token: String, - #[pb(index = 5)] - pub icon_url: String, + #[pb(index = 5)] + pub icon_url: String, } impl std::convert::From for UserProfilePB { - fn from(user_profile: UserProfile) -> Self { - Self { - id: user_profile.id, - email: user_profile.email, - name: user_profile.name, - token: user_profile.token, - icon_url: user_profile.icon_url, - } + fn from(user_profile: UserProfile) -> Self { + Self { + id: user_profile.id, + email: user_profile.email, + name: user_profile.name, + token: user_profile.token, + icon_url: user_profile.icon_url, } + } } #[derive(ProtoBuf, Default)] pub struct UpdateUserProfilePayloadPB { - #[pb(index = 1)] - pub id: String, + #[pb(index = 1)] + pub id: String, - #[pb(index = 2, one_of)] - pub name: Option, + #[pb(index = 2, one_of)] + pub name: Option, - #[pb(index = 3, one_of)] - pub email: Option, + #[pb(index = 3, one_of)] + pub email: Option, - #[pb(index = 4, one_of)] - pub password: Option, + #[pb(index = 4, one_of)] + pub password: Option, - #[pb(index = 5, one_of)] - pub icon_url: Option, + #[pb(index = 5, one_of)] + pub icon_url: Option, } impl UpdateUserProfilePayloadPB { - pub fn new(id: &str) -> Self { - Self { - id: id.to_owned(), - ..Default::default() - } + pub fn new(id: &str) -> Self { + Self { + id: id.to_owned(), + ..Default::default() } + } - pub fn name(mut self, name: &str) -> Self { - self.name = Some(name.to_owned()); - self - } + pub fn name(mut self, name: &str) -> Self { + self.name = Some(name.to_owned()); + self + } - pub fn email(mut self, email: &str) -> Self { - self.email = Some(email.to_owned()); - self - } + pub fn email(mut self, email: &str) -> Self { + self.email = Some(email.to_owned()); + self + } - pub fn password(mut self, password: &str) -> Self { - self.password = Some(password.to_owned()); - self - } + pub fn password(mut self, password: &str) -> Self { + self.password = Some(password.to_owned()); + self + } - pub fn icon_url(mut self, icon_url: &str) -> Self { - self.icon_url = Some(icon_url.to_owned()); - self - } + pub fn icon_url(mut self, icon_url: &str) -> Self { + self.icon_url = Some(icon_url.to_owned()); + self + } } impl TryInto for UpdateUserProfilePayloadPB { - type Error = ErrorCode; + type Error = ErrorCode; - fn try_into(self) -> Result { - let id = UserId::parse(self.id)?.0; + fn try_into(self) -> Result { + let id = UserId::parse(self.id)?.0; - let name = match self.name { - None => None, - Some(name) => Some(UserName::parse(name)?.0), - }; + let name = match self.name { + None => None, + Some(name) => Some(UserName::parse(name)?.0), + }; - let email = match self.email { - None => None, - Some(email) => Some(UserEmail::parse(email)?.0), - }; + let email = match self.email { + None => None, + Some(email) => Some(UserEmail::parse(email)?.0), + }; - let password = match self.password { - None => None, - Some(password) => Some(UserPassword::parse(password)?.0), - }; + let password = match self.password { + None => None, + Some(password) => Some(UserPassword::parse(password)?.0), + }; - let icon_url = match self.icon_url { - None => None, - Some(icon_url) => Some(UserIcon::parse(icon_url)?.0), - }; + let icon_url = match self.icon_url { + None => None, + Some(icon_url) => Some(UserIcon::parse(icon_url)?.0), + }; - Ok(UpdateUserProfileParams { - id, - name, - email, - password, - icon_url, - }) - } + Ok(UpdateUserProfileParams { + id, + name, + email, + password, + icon_url, + }) + } } diff --git a/frontend/rust-lib/flowy-user/src/entities/user_setting.rs b/frontend/rust-lib/flowy-user/src/entities/user_setting.rs index 87c737b935..665a465533 100644 --- a/frontend/rust-lib/flowy-user/src/entities/user_setting.rs +++ b/frontend/rust-lib/flowy-user/src/entities/user_setting.rs @@ -4,80 +4,80 @@ use std::collections::HashMap; #[derive(ProtoBuf, Default, Debug, Clone)] pub struct UserPreferencesPB { - #[pb(index = 1)] - user_id: String, + #[pb(index = 1)] + user_id: String, - #[pb(index = 2)] - appearance_setting: AppearanceSettingsPB, + #[pb(index = 2)] + appearance_setting: AppearanceSettingsPB, } #[derive(ProtoBuf, Serialize, Deserialize, Debug, Clone)] pub struct AppearanceSettingsPB { - #[pb(index = 1)] - pub theme: String, + #[pb(index = 1)] + pub theme: String, - #[pb(index = 2)] - #[serde(default)] - pub theme_mode: ThemeModePB, + #[pb(index = 2)] + #[serde(default)] + pub theme_mode: ThemeModePB, - #[pb(index = 3)] - pub font: String, + #[pb(index = 3)] + pub font: String, - #[pb(index = 4)] - pub monospace_font: String, + #[pb(index = 4)] + pub monospace_font: String, - #[pb(index = 5)] - #[serde(default)] - pub locale: LocaleSettingsPB, + #[pb(index = 5)] + #[serde(default)] + pub locale: LocaleSettingsPB, - #[pb(index = 6)] - #[serde(default = "DEFAULT_RESET_VALUE")] - pub reset_to_default: bool, + #[pb(index = 6)] + #[serde(default = "DEFAULT_RESET_VALUE")] + pub reset_to_default: bool, - #[pb(index = 7)] - #[serde(default)] - pub setting_key_value: HashMap, + #[pb(index = 7)] + #[serde(default)] + pub setting_key_value: HashMap, - #[pb(index = 8)] - #[serde(default)] - pub is_menu_collapsed: bool, + #[pb(index = 8)] + #[serde(default)] + pub is_menu_collapsed: bool, - #[pb(index = 9)] - #[serde(default)] - pub menu_offset: f64, + #[pb(index = 9)] + #[serde(default)] + pub menu_offset: f64, } const DEFAULT_RESET_VALUE: fn() -> bool = || APPEARANCE_RESET_AS_DEFAULT; #[derive(ProtoBuf_Enum, Serialize, Deserialize, Clone, Debug)] pub enum ThemeModePB { - Light = 0, - Dark = 1, - System = 2, + Light = 0, + Dark = 1, + System = 2, } impl std::default::Default for ThemeModePB { - fn default() -> Self { - ThemeModePB::System - } + fn default() -> Self { + ThemeModePB::System + } } #[derive(ProtoBuf, Serialize, Deserialize, Debug, Clone)] pub struct LocaleSettingsPB { - #[pb(index = 1)] - pub language_code: String, + #[pb(index = 1)] + pub language_code: String, - #[pb(index = 2)] - pub country_code: String, + #[pb(index = 2)] + pub country_code: String, } impl std::default::Default for LocaleSettingsPB { - fn default() -> Self { - Self { - language_code: "en".to_owned(), - country_code: "".to_owned(), - } + fn default() -> Self { + Self { + language_code: "en".to_owned(), + country_code: "".to_owned(), } + } } pub const APPEARANCE_DEFAULT_THEME: &str = "light"; @@ -88,17 +88,17 @@ const APPEARANCE_DEFAULT_IS_MENU_COLLAPSED: bool = false; const APPEARANCE_DEFAULT_MENU_OFFSET: f64 = 0.0; impl std::default::Default for AppearanceSettingsPB { - fn default() -> Self { - AppearanceSettingsPB { - theme: APPEARANCE_DEFAULT_THEME.to_owned(), - theme_mode: ThemeModePB::default(), - font: APPEARANCE_DEFAULT_FONT.to_owned(), - monospace_font: APPEARANCE_DEFAULT_MONOSPACE_FONT.to_owned(), - locale: LocaleSettingsPB::default(), - reset_to_default: APPEARANCE_RESET_AS_DEFAULT, - setting_key_value: HashMap::default(), - is_menu_collapsed: APPEARANCE_DEFAULT_IS_MENU_COLLAPSED, - menu_offset: APPEARANCE_DEFAULT_MENU_OFFSET, - } + fn default() -> Self { + AppearanceSettingsPB { + theme: APPEARANCE_DEFAULT_THEME.to_owned(), + theme_mode: ThemeModePB::default(), + font: APPEARANCE_DEFAULT_FONT.to_owned(), + monospace_font: APPEARANCE_DEFAULT_MONOSPACE_FONT.to_owned(), + locale: LocaleSettingsPB::default(), + reset_to_default: APPEARANCE_RESET_AS_DEFAULT, + setting_key_value: HashMap::default(), + is_menu_collapsed: APPEARANCE_DEFAULT_IS_MENU_COLLAPSED, + menu_offset: APPEARANCE_DEFAULT_MENU_OFFSET, } + } } diff --git a/frontend/rust-lib/flowy-user/src/event_map.rs b/frontend/rust-lib/flowy-user/src/event_map.rs index 37e0aae8ca..11a7aa83ba 100644 --- a/frontend/rust-lib/flowy-user/src/event_map.rs +++ b/frontend/rust-lib/flowy-user/src/event_map.rs @@ -4,37 +4,43 @@ use lib_dispatch::prelude::*; use lib_infra::future::{Fut, FutureResult}; use std::sync::Arc; -use user_model::{SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfile}; +use user_model::{ + SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfile, +}; pub fn init(user_session: Arc) -> AFPlugin { - AFPlugin::new() - .name("Flowy-User") - .state(user_session) - .event(UserEvent::SignIn, sign_in) - .event(UserEvent::SignUp, sign_up) - .event(UserEvent::InitUser, init_user_handler) - .event(UserEvent::GetUserProfile, get_user_profile_handler) - .event(UserEvent::SignOut, sign_out) - .event(UserEvent::UpdateUserProfile, update_user_profile_handler) - .event(UserEvent::CheckUser, check_user_handler) - .event(UserEvent::SetAppearanceSetting, set_appearance_setting) - .event(UserEvent::GetAppearanceSetting, get_appearance_setting) - .event(UserEvent::GetUserSetting, get_user_setting) + AFPlugin::new() + .name("Flowy-User") + .state(user_session) + .event(UserEvent::SignIn, sign_in) + .event(UserEvent::SignUp, sign_up) + .event(UserEvent::InitUser, init_user_handler) + .event(UserEvent::GetUserProfile, get_user_profile_handler) + .event(UserEvent::SignOut, sign_out) + .event(UserEvent::UpdateUserProfile, update_user_profile_handler) + .event(UserEvent::CheckUser, check_user_handler) + .event(UserEvent::SetAppearanceSetting, set_appearance_setting) + .event(UserEvent::GetAppearanceSetting, get_appearance_setting) + .event(UserEvent::GetUserSetting, get_user_setting) } pub trait UserStatusCallback: Send + Sync + 'static { - fn did_sign_in(&self, token: &str, user_id: &str) -> Fut>; - fn did_sign_up(&self, user_profile: &UserProfile) -> Fut>; - fn did_expired(&self, token: &str, user_id: &str) -> Fut>; + fn did_sign_in(&self, token: &str, user_id: &str) -> Fut>; + fn did_sign_up(&self, user_profile: &UserProfile) -> Fut>; + fn did_expired(&self, token: &str, user_id: &str) -> Fut>; } pub trait UserCloudService: Send + Sync { - fn sign_up(&self, params: SignUpParams) -> FutureResult; - fn sign_in(&self, params: SignInParams) -> FutureResult; - fn sign_out(&self, token: &str) -> FutureResult<(), FlowyError>; - fn update_user(&self, token: &str, params: UpdateUserProfileParams) -> FutureResult<(), FlowyError>; - fn get_user(&self, token: &str) -> FutureResult; - fn ws_addr(&self) -> String; + fn sign_up(&self, params: SignUpParams) -> FutureResult; + fn sign_in(&self, params: SignInParams) -> FutureResult; + fn sign_out(&self, token: &str) -> FutureResult<(), FlowyError>; + fn update_user( + &self, + token: &str, + params: UpdateUserProfileParams, + ) -> FutureResult<(), FlowyError>; + fn get_user(&self, token: &str) -> FutureResult; + fn ws_addr(&self) -> String; } use flowy_derive::{Flowy_Event, ProtoBuf_Enum}; @@ -44,43 +50,43 @@ use strum_macros::Display; #[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)] #[event_err = "FlowyError"] pub enum UserEvent { - /// Logging into an account using a register email and password - #[event(input = "SignInPayloadPB", output = "UserProfilePB")] - SignIn = 0, + /// Logging into an account using a register email and password + #[event(input = "SignInPayloadPB", output = "UserProfilePB")] + SignIn = 0, - /// Creating a new account - #[event(input = "SignUpPayloadPB", output = "UserProfilePB")] - SignUp = 1, + /// Creating a new account + #[event(input = "SignUpPayloadPB", output = "UserProfilePB")] + SignUp = 1, - /// Logging out fo an account - #[event(passthrough)] - SignOut = 2, + /// Logging out fo an account + #[event(passthrough)] + SignOut = 2, - /// Update the user information - #[event(input = "UpdateUserProfilePayloadPB")] - UpdateUserProfile = 3, + /// Update the user information + #[event(input = "UpdateUserProfilePayloadPB")] + UpdateUserProfile = 3, - /// Get the user information - #[event(output = "UserProfilePB")] - GetUserProfile = 4, + /// Get the user information + #[event(output = "UserProfilePB")] + GetUserProfile = 4, - /// Check the user current session is valid or not - #[event(output = "UserProfilePB")] - CheckUser = 5, + /// Check the user current session is valid or not + #[event(output = "UserProfilePB")] + CheckUser = 5, - /// Initialize resources for the current user after launching the application - #[event()] - InitUser = 6, + /// Initialize resources for the current user after launching the application + #[event()] + InitUser = 6, - /// Change the visual elements of the interface, such as theme, font and more - #[event(input = "AppearanceSettingsPB")] - SetAppearanceSetting = 7, + /// Change the visual elements of the interface, such as theme, font and more + #[event(input = "AppearanceSettingsPB")] + SetAppearanceSetting = 7, - /// Get the appearance setting - #[event(output = "AppearanceSettingsPB")] - GetAppearanceSetting = 8, + /// Get the appearance setting + #[event(output = "AppearanceSettingsPB")] + GetAppearanceSetting = 8, - /// Get the settings of the user, such as the user storage folder - #[event(output = "UserSettingPB")] - GetUserSetting = 9, + /// Get the settings of the user, such as the user storage folder + #[event(output = "UserSettingPB")] + GetUserSetting = 9, } diff --git a/frontend/rust-lib/flowy-user/src/handlers/auth_handler.rs b/frontend/rust-lib/flowy-user/src/handlers/auth_handler.rs index c9ee47db31..eaaafbdea4 100644 --- a/frontend/rust-lib/flowy-user/src/handlers/auth_handler.rs +++ b/frontend/rust-lib/flowy-user/src/handlers/auth_handler.rs @@ -8,12 +8,12 @@ use user_model::{SignInParams, SignUpParams}; // tracing instrument 👉🏻 https://docs.rs/tracing/0.1.26/tracing/attr.instrument.html #[tracing::instrument(level = "debug", name = "sign_in", skip(data, session), fields(email = %data.email), err)] pub async fn sign_in( - data: AFPluginData, - session: AFPluginState>, + data: AFPluginData, + session: AFPluginState>, ) -> DataResult { - let params: SignInParams = data.into_inner().try_into()?; - let user_profile: UserProfilePB = session.sign_in(params).await?.into(); - data_result(user_profile) + let params: SignInParams = data.into_inner().try_into()?; + let user_profile: UserProfilePB = session.sign_in(params).await?.into(); + data_result(user_profile) } #[tracing::instrument( @@ -27,11 +27,11 @@ pub async fn sign_in( err )] pub async fn sign_up( - data: AFPluginData, - session: AFPluginState>, + data: AFPluginData, + session: AFPluginState>, ) -> DataResult { - let params: SignUpParams = data.into_inner().try_into()?; - let user_profile: UserProfilePB = session.sign_up(params).await?.into(); + let params: SignUpParams = data.into_inner().try_into()?; + let user_profile: UserProfilePB = session.sign_up(params).await?.into(); - data_result(user_profile) + data_result(user_profile) } diff --git a/frontend/rust-lib/flowy-user/src/handlers/user_handler.rs b/frontend/rust-lib/flowy-user/src/handlers/user_handler.rs index fb26d3015b..6410a6268a 100644 --- a/frontend/rust-lib/flowy-user/src/handlers/user_handler.rs +++ b/frontend/rust-lib/flowy-user/src/handlers/user_handler.rs @@ -1,5 +1,6 @@ use crate::entities::{ - AppearanceSettingsPB, UpdateUserProfilePayloadPB, UserProfilePB, UserSettingPB, APPEARANCE_DEFAULT_THEME, + AppearanceSettingsPB, UpdateUserProfilePayloadPB, UserProfilePB, UserSettingPB, + APPEARANCE_DEFAULT_THEME, }; use crate::{errors::FlowyError, services::UserSession}; use flowy_sqlite::kv::KV; @@ -9,73 +10,82 @@ use user_model::UpdateUserProfileParams; #[tracing::instrument(level = "debug", skip(session))] pub async fn init_user_handler(session: AFPluginState>) -> Result<(), FlowyError> { - session.init_user().await?; - Ok(()) + session.init_user().await?; + Ok(()) } #[tracing::instrument(level = "debug", skip(session))] -pub async fn check_user_handler(session: AFPluginState>) -> DataResult { - let user_profile: UserProfilePB = session.check_user().await?.into(); - data_result(user_profile) +pub async fn check_user_handler( + session: AFPluginState>, +) -> DataResult { + let user_profile: UserProfilePB = session.check_user().await?.into(); + data_result(user_profile) } #[tracing::instrument(level = "debug", skip(session))] pub async fn get_user_profile_handler( - session: AFPluginState>, + session: AFPluginState>, ) -> DataResult { - let user_profile: UserProfilePB = session.get_user_profile().await?.into(); - data_result(user_profile) + let user_profile: UserProfilePB = session.get_user_profile().await?.into(); + data_result(user_profile) } #[tracing::instrument(level = "debug", name = "sign_out", skip(session))] pub async fn sign_out(session: AFPluginState>) -> Result<(), FlowyError> { - session.sign_out().await?; - Ok(()) + session.sign_out().await?; + Ok(()) } #[tracing::instrument(level = "debug", skip(data, session))] pub async fn update_user_profile_handler( - data: AFPluginData, - session: AFPluginState>, + data: AFPluginData, + session: AFPluginState>, ) -> Result<(), FlowyError> { - let params: UpdateUserProfileParams = data.into_inner().try_into()?; - session.update_user_profile(params).await?; - Ok(()) + let params: UpdateUserProfileParams = data.into_inner().try_into()?; + session.update_user_profile(params).await?; + Ok(()) } const APPEARANCE_SETTING_CACHE_KEY: &str = "appearance_settings"; #[tracing::instrument(level = "debug", skip(data), err)] -pub async fn set_appearance_setting(data: AFPluginData) -> Result<(), FlowyError> { - let mut setting = data.into_inner(); - if setting.theme.is_empty() { - setting.theme = APPEARANCE_DEFAULT_THEME.to_string(); - } +pub async fn set_appearance_setting( + data: AFPluginData, +) -> Result<(), FlowyError> { + let mut setting = data.into_inner(); + if setting.theme.is_empty() { + setting.theme = APPEARANCE_DEFAULT_THEME.to_string(); + } - let s = serde_json::to_string(&setting)?; - KV::set_str(APPEARANCE_SETTING_CACHE_KEY, s); - Ok(()) + let s = serde_json::to_string(&setting)?; + KV::set_str(APPEARANCE_SETTING_CACHE_KEY, s); + Ok(()) } #[tracing::instrument(level = "debug", err)] pub async fn get_appearance_setting() -> DataResult { - match KV::get_str(APPEARANCE_SETTING_CACHE_KEY) { - None => data_result(AppearanceSettingsPB::default()), - Some(s) => { - let setting = match serde_json::from_str(&s) { - Ok(setting) => setting, - Err(e) => { - tracing::error!("Deserialize AppearanceSettings failed: {:?}, fallback to default", e); - AppearanceSettingsPB::default() - } - }; - data_result(setting) - } - } + match KV::get_str(APPEARANCE_SETTING_CACHE_KEY) { + None => data_result(AppearanceSettingsPB::default()), + Some(s) => { + let setting = match serde_json::from_str(&s) { + Ok(setting) => setting, + Err(e) => { + tracing::error!( + "Deserialize AppearanceSettings failed: {:?}, fallback to default", + e + ); + AppearanceSettingsPB::default() + }, + }; + data_result(setting) + }, + } } #[tracing::instrument(level = "debug", skip_all, err)] -pub async fn get_user_setting(session: AFPluginState>) -> DataResult { - let user_setting = session.user_setting()?; - data_result(user_setting) +pub async fn get_user_setting( + session: AFPluginState>, +) -> DataResult { + let user_setting = session.user_setting()?; + data_result(user_setting) } diff --git a/frontend/rust-lib/flowy-user/src/lib.rs b/frontend/rust-lib/flowy-user/src/lib.rs index 27091e2ff3..5156668ae1 100644 --- a/frontend/rust-lib/flowy-user/src/lib.rs +++ b/frontend/rust-lib/flowy-user/src/lib.rs @@ -10,5 +10,5 @@ pub mod services; extern crate flowy_sqlite; pub mod errors { - pub use flowy_error::*; + pub use flowy_error::*; } diff --git a/frontend/rust-lib/flowy-user/src/notification.rs b/frontend/rust-lib/flowy-user/src/notification.rs index 1405eb7cc6..f63a6571aa 100644 --- a/frontend/rust-lib/flowy-user/src/notification.rs +++ b/frontend/rust-lib/flowy-user/src/notification.rs @@ -4,27 +4,27 @@ const OBSERVABLE_CATEGORY: &str = "User"; #[derive(ProtoBuf_Enum, Debug)] pub(crate) enum UserNotification { - Unknown = 0, - DidUserSignIn = 1, - DidUpdateUserProfile = 2, + Unknown = 0, + DidUserSignIn = 1, + DidUpdateUserProfile = 2, } impl std::default::Default for UserNotification { - fn default() -> Self { - UserNotification::Unknown - } + fn default() -> Self { + UserNotification::Unknown + } } impl std::convert::From for i32 { - fn from(notification: UserNotification) -> Self { - notification as i32 - } + fn from(notification: UserNotification) -> Self { + notification as i32 + } } pub(crate) fn send_notification(id: &str, ty: UserNotification) -> NotificationBuilder { - NotificationBuilder::new(id, ty, OBSERVABLE_CATEGORY) + NotificationBuilder::new(id, ty, OBSERVABLE_CATEGORY) } pub(crate) fn send_sign_in_notification() -> NotificationBuilder { - NotificationBuilder::new("", UserNotification::DidUserSignIn, OBSERVABLE_CATEGORY) + NotificationBuilder::new("", UserNotification::DidUserSignIn, OBSERVABLE_CATEGORY) } diff --git a/frontend/rust-lib/flowy-user/src/services/database.rs b/frontend/rust-lib/flowy-user/src/services/database.rs index 5c621e87b3..49d08d44a5 100644 --- a/frontend/rust-lib/flowy-user/src/services/database.rs +++ b/frontend/rust-lib/flowy-user/src/services/database.rs @@ -8,144 +8,144 @@ use std::{collections::HashMap, sync::Arc, time::Duration}; use user_model::{SignInResponse, SignUpResponse, UpdateUserProfileParams, UserProfile}; pub struct UserDB { - db_dir: String, + db_dir: String, } impl UserDB { - pub fn new(db_dir: &str) -> Self { - Self { - db_dir: db_dir.to_owned(), - } + pub fn new(db_dir: &str) -> Self { + Self { + db_dir: db_dir.to_owned(), + } + } + + fn open_user_db_if_need(&self, user_id: &str) -> Result, FlowyError> { + if user_id.is_empty() { + return Err(ErrorCode::UserIdIsEmpty.into()); } - fn open_user_db_if_need(&self, user_id: &str) -> Result, FlowyError> { - if user_id.is_empty() { - return Err(ErrorCode::UserIdIsEmpty.into()); - } - - if let Some(database) = DB_MAP.read().get(user_id) { - return Ok(database.get_pool()); - } - - let mut write_guard = DB_MAP.write(); - // The Write guard acquire exclusive access that will guarantee the user db only initialize once. - match write_guard.get(user_id) { - None => {} - Some(database) => return Ok(database.get_pool()), - } - - let mut dir = PathBuf::new(); - dir.push(&self.db_dir); - dir.push(user_id); - let dir = dir.to_str().unwrap().to_owned(); - - tracing::trace!("open user db {} at path: {}", user_id, dir); - let db = flowy_sqlite::init(&dir).map_err(|e| { - tracing::error!("open user: {} db failed, {:?}", user_id, e); - FlowyError::internal().context(e) - })?; - let pool = db.get_pool(); - write_guard.insert(user_id.to_owned(), db); - drop(write_guard); - Ok(pool) + if let Some(database) = DB_MAP.read().get(user_id) { + return Ok(database.get_pool()); } - pub(crate) fn close_user_db(&self, user_id: &str) -> Result<(), FlowyError> { - match DB_MAP.try_write_for(Duration::from_millis(300)) { - None => Err(FlowyError::internal().context("Acquire write lock to close user db failed")), - Some(mut write_guard) => { - write_guard.remove(user_id); - Ok(()) - } - } + let mut write_guard = DB_MAP.write(); + // The Write guard acquire exclusive access that will guarantee the user db only initialize once. + match write_guard.get(user_id) { + None => {}, + Some(database) => return Ok(database.get_pool()), } - pub(crate) fn get_connection(&self, user_id: &str) -> Result { - let conn = self.get_pool(user_id)?.get()?; - Ok(conn) - } + let mut dir = PathBuf::new(); + dir.push(&self.db_dir); + dir.push(user_id); + let dir = dir.to_str().unwrap().to_owned(); - pub(crate) fn get_pool(&self, user_id: &str) -> Result, FlowyError> { - let pool = self.open_user_db_if_need(user_id)?; - Ok(pool) + tracing::trace!("open user db {} at path: {}", user_id, dir); + let db = flowy_sqlite::init(&dir).map_err(|e| { + tracing::error!("open user: {} db failed, {:?}", user_id, e); + FlowyError::internal().context(e) + })?; + let pool = db.get_pool(); + write_guard.insert(user_id.to_owned(), db); + drop(write_guard); + Ok(pool) + } + + pub(crate) fn close_user_db(&self, user_id: &str) -> Result<(), FlowyError> { + match DB_MAP.try_write_for(Duration::from_millis(300)) { + None => Err(FlowyError::internal().context("Acquire write lock to close user db failed")), + Some(mut write_guard) => { + write_guard.remove(user_id); + Ok(()) + }, } + } + + pub(crate) fn get_connection(&self, user_id: &str) -> Result { + let conn = self.get_pool(user_id)?.get()?; + Ok(conn) + } + + pub(crate) fn get_pool(&self, user_id: &str) -> Result, FlowyError> { + let pool = self.open_user_db_if_need(user_id)?; + Ok(pool) + } } lazy_static! { - static ref DB_MAP: RwLock> = RwLock::new(HashMap::new()); + static ref DB_MAP: RwLock> = RwLock::new(HashMap::new()); } #[derive(Clone, Default, Queryable, Identifiable, Insertable)] #[table_name = "user_table"] pub struct UserTable { - pub(crate) id: String, - pub(crate) name: String, - pub(crate) token: String, - pub(crate) email: String, - pub(crate) workspace: String, // deprecated - pub(crate) icon_url: String, + pub(crate) id: String, + pub(crate) name: String, + pub(crate) token: String, + pub(crate) email: String, + pub(crate) workspace: String, // deprecated + pub(crate) icon_url: String, } impl UserTable { - pub fn new(id: String, name: String, email: String, token: String) -> Self { - Self { - id, - name, - email, - token, - icon_url: "".to_owned(), - workspace: "".to_owned(), - } + pub fn new(id: String, name: String, email: String, token: String) -> Self { + Self { + id, + name, + email, + token, + icon_url: "".to_owned(), + workspace: "".to_owned(), } + } - pub fn set_workspace(mut self, workspace: String) -> Self { - self.workspace = workspace; - self - } + pub fn set_workspace(mut self, workspace: String) -> Self { + self.workspace = workspace; + self + } } impl std::convert::From for UserTable { - fn from(resp: SignUpResponse) -> Self { - UserTable::new(resp.user_id, resp.name, resp.email, resp.token) - } + fn from(resp: SignUpResponse) -> Self { + UserTable::new(resp.user_id, resp.name, resp.email, resp.token) + } } impl std::convert::From for UserTable { - fn from(resp: SignInResponse) -> Self { - UserTable::new(resp.user_id, resp.name, resp.email, resp.token) - } + fn from(resp: SignInResponse) -> Self { + UserTable::new(resp.user_id, resp.name, resp.email, resp.token) + } } impl std::convert::From for UserProfile { - fn from(table: UserTable) -> Self { - UserProfile { - id: table.id, - email: table.email, - name: table.name, - token: table.token, - icon_url: table.icon_url, - } + fn from(table: UserTable) -> Self { + UserProfile { + id: table.id, + email: table.email, + name: table.name, + token: table.token, + icon_url: table.icon_url, } + } } #[derive(AsChangeset, Identifiable, Default, Debug)] #[table_name = "user_table"] pub struct UserTableChangeset { - pub id: String, - pub workspace: Option, // deprecated - pub name: Option, - pub email: Option, - pub icon_url: Option, + pub id: String, + pub workspace: Option, // deprecated + pub name: Option, + pub email: Option, + pub icon_url: Option, } impl UserTableChangeset { - pub fn new(params: UpdateUserProfileParams) -> Self { - UserTableChangeset { - id: params.id, - workspace: None, - name: params.name, - email: params.email, - icon_url: params.icon_url, - } + pub fn new(params: UpdateUserProfileParams) -> Self { + UserTableChangeset { + id: params.id, + workspace: None, + name: params.name, + email: params.email, + icon_url: params.icon_url, } + } } diff --git a/frontend/rust-lib/flowy-user/src/services/user_session.rs b/frontend/rust-lib/flowy-user/src/services/user_session.rs index 8a0ecfd0bb..f9ec7ca80c 100644 --- a/frontend/rust-lib/flowy-user/src/services/user_session.rs +++ b/frontend/rust-lib/flowy-user/src/services/user_session.rs @@ -1,362 +1,374 @@ use crate::entities::{UserProfilePB, UserSettingPB}; use crate::event_map::UserStatusCallback; use crate::{ - errors::{ErrorCode, FlowyError}, - event_map::UserCloudService, - notification::*, - services::database::{UserDB, UserTable, UserTableChangeset}, + errors::{ErrorCode, FlowyError}, + event_map::UserCloudService, + notification::*, + services::database::{UserDB, UserTable, UserTableChangeset}, }; use flowy_sqlite::ConnectionPool; use flowy_sqlite::{ - kv::KV, - query_dsl::*, - schema::{user_table, user_table::dsl}, - DBConnection, ExpressionMethods, UserDatabaseConnection, + kv::KV, + query_dsl::*, + schema::{user_table, user_table::dsl}, + DBConnection, ExpressionMethods, UserDatabaseConnection, }; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::RwLock; -use user_model::{SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfile}; +use user_model::{ + SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfile, +}; pub struct UserSessionConfig { - root_dir: String, + root_dir: String, - /// Used as the key of `Session` when saving session information to KV. - session_cache_key: String, + /// Used as the key of `Session` when saving session information to KV. + session_cache_key: String, } impl UserSessionConfig { - /// The `root_dir` represents as the root of the user folders. It must be unique for each - /// users. - pub fn new(name: &str, root_dir: &str) -> Self { - let session_cache_key = format!("{}_session_cache", name); - Self { - root_dir: root_dir.to_owned(), - session_cache_key, - } + /// The `root_dir` represents as the root of the user folders. It must be unique for each + /// users. + pub fn new(name: &str, root_dir: &str) -> Self { + let session_cache_key = format!("{}_session_cache", name); + Self { + root_dir: root_dir.to_owned(), + session_cache_key, } + } } pub struct UserSession { - database: UserDB, - config: UserSessionConfig, - cloud_service: Arc, - user_status_callback: RwLock>>, + database: UserDB, + config: UserSessionConfig, + cloud_service: Arc, + user_status_callback: RwLock>>, } impl UserSession { - pub fn new(config: UserSessionConfig, cloud_service: Arc) -> Self { - let db = UserDB::new(&config.root_dir); - let user_status_callback = RwLock::new(None); - Self { - database: db, - config, - cloud_service, - user_status_callback, - } + pub fn new(config: UserSessionConfig, cloud_service: Arc) -> Self { + let db = UserDB::new(&config.root_dir); + let user_status_callback = RwLock::new(None); + Self { + database: db, + config, + cloud_service, + user_status_callback, } + } - pub async fn init(&self, user_status_callback: C) { - if let Ok(session) = self.get_session() { - let _ = user_status_callback.did_sign_in(&session.token, &session.user_id).await; - } - *self.user_status_callback.write().await = Some(Arc::new(user_status_callback)); + pub async fn init(&self, user_status_callback: C) { + if let Ok(session) = self.get_session() { + let _ = user_status_callback + .did_sign_in(&session.token, &session.user_id) + .await; } + *self.user_status_callback.write().await = Some(Arc::new(user_status_callback)); + } - pub fn db_connection(&self) -> Result { - let user_id = self.get_session()?.user_id; - self.database.get_connection(&user_id) - } + pub fn db_connection(&self) -> Result { + let user_id = self.get_session()?.user_id; + self.database.get_connection(&user_id) + } - // The caller will be not 'Sync' before of the return value, - // PooledConnection is not sync. You can use - // db_connection_pool function to require the ConnectionPool that is 'Sync'. - // - // let pool = self.db_connection_pool()?; - // let conn: PooledConnection = pool.get()?; - pub fn db_pool(&self) -> Result, FlowyError> { - let user_id = self.get_session()?.user_id; - self.database.get_pool(&user_id) - } + // The caller will be not 'Sync' before of the return value, + // PooledConnection is not sync. You can use + // db_connection_pool function to require the ConnectionPool that is 'Sync'. + // + // let pool = self.db_connection_pool()?; + // let conn: PooledConnection = pool.get()?; + pub fn db_pool(&self) -> Result, FlowyError> { + let user_id = self.get_session()?.user_id; + self.database.get_pool(&user_id) + } - #[tracing::instrument(level = "debug", skip(self))] - pub async fn sign_in(&self, params: SignInParams) -> Result { - if self.is_user_login(¶ms.email) { - match self.get_user_profile().await { - Ok(profile) => { - send_sign_in_notification() - .payload::(profile.clone().into()) - .send(); - Ok(profile) - } - Err(err) => Err(err), - } - } else { - let resp = self.cloud_service.sign_in(params).await?; - let session: Session = resp.clone().into(); - self.set_session(Some(session))?; - let user_profile: UserProfile = self.save_user(resp.into()).await?.into(); - let _ = self - .user_status_callback - .read() - .await - .as_ref() - .unwrap() - .did_sign_in(&user_profile.token, &user_profile.id) - .await; - send_sign_in_notification() - .payload::(user_profile.clone().into()) - .send(); - Ok(user_profile) - } - } - - #[tracing::instrument(level = "debug", skip(self))] - pub async fn sign_up(&self, params: SignUpParams) -> Result { - if self.is_user_login(¶ms.email) { - self.get_user_profile().await - } else { - let resp = self.cloud_service.sign_up(params).await?; - let session: Session = resp.clone().into(); - self.set_session(Some(session))?; - let user_table = self.save_user(resp.into()).await?; - let user_profile: UserProfile = user_table.into(); - let _ = self - .user_status_callback - .read() - .await - .as_ref() - .unwrap() - .did_sign_up(&user_profile) - .await; - Ok(user_profile) - } - } - - #[tracing::instrument(level = "debug", skip(self))] - pub async fn sign_out(&self) -> Result<(), FlowyError> { - let session = self.get_session()?; - let _ = - diesel::delete(dsl::user_table.filter(dsl::id.eq(&session.user_id))).execute(&*(self.db_connection()?))?; - self.database.close_user_db(&session.user_id)?; - self.set_session(None)?; - let _ = self - .user_status_callback - .read() - .await - .as_ref() - .unwrap() - .did_expired(&session.token, &session.user_id) - .await; - self.sign_out_on_server(&session.token).await?; - - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self))] - pub async fn update_user_profile(&self, params: UpdateUserProfileParams) -> Result<(), FlowyError> { - let session = self.get_session()?; - let changeset = UserTableChangeset::new(params.clone()); - diesel_update_table!(user_table, changeset, &*self.db_connection()?); - - let user_profile = self.get_user_profile().await?; - let profile_pb: UserProfilePB = user_profile.into(); - send_notification(&session.token, UserNotification::DidUpdateUserProfile) - .payload(profile_pb) + #[tracing::instrument(level = "debug", skip(self))] + pub async fn sign_in(&self, params: SignInParams) -> Result { + if self.is_user_login(¶ms.email) { + match self.get_user_profile().await { + Ok(profile) => { + send_sign_in_notification() + .payload::(profile.clone().into()) .send(); - self.update_user_on_server(&session.token, params).await?; - Ok(()) + Ok(profile) + }, + Err(err) => Err(err), + } + } else { + let resp = self.cloud_service.sign_in(params).await?; + let session: Session = resp.clone().into(); + self.set_session(Some(session))?; + let user_profile: UserProfile = self.save_user(resp.into()).await?.into(); + let _ = self + .user_status_callback + .read() + .await + .as_ref() + .unwrap() + .did_sign_in(&user_profile.token, &user_profile.id) + .await; + send_sign_in_notification() + .payload::(user_profile.clone().into()) + .send(); + Ok(user_profile) } + } - pub async fn init_user(&self) -> Result<(), FlowyError> { - Ok(()) + #[tracing::instrument(level = "debug", skip(self))] + pub async fn sign_up(&self, params: SignUpParams) -> Result { + if self.is_user_login(¶ms.email) { + self.get_user_profile().await + } else { + let resp = self.cloud_service.sign_up(params).await?; + let session: Session = resp.clone().into(); + self.set_session(Some(session))?; + let user_table = self.save_user(resp.into()).await?; + let user_profile: UserProfile = user_table.into(); + let _ = self + .user_status_callback + .read() + .await + .as_ref() + .unwrap() + .did_sign_up(&user_profile) + .await; + Ok(user_profile) } + } - pub async fn check_user(&self) -> Result { - let (user_id, token) = self.get_session()?.into_part(); + #[tracing::instrument(level = "debug", skip(self))] + pub async fn sign_out(&self) -> Result<(), FlowyError> { + let session = self.get_session()?; + let _ = diesel::delete(dsl::user_table.filter(dsl::id.eq(&session.user_id))) + .execute(&*(self.db_connection()?))?; + self.database.close_user_db(&session.user_id)?; + self.set_session(None)?; + let _ = self + .user_status_callback + .read() + .await + .as_ref() + .unwrap() + .did_expired(&session.token, &session.user_id) + .await; + self.sign_out_on_server(&session.token).await?; - let user = dsl::user_table - .filter(user_table::id.eq(&user_id)) - .first::(&*(self.db_connection()?))?; + Ok(()) + } - self.read_user_profile_on_server(&token)?; - Ok(user.into()) - } + #[tracing::instrument(level = "debug", skip(self))] + pub async fn update_user_profile( + &self, + params: UpdateUserProfileParams, + ) -> Result<(), FlowyError> { + let session = self.get_session()?; + let changeset = UserTableChangeset::new(params.clone()); + diesel_update_table!(user_table, changeset, &*self.db_connection()?); - pub async fn get_user_profile(&self) -> Result { - let (user_id, token) = self.get_session()?.into_part(); - let user = dsl::user_table - .filter(user_table::id.eq(&user_id)) - .first::(&*(self.db_connection()?))?; + let user_profile = self.get_user_profile().await?; + let profile_pb: UserProfilePB = user_profile.into(); + send_notification(&session.token, UserNotification::DidUpdateUserProfile) + .payload(profile_pb) + .send(); + self.update_user_on_server(&session.token, params).await?; + Ok(()) + } - self.read_user_profile_on_server(&token)?; - Ok(user.into()) - } + pub async fn init_user(&self) -> Result<(), FlowyError> { + Ok(()) + } - pub fn user_dir(&self) -> Result { - let session = self.get_session()?; - Ok(format!("{}/{}", self.config.root_dir, session.user_id)) - } + pub async fn check_user(&self) -> Result { + let (user_id, token) = self.get_session()?.into_part(); - pub fn user_setting(&self) -> Result { - let user_setting = UserSettingPB { - user_folder: self.user_dir()?, - }; - Ok(user_setting) - } + let user = dsl::user_table + .filter(user_table::id.eq(&user_id)) + .first::(&*(self.db_connection()?))?; - pub fn user_id(&self) -> Result { - Ok(self.get_session()?.user_id) - } + self.read_user_profile_on_server(&token)?; + Ok(user.into()) + } - pub fn user_name(&self) -> Result { - Ok(self.get_session()?.name) - } + pub async fn get_user_profile(&self) -> Result { + let (user_id, token) = self.get_session()?.into_part(); + let user = dsl::user_table + .filter(user_table::id.eq(&user_id)) + .first::(&*(self.db_connection()?))?; - pub fn token(&self) -> Result { - Ok(self.get_session()?.token) - } + self.read_user_profile_on_server(&token)?; + Ok(user.into()) + } + + pub fn user_dir(&self) -> Result { + let session = self.get_session()?; + Ok(format!("{}/{}", self.config.root_dir, session.user_id)) + } + + pub fn user_setting(&self) -> Result { + let user_setting = UserSettingPB { + user_folder: self.user_dir()?, + }; + Ok(user_setting) + } + + pub fn user_id(&self) -> Result { + Ok(self.get_session()?.user_id) + } + + pub fn user_name(&self) -> Result { + Ok(self.get_session()?.name) + } + + pub fn token(&self) -> Result { + Ok(self.get_session()?.token) + } } impl UserSession { - fn read_user_profile_on_server(&self, _token: &str) -> Result<(), FlowyError> { - Ok(()) - } + fn read_user_profile_on_server(&self, _token: &str) -> Result<(), FlowyError> { + Ok(()) + } - async fn update_user_on_server(&self, token: &str, params: UpdateUserProfileParams) -> Result<(), FlowyError> { - let server = self.cloud_service.clone(); - let token = token.to_owned(); - let _ = tokio::spawn(async move { - match server.update_user(&token, params).await { - Ok(_) => {} - Err(e) => { - // TODO: retry? - tracing::error!("update user profile failed: {:?}", e); - } - } - }) - .await; - Ok(()) - } + async fn update_user_on_server( + &self, + token: &str, + params: UpdateUserProfileParams, + ) -> Result<(), FlowyError> { + let server = self.cloud_service.clone(); + let token = token.to_owned(); + let _ = tokio::spawn(async move { + match server.update_user(&token, params).await { + Ok(_) => {}, + Err(e) => { + // TODO: retry? + tracing::error!("update user profile failed: {:?}", e); + }, + } + }) + .await; + Ok(()) + } - async fn sign_out_on_server(&self, token: &str) -> Result<(), FlowyError> { - let server = self.cloud_service.clone(); - let token = token.to_owned(); - let _ = tokio::spawn(async move { - match server.sign_out(&token).await { - Ok(_) => {} - Err(e) => tracing::error!("Sign out failed: {:?}", e), - } - }) - .await; - Ok(()) - } + async fn sign_out_on_server(&self, token: &str) -> Result<(), FlowyError> { + let server = self.cloud_service.clone(); + let token = token.to_owned(); + let _ = tokio::spawn(async move { + match server.sign_out(&token).await { + Ok(_) => {}, + Err(e) => tracing::error!("Sign out failed: {:?}", e), + } + }) + .await; + Ok(()) + } - async fn save_user(&self, user: UserTable) -> Result { - let conn = self.db_connection()?; - let _ = diesel::insert_into(user_table::table) - .values(user.clone()) - .execute(&*conn)?; - Ok(user) - } + async fn save_user(&self, user: UserTable) -> Result { + let conn = self.db_connection()?; + let _ = diesel::insert_into(user_table::table) + .values(user.clone()) + .execute(&*conn)?; + Ok(user) + } - fn set_session(&self, session: Option) -> Result<(), FlowyError> { - tracing::debug!("Set user session: {:?}", session); - match &session { - None => KV::remove(&self.config.session_cache_key).map_err(|e| FlowyError::new(ErrorCode::Internal, &e))?, - Some(session) => KV::set_str(&self.config.session_cache_key, session.clone().into()), - } - Ok(()) + fn set_session(&self, session: Option) -> Result<(), FlowyError> { + tracing::debug!("Set user session: {:?}", session); + match &session { + None => KV::remove(&self.config.session_cache_key) + .map_err(|e| FlowyError::new(ErrorCode::Internal, &e))?, + Some(session) => KV::set_str(&self.config.session_cache_key, session.clone().into()), } + Ok(()) + } - fn get_session(&self) -> Result { - match KV::get_str(&self.config.session_cache_key) { - None => Err(FlowyError::unauthorized()), - Some(s) => Ok(Session::from(s)), - } + fn get_session(&self) -> Result { + match KV::get_str(&self.config.session_cache_key) { + None => Err(FlowyError::unauthorized()), + Some(s) => Ok(Session::from(s)), } + } - fn is_user_login(&self, email: &str) -> bool { - match self.get_session() { - Ok(session) => session.email == email, - Err(_) => false, - } + fn is_user_login(&self, email: &str) -> bool { + match self.get_session() { + Ok(session) => session.email == email, + Err(_) => false, } + } } pub async fn update_user( - _cloud_service: Arc, - pool: Arc, - params: UpdateUserProfileParams, + _cloud_service: Arc, + pool: Arc, + params: UpdateUserProfileParams, ) -> Result<(), FlowyError> { - let changeset = UserTableChangeset::new(params); - let conn = pool.get()?; - diesel_update_table!(user_table, changeset, &*conn); - Ok(()) + let changeset = UserTableChangeset::new(params); + let conn = pool.get()?; + diesel_update_table!(user_table, changeset, &*conn); + Ok(()) } impl UserDatabaseConnection for UserSession { - fn get_connection(&self) -> Result { - self.db_connection().map_err(|e| format!("{:?}", e)) - } + fn get_connection(&self) -> Result { + self.db_connection().map_err(|e| format!("{:?}", e)) + } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] struct Session { - user_id: String, - token: String, - email: String, - #[serde(default)] - name: String, + user_id: String, + token: String, + email: String, + #[serde(default)] + name: String, } impl std::convert::From for Session { - fn from(resp: SignInResponse) -> Self { - Session { - user_id: resp.user_id, - token: resp.token, - email: resp.email, - name: resp.name, - } + fn from(resp: SignInResponse) -> Self { + Session { + user_id: resp.user_id, + token: resp.token, + email: resp.email, + name: resp.name, } + } } impl std::convert::From for Session { - fn from(resp: SignUpResponse) -> Self { - Session { - user_id: resp.user_id, - token: resp.token, - email: resp.email, - name: resp.name, - } + fn from(resp: SignUpResponse) -> Self { + Session { + user_id: resp.user_id, + token: resp.token, + email: resp.email, + name: resp.name, } + } } impl Session { - pub fn into_part(self) -> (String, String) { - (self.user_id, self.token) - } + pub fn into_part(self) -> (String, String) { + (self.user_id, self.token) + } } impl std::convert::From for Session { - fn from(s: String) -> Self { - match serde_json::from_str(&s) { - Ok(s) => s, - Err(e) => { - tracing::error!("Deserialize string to Session failed: {:?}", e); - Session::default() - } - } + fn from(s: String) -> Self { + match serde_json::from_str(&s) { + Ok(s) => s, + Err(e) => { + tracing::error!("Deserialize string to Session failed: {:?}", e); + Session::default() + }, } + } } impl std::convert::From for String { - fn from(session: Session) -> Self { - match serde_json::to_string(&session) { - Ok(s) => s, - Err(e) => { - tracing::error!("Serialize session to string failed: {:?}", e); - "".to_string() - } - } + fn from(session: Session) -> Self { + match serde_json::to_string(&session) { + Ok(s) => s, + Err(e) => { + tracing::error!("Serialize session to string failed: {:?}", e); + "".to_string() + }, } + } } diff --git a/frontend/rust-lib/flowy-user/tests/event/auth_test.rs b/frontend/rust-lib/flowy-user/tests/event/auth_test.rs index 288b9d8b08..7fd5e0bfa8 100644 --- a/frontend/rust-lib/flowy-user/tests/event/auth_test.rs +++ b/frontend/rust-lib/flowy-user/tests/event/auth_test.rs @@ -5,105 +5,107 @@ use flowy_user::{errors::ErrorCode, event_map::UserEvent::*}; #[tokio::test] async fn sign_up_with_invalid_email() { - for email in invalid_email_test_case() { - let sdk = FlowySDKTest::default(); - let request = SignUpPayloadPB { - email: email.to_string(), - name: valid_name(), - password: login_password(), - }; + for email in invalid_email_test_case() { + let sdk = FlowySDKTest::default(); + let request = SignUpPayloadPB { + email: email.to_string(), + name: valid_name(), + password: login_password(), + }; - assert_eq!( - UserModuleEventBuilder::new(sdk) - .event(SignUp) - .payload(request) - .async_send() - .await - .error() - .code, - ErrorCode::EmailFormatInvalid.value() - ); - } + assert_eq!( + UserModuleEventBuilder::new(sdk) + .event(SignUp) + .payload(request) + .async_send() + .await + .error() + .code, + ErrorCode::EmailFormatInvalid.value() + ); + } } #[tokio::test] async fn sign_up_with_invalid_password() { - for password in invalid_password_test_case() { - let sdk = FlowySDKTest::default(); - let request = SignUpPayloadPB { - email: random_email(), - name: valid_name(), - password, - }; + for password in invalid_password_test_case() { + let sdk = FlowySDKTest::default(); + let request = SignUpPayloadPB { + email: random_email(), + name: valid_name(), + password, + }; - UserModuleEventBuilder::new(sdk) - .event(SignUp) - .payload(request) - .async_send() - .await - .assert_error(); - } + UserModuleEventBuilder::new(sdk) + .event(SignUp) + .payload(request) + .async_send() + .await + .assert_error(); + } } #[tokio::test] async fn sign_in_success() { - let test = FlowySDKTest::default(); - let _ = UserModuleEventBuilder::new(test.clone()).event(SignOut).sync_send(); - let sign_up_context = test.sign_up().await; + let test = FlowySDKTest::default(); + let _ = UserModuleEventBuilder::new(test.clone()) + .event(SignOut) + .sync_send(); + let sign_up_context = test.sign_up().await; - let request = SignInPayloadPB { - email: sign_up_context.user_profile.email.clone(), - password: sign_up_context.password.clone(), - name: "".to_string(), - }; + let request = SignInPayloadPB { + email: sign_up_context.user_profile.email.clone(), + password: sign_up_context.password.clone(), + name: "".to_string(), + }; - let response = UserModuleEventBuilder::new(test.clone()) - .event(SignIn) - .payload(request) - .async_send() - .await - .parse::(); - dbg!(&response); + let response = UserModuleEventBuilder::new(test.clone()) + .event(SignIn) + .payload(request) + .async_send() + .await + .parse::(); + dbg!(&response); } #[tokio::test] async fn sign_in_with_invalid_email() { - for email in invalid_email_test_case() { - let sdk = FlowySDKTest::default(); - let request = SignInPayloadPB { - email: email.to_string(), - password: login_password(), - name: "".to_string(), - }; + for email in invalid_email_test_case() { + let sdk = FlowySDKTest::default(); + let request = SignInPayloadPB { + email: email.to_string(), + password: login_password(), + name: "".to_string(), + }; - assert_eq!( - UserModuleEventBuilder::new(sdk) - .event(SignIn) - .payload(request) - .async_send() - .await - .error() - .code, - ErrorCode::EmailFormatInvalid.value() - ); - } + assert_eq!( + UserModuleEventBuilder::new(sdk) + .event(SignIn) + .payload(request) + .async_send() + .await + .error() + .code, + ErrorCode::EmailFormatInvalid.value() + ); + } } #[tokio::test] async fn sign_in_with_invalid_password() { - for password in invalid_password_test_case() { - let sdk = FlowySDKTest::default(); + for password in invalid_password_test_case() { + let sdk = FlowySDKTest::default(); - let request = SignInPayloadPB { - email: random_email(), - password, - name: "".to_string(), - }; + let request = SignInPayloadPB { + email: random_email(), + password, + name: "".to_string(), + }; - UserModuleEventBuilder::new(sdk) - .event(SignIn) - .payload(request) - .async_send() - .await - .assert_error(); - } + UserModuleEventBuilder::new(sdk) + .event(SignIn) + .payload(request) + .async_send() + .await + .assert_error(); + } } diff --git a/frontend/rust-lib/flowy-user/tests/event/helper.rs b/frontend/rust-lib/flowy-user/tests/event/helper.rs index e43b5b48a0..d35c923e6f 100644 --- a/frontend/rust-lib/flowy-user/tests/event/helper.rs +++ b/frontend/rust-lib/flowy-user/tests/event/helper.rs @@ -1,42 +1,42 @@ pub use flowy_test::{ - event_builder::*, - prelude::{login_password, random_email}, + event_builder::*, + prelude::{login_password, random_email}, }; pub(crate) fn invalid_email_test_case() -> Vec { - // https://gist.github.com/cjaoude/fd9910626629b53c4d25 - vec![ - "annie@", - "annie@gmail@", - "#@%^%#$@#$@#.com", - "@example.com", - "Joe Smith ", - "email.example.com", - "email@example@example.com", - "email@-example.com", - "email@example..com", - "あいうえお@example.com", - /* The following email is valid according to the validate_email function return - * ".email@example.com", - * "email.@example.com", - * "email..email@example.com", - * "email@example", - * "email@example.web", - * "email@111.222.333.44444", - * "Abc..123@example.com", */ - ] + // https://gist.github.com/cjaoude/fd9910626629b53c4d25 + vec![ + "annie@", + "annie@gmail@", + "#@%^%#$@#$@#.com", + "@example.com", + "Joe Smith ", + "email.example.com", + "email@example@example.com", + "email@-example.com", + "email@example..com", + "あいうえお@example.com", + /* The following email is valid according to the validate_email function return + * ".email@example.com", + * "email.@example.com", + * "email..email@example.com", + * "email@example", + * "email@example.web", + * "email@111.222.333.44444", + * "Abc..123@example.com", */ + ] + .iter() + .map(|s| s.to_string()) + .collect::>() +} + +pub(crate) fn invalid_password_test_case() -> Vec { + vec!["123456", "1234".repeat(100).as_str()] .iter() .map(|s| s.to_string()) .collect::>() } -pub(crate) fn invalid_password_test_case() -> Vec { - vec!["123456", "1234".repeat(100).as_str()] - .iter() - .map(|s| s.to_string()) - .collect::>() -} - pub(crate) fn valid_name() -> String { - "AppFlowy".to_string() + "AppFlowy".to_string() } diff --git a/frontend/rust-lib/flowy-user/tests/event/user_profile_test.rs b/frontend/rust-lib/flowy-user/tests/event/user_profile_test.rs index fa77b69509..ebaf06016d 100644 --- a/frontend/rust-lib/flowy-user/tests/event/user_profile_test.rs +++ b/frontend/rust-lib/flowy-user/tests/event/user_profile_test.rs @@ -8,120 +8,120 @@ use nanoid::nanoid; #[tokio::test] async fn user_profile_get_failed() { - let sdk = FlowySDKTest::default(); - let result = UserModuleEventBuilder::new(sdk) - .event(GetUserProfile) - .assert_error() - .async_send() - .await; - assert!(result.user_profile().is_none()) + let sdk = FlowySDKTest::default(); + let result = UserModuleEventBuilder::new(sdk) + .event(GetUserProfile) + .assert_error() + .async_send() + .await; + assert!(result.user_profile().is_none()) } #[tokio::test] async fn user_profile_get() { - let test = FlowySDKTest::default(); - let user_profile = test.init_user().await; - let user = UserModuleEventBuilder::new(test.clone()) - .event(GetUserProfile) - .sync_send() - .parse::(); - assert_eq!(user_profile, user); + let test = FlowySDKTest::default(); + let user_profile = test.init_user().await; + let user = UserModuleEventBuilder::new(test.clone()) + .event(GetUserProfile) + .sync_send() + .parse::(); + assert_eq!(user_profile, user); } #[tokio::test] async fn user_update_with_name() { - let sdk = FlowySDKTest::default(); - let user = sdk.init_user().await; - let new_name = "hello_world".to_owned(); - let request = UpdateUserProfilePayloadPB::new(&user.id).name(&new_name); - let _ = UserModuleEventBuilder::new(sdk.clone()) - .event(UpdateUserProfile) - .payload(request) - .sync_send(); + let sdk = FlowySDKTest::default(); + let user = sdk.init_user().await; + let new_name = "hello_world".to_owned(); + let request = UpdateUserProfilePayloadPB::new(&user.id).name(&new_name); + let _ = UserModuleEventBuilder::new(sdk.clone()) + .event(UpdateUserProfile) + .payload(request) + .sync_send(); - let user_profile = UserModuleEventBuilder::new(sdk.clone()) - .event(GetUserProfile) - .assert_error() - .sync_send() - .parse::(); + let user_profile = UserModuleEventBuilder::new(sdk.clone()) + .event(GetUserProfile) + .assert_error() + .sync_send() + .parse::(); - assert_eq!(user_profile.name, new_name,); + assert_eq!(user_profile.name, new_name,); } #[tokio::test] async fn user_update_with_email() { - let sdk = FlowySDKTest::default(); - let user = sdk.init_user().await; - let new_email = format!("{}@gmail.com", nanoid!(6)); - let request = UpdateUserProfilePayloadPB::new(&user.id).email(&new_email); - let _ = UserModuleEventBuilder::new(sdk.clone()) - .event(UpdateUserProfile) - .payload(request) - .sync_send(); - let user_profile = UserModuleEventBuilder::new(sdk.clone()) - .event(GetUserProfile) - .assert_error() - .sync_send() - .parse::(); + let sdk = FlowySDKTest::default(); + let user = sdk.init_user().await; + let new_email = format!("{}@gmail.com", nanoid!(6)); + let request = UpdateUserProfilePayloadPB::new(&user.id).email(&new_email); + let _ = UserModuleEventBuilder::new(sdk.clone()) + .event(UpdateUserProfile) + .payload(request) + .sync_send(); + let user_profile = UserModuleEventBuilder::new(sdk.clone()) + .event(GetUserProfile) + .assert_error() + .sync_send() + .parse::(); - assert_eq!(user_profile.email, new_email,); + assert_eq!(user_profile.email, new_email,); } #[tokio::test] async fn user_update_with_password() { - let sdk = FlowySDKTest::default(); - let user = sdk.init_user().await; - let new_password = "H123world!".to_owned(); - let request = UpdateUserProfilePayloadPB::new(&user.id).password(&new_password); + let sdk = FlowySDKTest::default(); + let user = sdk.init_user().await; + let new_password = "H123world!".to_owned(); + let request = UpdateUserProfilePayloadPB::new(&user.id).password(&new_password); - let _ = UserModuleEventBuilder::new(sdk.clone()) - .event(UpdateUserProfile) - .payload(request) - .sync_send() - .assert_success(); + let _ = UserModuleEventBuilder::new(sdk.clone()) + .event(UpdateUserProfile) + .payload(request) + .sync_send() + .assert_success(); } #[tokio::test] async fn user_update_with_invalid_email() { - let test = FlowySDKTest::default(); - let user = test.init_user().await; - for email in invalid_email_test_case() { - let request = UpdateUserProfilePayloadPB::new(&user.id).email(&email); - assert_eq!( - UserModuleEventBuilder::new(test.clone()) - .event(UpdateUserProfile) - .payload(request) - .sync_send() - .error() - .code, - ErrorCode::EmailFormatInvalid.value() - ); - } + let test = FlowySDKTest::default(); + let user = test.init_user().await; + for email in invalid_email_test_case() { + let request = UpdateUserProfilePayloadPB::new(&user.id).email(&email); + assert_eq!( + UserModuleEventBuilder::new(test.clone()) + .event(UpdateUserProfile) + .payload(request) + .sync_send() + .error() + .code, + ErrorCode::EmailFormatInvalid.value() + ); + } } #[tokio::test] async fn user_update_with_invalid_password() { - let test = FlowySDKTest::default(); - let user = test.init_user().await; - for password in invalid_password_test_case() { - let request = UpdateUserProfilePayloadPB::new(&user.id).password(&password); + let test = FlowySDKTest::default(); + let user = test.init_user().await; + for password in invalid_password_test_case() { + let request = UpdateUserProfilePayloadPB::new(&user.id).password(&password); - UserModuleEventBuilder::new(test.clone()) - .event(UpdateUserProfile) - .payload(request) - .sync_send() - .assert_error(); - } + UserModuleEventBuilder::new(test.clone()) + .event(UpdateUserProfile) + .payload(request) + .sync_send() + .assert_error(); + } } #[tokio::test] async fn user_update_with_invalid_name() { - let test = FlowySDKTest::default(); - let user = test.init_user().await; - let request = UpdateUserProfilePayloadPB::new(&user.id).name(""); - UserModuleEventBuilder::new(test.clone()) - .event(UpdateUserProfile) - .payload(request) - .sync_send() - .assert_error(); + let test = FlowySDKTest::default(); + let user = test.init_user().await; + let request = UpdateUserProfilePayloadPB::new(&user.id).name(""); + UserModuleEventBuilder::new(test.clone()) + .event(UpdateUserProfile) + .payload(request) + .sync_send() + .assert_error(); } diff --git a/frontend/rust-lib/lib-dispatch/src/byte_trait.rs b/frontend/rust-lib/lib-dispatch/src/byte_trait.rs index a13f3e5b57..4548fd3cab 100644 --- a/frontend/rust-lib/lib-dispatch/src/byte_trait.rs +++ b/frontend/rust-lib/lib-dispatch/src/byte_trait.rs @@ -3,25 +3,27 @@ use bytes::Bytes; // To bytes pub trait ToBytes { - fn into_bytes(self) -> Result; + fn into_bytes(self) -> Result; } #[cfg(feature = "use_protobuf")] impl ToBytes for T where - T: std::convert::TryInto, + T: std::convert::TryInto, { - fn into_bytes(self) -> Result { - match self.try_into() { - Ok(data) => Ok(data), - Err(e) => Err(InternalError::ProtobufError(format!( - "Serial {:?} to bytes failed:{:?}", - std::any::type_name::(), - e - )) - .into()), - } + fn into_bytes(self) -> Result { + match self.try_into() { + Ok(data) => Ok(data), + Err(e) => Err( + InternalError::ProtobufError(format!( + "Serial {:?} to bytes failed:{:?}", + std::any::type_name::(), + e + )) + .into(), + ), } + } } // #[cfg(feature = "use_serde")] @@ -40,30 +42,30 @@ where // From bytes pub trait AFPluginFromBytes: Sized { - fn parse_from_bytes(bytes: Bytes) -> Result; + fn parse_from_bytes(bytes: Bytes) -> Result; } #[cfg(feature = "use_protobuf")] impl AFPluginFromBytes for T where - // // https://stackoverflow.com/questions/62871045/tryfromu8-trait-bound-in-trait - // T: for<'a> std::convert::TryFrom<&'a Bytes, Error = - // protobuf::ProtobufError>, - T: std::convert::TryFrom, + // // https://stackoverflow.com/questions/62871045/tryfromu8-trait-bound-in-trait + // T: for<'a> std::convert::TryFrom<&'a Bytes, Error = + // protobuf::ProtobufError>, + T: std::convert::TryFrom, { - fn parse_from_bytes(bytes: Bytes) -> Result { - match T::try_from(bytes) { - Ok(data) => Ok(data), - Err(e) => { - tracing::error!( - "Parse payload to {} failed with error: {:?}", - std::any::type_name::(), - e - ); - Err(e.into()) - } - } + fn parse_from_bytes(bytes: Bytes) -> Result { + match T::try_from(bytes) { + Ok(data) => Ok(data), + Err(e) => { + tracing::error!( + "Parse payload to {} failed with error: {:?}", + std::any::type_name::(), + e + ); + Err(e.into()) + }, } + } } // // #[cfg(feature = "use_serde")] diff --git a/frontend/rust-lib/lib-dispatch/src/data.rs b/frontend/rust-lib/lib-dispatch/src/data.rs index 8f37383934..c6381aa7fb 100644 --- a/frontend/rust-lib/lib-dispatch/src/data.rs +++ b/frontend/rust-lib/lib-dispatch/src/data.rs @@ -1,9 +1,9 @@ use crate::{ - byte_trait::*, - errors::{DispatchError, InternalError}, - request::{unexpected_none_payload, AFPluginEventRequest, FromAFPluginRequest, Payload}, - response::{AFPluginEventResponse, AFPluginResponder, ResponseBuilder}, - util::ready::{ready, Ready}, + byte_trait::*, + errors::{DispatchError, InternalError}, + request::{unexpected_none_payload, AFPluginEventRequest, FromAFPluginRequest, Payload}, + response::{AFPluginEventResponse, AFPluginResponder, ResponseBuilder}, + util::ready::{ready, Ready}, }; use bytes::Bytes; use std::ops; @@ -11,111 +11,118 @@ use std::ops; pub struct AFPluginData(pub T); impl AFPluginData { - pub fn into_inner(self) -> T { - self.0 - } + pub fn into_inner(self) -> T { + self.0 + } } impl ops::Deref for AFPluginData { - type Target = T; + type Target = T; - fn deref(&self) -> &T { - &self.0 - } + fn deref(&self) -> &T { + &self.0 + } } impl ops::DerefMut for AFPluginData { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } } impl FromAFPluginRequest for AFPluginData where - T: AFPluginFromBytes + 'static, + T: AFPluginFromBytes + 'static, { - type Error = DispatchError; - type Future = Ready>; + type Error = DispatchError; + type Future = Ready>; - #[inline] - fn from_request(req: &AFPluginEventRequest, payload: &mut Payload) -> Self::Future { - match payload { - Payload::None => ready(Err(unexpected_none_payload(req))), - Payload::Bytes(bytes) => match T::parse_from_bytes(bytes.clone()) { - Ok(data) => ready(Ok(AFPluginData(data))), - Err(e) => ready(Err(InternalError::DeserializeFromBytes(format!("{}", e)).into())), - }, - } + #[inline] + fn from_request(req: &AFPluginEventRequest, payload: &mut Payload) -> Self::Future { + match payload { + Payload::None => ready(Err(unexpected_none_payload(req))), + Payload::Bytes(bytes) => match T::parse_from_bytes(bytes.clone()) { + Ok(data) => ready(Ok(AFPluginData(data))), + Err(e) => ready(Err( + InternalError::DeserializeFromBytes(format!("{}", e)).into(), + )), + }, } + } } impl AFPluginResponder for AFPluginData where - T: ToBytes, + T: ToBytes, { - fn respond_to(self, _request: &AFPluginEventRequest) -> AFPluginEventResponse { - match self.into_inner().into_bytes() { - Ok(bytes) => { - log::trace!("Serialize Data: {:?} to event response", std::any::type_name::()); - ResponseBuilder::Ok().data(bytes).build() - } - Err(e) => e.into(), - } + fn respond_to(self, _request: &AFPluginEventRequest) -> AFPluginEventResponse { + match self.into_inner().into_bytes() { + Ok(bytes) => { + log::trace!( + "Serialize Data: {:?} to event response", + std::any::type_name::() + ); + ResponseBuilder::Ok().data(bytes).build() + }, + Err(e) => e.into(), } + } } impl std::convert::TryFrom<&Payload> for AFPluginData where - T: AFPluginFromBytes, + T: AFPluginFromBytes, { - type Error = DispatchError; - fn try_from(payload: &Payload) -> Result, Self::Error> { - parse_payload(payload) - } + type Error = DispatchError; + fn try_from(payload: &Payload) -> Result, Self::Error> { + parse_payload(payload) + } } impl std::convert::TryFrom for AFPluginData where - T: AFPluginFromBytes, + T: AFPluginFromBytes, { - type Error = DispatchError; - fn try_from(payload: Payload) -> Result, Self::Error> { - parse_payload(&payload) - } + type Error = DispatchError; + fn try_from(payload: Payload) -> Result, Self::Error> { + parse_payload(&payload) + } } fn parse_payload(payload: &Payload) -> Result, DispatchError> where - T: AFPluginFromBytes, + T: AFPluginFromBytes, { - match payload { - Payload::None => Err(InternalError::UnexpectedNone(format!( - "Parse fail, expected payload:{:?}", - std::any::type_name::() - )) - .into()), - Payload::Bytes(bytes) => { - let data = T::parse_from_bytes(bytes.clone())?; - Ok(AFPluginData(data)) - } - } + match payload { + Payload::None => Err( + InternalError::UnexpectedNone(format!( + "Parse fail, expected payload:{:?}", + std::any::type_name::() + )) + .into(), + ), + Payload::Bytes(bytes) => { + let data = T::parse_from_bytes(bytes.clone())?; + Ok(AFPluginData(data)) + }, + } } impl std::convert::TryInto for AFPluginData where - T: ToBytes, + T: ToBytes, { - type Error = DispatchError; + type Error = DispatchError; - fn try_into(self) -> Result { - let inner = self.into_inner(); - let bytes = inner.into_bytes()?; - Ok(Payload::Bytes(bytes)) - } + fn try_into(self) -> Result { + let inner = self.into_inner(); + let bytes = inner.into_bytes()?; + Ok(Payload::Bytes(bytes)) + } } impl ToBytes for AFPluginData { - fn into_bytes(self) -> Result { - Ok(Bytes::from(self.0)) - } + fn into_bytes(self) -> Result { + Ok(Bytes::from(self.0)) + } } diff --git a/frontend/rust-lib/lib-dispatch/src/dispatcher.rs b/frontend/rust-lib/lib-dispatch/src/dispatcher.rs index dc5defd0f8..dfd0d1dcc6 100644 --- a/frontend/rust-lib/lib-dispatch/src/dispatcher.rs +++ b/frontend/rust-lib/lib-dispatch/src/dispatcher.rs @@ -1,9 +1,9 @@ use crate::runtime::AFPluginRuntime; use crate::{ - errors::{DispatchError, Error, InternalError}, - module::{as_plugin_map, AFPlugin, AFPluginMap, AFPluginRequest}, - response::AFPluginEventResponse, - service::{AFPluginServiceFactory, Service}, + errors::{DispatchError, Error, InternalError}, + module::{as_plugin_map, AFPlugin, AFPluginMap, AFPluginRequest}, + response::AFPluginEventResponse, + service::{AFPluginServiceFactory, Service}, }; use derivative::*; use futures_core::future::BoxFuture; @@ -13,173 +13,180 @@ use std::{future::Future, sync::Arc}; use tokio::macros::support::{Pin, Poll}; pub struct AFPluginDispatcher { - plugins: AFPluginMap, - runtime: AFPluginRuntime, + plugins: AFPluginMap, + runtime: AFPluginRuntime, } impl AFPluginDispatcher { - pub fn construct(runtime: AFPluginRuntime, module_factory: F) -> AFPluginDispatcher - where - F: FnOnce() -> Vec, - { - let plugins = module_factory(); - tracing::trace!("{}", plugin_info(&plugins)); - AFPluginDispatcher { - plugins: as_plugin_map(plugins), - runtime, - } + pub fn construct(runtime: AFPluginRuntime, module_factory: F) -> AFPluginDispatcher + where + F: FnOnce() -> Vec, + { + let plugins = module_factory(); + tracing::trace!("{}", plugin_info(&plugins)); + AFPluginDispatcher { + plugins: as_plugin_map(plugins), + runtime, } + } - pub fn async_send(dispatch: Arc, request: Req) -> DispatchFuture - where - Req: std::convert::Into, - { - AFPluginDispatcher::async_send_with_callback(dispatch, request, |_| Box::pin(async {})) - } + pub fn async_send( + dispatch: Arc, + request: Req, + ) -> DispatchFuture + where + Req: std::convert::Into, + { + AFPluginDispatcher::async_send_with_callback(dispatch, request, |_| Box::pin(async {})) + } - pub fn async_send_with_callback( - dispatch: Arc, - request: Req, - callback: Callback, - ) -> DispatchFuture - where - Req: std::convert::Into, - Callback: FnOnce(AFPluginEventResponse) -> BoxFuture<'static, ()> + 'static + Send + Sync, - { - let request: AFPluginRequest = request.into(); - let plugins = dispatch.plugins.clone(); - let service = Box::new(DispatchService { plugins }); - tracing::trace!("Async event: {:?}", &request.event); - let service_ctx = DispatchContext { - request, - callback: Some(Box::new(callback)), - }; - let join_handle = dispatch.runtime.spawn(async move { - service.call(service_ctx).await.unwrap_or_else(|e| { - tracing::error!("Dispatch runtime error: {:?}", e); - InternalError::Other(format!("{:?}", e)).as_response() - }) - }); + pub fn async_send_with_callback( + dispatch: Arc, + request: Req, + callback: Callback, + ) -> DispatchFuture + where + Req: std::convert::Into, + Callback: FnOnce(AFPluginEventResponse) -> BoxFuture<'static, ()> + 'static + Send + Sync, + { + let request: AFPluginRequest = request.into(); + let plugins = dispatch.plugins.clone(); + let service = Box::new(DispatchService { plugins }); + tracing::trace!("Async event: {:?}", &request.event); + let service_ctx = DispatchContext { + request, + callback: Some(Box::new(callback)), + }; + let join_handle = dispatch.runtime.spawn(async move { + service.call(service_ctx).await.unwrap_or_else(|e| { + tracing::error!("Dispatch runtime error: {:?}", e); + InternalError::Other(format!("{:?}", e)).as_response() + }) + }); - DispatchFuture { - fut: Box::pin(async move { - join_handle.await.unwrap_or_else(|e| { - let msg = format!("EVENT_DISPATCH join error: {:?}", e); - tracing::error!("{}", msg); - let error = InternalError::JoinError(msg); - error.as_response() - }) - }), - } - } - - pub fn sync_send(dispatch: Arc, request: AFPluginRequest) -> AFPluginEventResponse { - futures::executor::block_on(async { - AFPluginDispatcher::async_send_with_callback(dispatch, request, |_| Box::pin(async {})).await + DispatchFuture { + fut: Box::pin(async move { + join_handle.await.unwrap_or_else(|e| { + let msg = format!("EVENT_DISPATCH join error: {:?}", e); + tracing::error!("{}", msg); + let error = InternalError::JoinError(msg); + error.as_response() }) + }), } + } - pub fn spawn(&self, f: F) - where - F: Future + Send + 'static, - { - self.runtime.spawn(f); - } + pub fn sync_send( + dispatch: Arc, + request: AFPluginRequest, + ) -> AFPluginEventResponse { + futures::executor::block_on(async { + AFPluginDispatcher::async_send_with_callback(dispatch, request, |_| Box::pin(async {})).await + }) + } + + pub fn spawn(&self, f: F) + where + F: Future + Send + 'static, + { + self.runtime.spawn(f); + } } #[pin_project] pub struct DispatchFuture { - #[pin] - pub fut: Pin + Sync + Send>>, + #[pin] + pub fut: Pin + Sync + Send>>, } impl Future for DispatchFuture where - T: Send + Sync, + T: Send + Sync, { - type Output = T; + type Output = T; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - Poll::Ready(futures_core::ready!(this.fut.poll(cx))) - } + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.as_mut().project(); + Poll::Ready(futures_core::ready!(this.fut.poll(cx))) + } } -pub type BoxFutureCallback = Box BoxFuture<'static, ()> + 'static + Send + Sync>; +pub type BoxFutureCallback = + Box BoxFuture<'static, ()> + 'static + Send + Sync>; #[derive(Derivative)] #[derivative(Debug)] pub struct DispatchContext { - pub request: AFPluginRequest, - #[derivative(Debug = "ignore")] - pub callback: Option, + pub request: AFPluginRequest, + #[derivative(Debug = "ignore")] + pub callback: Option, } impl DispatchContext { - pub(crate) fn into_parts(self) -> (AFPluginRequest, Option) { - let DispatchContext { request, callback } = self; - (request, callback) - } + pub(crate) fn into_parts(self) -> (AFPluginRequest, Option) { + let DispatchContext { request, callback } = self; + (request, callback) + } } pub(crate) struct DispatchService { - pub(crate) plugins: AFPluginMap, + pub(crate) plugins: AFPluginMap, } impl Service for DispatchService { - type Response = AFPluginEventResponse; - type Error = DispatchError; - type Future = BoxFuture<'static, Result>; + type Response = AFPluginEventResponse; + type Error = DispatchError; + type Future = BoxFuture<'static, Result>; - #[cfg_attr( - feature = "use_tracing", - tracing::instrument(name = "DispatchService", level = "debug", skip(self, ctx)) - )] - fn call(&self, ctx: DispatchContext) -> Self::Future { - let module_map = self.plugins.clone(); - let (request, callback) = ctx.into_parts(); + #[cfg_attr( + feature = "use_tracing", + tracing::instrument(name = "DispatchService", level = "debug", skip(self, ctx)) + )] + fn call(&self, ctx: DispatchContext) -> Self::Future { + let module_map = self.plugins.clone(); + let (request, callback) = ctx.into_parts(); - Box::pin(async move { - let result = { - // print_module_map_info(&module_map); - match module_map.get(&request.event) { - Some(module) => { - tracing::trace!("Handle event: {:?} by {:?}", &request.event, module.name); - let fut = module.new_service(()); - let service_fut = fut.await?.call(request); - service_fut.await - } - None => { - let msg = format!("Can not find the event handler. {:?}", request); - tracing::error!("{}", msg); - Err(InternalError::HandleNotFound(msg).into()) - } - } - }; + Box::pin(async move { + let result = { + // print_module_map_info(&module_map); + match module_map.get(&request.event) { + Some(module) => { + tracing::trace!("Handle event: {:?} by {:?}", &request.event, module.name); + let fut = module.new_service(()); + let service_fut = fut.await?.call(request); + service_fut.await + }, + None => { + let msg = format!("Can not find the event handler. {:?}", request); + tracing::error!("{}", msg); + Err(InternalError::HandleNotFound(msg).into()) + }, + } + }; - let response = result.unwrap_or_else(|e| e.into()); - tracing::trace!("Dispatch result: {:?}", response); - if let Some(callback) = callback { - callback(response.clone()).await; - } + let response = result.unwrap_or_else(|e| e.into()); + tracing::trace!("Dispatch result: {:?}", response); + if let Some(callback) = callback { + callback(response.clone()).await; + } - Ok(response) - }) - } + Ok(response) + }) + } } #[allow(dead_code)] fn plugin_info(plugins: &[AFPlugin]) -> String { - let mut info = format!("{} plugins loaded\n", plugins.len()); - for module in plugins { - info.push_str(&format!("-> {} loaded \n", module.name)); - } - info + let mut info = format!("{} plugins loaded\n", plugins.len()); + for module in plugins { + info.push_str(&format!("-> {} loaded \n", module.name)); + } + info } #[allow(dead_code)] fn print_plugins(plugins: &AFPluginMap) { - plugins.iter().for_each(|(k, v)| { - tracing::info!("Event: {:?} plugin : {:?}", k, v.name); - }) + plugins.iter().for_each(|(k, v)| { + tracing::info!("Event: {:?} plugin : {:?}", k, v.name); + }) } diff --git a/frontend/rust-lib/lib-dispatch/src/errors/errors.rs b/frontend/rust-lib/lib-dispatch/src/errors/errors.rs index 58e0af7f17..cbfd8542ec 100644 --- a/frontend/rust-lib/lib-dispatch/src/errors/errors.rs +++ b/frontend/rust-lib/lib-dispatch/src/errors/errors.rs @@ -1,7 +1,7 @@ use crate::{ - byte_trait::AFPluginFromBytes, - request::AFPluginEventRequest, - response::{AFPluginEventResponse, ResponseBuilder}, + byte_trait::AFPluginFromBytes, + request::AFPluginEventRequest, + response::{AFPluginEventResponse, ResponseBuilder}, }; use bytes::Bytes; use dyn_clone::DynClone; @@ -10,124 +10,129 @@ use std::fmt; use tokio::{sync::mpsc::error::SendError, task::JoinError}; pub trait Error: fmt::Debug + DynClone + Send + Sync { - fn as_response(&self) -> AFPluginEventResponse; + fn as_response(&self) -> AFPluginEventResponse; } dyn_clone::clone_trait_object!(Error); impl From for DispatchError { - fn from(err: T) -> DispatchError { - DispatchError { inner: Box::new(err) } + fn from(err: T) -> DispatchError { + DispatchError { + inner: Box::new(err), } + } } #[derive(Clone)] pub struct DispatchError { - inner: Box, + inner: Box, } impl DispatchError { - pub fn inner_error(&self) -> &dyn Error { - self.inner.as_ref() - } + pub fn inner_error(&self) -> &dyn Error { + self.inner.as_ref() + } } impl fmt::Display for DispatchError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", &self.inner) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", &self.inner) + } } impl fmt::Debug for DispatchError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", &self.inner) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", &self.inner) + } } impl std::error::Error for DispatchError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - None - } + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } - fn cause(&self) -> Option<&dyn std::error::Error> { - None - } + fn cause(&self) -> Option<&dyn std::error::Error> { + None + } } impl From> for DispatchError { - fn from(err: SendError) -> Self { - InternalError::Other(format!("{}", err)).into() - } + fn from(err: SendError) -> Self { + InternalError::Other(format!("{}", err)).into() + } } impl From for DispatchError { - fn from(s: String) -> Self { - InternalError::Other(s).into() - } + fn from(s: String) -> Self { + InternalError::Other(s).into() + } } #[cfg(feature = "use_protobuf")] impl From for DispatchError { - fn from(e: protobuf::ProtobufError) -> Self { - InternalError::ProtobufError(format!("{:?}", e)).into() - } + fn from(e: protobuf::ProtobufError) -> Self { + InternalError::ProtobufError(format!("{:?}", e)).into() + } } impl AFPluginFromBytes for DispatchError { - fn parse_from_bytes(bytes: Bytes) -> Result { - let s = String::from_utf8(bytes.to_vec()).unwrap(); - Ok(InternalError::DeserializeFromBytes(s).into()) - } + fn parse_from_bytes(bytes: Bytes) -> Result { + let s = String::from_utf8(bytes.to_vec()).unwrap(); + Ok(InternalError::DeserializeFromBytes(s).into()) + } } impl From for AFPluginEventResponse { - fn from(err: DispatchError) -> Self { - err.inner_error().as_response() - } + fn from(err: DispatchError) -> Self { + err.inner_error().as_response() + } } #[cfg(feature = "use_serde")] impl serde::Serialize for DispatchError { - fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> - where - S: serde::Serializer, - { - serializer.serialize_str(&format!("{}", self)) - } + fn serialize( + &self, + serializer: S, + ) -> Result<::Ok, ::Error> + where + S: serde::Serializer, + { + serializer.serialize_str(&format!("{}", self)) + } } #[derive(Clone, Debug)] pub(crate) enum InternalError { - ProtobufError(String), - UnexpectedNone(String), - DeserializeFromBytes(String), - JoinError(String), - ServiceNotFound(String), - HandleNotFound(String), - Other(String), + ProtobufError(String), + UnexpectedNone(String), + DeserializeFromBytes(String), + JoinError(String), + ServiceNotFound(String), + HandleNotFound(String), + Other(String), } impl fmt::Display for InternalError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - InternalError::ProtobufError(s) => fmt::Display::fmt(&s, f), - InternalError::UnexpectedNone(s) => fmt::Display::fmt(&s, f), - InternalError::DeserializeFromBytes(s) => fmt::Display::fmt(&s, f), - InternalError::JoinError(s) => fmt::Display::fmt(&s, f), - InternalError::ServiceNotFound(s) => fmt::Display::fmt(&s, f), - InternalError::HandleNotFound(s) => fmt::Display::fmt(&s, f), - InternalError::Other(s) => fmt::Display::fmt(&s, f), - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + InternalError::ProtobufError(s) => fmt::Display::fmt(&s, f), + InternalError::UnexpectedNone(s) => fmt::Display::fmt(&s, f), + InternalError::DeserializeFromBytes(s) => fmt::Display::fmt(&s, f), + InternalError::JoinError(s) => fmt::Display::fmt(&s, f), + InternalError::ServiceNotFound(s) => fmt::Display::fmt(&s, f), + InternalError::HandleNotFound(s) => fmt::Display::fmt(&s, f), + InternalError::Other(s) => fmt::Display::fmt(&s, f), } + } } impl Error for InternalError { - fn as_response(&self) -> AFPluginEventResponse { - ResponseBuilder::Err().data(self.to_string()).build() - } + fn as_response(&self) -> AFPluginEventResponse { + ResponseBuilder::Err().data(self.to_string()).build() + } } impl std::convert::From for InternalError { - fn from(e: JoinError) -> Self { - InternalError::JoinError(format!("{}", e)) - } + fn from(e: JoinError) -> Self { + InternalError::JoinError(format!("{}", e)) + } } diff --git a/frontend/rust-lib/lib-dispatch/src/lib.rs b/frontend/rust-lib/lib-dispatch/src/lib.rs index d35d17e3a5..edd968ae48 100644 --- a/frontend/rust-lib/lib-dispatch/src/lib.rs +++ b/frontend/rust-lib/lib-dispatch/src/lib.rs @@ -16,5 +16,7 @@ pub mod runtime; pub use errors::Error; pub mod prelude { - pub use crate::{byte_trait::*, data::*, dispatcher::*, errors::*, module::*, request::*, response::*}; + pub use crate::{ + byte_trait::*, data::*, dispatcher::*, errors::*, module::*, request::*, response::*, + }; } diff --git a/frontend/rust-lib/lib-dispatch/src/macros.rs b/frontend/rust-lib/lib-dispatch/src/macros.rs index c8fb5eb832..0c19c7e14e 100644 --- a/frontend/rust-lib/lib-dispatch/src/macros.rs +++ b/frontend/rust-lib/lib-dispatch/src/macros.rs @@ -1,8 +1,8 @@ #[macro_export] macro_rules! dispatch_future { - ($fut:expr) => { - DispatchFuture { - fut: Box::pin(async move { $fut.await }), - } - }; + ($fut:expr) => { + DispatchFuture { + fut: Box::pin(async move { $fut.await }), + } + }; } diff --git a/frontend/rust-lib/lib-dispatch/src/module/container.rs b/frontend/rust-lib/lib-dispatch/src/module/container.rs index fb0a1c84b8..a95b3b702e 100644 --- a/frontend/rust-lib/lib-dispatch/src/module/container.rs +++ b/frontend/rust-lib/lib-dispatch/src/module/container.rs @@ -1,59 +1,66 @@ use std::{ - any::{Any, TypeId}, - collections::HashMap, + any::{Any, TypeId}, + collections::HashMap, }; #[derive(Default, Debug)] pub struct AFPluginStateMap(HashMap>); impl AFPluginStateMap { - #[inline] - pub fn new() -> AFPluginStateMap { - AFPluginStateMap(HashMap::default()) - } + #[inline] + pub fn new() -> AFPluginStateMap { + AFPluginStateMap(HashMap::default()) + } - pub fn insert(&mut self, val: T) -> Option - where - T: 'static + Send + Sync, - { - self.0.insert(TypeId::of::(), Box::new(val)).and_then(downcast_owned) - } + pub fn insert(&mut self, val: T) -> Option + where + T: 'static + Send + Sync, + { + self + .0 + .insert(TypeId::of::(), Box::new(val)) + .and_then(downcast_owned) + } - pub fn remove(&mut self) -> Option - where - T: 'static + Send + Sync, - { - self.0.remove(&TypeId::of::()).and_then(downcast_owned) - } + pub fn remove(&mut self) -> Option + where + T: 'static + Send + Sync, + { + self.0.remove(&TypeId::of::()).and_then(downcast_owned) + } - pub fn get(&self) -> Option<&T> - where - T: 'static + Send + Sync, - { - self.0.get(&TypeId::of::()).and_then(|boxed| boxed.downcast_ref()) - } + pub fn get(&self) -> Option<&T> + where + T: 'static + Send + Sync, + { + self + .0 + .get(&TypeId::of::()) + .and_then(|boxed| boxed.downcast_ref()) + } - pub fn get_mut(&mut self) -> Option<&mut T> - where - T: 'static + Send + Sync, - { - self.0 - .get_mut(&TypeId::of::()) - .and_then(|boxed| boxed.downcast_mut()) - } + pub fn get_mut(&mut self) -> Option<&mut T> + where + T: 'static + Send + Sync, + { + self + .0 + .get_mut(&TypeId::of::()) + .and_then(|boxed| boxed.downcast_mut()) + } - pub fn contains(&self) -> bool - where - T: 'static + Send + Sync, - { - self.0.contains_key(&TypeId::of::()) - } + pub fn contains(&self) -> bool + where + T: 'static + Send + Sync, + { + self.0.contains_key(&TypeId::of::()) + } - pub fn extend(&mut self, other: AFPluginStateMap) { - self.0.extend(other.0); - } + pub fn extend(&mut self, other: AFPluginStateMap) { + self.0.extend(other.0); + } } fn downcast_owned(boxed: Box) -> Option { - boxed.downcast().ok().map(|boxed| *boxed) + boxed.downcast().ok().map(|boxed| *boxed) } diff --git a/frontend/rust-lib/lib-dispatch/src/module/data.rs b/frontend/rust-lib/lib-dispatch/src/module/data.rs index 16c80f3acd..8099546688 100644 --- a/frontend/rust-lib/lib-dispatch/src/module/data.rs +++ b/frontend/rust-lib/lib-dispatch/src/module/data.rs @@ -1,7 +1,7 @@ use crate::{ - errors::{DispatchError, InternalError}, - request::{payload::Payload, AFPluginEventRequest, FromAFPluginRequest}, - util::ready::{ready, Ready}, + errors::{DispatchError, InternalError}, + request::{payload::Payload, AFPluginEventRequest, FromAFPluginRequest}, + util::ready::{ready, Ready}, }; use std::{any::type_name, ops::Deref, sync::Arc}; @@ -9,61 +9,64 @@ pub struct AFPluginState(Arc); impl AFPluginState where - T: Send + Sync, + T: Send + Sync, { - pub fn new(data: T) -> Self { - AFPluginState(Arc::new(data)) - } + pub fn new(data: T) -> Self { + AFPluginState(Arc::new(data)) + } - pub fn get_ref(&self) -> &T { - self.0.as_ref() - } + pub fn get_ref(&self) -> &T { + self.0.as_ref() + } } impl Deref for AFPluginState where - T: ?Sized + Send + Sync, + T: ?Sized + Send + Sync, { - type Target = Arc; + type Target = Arc; - fn deref(&self) -> &Arc { - &self.0 - } + fn deref(&self) -> &Arc { + &self.0 + } } impl Clone for AFPluginState where - T: ?Sized + Send + Sync, + T: ?Sized + Send + Sync, { - fn clone(&self) -> AFPluginState { - AFPluginState(self.0.clone()) - } + fn clone(&self) -> AFPluginState { + AFPluginState(self.0.clone()) + } } impl From> for AFPluginState where - T: ?Sized + Send + Sync, + T: ?Sized + Send + Sync, { - fn from(arc: Arc) -> Self { - AFPluginState(arc) - } + fn from(arc: Arc) -> Self { + AFPluginState(arc) + } } impl FromAFPluginRequest for AFPluginState where - T: ?Sized + Send + Sync + 'static, + T: ?Sized + Send + Sync + 'static, { - type Error = DispatchError; - type Future = Ready>; + type Error = DispatchError; + type Future = Ready>; - #[inline] - fn from_request(req: &AFPluginEventRequest, _: &mut Payload) -> Self::Future { - if let Some(state) = req.get_state::>() { - ready(Ok(state.clone())) - } else { - let msg = format!("Failed to get the plugin state of type: {}", type_name::()); - log::error!("{}", msg,); - ready(Err(InternalError::Other(msg).into())) - } + #[inline] + fn from_request(req: &AFPluginEventRequest, _: &mut Payload) -> Self::Future { + if let Some(state) = req.get_state::>() { + ready(Ok(state.clone())) + } else { + let msg = format!( + "Failed to get the plugin state of type: {}", + type_name::() + ); + log::error!("{}", msg,); + ready(Err(InternalError::Other(msg).into())) } + } } diff --git a/frontend/rust-lib/lib-dispatch/src/module/module.rs b/frontend/rust-lib/lib-dispatch/src/module/module.rs index 9b8c95957d..5b1bc0d1e7 100644 --- a/frontend/rust-lib/lib-dispatch/src/module/module.rs +++ b/frontend/rust-lib/lib-dispatch/src/module/module.rs @@ -1,12 +1,12 @@ use crate::{ - errors::{DispatchError, InternalError}, - module::{container::AFPluginStateMap, AFPluginState}, - request::{payload::Payload, AFPluginEventRequest, FromAFPluginRequest}, - response::{AFPluginEventResponse, AFPluginResponder}, - service::{ - factory, AFPluginHandler, AFPluginHandlerService, AFPluginServiceFactory, BoxService, BoxServiceFactory, - Service, ServiceRequest, ServiceResponse, - }, + errors::{DispatchError, InternalError}, + module::{container::AFPluginStateMap, AFPluginState}, + request::{payload::Payload, AFPluginEventRequest, FromAFPluginRequest}, + response::{AFPluginEventResponse, AFPluginResponder}, + service::{ + factory, AFPluginHandler, AFPluginHandlerService, AFPluginServiceFactory, BoxService, + BoxServiceFactory, Service, ServiceRequest, ServiceResponse, + }, }; use futures_core::future::BoxFuture; use futures_core::ready; @@ -14,35 +14,35 @@ use nanoid::nanoid; use pin_project::pin_project; use std::sync::Arc; use std::{ - collections::HashMap, - fmt, - fmt::{Debug, Display}, - future::Future, - hash::Hash, - pin::Pin, - task::{Context, Poll}, + collections::HashMap, + fmt, + fmt::{Debug, Display}, + future::Future, + hash::Hash, + pin::Pin, + task::{Context, Poll}, }; pub type AFPluginMap = Arc>>; pub(crate) fn as_plugin_map(plugins: Vec) -> AFPluginMap { - let mut plugin_map = HashMap::new(); - plugins.into_iter().for_each(|m| { - let events = m.events(); - let plugins = Arc::new(m); - events.into_iter().for_each(|e| { - plugin_map.insert(e, plugins.clone()); - }); + let mut plugin_map = HashMap::new(); + plugins.into_iter().for_each(|m| { + let events = m.events(); + let plugins = Arc::new(m); + events.into_iter().for_each(|e| { + plugin_map.insert(e, plugins.clone()); }); - Arc::new(plugin_map) + }); + Arc::new(plugin_map) } #[derive(PartialEq, Eq, Hash, Debug, Clone)] pub struct AFPluginEvent(pub String); impl std::convert::From for AFPluginEvent { - fn from(t: T) -> Self { - AFPluginEvent(format!("{}", t)) - } + fn from(t: T) -> Self { + AFPluginEvent(format!("{}", t)) + } } /// A plugin is used to handle the events that the plugin can handle. @@ -52,67 +52,74 @@ impl std::convert::From for AFPluginE /// which means only one handler will get called. /// pub struct AFPlugin { - pub name: String, + pub name: String, - /// a list of `AFPluginState` that the plugin registers. The state can be read by the plugin's handler. - states: Arc, + /// a list of `AFPluginState` that the plugin registers. The state can be read by the plugin's handler. + states: Arc, - /// Contains a list of factories that are used to generate the services used to handle the passed-in - /// `ServiceRequest`. - /// - event_service_factory: - Arc>>, + /// Contains a list of factories that are used to generate the services used to handle the passed-in + /// `ServiceRequest`. + /// + event_service_factory: Arc< + HashMap>, + >, } impl std::default::Default for AFPlugin { - fn default() -> Self { - Self { - name: "".to_owned(), - states: Arc::new(AFPluginStateMap::new()), - event_service_factory: Arc::new(HashMap::new()), - } + fn default() -> Self { + Self { + name: "".to_owned(), + states: Arc::new(AFPluginStateMap::new()), + event_service_factory: Arc::new(HashMap::new()), } + } } impl AFPlugin { - pub fn new() -> Self { - AFPlugin::default() - } + pub fn new() -> Self { + AFPlugin::default() + } - pub fn name(mut self, s: &str) -> Self { - self.name = s.to_owned(); - self - } + pub fn name(mut self, s: &str) -> Self { + self.name = s.to_owned(); + self + } - pub fn state(mut self, data: D) -> Self { - Arc::get_mut(&mut self.states).unwrap().insert(AFPluginState::new(data)); + pub fn state(mut self, data: D) -> Self { + Arc::get_mut(&mut self.states) + .unwrap() + .insert(AFPluginState::new(data)); - self - } + self + } - pub fn event(mut self, event: E, handler: H) -> Self - where - H: AFPluginHandler, - T: FromAFPluginRequest + 'static + Send + Sync, - ::Future: Sync + Send, - R: Future + 'static + Send + Sync, - R::Output: AFPluginResponder + 'static, - E: Eq + Hash + Debug + Clone + Display, - { - let event: AFPluginEvent = event.into(); - if self.event_service_factory.contains_key(&event) { - panic!("Register duplicate Event: {:?}", &event); - } else { - Arc::get_mut(&mut self.event_service_factory) - .unwrap() - .insert(event, factory(AFPluginHandlerService::new(handler))); - } - self + pub fn event(mut self, event: E, handler: H) -> Self + where + H: AFPluginHandler, + T: FromAFPluginRequest + 'static + Send + Sync, + ::Future: Sync + Send, + R: Future + 'static + Send + Sync, + R::Output: AFPluginResponder + 'static, + E: Eq + Hash + Debug + Clone + Display, + { + let event: AFPluginEvent = event.into(); + if self.event_service_factory.contains_key(&event) { + panic!("Register duplicate Event: {:?}", &event); + } else { + Arc::get_mut(&mut self.event_service_factory) + .unwrap() + .insert(event, factory(AFPluginHandlerService::new(handler))); } + self + } - pub fn events(&self) -> Vec { - self.event_service_factory.keys().cloned().collect::>() - } + pub fn events(&self) -> Vec { + self + .event_service_factory + .keys() + .cloned() + .collect::>() + } } /// A request that will be passed to the corresponding plugin. @@ -121,101 +128,106 @@ impl AFPlugin { /// #[derive(Debug, Clone)] pub struct AFPluginRequest { - pub id: String, - pub event: AFPluginEvent, - pub(crate) payload: Payload, + pub id: String, + pub event: AFPluginEvent, + pub(crate) payload: Payload, } impl AFPluginRequest { - pub fn new(event: E) -> Self - where - E: Into, - { - Self { - id: nanoid!(6), - event: event.into(), - payload: Payload::None, - } + pub fn new(event: E) -> Self + where + E: Into, + { + Self { + id: nanoid!(6), + event: event.into(), + payload: Payload::None, } + } - pub fn payload

(mut self, payload: P) -> Self - where - P: Into, - { - self.payload = payload.into(); - self - } + pub fn payload

(mut self, payload: P) -> Self + where + P: Into, + { + self.payload = payload.into(); + self + } } impl std::fmt::Display for AFPluginRequest { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}:{:?}", self.id, self.event) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{:?}", self.id, self.event) + } } impl AFPluginServiceFactory for AFPlugin { - type Response = AFPluginEventResponse; - type Error = DispatchError; - type Service = BoxService; - type Context = (); - type Future = BoxFuture<'static, Result>; + type Response = AFPluginEventResponse; + type Error = DispatchError; + type Service = BoxService; + type Context = (); + type Future = BoxFuture<'static, Result>; - fn new_service(&self, _cfg: Self::Context) -> Self::Future { - let services = self.event_service_factory.clone(); - let states = self.states.clone(); - Box::pin(async move { - let service = AFPluginService { services, states }; - Ok(Box::new(service) as Self::Service) - }) - } + fn new_service(&self, _cfg: Self::Context) -> Self::Future { + let services = self.event_service_factory.clone(); + let states = self.states.clone(); + Box::pin(async move { + let service = AFPluginService { services, states }; + Ok(Box::new(service) as Self::Service) + }) + } } pub struct AFPluginService { - services: Arc>>, - states: Arc, + services: Arc< + HashMap>, + >, + states: Arc, } impl Service for AFPluginService { - type Response = AFPluginEventResponse; - type Error = DispatchError; - type Future = BoxFuture<'static, Result>; + type Response = AFPluginEventResponse; + type Error = DispatchError; + type Future = BoxFuture<'static, Result>; - fn call(&self, request: AFPluginRequest) -> Self::Future { - let AFPluginRequest { id, event, payload } = request; - let states = self.states.clone(); - let request = AFPluginEventRequest::new(id, event, states); + fn call(&self, request: AFPluginRequest) -> Self::Future { + let AFPluginRequest { id, event, payload } = request; + let states = self.states.clone(); + let request = AFPluginEventRequest::new(id, event, states); - match self.services.get(&request.event) { - Some(factory) => { - let service_fut = factory.new_service(()); - let fut = AFPluginServiceFuture { - fut: Box::pin(async { - let service = service_fut.await?; - let service_req = ServiceRequest::new(request, payload); - service.call(service_req).await - }), - }; - Box::pin(async move { Ok(fut.await.unwrap_or_else(|e| e.into())) }) - } - None => { - let msg = format!("Can not find service factory for event: {:?}", request.event); - Box::pin(async { Err(InternalError::ServiceNotFound(msg).into()) }) - } - } + match self.services.get(&request.event) { + Some(factory) => { + let service_fut = factory.new_service(()); + let fut = AFPluginServiceFuture { + fut: Box::pin(async { + let service = service_fut.await?; + let service_req = ServiceRequest::new(request, payload); + service.call(service_req).await + }), + }; + Box::pin(async move { Ok(fut.await.unwrap_or_else(|e| e.into())) }) + }, + None => { + let msg = format!( + "Can not find service factory for event: {:?}", + request.event + ); + Box::pin(async { Err(InternalError::ServiceNotFound(msg).into()) }) + }, } + } } #[pin_project] pub struct AFPluginServiceFuture { - #[pin] - fut: BoxFuture<'static, Result>, + #[pin] + fut: BoxFuture<'static, Result>, } impl Future for AFPluginServiceFuture { - type Output = Result; + type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let (_, response) = ready!(self.as_mut().project().fut.poll(cx))?.into_parts(); - Poll::Ready(Ok(response)) - } + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let (_, response) = ready!(self.as_mut().project().fut.poll(cx))?.into_parts(); + Poll::Ready(Ok(response)) + } } diff --git a/frontend/rust-lib/lib-dispatch/src/request/payload.rs b/frontend/rust-lib/lib-dispatch/src/request/payload.rs index 4578548212..c371537f36 100644 --- a/frontend/rust-lib/lib-dispatch/src/request/payload.rs +++ b/frontend/rust-lib/lib-dispatch/src/request/payload.rs @@ -7,69 +7,69 @@ pub enum PayloadError {} #[derive(Clone)] #[cfg_attr(feature = "use_serde", derive(serde::Serialize))] pub enum Payload { - None, - Bytes(Bytes), + None, + Bytes(Bytes), } impl Payload { - pub fn to_vec(self) -> Vec { - match self { - Payload::None => vec![], - Payload::Bytes(bytes) => bytes.to_vec(), - } + pub fn to_vec(self) -> Vec { + match self { + Payload::None => vec![], + Payload::Bytes(bytes) => bytes.to_vec(), } + } } impl std::fmt::Debug for Payload { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - format_payload_print(self, f) - } + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + format_payload_print(self, f) + } } impl std::fmt::Display for Payload { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - format_payload_print(self, f) - } + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + format_payload_print(self, f) + } } fn format_payload_print(payload: &Payload, f: &mut Formatter<'_>) -> fmt::Result { - match payload { - Payload::Bytes(bytes) => f.write_fmt(format_args!("{} bytes", bytes.len())), - Payload::None => f.write_str("Empty"), - } + match payload { + Payload::Bytes(bytes) => f.write_fmt(format_args!("{} bytes", bytes.len())), + Payload::None => f.write_str("Empty"), + } } impl std::convert::From for Payload { - fn from(s: String) -> Self { - Payload::Bytes(Bytes::from(s)) - } + fn from(s: String) -> Self { + Payload::Bytes(Bytes::from(s)) + } } impl std::convert::From<&'_ String> for Payload { - fn from(s: &String) -> Self { - Payload::Bytes(Bytes::from(s.to_owned())) - } + fn from(s: &String) -> Self { + Payload::Bytes(Bytes::from(s.to_owned())) + } } impl std::convert::From for Payload { - fn from(bytes: Bytes) -> Self { - Payload::Bytes(bytes) - } + fn from(bytes: Bytes) -> Self { + Payload::Bytes(bytes) + } } impl std::convert::From<()> for Payload { - fn from(_: ()) -> Self { - Payload::None - } + fn from(_: ()) -> Self { + Payload::None + } } impl std::convert::From> for Payload { - fn from(bytes: Vec) -> Self { - Payload::Bytes(Bytes::from(bytes)) - } + fn from(bytes: Vec) -> Self { + Payload::Bytes(Bytes::from(bytes)) + } } impl std::convert::From<&str> for Payload { - fn from(s: &str) -> Self { - s.to_string().into() - } + fn from(s: &str) -> Self { + s.to_string().into() + } } diff --git a/frontend/rust-lib/lib-dispatch/src/request/request.rs b/frontend/rust-lib/lib-dispatch/src/request/request.rs index 32ea3c592a..20af0bbc02 100644 --- a/frontend/rust-lib/lib-dispatch/src/request/request.rs +++ b/frontend/rust-lib/lib-dispatch/src/request/request.rs @@ -1,118 +1,118 @@ use std::future::Future; use crate::{ - errors::{DispatchError, InternalError}, - module::{AFPluginEvent, AFPluginStateMap}, - request::payload::Payload, - util::ready::{ready, Ready}, + errors::{DispatchError, InternalError}, + module::{AFPluginEvent, AFPluginStateMap}, + request::payload::Payload, + util::ready::{ready, Ready}, }; use derivative::*; use futures_core::ready; use std::{ - fmt::Debug, - pin::Pin, - sync::Arc, - task::{Context, Poll}, + fmt::Debug, + pin::Pin, + sync::Arc, + task::{Context, Poll}, }; #[derive(Clone, Debug, Derivative)] pub struct AFPluginEventRequest { - #[allow(dead_code)] - pub(crate) id: String, - pub(crate) event: AFPluginEvent, - #[derivative(Debug = "ignore")] - pub(crate) states: Arc, + #[allow(dead_code)] + pub(crate) id: String, + pub(crate) event: AFPluginEvent, + #[derivative(Debug = "ignore")] + pub(crate) states: Arc, } impl AFPluginEventRequest { - pub fn new(id: String, event: E, module_data: Arc) -> AFPluginEventRequest - where - E: Into, - { - Self { - id, - event: event.into(), - states: module_data, - } + pub fn new(id: String, event: E, module_data: Arc) -> AFPluginEventRequest + where + E: Into, + { + Self { + id, + event: event.into(), + states: module_data, + } + } + + pub fn get_state(&self) -> Option<&T> + where + T: Send + Sync, + { + if let Some(data) = self.states.get::() { + return Some(data); } - pub fn get_state(&self) -> Option<&T> - where - T: Send + Sync, - { - if let Some(data) = self.states.get::() { - return Some(data); - } - - None - } + None + } } pub trait FromAFPluginRequest: Sized { - type Error: Into; - type Future: Future>; + type Error: Into; + type Future: Future>; - fn from_request(req: &AFPluginEventRequest, payload: &mut Payload) -> Self::Future; + fn from_request(req: &AFPluginEventRequest, payload: &mut Payload) -> Self::Future; } #[doc(hidden)] impl FromAFPluginRequest for () { - type Error = DispatchError; - type Future = Ready>; + type Error = DispatchError; + type Future = Ready>; - fn from_request(_req: &AFPluginEventRequest, _payload: &mut Payload) -> Self::Future { - ready(Ok(())) - } + fn from_request(_req: &AFPluginEventRequest, _payload: &mut Payload) -> Self::Future { + ready(Ok(())) + } } #[doc(hidden)] impl FromAFPluginRequest for String { - type Error = DispatchError; - type Future = Ready>; + type Error = DispatchError; + type Future = Ready>; - fn from_request(req: &AFPluginEventRequest, payload: &mut Payload) -> Self::Future { - match &payload { - Payload::None => ready(Err(unexpected_none_payload(req))), - Payload::Bytes(buf) => ready(Ok(String::from_utf8_lossy(buf).into_owned())), - } + fn from_request(req: &AFPluginEventRequest, payload: &mut Payload) -> Self::Future { + match &payload { + Payload::None => ready(Err(unexpected_none_payload(req))), + Payload::Bytes(buf) => ready(Ok(String::from_utf8_lossy(buf).into_owned())), } + } } pub fn unexpected_none_payload(request: &AFPluginEventRequest) -> DispatchError { - log::warn!("{:?} expected payload", &request.event); - InternalError::UnexpectedNone("Expected payload".to_string()).into() + log::warn!("{:?} expected payload", &request.event); + InternalError::UnexpectedNone("Expected payload".to_string()).into() } #[doc(hidden)] impl FromAFPluginRequest for Result where - T: FromAFPluginRequest, + T: FromAFPluginRequest, { - type Error = DispatchError; - type Future = FromRequestFuture; + type Error = DispatchError; + type Future = FromRequestFuture; - fn from_request(req: &AFPluginEventRequest, payload: &mut Payload) -> Self::Future { - FromRequestFuture { - fut: T::from_request(req, payload), - } + fn from_request(req: &AFPluginEventRequest, payload: &mut Payload) -> Self::Future { + FromRequestFuture { + fut: T::from_request(req, payload), } + } } #[pin_project::pin_project] pub struct FromRequestFuture { - #[pin] - fut: Fut, + #[pin] + fut: Fut, } impl Future for FromRequestFuture where - Fut: Future>, + Fut: Future>, { - type Output = Result, DispatchError>; + type Output = Result, DispatchError>; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - let res = ready!(this.fut.poll(cx)); - Poll::Ready(Ok(res)) - } + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let res = ready!(this.fut.poll(cx)); + Poll::Ready(Ok(res)) + } } diff --git a/frontend/rust-lib/lib-dispatch/src/response/builder.rs b/frontend/rust-lib/lib-dispatch/src/response/builder.rs index 9a72756256..8156684665 100644 --- a/frontend/rust-lib/lib-dispatch/src/response/builder.rs +++ b/frontend/rust-lib/lib-dispatch/src/response/builder.rs @@ -1,42 +1,42 @@ use crate::{ - request::Payload, - response::{AFPluginEventResponse, StatusCode}, + request::Payload, + response::{AFPluginEventResponse, StatusCode}, }; macro_rules! static_response { - ($name:ident, $status:expr) => { - #[allow(non_snake_case, missing_docs)] - pub fn $name() -> ResponseBuilder { - ResponseBuilder::new($status) - } - }; + ($name:ident, $status:expr) => { + #[allow(non_snake_case, missing_docs)] + pub fn $name() -> ResponseBuilder { + ResponseBuilder::new($status) + } + }; } pub struct ResponseBuilder { - pub payload: T, - pub status: StatusCode, + pub payload: T, + pub status: StatusCode, } impl ResponseBuilder { - pub fn new(status: StatusCode) -> Self { - ResponseBuilder { - payload: Payload::None, - status, - } + pub fn new(status: StatusCode) -> Self { + ResponseBuilder { + payload: Payload::None, + status, } + } - pub fn data>(mut self, data: D) -> Self { - self.payload = data.into(); - self + pub fn data>(mut self, data: D) -> Self { + self.payload = data.into(); + self + } + + pub fn build(self) -> AFPluginEventResponse { + AFPluginEventResponse { + payload: self.payload, + status_code: self.status, } + } - pub fn build(self) -> AFPluginEventResponse { - AFPluginEventResponse { - payload: self.payload, - status_code: self.status, - } - } - - static_response!(Ok, StatusCode::Ok); - static_response!(Err, StatusCode::Err); + static_response!(Ok, StatusCode::Ok); + static_response!(Err, StatusCode::Err); } diff --git a/frontend/rust-lib/lib-dispatch/src/response/responder.rs b/frontend/rust-lib/lib-dispatch/src/response/responder.rs index 0b25d1f2b9..f0f56c2e24 100644 --- a/frontend/rust-lib/lib-dispatch/src/response/responder.rs +++ b/frontend/rust-lib/lib-dispatch/src/response/responder.rs @@ -1,23 +1,23 @@ #[allow(unused_imports)] use crate::errors::{DispatchError, InternalError}; use crate::{ - request::AFPluginEventRequest, - response::{AFPluginEventResponse, ResponseBuilder}, + request::AFPluginEventRequest, + response::{AFPluginEventResponse, ResponseBuilder}, }; use bytes::Bytes; pub trait AFPluginResponder { - fn respond_to(self, req: &AFPluginEventRequest) -> AFPluginEventResponse; + fn respond_to(self, req: &AFPluginEventRequest) -> AFPluginEventResponse; } macro_rules! impl_responder { - ($res: ty) => { - impl AFPluginResponder for $res { - fn respond_to(self, _: &AFPluginEventRequest) -> AFPluginEventResponse { - ResponseBuilder::Ok().data(self).build() - } - } - }; + ($res: ty) => { + impl AFPluginResponder for $res { + fn respond_to(self, _: &AFPluginEventRequest) -> AFPluginEventResponse { + ResponseBuilder::Ok().data(self).build() + } + } + }; } impl_responder!(&'static str); @@ -29,13 +29,13 @@ impl_responder!(Vec); impl AFPluginResponder for Result where - T: AFPluginResponder, - E: Into, + T: AFPluginResponder, + E: Into, { - fn respond_to(self, request: &AFPluginEventRequest) -> AFPluginEventResponse { - match self { - Ok(val) => val.respond_to(request), - Err(e) => e.into().into(), - } + fn respond_to(self, request: &AFPluginEventRequest) -> AFPluginEventResponse { + match self { + Ok(val) => val.respond_to(request), + Err(e) => e.into().into(), } + } } diff --git a/frontend/rust-lib/lib-dispatch/src/response/response.rs b/frontend/rust-lib/lib-dispatch/src/response/response.rs index 650e4de83c..feb16f251c 100644 --- a/frontend/rust-lib/lib-dispatch/src/response/response.rs +++ b/frontend/rust-lib/lib-dispatch/src/response/response.rs @@ -1,9 +1,9 @@ use crate::{ - byte_trait::AFPluginFromBytes, - data::AFPluginData, - errors::DispatchError, - request::{AFPluginEventRequest, Payload}, - response::AFPluginResponder, + byte_trait::AFPluginFromBytes, + data::AFPluginData, + errors::DispatchError, + request::{AFPluginEventRequest, Payload}, + response::AFPluginResponder, }; use derivative::*; use std::{convert::TryFrom, fmt, fmt::Formatter}; @@ -12,70 +12,70 @@ use std::{convert::TryFrom, fmt, fmt::Formatter}; #[cfg_attr(feature = "use_serde", derive(serde_repr::Serialize_repr))] #[repr(u8)] pub enum StatusCode { - Ok = 0, - Err = 1, + Ok = 0, + Err = 1, } // serde user guide: https://serde.rs/field-attrs.html #[derive(Debug, Clone, Derivative)] #[cfg_attr(feature = "use_serde", derive(serde::Serialize))] pub struct AFPluginEventResponse { - #[derivative(Debug = "ignore")] - pub payload: Payload, - pub status_code: StatusCode, + #[derivative(Debug = "ignore")] + pub payload: Payload, + pub status_code: StatusCode, } impl AFPluginEventResponse { - pub fn new(status_code: StatusCode) -> Self { - AFPluginEventResponse { - payload: Payload::None, - status_code, - } + pub fn new(status_code: StatusCode) -> Self { + AFPluginEventResponse { + payload: Payload::None, + status_code, } + } - pub fn parse(self) -> Result, DispatchError> - where - T: AFPluginFromBytes, - E: AFPluginFromBytes, - { - match self.status_code { - StatusCode::Ok => { - let data = >::try_from(self.payload)?; - Ok(Ok(data.into_inner())) - } - StatusCode::Err => { - let err = >::try_from(self.payload)?; - Ok(Err(err.into_inner())) - } - } + pub fn parse(self) -> Result, DispatchError> + where + T: AFPluginFromBytes, + E: AFPluginFromBytes, + { + match self.status_code { + StatusCode::Ok => { + let data = >::try_from(self.payload)?; + Ok(Ok(data.into_inner())) + }, + StatusCode::Err => { + let err = >::try_from(self.payload)?; + Ok(Err(err.into_inner())) + }, } + } } impl std::fmt::Display for AFPluginEventResponse { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_fmt(format_args!("Status_Code: {:?}", self.status_code))?; + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("Status_Code: {:?}", self.status_code))?; - match &self.payload { - Payload::Bytes(b) => f.write_fmt(format_args!("Data: {} bytes", b.len()))?, - Payload::None => f.write_fmt(format_args!("Data: Empty"))?, - } - - Ok(()) + match &self.payload { + Payload::Bytes(b) => f.write_fmt(format_args!("Data: {} bytes", b.len()))?, + Payload::None => f.write_fmt(format_args!("Data: Empty"))?, } + + Ok(()) + } } impl AFPluginResponder for AFPluginEventResponse { - #[inline] - fn respond_to(self, _: &AFPluginEventRequest) -> AFPluginEventResponse { - self - } + #[inline] + fn respond_to(self, _: &AFPluginEventRequest) -> AFPluginEventResponse { + self + } } pub type DataResult = std::result::Result, E>; pub fn data_result(data: T) -> Result, E> where - E: Into, + E: Into, { - Ok(AFPluginData(data)) + Ok(AFPluginData(data)) } diff --git a/frontend/rust-lib/lib-dispatch/src/runtime.rs b/frontend/rust-lib/lib-dispatch/src/runtime.rs index b622332d78..656612b359 100644 --- a/frontend/rust-lib/lib-dispatch/src/runtime.rs +++ b/frontend/rust-lib/lib-dispatch/src/runtime.rs @@ -4,23 +4,23 @@ use tokio::runtime; pub type AFPluginRuntime = tokio::runtime::Runtime; pub fn tokio_default_runtime() -> io::Result { - runtime::Builder::new_multi_thread() - .thread_name("dispatch-rt") - .enable_io() - .enable_time() - .on_thread_start(move || { - tracing::trace!( - "{:?} thread started: thread_id= {}", - thread::current(), - thread_id::get() - ); - }) - .on_thread_stop(move || { - tracing::trace!( - "{:?} thread stopping: thread_id= {}", - thread::current(), - thread_id::get(), - ); - }) - .build() + runtime::Builder::new_multi_thread() + .thread_name("dispatch-rt") + .enable_io() + .enable_time() + .on_thread_start(move || { + tracing::trace!( + "{:?} thread started: thread_id= {}", + thread::current(), + thread_id::get() + ); + }) + .on_thread_stop(move || { + tracing::trace!( + "{:?} thread stopping: thread_id= {}", + thread::current(), + thread_id::get(), + ); + }) + .build() } diff --git a/frontend/rust-lib/lib-dispatch/src/service/boxed.rs b/frontend/rust-lib/lib-dispatch/src/service/boxed.rs index 5d629e9101..6d2a72e843 100644 --- a/frontend/rust-lib/lib-dispatch/src/service/boxed.rs +++ b/frontend/rust-lib/lib-dispatch/src/service/boxed.rs @@ -3,51 +3,54 @@ use futures_core::future::BoxFuture; pub fn factory(factory: SF) -> BoxServiceFactory where - SF: AFPluginServiceFactory + 'static + Sync + Send, - Req: 'static, - SF::Response: 'static, - SF::Service: 'static, - SF::Future: 'static, - SF::Error: 'static + Send + Sync, - >::Service: Sync + Send, - <>::Service as Service>::Future: Send + Sync, - >::Future: Send + Sync, + SF: AFPluginServiceFactory + 'static + Sync + Send, + Req: 'static, + SF::Response: 'static, + SF::Service: 'static, + SF::Future: 'static, + SF::Error: 'static + Send + Sync, + >::Service: Sync + Send, + <>::Service as Service>::Future: Send + Sync, + >::Future: Send + Sync, { - BoxServiceFactory(Box::new(FactoryWrapper(factory))) + BoxServiceFactory(Box::new(FactoryWrapper(factory))) } type Inner = Box< - dyn AFPluginServiceFactory< - Req, - Context = Cfg, - Response = Res, - Error = Err, - Service = BoxService, - Future = BoxFuture<'static, Result, Err>>, - > + Sync - + Send, + dyn AFPluginServiceFactory< + Req, + Context = Cfg, + Response = Res, + Error = Err, + Service = BoxService, + Future = BoxFuture<'static, Result, Err>>, + > + Sync + + Send, >; pub struct BoxServiceFactory(Inner); impl AFPluginServiceFactory for BoxServiceFactory where - Req: 'static, - Res: 'static, - Err: 'static, + Req: 'static, + Res: 'static, + Err: 'static, { - type Response = Res; - type Error = Err; - type Service = BoxService; - type Context = Cfg; - type Future = BoxFuture<'static, Result>; + type Response = Res; + type Error = Err; + type Service = BoxService; + type Context = Cfg; + type Future = BoxFuture<'static, Result>; - fn new_service(&self, cfg: Cfg) -> Self::Future { - self.0.new_service(cfg) - } + fn new_service(&self, cfg: Cfg) -> Self::Future { + self.0.new_service(cfg) + } } -pub type BoxService = - Box>> + Sync + Send>; +pub type BoxService = Box< + dyn Service>> + + Sync + + Send, +>; // #[allow(dead_code)] // pub fn service(service: S) -> BoxService @@ -61,62 +64,65 @@ pub type BoxService = impl Service for Box where - S: Service + ?Sized, + S: Service + ?Sized, { - type Response = S::Response; - type Error = S::Error; - type Future = S::Future; + type Response = S::Response; + type Error = S::Error; + type Future = S::Future; - fn call(&self, request: Req) -> S::Future { - (**self).call(request) - } + fn call(&self, request: Req) -> S::Future { + (**self).call(request) + } } struct ServiceWrapper { - inner: S, + inner: S, } impl ServiceWrapper { - fn new(inner: S) -> Self { - Self { inner } - } + fn new(inner: S) -> Self { + Self { inner } + } } impl Service for ServiceWrapper where - S: Service, - S::Future: 'static + Send + Sync, + S: Service, + S::Future: 'static + Send + Sync, { - type Response = Res; - type Error = Err; - type Future = BoxFuture<'static, Result>; + type Response = Res; + type Error = Err; + type Future = BoxFuture<'static, Result>; - fn call(&self, req: Req) -> Self::Future { - Box::pin(self.inner.call(req)) - } + fn call(&self, req: Req) -> Self::Future { + Box::pin(self.inner.call(req)) + } } struct FactoryWrapper(SF); impl AFPluginServiceFactory for FactoryWrapper where - Req: 'static, - Res: 'static, - Err: 'static, - SF: AFPluginServiceFactory, - SF::Future: 'static, - SF::Service: 'static + Send + Sync, - <>::Service as Service>::Future: Send + Sync + 'static, - >::Future: Send + Sync, + Req: 'static, + Res: 'static, + Err: 'static, + SF: AFPluginServiceFactory, + SF::Future: 'static, + SF::Service: 'static + Send + Sync, + <>::Service as Service>::Future: Send + Sync + 'static, + >::Future: Send + Sync, { - type Response = Res; - type Error = Err; - type Service = BoxService; - type Context = Cfg; - type Future = BoxFuture<'static, Result>; + type Response = Res; + type Error = Err; + type Service = BoxService; + type Context = Cfg; + type Future = BoxFuture<'static, Result>; - fn new_service(&self, cfg: Cfg) -> Self::Future { - let f = self.0.new_service(cfg); - Box::pin(async { f.await.map(|s| Box::new(ServiceWrapper::new(s)) as Self::Service) }) - } + fn new_service(&self, cfg: Cfg) -> Self::Future { + let f = self.0.new_service(cfg); + Box::pin(async { + f.await + .map(|s| Box::new(ServiceWrapper::new(s)) as Self::Service) + }) + } } diff --git a/frontend/rust-lib/lib-dispatch/src/service/handler.rs b/frontend/rust-lib/lib-dispatch/src/service/handler.rs index 52c9081983..c55d4d0b16 100644 --- a/frontend/rust-lib/lib-dispatch/src/service/handler.rs +++ b/frontend/rust-lib/lib-dispatch/src/service/handler.rs @@ -1,155 +1,155 @@ use std::{ - future::Future, - marker::PhantomData, - pin::Pin, - task::{Context, Poll}, + future::Future, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, }; use futures_core::ready; use pin_project::pin_project; use crate::{ - errors::DispatchError, - request::{payload::Payload, AFPluginEventRequest, FromAFPluginRequest}, - response::{AFPluginEventResponse, AFPluginResponder}, - service::{AFPluginServiceFactory, Service, ServiceRequest, ServiceResponse}, - util::ready::*, + errors::DispatchError, + request::{payload::Payload, AFPluginEventRequest, FromAFPluginRequest}, + response::{AFPluginEventResponse, AFPluginResponder}, + service::{AFPluginServiceFactory, Service, ServiceRequest, ServiceResponse}, + util::ready::*, }; /// A closure that is run every time for the specified plugin event pub trait AFPluginHandler: Clone + 'static + Sync + Send where - R: Future + Send + Sync, - R::Output: AFPluginResponder, + R: Future + Send + Sync, + R::Output: AFPluginResponder, { - fn call(&self, param: T) -> R; + fn call(&self, param: T) -> R; } pub struct AFPluginHandlerService where - H: AFPluginHandler, - T: FromAFPluginRequest, - R: Future + Sync + Send, - R::Output: AFPluginResponder, + H: AFPluginHandler, + T: FromAFPluginRequest, + R: Future + Sync + Send, + R::Output: AFPluginResponder, { - handler: H, - _phantom: PhantomData<(T, R)>, + handler: H, + _phantom: PhantomData<(T, R)>, } impl AFPluginHandlerService where - H: AFPluginHandler, - T: FromAFPluginRequest, - R: Future + Sync + Send, - R::Output: AFPluginResponder, + H: AFPluginHandler, + T: FromAFPluginRequest, + R: Future + Sync + Send, + R::Output: AFPluginResponder, { - pub fn new(handler: H) -> Self { - Self { - handler, - _phantom: PhantomData, - } + pub fn new(handler: H) -> Self { + Self { + handler, + _phantom: PhantomData, } + } } impl Clone for AFPluginHandlerService where - H: AFPluginHandler, - T: FromAFPluginRequest, - R: Future + Sync + Send, - R::Output: AFPluginResponder, + H: AFPluginHandler, + T: FromAFPluginRequest, + R: Future + Sync + Send, + R::Output: AFPluginResponder, { - fn clone(&self) -> Self { - Self { - handler: self.handler.clone(), - _phantom: PhantomData, - } + fn clone(&self) -> Self { + Self { + handler: self.handler.clone(), + _phantom: PhantomData, } + } } impl AFPluginServiceFactory for AFPluginHandlerService where - F: AFPluginHandler, - T: FromAFPluginRequest, - R: Future + Send + Sync, - R::Output: AFPluginResponder, + F: AFPluginHandler, + T: FromAFPluginRequest, + R: Future + Send + Sync, + R::Output: AFPluginResponder, { - type Response = ServiceResponse; - type Error = DispatchError; - type Service = Self; - type Context = (); - type Future = Ready>; + type Response = ServiceResponse; + type Error = DispatchError; + type Service = Self; + type Context = (); + type Future = Ready>; - fn new_service(&self, _: ()) -> Self::Future { - ready(Ok(self.clone())) - } + fn new_service(&self, _: ()) -> Self::Future { + ready(Ok(self.clone())) + } } impl Service for AFPluginHandlerService where - H: AFPluginHandler, - T: FromAFPluginRequest, - R: Future + Sync + Send, - R::Output: AFPluginResponder, + H: AFPluginHandler, + T: FromAFPluginRequest, + R: Future + Sync + Send, + R::Output: AFPluginResponder, { - type Response = ServiceResponse; - type Error = DispatchError; - type Future = HandlerServiceFuture; + type Response = ServiceResponse; + type Error = DispatchError; + type Future = HandlerServiceFuture; - fn call(&self, req: ServiceRequest) -> Self::Future { - let (req, mut payload) = req.into_parts(); - let fut = T::from_request(&req, &mut payload); - HandlerServiceFuture::Extract(fut, Some(req), self.handler.clone()) - } + fn call(&self, req: ServiceRequest) -> Self::Future { + let (req, mut payload) = req.into_parts(); + let fut = T::from_request(&req, &mut payload); + HandlerServiceFuture::Extract(fut, Some(req), self.handler.clone()) + } } #[pin_project(project = HandlerServiceProj)] pub enum HandlerServiceFuture where - H: AFPluginHandler, - T: FromAFPluginRequest, - R: Future + Sync + Send, - R::Output: AFPluginResponder, + H: AFPluginHandler, + T: FromAFPluginRequest, + R: Future + Sync + Send, + R::Output: AFPluginResponder, { - Extract(#[pin] T::Future, Option, H), - Handle(#[pin] R, Option), + Extract(#[pin] T::Future, Option, H), + Handle(#[pin] R, Option), } impl Future for HandlerServiceFuture where - F: AFPluginHandler, - T: FromAFPluginRequest, - R: Future + Sync + Send, - R::Output: AFPluginResponder, + F: AFPluginHandler, + T: FromAFPluginRequest, + R: Future + Sync + Send, + R::Output: AFPluginResponder, { - type Output = Result; + type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - loop { - match self.as_mut().project() { - HandlerServiceProj::Extract(fut, req, handle) => { - match ready!(fut.poll(cx)) { - Ok(params) => { - let fut = handle.call(params); - let state = HandlerServiceFuture::Handle(fut, req.take()); - self.as_mut().set(state); - } - Err(err) => { - let req = req.take().unwrap(); - let system_err: DispatchError = err.into(); - let res: AFPluginEventResponse = system_err.into(); - return Poll::Ready(Ok(ServiceResponse::new(req, res))); - } - }; - } - HandlerServiceProj::Handle(fut, req) => { - let result = ready!(fut.poll(cx)); - let req = req.take().unwrap(); - let resp = result.respond_to(&req); - return Poll::Ready(Ok(ServiceResponse::new(req, resp))); - } - } - } + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + loop { + match self.as_mut().project() { + HandlerServiceProj::Extract(fut, req, handle) => { + match ready!(fut.poll(cx)) { + Ok(params) => { + let fut = handle.call(params); + let state = HandlerServiceFuture::Handle(fut, req.take()); + self.as_mut().set(state); + }, + Err(err) => { + let req = req.take().unwrap(); + let system_err: DispatchError = err.into(); + let res: AFPluginEventResponse = system_err.into(); + return Poll::Ready(Ok(ServiceResponse::new(req, res))); + }, + }; + }, + HandlerServiceProj::Handle(fut, req) => { + let result = ready!(fut.poll(cx)); + let req = req.take().unwrap(); + let resp = result.respond_to(&req); + return Poll::Ready(Ok(ServiceResponse::new(req, resp))); + }, + } } + } } macro_rules! factory_tuple ({ $($param:ident)* } => { diff --git a/frontend/rust-lib/lib-dispatch/src/service/service.rs b/frontend/rust-lib/lib-dispatch/src/service/service.rs index b1fed6e1aa..c807f4f4d6 100644 --- a/frontend/rust-lib/lib-dispatch/src/service/service.rs +++ b/frontend/rust-lib/lib-dispatch/src/service/service.rs @@ -1,57 +1,60 @@ use std::future::Future; use crate::{ - request::{payload::Payload, AFPluginEventRequest}, - response::AFPluginEventResponse, + request::{payload::Payload, AFPluginEventRequest}, + response::AFPluginEventResponse, }; pub trait Service { - type Response; - type Error; - type Future: Future>; + type Response; + type Error; + type Future: Future>; - fn call(&self, req: Request) -> Self::Future; + fn call(&self, req: Request) -> Self::Future; } /// Returns a future that can handle the request. For the moment, the request will be the /// `AFPluginRequest` pub trait AFPluginServiceFactory { - type Response; - type Error; - type Service: Service; - type Context; - type Future: Future>; + type Response; + type Error; + type Service: Service; + type Context; + type Future: Future>; - fn new_service(&self, cfg: Self::Context) -> Self::Future; + fn new_service(&self, cfg: Self::Context) -> Self::Future; } pub(crate) struct ServiceRequest { - event_state: AFPluginEventRequest, - payload: Payload, + event_state: AFPluginEventRequest, + payload: Payload, } impl ServiceRequest { - pub(crate) fn new(event_state: AFPluginEventRequest, payload: Payload) -> Self { - Self { event_state, payload } + pub(crate) fn new(event_state: AFPluginEventRequest, payload: Payload) -> Self { + Self { + event_state, + payload, } + } - #[inline] - pub(crate) fn into_parts(self) -> (AFPluginEventRequest, Payload) { - (self.event_state, self.payload) - } + #[inline] + pub(crate) fn into_parts(self) -> (AFPluginEventRequest, Payload) { + (self.event_state, self.payload) + } } pub struct ServiceResponse { - request: AFPluginEventRequest, - response: AFPluginEventResponse, + request: AFPluginEventRequest, + response: AFPluginEventResponse, } impl ServiceResponse { - pub fn new(request: AFPluginEventRequest, response: AFPluginEventResponse) -> Self { - ServiceResponse { request, response } - } + pub fn new(request: AFPluginEventRequest, response: AFPluginEventResponse) -> Self { + ServiceResponse { request, response } + } - pub fn into_parts(self) -> (AFPluginEventRequest, AFPluginEventResponse) { - (self.request, self.response) - } + pub fn into_parts(self) -> (AFPluginEventRequest, AFPluginEventResponse) { + (self.request, self.response) + } } diff --git a/frontend/rust-lib/lib-dispatch/src/util/ready.rs b/frontend/rust-lib/lib-dispatch/src/util/ready.rs index 80720b5803..b0e5345717 100644 --- a/frontend/rust-lib/lib-dispatch/src/util/ready.rs +++ b/frontend/rust-lib/lib-dispatch/src/util/ready.rs @@ -1,32 +1,32 @@ use std::{ - future::Future, - pin::Pin, - task::{Context, Poll}, + future::Future, + pin::Pin, + task::{Context, Poll}, }; pub struct Ready { - val: Option, + val: Option, } impl Ready { - #[inline] - pub fn into_inner(mut self) -> T { - self.val.take().unwrap() - } + #[inline] + pub fn into_inner(mut self) -> T { + self.val.take().unwrap() + } } impl Unpin for Ready {} impl Future for Ready { - type Output = T; + type Output = T; - #[inline] - fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { - let val = self.val.take().expect("Ready polled after completion"); - Poll::Ready(val) - } + #[inline] + fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + let val = self.val.take().expect("Ready polled after completion"); + Poll::Ready(val) + } } pub fn ready(val: T) -> Ready { - Ready { val: Some(val) } + Ready { val: Some(val) } } diff --git a/frontend/rust-lib/lib-dispatch/tests/api/module.rs b/frontend/rust-lib/lib-dispatch/tests/api/module.rs index 90355d8b9b..cf68fa583b 100644 --- a/frontend/rust-lib/lib-dispatch/tests/api/module.rs +++ b/frontend/rust-lib/lib-dispatch/tests/api/module.rs @@ -3,23 +3,23 @@ use lib_dispatch::runtime::tokio_default_runtime; use std::sync::Arc; pub async fn hello() -> String { - "say hello".to_string() + "say hello".to_string() } #[tokio::test] async fn test() { - let event = "1"; - let runtime = tokio_default_runtime().unwrap(); - let dispatch = Arc::new(AFPluginDispatcher::construct(runtime, || { - vec![AFPlugin::new().event(event, hello)] - })); - let request = AFPluginRequest::new(event); - let _ = AFPluginDispatcher::async_send_with_callback(dispatch.clone(), request, |resp| { - Box::pin(async move { - dbg!(&resp); - }) + let event = "1"; + let runtime = tokio_default_runtime().unwrap(); + let dispatch = Arc::new(AFPluginDispatcher::construct(runtime, || { + vec![AFPlugin::new().event(event, hello)] + })); + let request = AFPluginRequest::new(event); + let _ = AFPluginDispatcher::async_send_with_callback(dispatch.clone(), request, |resp| { + Box::pin(async move { + dbg!(&resp); }) - .await; + }) + .await; - std::mem::forget(dispatch); + std::mem::forget(dispatch); } diff --git a/frontend/rust-lib/lib-log/src/layer.rs b/frontend/rust-lib/lib-log/src/layer.rs index b2eaabcc22..370893f375 100644 --- a/frontend/rust-lib/lib-log/src/layer.rs +++ b/frontend/rust-lib/lib-log/src/layer.rs @@ -17,195 +17,205 @@ const FLOWY_RESERVED_FIELDS: [&str; 3] = [LEVEL, TIME, MESSAGE]; const IGNORE_FIELDS: [&str; 2] = [LOG_MODULE_PATH, LOG_TARGET_PATH]; pub struct FlowyFormattingLayer { - make_writer: W, - with_target: bool, + make_writer: W, + with_target: bool, } impl FlowyFormattingLayer { - pub fn new(make_writer: W) -> Self { - Self { - make_writer, - with_target: false, + pub fn new(make_writer: W) -> Self { + Self { + make_writer, + with_target: false, + } + } + + fn serialize_flowy_folder_fields( + &self, + map_serializer: &mut impl SerializeMap, + message: &str, + _level: &Level, + ) -> Result<(), std::io::Error> { + map_serializer.serialize_entry(MESSAGE, &message)?; + // map_serializer.serialize_entry(LEVEL, &format!("{}", level))?; + // map_serializer.serialize_entry(TIME, &chrono::Utc::now().timestamp())?; + Ok(()) + } + + fn serialize_span tracing_subscriber::registry::LookupSpan<'a>>( + &self, + span: &SpanRef, + ty: Type, + ) -> Result, std::io::Error> { + let mut buffer = Vec::new(); + let mut serializer = serde_json::Serializer::new(&mut buffer); + let mut map_serializer = serializer.serialize_map(None)?; + let message = format_span_context(span, ty); + self.serialize_flowy_folder_fields(&mut map_serializer, &message, span.metadata().level())?; + if self.with_target { + map_serializer.serialize_entry("target", &span.metadata().target())?; + } + + map_serializer.serialize_entry("line", &span.metadata().line())?; + map_serializer.serialize_entry("file", &span.metadata().file())?; + + let extensions = span.extensions(); + if let Some(visitor) = extensions.get::() { + for (key, value) in visitor.values() { + if !FLOWY_RESERVED_FIELDS.contains(key) && !IGNORE_FIELDS.contains(key) { + map_serializer.serialize_entry(key, value)?; + } else { + tracing::debug!( + "{} is a reserved field in the bunyan log format. Skipping it.", + key + ); } + } } + map_serializer.end()?; + Ok(buffer) + } - fn serialize_flowy_folder_fields( - &self, - map_serializer: &mut impl SerializeMap, - message: &str, - _level: &Level, - ) -> Result<(), std::io::Error> { - map_serializer.serialize_entry(MESSAGE, &message)?; - // map_serializer.serialize_entry(LEVEL, &format!("{}", level))?; - // map_serializer.serialize_entry(TIME, &chrono::Utc::now().timestamp())?; - Ok(()) - } - - fn serialize_span tracing_subscriber::registry::LookupSpan<'a>>( - &self, - span: &SpanRef, - ty: Type, - ) -> Result, std::io::Error> { - let mut buffer = Vec::new(); - let mut serializer = serde_json::Serializer::new(&mut buffer); - let mut map_serializer = serializer.serialize_map(None)?; - let message = format_span_context(span, ty); - self.serialize_flowy_folder_fields(&mut map_serializer, &message, span.metadata().level())?; - if self.with_target { - map_serializer.serialize_entry("target", &span.metadata().target())?; - } - - map_serializer.serialize_entry("line", &span.metadata().line())?; - map_serializer.serialize_entry("file", &span.metadata().file())?; - - let extensions = span.extensions(); - if let Some(visitor) = extensions.get::() { - for (key, value) in visitor.values() { - if !FLOWY_RESERVED_FIELDS.contains(key) && !IGNORE_FIELDS.contains(key) { - map_serializer.serialize_entry(key, value)?; - } else { - tracing::debug!("{} is a reserved field in the bunyan log format. Skipping it.", key); - } - } - } - map_serializer.end()?; - Ok(buffer) - } - - fn emit(&self, mut buffer: Vec) -> Result<(), std::io::Error> { - buffer.write_all(b"\n")?; - self.make_writer.make_writer().write_all(&buffer) - } + fn emit(&self, mut buffer: Vec) -> Result<(), std::io::Error> { + buffer.write_all(b"\n")?; + self.make_writer.make_writer().write_all(&buffer) + } } /// The type of record we are dealing with: entering a span, exiting a span, an /// event. #[derive(Clone, Debug)] pub enum Type { - EnterSpan, - ExitSpan, - Event, + EnterSpan, + ExitSpan, + Event, } impl fmt::Display for Type { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let repr = match self { - Type::EnterSpan => "START", - Type::ExitSpan => "END", - Type::Event => "EVENT", - }; - write!(f, "{}", repr) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let repr = match self { + Type::EnterSpan => "START", + Type::ExitSpan => "END", + Type::Event => "EVENT", + }; + write!(f, "{}", repr) + } } fn format_span_context tracing_subscriber::registry::LookupSpan<'a>>( - span: &SpanRef, - ty: Type, + span: &SpanRef, + ty: Type, ) -> String { - format!("[⛳ {} - {}]", span.metadata().name().to_uppercase(), ty) + format!("[⛳ {} - {}]", span.metadata().name().to_uppercase(), ty) } fn format_event_message tracing_subscriber::registry::LookupSpan<'a>>( - current_span: &Option>, - event: &Event, - event_visitor: &JsonStorage<'_>, + current_span: &Option>, + event: &Event, + event_visitor: &JsonStorage<'_>, ) -> String { - // Extract the "message" field, if provided. Fallback to the target, if missing. - let mut message = event_visitor - .values() - .get("message") - .and_then(|v| match v { - Value::String(s) => Some(s.as_str()), - _ => None, - }) - .unwrap_or_else(|| event.metadata().target()) - .to_owned(); + // Extract the "message" field, if provided. Fallback to the target, if missing. + let mut message = event_visitor + .values() + .get("message") + .and_then(|v| match v { + Value::String(s) => Some(s.as_str()), + _ => None, + }) + .unwrap_or_else(|| event.metadata().target()) + .to_owned(); - // If the event is in the context of a span, prepend the span name to the - // message. - if let Some(span) = ¤t_span { - message = format!("{} {}", format_span_context(span, Type::Event), message); - } + // If the event is in the context of a span, prepend the span name to the + // message. + if let Some(span) = ¤t_span { + message = format!("{} {}", format_span_context(span, Type::Event), message); + } - message + message } impl Layer for FlowyFormattingLayer where - S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, - W: MakeWriter + 'static, + S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, + W: MakeWriter + 'static, { - fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) { - // Events do not necessarily happen in the context of a span, hence - // lookup_current returns an `Option>` instead of a - // `SpanRef<_>`. - let current_span = ctx.lookup_current(); + fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) { + // Events do not necessarily happen in the context of a span, hence + // lookup_current returns an `Option>` instead of a + // `SpanRef<_>`. + let current_span = ctx.lookup_current(); - let mut event_visitor = JsonStorage::default(); - event.record(&mut event_visitor); + let mut event_visitor = JsonStorage::default(); + event.record(&mut event_visitor); - // Opting for a closure to use the ? operator and get more linear code. - let format = || { - let mut buffer = Vec::new(); + // Opting for a closure to use the ? operator and get more linear code. + let format = || { + let mut buffer = Vec::new(); - let mut serializer = serde_json::Serializer::new(&mut buffer); - let mut map_serializer = serializer.serialize_map(None)?; + let mut serializer = serde_json::Serializer::new(&mut buffer); + let mut map_serializer = serializer.serialize_map(None)?; - let message = format_event_message(¤t_span, event, &event_visitor); - self.serialize_flowy_folder_fields(&mut map_serializer, &message, event.metadata().level())?; - // Additional metadata useful for debugging - // They should be nested under `src` (see https://github.com/trentm/node-bunyan#src ) - // but `tracing` does not support nested values yet + let message = format_event_message(¤t_span, event, &event_visitor); + self.serialize_flowy_folder_fields( + &mut map_serializer, + &message, + event.metadata().level(), + )?; + // Additional metadata useful for debugging + // They should be nested under `src` (see https://github.com/trentm/node-bunyan#src ) + // but `tracing` does not support nested values yet - if self.with_target { - map_serializer.serialize_entry("target", event.metadata().target())?; + if self.with_target { + map_serializer.serialize_entry("target", event.metadata().target())?; + } + + // map_serializer.serialize_entry("line", &event.metadata().line())?; + // map_serializer.serialize_entry("file", &event.metadata().file())?; + + // Add all the other fields associated with the event, expect the message we + // already used. + for (key, value) in event_visitor.values().iter().filter(|(&key, _)| { + key != "message" && !FLOWY_RESERVED_FIELDS.contains(&key) && !IGNORE_FIELDS.contains(&key) + }) { + map_serializer.serialize_entry(key, value)?; + } + + // Add all the fields from the current span, if we have one. + if let Some(span) = ¤t_span { + let extensions = span.extensions(); + if let Some(visitor) = extensions.get::() { + for (key, value) in visitor.values() { + if !FLOWY_RESERVED_FIELDS.contains(key) && !IGNORE_FIELDS.contains(key) { + map_serializer.serialize_entry(key, value)?; + } else { + tracing::debug!( + "{} is a reserved field in the flowy log format. Skipping it.", + key + ); } - - // map_serializer.serialize_entry("line", &event.metadata().line())?; - // map_serializer.serialize_entry("file", &event.metadata().file())?; - - // Add all the other fields associated with the event, expect the message we - // already used. - for (key, value) in event_visitor.values().iter().filter(|(&key, _)| { - key != "message" && !FLOWY_RESERVED_FIELDS.contains(&key) && !IGNORE_FIELDS.contains(&key) - }) { - map_serializer.serialize_entry(key, value)?; - } - - // Add all the fields from the current span, if we have one. - if let Some(span) = ¤t_span { - let extensions = span.extensions(); - if let Some(visitor) = extensions.get::() { - for (key, value) in visitor.values() { - if !FLOWY_RESERVED_FIELDS.contains(key) && !IGNORE_FIELDS.contains(key) { - map_serializer.serialize_entry(key, value)?; - } else { - tracing::debug!("{} is a reserved field in the flowy log format. Skipping it.", key); - } - } - } - } - map_serializer.end()?; - Ok(buffer) - }; - - let result: std::io::Result> = format(); - if let Ok(formatted) = result { - let _ = self.emit(formatted); + } } - } + } + map_serializer.end()?; + Ok(buffer) + }; - fn new_span(&self, _attrs: &Attributes, id: &Id, ctx: Context<'_, S>) { - let span = ctx.span(id).expect("Span not found, this is a bug"); - if let Ok(serialized) = self.serialize_span(&span, Type::EnterSpan) { - let _ = self.emit(serialized); - } + let result: std::io::Result> = format(); + if let Ok(formatted) = result { + let _ = self.emit(formatted); } + } - fn on_close(&self, id: Id, ctx: Context<'_, S>) { - let span = ctx.span(&id).expect("Span not found, this is a bug"); - if let Ok(serialized) = self.serialize_span(&span, Type::ExitSpan) { - let _ = self.emit(serialized); - } + fn new_span(&self, _attrs: &Attributes, id: &Id, ctx: Context<'_, S>) { + let span = ctx.span(id).expect("Span not found, this is a bug"); + if let Ok(serialized) = self.serialize_span(&span, Type::EnterSpan) { + let _ = self.emit(serialized); } + } + + fn on_close(&self, id: Id, ctx: Context<'_, S>) { + let span = ctx.span(&id).expect("Span not found, this is a bug"); + if let Ok(serialized) = self.serialize_span(&span, Type::ExitSpan) { + let _ = self.emit(serialized); + } + } } diff --git a/frontend/rust-lib/lib-log/src/lib.rs b/frontend/rust-lib/lib-log/src/lib.rs index 1e68d359ee..d445812746 100644 --- a/frontend/rust-lib/lib-log/src/lib.rs +++ b/frontend/rust-lib/lib-log/src/lib.rs @@ -10,38 +10,38 @@ use tracing_log::LogTracer; use tracing_subscriber::{layer::SubscriberExt, EnvFilter}; lazy_static! { - static ref LOG_GUARD: RwLock> = RwLock::new(None); + static ref LOG_GUARD: RwLock> = RwLock::new(None); } pub struct Builder { - #[allow(dead_code)] - name: String, - env_filter: String, - file_appender: RollingFileAppender, + #[allow(dead_code)] + name: String, + env_filter: String, + file_appender: RollingFileAppender, } impl Builder { - pub fn new(name: &str, directory: &str) -> Self { - // let directory = directory.as_ref().to_str().unwrap().to_owned(); - let local_file_name = format!("{}.log", name); + pub fn new(name: &str, directory: &str) -> Self { + // let directory = directory.as_ref().to_str().unwrap().to_owned(); + let local_file_name = format!("{}.log", name); - Builder { - name: name.to_owned(), - env_filter: "Info".to_owned(), - file_appender: tracing_appender::rolling::daily(directory, local_file_name), - } + Builder { + name: name.to_owned(), + env_filter: "Info".to_owned(), + file_appender: tracing_appender::rolling::daily(directory, local_file_name), } + } - pub fn env_filter(mut self, env_filter: &str) -> Self { - self.env_filter = env_filter.to_owned(); - self - } + pub fn env_filter(mut self, env_filter: &str) -> Self { + self.env_filter = env_filter.to_owned(); + self + } - pub fn build(self) -> std::result::Result<(), String> { - let env_filter = EnvFilter::new(self.env_filter); + pub fn build(self) -> std::result::Result<(), String> { + let env_filter = EnvFilter::new(self.env_filter); - let (non_blocking, guard) = tracing_appender::non_blocking(self.file_appender); - let subscriber = tracing_subscriber::fmt() + let (non_blocking, guard) = tracing_appender::non_blocking(self.file_appender); + let subscriber = tracing_subscriber::fmt() // .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE) .with_ansi(false) .with_target(false) @@ -59,40 +59,43 @@ impl Builder { .with(FlowyFormattingLayer::new(std::io::stdout)) .with(FlowyFormattingLayer::new(non_blocking)); - // if cfg!(feature = "use_bunyan") { - // let formatting_layer = BunyanFormattingLayer::new(self.name.clone(), - // std::io::stdout); let _ = - // set_global_default(subscriber.with(JsonStorageLayer).with(formatting_layer)). - // map_err(|e| format!("{:?}", e))?; } else { - // let _ = set_global_default(subscriber).map_err(|e| format!("{:?}", e))?; - // } + // if cfg!(feature = "use_bunyan") { + // let formatting_layer = BunyanFormattingLayer::new(self.name.clone(), + // std::io::stdout); let _ = + // set_global_default(subscriber.with(JsonStorageLayer).with(formatting_layer)). + // map_err(|e| format!("{:?}", e))?; } else { + // let _ = set_global_default(subscriber).map_err(|e| format!("{:?}", e))?; + // } - set_global_default(subscriber).map_err(|e| format!("{:?}", e))?; - LogTracer::builder() - .with_max_level(LevelFilter::Trace) - .init() - .map_err(|e| format!("{:?}", e))?; + set_global_default(subscriber).map_err(|e| format!("{:?}", e))?; + LogTracer::builder() + .with_max_level(LevelFilter::Trace) + .init() + .map_err(|e| format!("{:?}", e))?; - *LOG_GUARD.write().unwrap() = Some(guard); - Ok(()) - } + *LOG_GUARD.write().unwrap() = Some(guard); + Ok(()) + } } #[cfg(test)] mod tests { - use super::*; - // run cargo test --features="use_bunyan" or cargo test - #[test] - fn test_log() { - Builder::new("flowy", ".").env_filter("debug").build().unwrap(); - tracing::info!("😁 tracing::info call"); - log::debug!("😁 log::debug call"); + use super::*; + // run cargo test --features="use_bunyan" or cargo test + #[test] + fn test_log() { + Builder::new("flowy", ".") + .env_filter("debug") + .build() + .unwrap(); + tracing::info!("😁 tracing::info call"); + log::debug!("😁 log::debug call"); - say("hello world"); - } + say("hello world"); + } - #[tracing::instrument(level = "trace", name = "say")] - fn say(s: &str) { - tracing::info!("{}", s); - } + #[tracing::instrument(level = "trace", name = "say")] + fn say(s: &str) { + tracing::info!("{}", s); + } } diff --git a/frontend/rust-lib/rustfmt.toml b/frontend/rust-lib/rustfmt.toml index 2464575494..5cb0d67ee5 100644 --- a/frontend/rust-lib/rustfmt.toml +++ b/frontend/rust-lib/rustfmt.toml @@ -1,18 +1,12 @@ # https://rust-lang.github.io/rustfmt/?version=master&search= -max_width = 120 -tab_spaces = 4 -# fn_single_line = true -# match_block_trailing_comma = true -# normalize_comments = true -# wrap_comments = true -# use_field_init_shorthand = true -# use_try_shorthand = true -# normalize_doc_attributes = true -# report_todo = "Never" -# report_fixme = "Always" -# imports_layout = "HorizontalVertical" -# imports_granularity = "Crate" -# reorder_modules = true -# reorder_imports = true -# enum_discrim_align_threshold = 20 -edition = "2018" +max_width = 100 +tab_spaces = 2 +newline_style = "Auto" +match_block_trailing_comma = true +use_field_init_shorthand = true +use_try_shorthand = true +reorder_imports = true +reorder_modules = true +remove_nested_parens = true +merge_derives = true +edition = "2021" \ No newline at end of file diff --git a/shared-lib/document-model/src/document.rs b/shared-lib/document-model/src/document.rs index 90886b8532..8e8f654637 100644 --- a/shared-lib/document-model/src/document.rs +++ b/shared-lib/document-model/src/document.rs @@ -3,65 +3,67 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct CreateDocumentParams { - pub doc_id: String, - pub revisions: Vec, + pub doc_id: String, + pub revisions: Vec, } #[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)] pub struct DocumentInfo { - pub doc_id: String, - pub data: Vec, - pub rev_id: i64, - pub base_rev_id: i64, + pub doc_id: String, + pub data: Vec, + pub rev_id: i64, + pub base_rev_id: i64, } impl std::convert::TryFrom for DocumentInfo { - type Error = String; + type Error = String; - fn try_from(revision: Revision) -> Result { - if !revision.is_initial() { - return Err("Revision's rev_id should be 0 when creating the document".to_string()); - } - - Ok(DocumentInfo { - doc_id: revision.object_id, - data: revision.bytes, - rev_id: revision.rev_id, - base_rev_id: revision.base_rev_id, - }) + fn try_from(revision: Revision) -> Result { + if !revision.is_initial() { + return Err("Revision's rev_id should be 0 when creating the document".to_string()); } + + Ok(DocumentInfo { + doc_id: revision.object_id, + data: revision.bytes, + rev_id: revision.rev_id, + base_rev_id: revision.base_rev_id, + }) + } } #[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct ResetDocumentParams { - pub doc_id: String, - pub revisions: Vec, + pub doc_id: String, + pub revisions: Vec, } #[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct DocumentId { - pub value: String, + pub value: String, } impl AsRef for DocumentId { - fn as_ref(&self) -> &str { - &self.value - } + fn as_ref(&self) -> &str { + &self.value + } } impl std::convert::From for DocumentId { - fn from(value: String) -> Self { - DocumentId { value } - } + fn from(value: String) -> Self { + DocumentId { value } + } } impl std::convert::From for String { - fn from(block_id: DocumentId) -> Self { - block_id.value - } + fn from(block_id: DocumentId) -> Self { + block_id.value + } } impl std::convert::From<&String> for DocumentId { - fn from(s: &String) -> Self { - DocumentId { value: s.to_owned() } + fn from(s: &String) -> Self { + DocumentId { + value: s.to_owned(), } + } } diff --git a/shared-lib/flowy-client-network-config/src/configuration.rs b/shared-lib/flowy-client-network-config/src/configuration.rs index 883151dc48..d7a8bcc79d 100644 --- a/shared-lib/flowy-client-network-config/src/configuration.rs +++ b/shared-lib/flowy-client-network-config/src/configuration.rs @@ -5,109 +5,109 @@ pub const HEADER_TOKEN: &str = "token"; #[derive(serde::Deserialize, Clone, Debug)] pub struct ClientServerConfiguration { - #[serde(deserialize_with = "deserialize_number_from_string")] - pub port: u16, - pub host: String, - pub http_scheme: String, - pub ws_scheme: String, + #[serde(deserialize_with = "deserialize_number_from_string")] + pub port: u16, + pub host: String, + pub http_scheme: String, + pub ws_scheme: String, } pub fn get_client_server_configuration() -> Result { - let mut settings = config::Config::default(); - let base = include_str!("../configuration/base.yaml"); - settings.merge(config::File::from_str(base, FileFormat::Yaml).required(true))?; + let mut settings = config::Config::default(); + let base = include_str!("../configuration/base.yaml"); + settings.merge(config::File::from_str(base, FileFormat::Yaml).required(true))?; - let environment: Environment = std::env::var("APP_ENVIRONMENT") - .unwrap_or_else(|_| "local".into()) - .try_into() - .expect("Failed to parse APP_ENVIRONMENT."); + let environment: Environment = std::env::var("APP_ENVIRONMENT") + .unwrap_or_else(|_| "local".into()) + .try_into() + .expect("Failed to parse APP_ENVIRONMENT."); - let custom = match environment { - Environment::Local => include_str!("../configuration/local.yaml"), - Environment::Production => include_str!("../configuration/production.yaml"), - }; + let custom = match environment { + Environment::Local => include_str!("../configuration/local.yaml"), + Environment::Production => include_str!("../configuration/production.yaml"), + }; - settings.merge(config::File::from_str(custom, FileFormat::Yaml).required(true))?; - settings.try_into() + settings.merge(config::File::from_str(custom, FileFormat::Yaml).required(true))?; + settings.try_into() } impl ClientServerConfiguration { - pub fn reset_host_with_port(&mut self, host: &str, port: u16) { - self.host = host.to_owned(); - self.port = port; - } + pub fn reset_host_with_port(&mut self, host: &str, port: u16) { + self.host = host.to_owned(); + self.port = port; + } - pub fn base_url(&self) -> String { - format!("{}://{}:{}", self.http_scheme, self.host, self.port) - } + pub fn base_url(&self) -> String { + format!("{}://{}:{}", self.http_scheme, self.host, self.port) + } - pub fn sign_up_url(&self) -> String { - format!("{}/api/register", self.base_url()) - } + pub fn sign_up_url(&self) -> String { + format!("{}/api/register", self.base_url()) + } - pub fn sign_in_url(&self) -> String { - format!("{}/api/auth", self.base_url()) - } + pub fn sign_in_url(&self) -> String { + format!("{}/api/auth", self.base_url()) + } - pub fn sign_out_url(&self) -> String { - format!("{}/api/auth", self.base_url()) - } + pub fn sign_out_url(&self) -> String { + format!("{}/api/auth", self.base_url()) + } - pub fn user_profile_url(&self) -> String { - format!("{}/api/user", self.base_url()) - } + pub fn user_profile_url(&self) -> String { + format!("{}/api/user", self.base_url()) + } - pub fn workspace_url(&self) -> String { - format!("{}/api/workspace", self.base_url()) - } + pub fn workspace_url(&self) -> String { + format!("{}/api/workspace", self.base_url()) + } - pub fn app_url(&self) -> String { - format!("{}/api/app", self.base_url()) - } + pub fn app_url(&self) -> String { + format!("{}/api/app", self.base_url()) + } - pub fn view_url(&self) -> String { - format!("{}/api/view", self.base_url()) - } + pub fn view_url(&self) -> String { + format!("{}/api/view", self.base_url()) + } - pub fn doc_url(&self) -> String { - format!("{}/api/doc", self.base_url()) - } + pub fn doc_url(&self) -> String { + format!("{}/api/doc", self.base_url()) + } - pub fn trash_url(&self) -> String { - format!("{}/api/trash", self.base_url()) - } + pub fn trash_url(&self) -> String { + format!("{}/api/trash", self.base_url()) + } - pub fn ws_addr(&self) -> String { - format!("{}://{}:{}/ws", self.ws_scheme, self.host, self.port) - } + pub fn ws_addr(&self) -> String { + format!("{}://{}:{}/ws", self.ws_scheme, self.host, self.port) + } } pub enum Environment { - Local, - Production, + Local, + Production, } impl Environment { - #[allow(dead_code)] - pub fn as_str(&self) -> &'static str { - match self { - Environment::Local => "local", - Environment::Production => "production", - } + #[allow(dead_code)] + pub fn as_str(&self) -> &'static str { + match self { + Environment::Local => "local", + Environment::Production => "production", } + } } impl TryFrom for Environment { - type Error = String; + type Error = String; - fn try_from(s: String) -> Result { - match s.to_lowercase().as_str() { - "local" => Ok(Self::Local), - "production" => Ok(Self::Production), - other => Err(format!( - "{} is not a supported environment. Use either `local` or `production`.", - other - )), - } + fn try_from(s: String) -> Result { + match s.to_lowercase().as_str() { + "local" => Ok(Self::Local), + "production" => Ok(Self::Production), + other => Err(format!( + "{} is not a supported environment. Use either `local` or `production`.", + other + )), } + } } diff --git a/shared-lib/flowy-client-ws/src/connection.rs b/shared-lib/flowy-client-ws/src/connection.rs index 04ce6fd8cc..87f2abcc1a 100644 --- a/shared-lib/flowy-client-ws/src/connection.rs +++ b/shared-lib/flowy-client-ws/src/connection.rs @@ -11,163 +11,166 @@ use tokio::sync::broadcast; #[derive(Debug, Clone, PartialEq, Eq, Error, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum WSErrorCode { - #[error("Internal error")] - Internal = 0, + #[error("Internal error")] + Internal = 0, } pub trait FlowyRawWebSocket: Send + Sync { - fn initialize(&self) -> FutureResult<(), WSErrorCode>; - fn start_connect(&self, addr: String, user_id: String) -> FutureResult<(), WSErrorCode>; - fn stop_connect(&self) -> FutureResult<(), WSErrorCode>; - fn subscribe_connect_state(&self) -> BoxFuture>; - fn reconnect(&self, count: usize) -> FutureResult<(), WSErrorCode>; - fn add_msg_receiver(&self, receiver: Arc) -> Result<(), WSErrorCode>; - fn ws_msg_sender(&self) -> FutureResult>, WSErrorCode>; + fn initialize(&self) -> FutureResult<(), WSErrorCode>; + fn start_connect(&self, addr: String, user_id: String) -> FutureResult<(), WSErrorCode>; + fn stop_connect(&self) -> FutureResult<(), WSErrorCode>; + fn subscribe_connect_state(&self) -> BoxFuture>; + fn reconnect(&self, count: usize) -> FutureResult<(), WSErrorCode>; + fn add_msg_receiver(&self, receiver: Arc) -> Result<(), WSErrorCode>; + fn ws_msg_sender(&self) -> FutureResult>, WSErrorCode>; } pub trait FlowyWebSocket: Send + Sync { - fn send(&self, msg: WebSocketRawMessage) -> Result<(), WSErrorCode>; + fn send(&self, msg: WebSocketRawMessage) -> Result<(), WSErrorCode>; } #[derive(Debug, Clone, Eq, PartialEq)] pub enum NetworkType { - Unknown = 0, - Wifi = 1, - Cell = 2, - Ethernet = 3, - Bluetooth = 4, - VPN = 5, + Unknown = 0, + Wifi = 1, + Cell = 2, + Ethernet = 3, + Bluetooth = 4, + VPN = 5, } impl std::default::Default for NetworkType { - fn default() -> Self { - NetworkType::Unknown - } + fn default() -> Self { + NetworkType::Unknown + } } impl NetworkType { - pub fn is_connect(&self) -> bool { - !matches!(self, NetworkType::Unknown | NetworkType::Bluetooth) - } + pub fn is_connect(&self) -> bool { + !matches!(self, NetworkType::Unknown | NetworkType::Bluetooth) + } } pub struct FlowyWebSocketConnect { - inner: Arc, - connect_type: RwLock, - status_notifier: broadcast::Sender, - addr: String, + inner: Arc, + connect_type: RwLock, + status_notifier: broadcast::Sender, + addr: String, } impl FlowyWebSocketConnect { - pub fn new(addr: String) -> Self { - let ws = Arc::new(Arc::new(WSController::new())); - let (status_notifier, _) = broadcast::channel(10); - FlowyWebSocketConnect { - inner: ws, - connect_type: RwLock::new(NetworkType::default()), - status_notifier, - addr, - } + pub fn new(addr: String) -> Self { + let ws = Arc::new(Arc::new(WSController::new())); + let (status_notifier, _) = broadcast::channel(10); + FlowyWebSocketConnect { + inner: ws, + connect_type: RwLock::new(NetworkType::default()), + status_notifier, + addr, } + } - pub fn from_local(addr: String, ws: Arc) -> Self { - let (status_notifier, _) = broadcast::channel(10); - FlowyWebSocketConnect { - inner: ws, - connect_type: RwLock::new(NetworkType::default()), - status_notifier, - addr, - } + pub fn from_local(addr: String, ws: Arc) -> Self { + let (status_notifier, _) = broadcast::channel(10); + FlowyWebSocketConnect { + inner: ws, + connect_type: RwLock::new(NetworkType::default()), + status_notifier, + addr, } + } - pub async fn init(&self) { - match self.inner.initialize().await { - Ok(_) => {} - Err(e) => tracing::error!("FlowyWebSocketConnect init error: {:?}", e), - } + pub async fn init(&self) { + match self.inner.initialize().await { + Ok(_) => {}, + Err(e) => tracing::error!("FlowyWebSocketConnect init error: {:?}", e), } + } - pub async fn start(&self, token: String, user_id: String) -> Result<(), WSErrorCode> { - let addr = format!("{}/{}", self.addr, &token); - self.inner.stop_connect().await?; - self.inner.start_connect(addr, user_id).await?; - Ok(()) + pub async fn start(&self, token: String, user_id: String) -> Result<(), WSErrorCode> { + let addr = format!("{}/{}", self.addr, &token); + self.inner.stop_connect().await?; + self.inner.start_connect(addr, user_id).await?; + Ok(()) + } + + pub async fn stop(&self) { + let _ = self.inner.stop_connect().await; + } + + pub fn update_network_type(&self, new_type: NetworkType) { + tracing::debug!("Network new state: {:?}", new_type); + let old_type = self.connect_type.read().clone(); + let _ = self.status_notifier.send(new_type.clone()); + + if old_type != new_type { + tracing::debug!("Connect type switch from {:?} to {:?}", old_type, new_type); + match (old_type.is_connect(), new_type.is_connect()) { + (false, true) => { + let ws_controller = self.inner.clone(); + tokio::spawn(async move { retry_connect(ws_controller, 100).await }); + }, + (true, false) => { + // + }, + _ => {}, + } + + *self.connect_type.write() = new_type; } + } - pub async fn stop(&self) { - let _ = self.inner.stop_connect().await; - } + pub async fn subscribe_websocket_state(&self) -> broadcast::Receiver { + self.inner.subscribe_connect_state().await + } - pub fn update_network_type(&self, new_type: NetworkType) { - tracing::debug!("Network new state: {:?}", new_type); - let old_type = self.connect_type.read().clone(); - let _ = self.status_notifier.send(new_type.clone()); + pub fn subscribe_network_ty(&self) -> broadcast::Receiver { + self.status_notifier.subscribe() + } - if old_type != new_type { - tracing::debug!("Connect type switch from {:?} to {:?}", old_type, new_type); - match (old_type.is_connect(), new_type.is_connect()) { - (false, true) => { - let ws_controller = self.inner.clone(); - tokio::spawn(async move { retry_connect(ws_controller, 100).await }); - } - (true, false) => { - // - } - _ => {} - } + pub fn add_ws_message_receiver( + &self, + receiver: Arc, + ) -> Result<(), WSErrorCode> { + self.inner.add_msg_receiver(receiver)?; + Ok(()) + } - *self.connect_type.write() = new_type; - } - } - - pub async fn subscribe_websocket_state(&self) -> broadcast::Receiver { - self.inner.subscribe_connect_state().await - } - - pub fn subscribe_network_ty(&self) -> broadcast::Receiver { - self.status_notifier.subscribe() - } - - pub fn add_ws_message_receiver(&self, receiver: Arc) -> Result<(), WSErrorCode> { - self.inner.add_msg_receiver(receiver)?; - Ok(()) - } - - pub async fn web_socket(&self) -> Result>, WSErrorCode> { - self.inner.ws_msg_sender().await - } + pub async fn web_socket(&self) -> Result>, WSErrorCode> { + self.inner.ws_msg_sender().await + } } #[tracing::instrument(level = "debug", skip(ws_conn))] pub fn listen_on_websocket(ws_conn: Arc) { - let raw_web_socket = ws_conn.inner.clone(); - let _ = tokio::spawn(async move { - let mut notify = ws_conn.inner.subscribe_connect_state().await; - loop { - match notify.recv().await { - Ok(state) => { - tracing::info!("Websocket state changed: {}", state); - match state { - WSConnectState::Init => {} - WSConnectState::Connected => {} - WSConnectState::Connecting => {} - WSConnectState::Disconnected => retry_connect(raw_web_socket.clone(), 100).await, - } - } - Err(e) => { - tracing::error!("Websocket state notify error: {:?}", e); - break; - } - } - } - }); + let raw_web_socket = ws_conn.inner.clone(); + let _ = tokio::spawn(async move { + let mut notify = ws_conn.inner.subscribe_connect_state().await; + loop { + match notify.recv().await { + Ok(state) => { + tracing::info!("Websocket state changed: {}", state); + match state { + WSConnectState::Init => {}, + WSConnectState::Connected => {}, + WSConnectState::Connecting => {}, + WSConnectState::Disconnected => retry_connect(raw_web_socket.clone(), 100).await, + } + }, + Err(e) => { + tracing::error!("Websocket state notify error: {:?}", e); + break; + }, + } + } + }); } async fn retry_connect(ws: Arc, count: usize) { - match ws.reconnect(count).await { - Ok(_) => {} - Err(e) => { - tracing::error!("websocket connect failed: {:?}", e); - } - } + match ws.reconnect(count).await { + Ok(_) => {}, + Err(e) => { + tracing::error!("websocket connect failed: {:?}", e); + }, + } } diff --git a/shared-lib/flowy-client-ws/src/ws.rs b/shared-lib/flowy-client-ws/src/ws.rs index 1de5c54cde..149b6da821 100644 --- a/shared-lib/flowy-client-ws/src/ws.rs +++ b/shared-lib/flowy-client-ws/src/ws.rs @@ -8,68 +8,74 @@ use std::sync::Arc; use tokio::sync::broadcast::Receiver; impl FlowyRawWebSocket for Arc { - fn initialize(&self) -> FutureResult<(), WSErrorCode> { - FutureResult::new(async { Ok(()) }) - } + fn initialize(&self) -> FutureResult<(), WSErrorCode> { + FutureResult::new(async { Ok(()) }) + } - fn start_connect(&self, addr: String, _user_id: String) -> FutureResult<(), WSErrorCode> { - let cloned_ws = self.clone(); - FutureResult::new(async move { - cloned_ws.start(addr).await.map_err(internal_error)?; - Ok(()) - }) - } + fn start_connect(&self, addr: String, _user_id: String) -> FutureResult<(), WSErrorCode> { + let cloned_ws = self.clone(); + FutureResult::new(async move { + cloned_ws.start(addr).await.map_err(internal_error)?; + Ok(()) + }) + } - fn stop_connect(&self) -> FutureResult<(), WSErrorCode> { - let controller = self.clone(); - FutureResult::new(async move { - controller.stop().await; - Ok(()) - }) - } + fn stop_connect(&self) -> FutureResult<(), WSErrorCode> { + let controller = self.clone(); + FutureResult::new(async move { + controller.stop().await; + Ok(()) + }) + } - fn subscribe_connect_state(&self) -> BoxFuture> { - let cloned_ws = self.clone(); - Box::pin(async move { cloned_ws.subscribe_state().await }) - } + fn subscribe_connect_state(&self) -> BoxFuture> { + let cloned_ws = self.clone(); + Box::pin(async move { cloned_ws.subscribe_state().await }) + } - fn reconnect(&self, count: usize) -> FutureResult<(), WSErrorCode> { - let cloned_ws = self.clone(); - FutureResult::new(async move { - cloned_ws.retry(count).await.map_err(internal_error)?; - Ok(()) - }) - } + fn reconnect(&self, count: usize) -> FutureResult<(), WSErrorCode> { + let cloned_ws = self.clone(); + FutureResult::new(async move { + cloned_ws.retry(count).await.map_err(internal_error)?; + Ok(()) + }) + } - fn add_msg_receiver(&self, receiver: Arc) -> Result<(), WSErrorCode> { - self.add_ws_message_receiver(receiver).map_err(internal_error)?; - Ok(()) - } + fn add_msg_receiver(&self, receiver: Arc) -> Result<(), WSErrorCode> { + self + .add_ws_message_receiver(receiver) + .map_err(internal_error)?; + Ok(()) + } - fn ws_msg_sender(&self) -> FutureResult>, WSErrorCode> { - let cloned_self = self.clone(); - FutureResult::new(async move { - match cloned_self.ws_message_sender().await.map_err(internal_error)? { - None => Ok(None), - Some(sender) => { - let sender = sender as Arc; - Ok(Some(sender)) - } - } - }) - } + fn ws_msg_sender(&self) -> FutureResult>, WSErrorCode> { + let cloned_self = self.clone(); + FutureResult::new(async move { + match cloned_self + .ws_message_sender() + .await + .map_err(internal_error)? + { + None => Ok(None), + Some(sender) => { + let sender = sender as Arc; + Ok(Some(sender)) + }, + } + }) + } } impl FlowyWebSocket for WSSender { - fn send(&self, msg: WebSocketRawMessage) -> Result<(), WSErrorCode> { - self.send_msg(msg).map_err(internal_error)?; - Ok(()) - } + fn send(&self, msg: WebSocketRawMessage) -> Result<(), WSErrorCode> { + self.send_msg(msg).map_err(internal_error)?; + Ok(()) + } } fn internal_error(_e: T) -> WSErrorCode where - T: std::fmt::Debug, + T: std::fmt::Debug, { - WSErrorCode::Internal + WSErrorCode::Internal } diff --git a/shared-lib/flowy-server-sync/src/server_document/document_manager.rs b/shared-lib/flowy-server-sync/src/server_document/document_manager.rs index 572c0977b0..1717699984 100644 --- a/shared-lib/flowy-server-sync/src/server_document/document_manager.rs +++ b/shared-lib/flowy-server-sync/src/server_document/document_manager.rs @@ -11,285 +11,332 @@ use lib_ot::text_delta::DeltaTextOperations; use revision_model::Revision; use std::{collections::HashMap, sync::Arc}; use tokio::{ - sync::{mpsc, oneshot, RwLock}, - task::spawn_blocking, + sync::{mpsc, oneshot, RwLock}, + task::spawn_blocking, }; use ws_model::ws_revision::{ClientRevisionWSData, ServerRevisionWSDataBuilder}; pub struct ServerDocumentManager { - document_handlers: Arc>>>, - persistence: Arc, + document_handlers: Arc>>>, + persistence: Arc, } impl ServerDocumentManager { - pub fn new(persistence: Arc) -> Self { - Self { - document_handlers: Arc::new(RwLock::new(HashMap::new())), - persistence, - } + pub fn new(persistence: Arc) -> Self { + Self { + document_handlers: Arc::new(RwLock::new(HashMap::new())), + persistence, + } + } + + pub async fn handle_client_revisions( + &self, + user: Arc, + client_data: ClientRevisionWSData, + ) -> Result<(), SyncError> { + let cloned_user = user.clone(); + let ack_id = client_data.rev_id; + let object_id = client_data.object_id; + + let result = match self.get_document_handler(&object_id).await { + None => { + tracing::trace!( + "Can't find the document. Creating the document {}", + object_id + ); + let _ = self + .create_document(&object_id, client_data.revisions) + .await + .map_err(|e| { + SyncError::internal().context(format!("Server create document failed: {}", e)) + })?; + Ok(()) + }, + Some(handler) => { + handler.apply_revisions(user, client_data.revisions).await?; + Ok(()) + }, + }; + + if result.is_ok() { + cloned_user.receive(RevisionSyncResponse::Ack( + ServerRevisionWSDataBuilder::build_ack_message(&object_id, ack_id), + )); + } + result + } + + pub async fn handle_client_ping( + &self, + user: Arc, + client_data: ClientRevisionWSData, + ) -> Result<(), SyncError> { + let rev_id = client_data.rev_id; + let doc_id = client_data.object_id.clone(); + match self.get_document_handler(&doc_id).await { + None => { + tracing::trace!("Document:{} doesn't exist, ignore client ping", doc_id); + Ok(()) + }, + Some(handler) => { + handler.apply_ping(rev_id, user).await?; + Ok(()) + }, + } + } + + pub async fn handle_document_reset( + &self, + doc_id: &str, + mut revisions: Vec, + ) -> Result<(), SyncError> { + revisions.sort_by(|a, b| a.rev_id.cmp(&b.rev_id)); + + match self.get_document_handler(doc_id).await { + None => { + tracing::warn!("Document:{} doesn't exist, ignore document reset", doc_id); + Ok(()) + }, + Some(handler) => { + handler.apply_document_reset(revisions).await?; + Ok(()) + }, + } + } + + async fn get_document_handler(&self, doc_id: &str) -> Option> { + if let Some(handler) = self.document_handlers.read().await.get(doc_id).cloned() { + return Some(handler); } - pub async fn handle_client_revisions( - &self, - user: Arc, - client_data: ClientRevisionWSData, - ) -> Result<(), SyncError> { - let cloned_user = user.clone(); - let ack_id = client_data.rev_id; - let object_id = client_data.object_id; - - let result = match self.get_document_handler(&object_id).await { - None => { - tracing::trace!("Can't find the document. Creating the document {}", object_id); - let _ = self - .create_document(&object_id, client_data.revisions) - .await - .map_err(|e| SyncError::internal().context(format!("Server create document failed: {}", e)))?; - Ok(()) - } - Some(handler) => { - handler.apply_revisions(user, client_data.revisions).await?; - Ok(()) - } - }; - - if result.is_ok() { - cloned_user.receive(RevisionSyncResponse::Ack( - ServerRevisionWSDataBuilder::build_ack_message(&object_id, ack_id), - )); - } - result + let mut write_guard = self.document_handlers.write().await; + match self.persistence.read_document(doc_id).await { + Ok(doc) => { + let handler = self.create_document_handler(doc).await.unwrap(); + write_guard.insert(doc_id.to_owned(), handler.clone()); + drop(write_guard); + Some(handler) + }, + Err(_) => None, } + } - pub async fn handle_client_ping( - &self, - user: Arc, - client_data: ClientRevisionWSData, - ) -> Result<(), SyncError> { - let rev_id = client_data.rev_id; - let doc_id = client_data.object_id.clone(); - match self.get_document_handler(&doc_id).await { - None => { - tracing::trace!("Document:{} doesn't exist, ignore client ping", doc_id); - Ok(()) - } - Some(handler) => { - handler.apply_ping(rev_id, user).await?; - Ok(()) - } - } + async fn create_document( + &self, + doc_id: &str, + revisions: Vec, + ) -> Result, SyncError> { + match self.persistence.create_document(doc_id, revisions).await? { + None => Err(SyncError::internal().context("Create document info from revisions failed")), + Some(doc) => { + let handler = self.create_document_handler(doc).await?; + self + .document_handlers + .write() + .await + .insert(doc_id.to_owned(), handler.clone()); + Ok(handler) + }, } + } - pub async fn handle_document_reset(&self, doc_id: &str, mut revisions: Vec) -> Result<(), SyncError> { - revisions.sort_by(|a, b| a.rev_id.cmp(&b.rev_id)); - - match self.get_document_handler(doc_id).await { - None => { - tracing::warn!("Document:{} doesn't exist, ignore document reset", doc_id); - Ok(()) - } - Some(handler) => { - handler.apply_document_reset(revisions).await?; - Ok(()) - } - } - } - - async fn get_document_handler(&self, doc_id: &str) -> Option> { - if let Some(handler) = self.document_handlers.read().await.get(doc_id).cloned() { - return Some(handler); - } - - let mut write_guard = self.document_handlers.write().await; - match self.persistence.read_document(doc_id).await { - Ok(doc) => { - let handler = self.create_document_handler(doc).await.unwrap(); - write_guard.insert(doc_id.to_owned(), handler.clone()); - drop(write_guard); - Some(handler) - } - Err(_) => None, - } - } - - async fn create_document( - &self, - doc_id: &str, - revisions: Vec, - ) -> Result, SyncError> { - match self.persistence.create_document(doc_id, revisions).await? { - None => Err(SyncError::internal().context("Create document info from revisions failed")), - Some(doc) => { - let handler = self.create_document_handler(doc).await?; - self.document_handlers - .write() - .await - .insert(doc_id.to_owned(), handler.clone()); - Ok(handler) - } - } - } - - #[tracing::instrument(level = "debug", skip(self, doc), err)] - async fn create_document_handler(&self, doc: DocumentInfo) -> Result, SyncError> { - let persistence = self.persistence.clone(); - let handle = spawn_blocking(|| OpenDocumentHandler::new(doc, persistence)) - .await - .map_err(|e| SyncError::internal().context(format!("Create document handler failed: {}", e)))?; - Ok(Arc::new(handle?)) - } + #[tracing::instrument(level = "debug", skip(self, doc), err)] + async fn create_document_handler( + &self, + doc: DocumentInfo, + ) -> Result, SyncError> { + let persistence = self.persistence.clone(); + let handle = spawn_blocking(|| OpenDocumentHandler::new(doc, persistence)) + .await + .map_err(|e| { + SyncError::internal().context(format!("Create document handler failed: {}", e)) + })?; + Ok(Arc::new(handle?)) + } } impl std::ops::Drop for ServerDocumentManager { - fn drop(&mut self) { - log::trace!("ServerDocumentManager was dropped"); - } + fn drop(&mut self) { + log::trace!("ServerDocumentManager was dropped"); + } } type DocumentRevisionSynchronizer = RevisionSynchronizer; struct OpenDocumentHandler { - doc_id: String, - sender: mpsc::Sender, - users: DashMap>, + doc_id: String, + sender: mpsc::Sender, + users: DashMap>, } impl OpenDocumentHandler { - fn new(doc: DocumentInfo, persistence: Arc) -> Result { - let doc_id = doc.doc_id.clone(); - let (sender, receiver) = mpsc::channel(1000); - let users = DashMap::new(); + fn new( + doc: DocumentInfo, + persistence: Arc, + ) -> Result { + let doc_id = doc.doc_id.clone(); + let (sender, receiver) = mpsc::channel(1000); + let users = DashMap::new(); - let operations = DeltaTextOperations::from_bytes(&doc.data)?; - let sync_object = ServerDocument::from_operations(&doc_id, operations); - let synchronizer = Arc::new(DocumentRevisionSynchronizer::new(doc.rev_id, sync_object, persistence)); + let operations = DeltaTextOperations::from_bytes(&doc.data)?; + let sync_object = ServerDocument::from_operations(&doc_id, operations); + let synchronizer = Arc::new(DocumentRevisionSynchronizer::new( + doc.rev_id, + sync_object, + persistence, + )); - let queue = DocumentCommandRunner::new(&doc.doc_id, receiver, synchronizer); - tokio::task::spawn(queue.run()); - Ok(Self { doc_id, sender, users }) - } + let queue = DocumentCommandRunner::new(&doc.doc_id, receiver, synchronizer); + tokio::task::spawn(queue.run()); + Ok(Self { + doc_id, + sender, + users, + }) + } - #[tracing::instrument( - name = "server_document_apply_revision", - level = "trace", - skip(self, user, revisions), - err - )] - async fn apply_revisions(&self, user: Arc, revisions: Vec) -> Result<(), SyncError> { - let (ret, rx) = oneshot::channel(); - self.users.insert(user.user_id(), user.clone()); - let msg = DocumentCommand::ApplyRevisions { user, revisions, ret }; + #[tracing::instrument( + name = "server_document_apply_revision", + level = "trace", + skip(self, user, revisions), + err + )] + async fn apply_revisions( + &self, + user: Arc, + revisions: Vec, + ) -> Result<(), SyncError> { + let (ret, rx) = oneshot::channel(); + self.users.insert(user.user_id(), user.clone()); + let msg = DocumentCommand::ApplyRevisions { + user, + revisions, + ret, + }; - self.send(msg, rx).await? - } + self.send(msg, rx).await? + } - async fn apply_ping(&self, rev_id: i64, user: Arc) -> Result<(), SyncError> { - let (ret, rx) = oneshot::channel(); - self.users.insert(user.user_id(), user.clone()); - let msg = DocumentCommand::Ping { user, rev_id, ret }; - self.send(msg, rx).await? - } + async fn apply_ping(&self, rev_id: i64, user: Arc) -> Result<(), SyncError> { + let (ret, rx) = oneshot::channel(); + self.users.insert(user.user_id(), user.clone()); + let msg = DocumentCommand::Ping { user, rev_id, ret }; + self.send(msg, rx).await? + } - #[tracing::instrument(level = "debug", skip(self, revisions), err)] - async fn apply_document_reset(&self, revisions: Vec) -> Result<(), SyncError> { - let (ret, rx) = oneshot::channel(); - let msg = DocumentCommand::Reset { revisions, ret }; - self.send(msg, rx).await? - } + #[tracing::instrument(level = "debug", skip(self, revisions), err)] + async fn apply_document_reset(&self, revisions: Vec) -> Result<(), SyncError> { + let (ret, rx) = oneshot::channel(); + let msg = DocumentCommand::Reset { revisions, ret }; + self.send(msg, rx).await? + } - async fn send(&self, msg: DocumentCommand, rx: oneshot::Receiver) -> SyncResult { - self.sender - .send(msg) - .await - .map_err(|e| SyncError::internal().context(format!("Send document command failed: {}", e)))?; - rx.await.map_err(internal_sync_error) - } + async fn send(&self, msg: DocumentCommand, rx: oneshot::Receiver) -> SyncResult { + self + .sender + .send(msg) + .await + .map_err(|e| SyncError::internal().context(format!("Send document command failed: {}", e)))?; + rx.await.map_err(internal_sync_error) + } } impl std::ops::Drop for OpenDocumentHandler { - fn drop(&mut self) { - tracing::trace!("{} OpenDocHandle was dropped", self.doc_id); - } + fn drop(&mut self) { + tracing::trace!("{} OpenDocHandle was dropped", self.doc_id); + } } // #[derive(Debug)] enum DocumentCommand { - ApplyRevisions { - user: Arc, - revisions: Vec, - ret: oneshot::Sender>, - }, - Ping { - user: Arc, - rev_id: i64, - ret: oneshot::Sender>, - }, - Reset { - revisions: Vec, - ret: oneshot::Sender>, - }, + ApplyRevisions { + user: Arc, + revisions: Vec, + ret: oneshot::Sender>, + }, + Ping { + user: Arc, + rev_id: i64, + ret: oneshot::Sender>, + }, + Reset { + revisions: Vec, + ret: oneshot::Sender>, + }, } struct DocumentCommandRunner { - pub doc_id: String, - receiver: Option>, - synchronizer: Arc, + pub doc_id: String, + receiver: Option>, + synchronizer: Arc, } impl DocumentCommandRunner { - fn new( - doc_id: &str, - receiver: mpsc::Receiver, - synchronizer: Arc, - ) -> Self { - Self { - doc_id: doc_id.to_owned(), - receiver: Some(receiver), - synchronizer, - } + fn new( + doc_id: &str, + receiver: mpsc::Receiver, + synchronizer: Arc, + ) -> Self { + Self { + doc_id: doc_id.to_owned(), + receiver: Some(receiver), + synchronizer, } + } - async fn run(mut self) { - let mut receiver = self - .receiver - .take() - .expect("DocumentCommandRunner's receiver should only take one time"); + async fn run(mut self) { + let mut receiver = self + .receiver + .take() + .expect("DocumentCommandRunner's receiver should only take one time"); - let stream = stream! { - loop { - match receiver.recv().await { - Some(msg) => yield msg, - None => break, - } - } - }; - stream.for_each(|msg| self.handle_message(msg)).await; - } - - async fn handle_message(&self, msg: DocumentCommand) { - match msg { - DocumentCommand::ApplyRevisions { user, revisions, ret } => { - let result = self - .synchronizer - .sync_revisions(user, revisions) - .await - .map_err(internal_sync_error); - let _ = ret.send(result); - } - DocumentCommand::Ping { user, rev_id, ret } => { - let result = self.synchronizer.pong(user, rev_id).await.map_err(internal_sync_error); - let _ = ret.send(result); - } - DocumentCommand::Reset { revisions, ret } => { - let result = self.synchronizer.reset(revisions).await.map_err(internal_sync_error); - let _ = ret.send(result); + let stream = stream! { + loop { + match receiver.recv().await { + Some(msg) => yield msg, + None => break, } } + }; + stream.for_each(|msg| self.handle_message(msg)).await; + } + + async fn handle_message(&self, msg: DocumentCommand) { + match msg { + DocumentCommand::ApplyRevisions { + user, + revisions, + ret, + } => { + let result = self + .synchronizer + .sync_revisions(user, revisions) + .await + .map_err(internal_sync_error); + let _ = ret.send(result); + }, + DocumentCommand::Ping { user, rev_id, ret } => { + let result = self + .synchronizer + .pong(user, rev_id) + .await + .map_err(internal_sync_error); + let _ = ret.send(result); + }, + DocumentCommand::Reset { revisions, ret } => { + let result = self + .synchronizer + .reset(revisions) + .await + .map_err(internal_sync_error); + let _ = ret.send(result); + }, } + } } impl std::ops::Drop for DocumentCommandRunner { - fn drop(&mut self) { - tracing::trace!("{} DocumentCommandQueue was dropped", self.doc_id); - } + fn drop(&mut self) { + tracing::trace!("{} DocumentCommandQueue was dropped", self.doc_id); + } } diff --git a/shared-lib/flowy-server-sync/src/server_document/document_pad.rs b/shared-lib/flowy-server-sync/src/server_document/document_pad.rs index 2c60c780b3..a7e2da1210 100644 --- a/shared-lib/flowy-server-sync/src/server_document/document_pad.rs +++ b/shared-lib/flowy-server-sync/src/server_document/document_pad.rs @@ -3,41 +3,44 @@ use flowy_sync::{RevisionOperations, RevisionSyncObject}; use lib_ot::{core::*, text_delta::DeltaTextOperations}; pub struct ServerDocument { - document_id: String, - operations: DeltaTextOperations, + document_id: String, + operations: DeltaTextOperations, } impl ServerDocument { - pub fn from_operations(document_id: &str, operations: DeltaTextOperations) -> Self { - let document_id = document_id.to_owned(); - ServerDocument { - document_id, - operations, - } + pub fn from_operations(document_id: &str, operations: DeltaTextOperations) -> Self { + let document_id = document_id.to_owned(); + ServerDocument { + document_id, + operations, } + } } impl RevisionSyncObject for ServerDocument { - fn object_id(&self) -> &str { - &self.document_id - } + fn object_id(&self) -> &str { + &self.document_id + } - fn object_json(&self) -> String { - self.operations.json_str() - } + fn object_json(&self) -> String { + self.operations.json_str() + } - fn compose(&mut self, other: &DeltaTextOperations) -> Result<(), SyncError> { - let operations = self.operations.compose(other)?; - self.operations = operations; - Ok(()) - } + fn compose(&mut self, other: &DeltaTextOperations) -> Result<(), SyncError> { + let operations = self.operations.compose(other)?; + self.operations = operations; + Ok(()) + } - fn transform(&self, other: &DeltaTextOperations) -> Result<(DeltaTextOperations, DeltaTextOperations), SyncError> { - let value = self.operations.transform(other)?; - Ok(value) - } + fn transform( + &self, + other: &DeltaTextOperations, + ) -> Result<(DeltaTextOperations, DeltaTextOperations), SyncError> { + let value = self.operations.transform(other)?; + Ok(value) + } - fn set_operations(&mut self, operations: RevisionOperations) { - self.operations = operations; - } + fn set_operations(&mut self, operations: RevisionOperations) { + self.operations = operations; + } } diff --git a/shared-lib/flowy-server-sync/src/server_folder/folder_manager.rs b/shared-lib/flowy-server-sync/src/server_folder/folder_manager.rs index c7c6d3c5fb..19ec82bd16 100644 --- a/shared-lib/flowy-server-sync/src/server_folder/folder_manager.rs +++ b/shared-lib/flowy-server-sync/src/server_folder/folder_manager.rs @@ -9,252 +9,286 @@ use futures::stream::StreamExt; use revision_model::Revision; use std::{collections::HashMap, sync::Arc}; use tokio::{ - sync::{mpsc, oneshot, RwLock}, - task::spawn_blocking, + sync::{mpsc, oneshot, RwLock}, + task::spawn_blocking, }; use ws_model::ws_revision::{ClientRevisionWSData, ServerRevisionWSDataBuilder}; pub struct ServerFolderManager { - folder_handlers: Arc>>>, - persistence: Arc, + folder_handlers: Arc>>>, + persistence: Arc, } impl ServerFolderManager { - pub fn new(persistence: Arc) -> Self { - Self { - folder_handlers: Arc::new(RwLock::new(HashMap::new())), - persistence, - } + pub fn new(persistence: Arc) -> Self { + Self { + folder_handlers: Arc::new(RwLock::new(HashMap::new())), + persistence, + } + } + + pub async fn handle_client_revisions( + &self, + user: Arc, + client_data: ClientRevisionWSData, + ) -> Result<(), SyncError> { + let cloned_user = user.clone(); + let ack_id = client_data.rev_id; + let folder_id = client_data.object_id; + let user_id = user.user_id(); + + let result = match self.get_folder_handler(&user_id, &folder_id).await { + None => { + let _ = self + .create_folder(&user_id, &folder_id, client_data.revisions) + .await + .map_err(|e| { + SyncError::internal().context(format!("Server create folder failed: {:?}", e)) + })?; + Ok(()) + }, + Some(handler) => { + handler.apply_revisions(user, client_data.revisions).await?; + Ok(()) + }, + }; + + if result.is_ok() { + cloned_user.receive(RevisionSyncResponse::Ack( + ServerRevisionWSDataBuilder::build_ack_message(&folder_id, ack_id), + )); + } + result + } + + pub async fn handle_client_ping( + &self, + user: Arc, + client_data: ClientRevisionWSData, + ) -> Result<(), SyncError> { + let user_id = user.user_id(); + let rev_id = client_data.rev_id; + let folder_id = client_data.object_id.clone(); + match self.get_folder_handler(&user_id, &folder_id).await { + None => { + tracing::trace!("Folder:{} doesn't exist, ignore client ping", folder_id); + Ok(()) + }, + Some(handler) => { + handler.apply_ping(rev_id, user).await?; + Ok(()) + }, + } + } + + async fn get_folder_handler( + &self, + user_id: &str, + folder_id: &str, + ) -> Option> { + let folder_id = folder_id.to_owned(); + if let Some(handler) = self.folder_handlers.read().await.get(&folder_id).cloned() { + return Some(handler); } - pub async fn handle_client_revisions( - &self, - user: Arc, - client_data: ClientRevisionWSData, - ) -> Result<(), SyncError> { - let cloned_user = user.clone(); - let ack_id = client_data.rev_id; - let folder_id = client_data.object_id; - let user_id = user.user_id(); - - let result = match self.get_folder_handler(&user_id, &folder_id).await { - None => { - let _ = self - .create_folder(&user_id, &folder_id, client_data.revisions) - .await - .map_err(|e| SyncError::internal().context(format!("Server create folder failed: {:?}", e)))?; - Ok(()) - } - Some(handler) => { - handler.apply_revisions(user, client_data.revisions).await?; - Ok(()) - } - }; - - if result.is_ok() { - cloned_user.receive(RevisionSyncResponse::Ack( - ServerRevisionWSDataBuilder::build_ack_message(&folder_id, ack_id), - )); - } - result + let mut write_guard = self.folder_handlers.write().await; + match self.persistence.read_folder(user_id, &folder_id).await { + Ok(folder_info) => { + let handler = self + .create_folder_handler(folder_info) + .await + .map_err(internal_sync_error) + .unwrap(); + write_guard.insert(folder_id, handler.clone()); + drop(write_guard); + Some(handler) + }, + Err(_) => None, } + } - pub async fn handle_client_ping( - &self, - user: Arc, - client_data: ClientRevisionWSData, - ) -> Result<(), SyncError> { - let user_id = user.user_id(); - let rev_id = client_data.rev_id; - let folder_id = client_data.object_id.clone(); - match self.get_folder_handler(&user_id, &folder_id).await { - None => { - tracing::trace!("Folder:{} doesn't exist, ignore client ping", folder_id); - Ok(()) - } - Some(handler) => { - handler.apply_ping(rev_id, user).await?; - Ok(()) - } - } - } - - async fn get_folder_handler(&self, user_id: &str, folder_id: &str) -> Option> { - let folder_id = folder_id.to_owned(); - if let Some(handler) = self.folder_handlers.read().await.get(&folder_id).cloned() { - return Some(handler); - } - - let mut write_guard = self.folder_handlers.write().await; - match self.persistence.read_folder(user_id, &folder_id).await { - Ok(folder_info) => { - let handler = self - .create_folder_handler(folder_info) - .await - .map_err(internal_sync_error) - .unwrap(); - write_guard.insert(folder_id, handler.clone()); - drop(write_guard); - Some(handler) - } - Err(_) => None, - } - } - - async fn create_folder_handler(&self, folder_info: FolderInfo) -> Result, SyncError> { - let persistence = self.persistence.clone(); - let handle = spawn_blocking(|| OpenFolderHandler::new(folder_info, persistence)) - .await - .map_err(|e| SyncError::internal().context(format!("Create folder handler failed: {}", e)))?; - Ok(Arc::new(handle?)) - } - - #[tracing::instrument(level = "debug", skip(self, revisions), err)] - async fn create_folder( - &self, - user_id: &str, - folder_id: &str, - revisions: Vec, - ) -> Result, SyncError> { - match self.persistence.create_folder(user_id, folder_id, revisions).await? { - Some(folder_info) => { - let handler = self.create_folder_handler(folder_info).await?; - self.folder_handlers - .write() - .await - .insert(folder_id.to_owned(), handler.clone()); - Ok(handler) - } - None => Err(SyncError::internal().context(String::new())), - } + async fn create_folder_handler( + &self, + folder_info: FolderInfo, + ) -> Result, SyncError> { + let persistence = self.persistence.clone(); + let handle = spawn_blocking(|| OpenFolderHandler::new(folder_info, persistence)) + .await + .map_err(|e| SyncError::internal().context(format!("Create folder handler failed: {}", e)))?; + Ok(Arc::new(handle?)) + } + + #[tracing::instrument(level = "debug", skip(self, revisions), err)] + async fn create_folder( + &self, + user_id: &str, + folder_id: &str, + revisions: Vec, + ) -> Result, SyncError> { + match self + .persistence + .create_folder(user_id, folder_id, revisions) + .await? + { + Some(folder_info) => { + let handler = self.create_folder_handler(folder_info).await?; + self + .folder_handlers + .write() + .await + .insert(folder_id.to_owned(), handler.clone()); + Ok(handler) + }, + None => Err(SyncError::internal().context(String::new())), } + } } struct OpenFolderHandler { - folder_id: String, - sender: mpsc::Sender, + folder_id: String, + sender: mpsc::Sender, } impl OpenFolderHandler { - fn new(folder_info: FolderInfo, persistence: Arc) -> SyncResult { - let (sender, receiver) = mpsc::channel(1000); - let folder_id = folder_info.folder_id.clone(); - let operations = FolderOperations::from_bytes(&folder_info.text)?; - let sync_object = ServerFolder::from_operations(&folder_id, operations); - let synchronizer = Arc::new(FolderRevisionSynchronizer::new( - folder_info.rev_id, - sync_object, - persistence, - )); + fn new( + folder_info: FolderInfo, + persistence: Arc, + ) -> SyncResult { + let (sender, receiver) = mpsc::channel(1000); + let folder_id = folder_info.folder_id.clone(); + let operations = FolderOperations::from_bytes(&folder_info.text)?; + let sync_object = ServerFolder::from_operations(&folder_id, operations); + let synchronizer = Arc::new(FolderRevisionSynchronizer::new( + folder_info.rev_id, + sync_object, + persistence, + )); - let queue = FolderCommandRunner::new(&folder_id, receiver, synchronizer); - tokio::task::spawn(queue.run()); + let queue = FolderCommandRunner::new(&folder_id, receiver, synchronizer); + tokio::task::spawn(queue.run()); - Ok(Self { folder_id, sender }) - } + Ok(Self { folder_id, sender }) + } - #[tracing::instrument( - name = "server_folder_apply_revision", - level = "trace", - skip(self, user, revisions), - err - )] - async fn apply_revisions(&self, user: Arc, revisions: Vec) -> SyncResult<()> { - let (ret, rx) = oneshot::channel(); - let msg = FolderCommand::ApplyRevisions { user, revisions, ret }; + #[tracing::instrument( + name = "server_folder_apply_revision", + level = "trace", + skip(self, user, revisions), + err + )] + async fn apply_revisions( + &self, + user: Arc, + revisions: Vec, + ) -> SyncResult<()> { + let (ret, rx) = oneshot::channel(); + let msg = FolderCommand::ApplyRevisions { + user, + revisions, + ret, + }; - self.send(msg, rx).await? - } + self.send(msg, rx).await? + } - async fn apply_ping(&self, rev_id: i64, user: Arc) -> Result<(), SyncError> { - let (ret, rx) = oneshot::channel(); - let msg = FolderCommand::Ping { user, rev_id, ret }; - self.send(msg, rx).await? - } + async fn apply_ping(&self, rev_id: i64, user: Arc) -> Result<(), SyncError> { + let (ret, rx) = oneshot::channel(); + let msg = FolderCommand::Ping { user, rev_id, ret }; + self.send(msg, rx).await? + } - async fn send(&self, msg: FolderCommand, rx: oneshot::Receiver) -> SyncResult { - self.sender - .send(msg) - .await - .map_err(|e| SyncError::internal().context(format!("Send folder command failed: {}", e)))?; - rx.await.map_err(internal_sync_error) - } + async fn send(&self, msg: FolderCommand, rx: oneshot::Receiver) -> SyncResult { + self + .sender + .send(msg) + .await + .map_err(|e| SyncError::internal().context(format!("Send folder command failed: {}", e)))?; + rx.await.map_err(internal_sync_error) + } } impl std::ops::Drop for OpenFolderHandler { - fn drop(&mut self) { - tracing::trace!("{} OpenFolderHandler was dropped", self.folder_id); - } + fn drop(&mut self) { + tracing::trace!("{} OpenFolderHandler was dropped", self.folder_id); + } } enum FolderCommand { - ApplyRevisions { - user: Arc, - revisions: Vec, - ret: oneshot::Sender>, - }, - Ping { - user: Arc, - rev_id: i64, - ret: oneshot::Sender>, - }, + ApplyRevisions { + user: Arc, + revisions: Vec, + ret: oneshot::Sender>, + }, + Ping { + user: Arc, + rev_id: i64, + ret: oneshot::Sender>, + }, } struct FolderCommandRunner { - folder_id: String, - receiver: Option>, - synchronizer: Arc, + folder_id: String, + receiver: Option>, + synchronizer: Arc, } impl FolderCommandRunner { - fn new( - folder_id: &str, - receiver: mpsc::Receiver, - synchronizer: Arc, - ) -> Self { - Self { - folder_id: folder_id.to_owned(), - receiver: Some(receiver), - synchronizer, - } + fn new( + folder_id: &str, + receiver: mpsc::Receiver, + synchronizer: Arc, + ) -> Self { + Self { + folder_id: folder_id.to_owned(), + receiver: Some(receiver), + synchronizer, } + } - async fn run(mut self) { - let mut receiver = self - .receiver - .take() - .expect("FolderCommandRunner's receiver should only take one time"); + async fn run(mut self) { + let mut receiver = self + .receiver + .take() + .expect("FolderCommandRunner's receiver should only take one time"); - let stream = stream! { - loop { - match receiver.recv().await { - Some(msg) => yield msg, - None => break, - } - } - }; - stream.for_each(|msg| self.handle_message(msg)).await; - } - - async fn handle_message(&self, msg: FolderCommand) { - match msg { - FolderCommand::ApplyRevisions { user, revisions, ret } => { - let result = self - .synchronizer - .sync_revisions(user, revisions) - .await - .map_err(internal_sync_error); - let _ = ret.send(result); - } - FolderCommand::Ping { user, rev_id, ret } => { - let result = self.synchronizer.pong(user, rev_id).await.map_err(internal_sync_error); - let _ = ret.send(result); + let stream = stream! { + loop { + match receiver.recv().await { + Some(msg) => yield msg, + None => break, } } + }; + stream.for_each(|msg| self.handle_message(msg)).await; + } + + async fn handle_message(&self, msg: FolderCommand) { + match msg { + FolderCommand::ApplyRevisions { + user, + revisions, + ret, + } => { + let result = self + .synchronizer + .sync_revisions(user, revisions) + .await + .map_err(internal_sync_error); + let _ = ret.send(result); + }, + FolderCommand::Ping { user, rev_id, ret } => { + let result = self + .synchronizer + .pong(user, rev_id) + .await + .map_err(internal_sync_error); + let _ = ret.send(result); + }, } + } } impl std::ops::Drop for FolderCommandRunner { - fn drop(&mut self) { - tracing::trace!("{} FolderCommandRunner was dropped", self.folder_id); - } + fn drop(&mut self) { + tracing::trace!("{} FolderCommandRunner was dropped", self.folder_id); + } } diff --git a/shared-lib/flowy-server-sync/src/server_folder/folder_pad.rs b/shared-lib/flowy-server-sync/src/server_folder/folder_pad.rs index 8aab49c1c1..c9f9f70228 100644 --- a/shared-lib/flowy-server-sync/src/server_folder/folder_pad.rs +++ b/shared-lib/flowy-server-sync/src/server_folder/folder_pad.rs @@ -9,68 +9,74 @@ pub type FolderOperations = DeltaOperations; pub type FolderOperationsBuilder = DeltaOperationBuilder; pub struct ServerFolder { - folder_id: String, - operations: FolderOperations, + folder_id: String, + operations: FolderOperations, } impl ServerFolder { - pub fn from_operations(folder_id: &str, operations: FolderOperations) -> Self { - Self { - folder_id: folder_id.to_owned(), - operations, - } + pub fn from_operations(folder_id: &str, operations: FolderOperations) -> Self { + Self { + folder_id: folder_id.to_owned(), + operations, } + } } impl RevisionSyncObject for ServerFolder { - fn object_id(&self) -> &str { - &self.folder_id - } + fn object_id(&self) -> &str { + &self.folder_id + } - fn object_json(&self) -> String { - self.operations.json_str() - } + fn object_json(&self) -> String { + self.operations.json_str() + } - fn compose(&mut self, other: &FolderOperations) -> Result<(), SyncError> { - let operations = self.operations.compose(other)?; - self.operations = operations; - Ok(()) - } + fn compose(&mut self, other: &FolderOperations) -> Result<(), SyncError> { + let operations = self.operations.compose(other)?; + self.operations = operations; + Ok(()) + } - fn transform(&self, other: &FolderOperations) -> Result<(FolderOperations, FolderOperations), SyncError> { - let value = self.operations.transform(other)?; - Ok(value) - } + fn transform( + &self, + other: &FolderOperations, + ) -> Result<(FolderOperations, FolderOperations), SyncError> { + let value = self.operations.transform(other)?; + Ok(value) + } - fn set_operations(&mut self, operations: RevisionOperations) { - self.operations = operations; - } + fn set_operations(&mut self, operations: RevisionOperations) { + self.operations = operations; + } } #[inline] -pub fn make_folder_from_revisions(folder_id: &str, revisions: Vec) -> Result, SyncError> { - if revisions.is_empty() { - return Ok(None); - } +pub fn make_folder_from_revisions( + folder_id: &str, + revisions: Vec, +) -> Result, SyncError> { + if revisions.is_empty() { + return Ok(None); + } - let mut folder_delta = FolderOperations::new(); - let mut base_rev_id = 0; - let mut rev_id = 0; - for revision in revisions { - base_rev_id = revision.base_rev_id; - rev_id = revision.rev_id; - if revision.bytes.is_empty() { - tracing::warn!("revision delta_data is empty"); - } - let delta = FolderOperations::from_bytes(revision.bytes)?; - folder_delta = folder_delta.compose(&delta)?; + let mut folder_delta = FolderOperations::new(); + let mut base_rev_id = 0; + let mut rev_id = 0; + for revision in revisions { + base_rev_id = revision.base_rev_id; + rev_id = revision.rev_id; + if revision.bytes.is_empty() { + tracing::warn!("revision delta_data is empty"); } + let delta = FolderOperations::from_bytes(revision.bytes)?; + folder_delta = folder_delta.compose(&delta)?; + } - let text = folder_delta.json_str(); - Ok(Some(FolderInfo { - folder_id: folder_id.to_string(), - text, - rev_id, - base_rev_id, - })) + let text = folder_delta.json_str(); + Ok(Some(FolderInfo { + folder_id: folder_id.to_string(), + text, + rev_id, + base_rev_id, + })) } diff --git a/shared-lib/flowy-sync/src/errors.rs b/shared-lib/flowy-sync/src/errors.rs index bf319a9a82..9958c3ed4a 100644 --- a/shared-lib/flowy-sync/src/errors.rs +++ b/shared-lib/flowy-sync/src/errors.rs @@ -3,74 +3,80 @@ use std::fmt::Debug; use strum_macros::Display; macro_rules! static_error { - ($name:ident, $status:expr) => { - #[allow(non_snake_case, missing_docs)] - pub fn $name() -> SyncError { - SyncError { - code: $status, - msg: format!("{}", $status), - } - } - }; + ($name:ident, $status:expr) => { + #[allow(non_snake_case, missing_docs)] + pub fn $name() -> SyncError { + SyncError { + code: $status, + msg: format!("{}", $status), + } + } + }; } pub type SyncResult = std::result::Result; #[derive(Debug, Clone)] pub struct SyncError { - pub code: ErrorCode, - pub msg: String, + pub code: ErrorCode, + pub msg: String, } impl SyncError { - fn new(code: ErrorCode, msg: &str) -> Self { - Self { - code, - msg: msg.to_owned(), - } + fn new(code: ErrorCode, msg: &str) -> Self { + Self { + code, + msg: msg.to_owned(), } + } - pub fn context(mut self, error: T) -> Self { - self.msg = format!("{:?}", error); - self - } + pub fn context(mut self, error: T) -> Self { + self.msg = format!("{:?}", error); + self + } - static_error!(serde, ErrorCode::SerdeError); - static_error!(internal, ErrorCode::InternalError); - static_error!(undo, ErrorCode::UndoFail); - static_error!(redo, ErrorCode::RedoFail); - static_error!(out_of_bound, ErrorCode::OutOfBound); - static_error!(record_not_found, ErrorCode::RecordNotFound); - static_error!(revision_conflict, ErrorCode::RevisionConflict); - static_error!(can_not_delete_primary_field, ErrorCode::CannotDeleteThePrimaryField); - static_error!(unexpected_empty_revision, ErrorCode::UnexpectedEmptyRevision); + static_error!(serde, ErrorCode::SerdeError); + static_error!(internal, ErrorCode::InternalError); + static_error!(undo, ErrorCode::UndoFail); + static_error!(redo, ErrorCode::RedoFail); + static_error!(out_of_bound, ErrorCode::OutOfBound); + static_error!(record_not_found, ErrorCode::RecordNotFound); + static_error!(revision_conflict, ErrorCode::RevisionConflict); + static_error!( + can_not_delete_primary_field, + ErrorCode::CannotDeleteThePrimaryField + ); + static_error!( + unexpected_empty_revision, + ErrorCode::UnexpectedEmptyRevision + ); } impl fmt::Display for SyncError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}: {}", &self.code, &self.msg) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}: {}", &self.code, &self.msg) + } } #[derive(Debug, Clone, Display, PartialEq, Eq)] pub enum ErrorCode { - DocumentIdInvalid = 0, - DocumentNotfound = 1, - UndoFail = 200, - RedoFail = 201, - OutOfBound = 202, - RevisionConflict = 203, - RecordNotFound = 300, - CannotDeleteThePrimaryField = 301, - UnexpectedEmptyRevision = 302, - SerdeError = 999, - InternalError = 1000, + DocumentIdInvalid = 0, + DocumentNotfound = 1, + UndoFail = 200, + RedoFail = 201, + OutOfBound = 202, + RevisionConflict = 203, + RecordNotFound = 300, + CannotDeleteThePrimaryField = 301, + UnexpectedEmptyRevision = 302, + SerdeError = 999, + InternalError = 1000, } impl std::convert::From for SyncError { - fn from(error: lib_ot::errors::OTError) -> Self { - SyncError::new(ErrorCode::InternalError, "").context(error) - } + fn from(error: lib_ot::errors::OTError) -> Self { + SyncError::new(ErrorCode::InternalError, "").context(error) + } } // impl std::convert::From for SyncError { @@ -81,7 +87,7 @@ impl std::convert::From for SyncError { pub fn internal_sync_error(e: T) -> SyncError where - T: std::fmt::Debug, + T: std::fmt::Debug, { - SyncError::internal().context(e) + SyncError::internal().context(e) } diff --git a/shared-lib/flowy-sync/src/ext.rs b/shared-lib/flowy-sync/src/ext.rs index 778c3c14d0..7fc5bd8251 100644 --- a/shared-lib/flowy-sync/src/ext.rs +++ b/shared-lib/flowy-sync/src/ext.rs @@ -8,70 +8,94 @@ use std::fmt::Debug; use std::sync::Arc; pub trait FolderCloudPersistence: Send + Sync + Debug { - fn read_folder(&self, user_id: &str, folder_id: &str) -> BoxResultFuture; + fn read_folder(&self, user_id: &str, folder_id: &str) -> BoxResultFuture; - fn create_folder( - &self, - user_id: &str, - folder_id: &str, - revisions: Vec, - ) -> BoxResultFuture, SyncError>; + fn create_folder( + &self, + user_id: &str, + folder_id: &str, + revisions: Vec, + ) -> BoxResultFuture, SyncError>; - fn save_folder_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError>; + fn save_folder_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError>; - fn read_folder_revisions( - &self, - folder_id: &str, - rev_ids: Option>, - ) -> BoxResultFuture, SyncError>; + fn read_folder_revisions( + &self, + folder_id: &str, + rev_ids: Option>, + ) -> BoxResultFuture, SyncError>; - fn reset_folder(&self, folder_id: &str, revisions: Vec) -> BoxResultFuture<(), SyncError>; + fn reset_folder( + &self, + folder_id: &str, + revisions: Vec, + ) -> BoxResultFuture<(), SyncError>; } impl RevisionSyncPersistence for Arc { - fn read_revisions(&self, object_id: &str, rev_ids: Option>) -> BoxResultFuture, SyncError> { - (**self).read_folder_revisions(object_id, rev_ids) - } + fn read_revisions( + &self, + object_id: &str, + rev_ids: Option>, + ) -> BoxResultFuture, SyncError> { + (**self).read_folder_revisions(object_id, rev_ids) + } - fn save_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError> { - (**self).save_folder_revisions(revisions) - } + fn save_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError> { + (**self).save_folder_revisions(revisions) + } - fn reset_object(&self, object_id: &str, revisions: Vec) -> BoxResultFuture<(), SyncError> { - (**self).reset_folder(object_id, revisions) - } + fn reset_object( + &self, + object_id: &str, + revisions: Vec, + ) -> BoxResultFuture<(), SyncError> { + (**self).reset_folder(object_id, revisions) + } } pub trait DocumentCloudPersistence: Send + Sync + Debug { - fn read_document(&self, doc_id: &str) -> BoxResultFuture; + fn read_document(&self, doc_id: &str) -> BoxResultFuture; - fn create_document( - &self, - doc_id: &str, - revisions: Vec, - ) -> BoxResultFuture, SyncError>; + fn create_document( + &self, + doc_id: &str, + revisions: Vec, + ) -> BoxResultFuture, SyncError>; - fn read_document_revisions( - &self, - doc_id: &str, - rev_ids: Option>, - ) -> BoxResultFuture, SyncError>; + fn read_document_revisions( + &self, + doc_id: &str, + rev_ids: Option>, + ) -> BoxResultFuture, SyncError>; - fn save_document_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError>; + fn save_document_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError>; - fn reset_document(&self, doc_id: &str, revisions: Vec) -> BoxResultFuture<(), SyncError>; + fn reset_document( + &self, + doc_id: &str, + revisions: Vec, + ) -> BoxResultFuture<(), SyncError>; } impl RevisionSyncPersistence for Arc { - fn read_revisions(&self, object_id: &str, rev_ids: Option>) -> BoxResultFuture, SyncError> { - (**self).read_document_revisions(object_id, rev_ids) - } + fn read_revisions( + &self, + object_id: &str, + rev_ids: Option>, + ) -> BoxResultFuture, SyncError> { + (**self).read_document_revisions(object_id, rev_ids) + } - fn save_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError> { - (**self).save_document_revisions(revisions) - } + fn save_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError> { + (**self).save_document_revisions(revisions) + } - fn reset_object(&self, object_id: &str, revisions: Vec) -> BoxResultFuture<(), SyncError> { - (**self).reset_document(object_id, revisions) - } + fn reset_object( + &self, + object_id: &str, + revisions: Vec, + ) -> BoxResultFuture<(), SyncError> { + (**self).reset_document(object_id, revisions) + } } diff --git a/shared-lib/flowy-sync/src/lib.rs b/shared-lib/flowy-sync/src/lib.rs index e0f63cde58..7fbd918fc4 100644 --- a/shared-lib/flowy-sync/src/lib.rs +++ b/shared-lib/flowy-sync/src/lib.rs @@ -20,243 +20,291 @@ use ws_model::ws_revision::{ServerRevisionWSData, ServerRevisionWSDataBuilder}; pub type RevisionOperations = DeltaOperations; pub trait RevisionUser: Send + Sync + Debug { - fn user_id(&self) -> String; - fn receive(&self, resp: RevisionSyncResponse); + fn user_id(&self) -> String; + fn receive(&self, resp: RevisionSyncResponse); } pub enum RevisionSyncResponse { - Pull(ServerRevisionWSData), - Push(ServerRevisionWSData), - Ack(ServerRevisionWSData), + Pull(ServerRevisionWSData), + Push(ServerRevisionWSData), + Ack(ServerRevisionWSData), } pub trait RevisionSyncObject: Send + Sync + 'static { - fn object_id(&self) -> &str; + fn object_id(&self) -> &str; - fn object_json(&self) -> String; + fn object_json(&self) -> String; - fn compose(&mut self, other: &RevisionOperations) -> Result<(), SyncError>; + fn compose(&mut self, other: &RevisionOperations) -> Result<(), SyncError>; - fn transform( - &self, - other: &RevisionOperations, - ) -> Result<(RevisionOperations, RevisionOperations), SyncError>; + fn transform( + &self, + other: &RevisionOperations, + ) -> Result<(RevisionOperations, RevisionOperations), SyncError>; - fn set_operations(&mut self, operations: RevisionOperations); + fn set_operations(&mut self, operations: RevisionOperations); } pub trait RevisionSyncPersistence: Send + Sync + 'static { - fn read_revisions(&self, object_id: &str, rev_ids: Option>) -> BoxResultFuture, SyncError>; + fn read_revisions( + &self, + object_id: &str, + rev_ids: Option>, + ) -> BoxResultFuture, SyncError>; - fn save_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError>; + fn save_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError>; - fn reset_object(&self, object_id: &str, revisions: Vec) -> BoxResultFuture<(), SyncError>; + fn reset_object( + &self, + object_id: &str, + revisions: Vec, + ) -> BoxResultFuture<(), SyncError>; } impl RevisionSyncPersistence for Arc where - T: RevisionSyncPersistence + Sized, + T: RevisionSyncPersistence + Sized, { - fn read_revisions(&self, object_id: &str, rev_ids: Option>) -> BoxResultFuture, SyncError> { - (**self).read_revisions(object_id, rev_ids) - } + fn read_revisions( + &self, + object_id: &str, + rev_ids: Option>, + ) -> BoxResultFuture, SyncError> { + (**self).read_revisions(object_id, rev_ids) + } - fn save_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError> { - (**self).save_revisions(revisions) - } + fn save_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError> { + (**self).save_revisions(revisions) + } - fn reset_object(&self, object_id: &str, revisions: Vec) -> BoxResultFuture<(), SyncError> { - (**self).reset_object(object_id, revisions) - } + fn reset_object( + &self, + object_id: &str, + revisions: Vec, + ) -> BoxResultFuture<(), SyncError> { + (**self).reset_object(object_id, revisions) + } } pub struct RevisionSynchronizer { - object_id: String, - rev_id: AtomicI64, - object: Arc>>, - persistence: Arc, + object_id: String, + rev_id: AtomicI64, + object: Arc>>, + persistence: Arc, } impl RevisionSynchronizer where - Attribute: OperationAttributes + DeserializeOwned + serde::Serialize + 'static, + Attribute: OperationAttributes + DeserializeOwned + serde::Serialize + 'static, { - pub fn new(rev_id: i64, sync_object: S, persistence: P) -> RevisionSynchronizer - where - S: RevisionSyncObject, - P: RevisionSyncPersistence, + pub fn new(rev_id: i64, sync_object: S, persistence: P) -> RevisionSynchronizer + where + S: RevisionSyncObject, + P: RevisionSyncPersistence, + { + let object = Arc::new(RwLock::new(sync_object)); + let persistence = Arc::new(persistence); + let object_id = object.read().object_id().to_owned(); + RevisionSynchronizer { + object_id, + rev_id: AtomicI64::new(rev_id), + object, + persistence, + } + } + + #[tracing::instrument(level = "trace", skip(self, user, revisions), err)] + pub async fn sync_revisions( + &self, + user: Arc, + revisions: Vec, + ) -> Result<(), SyncError> { + let object_id = self.object_id.clone(); + if revisions.is_empty() { + // Return all the revisions to client + let revisions = self.persistence.read_revisions(&object_id, None).await?; + let data = ServerRevisionWSDataBuilder::build_push_message(&object_id, revisions); + user.receive(RevisionSyncResponse::Push(data)); + return Ok(()); + } + + let server_base_rev_id = self.rev_id.load(SeqCst); + let first_revision = revisions.first().unwrap().clone(); + if self + .is_applied_before(&first_revision, &self.persistence) + .await { - let object = Arc::new(RwLock::new(sync_object)); - let persistence = Arc::new(persistence); - let object_id = object.read().object_id().to_owned(); - RevisionSynchronizer { - object_id, - rev_id: AtomicI64::new(rev_id), - object, - persistence, - } + // Server has received this revision before, so ignore the following revisions + return Ok(()); } - #[tracing::instrument(level = "trace", skip(self, user, revisions), err)] - pub async fn sync_revisions(&self, user: Arc, revisions: Vec) -> Result<(), SyncError> { - let object_id = self.object_id.clone(); - if revisions.is_empty() { - // Return all the revisions to client - let revisions = self.persistence.read_revisions(&object_id, None).await?; - let data = ServerRevisionWSDataBuilder::build_push_message(&object_id, revisions); - user.receive(RevisionSyncResponse::Push(data)); - return Ok(()); - } - - let server_base_rev_id = self.rev_id.load(SeqCst); - let first_revision = revisions.first().unwrap().clone(); - if self.is_applied_before(&first_revision, &self.persistence).await { - // Server has received this revision before, so ignore the following revisions - return Ok(()); - } - - match server_base_rev_id.cmp(&first_revision.rev_id) { - Ordering::Less => { - let server_rev_id = next(server_base_rev_id); - if server_base_rev_id == first_revision.base_rev_id || server_rev_id == first_revision.rev_id { - // The rev is in the right order, just compose it. - for revision in revisions.iter() { - self.compose_revision(revision)?; - } - self.persistence.save_revisions(revisions).await?; - } else { - // The server ops is outdated, pull the missing revision from the client. - let range = RevisionRange { - start: server_rev_id, - end: first_revision.rev_id, - }; - let msg = ServerRevisionWSDataBuilder::build_pull_message(&self.object_id, range); - user.receive(RevisionSyncResponse::Pull(msg)); - } - } - Ordering::Equal => { - // Do nothing - tracing::trace!("Applied {} revision rev_id is the same as cur_rev_id", self.object_id); - } - Ordering::Greater => { - // The client ops is outdated. Transform the client revision ops and then - // send the prime ops to the client. Client should compose the this prime - // ops. - let from_rev_id = first_revision.rev_id; - let to_rev_id = server_base_rev_id; - self.push_revisions_to_user(user, from_rev_id, to_rev_id).await; - } - } - Ok(()) - } - - #[tracing::instrument(level = "trace", skip(self, user), fields(server_rev_id), err)] - pub async fn pong(&self, user: Arc, client_rev_id: i64) -> Result<(), SyncError> { - let object_id = self.object_id.clone(); - let server_rev_id = self.rev_id(); - tracing::Span::current().record("server_rev_id", &server_rev_id); - match server_rev_id.cmp(&client_rev_id) { - Ordering::Less => { - tracing::trace!("Client should not send ping and the server should pull the revisions from the client") - } - Ordering::Equal => tracing::trace!("{} is up to date.", object_id), - Ordering::Greater => { - // The client ops is outdated. Transform the client revision ops and then - // send the prime ops to the client. Client should compose the this prime - // ops. - let from_rev_id = client_rev_id; - let to_rev_id = server_rev_id; - tracing::trace!("Push revisions to user"); - self.push_revisions_to_user(user, from_rev_id, to_rev_id).await; - } - } - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self, revisions), fields(object_id), err)] - pub async fn reset(&self, revisions: Vec) -> Result<(), SyncError> { - let object_id = self.object_id.clone(); - tracing::Span::current().record("object_id", &object_id.as_str()); - let (_, rev_id) = pair_rev_id_from_revision_pbs(&revisions); - let operations = make_operations_from_revisions(revisions.clone())?; - self.persistence.reset_object(&object_id, revisions).await?; - self.object.write().set_operations(operations); - let _ = self.rev_id.fetch_update(SeqCst, SeqCst, |_e| Some(rev_id)); - Ok(()) - } - - pub fn object_json(&self) -> String { - self.object.read().object_json() - } - - fn compose_revision(&self, revision: &Revision) -> Result<(), SyncError> { - let operations = RevisionOperations::::from_bytes(&revision.bytes)?; - self.compose_operations(operations)?; - let _ = self.rev_id.fetch_update(SeqCst, SeqCst, |_e| Some(revision.rev_id)); - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self, revision))] - fn transform_revision( - &self, - revision: &Revision, - ) -> Result<(RevisionOperations, RevisionOperations), SyncError> { - let client_operations = RevisionOperations::::from_bytes(&revision.bytes)?; - let result = self.object.read().transform(&client_operations)?; - Ok(result) - } - - fn compose_operations(&self, operations: RevisionOperations) -> Result<(), SyncError> { - if operations.is_empty() { - tracing::warn!("Composed operations is empty"); - } - - match self.object.try_write_for(Duration::from_millis(300)) { - None => tracing::error!("Failed to acquire write lock of object"), - Some(mut write_guard) => { - write_guard.compose(&operations)?; - } - } - Ok(()) - } - - pub(crate) fn rev_id(&self) -> i64 { - self.rev_id.load(SeqCst) - } - - async fn is_applied_before(&self, new_revision: &Revision, persistence: &Arc) -> bool { - let rev_ids = Some(vec![new_revision.rev_id]); - if let Ok(revisions) = persistence.read_revisions(&self.object_id, rev_ids).await { - if let Some(revision) = revisions.first() { - if revision.md5 == new_revision.md5 { - return true; - } - } - }; - - false - } - - async fn push_revisions_to_user(&self, user: Arc, from: i64, to: i64) { - let rev_ids: Vec = (from..=to).collect(); - tracing::debug!("Push revision: {} -> {} to client", from, to); - match self - .persistence - .read_revisions(&self.object_id, Some(rev_ids.clone())) - .await + match server_base_rev_id.cmp(&first_revision.rev_id) { + Ordering::Less => { + let server_rev_id = next(server_base_rev_id); + if server_base_rev_id == first_revision.base_rev_id + || server_rev_id == first_revision.rev_id { - Ok(revisions) => { - if !rev_ids.is_empty() && revisions.is_empty() { - tracing::trace!("{}: can not read the revisions in range {:?}", self.object_id, rev_ids); - // assert_eq!(revisions.is_empty(), rev_ids.is_empty(),); - } - - let data = ServerRevisionWSDataBuilder::build_push_message(&self.object_id, revisions); - user.receive(RevisionSyncResponse::Push(data)); - } - Err(e) => { - tracing::error!("{:?}", e); - } - }; + // The rev is in the right order, just compose it. + for revision in revisions.iter() { + self.compose_revision(revision)?; + } + self.persistence.save_revisions(revisions).await?; + } else { + // The server ops is outdated, pull the missing revision from the client. + let range = RevisionRange { + start: server_rev_id, + end: first_revision.rev_id, + }; + let msg = ServerRevisionWSDataBuilder::build_pull_message(&self.object_id, range); + user.receive(RevisionSyncResponse::Pull(msg)); + } + }, + Ordering::Equal => { + // Do nothing + tracing::trace!( + "Applied {} revision rev_id is the same as cur_rev_id", + self.object_id + ); + }, + Ordering::Greater => { + // The client ops is outdated. Transform the client revision ops and then + // send the prime ops to the client. Client should compose the this prime + // ops. + let from_rev_id = first_revision.rev_id; + let to_rev_id = server_base_rev_id; + self + .push_revisions_to_user(user, from_rev_id, to_rev_id) + .await; + }, } + Ok(()) + } + + #[tracing::instrument(level = "trace", skip(self, user), fields(server_rev_id), err)] + pub async fn pong( + &self, + user: Arc, + client_rev_id: i64, + ) -> Result<(), SyncError> { + let object_id = self.object_id.clone(); + let server_rev_id = self.rev_id(); + tracing::Span::current().record("server_rev_id", &server_rev_id); + match server_rev_id.cmp(&client_rev_id) { + Ordering::Less => { + tracing::trace!( + "Client should not send ping and the server should pull the revisions from the client" + ) + }, + Ordering::Equal => tracing::trace!("{} is up to date.", object_id), + Ordering::Greater => { + // The client ops is outdated. Transform the client revision ops and then + // send the prime ops to the client. Client should compose the this prime + // ops. + let from_rev_id = client_rev_id; + let to_rev_id = server_rev_id; + tracing::trace!("Push revisions to user"); + self + .push_revisions_to_user(user, from_rev_id, to_rev_id) + .await; + }, + } + Ok(()) + } + + #[tracing::instrument(level = "debug", skip(self, revisions), fields(object_id), err)] + pub async fn reset(&self, revisions: Vec) -> Result<(), SyncError> { + let object_id = self.object_id.clone(); + tracing::Span::current().record("object_id", &object_id.as_str()); + let (_, rev_id) = pair_rev_id_from_revision_pbs(&revisions); + let operations = make_operations_from_revisions(revisions.clone())?; + self.persistence.reset_object(&object_id, revisions).await?; + self.object.write().set_operations(operations); + let _ = self.rev_id.fetch_update(SeqCst, SeqCst, |_e| Some(rev_id)); + Ok(()) + } + + pub fn object_json(&self) -> String { + self.object.read().object_json() + } + + fn compose_revision(&self, revision: &Revision) -> Result<(), SyncError> { + let operations = RevisionOperations::::from_bytes(&revision.bytes)?; + self.compose_operations(operations)?; + let _ = self + .rev_id + .fetch_update(SeqCst, SeqCst, |_e| Some(revision.rev_id)); + Ok(()) + } + + #[tracing::instrument(level = "debug", skip(self, revision))] + fn transform_revision( + &self, + revision: &Revision, + ) -> Result<(RevisionOperations, RevisionOperations), SyncError> { + let client_operations = RevisionOperations::::from_bytes(&revision.bytes)?; + let result = self.object.read().transform(&client_operations)?; + Ok(result) + } + + fn compose_operations(&self, operations: RevisionOperations) -> Result<(), SyncError> { + if operations.is_empty() { + tracing::warn!("Composed operations is empty"); + } + + match self.object.try_write_for(Duration::from_millis(300)) { + None => tracing::error!("Failed to acquire write lock of object"), + Some(mut write_guard) => { + write_guard.compose(&operations)?; + }, + } + Ok(()) + } + + pub(crate) fn rev_id(&self) -> i64 { + self.rev_id.load(SeqCst) + } + + async fn is_applied_before( + &self, + new_revision: &Revision, + persistence: &Arc, + ) -> bool { + let rev_ids = Some(vec![new_revision.rev_id]); + if let Ok(revisions) = persistence.read_revisions(&self.object_id, rev_ids).await { + if let Some(revision) = revisions.first() { + if revision.md5 == new_revision.md5 { + return true; + } + } + }; + + false + } + + async fn push_revisions_to_user(&self, user: Arc, from: i64, to: i64) { + let rev_ids: Vec = (from..=to).collect(); + tracing::debug!("Push revision: {} -> {} to client", from, to); + match self + .persistence + .read_revisions(&self.object_id, Some(rev_ids.clone())) + .await + { + Ok(revisions) => { + if !rev_ids.is_empty() && revisions.is_empty() { + tracing::trace!( + "{}: can not read the revisions in range {:?}", + self.object_id, + rev_ids + ); + // assert_eq!(revisions.is_empty(), rev_ids.is_empty(),); + } + + let data = ServerRevisionWSDataBuilder::build_push_message(&self.object_id, revisions); + user.receive(RevisionSyncResponse::Push(data)); + }, + Err(e) => { + tracing::error!("{:?}", e); + }, + }; + } } diff --git a/shared-lib/flowy-sync/src/util.rs b/shared-lib/flowy-sync/src/util.rs index 328050ab74..9cf930c319 100644 --- a/shared-lib/flowy-sync/src/util.rs +++ b/shared-lib/flowy-sync/src/util.rs @@ -5,41 +5,41 @@ use revision_model::Revision; use serde::de::DeserializeOwned; pub fn pair_rev_id_from_revision_pbs(revisions: &[Revision]) -> (i64, i64) { - let mut rev_id = 0; - revisions.iter().for_each(|revision| { - if rev_id < revision.rev_id { - rev_id = revision.rev_id; - } - }); - - if rev_id > 0 { - (rev_id - 1, rev_id) - } else { - (0, rev_id) + let mut rev_id = 0; + revisions.iter().for_each(|revision| { + if rev_id < revision.rev_id { + rev_id = revision.rev_id; } + }); + + if rev_id > 0 { + (rev_id - 1, rev_id) + } else { + (0, rev_id) + } } #[tracing::instrument(level = "trace", skip(revisions), err)] pub fn make_operations_from_revisions(revisions: Vec) -> SyncResult> where - T: OperationAttributes + DeserializeOwned + OperationAttributes + serde::Serialize, + T: OperationAttributes + DeserializeOwned + OperationAttributes + serde::Serialize, { - let mut new_operations = DeltaOperations::::new(); - for revision in revisions { - if revision.bytes.is_empty() { - return Err(SyncError::unexpected_empty_revision().context("Unexpected Empty revision")); - } - let operations = DeltaOperations::::from_bytes(revision.bytes).map_err(|e| { - let err_msg = format!("Deserialize revision failed: {:?}", e); - SyncError::internal().context(err_msg) - })?; - - new_operations = new_operations.compose(&operations)?; + let mut new_operations = DeltaOperations::::new(); + for revision in revisions { + if revision.bytes.is_empty() { + return Err(SyncError::unexpected_empty_revision().context("Unexpected Empty revision")); } - Ok(new_operations) + let operations = DeltaOperations::::from_bytes(revision.bytes).map_err(|e| { + let err_msg = format!("Deserialize revision failed: {:?}", e); + SyncError::internal().context(err_msg) + })?; + + new_operations = new_operations.compose(&operations)?; + } + Ok(new_operations) } #[inline] pub fn next(rev_id: i64) -> i64 { - rev_id + 1 + rev_id + 1 } diff --git a/shared-lib/folder-model/src/app_rev.rs b/shared-lib/folder-model/src/app_rev.rs index 457ea78152..c20575f807 100644 --- a/shared-lib/folder-model/src/app_rev.rs +++ b/shared-lib/folder-model/src/app_rev.rs @@ -3,38 +3,38 @@ use nanoid::nanoid; use serde::{Deserialize, Serialize}; pub fn gen_app_id() -> String { - nanoid!(10) + nanoid!(10) } #[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct AppRevision { - pub id: String, + pub id: String, - pub workspace_id: String, + pub workspace_id: String, - pub name: String, + pub name: String, - pub desc: String, + pub desc: String, - pub belongings: Vec, + pub belongings: Vec, - #[serde(default)] - pub version: i64, + #[serde(default)] + pub version: i64, - #[serde(default)] - pub modified_time: i64, + #[serde(default)] + pub modified_time: i64, - #[serde(default)] - pub create_time: i64, + #[serde(default)] + pub create_time: i64, } impl std::convert::From for TrashRevision { - fn from(app_rev: AppRevision) -> Self { - TrashRevision { - id: app_rev.id, - name: app_rev.name, - modified_time: app_rev.modified_time, - create_time: app_rev.create_time, - ty: TrashTypeRevision::TrashApp, - } + fn from(app_rev: AppRevision) -> Self { + TrashRevision { + id: app_rev.id, + name: app_rev.name, + modified_time: app_rev.modified_time, + create_time: app_rev.create_time, + ty: TrashTypeRevision::TrashApp, } + } } diff --git a/shared-lib/folder-model/src/folder.rs b/shared-lib/folder-model/src/folder.rs index 5e85360c79..d5a1904414 100644 --- a/shared-lib/folder-model/src/folder.rs +++ b/shared-lib/folder-model/src/folder.rs @@ -2,8 +2,8 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)] pub struct FolderInfo { - pub folder_id: String, - pub text: String, - pub rev_id: i64, - pub base_rev_id: i64, + pub folder_id: String, + pub text: String, + pub rev_id: i64, + pub base_rev_id: i64, } diff --git a/shared-lib/folder-model/src/folder_rev.rs b/shared-lib/folder-model/src/folder_rev.rs index c6378aab4d..2c7637eb47 100644 --- a/shared-lib/folder-model/src/folder_rev.rs +++ b/shared-lib/folder-model/src/folder_rev.rs @@ -6,61 +6,65 @@ use std::sync::Arc; #[derive(Debug, Default, Serialize, Clone, Eq, PartialEq)] pub struct FolderRevision { - pub workspaces: Vec>, - pub trash: Vec>, + pub workspaces: Vec>, + pub trash: Vec>, } impl<'de> Deserialize<'de> for FolderRevision { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct FolderVisitor<'a>(&'a mut Option); - impl<'de, 'a> Visitor<'de> for FolderVisitor<'a> { - type Value = (); - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("Expect struct FolderRevision") - } + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FolderVisitor<'a>(&'a mut Option); + impl<'de, 'a> Visitor<'de> for FolderVisitor<'a> { + type Value = (); + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Expect struct FolderRevision") + } - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut workspaces: Option> = None; - let mut trash: Option> = None; - while let Some(key) = map.next_key::()? { - if key == "workspaces" && workspaces.is_none() { - workspaces = Some(map.next_value::>()?); - } - if key == "trash" && trash.is_none() { - trash = Some(map.next_value::>()?); - } - } - - if let Some(workspaces) = workspaces { - *self.0 = Some(FolderRevision { - workspaces: workspaces.into_iter().map(Arc::new).collect(), - trash: trash.unwrap_or_default().into_iter().map(Arc::new).collect(), - }); - Ok(()) - } else { - Err(de::Error::missing_field("workspaces")) - } - } + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut workspaces: Option> = None; + let mut trash: Option> = None; + while let Some(key) = map.next_key::()? { + if key == "workspaces" && workspaces.is_none() { + workspaces = Some(map.next_value::>()?); + } + if key == "trash" && trash.is_none() { + trash = Some(map.next_value::>()?); + } } - let mut folder_rev: Option = None; - const FIELDS: &[&str] = &["workspaces", "trash"]; - let _ = serde::Deserializer::deserialize_struct( - deserializer, - "FolderRevision", - FIELDS, - FolderVisitor(&mut folder_rev), - ); - - match folder_rev { - None => Err(de::Error::missing_field("workspaces or trash")), - Some(folder_rev) => Ok(folder_rev), + if let Some(workspaces) = workspaces { + *self.0 = Some(FolderRevision { + workspaces: workspaces.into_iter().map(Arc::new).collect(), + trash: trash + .unwrap_or_default() + .into_iter() + .map(Arc::new) + .collect(), + }); + Ok(()) + } else { + Err(de::Error::missing_field("workspaces")) } + } } + + let mut folder_rev: Option = None; + const FIELDS: &[&str] = &["workspaces", "trash"]; + let _ = serde::Deserializer::deserialize_struct( + deserializer, + "FolderRevision", + FIELDS, + FolderVisitor(&mut folder_rev), + ); + + match folder_rev { + None => Err(de::Error::missing_field("workspaces or trash")), + Some(folder_rev) => Ok(folder_rev), + } + } } diff --git a/shared-lib/folder-model/src/macros.rs b/shared-lib/folder-model/src/macros.rs index 36e53c3a86..95e3c2d638 100644 --- a/shared-lib/folder-model/src/macros.rs +++ b/shared-lib/folder-model/src/macros.rs @@ -1,43 +1,43 @@ #[macro_export] macro_rules! impl_def_and_def_mut { - ($target:ident, $item: ident) => { - impl std::ops::Deref for $target { - type Target = Vec<$item>; + ($target:ident, $item: ident) => { + impl std::ops::Deref for $target { + type Target = Vec<$item>; - fn deref(&self) -> &Self::Target { - &self.items - } - } - impl std::ops::DerefMut for $target { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.items - } + fn deref(&self) -> &Self::Target { + &self.items + } + } + impl std::ops::DerefMut for $target { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.items + } + } + + impl $target { + #[allow(dead_code)] + pub fn into_inner(self) -> Vec<$item> { + self.items + } + + #[allow(dead_code)] + pub fn push(&mut self, item: $item) { + if self.items.contains(&item) { + log::error!("add duplicate item: {:?}", item); + return; } - impl $target { - #[allow(dead_code)] - pub fn into_inner(self) -> Vec<$item> { - self.items - } + self.items.push(item); + } - #[allow(dead_code)] - pub fn push(&mut self, item: $item) { - if self.items.contains(&item) { - log::error!("add duplicate item: {:?}", item); - return; - } + #[allow(dead_code)] + pub fn take_items(&mut self) -> Vec<$item> { + std::mem::take(&mut self.items) + } - self.items.push(item); - } - - #[allow(dead_code)] - pub fn take_items(&mut self) -> Vec<$item> { - std::mem::take(&mut self.items) - } - - pub fn first_or_crash(&self) -> &$item { - self.items.first().unwrap() - } - } - }; + pub fn first_or_crash(&self) -> &$item { + self.items.first().unwrap() + } + } + }; } diff --git a/shared-lib/folder-model/src/trash_rev.rs b/shared-lib/folder-model/src/trash_rev.rs index 363f524c6d..73b8526ebf 100644 --- a/shared-lib/folder-model/src/trash_rev.rs +++ b/shared-lib/folder-model/src/trash_rev.rs @@ -4,132 +4,132 @@ use serde_repr::*; use std::fmt; #[derive(Default, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] pub struct TrashRevision { - pub id: String, + pub id: String, - pub name: String, + pub name: String, - #[serde(default)] - pub modified_time: i64, + #[serde(default)] + pub modified_time: i64, - #[serde(default)] - pub create_time: i64, + #[serde(default)] + pub create_time: i64, - pub ty: TrashTypeRevision, + pub ty: TrashTypeRevision, } #[derive(Eq, PartialEq, Debug, Clone, Hash, Serialize_repr)] #[repr(u8)] pub enum TrashTypeRevision { - Unknown = 0, - TrashView = 1, - TrashApp = 2, + Unknown = 0, + TrashView = 1, + TrashApp = 2, } impl<'de> serde::Deserialize<'de> for TrashTypeRevision { - fn deserialize(deserializer: D) -> core::result::Result - where - D: serde::Deserializer<'de>, - { - struct TrashTypeVisitor(); + fn deserialize(deserializer: D) -> core::result::Result + where + D: serde::Deserializer<'de>, + { + struct TrashTypeVisitor(); - impl<'de> Visitor<'de> for TrashTypeVisitor { - type Value = TrashTypeRevision; + impl<'de> Visitor<'de> for TrashTypeVisitor { + type Value = TrashTypeRevision; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("expected enum TrashTypeRevision with type: u8") - } + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("expected enum TrashTypeRevision with type: u8") + } - fn visit_i8(self, v: i8) -> Result - where - E: serde::de::Error, - { - self.visit_u8(v as u8) - } + fn visit_i8(self, v: i8) -> Result + where + E: serde::de::Error, + { + self.visit_u8(v as u8) + } - fn visit_i16(self, v: i16) -> Result - where - E: serde::de::Error, - { - self.visit_u8(v as u8) - } + fn visit_i16(self, v: i16) -> Result + where + E: serde::de::Error, + { + self.visit_u8(v as u8) + } - fn visit_i32(self, v: i32) -> Result - where - E: serde::de::Error, - { - self.visit_u8(v as u8) - } + fn visit_i32(self, v: i32) -> Result + where + E: serde::de::Error, + { + self.visit_u8(v as u8) + } - fn visit_i64(self, v: i64) -> Result - where - E: serde::de::Error, - { - self.visit_u8(v as u8) - } + fn visit_i64(self, v: i64) -> Result + where + E: serde::de::Error, + { + self.visit_u8(v as u8) + } - fn visit_u8(self, v: u8) -> Result - where - E: serde::de::Error, - { - let ty = match v { - 0 => TrashTypeRevision::Unknown, - 1 => TrashTypeRevision::TrashView, - 2 => TrashTypeRevision::TrashApp, - _ => TrashTypeRevision::Unknown, - }; + fn visit_u8(self, v: u8) -> Result + where + E: serde::de::Error, + { + let ty = match v { + 0 => TrashTypeRevision::Unknown, + 1 => TrashTypeRevision::TrashView, + 2 => TrashTypeRevision::TrashApp, + _ => TrashTypeRevision::Unknown, + }; - Ok(ty) - } + Ok(ty) + } - fn visit_u16(self, v: u16) -> Result - where - E: serde::de::Error, - { - self.visit_u8(v as u8) - } + fn visit_u16(self, v: u16) -> Result + where + E: serde::de::Error, + { + self.visit_u8(v as u8) + } - fn visit_u32(self, v: u32) -> Result - where - E: serde::de::Error, - { - self.visit_u8(v as u8) - } + fn visit_u32(self, v: u32) -> Result + where + E: serde::de::Error, + { + self.visit_u8(v as u8) + } - fn visit_u64(self, v: u64) -> Result - where - E: serde::de::Error, - { - self.visit_u8(v as u8) - } + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + self.visit_u8(v as u8) + } - fn visit_str(self, s: &str) -> Result - where - E: serde::de::Error, - { - let value = match s { - "Unknown" => TrashTypeRevision::Unknown, - "TrashView" => TrashTypeRevision::TrashView, - "TrashApp" => TrashTypeRevision::TrashApp, - _ => TrashTypeRevision::Unknown, - }; - Ok(value) - } - } - - deserializer.deserialize_any(TrashTypeVisitor()) + fn visit_str(self, s: &str) -> Result + where + E: serde::de::Error, + { + let value = match s { + "Unknown" => TrashTypeRevision::Unknown, + "TrashView" => TrashTypeRevision::TrashView, + "TrashApp" => TrashTypeRevision::TrashApp, + _ => TrashTypeRevision::Unknown, + }; + Ok(value) + } } + + deserializer.deserialize_any(TrashTypeVisitor()) + } } impl std::default::Default for TrashTypeRevision { - fn default() -> Self { - TrashTypeRevision::Unknown - } + fn default() -> Self { + TrashTypeRevision::Unknown + } } impl std::convert::From for u8 { - fn from(rev: TrashTypeRevision) -> Self { - match rev { - TrashTypeRevision::Unknown => 0, - TrashTypeRevision::TrashView => 1, - TrashTypeRevision::TrashApp => 2, - } + fn from(rev: TrashTypeRevision) -> Self { + match rev { + TrashTypeRevision::Unknown => 0, + TrashTypeRevision::TrashView => 1, + TrashTypeRevision::TrashApp => 2, } + } } diff --git a/shared-lib/folder-model/src/user_default.rs b/shared-lib/folder-model/src/user_default.rs index 1dbd38f358..89e230c880 100644 --- a/shared-lib/folder-model/src/user_default.rs +++ b/shared-lib/folder-model/src/user_default.rs @@ -1,62 +1,62 @@ use crate::{ - gen_app_id, gen_view_id, gen_workspace_id, AppRevision, ViewDataFormatRevision, ViewLayoutTypeRevision, - ViewRevision, WorkspaceRevision, + gen_app_id, gen_view_id, gen_workspace_id, AppRevision, ViewDataFormatRevision, + ViewLayoutTypeRevision, ViewRevision, WorkspaceRevision, }; use chrono::Utc; pub fn create_default_workspace() -> WorkspaceRevision { - let time = Utc::now(); - let workspace_id = gen_workspace_id(); - let name = "Workspace".to_string(); - let desc = "".to_string(); + let time = Utc::now(); + let workspace_id = gen_workspace_id(); + let name = "Workspace".to_string(); + let desc = "".to_string(); - let apps = vec![create_default_app(workspace_id.to_string(), time)]; + let apps = vec![create_default_app(workspace_id.to_string(), time)]; - WorkspaceRevision { - id: workspace_id, - name, - desc, - apps, - modified_time: time.timestamp(), - create_time: time.timestamp(), - } + WorkspaceRevision { + id: workspace_id, + name, + desc, + apps, + modified_time: time.timestamp(), + create_time: time.timestamp(), + } } fn create_default_app(workspace_id: String, time: chrono::DateTime) -> AppRevision { - let app_id = gen_app_id(); - let name = "⭐️ Getting started".to_string(); - let desc = "".to_string(); + let app_id = gen_app_id(); + let name = "⭐️ Getting started".to_string(); + let desc = "".to_string(); - let views = vec![create_default_view(app_id.to_string(), time)]; + let views = vec![create_default_view(app_id.to_string(), time)]; - AppRevision { - id: app_id, - workspace_id, - name, - desc, - belongings: views, - version: 0, - modified_time: time.timestamp(), - create_time: time.timestamp(), - } + AppRevision { + id: app_id, + workspace_id, + name, + desc, + belongings: views, + version: 0, + modified_time: time.timestamp(), + create_time: time.timestamp(), + } } fn create_default_view(app_id: String, time: chrono::DateTime) -> ViewRevision { - let view_id = gen_view_id(); - let name = "Read me".to_string(); + let view_id = gen_view_id(); + let name = "Read me".to_string(); - ViewRevision { - id: view_id, - app_id, - name, - desc: "".to_string(), - data_format: ViewDataFormatRevision::DeltaFormat, - version: 0, - belongings: vec![], - modified_time: time.timestamp(), - create_time: time.timestamp(), - ext_data: "".to_string(), - thumbnail: "".to_string(), - layout: ViewLayoutTypeRevision::Document, - } + ViewRevision { + id: view_id, + app_id, + name, + desc: "".to_string(), + data_format: ViewDataFormatRevision::DeltaFormat, + version: 0, + belongings: vec![], + modified_time: time.timestamp(), + create_time: time.timestamp(), + ext_data: "".to_string(), + thumbnail: "".to_string(), + layout: ViewLayoutTypeRevision::Document, + } } diff --git a/shared-lib/folder-model/src/view_rev.rs b/shared-lib/folder-model/src/view_rev.rs index 8cb9f5a866..2b916d6011 100644 --- a/shared-lib/folder-model/src/view_rev.rs +++ b/shared-lib/folder-model/src/view_rev.rs @@ -3,83 +3,83 @@ use nanoid::nanoid; use serde::{Deserialize, Serialize}; use serde_repr::*; pub fn gen_view_id() -> String { - nanoid!(10) + nanoid!(10) } #[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct ViewRevision { - pub id: String, + pub id: String, - #[serde(rename = "belong_to_id")] - pub app_id: String, + #[serde(rename = "belong_to_id")] + pub app_id: String, - pub name: String, + pub name: String, - pub desc: String, + pub desc: String, - #[serde(default)] - #[serde(rename = "data_type")] - pub data_format: ViewDataFormatRevision, + #[serde(default)] + #[serde(rename = "data_type")] + pub data_format: ViewDataFormatRevision, - pub version: i64, // Deprecated + pub version: i64, // Deprecated - pub belongings: Vec, + pub belongings: Vec, - #[serde(default)] - pub modified_time: i64, + #[serde(default)] + pub modified_time: i64, - #[serde(default)] - pub create_time: i64, + #[serde(default)] + pub create_time: i64, - #[serde(default)] - pub ext_data: String, + #[serde(default)] + pub ext_data: String, - #[serde(default)] - pub thumbnail: String, + #[serde(default)] + pub thumbnail: String, - #[serde(default = "DEFAULT_PLUGIN_TYPE")] - #[serde(rename = "plugin_type")] - pub layout: ViewLayoutTypeRevision, + #[serde(default = "DEFAULT_PLUGIN_TYPE")] + #[serde(rename = "plugin_type")] + pub layout: ViewLayoutTypeRevision, } const DEFAULT_PLUGIN_TYPE: fn() -> ViewLayoutTypeRevision = || ViewLayoutTypeRevision::Document; impl std::convert::From for TrashRevision { - fn from(view_rev: ViewRevision) -> Self { - TrashRevision { - id: view_rev.id, - name: view_rev.name, - modified_time: view_rev.modified_time, - create_time: view_rev.create_time, - ty: TrashTypeRevision::TrashView, - } + fn from(view_rev: ViewRevision) -> Self { + TrashRevision { + id: view_rev.id, + name: view_rev.name, + modified_time: view_rev.modified_time, + create_time: view_rev.create_time, + ty: TrashTypeRevision::TrashView, } + } } #[derive(Eq, PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum ViewDataFormatRevision { - DeltaFormat = 0, - DatabaseFormat = 1, - NodeFormat = 2, + DeltaFormat = 0, + DatabaseFormat = 1, + NodeFormat = 2, } impl std::default::Default for ViewDataFormatRevision { - fn default() -> Self { - ViewDataFormatRevision::DeltaFormat - } + fn default() -> Self { + ViewDataFormatRevision::DeltaFormat + } } #[derive(Eq, PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum ViewLayoutTypeRevision { - Document = 0, - // The for historical reasons, the value of Grid is not 1. - Grid = 3, - Board = 4, - Calendar = 5, + Document = 0, + // The for historical reasons, the value of Grid is not 1. + Grid = 3, + Board = 4, + Calendar = 5, } impl std::default::Default for ViewLayoutTypeRevision { - fn default() -> Self { - ViewLayoutTypeRevision::Document - } + fn default() -> Self { + ViewLayoutTypeRevision::Document + } } diff --git a/shared-lib/folder-model/src/workspace_rev.rs b/shared-lib/folder-model/src/workspace_rev.rs index 0f042e431c..9af2f6616d 100644 --- a/shared-lib/folder-model/src/workspace_rev.rs +++ b/shared-lib/folder-model/src/workspace_rev.rs @@ -2,21 +2,21 @@ use crate::AppRevision; use nanoid::nanoid; use serde::{Deserialize, Serialize}; pub fn gen_workspace_id() -> String { - nanoid!(10) + nanoid!(10) } #[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct WorkspaceRevision { - pub id: String, + pub id: String, - pub name: String, + pub name: String, - pub desc: String, + pub desc: String, - pub apps: Vec, + pub apps: Vec, - #[serde(default)] - pub modified_time: i64, + #[serde(default)] + pub modified_time: i64, - #[serde(default)] - pub create_time: i64, + #[serde(default)] + pub create_time: i64, } diff --git a/shared-lib/grid-model/src/filter_rev.rs b/shared-lib/grid-model/src/filter_rev.rs index 0dd65d4dc8..4f4e0e1ccd 100644 --- a/shared-lib/grid-model/src/filter_rev.rs +++ b/shared-lib/grid-model/src/filter_rev.rs @@ -3,10 +3,10 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] pub struct FilterRevision { - pub id: String, - pub field_id: String, - pub field_type: FieldTypeRevision, - pub condition: u8, - #[serde(default)] - pub content: String, + pub id: String, + pub field_id: String, + pub field_type: FieldTypeRevision, + pub condition: u8, + #[serde(default)] + pub content: String, } diff --git a/shared-lib/grid-model/src/grid_block.rs b/shared-lib/grid-model/src/grid_block.rs index d496dfe35e..9adc48da10 100644 --- a/shared-lib/grid-model/src/grid_block.rs +++ b/shared-lib/grid-model/src/grid_block.rs @@ -5,79 +5,81 @@ use std::collections::HashMap; use std::sync::Arc; pub fn gen_row_id() -> String { - nanoid!(6) + nanoid!(6) } pub const DEFAULT_ROW_HEIGHT: i32 = 42; #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct DatabaseBlockRevision { - pub block_id: String, - pub rows: Vec>, + pub block_id: String, + pub rows: Vec>, } pub type FieldId = String; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct RowRevision { - pub id: String, - pub block_id: String, - /// cells contains key/value pairs. - /// key: field id, - /// value: CellMeta - #[serde(with = "indexmap::serde_seq")] - pub cells: IndexMap, - pub height: i32, - pub visibility: bool, + pub id: String, + pub block_id: String, + /// cells contains key/value pairs. + /// key: field id, + /// value: CellMeta + #[serde(with = "indexmap::serde_seq")] + pub cells: IndexMap, + pub height: i32, + pub visibility: bool, } impl RowRevision { - pub fn new(block_id: &str) -> Self { - Self { - id: gen_row_id(), - block_id: block_id.to_owned(), - cells: Default::default(), - height: DEFAULT_ROW_HEIGHT, - visibility: true, - } + pub fn new(block_id: &str) -> Self { + Self { + id: gen_row_id(), + block_id: block_id.to_owned(), + cells: Default::default(), + height: DEFAULT_ROW_HEIGHT, + visibility: true, } + } } #[derive(Debug, Clone, Default)] pub struct RowChangeset { - pub row_id: String, - pub height: Option, - pub visibility: Option, - // Contains the key/value changes represents as the update of the cells. For example, - // if there is one cell was changed, then the `cell_by_field_id` will only have one key/value. - pub cell_by_field_id: HashMap, + pub row_id: String, + pub height: Option, + pub visibility: Option, + // Contains the key/value changes represents as the update of the cells. For example, + // if there is one cell was changed, then the `cell_by_field_id` will only have one key/value. + pub cell_by_field_id: HashMap, } impl RowChangeset { - pub fn new(row_id: String) -> Self { - Self { - row_id, - height: None, - visibility: None, - cell_by_field_id: Default::default(), - } + pub fn new(row_id: String) -> Self { + Self { + row_id, + height: None, + visibility: None, + cell_by_field_id: Default::default(), } + } - pub fn is_empty(&self) -> bool { - self.height.is_none() && self.visibility.is_none() && self.cell_by_field_id.is_empty() - } + pub fn is_empty(&self) -> bool { + self.height.is_none() && self.visibility.is_none() && self.cell_by_field_id.is_empty() + } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct CellRevision { - #[serde(rename = "data")] - pub type_cell_data: String, + #[serde(rename = "data")] + pub type_cell_data: String, } impl CellRevision { - pub fn new(data: String) -> Self { - Self { type_cell_data: data } + pub fn new(data: String) -> Self { + Self { + type_cell_data: data, } + } - pub fn is_empty(&self) -> bool { - self.type_cell_data.is_empty() - } + pub fn is_empty(&self) -> bool { + self.type_cell_data.is_empty() + } } diff --git a/shared-lib/grid-model/src/grid_rev.rs b/shared-lib/grid-model/src/grid_rev.rs index d9a09c8de4..7127cdf1ea 100644 --- a/shared-lib/grid-model/src/grid_rev.rs +++ b/shared-lib/grid-model/src/grid_rev.rs @@ -6,216 +6,219 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; pub fn gen_grid_id() -> String { - // nanoid calculator https://zelark.github.io/nano-id-cc/ - nanoid!(10) + // nanoid calculator https://zelark.github.io/nano-id-cc/ + nanoid!(10) } pub fn gen_block_id() -> String { - nanoid!(10) + nanoid!(10) } pub fn gen_field_id() -> String { - nanoid!(6) + nanoid!(6) } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct DatabaseRevision { - pub grid_id: String, - pub fields: Vec>, - pub blocks: Vec>, + pub grid_id: String, + pub fields: Vec>, + pub blocks: Vec>, } impl DatabaseRevision { - pub fn new(grid_id: &str) -> Self { - Self { - grid_id: grid_id.to_owned(), - fields: vec![], - blocks: vec![], - } + pub fn new(grid_id: &str) -> Self { + Self { + grid_id: grid_id.to_owned(), + fields: vec![], + blocks: vec![], } + } - pub fn from_build_context( - grid_id: &str, - field_revs: Vec>, - block_metas: Vec, - ) -> Self { - Self { - grid_id: grid_id.to_owned(), - fields: field_revs, - blocks: block_metas.into_iter().map(Arc::new).collect(), - } + pub fn from_build_context( + grid_id: &str, + field_revs: Vec>, + block_metas: Vec, + ) -> Self { + Self { + grid_id: grid_id.to_owned(), + fields: field_revs, + blocks: block_metas.into_iter().map(Arc::new).collect(), } + } } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct GridBlockMetaRevision { - pub block_id: String, - pub start_row_index: i32, - pub row_count: i32, + pub block_id: String, + pub start_row_index: i32, + pub row_count: i32, } impl GridBlockMetaRevision { - pub fn len(&self) -> i32 { - self.row_count - } + pub fn len(&self) -> i32 { + self.row_count + } - pub fn is_empty(&self) -> bool { - self.row_count == 0 - } + pub fn is_empty(&self) -> bool { + self.row_count == 0 + } } impl GridBlockMetaRevision { - pub fn new() -> Self { - GridBlockMetaRevision { - block_id: gen_block_id(), - ..Default::default() - } + pub fn new() -> Self { + GridBlockMetaRevision { + block_id: gen_block_id(), + ..Default::default() } + } } pub struct GridBlockMetaRevisionChangeset { - pub block_id: String, - pub start_row_index: Option, - pub row_count: Option, + pub block_id: String, + pub start_row_index: Option, + pub row_count: Option, } impl GridBlockMetaRevisionChangeset { - pub fn from_row_count(block_id: String, row_count: i32) -> Self { - Self { - block_id, - start_row_index: None, - row_count: Some(row_count), - } + pub fn from_row_count(block_id: String, row_count: i32) -> Self { + Self { + block_id, + start_row_index: None, + row_count: Some(row_count), } + } } #[derive(Debug, Clone, Default, Serialize, Deserialize, Eq, PartialEq)] pub struct FieldRevision { - pub id: String, + pub id: String, - pub name: String, + pub name: String, - pub desc: String, + pub desc: String, - #[serde(rename = "field_type")] - pub ty: FieldTypeRevision, + #[serde(rename = "field_type")] + pub ty: FieldTypeRevision, - pub frozen: bool, + pub frozen: bool, - pub visibility: bool, + pub visibility: bool, - pub width: i32, + pub width: i32, - /// type_options contains key/value pairs - /// key: id of the FieldType - /// value: type-option data that can be parsed into specified TypeOptionStruct. - /// - /// For example, CheckboxTypeOption, MultiSelectTypeOption etc. - #[serde(with = "indexmap::serde_seq")] - pub type_options: IndexMap, + /// type_options contains key/value pairs + /// key: id of the FieldType + /// value: type-option data that can be parsed into specified TypeOptionStruct. + /// + /// For example, CheckboxTypeOption, MultiSelectTypeOption etc. + #[serde(with = "indexmap::serde_seq")] + pub type_options: IndexMap, - #[serde(default = "DEFAULT_IS_PRIMARY")] - pub is_primary: bool, + #[serde(default = "DEFAULT_IS_PRIMARY")] + pub is_primary: bool, } impl AsRef for FieldRevision { - fn as_ref(&self) -> &FieldRevision { - self - } + fn as_ref(&self) -> &FieldRevision { + self + } } const DEFAULT_IS_PRIMARY: fn() -> bool = || false; impl FieldRevision { - pub fn new>( - name: &str, - desc: &str, - field_type: T, - width: i32, - is_primary: bool, - ) -> Self { - Self { - id: gen_field_id(), - name: name.to_string(), - desc: desc.to_string(), - ty: field_type.into(), - frozen: false, - visibility: true, - width, - type_options: Default::default(), - is_primary, - } + pub fn new>( + name: &str, + desc: &str, + field_type: T, + width: i32, + is_primary: bool, + ) -> Self { + Self { + id: gen_field_id(), + name: name.to_string(), + desc: desc.to_string(), + ty: field_type.into(), + frozen: false, + visibility: true, + width, + type_options: Default::default(), + is_primary, } + } - pub fn insert_type_option(&mut self, type_option: &T) - where - T: TypeOptionDataSerializer + ?Sized, - { - let id = self.ty.to_string(); - self.type_options.insert(id, type_option.json_str()); - } + pub fn insert_type_option(&mut self, type_option: &T) + where + T: TypeOptionDataSerializer + ?Sized, + { + let id = self.ty.to_string(); + self.type_options.insert(id, type_option.json_str()); + } - pub fn get_type_option(&self, field_type_rev: FieldTypeRevision) -> Option { - let id = field_type_rev.to_string(); - self.type_options.get(&id).map(|s| T::from_json_str(s)) - } + pub fn get_type_option( + &self, + field_type_rev: FieldTypeRevision, + ) -> Option { + let id = field_type_rev.to_string(); + self.type_options.get(&id).map(|s| T::from_json_str(s)) + } - pub fn insert_type_option_str(&mut self, field_type: &FieldTypeRevision, json_str: String) { - let id = field_type.to_string(); - self.type_options.insert(id, json_str); - } + pub fn insert_type_option_str(&mut self, field_type: &FieldTypeRevision, json_str: String) { + let id = field_type.to_string(); + self.type_options.insert(id, json_str); + } - pub fn get_type_option_str>(&self, field_type: T) -> Option<&str> { - let field_type_rev = field_type.into(); - let id = field_type_rev.to_string(); - self.type_options.get(&id).map(|s| s.as_str()) - } + pub fn get_type_option_str>(&self, field_type: T) -> Option<&str> { + let field_type_rev = field_type.into(); + let id = field_type_rev.to_string(); + self.type_options.get(&id).map(|s| s.as_str()) + } } /// The macro [impl_type_option] will implement the [TypeOptionDataSerializer] for the type that /// supports the serde trait and the TryInto trait. pub trait TypeOptionDataSerializer { - fn json_str(&self) -> String; - fn protobuf_bytes(&self) -> Bytes; + fn json_str(&self) -> String; + fn protobuf_bytes(&self) -> Bytes; } /// The macro [impl_type_option] will implement the [TypeOptionDataDeserializer] for the type that /// supports the serde trait and the TryFrom trait. pub trait TypeOptionDataDeserializer { - fn from_json_str(s: &str) -> Self; - fn from_protobuf_bytes(bytes: Bytes) -> Self; + fn from_json_str(s: &str) -> Self; + fn from_protobuf_bytes(bytes: Bytes) -> Self; } #[derive(Clone, Default, Deserialize, Serialize)] pub struct BuildDatabaseContext { - pub field_revs: Vec>, - pub block_metas: Vec, - pub blocks: Vec, + pub field_revs: Vec>, + pub block_metas: Vec, + pub blocks: Vec, - // String in JSON format. It can be deserialized into [GridViewRevision] - pub grid_view_revision_data: String, + // String in JSON format. It can be deserialized into [GridViewRevision] + pub grid_view_revision_data: String, } impl BuildDatabaseContext { - pub fn new() -> Self { - Self::default() - } + pub fn new() -> Self { + Self::default() + } } impl std::convert::From for Bytes { - fn from(ctx: BuildDatabaseContext) -> Self { - let bytes = serde_json::to_vec(&ctx).unwrap_or_else(|_| vec![]); - Bytes::from(bytes) - } + fn from(ctx: BuildDatabaseContext) -> Self { + let bytes = serde_json::to_vec(&ctx).unwrap_or_else(|_| vec![]); + Bytes::from(bytes) + } } impl std::convert::TryFrom for BuildDatabaseContext { - type Error = serde_json::Error; + type Error = serde_json::Error; - fn try_from(bytes: Bytes) -> Result { - let ctx: BuildDatabaseContext = serde_json::from_slice(&bytes)?; - Ok(ctx) - } + fn try_from(bytes: Bytes) -> Result { + let ctx: BuildDatabaseContext = serde_json::from_slice(&bytes)?; + Ok(ctx) + } } pub type FieldTypeRevision = u8; diff --git a/shared-lib/grid-model/src/grid_setting_rev.rs b/shared-lib/grid-model/src/grid_setting_rev.rs index df665cbcfb..bfb6ccdee5 100644 --- a/shared-lib/grid-model/src/grid_setting_rev.rs +++ b/shared-lib/grid-model/src/grid_setting_rev.rs @@ -1,4 +1,6 @@ -use crate::{FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision, SortRevision}; +use crate::{ + FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision, SortRevision, +}; use indexmap::IndexMap; use nanoid::nanoid; use serde::{Deserialize, Serialize}; @@ -6,16 +8,16 @@ use std::fmt::Debug; use std::sync::Arc; pub fn gen_grid_filter_id() -> String { - nanoid!(6) + nanoid!(6) } pub fn gen_grid_group_id() -> String { - nanoid!(6) + nanoid!(6) } #[allow(dead_code)] pub fn gen_grid_sort_id() -> String { - nanoid!(6) + nanoid!(6) } pub type FilterConfiguration = Configuration; @@ -28,138 +30,159 @@ pub type SortConfiguration = Configuration; #[serde(transparent)] pub struct Configuration where - T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, + T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, { - /// Key: field_id - /// Value: this value contains key/value. - /// Key: FieldType, - /// Value: the corresponding objects. - #[serde(with = "indexmap::serde_seq")] - inner: IndexMap>, + /// Key: field_id + /// Value: this value contains key/value. + /// Key: FieldType, + /// Value: the corresponding objects. + #[serde(with = "indexmap::serde_seq")] + inner: IndexMap>, } impl Configuration where - T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, + T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, { - pub fn get_mut_objects(&mut self, field_id: &str, field_type: &FieldTypeRevision) -> Option<&mut Vec>> { - let value = self - .inner - .get_mut(field_id) - .and_then(|object_map| object_map.get_mut(field_type)); - if value.is_none() { - eprintln!("[Configuration] Can't find the {:?} with", std::any::type_name::()); - } - value + pub fn get_mut_objects( + &mut self, + field_id: &str, + field_type: &FieldTypeRevision, + ) -> Option<&mut Vec>> { + let value = self + .inner + .get_mut(field_id) + .and_then(|object_map| object_map.get_mut(field_type)); + if value.is_none() { + eprintln!( + "[Configuration] Can't find the {:?} with", + std::any::type_name::() + ); } + value + } - pub fn get_object( - &self, - field_id: &str, - field_type: &FieldTypeRevision, - predicate: impl Fn(&Arc) -> bool, - ) -> Option> { - let objects = self.get_objects(field_id, field_type)?; - let index = objects.iter().position(predicate)?; - objects.get(index).cloned() - } + pub fn get_object( + &self, + field_id: &str, + field_type: &FieldTypeRevision, + predicate: impl Fn(&Arc) -> bool, + ) -> Option> { + let objects = self.get_objects(field_id, field_type)?; + let index = objects.iter().position(predicate)?; + objects.get(index).cloned() + } - pub fn get_mut_object( - &mut self, - field_id: &str, - field_type: &FieldTypeRevision, - predicate: impl Fn(&Arc) -> bool, - ) -> Option<&mut Arc> { - let objects = self.get_mut_objects(field_id, field_type)?; - let index = objects.iter().position(predicate)?; - objects.get_mut(index) - } + pub fn get_mut_object( + &mut self, + field_id: &str, + field_type: &FieldTypeRevision, + predicate: impl Fn(&Arc) -> bool, + ) -> Option<&mut Arc> { + let objects = self.get_mut_objects(field_id, field_type)?; + let index = objects.iter().position(predicate)?; + objects.get_mut(index) + } - pub fn get_objects(&self, field_id: &str, field_type_rev: &FieldTypeRevision) -> Option>> { - self.inner - .get(field_id) - .and_then(|object_map| object_map.get(field_type_rev)) - .cloned() - } + pub fn get_objects( + &self, + field_id: &str, + field_type_rev: &FieldTypeRevision, + ) -> Option>> { + self + .inner + .get(field_id) + .and_then(|object_map| object_map.get(field_type_rev)) + .cloned() + } - pub fn get_objects_by_field_revs(&self, field_revs: &[Arc]) -> Vec> { - // Get the objects according to the FieldType, so we need iterate the field_revs. - let objects = field_revs - .iter() - .flat_map(|field_rev| { - let field_type = &field_rev.ty; - let field_id = &field_rev.id; + pub fn get_objects_by_field_revs(&self, field_revs: &[Arc]) -> Vec> { + // Get the objects according to the FieldType, so we need iterate the field_revs. + let objects = field_revs + .iter() + .flat_map(|field_rev| { + let field_type = &field_rev.ty; + let field_id = &field_rev.id; - let object_rev_map = self.inner.get(field_id)?; - let objects: Vec> = object_rev_map.get(field_type)?.clone(); - Some(objects) - }) - .flatten() - .collect::>>(); - objects - } + let object_rev_map = self.inner.get(field_id)?; + let objects: Vec> = object_rev_map.get(field_type)?.clone(); + Some(objects) + }) + .flatten() + .collect::>>(); + objects + } - pub fn get_all_objects(&self) -> Vec> { - self.inner.values().flat_map(|map| map.all_objects()).collect() - } + pub fn get_all_objects(&self) -> Vec> { + self + .inner + .values() + .flat_map(|map| map.all_objects()) + .collect() + } - /// add object to the end of the list - pub fn add_object(&mut self, field_id: &str, field_type: &FieldTypeRevision, object: T) { - let object_rev_map = self - .inner - .entry(field_id.to_string()) - .or_insert_with(ObjectIndexMap::::new); + /// add object to the end of the list + pub fn add_object(&mut self, field_id: &str, field_type: &FieldTypeRevision, object: T) { + let object_rev_map = self + .inner + .entry(field_id.to_string()) + .or_insert_with(ObjectIndexMap::::new); - object_rev_map - .entry(field_type.to_owned()) - .or_insert_with(Vec::new) - .push(Arc::new(object)) - } + object_rev_map + .entry(field_type.to_owned()) + .or_insert_with(Vec::new) + .push(Arc::new(object)) + } - pub fn clear(&mut self) { - self.inner.clear() - } + pub fn clear(&mut self) { + self.inner.clear() + } } #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(transparent)] pub struct ObjectIndexMap where - T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, + T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, { - #[serde(with = "indexmap::serde_seq")] - pub object_by_field_type: IndexMap>>, + #[serde(with = "indexmap::serde_seq")] + pub object_by_field_type: IndexMap>>, } impl ObjectIndexMap where - T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, + T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, { - pub fn new() -> Self { - ObjectIndexMap::default() - } + pub fn new() -> Self { + ObjectIndexMap::default() + } - pub fn all_objects(&self) -> Vec> { - self.object_by_field_type.values().flatten().cloned().collect() - } + pub fn all_objects(&self) -> Vec> { + self + .object_by_field_type + .values() + .flatten() + .cloned() + .collect() + } } impl std::ops::Deref for ObjectIndexMap where - T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, + T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, { - type Target = IndexMap>>; + type Target = IndexMap>>; - fn deref(&self) -> &Self::Target { - &self.object_by_field_type - } + fn deref(&self) -> &Self::Target { + &self.object_by_field_type + } } impl std::ops::DerefMut for ObjectIndexMap where - T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, + T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.object_by_field_type - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.object_by_field_type + } } diff --git a/shared-lib/grid-model/src/grid_view.rs b/shared-lib/grid-model/src/grid_view.rs index d1d0f6cc2b..b7a35988b8 100644 --- a/shared-lib/grid-model/src/grid_view.rs +++ b/shared-lib/grid-model/src/grid_view.rs @@ -5,88 +5,88 @@ use serde_repr::*; #[allow(dead_code)] pub fn gen_grid_view_id() -> String { - nanoid!(6) + nanoid!(6) } #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum LayoutRevision { - Grid = 0, - Board = 1, - Calendar = 2, + Grid = 0, + Board = 1, + Calendar = 2, } impl ToString for LayoutRevision { - fn to_string(&self) -> String { - let layout_rev = self.clone() as u8; - layout_rev.to_string() - } + fn to_string(&self) -> String { + let layout_rev = self.clone() as u8; + layout_rev.to_string() + } } impl std::default::Default for LayoutRevision { - fn default() -> Self { - LayoutRevision::Grid - } + fn default() -> Self { + LayoutRevision::Grid + } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct DatabaseViewRevision { - pub view_id: String, + pub view_id: String, - pub grid_id: String, + pub grid_id: String, - pub layout: LayoutRevision, + pub layout: LayoutRevision, - #[serde(default)] - pub filters: FilterConfiguration, + #[serde(default)] + pub filters: FilterConfiguration, - #[serde(default)] - pub groups: GroupConfiguration, + #[serde(default)] + pub groups: GroupConfiguration, - #[serde(default)] - pub sorts: SortConfiguration, + #[serde(default)] + pub sorts: SortConfiguration, } impl DatabaseViewRevision { - pub fn new(grid_id: String, view_id: String, layout: LayoutRevision) -> Self { - DatabaseViewRevision { - view_id, - grid_id, - layout, - filters: Default::default(), - groups: Default::default(), - sorts: Default::default(), - } + pub fn new(grid_id: String, view_id: String, layout: LayoutRevision) -> Self { + DatabaseViewRevision { + view_id, + grid_id, + layout, + filters: Default::default(), + groups: Default::default(), + sorts: Default::default(), } + } - pub fn from_json(json: String) -> Result { - serde_json::from_str(&json) - } + pub fn from_json(json: String) -> Result { + serde_json::from_str(&json) + } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct RowOrderRevision { - pub row_id: String, + pub row_id: String, } #[cfg(test)] mod tests { - use crate::DatabaseViewRevision; + use crate::DatabaseViewRevision; - #[test] - fn grid_view_revision_serde_test() { - let grid_view_revision = DatabaseViewRevision { - view_id: "1".to_string(), - grid_id: "1".to_string(), - layout: Default::default(), - filters: Default::default(), - groups: Default::default(), - sorts: Default::default(), - }; - let s = serde_json::to_string(&grid_view_revision).unwrap(); - assert_eq!( - s, - r#"{"view_id":"1","grid_id":"1","layout":0,"filters":[],"groups":[],"sorts":[]}"# - ); - } + #[test] + fn grid_view_revision_serde_test() { + let grid_view_revision = DatabaseViewRevision { + view_id: "1".to_string(), + grid_id: "1".to_string(), + layout: Default::default(), + filters: Default::default(), + groups: Default::default(), + sorts: Default::default(), + }; + let s = serde_json::to_string(&grid_view_revision).unwrap(); + assert_eq!( + s, + r#"{"view_id":"1","grid_id":"1","layout":0,"filters":[],"groups":[],"sorts":[]}"# + ); + } } diff --git a/shared-lib/grid-model/src/group_rev.rs b/shared-lib/grid-model/src/group_rev.rs index 613f3fa1a5..bd743bf1c8 100644 --- a/shared-lib/grid-model/src/group_rev.rs +++ b/shared-lib/grid-model/src/group_rev.rs @@ -4,194 +4,199 @@ use serde_json::Error; use serde_repr::*; pub trait GroupConfigurationContentSerde: Sized + Send + Sync { - fn from_json(s: &str) -> Result; - fn to_json(&self) -> Result; + fn from_json(s: &str) -> Result; + fn to_json(&self) -> Result; } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct GroupConfigurationRevision { - pub id: String, - pub field_id: String, - pub field_type_rev: FieldTypeRevision, - pub groups: Vec, - // This content is serde in Json format - pub content: String, + pub id: String, + pub field_id: String, + pub field_type_rev: FieldTypeRevision, + pub groups: Vec, + // This content is serde in Json format + pub content: String, } impl GroupConfigurationRevision { - pub fn new(field_id: String, field_type: FieldTypeRevision, content: T) -> Result - where - T: GroupConfigurationContentSerde, - { - let content = content.to_json()?; - Ok(Self { - id: gen_grid_group_id(), - field_id, - field_type_rev: field_type, - groups: vec![], - content, - }) - } + pub fn new( + field_id: String, + field_type: FieldTypeRevision, + content: T, + ) -> Result + where + T: GroupConfigurationContentSerde, + { + let content = content.to_json()?; + Ok(Self { + id: gen_grid_group_id(), + field_id, + field_type_rev: field_type, + groups: vec![], + content, + }) + } } #[derive(Default, Serialize, Deserialize)] pub struct TextGroupConfigurationRevision { - pub hide_empty: bool, + pub hide_empty: bool, } impl GroupConfigurationContentSerde for TextGroupConfigurationRevision { - fn from_json(s: &str) -> Result { - serde_json::from_str(s) - } - fn to_json(&self) -> Result { - serde_json::to_string(self) - } + fn from_json(s: &str) -> Result { + serde_json::from_str(s) + } + fn to_json(&self) -> Result { + serde_json::to_string(self) + } } #[derive(Default, Serialize, Deserialize)] pub struct NumberGroupConfigurationRevision { - pub hide_empty: bool, + pub hide_empty: bool, } impl GroupConfigurationContentSerde for NumberGroupConfigurationRevision { - fn from_json(s: &str) -> Result { - serde_json::from_str(s) - } - fn to_json(&self) -> Result { - serde_json::to_string(self) - } + fn from_json(s: &str) -> Result { + serde_json::from_str(s) + } + fn to_json(&self) -> Result { + serde_json::to_string(self) + } } #[derive(Default, Serialize, Deserialize)] pub struct URLGroupConfigurationRevision { - pub hide_empty: bool, + pub hide_empty: bool, } impl GroupConfigurationContentSerde for URLGroupConfigurationRevision { - fn from_json(s: &str) -> Result { - serde_json::from_str(s) - } - fn to_json(&self) -> Result { - serde_json::to_string(self) - } + fn from_json(s: &str) -> Result { + serde_json::from_str(s) + } + fn to_json(&self) -> Result { + serde_json::to_string(self) + } } #[derive(Default, Serialize, Deserialize)] pub struct CheckboxGroupConfigurationRevision { - pub hide_empty: bool, + pub hide_empty: bool, } impl GroupConfigurationContentSerde for CheckboxGroupConfigurationRevision { - fn from_json(s: &str) -> Result { - serde_json::from_str(s) - } + fn from_json(s: &str) -> Result { + serde_json::from_str(s) + } - fn to_json(&self) -> Result { - serde_json::to_string(self) - } + fn to_json(&self) -> Result { + serde_json::to_string(self) + } } #[derive(Default, Serialize, Deserialize)] pub struct SelectOptionGroupConfigurationRevision { - pub hide_empty: bool, + pub hide_empty: bool, } impl GroupConfigurationContentSerde for SelectOptionGroupConfigurationRevision { - fn from_json(s: &str) -> Result { - serde_json::from_str(s) - } + fn from_json(s: &str) -> Result { + serde_json::from_str(s) + } - fn to_json(&self) -> Result { - serde_json::to_string(self) - } + fn to_json(&self) -> Result { + serde_json::to_string(self) + } } #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] pub struct GroupRevision { - pub id: String, + pub id: String, - #[serde(default)] - pub name: String, + #[serde(default)] + pub name: String, - #[serde(default = "GROUP_REV_VISIBILITY")] - pub visible: bool, + #[serde(default = "GROUP_REV_VISIBILITY")] + pub visible: bool, } const GROUP_REV_VISIBILITY: fn() -> bool = || true; impl GroupRevision { - /// Create a new GroupRevision - /// - /// # Arguments - /// - /// * `id`: identifier for this group revision. This id must be unique. - /// * `group_name`: the name of this group - /// - /// returns: GroupRevision - pub fn new(id: String, group_name: String) -> Self { - Self { - id, - name: group_name, - visible: true, - } + /// Create a new GroupRevision + /// + /// # Arguments + /// + /// * `id`: identifier for this group revision. This id must be unique. + /// * `group_name`: the name of this group + /// + /// returns: GroupRevision + pub fn new(id: String, group_name: String) -> Self { + Self { + id, + name: group_name, + visible: true, } + } - pub fn update_with_other(&mut self, other: &GroupRevision) { - self.visible = other.visible - } + pub fn update_with_other(&mut self, other: &GroupRevision) { + self.visible = other.visible + } } #[derive(Default, Serialize, Deserialize)] pub struct DateGroupConfigurationRevision { - pub hide_empty: bool, - pub condition: DateCondition, + pub hide_empty: bool, + pub condition: DateCondition, } impl GroupConfigurationContentSerde for DateGroupConfigurationRevision { - fn from_json(s: &str) -> Result { - serde_json::from_str(s) - } - fn to_json(&self) -> Result { - serde_json::to_string(self) - } + fn from_json(s: &str) -> Result { + serde_json::from_str(s) + } + fn to_json(&self) -> Result { + serde_json::to_string(self) + } } #[derive(Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum DateCondition { - Relative = 0, - Day = 1, - Week = 2, - Month = 3, - Year = 4, + Relative = 0, + Day = 1, + Week = 2, + Month = 3, + Year = 4, } impl std::default::Default for DateCondition { - fn default() -> Self { - DateCondition::Relative - } + fn default() -> Self { + DateCondition::Relative + } } #[cfg(test)] mod tests { - use crate::{GroupConfigurationRevision, SelectOptionGroupConfigurationRevision}; + use crate::{GroupConfigurationRevision, SelectOptionGroupConfigurationRevision}; - #[test] - fn group_configuration_serde_test() { - let content = SelectOptionGroupConfigurationRevision { hide_empty: false }; - let rev = GroupConfigurationRevision::new("1".to_owned(), 2, content).unwrap(); - let json = serde_json::to_string(&rev).unwrap(); + #[test] + fn group_configuration_serde_test() { + let content = SelectOptionGroupConfigurationRevision { hide_empty: false }; + let rev = GroupConfigurationRevision::new("1".to_owned(), 2, content).unwrap(); + let json = serde_json::to_string(&rev).unwrap(); - let rev: GroupConfigurationRevision = serde_json::from_str(&json).unwrap(); - let _content: SelectOptionGroupConfigurationRevision = serde_json::from_str(&rev.content).unwrap(); - } + let rev: GroupConfigurationRevision = serde_json::from_str(&json).unwrap(); + let _content: SelectOptionGroupConfigurationRevision = + serde_json::from_str(&rev.content).unwrap(); + } - #[test] - fn group_configuration_serde_test2() { - let content = SelectOptionGroupConfigurationRevision { hide_empty: false }; - let content_json = serde_json::to_string(&content).unwrap(); - let rev = GroupConfigurationRevision::new("1".to_owned(), 2, content).unwrap(); + #[test] + fn group_configuration_serde_test2() { + let content = SelectOptionGroupConfigurationRevision { hide_empty: false }; + let content_json = serde_json::to_string(&content).unwrap(); + let rev = GroupConfigurationRevision::new("1".to_owned(), 2, content).unwrap(); - assert_eq!(rev.content, content_json); - } + assert_eq!(rev.content, content_json); + } } diff --git a/shared-lib/grid-model/src/sort_rev.rs b/shared-lib/grid-model/src/sort_rev.rs index 81f6a2f82e..ce2f83e95d 100644 --- a/shared-lib/grid-model/src/sort_rev.rs +++ b/shared-lib/grid-model/src/sort_rev.rs @@ -4,37 +4,37 @@ use serde_repr::*; #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] pub struct SortRevision { - pub id: String, - pub field_id: String, - pub field_type: FieldTypeRevision, - pub condition: SortCondition, + pub id: String, + pub field_id: String, + pub field_type: FieldTypeRevision, + pub condition: SortCondition, } #[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Hash, Clone, Debug)] #[repr(u8)] pub enum SortCondition { - Ascending = 0, - Descending = 1, + Ascending = 0, + Descending = 1, } impl std::convert::From for SortCondition { - fn from(num: u8) -> Self { - match num { - 0 => SortCondition::Ascending, - 1 => SortCondition::Descending, - _ => SortCondition::Ascending, - } + fn from(num: u8) -> Self { + match num { + 0 => SortCondition::Ascending, + 1 => SortCondition::Descending, + _ => SortCondition::Ascending, } + } } impl std::default::Default for SortCondition { - fn default() -> Self { - Self::Ascending - } + fn default() -> Self { + Self::Ascending + } } impl std::convert::From for u8 { - fn from(condition: SortCondition) -> Self { - condition as u8 - } + fn from(condition: SortCondition) -> Self { + condition as u8 + } } diff --git a/shared-lib/lib-infra/src/future.rs b/shared-lib/lib-infra/src/future.rs index 484a95d968..ef3613f262 100644 --- a/shared-lib/lib-infra/src/future.rs +++ b/shared-lib/lib-infra/src/future.rs @@ -2,66 +2,66 @@ use futures_core::future::BoxFuture; use futures_core::ready; use pin_project::pin_project; use std::{ - fmt::Debug, - future::Future, - pin::Pin, - task::{Context, Poll}, + fmt::Debug, + future::Future, + pin::Pin, + task::{Context, Poll}, }; pub fn to_fut(f: T) -> Fut where - T: Future + Send + Sync + 'static, + T: Future + Send + Sync + 'static, { - Fut { fut: Box::pin(f) } + Fut { fut: Box::pin(f) } } #[pin_project] pub struct Fut { - #[pin] - pub fut: Pin + Sync + Send>>, + #[pin] + pub fut: Pin + Sync + Send>>, } impl Future for Fut where - T: Send + Sync, + T: Send + Sync, { - type Output = T; + type Output = T; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - Poll::Ready(ready!(this.fut.poll(cx))) - } + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.as_mut().project(); + Poll::Ready(ready!(this.fut.poll(cx))) + } } #[pin_project] pub struct FutureResult { - #[pin] - pub fut: Pin> + Sync + Send>>, + #[pin] + pub fut: Pin> + Sync + Send>>, } impl FutureResult { - pub fn new(f: F) -> Self - where - F: Future> + Send + Sync + 'static, - { - Self { - fut: Box::pin(async { f.await }), - } + pub fn new(f: F) -> Self + where + F: Future> + Send + Sync + 'static, + { + Self { + fut: Box::pin(async { f.await }), } + } } impl Future for FutureResult where - T: Send + Sync, - E: Debug, + T: Send + Sync, + E: Debug, { - type Output = Result; + type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - let result = ready!(this.fut.poll(cx)); - Poll::Ready(result) - } + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.as_mut().project(); + let result = ready!(this.fut.poll(cx)); + Poll::Ready(result) + } } pub type BoxResultFuture<'a, T, E> = BoxFuture<'a, Result>; diff --git a/shared-lib/lib-infra/src/ref_map.rs b/shared-lib/lib-infra/src/ref_map.rs index 74cf5f23b7..ae08a77042 100644 --- a/shared-lib/lib-infra/src/ref_map.rs +++ b/shared-lib/lib-infra/src/ref_map.rs @@ -4,82 +4,89 @@ use std::sync::Arc; #[async_trait] pub trait RefCountValue { - async fn did_remove(&self) {} + async fn did_remove(&self) {} } struct RefCountHandler { - ref_count: usize, - inner: T, + ref_count: usize, + inner: T, } impl RefCountHandler { - pub fn new(inner: T) -> Self { - Self { ref_count: 1, inner } + pub fn new(inner: T) -> Self { + Self { + ref_count: 1, + inner, } + } - pub fn increase_ref_count(&mut self) { - self.ref_count += 1; - } + pub fn increase_ref_count(&mut self) { + self.ref_count += 1; + } } pub struct RefCountHashMap(HashMap>); impl std::default::Default for RefCountHashMap { - fn default() -> Self { - Self(HashMap::new()) - } + fn default() -> Self { + Self(HashMap::new()) + } } impl RefCountHashMap where - T: Clone + Send + Sync + RefCountValue + 'static, + T: Clone + Send + Sync + RefCountValue + 'static, { - pub fn new() -> Self { - Self::default() + pub fn new() -> Self { + Self::default() + } + + pub fn get(&self, key: &str) -> Option { + self.0.get(key).map(|handler| handler.inner.clone()) + } + + pub fn values(&self) -> Vec { + self + .0 + .values() + .map(|value| value.inner.clone()) + .collect::>() + } + + pub fn insert(&mut self, key: String, value: T) { + if let Some(handler) = self.0.get_mut(&key) { + handler.increase_ref_count(); + } else { + let handler = RefCountHandler::new(value); + self.0.insert(key, handler); + } + } + + pub async fn remove(&mut self, key: &str) { + let mut should_remove = false; + if let Some(value) = self.0.get_mut(key) { + if value.ref_count > 0 { + value.ref_count -= 1; + } + should_remove = value.ref_count == 0; } - pub fn get(&self, key: &str) -> Option { - self.0.get(key).map(|handler| handler.inner.clone()) - } - - pub fn values(&self) -> Vec { - self.0.values().map(|value| value.inner.clone()).collect::>() - } - - pub fn insert(&mut self, key: String, value: T) { - if let Some(handler) = self.0.get_mut(&key) { - handler.increase_ref_count(); - } else { - let handler = RefCountHandler::new(value); - self.0.insert(key, handler); - } - } - - pub async fn remove(&mut self, key: &str) { - let mut should_remove = false; - if let Some(value) = self.0.get_mut(key) { - if value.ref_count > 0 { - value.ref_count -= 1; - } - should_remove = value.ref_count == 0; - } - - if should_remove { - if let Some(handler) = self.0.remove(key) { - tokio::spawn(async move { - handler.inner.did_remove().await; - }); - } - } + if should_remove { + if let Some(handler) = self.0.remove(key) { + tokio::spawn(async move { + handler.inner.did_remove().await; + }); + } } + } } #[async_trait] impl RefCountValue for Arc where - T: RefCountValue + Sync + Send, + T: RefCountValue + Sync + Send, { - async fn did_remove(&self) { - (**self).did_remove().await - } + async fn did_remove(&self) { + (**self).did_remove().await + } } diff --git a/shared-lib/lib-infra/src/retry/future.rs b/shared-lib/lib-infra/src/retry/future.rs index d605e512de..670b02a982 100644 --- a/shared-lib/lib-infra/src/retry/future.rs +++ b/shared-lib/lib-infra/src/retry/future.rs @@ -3,73 +3,76 @@ use crate::retry::FixedInterval; use pin_project::pin_project; use std::{ - future::Future, - iter::{IntoIterator, Iterator}, - pin::Pin, - task::{Context, Poll}, + future::Future, + iter::{IntoIterator, Iterator}, + pin::Pin, + task::{Context, Poll}, }; use tokio::time::{sleep_until, Duration, Instant, Sleep}; #[pin_project(project = RetryStateProj)] enum RetryState where - A: Action, + A: Action, { - Running(#[pin] A::Future), - Sleeping(#[pin] Sleep), + Running(#[pin] A::Future), + Sleeping(#[pin] Sleep), } impl RetryState { - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> RetryFuturePoll { - match self.project() { - RetryStateProj::Running(future) => RetryFuturePoll::Running(future.poll(cx)), - RetryStateProj::Sleeping(future) => RetryFuturePoll::Sleeping(future.poll(cx)), - } + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> RetryFuturePoll { + match self.project() { + RetryStateProj::Running(future) => RetryFuturePoll::Running(future.poll(cx)), + RetryStateProj::Sleeping(future) => RetryFuturePoll::Sleeping(future.poll(cx)), } + } } enum RetryFuturePoll where - A: Action, + A: Action, { - Running(Poll>), - Sleeping(Poll<()>), + Running(Poll>), + Sleeping(Poll<()>), } /// Future that drives multiple attempts at an action via a retry strategy. #[pin_project] pub struct Retry where - I: Iterator, - A: Action, + I: Iterator, + A: Action, { - #[pin] - retry_if: RetryIf bool>, + #[pin] + retry_if: RetryIf bool>, } impl Retry where - I: Iterator, - A: Action, + I: Iterator, + A: Action, { - pub fn new>(strategy: T, action: A) -> Retry { - Retry { - retry_if: RetryIf::spawn(strategy, action, (|_| true) as fn(&A::Error) -> bool), - } + pub fn new>( + strategy: T, + action: A, + ) -> Retry { + Retry { + retry_if: RetryIf::spawn(strategy, action, (|_| true) as fn(&A::Error) -> bool), } + } } impl Future for Retry where - I: Iterator, - A: Action, + I: Iterator, + A: Action, { - type Output = Result; + type Output = Result; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let this = self.project(); - this.retry_if.poll(cx) - } + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let this = self.project(); + this.retry_if.poll(cx) + } } /// Future that drives multiple attempts at an action via a retry strategy. @@ -78,101 +81,109 @@ where #[pin_project] pub struct RetryIf where - I: Iterator, - A: Action, - C: Condition, + I: Iterator, + A: Action, + C: Condition, { - strategy: I, - #[pin] - state: RetryState, - action: A, - condition: C, + strategy: I, + #[pin] + state: RetryState, + action: A, + condition: C, } impl RetryIf where - I: Iterator, - A: Action, - C: Condition, + I: Iterator, + A: Action, + C: Condition, { - pub fn spawn>( - strategy: T, - mut action: A, - condition: C, - ) -> RetryIf { - RetryIf { - strategy: strategy.into_iter(), - state: RetryState::Running(action.run()), - action, - condition, - } + pub fn spawn>( + strategy: T, + mut action: A, + condition: C, + ) -> RetryIf { + RetryIf { + strategy: strategy.into_iter(), + state: RetryState::Running(action.run()), + action, + condition, } + } - fn attempt(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - let future = { - let this = self.as_mut().project(); - this.action.run() - }; - self.as_mut().project().state.set(RetryState::Running(future)); - self.poll(cx) - } + fn attempt(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let future = { + let this = self.as_mut().project(); + this.action.run() + }; + self + .as_mut() + .project() + .state + .set(RetryState::Running(future)); + self.poll(cx) + } - fn retry( - mut self: Pin<&mut Self>, - err: A::Error, - cx: &mut Context, - ) -> Result>, A::Error> { - match self.as_mut().project().strategy.next() { - None => Err(err), - Some(duration) => { - let deadline = Instant::now() + duration; - let future = sleep_until(deadline); - self.as_mut().project().state.set(RetryState::Sleeping(future)); - Ok(self.poll(cx)) - } - } + fn retry( + mut self: Pin<&mut Self>, + err: A::Error, + cx: &mut Context, + ) -> Result>, A::Error> { + match self.as_mut().project().strategy.next() { + None => Err(err), + Some(duration) => { + let deadline = Instant::now() + duration; + let future = sleep_until(deadline); + self + .as_mut() + .project() + .state + .set(RetryState::Sleeping(future)); + Ok(self.poll(cx)) + }, } + } } impl Future for RetryIf where - I: Iterator, - A: Action, - C: Condition, + I: Iterator, + A: Action, + C: Condition, { - type Output = Result; + type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { - match self.as_mut().project().state.poll(cx) { - RetryFuturePoll::Running(poll_result) => match poll_result { - Poll::Ready(Ok(ok)) => Poll::Ready(Ok(ok)), - Poll::Pending => Poll::Pending, - Poll::Ready(Err(err)) => { - if self.as_mut().project().condition.should_retry(&err) { - match self.retry(err, cx) { - Ok(poll) => poll, - Err(err) => Poll::Ready(Err(err)), - } - } else { - Poll::Ready(Err(err)) - } - } - }, - RetryFuturePoll::Sleeping(poll_result) => match poll_result { - Poll::Pending => Poll::Pending, - Poll::Ready(_) => self.attempt(cx), - }, - } + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + match self.as_mut().project().state.poll(cx) { + RetryFuturePoll::Running(poll_result) => match poll_result { + Poll::Ready(Ok(ok)) => Poll::Ready(Ok(ok)), + Poll::Pending => Poll::Pending, + Poll::Ready(Err(err)) => { + if self.as_mut().project().condition.should_retry(&err) { + match self.retry(err, cx) { + Ok(poll) => poll, + Err(err) => Poll::Ready(Err(err)), + } + } else { + Poll::Ready(Err(err)) + } + }, + }, + RetryFuturePoll::Sleeping(poll_result) => match poll_result { + Poll::Pending => Poll::Pending, + Poll::Ready(_) => self.attempt(cx), + }, } + } } /// An action can be run multiple times and produces a future. pub trait Action: Send + Sync { - type Future: Future>; - type Item; - type Error; + type Future: Future>; + type Item; + type Error; - fn run(&mut self) -> Self::Future; + fn run(&mut self) -> Self::Future; } // impl>, F: FnMut() -> T + Send + Sync> // Action for F { type Future = T; @@ -183,25 +194,25 @@ pub trait Action: Send + Sync { // } pub trait Condition { - fn should_retry(&mut self, error: &E) -> bool; + fn should_retry(&mut self, error: &E) -> bool; } impl bool> Condition for F { - fn should_retry(&mut self, error: &E) -> bool { - self(error) - } + fn should_retry(&mut self, error: &E) -> bool { + self(error) + } } pub fn spawn_retry( - retry_count: usize, - retry_per_millis: u64, - action: A, + retry_count: usize, + retry_per_millis: u64, + action: A, ) -> impl Future> where - A::Item: Send + Sync, - A::Error: Send + Sync, - ::Future: Send + Sync, + A::Item: Send + Sync, + A::Error: Send + Sync, + ::Future: Send + Sync, { - let strategy = FixedInterval::from_millis(retry_per_millis).take(retry_count); - Retry::new(strategy, action) + let strategy = FixedInterval::from_millis(retry_per_millis).take(retry_count); + Retry::new(strategy, action) } diff --git a/shared-lib/lib-infra/src/retry/strategy/exponential_backoff.rs b/shared-lib/lib-infra/src/retry/strategy/exponential_backoff.rs index 7eb996d469..37796353d5 100644 --- a/shared-lib/lib-infra/src/retry/strategy/exponential_backoff.rs +++ b/shared-lib/lib-infra/src/retry/strategy/exponential_backoff.rs @@ -4,124 +4,124 @@ use std::{iter::Iterator, time::Duration}; /// The power corresponds to the number of past attempts. #[derive(Debug, Clone)] pub struct ExponentialBackoff { - current: u64, - base: u64, - factor: u64, - max_delay: Option, + current: u64, + base: u64, + factor: u64, + max_delay: Option, } impl ExponentialBackoff { - /// Constructs a new exponential back-off strategy, - /// given a base duration in milliseconds. - /// - /// The resulting duration is calculated by taking the base to the `n`-th - /// power, where `n` denotes the number of past attempts. - pub fn from_millis(base: u64) -> ExponentialBackoff { - ExponentialBackoff { - current: base, - base, - factor: 1u64, - max_delay: None, - } + /// Constructs a new exponential back-off strategy, + /// given a base duration in milliseconds. + /// + /// The resulting duration is calculated by taking the base to the `n`-th + /// power, where `n` denotes the number of past attempts. + pub fn from_millis(base: u64) -> ExponentialBackoff { + ExponentialBackoff { + current: base, + base, + factor: 1u64, + max_delay: None, } + } - /// A multiplicative factor that will be applied to the retry delay. - /// - /// For example, using a factor of `1000` will make each delay in units of - /// seconds. - /// - /// Default factor is `1`. - pub fn factor(mut self, factor: u64) -> ExponentialBackoff { - self.factor = factor; - self - } + /// A multiplicative factor that will be applied to the retry delay. + /// + /// For example, using a factor of `1000` will make each delay in units of + /// seconds. + /// + /// Default factor is `1`. + pub fn factor(mut self, factor: u64) -> ExponentialBackoff { + self.factor = factor; + self + } - /// Apply a maximum delay. No retry delay will be longer than this - /// `Duration`. - pub fn max_delay(mut self, duration: Duration) -> ExponentialBackoff { - self.max_delay = Some(duration); - self - } + /// Apply a maximum delay. No retry delay will be longer than this + /// `Duration`. + pub fn max_delay(mut self, duration: Duration) -> ExponentialBackoff { + self.max_delay = Some(duration); + self + } } impl Iterator for ExponentialBackoff { - type Item = Duration; + type Item = Duration; - fn next(&mut self) -> Option { - // set delay duration by applying factor - let duration = if let Some(duration) = self.current.checked_mul(self.factor) { - Duration::from_millis(duration) - } else { - Duration::from_millis(u64::MAX) - }; + fn next(&mut self) -> Option { + // set delay duration by applying factor + let duration = if let Some(duration) = self.current.checked_mul(self.factor) { + Duration::from_millis(duration) + } else { + Duration::from_millis(u64::MAX) + }; - // check if we reached max delay - if let Some(ref max_delay) = self.max_delay { - if duration > *max_delay { - return Some(*max_delay); - } - } - - if let Some(next) = self.current.checked_mul(self.base) { - self.current = next; - } else { - self.current = u64::MAX; - } - - Some(duration) + // check if we reached max delay + if let Some(ref max_delay) = self.max_delay { + if duration > *max_delay { + return Some(*max_delay); + } } + + if let Some(next) = self.current.checked_mul(self.base) { + self.current = next; + } else { + self.current = u64::MAX; + } + + Some(duration) + } } #[test] fn returns_some_exponential_base_10() { - let mut s = ExponentialBackoff::from_millis(10); + let mut s = ExponentialBackoff::from_millis(10); - assert_eq!(s.next(), Some(Duration::from_millis(10))); - assert_eq!(s.next(), Some(Duration::from_millis(100))); - assert_eq!(s.next(), Some(Duration::from_millis(1000))); + assert_eq!(s.next(), Some(Duration::from_millis(10))); + assert_eq!(s.next(), Some(Duration::from_millis(100))); + assert_eq!(s.next(), Some(Duration::from_millis(1000))); } #[test] fn returns_some_exponential_base_2() { - let mut s = ExponentialBackoff::from_millis(2); + let mut s = ExponentialBackoff::from_millis(2); - assert_eq!(s.next(), Some(Duration::from_millis(2))); - assert_eq!(s.next(), Some(Duration::from_millis(4))); - assert_eq!(s.next(), Some(Duration::from_millis(8))); + assert_eq!(s.next(), Some(Duration::from_millis(2))); + assert_eq!(s.next(), Some(Duration::from_millis(4))); + assert_eq!(s.next(), Some(Duration::from_millis(8))); } #[test] fn saturates_at_maximum_value() { - let mut s = ExponentialBackoff::from_millis(u64::MAX - 1); + let mut s = ExponentialBackoff::from_millis(u64::MAX - 1); - assert_eq!(s.next(), Some(Duration::from_millis(u64::MAX - 1))); - assert_eq!(s.next(), Some(Duration::from_millis(u64::MAX))); - assert_eq!(s.next(), Some(Duration::from_millis(u64::MAX))); + assert_eq!(s.next(), Some(Duration::from_millis(u64::MAX - 1))); + assert_eq!(s.next(), Some(Duration::from_millis(u64::MAX))); + assert_eq!(s.next(), Some(Duration::from_millis(u64::MAX))); } #[test] fn can_use_factor_to_get_seconds() { - let factor = 1000; - let mut s = ExponentialBackoff::from_millis(2).factor(factor); + let factor = 1000; + let mut s = ExponentialBackoff::from_millis(2).factor(factor); - assert_eq!(s.next(), Some(Duration::from_secs(2))); - assert_eq!(s.next(), Some(Duration::from_secs(4))); - assert_eq!(s.next(), Some(Duration::from_secs(8))); + assert_eq!(s.next(), Some(Duration::from_secs(2))); + assert_eq!(s.next(), Some(Duration::from_secs(4))); + assert_eq!(s.next(), Some(Duration::from_secs(8))); } #[test] fn stops_increasing_at_max_delay() { - let mut s = ExponentialBackoff::from_millis(2).max_delay(Duration::from_millis(4)); + let mut s = ExponentialBackoff::from_millis(2).max_delay(Duration::from_millis(4)); - assert_eq!(s.next(), Some(Duration::from_millis(2))); - assert_eq!(s.next(), Some(Duration::from_millis(4))); - assert_eq!(s.next(), Some(Duration::from_millis(4))); + assert_eq!(s.next(), Some(Duration::from_millis(2))); + assert_eq!(s.next(), Some(Duration::from_millis(4))); + assert_eq!(s.next(), Some(Duration::from_millis(4))); } #[test] fn returns_max_when_max_less_than_base() { - let mut s = ExponentialBackoff::from_millis(20).max_delay(Duration::from_millis(10)); + let mut s = ExponentialBackoff::from_millis(20).max_delay(Duration::from_millis(10)); - assert_eq!(s.next(), Some(Duration::from_millis(10))); - assert_eq!(s.next(), Some(Duration::from_millis(10))); + assert_eq!(s.next(), Some(Duration::from_millis(10))); + assert_eq!(s.next(), Some(Duration::from_millis(10))); } diff --git a/shared-lib/lib-infra/src/retry/strategy/fixed_interval.rs b/shared-lib/lib-infra/src/retry/strategy/fixed_interval.rs index a45b749b37..b2340deeb2 100644 --- a/shared-lib/lib-infra/src/retry/strategy/fixed_interval.rs +++ b/shared-lib/lib-infra/src/retry/strategy/fixed_interval.rs @@ -3,37 +3,37 @@ use std::{iter::Iterator, time::Duration}; /// A retry strategy driven by a fixed interval. #[derive(Debug, Clone)] pub struct FixedInterval { - duration: Duration, + duration: Duration, } impl FixedInterval { - /// Constructs a new fixed interval strategy. - pub fn new(duration: Duration) -> FixedInterval { - FixedInterval { duration } - } + /// Constructs a new fixed interval strategy. + pub fn new(duration: Duration) -> FixedInterval { + FixedInterval { duration } + } - /// Constructs a new fixed interval strategy, - /// given a duration in milliseconds. - pub fn from_millis(millis: u64) -> FixedInterval { - FixedInterval { - duration: Duration::from_millis(millis), - } + /// Constructs a new fixed interval strategy, + /// given a duration in milliseconds. + pub fn from_millis(millis: u64) -> FixedInterval { + FixedInterval { + duration: Duration::from_millis(millis), } + } } impl Iterator for FixedInterval { - type Item = Duration; + type Item = Duration; - fn next(&mut self) -> Option { - Some(self.duration) - } + fn next(&mut self) -> Option { + Some(self.duration) + } } #[test] fn returns_some_fixed() { - let mut s = FixedInterval::new(Duration::from_millis(123)); + let mut s = FixedInterval::new(Duration::from_millis(123)); - assert_eq!(s.next(), Some(Duration::from_millis(123))); - assert_eq!(s.next(), Some(Duration::from_millis(123))); - assert_eq!(s.next(), Some(Duration::from_millis(123))); + assert_eq!(s.next(), Some(Duration::from_millis(123))); + assert_eq!(s.next(), Some(Duration::from_millis(123))); + assert_eq!(s.next(), Some(Duration::from_millis(123))); } diff --git a/shared-lib/lib-infra/src/retry/strategy/jitter.rs b/shared-lib/lib-infra/src/retry/strategy/jitter.rs index c11ae8ed87..179bd4372e 100644 --- a/shared-lib/lib-infra/src/retry/strategy/jitter.rs +++ b/shared-lib/lib-infra/src/retry/strategy/jitter.rs @@ -1,5 +1,5 @@ use std::time::Duration; pub fn jitter(duration: Duration) -> Duration { - duration.mul_f64(rand::random::()) + duration.mul_f64(rand::random::()) } diff --git a/shared-lib/lib-infra/src/util.rs b/shared-lib/lib-infra/src/util.rs index 09ca5e1890..cb6cf8a7d6 100644 --- a/shared-lib/lib-infra/src/util.rs +++ b/shared-lib/lib-infra/src/util.rs @@ -1,33 +1,38 @@ -pub fn move_vec_element(vec: &mut Vec, filter: F, _from_index: usize, to_index: usize) -> Result +pub fn move_vec_element( + vec: &mut Vec, + filter: F, + _from_index: usize, + to_index: usize, +) -> Result where - F: FnMut(&T) -> bool, + F: FnMut(&T) -> bool, { - match vec.iter().position(filter) { - None => Ok(false), - Some(index) => { - if vec.len() > to_index { - let removed_element = vec.remove(index); - vec.insert(to_index, removed_element); - Ok(true) - } else { - let msg = format!( - "Move element to invalid index: {}, current len: {}", - to_index, - vec.len() - ); - Err(msg) - } - } - } + match vec.iter().position(filter) { + None => Ok(false), + Some(index) => { + if vec.len() > to_index { + let removed_element = vec.remove(index); + vec.insert(to_index, removed_element); + Ok(true) + } else { + let msg = format!( + "Move element to invalid index: {}, current len: {}", + to_index, + vec.len() + ); + Err(msg) + } + }, + } } #[allow(dead_code)] pub fn timestamp() -> i64 { - chrono::Utc::now().timestamp() + chrono::Utc::now().timestamp() } #[inline] pub fn md5>(data: T) -> String { - let md5 = format!("{:x}", md5::compute(data)); - md5 + let md5 = format!("{:x}", md5::compute(data)); + md5 } diff --git a/shared-lib/lib-ot/src/core/attributes/attribute.rs b/shared-lib/lib-ot/src/core/attributes/attribute.rs index ed91a793af..95aae6a8f3 100644 --- a/shared-lib/lib-ot/src/core/attributes/attribute.rs +++ b/shared-lib/lib-ot/src/core/attributes/attribute.rs @@ -7,332 +7,339 @@ use std::fmt::Display; #[derive(Debug, Clone)] pub struct AttributeEntry { - pub key: AttributeKey, - pub value: AttributeValue, + pub key: AttributeKey, + pub value: AttributeValue, } impl AttributeEntry { - pub fn new, V: Into>(key: K, value: V) -> Self { - Self { - key: key.into(), - value: value.into(), - } + pub fn new, V: Into>(key: K, value: V) -> Self { + Self { + key: key.into(), + value: value.into(), } + } - pub fn clear(&mut self) { - self.value.ty = None; - self.value.value = None; - } + pub fn clear(&mut self) { + self.value.ty = None; + self.value.value = None; + } } impl std::convert::From for AttributeHashMap { - fn from(entry: AttributeEntry) -> Self { - let mut attributes = AttributeHashMap::new(); - attributes.insert_entry(entry); - attributes - } + fn from(entry: AttributeEntry) -> Self { + let mut attributes = AttributeHashMap::new(); + attributes.insert_entry(entry); + attributes + } } #[derive(Default, Clone, Serialize, Deserialize, Eq, PartialEq, Debug)] pub struct AttributeHashMap(IndexMap); impl std::ops::Deref for AttributeHashMap { - type Target = IndexMap; + type Target = IndexMap; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } impl std::ops::DerefMut for AttributeHashMap { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } } impl AttributeHashMap { - pub fn new() -> AttributeHashMap { - AttributeHashMap(IndexMap::new()) - } + pub fn new() -> AttributeHashMap { + AttributeHashMap(IndexMap::new()) + } - pub fn into_inner(self) -> IndexMap { - self.0 - } + pub fn into_inner(self) -> IndexMap { + self.0 + } - // pub fn from_value(attribute_map: HashMap) -> Self { - // Self(attribute_map) + // pub fn from_value(attribute_map: HashMap) -> Self { + // Self(attribute_map) + // } + + pub fn insert>(&mut self, key: K, value: V) { + self.0.insert(key.to_string(), value.into()); + } + + pub fn insert_entry(&mut self, entry: AttributeEntry) { + self.insert(entry.key, entry.value) + } + + /// Set the key's value to None + pub fn remove_value>(&mut self, key: K) { + // if let Some(mut_value) = self.0.get_mut(key.as_ref()) { + // mut_value.value = None; // } + self.insert(key.as_ref().to_string(), AttributeValue::none()); + } - pub fn insert>(&mut self, key: K, value: V) { - self.0.insert(key.to_string(), value.into()); + /// Set all key's value to None + pub fn remove_all_value(&mut self) { + self.0.iter_mut().for_each(|(_, v)| { + *v = AttributeValue::none(); + }) + } + + pub fn retain_values(&mut self, retain_keys: &[&str]) { + self.0.iter_mut().for_each(|(k, v)| { + if !retain_keys.contains(&k.as_str()) { + *v = AttributeValue::none(); + } + }) + } + + pub fn remove_key>(&mut self, key: K) { + self.0.remove(key.as_ref()); + } + + /// Create a new key/value map by constructing new attributes from the other + /// if it's not None and replace the key/value with self key/value. + pub fn merge(&mut self, other: Option) { + if other.is_none() { + return; } - pub fn insert_entry(&mut self, entry: AttributeEntry) { - self.insert(entry.key, entry.value) - } + let mut new_attributes = other.unwrap().0; + self.0.iter().for_each(|(k, v)| { + new_attributes.insert(k.clone(), v.clone()); + }); + self.0 = new_attributes; + } - /// Set the key's value to None - pub fn remove_value>(&mut self, key: K) { - // if let Some(mut_value) = self.0.get_mut(key.as_ref()) { - // mut_value.value = None; - // } - self.insert(key.as_ref().to_string(), AttributeValue::none()); - } + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } - /// Set all key's value to None - pub fn remove_all_value(&mut self) { - self.0.iter_mut().for_each(|(_, v)| { - *v = AttributeValue::none(); - }) - } - - pub fn retain_values(&mut self, retain_keys: &[&str]) { - self.0.iter_mut().for_each(|(k, v)| { - if !retain_keys.contains(&k.as_str()) { - *v = AttributeValue::none(); - } - }) - } - - pub fn remove_key>(&mut self, key: K) { - self.0.remove(key.as_ref()); - } - - /// Create a new key/value map by constructing new attributes from the other - /// if it's not None and replace the key/value with self key/value. - pub fn merge(&mut self, other: Option) { - if other.is_none() { - return; - } - - let mut new_attributes = other.unwrap().0; - self.0.iter().for_each(|(k, v)| { - new_attributes.insert(k.clone(), v.clone()); - }); - self.0 = new_attributes; - } - - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - pub fn to_json(&self) -> Result { - serde_json::to_string(self).map_err(|err| OTError::serde().context(err)) - } + pub fn to_json(&self) -> Result { + serde_json::to_string(self).map_err(|err| OTError::serde().context(err)) + } } impl Display for AttributeHashMap { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for (key, value) in self.0.iter() { - f.write_str(&format!("{:?}:{:?}", key, value))?; - } - Ok(()) + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (key, value) in self.0.iter() { + f.write_str(&format!("{:?}:{:?}", key, value))?; } + Ok(()) + } } impl OperationAttributes for AttributeHashMap { - fn is_empty(&self) -> bool { - self.is_empty() - } + fn is_empty(&self) -> bool { + self.is_empty() + } - fn remove(&mut self) { - self.retain(|_, v| v.value.is_some()); - } + fn remove(&mut self) { + self.retain(|_, v| v.value.is_some()); + } - fn extend(&mut self, other: Self) { - self.0.extend(other.0); - } + fn extend(&mut self, other: Self) { + self.0.extend(other.0); + } } impl OperationTransform for AttributeHashMap { - fn compose(&self, other: &Self) -> Result - where - Self: Sized, - { - let mut attributes = self.clone(); - attributes.0.extend(other.clone().0); - Ok(attributes) - } + fn compose(&self, other: &Self) -> Result + where + Self: Sized, + { + let mut attributes = self.clone(); + attributes.0.extend(other.clone().0); + Ok(attributes) + } - fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> - where - Self: Sized, - { - let a = self.iter().fold(AttributeHashMap::new(), |mut new_attributes, (k, v)| { - if !other.contains_key(k) { - new_attributes.insert(k.clone(), v.clone()); - } - new_attributes - }); + fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> + where + Self: Sized, + { + let a = self + .iter() + .fold(AttributeHashMap::new(), |mut new_attributes, (k, v)| { + if !other.contains_key(k) { + new_attributes.insert(k.clone(), v.clone()); + } + new_attributes + }); - let b = other - .iter() - .fold(AttributeHashMap::new(), |mut new_attributes, (k, v)| { - if !self.contains_key(k) { - new_attributes.insert(k.clone(), v.clone()); - } - new_attributes - }); + let b = other + .iter() + .fold(AttributeHashMap::new(), |mut new_attributes, (k, v)| { + if !self.contains_key(k) { + new_attributes.insert(k.clone(), v.clone()); + } + new_attributes + }); - Ok((a, b)) - } + Ok((a, b)) + } - fn invert(&self, other: &Self) -> Self { - let base_inverted = other.iter().fold(AttributeHashMap::new(), |mut attributes, (k, v)| { - if other.get(k) != self.get(k) && self.contains_key(k) { - attributes.insert(k.clone(), v.clone()); - } - attributes - }); + fn invert(&self, other: &Self) -> Self { + let base_inverted = other + .iter() + .fold(AttributeHashMap::new(), |mut attributes, (k, v)| { + if other.get(k) != self.get(k) && self.contains_key(k) { + attributes.insert(k.clone(), v.clone()); + } + attributes + }); - self.iter().fold(base_inverted, |mut attributes, (k, _)| { - if other.get(k) != self.get(k) && !other.contains_key(k) { - attributes.remove_value(k); - } - attributes - }) - } + self.iter().fold(base_inverted, |mut attributes, (k, _)| { + if other.get(k) != self.get(k) && !other.contains_key(k) { + attributes.remove_value(k); + } + attributes + }) + } } pub type AttributeKey = String; #[derive(Eq, PartialEq, Hash, Debug, Clone)] pub enum ValueType { - IntType = 0, - FloatType = 1, - StrType = 2, - BoolType = 3, + IntType = 0, + FloatType = 1, + StrType = 2, + BoolType = 3, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct AttributeValue { - pub ty: Option, - pub value: Option, + pub ty: Option, + pub value: Option, } impl AttributeValue { - pub fn none() -> Self { - Self { ty: None, value: None } + pub fn none() -> Self { + Self { + ty: None, + value: None, } + } - pub fn from_int(val: i64) -> Self { - Self { - ty: Some(ValueType::IntType), - value: Some(val.to_string()), - } + pub fn from_int(val: i64) -> Self { + Self { + ty: Some(ValueType::IntType), + value: Some(val.to_string()), } + } - pub fn from_float(val: f64) -> Self { - Self { - ty: Some(ValueType::FloatType), - value: Some(val.to_string()), - } + pub fn from_float(val: f64) -> Self { + Self { + ty: Some(ValueType::FloatType), + value: Some(val.to_string()), } + } - pub fn from_bool(val: bool) -> Self { - // let value = if val { Some(val.to_string()) } else { None }; - Self { - ty: Some(ValueType::BoolType), - value: Some(val.to_string()), - } + pub fn from_bool(val: bool) -> Self { + // let value = if val { Some(val.to_string()) } else { None }; + Self { + ty: Some(ValueType::BoolType), + value: Some(val.to_string()), } + } - pub fn from_string(s: T) -> Self { - let value = Some(s.to_string()); - Self { - ty: Some(ValueType::StrType), - value, - } + pub fn from_string(s: T) -> Self { + let value = Some(s.to_string()); + Self { + ty: Some(ValueType::StrType), + value, } + } - pub fn int_value(&self) -> Option { - let value = self.value.as_ref()?; - Some(value.parse::().unwrap_or(0)) - } + pub fn int_value(&self) -> Option { + let value = self.value.as_ref()?; + Some(value.parse::().unwrap_or(0)) + } - pub fn bool_value(&self) -> Option { - let value = self.value.as_ref()?; - Some(value.parse::().unwrap_or(false)) - } + pub fn bool_value(&self) -> Option { + let value = self.value.as_ref()?; + Some(value.parse::().unwrap_or(false)) + } - pub fn str_value(&self) -> Option { - self.value.clone() - } + pub fn str_value(&self) -> Option { + self.value.clone() + } - pub fn float_value(&self) -> Option { - let value = self.value.as_ref()?; - Some(value.parse::().unwrap_or(0.0)) - } + pub fn float_value(&self) -> Option { + let value = self.value.as_ref()?; + Some(value.parse::().unwrap_or(0.0)) + } } impl std::convert::From for AttributeValue { - fn from(value: bool) -> Self { - AttributeValue::from_bool(value) - } + fn from(value: bool) -> Self { + AttributeValue::from_bool(value) + } } impl std::convert::From for AttributeValue { - fn from(value: usize) -> Self { - AttributeValue::from_int(value as i64) - } + fn from(value: usize) -> Self { + AttributeValue::from_int(value as i64) + } } impl std::convert::From<&str> for AttributeValue { - fn from(value: &str) -> Self { - AttributeValue::from_string(value) - } + fn from(value: &str) -> Self { + AttributeValue::from_string(value) + } } impl std::convert::From for AttributeValue { - fn from(value: String) -> Self { - AttributeValue::from_string(value) - } + fn from(value: String) -> Self { + AttributeValue::from_string(value) + } } impl std::convert::From for AttributeValue { - fn from(value: f64) -> Self { - AttributeValue::from_float(value) - } + fn from(value: f64) -> Self { + AttributeValue::from_float(value) + } } impl std::convert::From for AttributeValue { - fn from(value: i64) -> Self { - AttributeValue::from_int(value) - } + fn from(value: i64) -> Self { + AttributeValue::from_int(value) + } } impl std::convert::From for AttributeValue { - fn from(value: i32) -> Self { - AttributeValue::from_int(value as i64) - } + fn from(value: i32) -> Self { + AttributeValue::from_int(value as i64) + } } #[derive(Default)] pub struct AttributeBuilder { - attributes: AttributeHashMap, + attributes: AttributeHashMap, } impl AttributeBuilder { - pub fn new() -> Self { - Self::default() - } + pub fn new() -> Self { + Self::default() + } - pub fn insert>(mut self, key: K, value: V) -> Self { - self.attributes.insert(key, value); - self - } + pub fn insert>(mut self, key: K, value: V) -> Self { + self.attributes.insert(key, value); + self + } - pub fn insert_entry(mut self, entry: AttributeEntry) -> Self { - self.attributes.insert_entry(entry); - self - } + pub fn insert_entry(mut self, entry: AttributeEntry) -> Self { + self.attributes.insert_entry(entry); + self + } - pub fn delete>(mut self, key: K) -> Self { - self.attributes.remove_value(key); - self - } + pub fn delete>(mut self, key: K) -> Self { + self.attributes.remove_value(key); + self + } - pub fn build(self) -> AttributeHashMap { - self.attributes - } + pub fn build(self) -> AttributeHashMap { + self.attributes + } } diff --git a/shared-lib/lib-ot/src/core/attributes/attribute_serde.rs b/shared-lib/lib-ot/src/core/attributes/attribute_serde.rs index 80fd745343..a4ddf24aee 100644 --- a/shared-lib/lib-ot/src/core/attributes/attribute_serde.rs +++ b/shared-lib/lib-ot/src/core/attributes/attribute_serde.rs @@ -1,174 +1,174 @@ -use crate::core::attributes::AttributeValue; - -use serde::{ - de::{self, MapAccess, Visitor}, - Deserialize, Deserializer, Serialize, Serializer, -}; -use std::fmt; - -impl Serialize for AttributeValue { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match &self.ty { - None => serializer.serialize_none(), - Some(ty) => match ty { - super::ValueType::IntType => { - if let Some(value) = self.int_value() { - serializer.serialize_i64(value) - } else { - serializer.serialize_none() - } - } - super::ValueType::FloatType => { - if let Some(value) = self.float_value() { - serializer.serialize_f64(value) - } else { - serializer.serialize_none() - } - } - super::ValueType::StrType => { - if let Some(value) = self.str_value() { - serializer.serialize_str(&value) - } else { - serializer.serialize_none() - } - } - super::ValueType::BoolType => { - if let Some(value) = self.bool_value() { - serializer.serialize_bool(value) - } else { - serializer.serialize_none() - } - } - }, - } - } -} - -impl<'de> Deserialize<'de> for AttributeValue { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct AttributeValueVisitor; - impl<'de> Visitor<'de> for AttributeValueVisitor { - type Value = AttributeValue; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("bool, usize or string") - } - - fn visit_bool(self, value: bool) -> Result - where - E: de::Error, - { - Ok(AttributeValue::from_bool(value)) - } - - fn visit_i8(self, value: i8) -> Result - where - E: de::Error, - { - Ok(AttributeValue::from_int(value as i64)) - } - - fn visit_i16(self, value: i16) -> Result - where - E: de::Error, - { - Ok(AttributeValue::from_int(value as i64)) - } - - fn visit_i32(self, value: i32) -> Result - where - E: de::Error, - { - Ok(AttributeValue::from_int(value as i64)) - } - - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - Ok(AttributeValue::from_int(value as i64)) - } - - fn visit_u8(self, value: u8) -> Result - where - E: de::Error, - { - Ok(AttributeValue::from_int(value as i64)) - } - - fn visit_u16(self, value: u16) -> Result - where - E: de::Error, - { - Ok(AttributeValue::from_int(value as i64)) - } - - fn visit_u32(self, value: u32) -> Result - where - E: de::Error, - { - Ok(AttributeValue::from_int(value as i64)) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - Ok(AttributeValue::from_int(value as i64)) - } - - fn visit_f32(self, value: f32) -> Result - where - E: de::Error, - { - Ok(AttributeValue::from_float(value as f64)) - } - - fn visit_f64(self, value: f64) -> Result - where - E: de::Error, - { - Ok(AttributeValue::from_float(value as f64)) - } - - fn visit_str(self, s: &str) -> Result - where - E: de::Error, - { - Ok(AttributeValue::from_string(s)) - } - - fn visit_none(self) -> Result - where - E: de::Error, - { - Ok(AttributeValue::none()) - } - - fn visit_unit(self) -> Result - where - E: de::Error, - { - // the value that contains null will be processed here. - Ok(AttributeValue::none()) - } - - fn visit_map(self, map: A) -> Result - where - A: MapAccess<'de>, - { - // https://github.com/serde-rs/json/issues/505 - let mut map = map; - let value = map.next_value::()?; - Ok(value) - } - } - - deserializer.deserialize_any(AttributeValueVisitor) - } -} +use crate::core::attributes::AttributeValue; + +use serde::{ + de::{self, MapAccess, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use std::fmt; + +impl Serialize for AttributeValue { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match &self.ty { + None => serializer.serialize_none(), + Some(ty) => match ty { + super::ValueType::IntType => { + if let Some(value) = self.int_value() { + serializer.serialize_i64(value) + } else { + serializer.serialize_none() + } + }, + super::ValueType::FloatType => { + if let Some(value) = self.float_value() { + serializer.serialize_f64(value) + } else { + serializer.serialize_none() + } + }, + super::ValueType::StrType => { + if let Some(value) = self.str_value() { + serializer.serialize_str(&value) + } else { + serializer.serialize_none() + } + }, + super::ValueType::BoolType => { + if let Some(value) = self.bool_value() { + serializer.serialize_bool(value) + } else { + serializer.serialize_none() + } + }, + }, + } + } +} + +impl<'de> Deserialize<'de> for AttributeValue { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct AttributeValueVisitor; + impl<'de> Visitor<'de> for AttributeValueVisitor { + type Value = AttributeValue; + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("bool, usize or string") + } + + fn visit_bool(self, value: bool) -> Result + where + E: de::Error, + { + Ok(AttributeValue::from_bool(value)) + } + + fn visit_i8(self, value: i8) -> Result + where + E: de::Error, + { + Ok(AttributeValue::from_int(value as i64)) + } + + fn visit_i16(self, value: i16) -> Result + where + E: de::Error, + { + Ok(AttributeValue::from_int(value as i64)) + } + + fn visit_i32(self, value: i32) -> Result + where + E: de::Error, + { + Ok(AttributeValue::from_int(value as i64)) + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + Ok(AttributeValue::from_int(value as i64)) + } + + fn visit_u8(self, value: u8) -> Result + where + E: de::Error, + { + Ok(AttributeValue::from_int(value as i64)) + } + + fn visit_u16(self, value: u16) -> Result + where + E: de::Error, + { + Ok(AttributeValue::from_int(value as i64)) + } + + fn visit_u32(self, value: u32) -> Result + where + E: de::Error, + { + Ok(AttributeValue::from_int(value as i64)) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(AttributeValue::from_int(value as i64)) + } + + fn visit_f32(self, value: f32) -> Result + where + E: de::Error, + { + Ok(AttributeValue::from_float(value as f64)) + } + + fn visit_f64(self, value: f64) -> Result + where + E: de::Error, + { + Ok(AttributeValue::from_float(value as f64)) + } + + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + Ok(AttributeValue::from_string(s)) + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(AttributeValue::none()) + } + + fn visit_unit(self) -> Result + where + E: de::Error, + { + // the value that contains null will be processed here. + Ok(AttributeValue::none()) + } + + fn visit_map(self, map: A) -> Result + where + A: MapAccess<'de>, + { + // https://github.com/serde-rs/json/issues/505 + let mut map = map; + let value = map.next_value::()?; + Ok(value) + } + } + + deserializer.deserialize_any(AttributeValueVisitor) + } +} diff --git a/shared-lib/lib-ot/src/core/delta/builder.rs b/shared-lib/lib-ot/src/core/delta/builder.rs index 805d58580a..90fd7195f5 100644 --- a/shared-lib/lib-ot/src/core/delta/builder.rs +++ b/shared-lib/lib-ot/src/core/delta/builder.rs @@ -16,121 +16,121 @@ use crate::core::delta::{trim, DeltaOperations}; /// assert_eq!(delta.content().unwrap(), "AppFlowy"); /// ``` pub struct DeltaOperationBuilder { - delta: DeltaOperations, + delta: DeltaOperations, } impl std::default::Default for DeltaOperationBuilder where - T: OperationAttributes, + T: OperationAttributes, { - fn default() -> Self { - Self { - delta: DeltaOperations::new(), - } + fn default() -> Self { + Self { + delta: DeltaOperations::new(), } + } } impl DeltaOperationBuilder where - T: OperationAttributes, + T: OperationAttributes, { - pub fn new() -> Self { - DeltaOperationBuilder::default() - } + pub fn new() -> Self { + DeltaOperationBuilder::default() + } - pub fn from_delta_operation(delta_operation: DeltaOperations) -> Self { - debug_assert!(delta_operation.utf16_base_len == 0); - let mut builder = DeltaOperationBuilder::new(); - delta_operation.ops.into_iter().for_each(|operation| { - builder.delta.add(operation); - }); - builder - } + pub fn from_delta_operation(delta_operation: DeltaOperations) -> Self { + debug_assert!(delta_operation.utf16_base_len == 0); + let mut builder = DeltaOperationBuilder::new(); + delta_operation.ops.into_iter().for_each(|operation| { + builder.delta.add(operation); + }); + builder + } - /// Retain the 'n' characters with the attributes. Use 'retain' instead if you don't - /// need any attributes. - /// # Examples - /// - /// ``` - /// use lib_ot::text_delta::{BuildInTextAttribute, DeltaTextOperations, DeltaTextOperationBuilder}; - /// - /// let mut attribute = BuildInTextAttribute::Bold(true); - /// let delta = DeltaTextOperationBuilder::new().retain_with_attributes(7, attribute.into()).build(); - /// - /// assert_eq!(delta.json_str(), r#"[{"retain":7,"attributes":{"bold":true}}]"#); - /// ``` - pub fn retain_with_attributes(mut self, n: usize, attrs: T) -> Self { - self.delta.retain(n, attrs); - self - } + /// Retain the 'n' characters with the attributes. Use 'retain' instead if you don't + /// need any attributes. + /// # Examples + /// + /// ``` + /// use lib_ot::text_delta::{BuildInTextAttribute, DeltaTextOperations, DeltaTextOperationBuilder}; + /// + /// let mut attribute = BuildInTextAttribute::Bold(true); + /// let delta = DeltaTextOperationBuilder::new().retain_with_attributes(7, attribute.into()).build(); + /// + /// assert_eq!(delta.json_str(), r#"[{"retain":7,"attributes":{"bold":true}}]"#); + /// ``` + pub fn retain_with_attributes(mut self, n: usize, attrs: T) -> Self { + self.delta.retain(n, attrs); + self + } - pub fn retain(mut self, n: usize) -> Self { - self.delta.retain(n, T::default()); - self - } + pub fn retain(mut self, n: usize) -> Self { + self.delta.retain(n, T::default()); + self + } - /// Deletes the given interval. Panics if interval is not properly sorted. - /// - /// # Examples - /// - /// ``` - /// use lib_ot::core::{OperationTransform, DeltaBuilder}; - /// - /// let delta = DeltaBuilder::new() - /// .insert("AppFlowy...") - /// .build(); - /// - /// let changeset = DeltaBuilder::new() - /// .retain(8) - /// .delete(3) - /// .build(); - /// - /// let new_delta = delta.compose(&changeset).unwrap(); - /// assert_eq!(new_delta.content().unwrap(), "AppFlowy"); - /// ``` - pub fn delete(mut self, n: usize) -> Self { - self.delta.delete(n); - self - } + /// Deletes the given interval. Panics if interval is not properly sorted. + /// + /// # Examples + /// + /// ``` + /// use lib_ot::core::{OperationTransform, DeltaBuilder}; + /// + /// let delta = DeltaBuilder::new() + /// .insert("AppFlowy...") + /// .build(); + /// + /// let changeset = DeltaBuilder::new() + /// .retain(8) + /// .delete(3) + /// .build(); + /// + /// let new_delta = delta.compose(&changeset).unwrap(); + /// assert_eq!(new_delta.content().unwrap(), "AppFlowy"); + /// ``` + pub fn delete(mut self, n: usize) -> Self { + self.delta.delete(n); + self + } - /// Inserts the string with attributes. Use 'insert' instead if you don't - /// need any attributes. - pub fn insert_with_attributes(mut self, s: &str, attrs: T) -> Self { - self.delta.insert(s, attrs); - self - } + /// Inserts the string with attributes. Use 'insert' instead if you don't + /// need any attributes. + pub fn insert_with_attributes(mut self, s: &str, attrs: T) -> Self { + self.delta.insert(s, attrs); + self + } - pub fn insert(mut self, s: &str) -> Self { - self.delta.insert(s, T::default()); - self - } + pub fn insert(mut self, s: &str) -> Self { + self.delta.insert(s, T::default()); + self + } - /// Removes trailing retain operation with empty attributes - /// - /// # Examples - /// - /// ``` - /// use lib_ot::core::{OperationTransform, DeltaBuilder}; - /// use lib_ot::text_delta::{BuildInTextAttribute, DeltaTextOperationBuilder}; - /// let delta = DeltaBuilder::new() - /// .retain(3) - /// .trim() - /// .build(); - /// assert_eq!(delta.ops.len(), 0); - /// - /// let delta = DeltaTextOperationBuilder::new() - /// .retain_with_attributes(3, BuildInTextAttribute::Bold(true).into()) - /// .trim() - /// .build(); - /// assert_eq!(delta.ops.len(), 1); - /// ``` - pub fn trim(mut self) -> Self { - trim(&mut self.delta); - self - } + /// Removes trailing retain operation with empty attributes + /// + /// # Examples + /// + /// ``` + /// use lib_ot::core::{OperationTransform, DeltaBuilder}; + /// use lib_ot::text_delta::{BuildInTextAttribute, DeltaTextOperationBuilder}; + /// let delta = DeltaBuilder::new() + /// .retain(3) + /// .trim() + /// .build(); + /// assert_eq!(delta.ops.len(), 0); + /// + /// let delta = DeltaTextOperationBuilder::new() + /// .retain_with_attributes(3, BuildInTextAttribute::Bold(true).into()) + /// .trim() + /// .build(); + /// assert_eq!(delta.ops.len(), 1); + /// ``` + pub fn trim(mut self) -> Self { + trim(&mut self.delta); + self + } - /// Builds the `Delta` - pub fn build(self) -> DeltaOperations { - self.delta - } + /// Builds the `Delta` + pub fn build(self) -> DeltaOperations { + self.delta + } } diff --git a/shared-lib/lib-ot/src/core/delta/cursor.rs b/shared-lib/lib-ot/src/core/delta/cursor.rs index abb08e2870..c9ed7acd3e 100644 --- a/shared-lib/lib-ot/src/core/delta/cursor.rs +++ b/shared-lib/lib-ot/src/core/delta/cursor.rs @@ -8,196 +8,198 @@ use std::{cmp::min, iter::Enumerate, slice::Iter}; /// A [OperationsCursor] is used to iterate the delta and return the corresponding delta. #[derive(Debug)] pub struct OperationsCursor<'a, T: OperationAttributes> { - pub(crate) delta: &'a DeltaOperations, - pub(crate) origin_iv: Interval, - pub(crate) consume_iv: Interval, - pub(crate) consume_count: usize, - pub(crate) op_offset: usize, - iter: Enumerate>>, - next_op: Option>, + pub(crate) delta: &'a DeltaOperations, + pub(crate) origin_iv: Interval, + pub(crate) consume_iv: Interval, + pub(crate) consume_count: usize, + pub(crate) op_offset: usize, + iter: Enumerate>>, + next_op: Option>, } impl<'a, T> OperationsCursor<'a, T> where - T: OperationAttributes, + T: OperationAttributes, { - /// # Arguments - /// - /// * `delta`: The delta you want to iterate over. - /// * `interval`: The range for the cursor movement. - /// - /// # Examples - /// - /// ``` - /// use lib_ot::core::{OperationsCursor, OperationIterator, Interval, DeltaOperation}; - /// use lib_ot::text_delta::DeltaTextOperations; - /// let mut delta = DeltaTextOperations::default(); - /// delta.add(DeltaOperation::insert("123")); - /// delta.add(DeltaOperation::insert("4")); - /// - /// let mut cursor = OperationsCursor::new(&delta, Interval::new(0, 3)); - /// assert_eq!(cursor.next_iv(), Interval::new(0,3)); - /// assert_eq!(cursor.next_with_len(Some(2)).unwrap(), DeltaOperation::insert("12")); - /// assert_eq!(cursor.get_next_op().unwrap(), DeltaOperation::insert("3")); - /// assert_eq!(cursor.get_next_op(), None); - /// ``` - pub fn new(delta: &'a DeltaOperations, interval: Interval) -> OperationsCursor<'a, T> { - // debug_assert!(interval.start <= delta.target_len); - let mut cursor = Self { - delta, - origin_iv: interval, - consume_iv: interval, - consume_count: 0, - op_offset: 0, - iter: delta.ops.iter().enumerate(), - next_op: None, - }; - cursor.descend(0); - cursor - } + /// # Arguments + /// + /// * `delta`: The delta you want to iterate over. + /// * `interval`: The range for the cursor movement. + /// + /// # Examples + /// + /// ``` + /// use lib_ot::core::{OperationsCursor, OperationIterator, Interval, DeltaOperation}; + /// use lib_ot::text_delta::DeltaTextOperations; + /// let mut delta = DeltaTextOperations::default(); + /// delta.add(DeltaOperation::insert("123")); + /// delta.add(DeltaOperation::insert("4")); + /// + /// let mut cursor = OperationsCursor::new(&delta, Interval::new(0, 3)); + /// assert_eq!(cursor.next_iv(), Interval::new(0,3)); + /// assert_eq!(cursor.next_with_len(Some(2)).unwrap(), DeltaOperation::insert("12")); + /// assert_eq!(cursor.get_next_op().unwrap(), DeltaOperation::insert("3")); + /// assert_eq!(cursor.get_next_op(), None); + /// ``` + pub fn new(delta: &'a DeltaOperations, interval: Interval) -> OperationsCursor<'a, T> { + // debug_assert!(interval.start <= delta.target_len); + let mut cursor = Self { + delta, + origin_iv: interval, + consume_iv: interval, + consume_count: 0, + op_offset: 0, + iter: delta.ops.iter().enumerate(), + next_op: None, + }; + cursor.descend(0); + cursor + } - /// Returns the next operation interval - pub fn next_iv(&self) -> Interval { - self.next_iv_with_len(None).unwrap_or_else(|| Interval::new(0, 0)) - } + /// Returns the next operation interval + pub fn next_iv(&self) -> Interval { + self + .next_iv_with_len(None) + .unwrap_or_else(|| Interval::new(0, 0)) + } - /// Returns the next operation - pub fn get_next_op(&mut self) -> Option> { - self.next_with_len(None) - } + /// Returns the next operation + pub fn get_next_op(&mut self) -> Option> { + self.next_with_len(None) + } - /// Returns the reference of the next operation - pub fn next_op(&self) -> Option<&DeltaOperation> { - let mut next_op = self.next_op.as_ref(); - if next_op.is_none() { - let mut offset = 0; - for op in &self.delta.ops { - offset += op.len(); - if offset > self.consume_count { - next_op = Some(op); - break; - } - } + /// Returns the reference of the next operation + pub fn next_op(&self) -> Option<&DeltaOperation> { + let mut next_op = self.next_op.as_ref(); + if next_op.is_none() { + let mut offset = 0; + for op in &self.delta.ops { + offset += op.len(); + if offset > self.consume_count { + next_op = Some(op); + break; } - next_op + } + } + next_op + } + + /// # Arguments + /// + /// * `expected_len`: Return the next operation with the specified length. + /// + /// + pub fn next_with_len(&mut self, expected_len: Option) -> Option> { + let mut find_op = None; + let holder = self.next_op.clone(); + let mut next_op = holder.as_ref(); + + if next_op.is_none() { + next_op = find_next(self); } - /// # Arguments - /// - /// * `expected_len`: Return the next operation with the specified length. - /// - /// - pub fn next_with_len(&mut self, expected_len: Option) -> Option> { - let mut find_op = None; - let holder = self.next_op.clone(); - let mut next_op = holder.as_ref(); + let mut consume_len = 0; + while find_op.is_none() && next_op.is_some() { + let op = next_op.take().unwrap(); + let interval = self + .next_iv_with_len(expected_len) + .unwrap_or_else(|| Interval::new(0, 0)); - if next_op.is_none() { - next_op = find_next(self); - } + // cache the op if the interval is empty. e.g. last_op_before(Some(0)) + if interval.is_empty() { + self.next_op = Some(op.clone()); + break; + } + find_op = op.shrink(interval); + let suffix = Interval::new(0, op.len()).suffix(interval); + if suffix.is_empty() { + self.next_op = None; + } else { + self.next_op = op.shrink(suffix); + } - let mut consume_len = 0; - while find_op.is_none() && next_op.is_some() { - let op = next_op.take().unwrap(); - let interval = self - .next_iv_with_len(expected_len) - .unwrap_or_else(|| Interval::new(0, 0)); + consume_len += interval.end; + self.consume_count += interval.end; + self.consume_iv.start = self.consume_count; - // cache the op if the interval is empty. e.g. last_op_before(Some(0)) - if interval.is_empty() { - self.next_op = Some(op.clone()); - break; - } - find_op = op.shrink(interval); - let suffix = Interval::new(0, op.len()).suffix(interval); - if suffix.is_empty() { - self.next_op = None; - } else { - self.next_op = op.shrink(suffix); - } - - consume_len += interval.end; - self.consume_count += interval.end; - self.consume_iv.start = self.consume_count; - - // continue to find the op in next iteration - if find_op.is_none() { - next_op = find_next(self); - } - } - - if find_op.is_some() { - if let Some(end) = expected_len { - // try to find the next op before the index if consume_len less than index - if end > consume_len && self.has_next() { - return self.next_with_len(Some(end - consume_len)); - } - } - } - find_op + // continue to find the op in next iteration + if find_op.is_none() { + next_op = find_next(self); + } } - pub fn has_next(&self) -> bool { - self.next_op().is_some() - } - - /// Finds the op within the current offset. - /// This function sets the start of the consume_iv to the offset, updates the consume_count - /// and the next_op reference. - /// - /// # Arguments - /// - /// * `offset`: Represents the offset of the delta string, in Utf16CodeUnit unit. - fn descend(&mut self, offset: usize) { - self.consume_iv.start += offset; - - if self.consume_count >= self.consume_iv.start { - return; - } - while let Some((o_index, op)) = self.iter.next() { - self.op_offset = o_index; - let start = self.consume_count; - let end = start + op.len(); - let intersect = Interval::new(start, end).intersect(self.consume_iv); - if intersect.is_empty() { - self.consume_count += op.len(); - } else { - self.next_op = Some(op.clone()); - break; - } + if find_op.is_some() { + if let Some(end) = expected_len { + // try to find the next op before the index if consume_len less than index + if end > consume_len && self.has_next() { + return self.next_with_len(Some(end - consume_len)); } + } } + find_op + } - fn next_iv_with_len(&self, expected_len: Option) -> Option { - let op = self.next_op()?; - let start = self.consume_count; - let end = match expected_len { - None => self.consume_count + op.len(), - Some(expected_len) => self.consume_count + min(expected_len, op.len()), - }; + pub fn has_next(&self) -> bool { + self.next_op().is_some() + } - let intersect = Interval::new(start, end).intersect(self.consume_iv); - let interval = intersect.translate_neg(start); - Some(interval) + /// Finds the op within the current offset. + /// This function sets the start of the consume_iv to the offset, updates the consume_count + /// and the next_op reference. + /// + /// # Arguments + /// + /// * `offset`: Represents the offset of the delta string, in Utf16CodeUnit unit. + fn descend(&mut self, offset: usize) { + self.consume_iv.start += offset; + + if self.consume_count >= self.consume_iv.start { + return; } + while let Some((o_index, op)) = self.iter.next() { + self.op_offset = o_index; + let start = self.consume_count; + let end = start + op.len(); + let intersect = Interval::new(start, end).intersect(self.consume_iv); + if intersect.is_empty() { + self.consume_count += op.len(); + } else { + self.next_op = Some(op.clone()); + break; + } + } + } + + fn next_iv_with_len(&self, expected_len: Option) -> Option { + let op = self.next_op()?; + let start = self.consume_count; + let end = match expected_len { + None => self.consume_count + op.len(), + Some(expected_len) => self.consume_count + min(expected_len, op.len()), + }; + + let intersect = Interval::new(start, end).intersect(self.consume_iv); + let interval = intersect.translate_neg(start); + Some(interval) + } } fn find_next<'a, T>(cursor: &mut OperationsCursor<'a, T>) -> Option<&'a DeltaOperation> where - T: OperationAttributes, + T: OperationAttributes, { - match cursor.iter.next() { - None => None, - Some((o_index, op)) => { - cursor.op_offset = o_index; - Some(op) - } - } + match cursor.iter.next() { + None => None, + Some((o_index, op)) => { + cursor.op_offset = o_index; + Some(op) + }, + } } type SeekResult = Result<(), OTError>; pub trait Metric { - fn seek(cursor: &mut OperationsCursor, offset: usize) -> SeekResult; + fn seek(cursor: &mut OperationsCursor, offset: usize) -> SeekResult; } /// [OpMetric] is used by [DeltaIterator] for seeking operations @@ -205,18 +207,21 @@ pub trait Metric { pub struct OpMetric(); impl Metric for OpMetric { - fn seek(cursor: &mut OperationsCursor, op_offset: usize) -> SeekResult { - check_bound(cursor.op_offset, op_offset)?; - let mut seek_cursor = OperationsCursor::new(cursor.delta, cursor.origin_iv); + fn seek( + cursor: &mut OperationsCursor, + op_offset: usize, + ) -> SeekResult { + check_bound(cursor.op_offset, op_offset)?; + let mut seek_cursor = OperationsCursor::new(cursor.delta, cursor.origin_iv); - while let Some((_, op)) = seek_cursor.iter.next() { - cursor.descend(op.len()); - if cursor.op_offset >= op_offset { - break; - } - } - Ok(()) + while let Some((_, op)) = seek_cursor.iter.next() { + cursor.descend(op.len()); + if cursor.op_offset >= op_offset { + break; + } } + Ok(()) + } } /// [Utf16CodeUnitMetric] is used by [OperationIterator] for seeking operations. @@ -224,21 +229,25 @@ impl Metric for OpMetric { pub struct Utf16CodeUnitMetric(); impl Metric for Utf16CodeUnitMetric { - fn seek(cursor: &mut OperationsCursor, offset: usize) -> SeekResult { - if offset > 0 { - check_bound(cursor.consume_count, offset)?; - let _ = cursor.next_with_len(Some(offset)); - } - - Ok(()) + fn seek(cursor: &mut OperationsCursor, offset: usize) -> SeekResult { + if offset > 0 { + check_bound(cursor.consume_count, offset)?; + let _ = cursor.next_with_len(Some(offset)); } + + Ok(()) + } } fn check_bound(current: usize, target: usize) -> Result<(), OTError> { - debug_assert!(current <= target); - if current > target { - let msg = format!("{} should be greater than current: {}", target, current); - return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).msg(&msg).build()); - } - Ok(()) + debug_assert!(current <= target); + if current > target { + let msg = format!("{} should be greater than current: {}", target, current); + return Err( + ErrorBuilder::new(OTErrorCode::IncompatibleLength) + .msg(&msg) + .build(), + ); + } + Ok(()) } diff --git a/shared-lib/lib-ot/src/core/delta/iterator.rs b/shared-lib/lib-ot/src/core/delta/iterator.rs index c0b3aaa3cb..7695fbd28f 100644 --- a/shared-lib/lib-ot/src/core/delta/iterator.rs +++ b/shared-lib/lib-ot/src/core/delta/iterator.rs @@ -30,250 +30,250 @@ pub(crate) const MAX_IV_LEN: usize = i32::MAX as usize; /// ); /// ``` pub struct OperationIterator<'a, T: OperationAttributes> { - cursor: OperationsCursor<'a, T>, + cursor: OperationsCursor<'a, T>, } impl<'a, T> OperationIterator<'a, T> where - T: OperationAttributes, + T: OperationAttributes, { - pub fn new(delta: &'a DeltaOperations) -> Self { - let interval = Interval::new(0, MAX_IV_LEN); - Self::from_interval(delta, interval) - } + pub fn new(delta: &'a DeltaOperations) -> Self { + let interval = Interval::new(0, MAX_IV_LEN); + Self::from_interval(delta, interval) + } - pub fn from_offset(delta: &'a DeltaOperations, offset: usize) -> Self { - let interval = Interval::new(0, MAX_IV_LEN); - let mut iter = Self::from_interval(delta, interval); - iter.seek::(offset); - iter - } + pub fn from_offset(delta: &'a DeltaOperations, offset: usize) -> Self { + let interval = Interval::new(0, MAX_IV_LEN); + let mut iter = Self::from_interval(delta, interval); + iter.seek::(offset); + iter + } - pub fn from_interval(delta: &'a DeltaOperations, interval: Interval) -> Self { - let cursor = OperationsCursor::new(delta, interval); - Self { cursor } - } + pub fn from_interval(delta: &'a DeltaOperations, interval: Interval) -> Self { + let cursor = OperationsCursor::new(delta, interval); + Self { cursor } + } - pub fn ops(&mut self) -> Vec> { - self.collect::>() - } + pub fn ops(&mut self) -> Vec> { + self.collect::>() + } - pub fn next_op_len(&self) -> Option { - let interval = self.cursor.next_iv(); - if interval.is_empty() { - None - } else { - Some(interval.size()) + pub fn next_op_len(&self) -> Option { + let interval = self.cursor.next_iv(); + if interval.is_empty() { + None + } else { + Some(interval.size()) + } + } + + pub fn next_op(&mut self) -> Option> { + self.cursor.get_next_op() + } + + pub fn next_op_with_len(&mut self, len: usize) -> Option> { + self.cursor.next_with_len(Some(len)) + } + + // find next op contains NEW_LINE + pub fn next_op_with_newline(&mut self) -> Option<(DeltaOperation, usize)> { + let mut offset = 0; + while self.has_next() { + if let Some(op) = self.next_op() { + if OpNewline::parse(&op).is_contain() { + return Some((op, offset)); } + offset += op.len(); + } } - pub fn next_op(&mut self) -> Option> { - self.cursor.get_next_op() - } + None + } - pub fn next_op_with_len(&mut self, len: usize) -> Option> { - self.cursor.next_with_len(Some(len)) + pub fn seek(&mut self, index: usize) { + match M::seek(&mut self.cursor, index) { + Ok(_) => {}, + Err(e) => log::error!("Seek fail: {:?}", e), } + } - // find next op contains NEW_LINE - pub fn next_op_with_newline(&mut self) -> Option<(DeltaOperation, usize)> { - let mut offset = 0; - while self.has_next() { - if let Some(op) = self.next_op() { - if OpNewline::parse(&op).is_contain() { - return Some((op, offset)); - } - offset += op.len(); - } - } + pub fn has_next(&self) -> bool { + self.cursor.has_next() + } - None + pub fn is_next_insert(&self) -> bool { + match self.cursor.next_op() { + None => false, + Some(op) => op.is_insert(), } + } - pub fn seek(&mut self, index: usize) { - match M::seek(&mut self.cursor, index) { - Ok(_) => {} - Err(e) => log::error!("Seek fail: {:?}", e), - } + pub fn is_next_retain(&self) -> bool { + match self.cursor.next_op() { + None => false, + Some(op) => op.is_retain(), } + } - pub fn has_next(&self) -> bool { - self.cursor.has_next() - } - - pub fn is_next_insert(&self) -> bool { - match self.cursor.next_op() { - None => false, - Some(op) => op.is_insert(), - } - } - - pub fn is_next_retain(&self) -> bool { - match self.cursor.next_op() { - None => false, - Some(op) => op.is_retain(), - } - } - - pub fn is_next_delete(&self) -> bool { - match self.cursor.next_op() { - None => false, - Some(op) => op.is_delete(), - } + pub fn is_next_delete(&self) -> bool { + match self.cursor.next_op() { + None => false, + Some(op) => op.is_delete(), } + } } impl<'a, T> Iterator for OperationIterator<'a, T> where - T: OperationAttributes, + T: OperationAttributes, { - type Item = DeltaOperation; - fn next(&mut self) -> Option { - self.next_op() - } + type Item = DeltaOperation; + fn next(&mut self) -> Option { + self.next_op() + } } pub fn is_empty_line_at_index(delta: &DeltaOperations, index: usize) -> bool { - let mut iter = OperationIterator::new(delta); - let (prev, next) = (iter.next_op_with_len(index), iter.next_op()); - if prev.is_none() { - return true; - } + let mut iter = OperationIterator::new(delta); + let (prev, next) = (iter.next_op_with_len(index), iter.next_op()); + if prev.is_none() { + return true; + } - if next.is_none() { - return false; - } + if next.is_none() { + return false; + } - let prev = prev.unwrap(); - let next = next.unwrap(); - OpNewline::parse(&prev).is_end() && OpNewline::parse(&next).is_start() + let prev = prev.unwrap(); + let next = next.unwrap(); + OpNewline::parse(&prev).is_end() && OpNewline::parse(&next).is_start() } pub struct AttributesIter<'a, T: OperationAttributes> { - delta_iter: OperationIterator<'a, T>, + delta_iter: OperationIterator<'a, T>, } impl<'a, T> AttributesIter<'a, T> where - T: OperationAttributes, + T: OperationAttributes, { - pub fn new(delta: &'a DeltaOperations) -> Self { - let interval = Interval::new(0, usize::MAX); - Self::from_interval(delta, interval) - } + pub fn new(delta: &'a DeltaOperations) -> Self { + let interval = Interval::new(0, usize::MAX); + Self::from_interval(delta, interval) + } - pub fn from_interval(delta: &'a DeltaOperations, interval: Interval) -> Self { - let delta_iter = OperationIterator::from_interval(delta, interval); - Self { delta_iter } - } + pub fn from_interval(delta: &'a DeltaOperations, interval: Interval) -> Self { + let delta_iter = OperationIterator::from_interval(delta, interval); + Self { delta_iter } + } - pub fn next_or_empty(&mut self) -> T { - match self.next() { - None => T::default(), - Some((_, attributes)) => attributes, - } + pub fn next_or_empty(&mut self) -> T { + match self.next() { + None => T::default(), + Some((_, attributes)) => attributes, } + } } impl<'a, T> Deref for AttributesIter<'a, T> where - T: OperationAttributes, + T: OperationAttributes, { - type Target = OperationIterator<'a, T>; + type Target = OperationIterator<'a, T>; - fn deref(&self) -> &Self::Target { - &self.delta_iter - } + fn deref(&self) -> &Self::Target { + &self.delta_iter + } } impl<'a, T> DerefMut for AttributesIter<'a, T> where - T: OperationAttributes, + T: OperationAttributes, { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.delta_iter - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.delta_iter + } } impl<'a, T> Iterator for AttributesIter<'a, T> where - T: OperationAttributes, + T: OperationAttributes, { - type Item = (usize, T); - fn next(&mut self) -> Option { - let next_op = self.delta_iter.next_op(); - next_op.as_ref()?; - let mut length: usize = 0; - let mut attributes = T::default(); + type Item = (usize, T); + fn next(&mut self) -> Option { + let next_op = self.delta_iter.next_op(); + next_op.as_ref()?; + let mut length: usize = 0; + let mut attributes = T::default(); - match next_op.unwrap() { - DeltaOperation::::Delete(_n) => {} - DeltaOperation::::Retain(retain) => { - tracing::trace!("extend retain attributes with {} ", &retain.attributes); - attributes.extend(retain.attributes.clone()); + match next_op.unwrap() { + DeltaOperation::::Delete(_n) => {}, + DeltaOperation::::Retain(retain) => { + tracing::trace!("extend retain attributes with {} ", &retain.attributes); + attributes.extend(retain.attributes.clone()); - length = retain.n; - } - DeltaOperation::::Insert(insert) => { - tracing::trace!("extend insert attributes with {} ", &insert.attributes); - attributes.extend(insert.attributes.clone()); - length = insert.utf16_size(); - } - } - - Some((length, attributes)) + length = retain.n; + }, + DeltaOperation::::Insert(insert) => { + tracing::trace!("extend insert attributes with {} ", &insert.attributes); + attributes.extend(insert.attributes.clone()); + length = insert.utf16_size(); + }, } + + Some((length, attributes)) + } } #[derive(PartialEq, Eq)] pub enum OpNewline { - Start, - End, - Contain, - Equal, - NotFound, + Start, + End, + Contain, + Equal, + NotFound, } impl OpNewline { - pub fn parse(op: &DeltaOperation) -> OpNewline { - let s = op.get_data(); + pub fn parse(op: &DeltaOperation) -> OpNewline { + let s = op.get_data(); - if s == NEW_LINE { - return OpNewline::Equal; - } - - if s.starts_with(NEW_LINE) { - return OpNewline::Start; - } - - if s.ends_with(NEW_LINE) { - return OpNewline::End; - } - - if s.contains(NEW_LINE) { - return OpNewline::Contain; - } - - OpNewline::NotFound + if s == NEW_LINE { + return OpNewline::Equal; } - pub fn is_start(&self) -> bool { - self == &OpNewline::Start || self.is_equal() + if s.starts_with(NEW_LINE) { + return OpNewline::Start; } - pub fn is_end(&self) -> bool { - self == &OpNewline::End || self.is_equal() + if s.ends_with(NEW_LINE) { + return OpNewline::End; } - pub fn is_not_found(&self) -> bool { - self == &OpNewline::NotFound + if s.contains(NEW_LINE) { + return OpNewline::Contain; } - pub fn is_contain(&self) -> bool { - self.is_start() || self.is_end() || self.is_equal() || self == &OpNewline::Contain - } + OpNewline::NotFound + } - pub fn is_equal(&self) -> bool { - self == &OpNewline::Equal - } + pub fn is_start(&self) -> bool { + self == &OpNewline::Start || self.is_equal() + } + + pub fn is_end(&self) -> bool { + self == &OpNewline::End || self.is_equal() + } + + pub fn is_not_found(&self) -> bool { + self == &OpNewline::NotFound + } + + pub fn is_contain(&self) -> bool { + self.is_start() || self.is_end() || self.is_equal() || self == &OpNewline::Contain + } + + pub fn is_equal(&self) -> bool { + self == &OpNewline::Equal + } } diff --git a/shared-lib/lib-ot/src/core/delta/operation/operation.rs b/shared-lib/lib-ot/src/core/delta/operation/operation.rs index 19efd022b5..7bb6b330ff 100644 --- a/shared-lib/lib-ot/src/core/delta/operation/operation.rs +++ b/shared-lib/lib-ot/src/core/delta/operation/operation.rs @@ -4,64 +4,64 @@ use crate::errors::OTError; use serde::{Deserialize, Serialize, __private::Formatter}; use std::fmt::Display; use std::{ - cmp::min, - fmt, - fmt::Debug, - ops::{Deref, DerefMut}, + cmp::min, + fmt, + fmt::Debug, + ops::{Deref, DerefMut}, }; pub trait OperationTransform { - /// Merges the operation with `other` into one operation while preserving - /// the changes of both. - /// - /// # Arguments - /// - /// * `other`: The delta gonna to merge. - /// - /// # Examples - /// - /// ``` - /// use lib_ot::core::{OperationTransform, DeltaBuilder}; - /// let delta = DeltaBuilder::new().build(); - /// let other = DeltaBuilder::new().insert("abc").build(); - /// let new_document = delta.compose(&other).unwrap(); - /// assert_eq!(new_document.content().unwrap(), "abc".to_owned()); - /// ``` - fn compose(&self, other: &Self) -> Result - where - Self: Sized; + /// Merges the operation with `other` into one operation while preserving + /// the changes of both. + /// + /// # Arguments + /// + /// * `other`: The delta gonna to merge. + /// + /// # Examples + /// + /// ``` + /// use lib_ot::core::{OperationTransform, DeltaBuilder}; + /// let delta = DeltaBuilder::new().build(); + /// let other = DeltaBuilder::new().insert("abc").build(); + /// let new_document = delta.compose(&other).unwrap(); + /// assert_eq!(new_document.content().unwrap(), "abc".to_owned()); + /// ``` + fn compose(&self, other: &Self) -> Result + where + Self: Sized; - /// Transforms two operations a and b that happened concurrently and - /// produces two operations a' and b'. - /// (a', b') = a.transform(b) - /// a.compose(b') = b.compose(a') - /// - fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> - where - Self: Sized; + /// Transforms two operations a and b that happened concurrently and + /// produces two operations a' and b'. + /// (a', b') = a.transform(b) + /// a.compose(b') = b.compose(a') + /// + fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> + where + Self: Sized; - /// Returns the invert delta from the other. It can be used to do the undo operation. - /// - /// # Arguments - /// - /// * `other`: Generate the undo delta for [Other]. [Other] can compose the undo delta to return - /// to the previous state. - /// - /// # Examples - /// - /// ``` - /// use lib_ot::core::{OperationTransform, DeltaBuilder}; - /// let initial_delta = DeltaBuilder::new().build(); - /// let delta = DeltaBuilder::new().insert("abc").build(); - /// - /// let undo_delta = delta.invert(&initial_delta); - /// let composed_delta = initial_delta.compose(&delta).unwrap(); - /// let inverted_delta = composed_delta.compose(&undo_delta).unwrap(); - /// - /// assert_eq!(initial_delta, inverted_delta); - /// - /// ``` - fn invert(&self, other: &Self) -> Self; + /// Returns the invert delta from the other. It can be used to do the undo operation. + /// + /// # Arguments + /// + /// * `other`: Generate the undo delta for [Other]. [Other] can compose the undo delta to return + /// to the previous state. + /// + /// # Examples + /// + /// ``` + /// use lib_ot::core::{OperationTransform, DeltaBuilder}; + /// let initial_delta = DeltaBuilder::new().build(); + /// let delta = DeltaBuilder::new().insert("abc").build(); + /// + /// let undo_delta = delta.invert(&initial_delta); + /// let composed_delta = initial_delta.compose(&delta).unwrap(); + /// let inverted_delta = composed_delta.compose(&undo_delta).unwrap(); + /// + /// assert_eq!(initial_delta, inverted_delta); + /// + /// ``` + fn invert(&self, other: &Self) -> Self; } /// Each operation can carry attributes. For example, the [TextAttributes] has a list of key/value attributes. @@ -70,15 +70,17 @@ pub trait OperationTransform { ///Because [Operation] is generic over the T, so you must specify the T. For example, the [Delta] uses ///[EmptyAttributes] as the T. [EmptyAttributes] does nothing, just a phantom. /// -pub trait OperationAttributes: Default + Display + Eq + PartialEq + Clone + Debug + OperationTransform { - fn is_empty(&self) -> bool { - true - } +pub trait OperationAttributes: + Default + Display + Eq + PartialEq + Clone + Debug + OperationTransform +{ + fn is_empty(&self) -> bool { + true + } - /// Remove the attribute which value is None. - fn remove(&mut self) {} + /// Remove the attribute which value is None. + fn remove(&mut self) {} - fn extend(&mut self, _other: Self) {} + fn extend(&mut self, _other: Self) {} } /// [DeltaOperation] consists of three types. @@ -93,387 +95,394 @@ pub trait OperationAttributes: Default + Display + Eq + PartialEq + Clone + Debu /// #[derive(Debug, Clone, Eq, PartialEq)] pub enum DeltaOperation { - Delete(usize), - Retain(Retain), - Insert(Insert), + Delete(usize), + Retain(Retain), + Insert(Insert), } impl DeltaOperation where - T: OperationAttributes, + T: OperationAttributes, { - pub fn delete(n: usize) -> Self { - Self::Delete(n) + pub fn delete(n: usize) -> Self { + Self::Delete(n) + } + + /// Create a [Retain] operation with the given attributes + pub fn retain_with_attributes(n: usize, attributes: T) -> Self { + Self::Retain(Retain { n, attributes }) + } + + /// Create a [Retain] operation without attributes + pub fn retain(n: usize) -> Self { + Self::Retain(Retain { + n, + attributes: T::default(), + }) + } + + /// Create a [Insert] operation with the given attributes + pub fn insert_with_attributes(s: &str, attributes: T) -> Self { + Self::Insert(Insert { + s: OTString::from(s), + attributes, + }) + } + + /// Create a [Insert] operation without attributes + pub fn insert(s: &str) -> Self { + Self::Insert(Insert { + s: OTString::from(s), + attributes: T::default(), + }) + } + + /// Return the String if the operation is [Insert] operation, otherwise return the empty string. + pub fn get_data(&self) -> &str { + match self { + DeltaOperation::Delete(_) => "", + DeltaOperation::Retain(_) => "", + DeltaOperation::Insert(insert) => &insert.s, + } + } + + pub fn get_attributes(&self) -> T { + match self { + DeltaOperation::Delete(_) => T::default(), + DeltaOperation::Retain(retain) => retain.attributes.clone(), + DeltaOperation::Insert(insert) => insert.attributes.clone(), + } + } + + pub fn set_attributes(&mut self, attributes: T) { + match self { + DeltaOperation::Delete(_) => log::error!("Delete should not contains attributes"), + DeltaOperation::Retain(retain) => retain.attributes = attributes, + DeltaOperation::Insert(insert) => insert.attributes = attributes, + } + } + + pub fn has_attribute(&self) -> bool { + !self.get_attributes().is_empty() + } + + pub fn len(&self) -> usize { + match self { + DeltaOperation::Delete(n) => *n, + DeltaOperation::Retain(r) => r.n, + DeltaOperation::Insert(i) => i.utf16_size(), + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + #[allow(dead_code)] + pub fn split(&self, index: usize) -> (Option>, Option>) { + debug_assert!(index < self.len()); + let left; + let right; + match self { + DeltaOperation::Delete(n) => { + left = Some(DeltaOperation::::delete(index)); + right = Some(DeltaOperation::::delete(*n - index)); + }, + DeltaOperation::Retain(retain) => { + left = Some(DeltaOperation::::delete(index)); + right = Some(DeltaOperation::::delete(retain.n - index)); + }, + DeltaOperation::Insert(insert) => { + let attributes = self.get_attributes(); + left = Some(DeltaOperation::::insert_with_attributes( + &insert.s[0..index], + attributes.clone(), + )); + right = Some(DeltaOperation::::insert_with_attributes( + &insert.s[index..insert.utf16_size()], + attributes, + )); + }, } - /// Create a [Retain] operation with the given attributes - pub fn retain_with_attributes(n: usize, attributes: T) -> Self { - Self::Retain(Retain { n, attributes }) - } + (left, right) + } - /// Create a [Retain] operation without attributes - pub fn retain(n: usize) -> Self { - Self::Retain(Retain { - n, - attributes: T::default(), - }) - } - - /// Create a [Insert] operation with the given attributes - pub fn insert_with_attributes(s: &str, attributes: T) -> Self { - Self::Insert(Insert { - s: OTString::from(s), - attributes, - }) - } - - /// Create a [Insert] operation without attributes - pub fn insert(s: &str) -> Self { - Self::Insert(Insert { - s: OTString::from(s), - attributes: T::default(), - }) - } - - /// Return the String if the operation is [Insert] operation, otherwise return the empty string. - pub fn get_data(&self) -> &str { - match self { - DeltaOperation::Delete(_) => "", - DeltaOperation::Retain(_) => "", - DeltaOperation::Insert(insert) => &insert.s, + /// Returns an operation with the specified width. + /// # Arguments + /// + /// * `interval`: Specify the shrink width of the operation. + /// + /// # Examples + /// + /// ``` + /// use lib_ot::core::{Interval, DeltaOperation, EmptyAttributes}; + /// let operation = DeltaOperation::::insert("1234"); + /// + /// let op1 = operation.shrink(Interval::new(0,3)).unwrap(); + /// assert_eq!(op1 , DeltaOperation::insert("123")); + /// + /// let op2= operation.shrink(Interval::new(3,4)).unwrap(); + /// assert_eq!(op2, DeltaOperation::insert("4")); + /// ``` + pub fn shrink(&self, interval: Interval) -> Option> { + let op = match self { + DeltaOperation::Delete(n) => DeltaOperation::delete(min(*n, interval.size())), + DeltaOperation::Retain(retain) => DeltaOperation::retain_with_attributes( + min(retain.n, interval.size()), + retain.attributes.clone(), + ), + DeltaOperation::Insert(insert) => { + if interval.start > insert.utf16_size() { + DeltaOperation::insert("") + } else { + let s = insert.s.sub_str(interval).unwrap_or_else(|| "".to_owned()); + DeltaOperation::insert_with_attributes(&s, insert.attributes.clone()) } + }, + }; + + match op.is_empty() { + true => None, + false => Some(op), } + } - pub fn get_attributes(&self) -> T { - match self { - DeltaOperation::Delete(_) => T::default(), - DeltaOperation::Retain(retain) => retain.attributes.clone(), - DeltaOperation::Insert(insert) => insert.attributes.clone(), - } + pub fn is_delete(&self) -> bool { + if let DeltaOperation::Delete(_) = self { + return true; } + false + } - pub fn set_attributes(&mut self, attributes: T) { - match self { - DeltaOperation::Delete(_) => log::error!("Delete should not contains attributes"), - DeltaOperation::Retain(retain) => retain.attributes = attributes, - DeltaOperation::Insert(insert) => insert.attributes = attributes, - } + pub fn is_insert(&self) -> bool { + if let DeltaOperation::Insert(_) = self { + return true; } + false + } - pub fn has_attribute(&self) -> bool { - !self.get_attributes().is_empty() + pub fn is_retain(&self) -> bool { + if let DeltaOperation::Retain(_) = self { + return true; } + false + } - pub fn len(&self) -> usize { - match self { - DeltaOperation::Delete(n) => *n, - DeltaOperation::Retain(r) => r.n, - DeltaOperation::Insert(i) => i.utf16_size(), - } - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - #[allow(dead_code)] - pub fn split(&self, index: usize) -> (Option>, Option>) { - debug_assert!(index < self.len()); - let left; - let right; - match self { - DeltaOperation::Delete(n) => { - left = Some(DeltaOperation::::delete(index)); - right = Some(DeltaOperation::::delete(*n - index)); - } - DeltaOperation::Retain(retain) => { - left = Some(DeltaOperation::::delete(index)); - right = Some(DeltaOperation::::delete(retain.n - index)); - } - DeltaOperation::Insert(insert) => { - let attributes = self.get_attributes(); - left = Some(DeltaOperation::::insert_with_attributes( - &insert.s[0..index], - attributes.clone(), - )); - right = Some(DeltaOperation::::insert_with_attributes( - &insert.s[index..insert.utf16_size()], - attributes, - )); - } - } - - (left, right) - } - - /// Returns an operation with the specified width. - /// # Arguments - /// - /// * `interval`: Specify the shrink width of the operation. - /// - /// # Examples - /// - /// ``` - /// use lib_ot::core::{Interval, DeltaOperation, EmptyAttributes}; - /// let operation = DeltaOperation::::insert("1234"); - /// - /// let op1 = operation.shrink(Interval::new(0,3)).unwrap(); - /// assert_eq!(op1 , DeltaOperation::insert("123")); - /// - /// let op2= operation.shrink(Interval::new(3,4)).unwrap(); - /// assert_eq!(op2, DeltaOperation::insert("4")); - /// ``` - pub fn shrink(&self, interval: Interval) -> Option> { - let op = match self { - DeltaOperation::Delete(n) => DeltaOperation::delete(min(*n, interval.size())), - DeltaOperation::Retain(retain) => { - DeltaOperation::retain_with_attributes(min(retain.n, interval.size()), retain.attributes.clone()) - } - DeltaOperation::Insert(insert) => { - if interval.start > insert.utf16_size() { - DeltaOperation::insert("") - } else { - let s = insert.s.sub_str(interval).unwrap_or_else(|| "".to_owned()); - DeltaOperation::insert_with_attributes(&s, insert.attributes.clone()) - } - } - }; - - match op.is_empty() { - true => None, - false => Some(op), - } - } - - pub fn is_delete(&self) -> bool { - if let DeltaOperation::Delete(_) = self { - return true; - } - false - } - - pub fn is_insert(&self) -> bool { - if let DeltaOperation::Insert(_) = self { - return true; - } - false - } - - pub fn is_retain(&self) -> bool { - if let DeltaOperation::Retain(_) = self { - return true; - } - false - } - - pub fn is_plain(&self) -> bool { - match self { - DeltaOperation::Delete(_) => true, - DeltaOperation::Retain(retain) => retain.is_plain(), - DeltaOperation::Insert(insert) => insert.is_plain(), - } + pub fn is_plain(&self) -> bool { + match self { + DeltaOperation::Delete(_) => true, + DeltaOperation::Retain(retain) => retain.is_plain(), + DeltaOperation::Insert(insert) => insert.is_plain(), } + } } impl fmt::Display for DeltaOperation where - T: OperationAttributes, + T: OperationAttributes, { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("{")?; - match self { - DeltaOperation::Delete(n) => { - f.write_fmt(format_args!("delete: {}", n))?; - } - DeltaOperation::Retain(r) => { - f.write_fmt(format_args!("{}", r))?; - } - DeltaOperation::Insert(i) => { - f.write_fmt(format_args!("{}", i))?; - } - } - f.write_str("}")?; - Ok(()) + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("{")?; + match self { + DeltaOperation::Delete(n) => { + f.write_fmt(format_args!("delete: {}", n))?; + }, + DeltaOperation::Retain(r) => { + f.write_fmt(format_args!("{}", r))?; + }, + DeltaOperation::Insert(i) => { + f.write_fmt(format_args!("{}", i))?; + }, } + f.write_str("}")?; + Ok(()) + } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct Retain { - pub n: usize, - pub attributes: T, + pub n: usize, + pub attributes: T, } impl fmt::Display for Retain where - T: OperationAttributes, + T: OperationAttributes, { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if self.attributes.is_empty() { - f.write_fmt(format_args!("retain: {}", self.n)) - } else { - f.write_fmt(format_args!("retain: {}, attributes: {}", self.n, self.attributes)) - } + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if self.attributes.is_empty() { + f.write_fmt(format_args!("retain: {}", self.n)) + } else { + f.write_fmt(format_args!( + "retain: {}, attributes: {}", + self.n, self.attributes + )) } + } } impl Retain where - T: OperationAttributes, + T: OperationAttributes, { - pub fn merge_or_new(&mut self, n: usize, attributes: T) -> Option> { - // tracing::trace!( - // "merge_retain_or_new_op: len: {:?}, l: {} - r: {}", - // n, - // self.attributes, - // attributes - // ); - if self.attributes == attributes { - self.n += n; - None - } else { - Some(DeltaOperation::retain_with_attributes(n, attributes)) - } + pub fn merge_or_new(&mut self, n: usize, attributes: T) -> Option> { + // tracing::trace!( + // "merge_retain_or_new_op: len: {:?}, l: {} - r: {}", + // n, + // self.attributes, + // attributes + // ); + if self.attributes == attributes { + self.n += n; + None + } else { + Some(DeltaOperation::retain_with_attributes(n, attributes)) } + } - pub fn is_plain(&self) -> bool { - self.attributes.is_empty() - } + pub fn is_plain(&self) -> bool { + self.attributes.is_empty() + } } impl std::convert::From for Retain where - T: OperationAttributes, + T: OperationAttributes, { - fn from(n: usize) -> Self { - Retain { - n, - attributes: T::default(), - } + fn from(n: usize) -> Self { + Retain { + n, + attributes: T::default(), } + } } impl Deref for Retain where - T: OperationAttributes, + T: OperationAttributes, { - type Target = usize; + type Target = usize; - fn deref(&self) -> &Self::Target { - &self.n - } + fn deref(&self) -> &Self::Target { + &self.n + } } impl DerefMut for Retain where - T: OperationAttributes, + T: OperationAttributes, { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.n - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.n + } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct Insert { - pub s: OTString, - pub attributes: T, + pub s: OTString, + pub attributes: T, } impl fmt::Display for Insert where - T: OperationAttributes, + T: OperationAttributes, { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let mut s = self.s.clone(); - if s.ends_with('\n') { - s.pop(); - if s.is_empty() { - s = "new_line".into(); - } - } - - if self.attributes.is_empty() { - f.write_fmt(format_args!("insert: {}", s)) - } else { - f.write_fmt(format_args!("insert: {}, attributes: {}", s, self.attributes)) - } + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut s = self.s.clone(); + if s.ends_with('\n') { + s.pop(); + if s.is_empty() { + s = "new_line".into(); + } } + + if self.attributes.is_empty() { + f.write_fmt(format_args!("insert: {}", s)) + } else { + f.write_fmt(format_args!( + "insert: {}, attributes: {}", + s, self.attributes + )) + } + } } impl Insert where - T: OperationAttributes, + T: OperationAttributes, { - pub fn utf16_size(&self) -> usize { - self.s.utf16_len() - } + pub fn utf16_size(&self) -> usize { + self.s.utf16_len() + } - pub fn merge_or_new_op(&mut self, s: &str, attributes: T) -> Option> { - if self.attributes == attributes { - self.s += s; - None - } else { - Some(DeltaOperation::::insert_with_attributes(s, attributes)) - } + pub fn merge_or_new_op(&mut self, s: &str, attributes: T) -> Option> { + if self.attributes == attributes { + self.s += s; + None + } else { + Some(DeltaOperation::::insert_with_attributes(s, attributes)) } + } - pub fn is_plain(&self) -> bool { - self.attributes.is_empty() - } + pub fn is_plain(&self) -> bool { + self.attributes.is_empty() + } } impl std::convert::From for Insert where - T: OperationAttributes, + T: OperationAttributes, { - fn from(s: String) -> Self { - Insert { - s: s.into(), - attributes: T::default(), - } + fn from(s: String) -> Self { + Insert { + s: s.into(), + attributes: T::default(), } + } } impl std::convert::From<&str> for Insert where - T: OperationAttributes, + T: OperationAttributes, { - fn from(s: &str) -> Self { - Insert::from(s.to_owned()) - } + fn from(s: &str) -> Self { + Insert::from(s.to_owned()) + } } impl std::convert::From for Insert where - T: OperationAttributes, + T: OperationAttributes, { - fn from(s: OTString) -> Self { - Insert { - s, - attributes: T::default(), - } + fn from(s: OTString) -> Self { + Insert { + s, + attributes: T::default(), } + } } #[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] pub struct EmptyAttributes(); impl fmt::Display for EmptyAttributes { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("PhantomAttributes") - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("PhantomAttributes") + } } impl OperationAttributes for EmptyAttributes {} impl OperationTransform for EmptyAttributes { - fn compose(&self, _other: &Self) -> Result { - Ok(self.clone()) - } + fn compose(&self, _other: &Self) -> Result { + Ok(self.clone()) + } - fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> { - Ok((self.clone(), other.clone())) - } + fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> { + Ok((self.clone(), other.clone())) + } - fn invert(&self, _other: &Self) -> Self { - self.clone() - } + fn invert(&self, _other: &Self) -> Self { + self.clone() + } } diff --git a/shared-lib/lib-ot/src/core/delta/operation/operation_serde.rs b/shared-lib/lib-ot/src/core/delta/operation/operation_serde.rs index f2b0843049..13ea729cdd 100644 --- a/shared-lib/lib-ot/src/core/delta/operation/operation_serde.rs +++ b/shared-lib/lib-ot/src/core/delta/operation/operation_serde.rs @@ -1,310 +1,340 @@ use crate::core::delta::operation::{DeltaOperation, Insert, OperationAttributes, Retain}; use crate::core::ot_str::OTString; use serde::{ - de, - de::{MapAccess, SeqAccess, Visitor}, - ser::SerializeMap, - Deserialize, Deserializer, Serialize, Serializer, + de, + de::{MapAccess, SeqAccess, Visitor}, + ser::SerializeMap, + Deserialize, Deserializer, Serialize, Serializer, }; use std::{fmt, marker::PhantomData}; impl Serialize for DeltaOperation where - T: OperationAttributes + Serialize, + T: OperationAttributes + Serialize, { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - DeltaOperation::Retain(retain) => retain.serialize(serializer), - DeltaOperation::Delete(i) => { - let mut map = serializer.serialize_map(Some(1))?; - map.serialize_entry("delete", i)?; - map.end() - } - DeltaOperation::Insert(insert) => insert.serialize(serializer), - } + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + DeltaOperation::Retain(retain) => retain.serialize(serializer), + DeltaOperation::Delete(i) => { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry("delete", i)?; + map.end() + }, + DeltaOperation::Insert(insert) => insert.serialize(serializer), } + } } impl<'de, T> Deserialize<'de> for DeltaOperation where - T: OperationAttributes + Deserialize<'de>, + T: OperationAttributes + Deserialize<'de>, { - fn deserialize(deserializer: D) -> Result, D::Error> + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + struct OperationVisitor(PhantomData T>); + + impl<'de, T> Visitor<'de> for OperationVisitor where - D: Deserializer<'de>, + T: OperationAttributes + Deserialize<'de>, { - struct OperationVisitor(PhantomData T>); + type Value = DeltaOperation; - impl<'de, T> Visitor<'de> for OperationVisitor - where - T: OperationAttributes + Deserialize<'de>, - { - type Value = DeltaOperation; + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an integer between -2^64 and 2^63 or a string") + } - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("an integer between -2^64 and 2^63 or a string") - } - - #[inline] - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - let mut operation = None; - let mut attributes = None; - while let Some(key) = map.next_key()? { - match key { - "delete" => { - if operation.is_some() { - return Err(de::Error::duplicate_field("operation")); - } - operation = Some(DeltaOperation::::Delete(map.next_value()?)); - } - "retain" => { - if operation.is_some() { - return Err(de::Error::duplicate_field("operation")); - } - let i: usize = map.next_value()?; - operation = Some(DeltaOperation::::Retain(i.into())); - } - "insert" => { - if operation.is_some() { - return Err(de::Error::duplicate_field("operation")); - } - let i: String = map.next_value()?; - operation = Some(DeltaOperation::::Insert(i.into())); - } - "attributes" => { - if attributes.is_some() { - return Err(de::Error::duplicate_field("attributes")); - } - let map: T = map.next_value()?; - attributes = Some(map); - } - _ => {} - } - } - match operation { - None => Err(de::Error::missing_field("operation")), - Some(mut operation) => { - if !operation.is_delete() { - operation.set_attributes(attributes.unwrap_or_default()); - } - Ok(operation) - } - } - } + #[inline] + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut operation = None; + let mut attributes = None; + while let Some(key) = map.next_key()? { + match key { + "delete" => { + if operation.is_some() { + return Err(de::Error::duplicate_field("operation")); + } + operation = Some(DeltaOperation::::Delete(map.next_value()?)); + }, + "retain" => { + if operation.is_some() { + return Err(de::Error::duplicate_field("operation")); + } + let i: usize = map.next_value()?; + operation = Some(DeltaOperation::::Retain(i.into())); + }, + "insert" => { + if operation.is_some() { + return Err(de::Error::duplicate_field("operation")); + } + let i: String = map.next_value()?; + operation = Some(DeltaOperation::::Insert(i.into())); + }, + "attributes" => { + if attributes.is_some() { + return Err(de::Error::duplicate_field("attributes")); + } + let map: T = map.next_value()?; + attributes = Some(map); + }, + _ => {}, + } } - - deserializer.deserialize_any(OperationVisitor(PhantomData)) + match operation { + None => Err(de::Error::missing_field("operation")), + Some(mut operation) => { + if !operation.is_delete() { + operation.set_attributes(attributes.unwrap_or_default()); + } + Ok(operation) + }, + } + } } + + deserializer.deserialize_any(OperationVisitor(PhantomData)) + } } impl Serialize for Retain where - T: OperationAttributes + Serialize, + T: OperationAttributes + Serialize, { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let len = false as usize + 1 + if self.attributes.is_empty() { 0 } else { 1 }; - let mut serde_state = serializer.serialize_struct("Retain", len)?; - serde::ser::SerializeStruct::serialize_field(&mut serde_state, "retain", &self.n)?; - if !self.attributes.is_empty() { - serde::ser::SerializeStruct::serialize_field(&mut serde_state, "attributes", &self.attributes)?; - } - serde::ser::SerializeStruct::end(serde_state) + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let len = false as usize + 1 + if self.attributes.is_empty() { 0 } else { 1 }; + let mut serde_state = serializer.serialize_struct("Retain", len)?; + serde::ser::SerializeStruct::serialize_field(&mut serde_state, "retain", &self.n)?; + if !self.attributes.is_empty() { + serde::ser::SerializeStruct::serialize_field( + &mut serde_state, + "attributes", + &self.attributes, + )?; } + serde::ser::SerializeStruct::end(serde_state) + } } impl<'de, T> Deserialize<'de> for Retain where - T: OperationAttributes + Deserialize<'de>, + T: OperationAttributes + Deserialize<'de>, { - fn deserialize(deserializer: D) -> Result>::Error> + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + struct RetainVisitor(PhantomData T>); + + impl<'de, T> Visitor<'de> for RetainVisitor where - D: Deserializer<'de>, + T: OperationAttributes + Deserialize<'de>, { - struct RetainVisitor(PhantomData T>); + type Value = Retain; - impl<'de, T> Visitor<'de> for RetainVisitor - where - T: OperationAttributes + Deserialize<'de>, - { - type Value = Retain; + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct Retain") + } - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("struct Retain") - } + #[inline] + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let len = match serde::de::SeqAccess::next_element::(&mut seq)? { + Some(val) => val, + None => { + return Err(de::Error::invalid_length( + 0, + &"struct Retain with 2 elements", + )); + }, + }; - #[inline] - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let len = match serde::de::SeqAccess::next_element::(&mut seq)? { - Some(val) => val, - None => { - return Err(de::Error::invalid_length(0, &"struct Retain with 2 elements")); - } - }; + let attributes = match serde::de::SeqAccess::next_element::(&mut seq)? { + Some(val) => val, + None => { + return Err(de::Error::invalid_length( + 1, + &"struct Retain with 2 elements", + )); + }, + }; - let attributes = match serde::de::SeqAccess::next_element::(&mut seq)? { - Some(val) => val, - None => { - return Err(de::Error::invalid_length(1, &"struct Retain with 2 elements")); - } - }; + Ok(Retain:: { n: len, attributes }) + } - Ok(Retain:: { n: len, attributes }) - } - - #[inline] - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - let mut len: Option = None; - let mut attributes: Option = None; - while let Some(key) = map.next_key()? { - match key { - "retain" => { - if len.is_some() { - return Err(de::Error::duplicate_field("retain")); - } - len = Some(map.next_value()?); - } - "attributes" => { - if attributes.is_some() { - return Err(de::Error::duplicate_field("attributes")); - } - attributes = Some(map.next_value()?); - } - _ => panic!(), - } - } - - if len.is_none() { - return Err(de::Error::missing_field("len")); - } - - if attributes.is_none() { - return Err(de::Error::missing_field("attributes")); - } - Ok(Retain:: { - n: len.unwrap(), - attributes: attributes.unwrap(), - }) - } + #[inline] + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut len: Option = None; + let mut attributes: Option = None; + while let Some(key) = map.next_key()? { + match key { + "retain" => { + if len.is_some() { + return Err(de::Error::duplicate_field("retain")); + } + len = Some(map.next_value()?); + }, + "attributes" => { + if attributes.is_some() { + return Err(de::Error::duplicate_field("attributes")); + } + attributes = Some(map.next_value()?); + }, + _ => panic!(), + } } - const FIELDS: &[&str] = &["retain", "attributes"]; - serde::Deserializer::deserialize_struct(deserializer, "Retain", FIELDS, RetainVisitor(PhantomData)) + + if len.is_none() { + return Err(de::Error::missing_field("len")); + } + + if attributes.is_none() { + return Err(de::Error::missing_field("attributes")); + } + Ok(Retain:: { + n: len.unwrap(), + attributes: attributes.unwrap(), + }) + } } + const FIELDS: &[&str] = &["retain", "attributes"]; + serde::Deserializer::deserialize_struct( + deserializer, + "Retain", + FIELDS, + RetainVisitor(PhantomData), + ) + } } impl Serialize for Insert where - T: OperationAttributes + Serialize, + T: OperationAttributes + Serialize, { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let len = false as usize + 1 + if self.attributes.is_empty() { 0 } else { 1 }; - let mut serde_state = serializer.serialize_struct("Insert", len)?; - serde::ser::SerializeStruct::serialize_field(&mut serde_state, "insert", &self.s)?; - if !self.attributes.is_empty() { - serde::ser::SerializeStruct::serialize_field(&mut serde_state, "attributes", &self.attributes)?; - } - serde::ser::SerializeStruct::end(serde_state) + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let len = false as usize + 1 + if self.attributes.is_empty() { 0 } else { 1 }; + let mut serde_state = serializer.serialize_struct("Insert", len)?; + serde::ser::SerializeStruct::serialize_field(&mut serde_state, "insert", &self.s)?; + if !self.attributes.is_empty() { + serde::ser::SerializeStruct::serialize_field( + &mut serde_state, + "attributes", + &self.attributes, + )?; } + serde::ser::SerializeStruct::end(serde_state) + } } impl<'de, T> Deserialize<'de> for Insert where - T: OperationAttributes + Deserialize<'de>, + T: OperationAttributes + Deserialize<'de>, { - fn deserialize(deserializer: D) -> Result>::Error> + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + struct InsertVisitor(PhantomData T>); + + impl<'de, T> Visitor<'de> for InsertVisitor where - D: Deserializer<'de>, + T: OperationAttributes + Deserialize<'de>, { - struct InsertVisitor(PhantomData T>); + type Value = Insert; - impl<'de, T> Visitor<'de> for InsertVisitor - where - T: OperationAttributes + Deserialize<'de>, - { - type Value = Insert; + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct Insert") + } - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("struct Insert") - } + #[inline] + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let s = match serde::de::SeqAccess::next_element::(&mut seq)? { + Some(val) => val, + None => { + return Err(de::Error::invalid_length( + 0, + &"struct Insert with 2 elements", + )); + }, + }; - #[inline] - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let s = match serde::de::SeqAccess::next_element::(&mut seq)? { - Some(val) => val, - None => { - return Err(de::Error::invalid_length(0, &"struct Insert with 2 elements")); - } - }; + let attributes = match serde::de::SeqAccess::next_element::(&mut seq)? { + Some(val) => val, + None => { + return Err(de::Error::invalid_length( + 1, + &"struct Retain with 2 elements", + )); + }, + }; - let attributes = match serde::de::SeqAccess::next_element::(&mut seq)? { - Some(val) => val, - None => { - return Err(de::Error::invalid_length(1, &"struct Retain with 2 elements")); - } - }; + Ok(Insert:: { s, attributes }) + } - Ok(Insert:: { s, attributes }) - } - - #[inline] - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - let mut s: Option = None; - let mut attributes: Option = None; - while let Some(key) = map.next_key()? { - match key { - "insert" => { - if s.is_some() { - return Err(de::Error::duplicate_field("insert")); - } - s = Some(map.next_value()?); - } - "attributes" => { - if attributes.is_some() { - return Err(de::Error::duplicate_field("attributes")); - } - attributes = Some(map.next_value()?); - } - _ => panic!(), - } - } - - if s.is_none() { - return Err(de::Error::missing_field("s")); - } - - if attributes.is_none() { - return Err(de::Error::missing_field("attributes")); - } - Ok(Insert:: { - s: s.unwrap(), - attributes: attributes.unwrap(), - }) - } + #[inline] + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut s: Option = None; + let mut attributes: Option = None; + while let Some(key) = map.next_key()? { + match key { + "insert" => { + if s.is_some() { + return Err(de::Error::duplicate_field("insert")); + } + s = Some(map.next_value()?); + }, + "attributes" => { + if attributes.is_some() { + return Err(de::Error::duplicate_field("attributes")); + } + attributes = Some(map.next_value()?); + }, + _ => panic!(), + } } - const FIELDS: &[&str] = &["insert", "attributes"]; - serde::Deserializer::deserialize_struct(deserializer, "Insert", FIELDS, InsertVisitor(PhantomData)) + + if s.is_none() { + return Err(de::Error::missing_field("s")); + } + + if attributes.is_none() { + return Err(de::Error::missing_field("attributes")); + } + Ok(Insert:: { + s: s.unwrap(), + attributes: attributes.unwrap(), + }) + } } + const FIELDS: &[&str] = &["insert", "attributes"]; + serde::Deserializer::deserialize_struct( + deserializer, + "Insert", + FIELDS, + InsertVisitor(PhantomData), + ) + } } diff --git a/shared-lib/lib-ot/src/core/delta/ops.rs b/shared-lib/lib-ot/src/core/delta/ops.rs index ce25e31497..f9299a20e1 100644 --- a/shared-lib/lib-ot/src/core/delta/ops.rs +++ b/shared-lib/lib-ot/src/core/delta/ops.rs @@ -1,4 +1,6 @@ -use crate::core::delta::operation::{DeltaOperation, EmptyAttributes, OperationAttributes, OperationTransform}; +use crate::core::delta::operation::{ + DeltaOperation, EmptyAttributes, OperationAttributes, OperationTransform, +}; use crate::core::delta::{OperationIterator, MAX_IV_LEN}; use crate::core::interval::Interval; use crate::core::ot_str::OTString; @@ -7,11 +9,11 @@ use crate::errors::{ErrorBuilder, OTError, OTErrorCode}; use bytes::Bytes; use serde::de::DeserializeOwned; use std::{ - cmp::{min, Ordering}, - fmt, - iter::FromIterator, - str, - str::FromStr, + cmp::{min, Ordering}, + fmt, + iter::FromIterator, + str, + str::FromStr, }; pub type DeltaBuilder = DeltaOperationBuilder; @@ -27,628 +29,641 @@ pub type DeltaBuilder = DeltaOperationBuilder; /// #[derive(Clone, Debug, PartialEq, Eq)] pub struct DeltaOperations { - pub ops: Vec>, + pub ops: Vec>, - /// 'Delete' and 'Retain' operation will update the [utf16_base_len] - /// Transforming the other delta, it requires the utf16_base_len must be equal. - pub utf16_base_len: usize, + /// 'Delete' and 'Retain' operation will update the [utf16_base_len] + /// Transforming the other delta, it requires the utf16_base_len must be equal. + pub utf16_base_len: usize, - /// Represents the current len of the delta. - /// 'Insert' and 'Retain' operation will update the [utf16_target_len] - pub utf16_target_len: usize, + /// Represents the current len of the delta. + /// 'Insert' and 'Retain' operation will update the [utf16_target_len] + pub utf16_target_len: usize, } impl Default for DeltaOperations where - T: OperationAttributes, + T: OperationAttributes, { - fn default() -> Self { - Self { - ops: Vec::new(), - utf16_base_len: 0, - utf16_target_len: 0, - } + fn default() -> Self { + Self { + ops: Vec::new(), + utf16_base_len: 0, + utf16_target_len: 0, } + } } impl fmt::Display for DeltaOperations where - T: OperationAttributes, + T: OperationAttributes, { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // f.write_str(&serde_json::to_string(self).unwrap_or("".to_owned()))?; - f.write_str("[ ")?; - for op in &self.ops { - f.write_fmt(format_args!("{} ", op))?; - } - f.write_str("]")?; - Ok(()) + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // f.write_str(&serde_json::to_string(self).unwrap_or("".to_owned()))?; + f.write_str("[ ")?; + for op in &self.ops { + f.write_fmt(format_args!("{} ", op))?; } + f.write_str("]")?; + Ok(()) + } } impl FromIterator> for DeltaOperations where - T: OperationAttributes, + T: OperationAttributes, { - fn from_iter>>(ops: I) -> Self { - let mut operations = DeltaOperations::default(); - for op in ops { - operations.add(op); - } - operations + fn from_iter>>(ops: I) -> Self { + let mut operations = DeltaOperations::default(); + for op in ops { + operations.add(op); } + operations + } } impl DeltaOperations where - T: OperationAttributes, + T: OperationAttributes, { - pub fn new() -> Self { - Self::default() + pub fn new() -> Self { + Self::default() + } + + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Self { + ops: Vec::with_capacity(capacity), + utf16_base_len: 0, + utf16_target_len: 0, + } + } + + /// Adding an operation. It will be added in sequence. + pub fn add(&mut self, op: DeltaOperation) { + match op { + DeltaOperation::Delete(i) => self.delete(i), + DeltaOperation::Insert(i) => self.insert(&i.s, i.attributes), + DeltaOperation::Retain(r) => self.retain(r.n, r.attributes), + } + } + + /// Creating a [Delete] operation with len [n] + pub fn delete(&mut self, n: usize) { + if n == 0 { + return; + } + self.utf16_base_len += n as usize; + if let Some(DeltaOperation::Delete(n_last)) = self.ops.last_mut() { + *n_last += n; + } else { + self.ops.push(DeltaOperation::delete(n)); + } + } + + /// Creating a [Insert] operation with string, [s]. + pub fn insert(&mut self, s: &str, attributes: T) { + let s: OTString = s.into(); + if s.is_empty() { + return; } - #[inline] - pub fn with_capacity(capacity: usize) -> Self { - Self { - ops: Vec::with_capacity(capacity), - utf16_base_len: 0, - utf16_target_len: 0, - } + self.utf16_target_len += s.utf16_len(); + let new_last = match self.ops.as_mut_slice() { + [.., DeltaOperation::::Insert(insert)] => { + // + insert.merge_or_new_op(&s, attributes) + }, + [.., DeltaOperation::::Insert(pre_insert), DeltaOperation::Delete(_)] => { + // + pre_insert.merge_or_new_op(&s, attributes) + }, + [.., op_last @ DeltaOperation::::Delete(_)] => { + let new_last = op_last.clone(); + *op_last = DeltaOperation::::insert_with_attributes(&s, attributes); + Some(new_last) + }, + _ => Some(DeltaOperation::::insert_with_attributes(&s, attributes)), + }; + + match new_last { + None => {}, + Some(new_last) => self.ops.push(new_last), } + } - /// Adding an operation. It will be added in sequence. - pub fn add(&mut self, op: DeltaOperation) { - match op { - DeltaOperation::Delete(i) => self.delete(i), - DeltaOperation::Insert(i) => self.insert(&i.s, i.attributes), - DeltaOperation::Retain(r) => self.retain(r.n, r.attributes), - } + /// Creating a [Retain] operation with len, [n]. + pub fn retain(&mut self, n: usize, attributes: T) { + if n == 0 { + return; } + self.utf16_base_len += n as usize; + self.utf16_target_len += n as usize; - /// Creating a [Delete] operation with len [n] - pub fn delete(&mut self, n: usize) { - if n == 0 { - return; - } - self.utf16_base_len += n as usize; - if let Some(DeltaOperation::Delete(n_last)) = self.ops.last_mut() { - *n_last += n; - } else { - self.ops.push(DeltaOperation::delete(n)); - } + if let Some(DeltaOperation::::Retain(retain)) = self.ops.last_mut() { + if let Some(new_op) = retain.merge_or_new(n, attributes) { + self.ops.push(new_op); + } + } else { + self + .ops + .push(DeltaOperation::::retain_with_attributes(n, attributes)); } + } - /// Creating a [Insert] operation with string, [s]. - pub fn insert(&mut self, s: &str, attributes: T) { - let s: OTString = s.into(); - if s.is_empty() { - return; - } - - self.utf16_target_len += s.utf16_len(); - let new_last = match self.ops.as_mut_slice() { - [.., DeltaOperation::::Insert(insert)] => { - // - insert.merge_or_new_op(&s, attributes) - } - [.., DeltaOperation::::Insert(pre_insert), DeltaOperation::Delete(_)] => { - // - pre_insert.merge_or_new_op(&s, attributes) - } - [.., op_last @ DeltaOperation::::Delete(_)] => { - let new_last = op_last.clone(); - *op_last = DeltaOperation::::insert_with_attributes(&s, attributes); - Some(new_last) - } - _ => Some(DeltaOperation::::insert_with_attributes(&s, attributes)), - }; - - match new_last { - None => {} - Some(new_last) => self.ops.push(new_last), - } + /// Return the a new string described by this delta. The new string will contains the input string. + /// The length of the [applied_str] must be equal to the the [utf16_base_len]. + /// + /// # Arguments + /// + /// * `applied_str`: A string represents the utf16_base_len content. it will be consumed by the [retain] + /// or [delete] operations. + /// + /// + /// # Examples + /// + /// ``` + /// use lib_ot::core::DeltaBuilder; + /// let s = "hello"; + /// let delta_a = DeltaBuilder::new().insert(s).build(); + /// let delta_b = DeltaBuilder::new() + /// .retain(s.len()) + /// .insert(", AppFlowy") + /// .build(); + /// + /// let after_a = delta_a.content().unwrap(); + /// let after_b = delta_b.apply(&after_a).unwrap(); + /// assert_eq!("hello, AppFlowy", &after_b); + /// ``` + pub fn apply(&self, applied_str: &str) -> Result { + let applied_str: OTString = applied_str.into(); + if applied_str.utf16_len() != self.utf16_base_len { + return Err( + ErrorBuilder::new(OTErrorCode::IncompatibleLength) + .msg(format!( + "Expected: {}, but received: {}", + self.utf16_base_len, + applied_str.utf16_len() + )) + .build(), + ); } - - /// Creating a [Retain] operation with len, [n]. - pub fn retain(&mut self, n: usize, attributes: T) { - if n == 0 { - return; - } - self.utf16_base_len += n as usize; - self.utf16_target_len += n as usize; - - if let Some(DeltaOperation::::Retain(retain)) = self.ops.last_mut() { - if let Some(new_op) = retain.merge_or_new(n, attributes) { - self.ops.push(new_op); - } - } else { - self.ops - .push(DeltaOperation::::retain_with_attributes(n, attributes)); - } + let mut new_s = String::new(); + let code_point_iter = &mut applied_str.utf16_iter(); + for op in &self.ops { + match &op { + DeltaOperation::Retain(retain) => { + for c in code_point_iter.take(retain.n as usize) { + new_s.push_str(str::from_utf8(c.0).unwrap_or("")); + } + }, + DeltaOperation::Delete(delete) => { + for _ in 0..*delete { + code_point_iter.next(); + } + }, + DeltaOperation::Insert(insert) => { + new_s += &insert.s; + }, + } } + Ok(new_s) + } - /// Return the a new string described by this delta. The new string will contains the input string. - /// The length of the [applied_str] must be equal to the the [utf16_base_len]. - /// - /// # Arguments - /// - /// * `applied_str`: A string represents the utf16_base_len content. it will be consumed by the [retain] - /// or [delete] operations. - /// - /// - /// # Examples - /// - /// ``` - /// use lib_ot::core::DeltaBuilder; - /// let s = "hello"; - /// let delta_a = DeltaBuilder::new().insert(s).build(); - /// let delta_b = DeltaBuilder::new() - /// .retain(s.len()) - /// .insert(", AppFlowy") - /// .build(); - /// - /// let after_a = delta_a.content().unwrap(); - /// let after_b = delta_b.apply(&after_a).unwrap(); - /// assert_eq!("hello, AppFlowy", &after_b); - /// ``` - pub fn apply(&self, applied_str: &str) -> Result { - let applied_str: OTString = applied_str.into(); - if applied_str.utf16_len() != self.utf16_base_len { - return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength) - .msg(format!( - "Expected: {}, but received: {}", - self.utf16_base_len, - applied_str.utf16_len() - )) - .build()); - } - let mut new_s = String::new(); - let code_point_iter = &mut applied_str.utf16_iter(); - for op in &self.ops { - match &op { - DeltaOperation::Retain(retain) => { - for c in code_point_iter.take(retain.n as usize) { - new_s.push_str(str::from_utf8(c.0).unwrap_or("")); - } - } - DeltaOperation::Delete(delete) => { - for _ in 0..*delete { - code_point_iter.next(); - } - } - DeltaOperation::Insert(insert) => { - new_s += &insert.s; - } - } - } - Ok(new_s) + pub fn inverted(&self) -> Self { + self.invert_str("") + } + + /// Computes the inverse [Delta]. The inverse of an operation is the + /// operation that reverts the effects of the operation + /// # Arguments + /// + /// * `inverted_s`: A string represents the utf16_base_len content. The len of [inverted_s] + /// must equal to the [utf16_base_len], it will be consumed by the [retain] or [delete] operations. + /// + /// If the delta's operations just contain a insert operation. The inverted_s must be empty string. + /// + /// # Examples + /// + /// ``` + /// use lib_ot::core::DeltaBuilder; + /// let s = "hello world"; + /// let delta = DeltaBuilder::new().insert(s).build(); + /// let invert_delta = delta.invert_str(s); + /// assert_eq!(delta.utf16_base_len, invert_delta.utf16_target_len); + /// assert_eq!(delta.utf16_target_len, invert_delta.utf16_base_len); + /// + /// assert_eq!(invert_delta.apply(s).unwrap(), "") + /// + /// ``` + /// + pub fn invert_str(&self, inverted_s: &str) -> Self { + let mut inverted = DeltaOperations::default(); + let inverted_s: OTString = inverted_s.into(); + let code_point_iter = &mut inverted_s.utf16_iter(); + + for op in &self.ops { + match &op { + DeltaOperation::Retain(retain) => { + inverted.retain(retain.n, T::default()); + for _ in 0..retain.n { + code_point_iter.next(); + } + }, + DeltaOperation::Insert(insert) => { + inverted.delete(insert.utf16_size()); + }, + DeltaOperation::Delete(delete) => { + let bytes = code_point_iter + .take(*delete as usize) + .into_iter() + .flat_map(|a| str::from_utf8(a.0).ok()) + .collect::(); + + inverted.insert(&bytes, op.get_attributes()); + }, + } } + inverted + } - pub fn inverted(&self) -> Self { - self.invert_str("") - } + /// Return true if the delta doesn't contain any [Insert] or [Delete] operations. + pub fn is_noop(&self) -> bool { + matches!(self.ops.as_slice(), [] | [DeltaOperation::Retain(_)]) + } - /// Computes the inverse [Delta]. The inverse of an operation is the - /// operation that reverts the effects of the operation - /// # Arguments - /// - /// * `inverted_s`: A string represents the utf16_base_len content. The len of [inverted_s] - /// must equal to the [utf16_base_len], it will be consumed by the [retain] or [delete] operations. - /// - /// If the delta's operations just contain a insert operation. The inverted_s must be empty string. - /// - /// # Examples - /// - /// ``` - /// use lib_ot::core::DeltaBuilder; - /// let s = "hello world"; - /// let delta = DeltaBuilder::new().insert(s).build(); - /// let invert_delta = delta.invert_str(s); - /// assert_eq!(delta.utf16_base_len, invert_delta.utf16_target_len); - /// assert_eq!(delta.utf16_target_len, invert_delta.utf16_base_len); - /// - /// assert_eq!(invert_delta.apply(s).unwrap(), "") - /// - /// ``` - /// - pub fn invert_str(&self, inverted_s: &str) -> Self { - let mut inverted = DeltaOperations::default(); - let inverted_s: OTString = inverted_s.into(); - let code_point_iter = &mut inverted_s.utf16_iter(); + pub fn is_empty(&self) -> bool { + self.ops.is_empty() + } - for op in &self.ops { - match &op { - DeltaOperation::Retain(retain) => { - inverted.retain(retain.n, T::default()); - for _ in 0..retain.n { - code_point_iter.next(); - } - } - DeltaOperation::Insert(insert) => { - inverted.delete(insert.utf16_size()); - } - DeltaOperation::Delete(delete) => { - let bytes = code_point_iter - .take(*delete as usize) - .into_iter() - .flat_map(|a| str::from_utf8(a.0).ok()) - .collect::(); + pub fn extend(&mut self, other: Self) { + other.ops.into_iter().for_each(|op| self.add(op)); + } - inverted.insert(&bytes, op.get_attributes()); - } - } - } - inverted - } - - /// Return true if the delta doesn't contain any [Insert] or [Delete] operations. - pub fn is_noop(&self) -> bool { - matches!(self.ops.as_slice(), [] | [DeltaOperation::Retain(_)]) - } - - pub fn is_empty(&self) -> bool { - self.ops.is_empty() - } - - pub fn extend(&mut self, other: Self) { - other.ops.into_iter().for_each(|op| self.add(op)); - } - - /// Get the content that the [Delta] represents. - pub fn content(&self) -> Result { - self.apply("") - } + /// Get the content that the [Delta] represents. + pub fn content(&self) -> Result { + self.apply("") + } } impl OperationTransform for DeltaOperations where - T: OperationAttributes, + T: OperationAttributes, { - fn compose(&self, other: &Self) -> Result - where - Self: Sized, - { - let mut new_delta = DeltaOperations::default(); - let mut iter = OperationIterator::new(self); - let mut other_iter = OperationIterator::new(other); + fn compose(&self, other: &Self) -> Result + where + Self: Sized, + { + let mut new_delta = DeltaOperations::default(); + let mut iter = OperationIterator::new(self); + let mut other_iter = OperationIterator::new(other); - while iter.has_next() || other_iter.has_next() { - if other_iter.is_next_insert() { - new_delta.add(other_iter.next_op().unwrap()); - continue; - } + while iter.has_next() || other_iter.has_next() { + if other_iter.is_next_insert() { + new_delta.add(other_iter.next_op().unwrap()); + continue; + } - if iter.is_next_delete() { - new_delta.add(iter.next_op().unwrap()); - continue; - } + if iter.is_next_delete() { + new_delta.add(iter.next_op().unwrap()); + continue; + } - let length = min( - iter.next_op_len().unwrap_or(MAX_IV_LEN), - other_iter.next_op_len().unwrap_or(MAX_IV_LEN), - ); + let length = min( + iter.next_op_len().unwrap_or(MAX_IV_LEN), + other_iter.next_op_len().unwrap_or(MAX_IV_LEN), + ); - let op = iter - .next_op_with_len(length) - .unwrap_or_else(|| DeltaOperation::retain(length)); - let other_op = other_iter - .next_op_with_len(length) - .unwrap_or_else(|| DeltaOperation::retain(length)); + let op = iter + .next_op_with_len(length) + .unwrap_or_else(|| DeltaOperation::retain(length)); + let other_op = other_iter + .next_op_with_len(length) + .unwrap_or_else(|| DeltaOperation::retain(length)); - // debug_assert_eq!(op.len(), other_op.len(), "Composing delta failed,"); + // debug_assert_eq!(op.len(), other_op.len(), "Composing delta failed,"); - match (&op, &other_op) { - (DeltaOperation::Retain(retain), DeltaOperation::Retain(other_retain)) => { - let composed_attrs = retain.attributes.compose(&other_retain.attributes)?; + match (&op, &other_op) { + (DeltaOperation::Retain(retain), DeltaOperation::Retain(other_retain)) => { + let composed_attrs = retain.attributes.compose(&other_retain.attributes)?; - new_delta.add(DeltaOperation::retain_with_attributes(retain.n, composed_attrs)) - } - (DeltaOperation::Insert(insert), DeltaOperation::Retain(other_retain)) => { - let mut composed_attrs = insert.attributes.compose(&other_retain.attributes)?; - composed_attrs.remove(); - new_delta.add(DeltaOperation::insert_with_attributes(op.get_data(), composed_attrs)) - } - (DeltaOperation::Retain(_), DeltaOperation::Delete(_)) => { - new_delta.add(other_op); - } - (a, b) => { - debug_assert!(a.is_insert()); - debug_assert!(b.is_delete()); - continue; - } - } - } - Ok(new_delta) + new_delta.add(DeltaOperation::retain_with_attributes( + retain.n, + composed_attrs, + )) + }, + (DeltaOperation::Insert(insert), DeltaOperation::Retain(other_retain)) => { + let mut composed_attrs = insert.attributes.compose(&other_retain.attributes)?; + composed_attrs.remove(); + new_delta.add(DeltaOperation::insert_with_attributes( + op.get_data(), + composed_attrs, + )) + }, + (DeltaOperation::Retain(_), DeltaOperation::Delete(_)) => { + new_delta.add(other_op); + }, + (a, b) => { + debug_assert!(a.is_insert()); + debug_assert!(b.is_delete()); + continue; + }, + } + } + Ok(new_delta) + } + + fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> + where + Self: Sized, + { + if self.utf16_base_len != other.utf16_base_len { + return Err( + ErrorBuilder::new(OTErrorCode::IncompatibleLength) + .msg(format!( + "cur base length: {}, other base length: {}", + self.utf16_base_len, other.utf16_base_len + )) + .build(), + ); } - fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> - where - Self: Sized, - { - if self.utf16_base_len != other.utf16_base_len { - return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength) - .msg(format!( - "cur base length: {}, other base length: {}", - self.utf16_base_len, other.utf16_base_len - )) - .build()); - } + let mut a_prime = DeltaOperations::default(); + let mut b_prime = DeltaOperations::default(); - let mut a_prime = DeltaOperations::default(); - let mut b_prime = DeltaOperations::default(); + let mut ops1 = self.ops.iter().cloned(); + let mut ops2 = other.ops.iter().cloned(); - let mut ops1 = self.ops.iter().cloned(); - let mut ops2 = other.ops.iter().cloned(); - - let mut next_op1 = ops1.next(); - let mut next_op2 = ops2.next(); - loop { - match (&next_op1, &next_op2) { - (None, None) => break, - (Some(DeltaOperation::Insert(insert)), _) => { - // let composed_attrs = transform_attributes(&next_op1, &next_op2, true); - a_prime.insert(&insert.s, insert.attributes.clone()); - b_prime.retain(insert.utf16_size(), insert.attributes.clone()); - next_op1 = ops1.next(); - } - (_, Some(DeltaOperation::Insert(o_insert))) => { - let composed_attrs = transform_op_attribute(&next_op1, &next_op2)?; - a_prime.retain(o_insert.utf16_size(), composed_attrs.clone()); - b_prime.insert(&o_insert.s, composed_attrs); - next_op2 = ops2.next(); - } - (None, _) => { - return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build()); - } - (_, None) => { - return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build()); - } - (Some(DeltaOperation::Retain(retain)), Some(DeltaOperation::Retain(o_retain))) => { - let composed_attrs = transform_op_attribute(&next_op1, &next_op2)?; - match retain.cmp(o_retain) { - Ordering::Less => { - a_prime.retain(retain.n, composed_attrs.clone()); - b_prime.retain(retain.n, composed_attrs.clone()); - next_op2 = Some(DeltaOperation::retain(o_retain.n - retain.n)); - next_op1 = ops1.next(); - } - Ordering::Equal => { - a_prime.retain(retain.n, composed_attrs.clone()); - b_prime.retain(retain.n, composed_attrs.clone()); - next_op1 = ops1.next(); - next_op2 = ops2.next(); - } - Ordering::Greater => { - a_prime.retain(o_retain.n, composed_attrs.clone()); - b_prime.retain(o_retain.n, composed_attrs.clone()); - next_op1 = Some(DeltaOperation::retain(retain.n - o_retain.n)); - next_op2 = ops2.next(); - } - }; - } - (Some(DeltaOperation::Delete(i)), Some(DeltaOperation::Delete(j))) => match i.cmp(j) { - Ordering::Less => { - next_op2 = Some(DeltaOperation::delete(*j - *i)); - next_op1 = ops1.next(); - } - Ordering::Equal => { - next_op1 = ops1.next(); - next_op2 = ops2.next(); - } - Ordering::Greater => { - next_op1 = Some(DeltaOperation::delete(*i - *j)); - next_op2 = ops2.next(); - } - }, - (Some(DeltaOperation::Delete(i)), Some(DeltaOperation::Retain(o_retain))) => { - match i.cmp(o_retain) { - Ordering::Less => { - a_prime.delete(*i); - next_op2 = Some(DeltaOperation::retain(o_retain.n - *i)); - next_op1 = ops1.next(); - } - Ordering::Equal => { - a_prime.delete(*i); - next_op1 = ops1.next(); - next_op2 = ops2.next(); - } - Ordering::Greater => { - a_prime.delete(o_retain.n); - next_op1 = Some(DeltaOperation::delete(*i - o_retain.n)); - next_op2 = ops2.next(); - } - }; - } - (Some(DeltaOperation::Retain(retain)), Some(DeltaOperation::Delete(j))) => { - match retain.cmp(j) { - Ordering::Less => { - b_prime.delete(retain.n); - next_op2 = Some(DeltaOperation::delete(*j - retain.n)); - next_op1 = ops1.next(); - } - Ordering::Equal => { - b_prime.delete(retain.n); - next_op1 = ops1.next(); - next_op2 = ops2.next(); - } - Ordering::Greater => { - b_prime.delete(*j); - next_op1 = Some(DeltaOperation::retain(retain.n - *j)); - next_op2 = ops2.next(); - } - }; - } - } - } - Ok((a_prime, b_prime)) + let mut next_op1 = ops1.next(); + let mut next_op2 = ops2.next(); + loop { + match (&next_op1, &next_op2) { + (None, None) => break, + (Some(DeltaOperation::Insert(insert)), _) => { + // let composed_attrs = transform_attributes(&next_op1, &next_op2, true); + a_prime.insert(&insert.s, insert.attributes.clone()); + b_prime.retain(insert.utf16_size(), insert.attributes.clone()); + next_op1 = ops1.next(); + }, + (_, Some(DeltaOperation::Insert(o_insert))) => { + let composed_attrs = transform_op_attribute(&next_op1, &next_op2)?; + a_prime.retain(o_insert.utf16_size(), composed_attrs.clone()); + b_prime.insert(&o_insert.s, composed_attrs); + next_op2 = ops2.next(); + }, + (None, _) => { + return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build()); + }, + (_, None) => { + return Err(ErrorBuilder::new(OTErrorCode::IncompatibleLength).build()); + }, + (Some(DeltaOperation::Retain(retain)), Some(DeltaOperation::Retain(o_retain))) => { + let composed_attrs = transform_op_attribute(&next_op1, &next_op2)?; + match retain.cmp(o_retain) { + Ordering::Less => { + a_prime.retain(retain.n, composed_attrs.clone()); + b_prime.retain(retain.n, composed_attrs.clone()); + next_op2 = Some(DeltaOperation::retain(o_retain.n - retain.n)); + next_op1 = ops1.next(); + }, + Ordering::Equal => { + a_prime.retain(retain.n, composed_attrs.clone()); + b_prime.retain(retain.n, composed_attrs.clone()); + next_op1 = ops1.next(); + next_op2 = ops2.next(); + }, + Ordering::Greater => { + a_prime.retain(o_retain.n, composed_attrs.clone()); + b_prime.retain(o_retain.n, composed_attrs.clone()); + next_op1 = Some(DeltaOperation::retain(retain.n - o_retain.n)); + next_op2 = ops2.next(); + }, + }; + }, + (Some(DeltaOperation::Delete(i)), Some(DeltaOperation::Delete(j))) => match i.cmp(j) { + Ordering::Less => { + next_op2 = Some(DeltaOperation::delete(*j - *i)); + next_op1 = ops1.next(); + }, + Ordering::Equal => { + next_op1 = ops1.next(); + next_op2 = ops2.next(); + }, + Ordering::Greater => { + next_op1 = Some(DeltaOperation::delete(*i - *j)); + next_op2 = ops2.next(); + }, + }, + (Some(DeltaOperation::Delete(i)), Some(DeltaOperation::Retain(o_retain))) => { + match i.cmp(o_retain) { + Ordering::Less => { + a_prime.delete(*i); + next_op2 = Some(DeltaOperation::retain(o_retain.n - *i)); + next_op1 = ops1.next(); + }, + Ordering::Equal => { + a_prime.delete(*i); + next_op1 = ops1.next(); + next_op2 = ops2.next(); + }, + Ordering::Greater => { + a_prime.delete(o_retain.n); + next_op1 = Some(DeltaOperation::delete(*i - o_retain.n)); + next_op2 = ops2.next(); + }, + }; + }, + (Some(DeltaOperation::Retain(retain)), Some(DeltaOperation::Delete(j))) => { + match retain.cmp(j) { + Ordering::Less => { + b_prime.delete(retain.n); + next_op2 = Some(DeltaOperation::delete(*j - retain.n)); + next_op1 = ops1.next(); + }, + Ordering::Equal => { + b_prime.delete(retain.n); + next_op1 = ops1.next(); + next_op2 = ops2.next(); + }, + Ordering::Greater => { + b_prime.delete(*j); + next_op1 = Some(DeltaOperation::retain(retain.n - *j)); + next_op2 = ops2.next(); + }, + }; + }, + } } + Ok((a_prime, b_prime)) + } - fn invert(&self, other: &Self) -> Self { - let mut inverted = DeltaOperations::default(); - let mut index = 0; - for op in &self.ops { - let len: usize = op.len() as usize; - match op { - DeltaOperation::Delete(n) => { - invert_other(&mut inverted, other, op, index, index + *n); - index += len; - } - DeltaOperation::Retain(_) => { - match op.has_attribute() { - true => invert_other(&mut inverted, other, op, index, index + len), - false => { - // tracing::trace!("invert retain: {} by retain {} {}", op, len, - // op.get_attributes()); - inverted.retain(len as usize, op.get_attributes()) - } - } - index += len; - } - DeltaOperation::Insert(_) => { - // tracing::trace!("invert insert: {} by delete {}", op, len); - inverted.delete(len as usize); - } - } - } - inverted + fn invert(&self, other: &Self) -> Self { + let mut inverted = DeltaOperations::default(); + let mut index = 0; + for op in &self.ops { + let len: usize = op.len() as usize; + match op { + DeltaOperation::Delete(n) => { + invert_other(&mut inverted, other, op, index, index + *n); + index += len; + }, + DeltaOperation::Retain(_) => { + match op.has_attribute() { + true => invert_other(&mut inverted, other, op, index, index + len), + false => { + // tracing::trace!("invert retain: {} by retain {} {}", op, len, + // op.get_attributes()); + inverted.retain(len as usize, op.get_attributes()) + }, + } + index += len; + }, + DeltaOperation::Insert(_) => { + // tracing::trace!("invert insert: {} by delete {}", op, len); + inverted.delete(len as usize); + }, + } } + inverted + } } /// Removes trailing retain operation with empty attributes, if present. pub fn trim(delta: &mut DeltaOperations) where - T: OperationAttributes, + T: OperationAttributes, { - if let Some(last) = delta.ops.last() { - if last.is_retain() && last.is_plain() { - delta.ops.pop(); - } + if let Some(last) = delta.ops.last() { + if last.is_retain() && last.is_plain() { + delta.ops.pop(); } + } } fn invert_other( - base: &mut DeltaOperations, - other: &DeltaOperations, - operation: &DeltaOperation, - start: usize, - end: usize, + base: &mut DeltaOperations, + other: &DeltaOperations, + operation: &DeltaOperation, + start: usize, + end: usize, ) { - tracing::trace!("invert op: {} [{}:{}]", operation, start, end); - let other_ops = OperationIterator::from_interval(other, Interval::new(start, end)).ops(); - other_ops.into_iter().for_each(|other_op| match operation { - DeltaOperation::Delete(_n) => { - // tracing::trace!("invert delete: {} by add {}", n, other_op); - base.add(other_op); - } - DeltaOperation::Retain(_retain) => { - tracing::trace!( - "invert attributes: {:?}, {:?}", - operation.get_attributes(), - other_op.get_attributes() - ); - let inverted_attrs = operation.get_attributes().invert(&other_op.get_attributes()); - base.retain(other_op.len(), inverted_attrs); - } - DeltaOperation::Insert(_) => { - log::error!("Impossible to here. Insert operation should be treated as delete") - } - }); + tracing::trace!("invert op: {} [{}:{}]", operation, start, end); + let other_ops = OperationIterator::from_interval(other, Interval::new(start, end)).ops(); + other_ops.into_iter().for_each(|other_op| match operation { + DeltaOperation::Delete(_n) => { + // tracing::trace!("invert delete: {} by add {}", n, other_op); + base.add(other_op); + }, + DeltaOperation::Retain(_retain) => { + tracing::trace!( + "invert attributes: {:?}, {:?}", + operation.get_attributes(), + other_op.get_attributes() + ); + let inverted_attrs = operation + .get_attributes() + .invert(&other_op.get_attributes()); + base.retain(other_op.len(), inverted_attrs); + }, + DeltaOperation::Insert(_) => { + log::error!("Impossible to here. Insert operation should be treated as delete") + }, + }); } fn transform_op_attribute( - left: &Option>, - right: &Option>, + left: &Option>, + right: &Option>, ) -> Result { - if left.is_none() { - if right.is_none() { - return Ok(T::default()); - } - return Ok(right.as_ref().unwrap().get_attributes()); + if left.is_none() { + if right.is_none() { + return Ok(T::default()); } - let left = left.as_ref().unwrap().get_attributes(); - let right = right.as_ref().unwrap().get_attributes(); - // TODO: replace with anyhow and this error. - Ok(left.transform(&right)?.0) + return Ok(right.as_ref().unwrap().get_attributes()); + } + let left = left.as_ref().unwrap().get_attributes(); + let right = right.as_ref().unwrap().get_attributes(); + // TODO: replace with anyhow and this error. + Ok(left.transform(&right)?.0) } impl DeltaOperations where - T: OperationAttributes + DeserializeOwned, + T: OperationAttributes + DeserializeOwned, { - /// # Examples - /// - /// ``` - /// use lib_ot::core::DeltaOperationBuilder; - /// use lib_ot::text_delta::{DeltaTextOperations}; - /// let json = r#"[ - /// {"retain":7,"attributes":{"bold":null}} - /// ]"#; - /// let delta = DeltaTextOperations::from_json(json).unwrap(); - /// assert_eq!(delta.json_str(), r#"[{"retain":7,"attributes":{"bold":null}}]"#); - /// ``` - pub fn from_json(json: &str) -> Result { - let delta = serde_json::from_str(json).map_err(|e| { - tracing::trace!("Deserialize failed: {:?}", e); - tracing::trace!("{:?}", json); - e - })?; - Ok(delta) - } + /// # Examples + /// + /// ``` + /// use lib_ot::core::DeltaOperationBuilder; + /// use lib_ot::text_delta::{DeltaTextOperations}; + /// let json = r#"[ + /// {"retain":7,"attributes":{"bold":null}} + /// ]"#; + /// let delta = DeltaTextOperations::from_json(json).unwrap(); + /// assert_eq!(delta.json_str(), r#"[{"retain":7,"attributes":{"bold":null}}]"#); + /// ``` + pub fn from_json(json: &str) -> Result { + let delta = serde_json::from_str(json).map_err(|e| { + tracing::trace!("Deserialize failed: {:?}", e); + tracing::trace!("{:?}", json); + e + })?; + Ok(delta) + } - /// Deserialize the bytes into [Delta]. It requires the bytes is in utf8 format. - pub fn from_bytes>(bytes: B) -> Result { - let json = str::from_utf8(bytes.as_ref())?.to_owned(); - let val = Self::from_json(&json)?; - Ok(val) - } + /// Deserialize the bytes into [Delta]. It requires the bytes is in utf8 format. + pub fn from_bytes>(bytes: B) -> Result { + let json = str::from_utf8(bytes.as_ref())?.to_owned(); + let val = Self::from_json(&json)?; + Ok(val) + } } impl DeltaOperations where - T: OperationAttributes + serde::Serialize, + T: OperationAttributes + serde::Serialize, { - /// Serialize the [Delta] into a String in JSON format - pub fn json_str(&self) -> String { - serde_json::to_string(self).unwrap_or_else(|_| "".to_owned()) - } + /// Serialize the [Delta] into a String in JSON format + pub fn json_str(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|_| "".to_owned()) + } - /// Serial the [Delta] into a String in Bytes format - pub fn json_bytes(&self) -> Bytes { - let json = self.json_str(); - Bytes::from(json.into_bytes()) - } + /// Serial the [Delta] into a String in Bytes format + pub fn json_bytes(&self) -> Bytes { + let json = self.json_str(); + Bytes::from(json.into_bytes()) + } } impl FromStr for DeltaOperations where - T: OperationAttributes, + T: OperationAttributes, { - type Err = (); + type Err = (); - fn from_str(s: &str) -> Result, Self::Err> { - let mut delta = DeltaOperations::with_capacity(1); - delta.add(DeltaOperation::Insert(s.into())); - Ok(delta) - } + fn from_str(s: &str) -> Result, Self::Err> { + let mut delta = DeltaOperations::with_capacity(1); + delta.add(DeltaOperation::Insert(s.into())); + Ok(delta) + } } impl std::convert::TryFrom> for DeltaOperations where - T: OperationAttributes + DeserializeOwned, + T: OperationAttributes + DeserializeOwned, { - type Error = OTError; - fn try_from(bytes: Vec) -> Result { - DeltaOperations::from_bytes(bytes) - } + type Error = OTError; + fn try_from(bytes: Vec) -> Result { + DeltaOperations::from_bytes(bytes) + } } impl std::convert::TryFrom for DeltaOperations where - T: OperationAttributes + DeserializeOwned, + T: OperationAttributes + DeserializeOwned, { - type Error = OTError; + type Error = OTError; - fn try_from(bytes: Bytes) -> Result { - DeltaOperations::from_bytes(&bytes) - } + fn try_from(bytes: Bytes) -> Result { + DeltaOperations::from_bytes(&bytes) + } } diff --git a/shared-lib/lib-ot/src/core/delta/ops_serde.rs b/shared-lib/lib-ot/src/core/delta/ops_serde.rs index 3b7cd29087..7837b49ced 100644 --- a/shared-lib/lib-ot/src/core/delta/ops_serde.rs +++ b/shared-lib/lib-ot/src/core/delta/ops_serde.rs @@ -2,61 +2,61 @@ use crate::core::delta::operation::OperationAttributes; use crate::core::delta::DeltaOperations; use serde::{ - de::{SeqAccess, Visitor}, - ser::SerializeSeq, - Deserialize, Deserializer, Serialize, Serializer, + de::{SeqAccess, Visitor}, + ser::SerializeSeq, + Deserialize, Deserializer, Serialize, Serializer, }; use std::{fmt, marker::PhantomData}; impl Serialize for DeltaOperations where - T: OperationAttributes + Serialize, + T: OperationAttributes + Serialize, { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut seq = serializer.serialize_seq(Some(self.ops.len()))?; - for op in self.ops.iter() { - seq.serialize_element(op)?; - } - seq.end() + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.ops.len()))?; + for op in self.ops.iter() { + seq.serialize_element(op)?; } + seq.end() + } } impl<'de, T> Deserialize<'de> for DeltaOperations where - T: OperationAttributes + Deserialize<'de>, + T: OperationAttributes + Deserialize<'de>, { - fn deserialize(deserializer: D) -> Result, D::Error> + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + struct OperationSeqVisitor(PhantomData T>); + + impl<'de, T> Visitor<'de> for OperationSeqVisitor where - D: Deserializer<'de>, + T: OperationAttributes + Deserialize<'de>, { - struct OperationSeqVisitor(PhantomData T>); + type Value = DeltaOperations; - impl<'de, T> Visitor<'de> for OperationSeqVisitor - where - T: OperationAttributes + Deserialize<'de>, - { - type Value = DeltaOperations; + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a sequence") + } - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a sequence") - } - - #[inline] - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let mut o = DeltaOperations::default(); - while let Some(op) = seq.next_element()? { - o.add(op); - } - Ok(o) - } + #[inline] + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut o = DeltaOperations::default(); + while let Some(op) = seq.next_element()? { + o.add(op); } - - deserializer.deserialize_seq(OperationSeqVisitor(PhantomData)) + Ok(o) + } } + + deserializer.deserialize_seq(OperationSeqVisitor(PhantomData)) + } } diff --git a/shared-lib/lib-ot/src/core/interval.rs b/shared-lib/lib-ot/src/core/interval.rs index 6bedf785e8..ca5f0b9f10 100644 --- a/shared-lib/lib-ot/src/core/interval.rs +++ b/shared-lib/lib-ot/src/core/interval.rs @@ -1,8 +1,8 @@ use serde::{Deserialize, Serialize}; use std::{ - cmp::{max, min}, - fmt, - ops::{Range, RangeInclusive, RangeTo, RangeToInclusive}, + cmp::{max, min}, + fmt, + ops::{Range, RangeInclusive, RangeTo, RangeToInclusive}, }; /// Representing a closed-open range; @@ -12,221 +12,232 @@ use std::{ /// considered empty. #[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct Interval { - pub start: usize, - pub end: usize, + pub start: usize, + pub end: usize, } impl Interval { - /// Construct a new `Interval` representing the range [start..end). - /// It is an invariant that `start <= end`. - pub fn new(start: usize, end: usize) -> Interval { - debug_assert!(start <= end); - Interval { start, end } - } + /// Construct a new `Interval` representing the range [start..end). + /// It is an invariant that `start <= end`. + pub fn new(start: usize, end: usize) -> Interval { + debug_assert!(start <= end); + Interval { start, end } + } - pub fn start(&self) -> usize { - self.start - } + pub fn start(&self) -> usize { + self.start + } - pub fn end(&self) -> usize { - self.end - } + pub fn end(&self) -> usize { + self.end + } - pub fn start_end(&self) -> (usize, usize) { - (self.start, self.end) - } + pub fn start_end(&self) -> (usize, usize) { + (self.start, self.end) + } - pub fn is_before(&self, val: usize) -> bool { - self.end <= val - } + pub fn is_before(&self, val: usize) -> bool { + self.end <= val + } - pub fn contains(&self, val: usize) -> bool { - self.start <= val && val < self.end - } + pub fn contains(&self, val: usize) -> bool { + self.start <= val && val < self.end + } - pub fn contains_range(&self, start: usize, end: usize) -> bool { - !self.intersect(Interval::new(start, end)).is_empty() - } + pub fn contains_range(&self, start: usize, end: usize) -> bool { + !self.intersect(Interval::new(start, end)).is_empty() + } - pub fn is_after(&self, val: usize) -> bool { - self.start > val - } + pub fn is_after(&self, val: usize) -> bool { + self.start > val + } - pub fn is_empty(&self) -> bool { - self.end <= self.start - } + pub fn is_empty(&self) -> bool { + self.end <= self.start + } - pub fn intersect(&self, other: Interval) -> Interval { - let start = max(self.start, other.start); - let end = min(self.end, other.end); - Interval { - start, - end: max(start, end), - } + pub fn intersect(&self, other: Interval) -> Interval { + let start = max(self.start, other.start); + let end = min(self.end, other.end); + Interval { + start, + end: max(start, end), } + } - // the first half of self - other - pub fn prefix(&self, other: Interval) -> Interval { - Interval { - start: min(self.start, other.start), - end: min(self.end, other.start), - } + // the first half of self - other + pub fn prefix(&self, other: Interval) -> Interval { + Interval { + start: min(self.start, other.start), + end: min(self.end, other.start), } + } - // the second half of self - other - pub fn suffix(&self, other: Interval) -> Interval { - Interval { - start: max(self.start, other.end), - end: max(self.end, other.end), - } + // the second half of self - other + pub fn suffix(&self, other: Interval) -> Interval { + Interval { + start: max(self.start, other.end), + end: max(self.end, other.end), } + } - pub fn translate(&self, amount: usize) -> Interval { - Interval { - start: self.start + amount, - end: self.end + amount, - } + pub fn translate(&self, amount: usize) -> Interval { + Interval { + start: self.start + amount, + end: self.end + amount, } + } - pub fn translate_neg(&self, amount: usize) -> Interval { - debug_assert!(self.start >= amount); - Interval { - start: self.start - amount, - end: self.end - amount, - } + pub fn translate_neg(&self, amount: usize) -> Interval { + debug_assert!(self.start >= amount); + Interval { + start: self.start - amount, + end: self.end - amount, } + } - pub fn union(&self, other: Interval) -> Interval { - if self.is_empty() { - return other; - } - if other.is_empty() { - return *self; - } - let start = min(self.start, other.start); - let end = max(self.end, other.end); - Interval { start, end } + pub fn union(&self, other: Interval) -> Interval { + if self.is_empty() { + return other; } + if other.is_empty() { + return *self; + } + let start = min(self.start, other.start); + let end = max(self.end, other.end); + Interval { start, end } + } - pub fn size(&self) -> usize { - self.end - self.start - } + pub fn size(&self) -> usize { + self.end - self.start + } } impl std::default::Default for Interval { - fn default() -> Self { - Interval::new(0, 0) - } + fn default() -> Self { + Interval::new(0, 0) + } } impl fmt::Display for Interval { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "[{}, {})", self.start(), self.end()) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[{}, {})", self.start(), self.end()) + } } impl fmt::Debug for Interval { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self, f) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } } impl From> for Interval { - fn from(src: Range) -> Interval { - let Range { start, end } = src; - Interval { start, end } - } + fn from(src: Range) -> Interval { + let Range { start, end } = src; + Interval { start, end } + } } impl From> for Interval { - fn from(src: RangeTo) -> Interval { - Interval::new(0, src.end) - } + fn from(src: RangeTo) -> Interval { + Interval::new(0, src.end) + } } impl From> for Interval { - fn from(src: RangeInclusive) -> Interval { - Interval::new(*src.start(), src.end().saturating_add(1)) - } + fn from(src: RangeInclusive) -> Interval { + Interval::new(*src.start(), src.end().saturating_add(1)) + } } impl From> for Interval { - fn from(src: RangeToInclusive) -> Interval { - Interval::new(0, src.end.saturating_add(1)) - } + fn from(src: RangeToInclusive) -> Interval { + Interval::new(0, src.end.saturating_add(1)) + } } #[cfg(test)] mod tests { - use crate::core::interval::Interval; + use crate::core::interval::Interval; - #[test] - fn contains() { - let i = Interval::new(2, 42); - assert!(!i.contains(1)); - assert!(i.contains(2)); - assert!(i.contains(3)); - assert!(i.contains(41)); - assert!(!i.contains(42)); - assert!(!i.contains(43)); - } + #[test] + fn contains() { + let i = Interval::new(2, 42); + assert!(!i.contains(1)); + assert!(i.contains(2)); + assert!(i.contains(3)); + assert!(i.contains(41)); + assert!(!i.contains(42)); + assert!(!i.contains(43)); + } - #[test] - fn before() { - let i = Interval::new(2, 42); - assert!(!i.is_before(1)); - assert!(!i.is_before(2)); - assert!(!i.is_before(3)); - assert!(!i.is_before(41)); - assert!(i.is_before(42)); - assert!(i.is_before(43)); - } + #[test] + fn before() { + let i = Interval::new(2, 42); + assert!(!i.is_before(1)); + assert!(!i.is_before(2)); + assert!(!i.is_before(3)); + assert!(!i.is_before(41)); + assert!(i.is_before(42)); + assert!(i.is_before(43)); + } - #[test] - fn after() { - let i = Interval::new(2, 42); - assert!(i.is_after(1)); - assert!(!i.is_after(2)); - assert!(!i.is_after(3)); - assert!(!i.is_after(41)); - assert!(!i.is_after(42)); - assert!(!i.is_after(43)); - } + #[test] + fn after() { + let i = Interval::new(2, 42); + assert!(i.is_after(1)); + assert!(!i.is_after(2)); + assert!(!i.is_after(3)); + assert!(!i.is_after(41)); + assert!(!i.is_after(42)); + assert!(!i.is_after(43)); + } - #[test] - fn translate() { - let i = Interval::new(2, 42); - assert_eq!(Interval::new(5, 45), i.translate(3)); - assert_eq!(Interval::new(1, 41), i.translate_neg(1)); - } + #[test] + fn translate() { + let i = Interval::new(2, 42); + assert_eq!(Interval::new(5, 45), i.translate(3)); + assert_eq!(Interval::new(1, 41), i.translate_neg(1)); + } - #[test] - fn empty() { - assert!(Interval::new(0, 0).is_empty()); - assert!(Interval::new(1, 1).is_empty()); - assert!(!Interval::new(1, 2).is_empty()); - } + #[test] + fn empty() { + assert!(Interval::new(0, 0).is_empty()); + assert!(Interval::new(1, 1).is_empty()); + assert!(!Interval::new(1, 2).is_empty()); + } - #[test] - fn intersect() { - assert_eq!(Interval::new(2, 3), Interval::new(1, 3).intersect(Interval::new(2, 4))); - assert!(Interval::new(1, 2).intersect(Interval::new(2, 43)).is_empty()); - } + #[test] + fn intersect() { + assert_eq!( + Interval::new(2, 3), + Interval::new(1, 3).intersect(Interval::new(2, 4)) + ); + assert!(Interval::new(1, 2) + .intersect(Interval::new(2, 43)) + .is_empty()); + } - #[test] - fn prefix() { - assert_eq!(Interval::new(1, 2), Interval::new(1, 4).prefix(Interval::new(2, 3))); - } + #[test] + fn prefix() { + assert_eq!( + Interval::new(1, 2), + Interval::new(1, 4).prefix(Interval::new(2, 3)) + ); + } - #[test] - fn suffix() { - assert_eq!(Interval::new(3, 4), Interval::new(1, 4).suffix(Interval::new(2, 3))); - } + #[test] + fn suffix() { + assert_eq!( + Interval::new(3, 4), + Interval::new(1, 4).suffix(Interval::new(2, 3)) + ); + } - #[test] - fn size() { - assert_eq!(40, Interval::new(2, 42).size()); - assert_eq!(0, Interval::new(1, 1).size()); - assert_eq!(1, Interval::new(1, 2).size()); - } + #[test] + fn size() { + assert_eq!(40, Interval::new(2, 42).size()); + assert_eq!(0, Interval::new(1, 1).size()); + assert_eq!(1, Interval::new(1, 2).size()); + } } diff --git a/shared-lib/lib-ot/src/core/node_tree/node.rs b/shared-lib/lib-ot/src/core/node_tree/node.rs index aae7ac97bc..018dd64b8c 100644 --- a/shared-lib/lib-ot/src/core/node_tree/node.rs +++ b/shared-lib/lib-ot/src/core/node_tree/node.rs @@ -7,55 +7,55 @@ use crate::text_delta::DeltaTextOperations; use serde::{Deserialize, Serialize}; pub trait ToNodeData: Send + Sync { - fn to_node_data(&self) -> NodeData; + fn to_node_data(&self) -> NodeData; } impl ToNodeData for Box where - T: ToNodeData, + T: ToNodeData, { - fn to_node_data(&self) -> NodeData { - (**self).to_node_data() - } + fn to_node_data(&self) -> NodeData { + (**self).to_node_data() + } } #[derive(Default, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub struct NodeData { - #[serde(rename = "type")] - pub node_type: String, + #[serde(rename = "type")] + pub node_type: String, - #[serde(skip_serializing_if = "AttributeHashMap::is_empty")] - #[serde(default)] - pub attributes: AttributeHashMap, + #[serde(skip_serializing_if = "AttributeHashMap::is_empty")] + #[serde(default)] + pub attributes: AttributeHashMap, - #[serde(serialize_with = "serialize_body")] - #[serde(deserialize_with = "deserialize_body")] - #[serde(skip_serializing_if = "Body::is_empty")] - #[serde(default)] - pub body: Body, + #[serde(serialize_with = "serialize_body")] + #[serde(deserialize_with = "deserialize_body")] + #[serde(skip_serializing_if = "Body::is_empty")] + #[serde(default)] + pub body: Body, - #[serde(skip_serializing_if = "Vec::is_empty")] - #[serde(default)] - pub children: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default)] + pub children: Vec, } impl NodeData { - pub fn new(node_type: T) -> NodeData { - NodeData { - node_type: node_type.to_string(), - ..Default::default() - } + pub fn new(node_type: T) -> NodeData { + NodeData { + node_type: node_type.to_string(), + ..Default::default() } + } - pub fn split(self) -> (Node, Vec) { - let node = Node { - node_type: self.node_type, - body: self.body, - attributes: self.attributes, - }; + pub fn split(self) -> (Node, Vec) { + let node = Node { + node_type: self.node_type, + body: self.body, + attributes: self.attributes, + }; - (node, self.children) - } + (node, self.children) + } } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -63,49 +63,53 @@ pub struct RepeatedNodeData(Vec); /// Builder for [`NodeData`] pub struct NodeDataBuilder { - node: NodeData, + node: NodeData, } impl NodeDataBuilder { - pub fn new(node_type: T) -> Self { - Self { - node: NodeData::new(node_type.to_string()), - } + pub fn new(node_type: T) -> Self { + Self { + node: NodeData::new(node_type.to_string()), } + } - /// Appends a new node to the end of the builder's node children. - pub fn add_node_data(mut self, node: NodeData) -> Self { - self.node.children.push(node); - self - } + /// Appends a new node to the end of the builder's node children. + pub fn add_node_data(mut self, node: NodeData) -> Self { + self.node.children.push(node); + self + } - pub fn extend_node_data(mut self, node: Vec) -> Self { - self.node.children.extend(node); - self - } + pub fn extend_node_data(mut self, node: Vec) -> Self { + self.node.children.extend(node); + self + } - /// Inserts attributes to the builder's node. - /// - /// The attributes will be replace if they shared the same key - pub fn insert_attribute, V: Into>(mut self, key: K, value: V) -> Self { - self.node.attributes.insert(key.into(), value); - self - } + /// Inserts attributes to the builder's node. + /// + /// The attributes will be replace if they shared the same key + pub fn insert_attribute, V: Into>( + mut self, + key: K, + value: V, + ) -> Self { + self.node.attributes.insert(key.into(), value); + self + } - pub fn insert_attribute_entry(mut self, entry: AttributeEntry) -> Self { - self.node.attributes.insert_entry(entry); - self - } + pub fn insert_attribute_entry(mut self, entry: AttributeEntry) -> Self { + self.node.attributes.insert_entry(entry); + self + } - pub fn insert_delta(mut self, delta: DeltaTextOperations) -> Self { - self.node.body = Body::Delta(delta); - self - } + pub fn insert_delta(mut self, delta: DeltaTextOperations) -> Self { + self.node.body = Body::Delta(delta); + self + } - /// Returns the builder's node - pub fn build(self) -> NodeData { - self.node - } + /// Returns the builder's node + pub fn build(self) -> NodeData { + self.node + } } /// NodeBody represents as the node's data. @@ -118,65 +122,65 @@ impl NodeDataBuilder { /// #[derive(Debug, Clone, PartialEq, Eq)] pub enum Body { - Empty, - Delta(DeltaTextOperations), + Empty, + Delta(DeltaTextOperations), } impl std::default::Default for Body { - fn default() -> Self { - Body::Empty - } + fn default() -> Self { + Body::Empty + } } impl Body { - fn is_empty(&self) -> bool { - matches!(self, Body::Empty) - } + fn is_empty(&self) -> bool { + matches!(self, Body::Empty) + } } impl OperationTransform for Body { - /// Only the same enum variant can perform the compose operation. - fn compose(&self, other: &Self) -> Result - where - Self: Sized, - { - match (self, other) { - (Delta(a), Delta(b)) => a.compose(b).map(Delta), - (Body::Empty, Delta(b)) => Ok(Delta(b.clone())), - (Body::Empty, Body::Empty) => Ok(Body::Empty), - (l, r) => { - let msg = format!("{:?} can not compose {:?}", l, r); - Err(OTError::internal().context(msg)) - } - } + /// Only the same enum variant can perform the compose operation. + fn compose(&self, other: &Self) -> Result + where + Self: Sized, + { + match (self, other) { + (Delta(a), Delta(b)) => a.compose(b).map(Delta), + (Body::Empty, Delta(b)) => Ok(Delta(b.clone())), + (Body::Empty, Body::Empty) => Ok(Body::Empty), + (l, r) => { + let msg = format!("{:?} can not compose {:?}", l, r); + Err(OTError::internal().context(msg)) + }, } + } - /// Only the same enum variant can perform the transform operation. - fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> - where - Self: Sized, - { - match (self, other) { - (Delta(l), Delta(r)) => l.transform(r).map(|(ta, tb)| (Delta(ta), Delta(tb))), - (Body::Empty, Body::Empty) => Ok((Body::Empty, Body::Empty)), - (l, r) => { - let msg = format!("{:?} can not compose {:?}", l, r); - Err(OTError::internal().context(msg)) - } - } + /// Only the same enum variant can perform the transform operation. + fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> + where + Self: Sized, + { + match (self, other) { + (Delta(l), Delta(r)) => l.transform(r).map(|(ta, tb)| (Delta(ta), Delta(tb))), + (Body::Empty, Body::Empty) => Ok((Body::Empty, Body::Empty)), + (l, r) => { + let msg = format!("{:?} can not compose {:?}", l, r); + Err(OTError::internal().context(msg)) + }, } + } - /// Only the same enum variant can perform the invert operation. - fn invert(&self, other: &Self) -> Self { - match (self, other) { - (Delta(l), Delta(r)) => Delta(l.invert(r)), - (Body::Empty, Body::Empty) => Body::Empty, - (l, r) => { - tracing::error!("{:?} can not compose {:?}", l, r); - l.clone() - } - } + /// Only the same enum variant can perform the invert operation. + fn invert(&self, other: &Self) -> Self { + match (self, other) { + (Delta(l), Delta(r)) => Delta(l.invert(r)), + (Body::Empty, Body::Empty) => Body::Empty, + (l, r) => { + tracing::error!("{:?} can not compose {:?}", l, r); + l.clone() + }, } + } } /// Represents the changeset of the [`NodeBody`] @@ -185,128 +189,131 @@ impl OperationTransform for Body { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum Changeset { - Delta { - delta: DeltaTextOperations, - inverted: DeltaTextOperations, - }, - Attributes { - new: AttributeHashMap, - old: AttributeHashMap, - }, + Delta { + delta: DeltaTextOperations, + inverted: DeltaTextOperations, + }, + Attributes { + new: AttributeHashMap, + old: AttributeHashMap, + }, } impl Changeset { - pub fn is_delta(&self) -> bool { - match self { - Changeset::Delta { .. } => true, - Changeset::Attributes { .. } => false, - } + pub fn is_delta(&self) -> bool { + match self { + Changeset::Delta { .. } => true, + Changeset::Attributes { .. } => false, } - pub fn is_attribute(&self) -> bool { - match self { - Changeset::Delta { .. } => false, - Changeset::Attributes { .. } => true, - } + } + pub fn is_attribute(&self) -> bool { + match self { + Changeset::Delta { .. } => false, + Changeset::Attributes { .. } => true, } - pub fn inverted(&self) -> Changeset { - match self { - Changeset::Delta { delta, inverted } => Changeset::Delta { - delta: inverted.clone(), - inverted: delta.clone(), - }, - Changeset::Attributes { new, old } => Changeset::Attributes { - new: old.clone(), - old: new.clone(), - }, - } + } + pub fn inverted(&self) -> Changeset { + match self { + Changeset::Delta { delta, inverted } => Changeset::Delta { + delta: inverted.clone(), + inverted: delta.clone(), + }, + Changeset::Attributes { new, old } => Changeset::Attributes { + new: old.clone(), + old: new.clone(), + }, } + } - pub fn compose(&mut self, other: &Changeset) -> Result<(), OTError> { - match (self, other) { - ( - Changeset::Delta { delta, inverted }, - Changeset::Delta { - delta: other_delta, - inverted: _, - }, - ) => { - let original = delta.compose(inverted)?; - let new_delta = delta.compose(other_delta)?; - let new_inverted = new_delta.invert(&original); + pub fn compose(&mut self, other: &Changeset) -> Result<(), OTError> { + match (self, other) { + ( + Changeset::Delta { delta, inverted }, + Changeset::Delta { + delta: other_delta, + inverted: _, + }, + ) => { + let original = delta.compose(inverted)?; + let new_delta = delta.compose(other_delta)?; + let new_inverted = new_delta.invert(&original); - *delta = new_delta; - *inverted = new_inverted; - Ok(()) - } - ( - Changeset::Attributes { new, old }, - Changeset::Attributes { - new: other_new, - old: other_old, - }, - ) => { - *new = other_new.clone(); - *old = other_old.clone(); - Ok(()) - } - (left, right) => { - let err = format!("Compose changeset failed. {:?} can't compose {:?}", left, right); - Err(OTError::compose().context(err)) - } - } + *delta = new_delta; + *inverted = new_inverted; + Ok(()) + }, + ( + Changeset::Attributes { new, old }, + Changeset::Attributes { + new: other_new, + old: other_old, + }, + ) => { + *new = other_new.clone(); + *old = other_old.clone(); + Ok(()) + }, + (left, right) => { + let err = format!( + "Compose changeset failed. {:?} can't compose {:?}", + left, right + ); + Err(OTError::compose().context(err)) + }, } + } } /// [`Node`] represents as a leaf in the [`NodeTree`]. /// #[derive(Clone, Eq, PartialEq, Debug)] pub struct Node { - pub node_type: String, - pub body: Body, - pub attributes: AttributeHashMap, + pub node_type: String, + pub body: Body, + pub attributes: AttributeHashMap, } impl Node { - pub fn new(node_type: &str) -> Node { - Node { - node_type: node_type.into(), - attributes: AttributeHashMap::new(), - body: Body::Empty, - } + pub fn new(node_type: &str) -> Node { + Node { + node_type: node_type.into(), + attributes: AttributeHashMap::new(), + body: Body::Empty, } + } - pub fn apply_changeset(&mut self, changeset: Changeset) -> Result<(), OTError> { - match changeset { - Changeset::Delta { delta, inverted: _ } => { - let new_body = self.body.compose(&Delta(delta))?; - self.body = new_body; - Ok(()) - } - Changeset::Attributes { new, old: _ } => { - let new_attributes = AttributeHashMap::compose(&self.attributes, &new)?; - self.attributes = new_attributes; - Ok(()) - } - } + pub fn apply_changeset(&mut self, changeset: Changeset) -> Result<(), OTError> { + match changeset { + Changeset::Delta { delta, inverted: _ } => { + let new_body = self.body.compose(&Delta(delta))?; + self.body = new_body; + Ok(()) + }, + Changeset::Attributes { new, old: _ } => { + let new_attributes = AttributeHashMap::compose(&self.attributes, &new)?; + self.attributes = new_attributes; + Ok(()) + }, } + } } impl std::convert::From for Node { - fn from(node: NodeData) -> Self { - Self { - node_type: node.node_type, - attributes: node.attributes, - body: node.body, - } + fn from(node: NodeData) -> Self { + Self { + node_type: node.node_type, + attributes: node.attributes, + body: node.body, } + } } impl std::convert::From<&NodeData> for Node { - fn from(node: &NodeData) -> Self { - Self { - node_type: node.node_type.clone(), - attributes: node.attributes.clone(), - body: node.body.clone(), - } + fn from(node: &NodeData) -> Self { + Self { + node_type: node.node_type.clone(), + attributes: node.attributes.clone(), + body: node.body.clone(), } + } } diff --git a/shared-lib/lib-ot/src/core/node_tree/node_serde.rs b/shared-lib/lib-ot/src/core/node_tree/node_serde.rs index 8e360b8937..ebc794a0bc 100644 --- a/shared-lib/lib-ot/src/core/node_tree/node_serde.rs +++ b/shared-lib/lib-ot/src/core/node_tree/node_serde.rs @@ -1,63 +1,63 @@ -use super::Body; -use crate::text_delta::DeltaTextOperations; -use serde::de::{self, MapAccess, Visitor}; -use serde::ser::SerializeMap; -use serde::{Deserializer, Serializer}; -use std::fmt; - -pub fn serialize_body(body: &Body, serializer: S) -> Result -where - S: Serializer, -{ - let mut map = serializer.serialize_map(Some(3))?; - match body { - Body::Empty => {} - Body::Delta(delta) => { - map.serialize_key("delta")?; - map.serialize_value(delta)?; - } - } - map.end() -} - -pub fn deserialize_body<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - struct NodeBodyVisitor(); - - impl<'de> Visitor<'de> for NodeBodyVisitor { - type Value = Body; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("Expect NodeBody") - } - - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - let mut delta: Option = None; - while let Some(key) = map.next_key()? { - match key { - "delta" => { - if delta.is_some() { - return Err(de::Error::duplicate_field("delta")); - } - delta = Some(map.next_value()?); - } - other => { - panic!("Unexpected key: {}", other); - } - } - } - - if let Some(delta) = delta { - return Ok(Body::Delta(delta)); - } - - Err(de::Error::missing_field("delta")) - } - } - deserializer.deserialize_any(NodeBodyVisitor()) -} +use super::Body; +use crate::text_delta::DeltaTextOperations; +use serde::de::{self, MapAccess, Visitor}; +use serde::ser::SerializeMap; +use serde::{Deserializer, Serializer}; +use std::fmt; + +pub fn serialize_body(body: &Body, serializer: S) -> Result +where + S: Serializer, +{ + let mut map = serializer.serialize_map(Some(3))?; + match body { + Body::Empty => {}, + Body::Delta(delta) => { + map.serialize_key("delta")?; + map.serialize_value(delta)?; + }, + } + map.end() +} + +pub fn deserialize_body<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + struct NodeBodyVisitor(); + + impl<'de> Visitor<'de> for NodeBodyVisitor { + type Value = Body; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Expect NodeBody") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut delta: Option = None; + while let Some(key) = map.next_key()? { + match key { + "delta" => { + if delta.is_some() { + return Err(de::Error::duplicate_field("delta")); + } + delta = Some(map.next_value()?); + }, + other => { + panic!("Unexpected key: {}", other); + }, + } + } + + if let Some(delta) = delta { + return Ok(Body::Delta(delta)); + } + + Err(de::Error::missing_field("delta")) + } + } + deserializer.deserialize_any(NodeBodyVisitor()) +} diff --git a/shared-lib/lib-ot/src/core/node_tree/operation.rs b/shared-lib/lib-ot/src/core/node_tree/operation.rs index 145e7f1428..546b048698 100644 --- a/shared-lib/lib-ot/src/core/node_tree/operation.rs +++ b/shared-lib/lib-ot/src/core/node_tree/operation.rs @@ -7,260 +7,264 @@ use std::sync::Arc; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "op")] pub enum NodeOperation { - #[serde(rename = "insert")] - Insert { path: Path, nodes: Vec }, + #[serde(rename = "insert")] + Insert { path: Path, nodes: Vec }, - #[serde(rename = "update")] - Update { path: Path, changeset: Changeset }, + #[serde(rename = "update")] + Update { path: Path, changeset: Changeset }, - #[serde(rename = "delete")] - Delete { path: Path, nodes: Vec }, + #[serde(rename = "delete")] + Delete { path: Path, nodes: Vec }, } impl NodeOperation { - pub fn get_path(&self) -> &Path { - match self { - NodeOperation::Insert { path, .. } => path, - NodeOperation::Delete { path, .. } => path, - NodeOperation::Update { path, .. } => path, - } + pub fn get_path(&self) -> &Path { + match self { + NodeOperation::Insert { path, .. } => path, + NodeOperation::Delete { path, .. } => path, + NodeOperation::Update { path, .. } => path, + } + } + + pub fn get_mut_path(&mut self) -> &mut Path { + match self { + NodeOperation::Insert { path, .. } => path, + NodeOperation::Delete { path, .. } => path, + NodeOperation::Update { path, .. } => path, + } + } + + pub fn is_update_delta(&self) -> bool { + match self { + NodeOperation::Insert { .. } => false, + NodeOperation::Update { path: _, changeset } => changeset.is_delta(), + NodeOperation::Delete { .. } => false, + } + } + + pub fn is_update_attribute(&self) -> bool { + match self { + NodeOperation::Insert { .. } => false, + NodeOperation::Update { path: _, changeset } => changeset.is_attribute(), + NodeOperation::Delete { .. } => false, + } + } + pub fn is_insert(&self) -> bool { + match self { + NodeOperation::Insert { .. } => true, + NodeOperation::Update { .. } => false, + NodeOperation::Delete { .. } => false, + } + } + pub fn can_compose(&self, other: &NodeOperation) -> bool { + if self.get_path() != other.get_path() { + return false; + } + if self.is_update_delta() && other.is_update_delta() { + return true; } - pub fn get_mut_path(&mut self) -> &mut Path { - match self { - NodeOperation::Insert { path, .. } => path, - NodeOperation::Delete { path, .. } => path, - NodeOperation::Update { path, .. } => path, - } + if self.is_update_attribute() && other.is_update_attribute() { + return true; } - pub fn is_update_delta(&self) -> bool { - match self { - NodeOperation::Insert { .. } => false, - NodeOperation::Update { path: _, changeset } => changeset.is_delta(), - NodeOperation::Delete { .. } => false, - } + if self.is_insert() && other.is_update_delta() { + return true; } + false + } - pub fn is_update_attribute(&self) -> bool { - match self { - NodeOperation::Insert { .. } => false, - NodeOperation::Update { path: _, changeset } => changeset.is_attribute(), - NodeOperation::Delete { .. } => false, - } - } - pub fn is_insert(&self) -> bool { - match self { - NodeOperation::Insert { .. } => true, - NodeOperation::Update { .. } => false, - NodeOperation::Delete { .. } => false, - } - } - pub fn can_compose(&self, other: &NodeOperation) -> bool { - if self.get_path() != other.get_path() { - return false; - } - if self.is_update_delta() && other.is_update_delta() { - return true; - } - - if self.is_update_attribute() && other.is_update_attribute() { - return true; - } - - if self.is_insert() && other.is_update_delta() { - return true; - } - false - } - - pub fn compose(&mut self, other: &NodeOperation) -> Result<(), OTError> { - match (self, other) { - ( - NodeOperation::Insert { path: _, nodes }, - NodeOperation::Update { - path: _other_path, - changeset, - }, - ) => { - match changeset { - Changeset::Delta { delta, inverted: _ } => { - if let Body::Delta(old_delta) = &mut nodes.last_mut().unwrap().body { - let new_delta = old_delta.compose(delta)?; - *old_delta = new_delta; - } - } - Changeset::Attributes { new: _, old: _ } => { - return Err(OTError::compose().context("Can't compose the attributes changeset")); - } - } - Ok(()) + pub fn compose(&mut self, other: &NodeOperation) -> Result<(), OTError> { + match (self, other) { + ( + NodeOperation::Insert { path: _, nodes }, + NodeOperation::Update { + path: _other_path, + changeset, + }, + ) => { + match changeset { + Changeset::Delta { delta, inverted: _ } => { + if let Body::Delta(old_delta) = &mut nodes.last_mut().unwrap().body { + let new_delta = old_delta.compose(delta)?; + *old_delta = new_delta; } - ( - NodeOperation::Update { path: _, changeset }, - NodeOperation::Update { - path: _, - changeset: other_changeset, - }, - ) => changeset.compose(other_changeset), - (_left, _right) => Err(OTError::compose().context("Can't compose the operation")), + }, + Changeset::Attributes { new: _, old: _ } => { + return Err(OTError::compose().context("Can't compose the attributes changeset")); + }, } + Ok(()) + }, + ( + NodeOperation::Update { path: _, changeset }, + NodeOperation::Update { + path: _, + changeset: other_changeset, + }, + ) => changeset.compose(other_changeset), + (_left, _right) => Err(OTError::compose().context("Can't compose the operation")), } + } - pub fn inverted(&self) -> NodeOperation { - match self { - NodeOperation::Insert { path, nodes } => NodeOperation::Delete { - path: path.clone(), - nodes: nodes.clone(), - }, - NodeOperation::Delete { path, nodes } => NodeOperation::Insert { - path: path.clone(), - nodes: nodes.clone(), - }, - NodeOperation::Update { path, changeset: body } => NodeOperation::Update { - path: path.clone(), - changeset: body.inverted(), - }, - } + pub fn inverted(&self) -> NodeOperation { + match self { + NodeOperation::Insert { path, nodes } => NodeOperation::Delete { + path: path.clone(), + nodes: nodes.clone(), + }, + NodeOperation::Delete { path, nodes } => NodeOperation::Insert { + path: path.clone(), + nodes: nodes.clone(), + }, + NodeOperation::Update { + path, + changeset: body, + } => NodeOperation::Update { + path: path.clone(), + changeset: body.inverted(), + }, } + } - /// Make the `other` operation can be applied to the version after applying the `self` operation. - /// The semantics of transform is used when editing conflicts occur, which is often determined by the version id。 - /// For example, if the inserted position has been acquired by others, then it's needed to do the transform to - /// make sure the inserted position is right. - /// - /// # Arguments - /// - /// * `other`: The operation that is going to be transformed - /// - /// # Examples - /// - /// ``` - /// use lib_ot::core::{NodeDataBuilder, NodeOperation, Path}; - /// let node_1 = NodeDataBuilder::new("text_1").build(); - /// let node_2 = NodeDataBuilder::new("text_2").build(); - /// - /// let op_1 = NodeOperation::Insert { - /// path: Path(vec![0, 1]), - /// nodes: vec![node_1], - /// }; - /// - /// let mut op_2 = NodeOperation::Insert { - /// path: Path(vec![0, 1]), - /// nodes: vec![node_2], - /// }; - /// - /// assert_eq!(serde_json::to_string(&op_2).unwrap(), r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text_2"}]}"#); - /// - /// op_1.transform(&mut op_2); - /// assert_eq!(serde_json::to_string(&op_2).unwrap(), r#"{"op":"insert","path":[0,2],"nodes":[{"type":"text_2"}]}"#); - /// assert_eq!(serde_json::to_string(&op_1).unwrap(), r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text_1"}]}"#); - /// ``` - pub fn transform(&self, other: &mut NodeOperation) { - match self { - NodeOperation::Insert { path, nodes } => { - let new_path = path.transform(other.get_path(), nodes.len()); - *other.get_mut_path() = new_path; - } - NodeOperation::Delete { path, nodes } => { - let new_path = path.transform(other.get_path(), nodes.len()); - *other.get_mut_path() = new_path; - } - _ => { - // Only insert/delete will change the path. - } - } + /// Make the `other` operation can be applied to the version after applying the `self` operation. + /// The semantics of transform is used when editing conflicts occur, which is often determined by the version id。 + /// For example, if the inserted position has been acquired by others, then it's needed to do the transform to + /// make sure the inserted position is right. + /// + /// # Arguments + /// + /// * `other`: The operation that is going to be transformed + /// + /// # Examples + /// + /// ``` + /// use lib_ot::core::{NodeDataBuilder, NodeOperation, Path}; + /// let node_1 = NodeDataBuilder::new("text_1").build(); + /// let node_2 = NodeDataBuilder::new("text_2").build(); + /// + /// let op_1 = NodeOperation::Insert { + /// path: Path(vec![0, 1]), + /// nodes: vec![node_1], + /// }; + /// + /// let mut op_2 = NodeOperation::Insert { + /// path: Path(vec![0, 1]), + /// nodes: vec![node_2], + /// }; + /// + /// assert_eq!(serde_json::to_string(&op_2).unwrap(), r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text_2"}]}"#); + /// + /// op_1.transform(&mut op_2); + /// assert_eq!(serde_json::to_string(&op_2).unwrap(), r#"{"op":"insert","path":[0,2],"nodes":[{"type":"text_2"}]}"#); + /// assert_eq!(serde_json::to_string(&op_1).unwrap(), r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text_1"}]}"#); + /// ``` + pub fn transform(&self, other: &mut NodeOperation) { + match self { + NodeOperation::Insert { path, nodes } => { + let new_path = path.transform(other.get_path(), nodes.len()); + *other.get_mut_path() = new_path; + }, + NodeOperation::Delete { path, nodes } => { + let new_path = path.transform(other.get_path(), nodes.len()); + *other.get_mut_path() = new_path; + }, + _ => { + // Only insert/delete will change the path. + }, } + } } type OperationIndexMap = Vec>; #[derive(Debug, Clone, Default)] pub struct NodeOperations { - inner: OperationIndexMap, + inner: OperationIndexMap, } impl NodeOperations { - pub fn new() -> Self { - Self::default() - } + pub fn new() -> Self { + Self::default() + } - pub fn from_operations(operations: Vec) -> Self { - let mut ops = Self::new(); - for op in operations { - ops.push_op(op) + pub fn from_operations(operations: Vec) -> Self { + let mut ops = Self::new(); + for op in operations { + ops.push_op(op) + } + ops + } + + pub fn from_bytes(bytes: Vec) -> Result { + let operation_list = + serde_json::from_slice(&bytes).map_err(|err| OTError::serde().context(err))?; + Ok(operation_list) + } + + pub fn to_bytes(&self) -> Result, OTError> { + let bytes = serde_json::to_vec(self).map_err(|err| OTError::serde().context(err))?; + Ok(bytes) + } + + pub fn values(&self) -> &Vec> { + &self.inner + } + + pub fn values_mut(&mut self) -> &mut Vec> { + &mut self.inner + } + + pub fn len(&self) -> usize { + self.values().len() + } + + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + pub fn into_inner(self) -> Vec> { + self.inner + } + + pub fn push_op>>(&mut self, other: T) { + let other = other.into(); + if let Some(last_operation) = self.inner.last_mut() { + if last_operation.can_compose(&other) { + let mut_operation = Arc::make_mut(last_operation); + if mut_operation.compose(&other).is_ok() { + return; } - ops + } } - pub fn from_bytes(bytes: Vec) -> Result { - let operation_list = serde_json::from_slice(&bytes).map_err(|err| OTError::serde().context(err))?; - Ok(operation_list) - } + // If the passed-in operation can't be composed, then append it to the end. + self.inner.push(other); + } - pub fn to_bytes(&self) -> Result, OTError> { - let bytes = serde_json::to_vec(self).map_err(|err| OTError::serde().context(err))?; - Ok(bytes) + pub fn compose(&mut self, other: NodeOperations) { + for operation in other.values() { + self.push_op(operation.clone()); } + } - pub fn values(&self) -> &Vec> { - &self.inner - } - - pub fn values_mut(&mut self) -> &mut Vec> { - &mut self.inner - } - - pub fn len(&self) -> usize { - self.values().len() - } - - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } - - pub fn into_inner(self) -> Vec> { - self.inner - } - - pub fn push_op>>(&mut self, other: T) { - let other = other.into(); - if let Some(last_operation) = self.inner.last_mut() { - if last_operation.can_compose(&other) { - let mut_operation = Arc::make_mut(last_operation); - if mut_operation.compose(&other).is_ok() { - return; - } - } - } - - // If the passed-in operation can't be composed, then append it to the end. - self.inner.push(other); - } - - pub fn compose(&mut self, other: NodeOperations) { - for operation in other.values() { - self.push_op(operation.clone()); - } - } - - pub fn inverted(&self) -> Self { - let mut operations = Self::new(); - for operation in self.values() { - operations.push_op(operation.inverted()); - } - operations + pub fn inverted(&self) -> Self { + let mut operations = Self::new(); + for operation in self.values() { + operations.push_op(operation.inverted()); } + operations + } } impl std::convert::From> for NodeOperations { - fn from(operations: Vec) -> Self { - Self::from_operations(operations) - } + fn from(operations: Vec) -> Self { + Self::from_operations(operations) + } } impl std::convert::From for NodeOperations { - fn from(operation: NodeOperation) -> Self { - Self::from_operations(vec![operation]) - } + fn from(operation: NodeOperation) -> Self { + Self::from_operations(vec![operation]) + } } diff --git a/shared-lib/lib-ot/src/core/node_tree/operation_serde.rs b/shared-lib/lib-ot/src/core/node_tree/operation_serde.rs index b35281d11e..70400a2eab 100644 --- a/shared-lib/lib-ot/src/core/node_tree/operation_serde.rs +++ b/shared-lib/lib-ot/src/core/node_tree/operation_serde.rs @@ -5,45 +5,45 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt; impl Serialize for NodeOperations { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let operations = self.values(); - let mut seq = serializer.serialize_seq(Some(operations.len()))?; - for operation in operations { - seq.serialize_element(&operation)?; - } - seq.end() + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let operations = self.values(); + let mut seq = serializer.serialize_seq(Some(operations.len()))?; + for operation in operations { + seq.serialize_element(&operation)?; } + seq.end() + } } impl<'de> Deserialize<'de> for NodeOperations { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct NodeOperationsVisitor(); + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct NodeOperationsVisitor(); - impl<'de> Visitor<'de> for NodeOperationsVisitor { - type Value = NodeOperations; + impl<'de> Visitor<'de> for NodeOperationsVisitor { + type Value = NodeOperations; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("Expected node operation") - } + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Expected node operation") + } - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let mut operations = NodeOperations::new(); - while let Some(operation) = seq.next_element::()? { - operations.push_op(operation); - } - Ok(operations) - } + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut operations = NodeOperations::new(); + while let Some(operation) = seq.next_element::()? { + operations.push_op(operation); } - - deserializer.deserialize_any(NodeOperationsVisitor()) + Ok(operations) + } } + + deserializer.deserialize_any(NodeOperationsVisitor()) + } } diff --git a/shared-lib/lib-ot/src/core/node_tree/path.rs b/shared-lib/lib-ot/src/core/node_tree/path.rs index ebe52c96fb..781dcf45e7 100644 --- a/shared-lib/lib-ot/src/core/node_tree/path.rs +++ b/shared-lib/lib-ot/src/core/node_tree/path.rs @@ -27,189 +27,189 @@ use serde::{Deserialize, Serialize}; pub struct Path(pub Vec); impl Path { - pub fn is_valid(&self) -> bool { - if self.is_empty() { - return false; - } - true + pub fn is_valid(&self) -> bool { + if self.is_empty() { + return false; } + true + } - pub fn clone_with(&self, element: usize) -> Self { - let mut cloned_self = self.clone(); - cloned_self.push(element); - cloned_self - } + pub fn clone_with(&self, element: usize) -> Self { + let mut cloned_self = self.clone(); + cloned_self.push(element); + cloned_self + } - pub fn is_root(&self) -> bool { - self.0.len() == 1 && self.0[0] == 0 - } + pub fn is_root(&self) -> bool { + self.0.len() == 1 && self.0[0] == 0 + } } impl std::ops::Deref for Path { - type Target = Vec; + type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } impl std::ops::DerefMut for Path { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } } impl std::convert::From for Path { - fn from(val: usize) -> Self { - Path(vec![val]) - } + fn from(val: usize) -> Self { + Path(vec![val]) + } } impl std::convert::From<&usize> for Path { - fn from(val: &usize) -> Self { - Path(vec![*val]) - } + fn from(val: &usize) -> Self { + Path(vec![*val]) + } } impl std::convert::From<&Path> for Path { - fn from(path: &Path) -> Self { - path.clone() - } + fn from(path: &Path) -> Self { + path.clone() + } } impl From> for Path { - fn from(v: Vec) -> Self { - Path(v) - } + fn from(v: Vec) -> Self { + Path(v) + } } impl From<&Vec> for Path { - fn from(values: &Vec) -> Self { - Path(values.clone()) - } + fn from(values: &Vec) -> Self { + Path(values.clone()) + } } impl From<&[usize]> for Path { - fn from(values: &[usize]) -> Self { - Path(values.to_vec()) - } + fn from(values: &[usize]) -> Self { + Path(values.to_vec()) + } } impl Path { - /// Calling this function if there are two changes want to modify the same path. - /// - /// # Arguments - /// - /// * `other`: the path that need to be transformed - /// * `offset`: represents the len of nodes referenced by the current path - /// - /// If two changes modify the same path or the path was shared by them. Then it needs to do the - /// transformation to make sure the changes are applied to the right path. - /// - /// returns: the path represents the position that the other path reference to. - /// - /// # Examples - /// - /// ``` - /// use lib_ot::core::Path; - /// let path = Path(vec![0, 1]); - /// for (old_path, len_of_nodes, expected_path) in vec![ - /// // Try to modify the path [0, 1], but someone has inserted one element before the - /// // current path [0,1] in advance. That causes the modified path [0,1] to no longer - /// // valid. It needs to do the transformation to get the right path. - /// // - /// // [0,2] is the path you want to modify. - /// (Path(vec![0, 1]), 1, Path(vec![0, 2])), - /// (Path(vec![0, 1]), 5, Path(vec![0, 6])), - /// (Path(vec![0, 2]), 1, Path(vec![0, 3])), - /// // Try to modify the path [0, 2,3,4], but someone has inserted one element before the - /// // current path [0,1] in advance. That cause the prefix path [0,2] of [0,2,3,4] - /// // no longer valid. - /// // It needs to do the transformation to get the right path. So [0,2] is transformed to [0,3] - /// // and the suffix [3,4] of the [0,2,3,4] remains the same. So the transformed result is - /// // - /// // [0,3,3,4] - /// (Path(vec![0, 2, 3, 4]), 1, Path(vec![0, 3, 3, 4])), - /// ] { - /// assert_eq!(path.transform(&old_path, len_of_nodes), expected_path); - /// } - /// // The path remains the same in the following test. Because the shared path is not changed. - /// let path = Path(vec![0, 1, 2]); - /// for (old_path, len_of_nodes, expected_path) in vec![ - /// // Try to modify the path [0,0,0,1,2], but someone has inserted one element - /// // before [0,1,2]. [0,0,0,1,2] and [0,1,2] share the same path [0,x], because - /// // the element was inserted at [0,1,2] that didn't affect the shared path [0, x]. - /// // So, after the transformation, the path is not changed. - /// (Path(vec![0, 0, 0, 1, 2]), 1, Path(vec![0, 0, 0, 1, 2])), - /// (Path(vec![0, 1]), 1, Path(vec![0, 1])), - /// ] { - /// assert_eq!(path.transform(&old_path, len_of_nodes), expected_path); - /// } - /// - /// let path = Path(vec![1, 1]); - /// for (old_path, len_of_nodes, expected_path) in vec![(Path(vec![1, 0]), 1, Path(vec![1, 0]))] { - /// assert_eq!(path.transform(&old_path, len_of_nodes), expected_path); - /// } - /// ``` - /// For example, client A and client B want to insert a node at the same index, the server applies - /// the changes made by client B. But, before applying the client A's changes, server transforms - /// the changes first in order to make sure that client A modify the right position. After that, - /// the changes can be applied to the server. - /// - /// ┌──────────┐ ┌──────────┐ ┌──────────┐ - /// │ Client A │ │ Server │ │ Client B │ - /// └─────┬────┘ └─────┬────┘ └────┬─────┘ - /// │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ┐ │ - /// │ │ Root │ - /// │ │ │ 0:A │ │ - /// │ │ ─ ─ ─ ─ ─ ─ ─ ─ │ - /// │ │ ◀───────────────────────│ - /// │ │ Insert B at index 1 │ - /// │ │ │ - /// │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ┐ │ - /// │ │ Root │ - /// │ │ │ 0:A │ │ - /// ├──────────────────────▶│ 1:B │ - /// │ Insert C at index 1 │ └ ─ ─ ─ ─ ─ ─ ─ ┘ │ - /// │ │ │ - /// │ │ transform index 1 to 2 │ - /// │ │ │ - /// │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ │ - /// │ │ Root │ │ - /// │ │ │ 0:A │ - /// ▼ ▼ 1:B │ ▼ - /// │ 2:C - /// ─ ─ ─ ─ ─ ─ ─ ─ ┘ - pub fn transform(&self, other: &Path, offset: usize) -> Path { - if self.len() > other.len() { - return other.clone(); - } - if self.is_empty() || other.is_empty() { - return other.clone(); - } - for i in 0..(self.len() - 1) { - if self.0[i] != other.0[i] { - return other.clone(); - } - } - - // Splits the `Path` into two part. The suffix will contain the last element of the `Path`. - let second_last_index = self.0.len() - 1; - let mut prefix: Vec = self.0[0..second_last_index].into(); - let mut suffix: Vec = other.0[self.0.len()..].into(); - let last_value = *self.0.last().unwrap(); - - let other_second_last_value = other.0[second_last_index]; - - // - if last_value <= other_second_last_value { - prefix.push(other_second_last_value + offset); - } else { - prefix.push(other_second_last_value); - } - - // concat the prefix and suffix into a new path - prefix.append(&mut suffix); - Path(prefix) + /// Calling this function if there are two changes want to modify the same path. + /// + /// # Arguments + /// + /// * `other`: the path that need to be transformed + /// * `offset`: represents the len of nodes referenced by the current path + /// + /// If two changes modify the same path or the path was shared by them. Then it needs to do the + /// transformation to make sure the changes are applied to the right path. + /// + /// returns: the path represents the position that the other path reference to. + /// + /// # Examples + /// + /// ``` + /// use lib_ot::core::Path; + /// let path = Path(vec![0, 1]); + /// for (old_path, len_of_nodes, expected_path) in vec![ + /// // Try to modify the path [0, 1], but someone has inserted one element before the + /// // current path [0,1] in advance. That causes the modified path [0,1] to no longer + /// // valid. It needs to do the transformation to get the right path. + /// // + /// // [0,2] is the path you want to modify. + /// (Path(vec![0, 1]), 1, Path(vec![0, 2])), + /// (Path(vec![0, 1]), 5, Path(vec![0, 6])), + /// (Path(vec![0, 2]), 1, Path(vec![0, 3])), + /// // Try to modify the path [0, 2,3,4], but someone has inserted one element before the + /// // current path [0,1] in advance. That cause the prefix path [0,2] of [0,2,3,4] + /// // no longer valid. + /// // It needs to do the transformation to get the right path. So [0,2] is transformed to [0,3] + /// // and the suffix [3,4] of the [0,2,3,4] remains the same. So the transformed result is + /// // + /// // [0,3,3,4] + /// (Path(vec![0, 2, 3, 4]), 1, Path(vec![0, 3, 3, 4])), + /// ] { + /// assert_eq!(path.transform(&old_path, len_of_nodes), expected_path); + /// } + /// // The path remains the same in the following test. Because the shared path is not changed. + /// let path = Path(vec![0, 1, 2]); + /// for (old_path, len_of_nodes, expected_path) in vec![ + /// // Try to modify the path [0,0,0,1,2], but someone has inserted one element + /// // before [0,1,2]. [0,0,0,1,2] and [0,1,2] share the same path [0,x], because + /// // the element was inserted at [0,1,2] that didn't affect the shared path [0, x]. + /// // So, after the transformation, the path is not changed. + /// (Path(vec![0, 0, 0, 1, 2]), 1, Path(vec![0, 0, 0, 1, 2])), + /// (Path(vec![0, 1]), 1, Path(vec![0, 1])), + /// ] { + /// assert_eq!(path.transform(&old_path, len_of_nodes), expected_path); + /// } + /// + /// let path = Path(vec![1, 1]); + /// for (old_path, len_of_nodes, expected_path) in vec![(Path(vec![1, 0]), 1, Path(vec![1, 0]))] { + /// assert_eq!(path.transform(&old_path, len_of_nodes), expected_path); + /// } + /// ``` + /// For example, client A and client B want to insert a node at the same index, the server applies + /// the changes made by client B. But, before applying the client A's changes, server transforms + /// the changes first in order to make sure that client A modify the right position. After that, + /// the changes can be applied to the server. + /// + /// ┌──────────┐ ┌──────────┐ ┌──────────┐ + /// │ Client A │ │ Server │ │ Client B │ + /// └─────┬────┘ └─────┬────┘ └────┬─────┘ + /// │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ┐ │ + /// │ │ Root │ + /// │ │ │ 0:A │ │ + /// │ │ ─ ─ ─ ─ ─ ─ ─ ─ │ + /// │ │ ◀───────────────────────│ + /// │ │ Insert B at index 1 │ + /// │ │ │ + /// │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ┐ │ + /// │ │ Root │ + /// │ │ │ 0:A │ │ + /// ├──────────────────────▶│ 1:B │ + /// │ Insert C at index 1 │ └ ─ ─ ─ ─ ─ ─ ─ ┘ │ + /// │ │ │ + /// │ │ transform index 1 to 2 │ + /// │ │ │ + /// │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ │ + /// │ │ Root │ │ + /// │ │ │ 0:A │ + /// ▼ ▼ 1:B │ ▼ + /// │ 2:C + /// ─ ─ ─ ─ ─ ─ ─ ─ ┘ + pub fn transform(&self, other: &Path, offset: usize) -> Path { + if self.len() > other.len() { + return other.clone(); } + if self.is_empty() || other.is_empty() { + return other.clone(); + } + for i in 0..(self.len() - 1) { + if self.0[i] != other.0[i] { + return other.clone(); + } + } + + // Splits the `Path` into two part. The suffix will contain the last element of the `Path`. + let second_last_index = self.0.len() - 1; + let mut prefix: Vec = self.0[0..second_last_index].into(); + let mut suffix: Vec = other.0[self.0.len()..].into(); + let last_value = *self.0.last().unwrap(); + + let other_second_last_value = other.0[second_last_index]; + + // + if last_value <= other_second_last_value { + prefix.push(other_second_last_value + offset); + } else { + prefix.push(other_second_last_value); + } + + // concat the prefix and suffix into a new path + prefix.append(&mut suffix); + Path(prefix) + } } diff --git a/shared-lib/lib-ot/src/core/node_tree/transaction.rs b/shared-lib/lib-ot/src/core/node_tree/transaction.rs index 6ff481c7b7..ad9ee5a20f 100644 --- a/shared-lib/lib-ot/src/core/node_tree/transaction.rs +++ b/shared-lib/lib-ot/src/core/node_tree/transaction.rs @@ -7,273 +7,280 @@ use std::sync::Arc; #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct Transaction { - pub operations: NodeOperations, + pub operations: NodeOperations, - #[serde(default)] - #[serde(skip_serializing_if = "Extension::is_empty")] - pub extension: Extension, + #[serde(default)] + #[serde(skip_serializing_if = "Extension::is_empty")] + pub extension: Extension, } impl Transaction { - pub fn new() -> Self { - Self::default() + pub fn new() -> Self { + Self::default() + } + + pub fn from_operations>(operations: T) -> Self { + Self { + operations: operations.into(), + extension: Extension::Empty, + } + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + let transaction = serde_json::from_slice(bytes).map_err(|err| OTError::serde().context(err))?; + Ok(transaction) + } + + pub fn to_bytes(&self) -> Result, OTError> { + let bytes = serde_json::to_vec(&self).map_err(|err| OTError::serde().context(err))?; + Ok(bytes) + } + + pub fn from_json(s: &str) -> Result { + let serde_transaction: Transaction = + serde_json::from_str(s).map_err(|err| OTError::serde().context(err))?; + let mut transaction = Self::new(); + transaction.extension = serde_transaction.extension; + for operation in serde_transaction.operations.into_inner() { + transaction.operations.push_op(operation); + } + Ok(transaction) + } + + pub fn to_json(&self) -> Result { + serde_json::to_string(&self).map_err(|err| OTError::serde().context(err)) + } + + pub fn into_operations(self) -> Vec> { + self.operations.into_inner() + } + + pub fn split(self) -> (Vec>, Extension) { + (self.operations.into_inner(), self.extension) + } + + pub fn push_operation>(&mut self, operation: T) { + let operation = operation.into(); + self.operations.push_op(operation); + } + + /// Make the `other` can be applied to the version after applying the `self` transaction. + /// + /// The semantics of transform is used when editing conflicts occur, which is often determined by the version id。 + /// the operations of the transaction will be transformed into the conflict operations. + pub fn transform(&self, other: &Transaction) -> Result { + let mut other = other.clone(); + other.extension = self.extension.clone(); + + for other_operation in other.operations.values_mut() { + let other_operation = Arc::make_mut(other_operation); + for operation in self.operations.values() { + operation.transform(other_operation); + } } - pub fn from_operations>(operations: T) -> Self { - Self { - operations: operations.into(), - extension: Extension::Empty, - } - } + Ok(other) + } - pub fn from_bytes(bytes: &[u8]) -> Result { - let transaction = serde_json::from_slice(bytes).map_err(|err| OTError::serde().context(err))?; - Ok(transaction) - } - - pub fn to_bytes(&self) -> Result, OTError> { - let bytes = serde_json::to_vec(&self).map_err(|err| OTError::serde().context(err))?; - Ok(bytes) - } - - pub fn from_json(s: &str) -> Result { - let serde_transaction: Transaction = serde_json::from_str(s).map_err(|err| OTError::serde().context(err))?; - let mut transaction = Self::new(); - transaction.extension = serde_transaction.extension; - for operation in serde_transaction.operations.into_inner() { - transaction.operations.push_op(operation); - } - Ok(transaction) - } - - pub fn to_json(&self) -> Result { - serde_json::to_string(&self).map_err(|err| OTError::serde().context(err)) - } - - pub fn into_operations(self) -> Vec> { - self.operations.into_inner() - } - - pub fn split(self) -> (Vec>, Extension) { - (self.operations.into_inner(), self.extension) - } - - pub fn push_operation>(&mut self, operation: T) { - let operation = operation.into(); - self.operations.push_op(operation); - } - - /// Make the `other` can be applied to the version after applying the `self` transaction. - /// - /// The semantics of transform is used when editing conflicts occur, which is often determined by the version id。 - /// the operations of the transaction will be transformed into the conflict operations. - pub fn transform(&self, other: &Transaction) -> Result { - let mut other = other.clone(); - other.extension = self.extension.clone(); - - for other_operation in other.operations.values_mut() { - let other_operation = Arc::make_mut(other_operation); - for operation in self.operations.values() { - operation.transform(other_operation); - } - } - - Ok(other) - } - - pub fn compose(&mut self, other: Transaction) -> Result<(), OTError> { - // For the moment, just append `other` operations to the end of `self`. - let Transaction { operations, extension } = other; - self.operations.compose(operations); - self.extension = extension; - Ok(()) - } + pub fn compose(&mut self, other: Transaction) -> Result<(), OTError> { + // For the moment, just append `other` operations to the end of `self`. + let Transaction { + operations, + extension, + } = other; + self.operations.compose(operations); + self.extension = extension; + Ok(()) + } } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Extension { - Empty, - TextSelection { - before_selection: Selection, - after_selection: Selection, - }, + Empty, + TextSelection { + before_selection: Selection, + after_selection: Selection, + }, } impl std::default::Default for Extension { - fn default() -> Self { - Extension::Empty - } + fn default() -> Self { + Extension::Empty + } } impl Extension { - fn is_empty(&self) -> bool { - matches!(self, Extension::Empty) - } + fn is_empty(&self) -> bool { + matches!(self, Extension::Empty) + } } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct Selection { - start: Position, - end: Position, + start: Position, + end: Position, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct Position { - path: Path, - offset: usize, + path: Path, + offset: usize, } #[derive(Default)] pub struct TransactionBuilder { - operations: NodeOperations, + operations: NodeOperations, } impl TransactionBuilder { - pub fn new() -> TransactionBuilder { - Self::default() + pub fn new() -> TransactionBuilder { + Self::default() + } + + /// + /// + /// # Arguments + /// + /// * `path`: the path that is used to save the nodes + /// * `nodes`: the nodes you will be save in the path + /// + /// # Examples + /// + /// ``` + /// // 0 -- text_1 + /// use lib_ot::core::{NodeTree, NodeData, TransactionBuilder}; + /// let mut node_tree = NodeTree::default(); + /// let transaction = TransactionBuilder::new() + /// .insert_nodes_at_path(0,vec![ NodeData::new("text_1")]) + /// .build(); + /// node_tree.apply_transaction(transaction).unwrap(); + /// + /// node_tree.node_id_at_path(vec![0]).unwrap(); + /// ``` + /// + pub fn insert_nodes_at_path>(self, path: T, nodes: Vec) -> Self { + self.push(NodeOperation::Insert { + path: path.into(), + nodes, + }) + } + + /// + /// + /// # Arguments + /// + /// * `path`: the path that is used to save the nodes + /// * `node`: the node data will be saved in the path + /// + /// # Examples + /// + /// ``` + /// // 0 + /// // -- 0 + /// // |-- text + /// use lib_ot::core::{NodeTree, NodeData, TransactionBuilder}; + /// let mut node_tree = NodeTree::default(); + /// let transaction = TransactionBuilder::new() + /// .insert_node_at_path(0, NodeData::new("text")) + /// .build(); + /// node_tree.apply_transaction(transaction).unwrap(); + /// ``` + /// + pub fn insert_node_at_path>(self, path: T, node: NodeData) -> Self { + self.insert_nodes_at_path(path, vec![node]) + } + + pub fn update_node_at_path>(mut self, path: T, changeset: Changeset) -> Self { + self.operations.push_op(NodeOperation::Update { + path: path.into(), + changeset, + }); + self + } + // + // pub fn update_delta_at_path>( + // mut self, + // path: T, + // new_delta: DeltaTextOperations, + // ) -> Result { + // let path = path.into(); + // let operation: NodeOperation = self + // .operations + // .get(&path) + // .ok_or(Err(OTError::record_not_found().context("Can't found the node")))?; + // + // match operation { + // NodeOperation::Insert { path, nodes } => {} + // NodeOperation::Update { path, changeset } => {} + // NodeOperation::Delete { .. } => {} + // } + // + // match node.body { + // Body::Empty => Ok(self), + // Body::Delta(delta) => { + // let inverted = new_delta.invert(&delta); + // let changeset = Changeset::Delta { + // delta: new_delta, + // inverted, + // }; + // Ok(self.update_node_at_path(path, changeset)) + // } + // } + // } + + pub fn delete_node_at_path(self, node_tree: &NodeTree, path: &Path) -> Self { + self.delete_nodes_at_path(node_tree, path, 1) + } + + pub fn delete_nodes_at_path(mut self, node_tree: &NodeTree, path: &Path, length: usize) -> Self { + let node_id = node_tree.node_id_at_path(path); + if node_id.is_none() { + tracing::warn!("Path: {:?} doesn't contains any nodes", path); + return self; } - /// - /// - /// # Arguments - /// - /// * `path`: the path that is used to save the nodes - /// * `nodes`: the nodes you will be save in the path - /// - /// # Examples - /// - /// ``` - /// // 0 -- text_1 - /// use lib_ot::core::{NodeTree, NodeData, TransactionBuilder}; - /// let mut node_tree = NodeTree::default(); - /// let transaction = TransactionBuilder::new() - /// .insert_nodes_at_path(0,vec![ NodeData::new("text_1")]) - /// .build(); - /// node_tree.apply_transaction(transaction).unwrap(); - /// - /// node_tree.node_id_at_path(vec![0]).unwrap(); - /// ``` - /// - pub fn insert_nodes_at_path>(self, path: T, nodes: Vec) -> Self { - self.push(NodeOperation::Insert { - path: path.into(), - nodes, - }) + let mut node_id = node_id.unwrap(); + let mut deleted_nodes = vec![]; + for _ in 0..length { + deleted_nodes.push(self.get_deleted_node_data(node_tree, node_id)); + node_id = node_tree.following_siblings(node_id).next().unwrap(); } - /// - /// - /// # Arguments - /// - /// * `path`: the path that is used to save the nodes - /// * `node`: the node data will be saved in the path - /// - /// # Examples - /// - /// ``` - /// // 0 - /// // -- 0 - /// // |-- text - /// use lib_ot::core::{NodeTree, NodeData, TransactionBuilder}; - /// let mut node_tree = NodeTree::default(); - /// let transaction = TransactionBuilder::new() - /// .insert_node_at_path(0, NodeData::new("text")) - /// .build(); - /// node_tree.apply_transaction(transaction).unwrap(); - /// ``` - /// - pub fn insert_node_at_path>(self, path: T, node: NodeData) -> Self { - self.insert_nodes_at_path(path, vec![node]) - } + self.operations.push_op(NodeOperation::Delete { + path: path.clone(), + nodes: deleted_nodes, + }); + self + } - pub fn update_node_at_path>(mut self, path: T, changeset: Changeset) -> Self { - self.operations.push_op(NodeOperation::Update { - path: path.into(), - changeset, - }); - self - } - // - // pub fn update_delta_at_path>( - // mut self, - // path: T, - // new_delta: DeltaTextOperations, - // ) -> Result { - // let path = path.into(); - // let operation: NodeOperation = self - // .operations - // .get(&path) - // .ok_or(Err(OTError::record_not_found().context("Can't found the node")))?; - // - // match operation { - // NodeOperation::Insert { path, nodes } => {} - // NodeOperation::Update { path, changeset } => {} - // NodeOperation::Delete { .. } => {} - // } - // - // match node.body { - // Body::Empty => Ok(self), - // Body::Delta(delta) => { - // let inverted = new_delta.invert(&delta); - // let changeset = Changeset::Delta { - // delta: new_delta, - // inverted, - // }; - // Ok(self.update_node_at_path(path, changeset)) - // } - // } - // } + fn get_deleted_node_data(&self, node_tree: &NodeTree, node_id: NodeId) -> NodeData { + recursive_get_deleted_node_data(node_tree, node_id) + } - pub fn delete_node_at_path(self, node_tree: &NodeTree, path: &Path) -> Self { - self.delete_nodes_at_path(node_tree, path, 1) - } + pub fn push(mut self, op: NodeOperation) -> Self { + self.operations.push_op(op); + self + } - pub fn delete_nodes_at_path(mut self, node_tree: &NodeTree, path: &Path, length: usize) -> Self { - let node_id = node_tree.node_id_at_path(path); - if node_id.is_none() { - tracing::warn!("Path: {:?} doesn't contains any nodes", path); - return self; - } - - let mut node_id = node_id.unwrap(); - let mut deleted_nodes = vec![]; - for _ in 0..length { - deleted_nodes.push(self.get_deleted_node_data(node_tree, node_id)); - node_id = node_tree.following_siblings(node_id).next().unwrap(); - } - - self.operations.push_op(NodeOperation::Delete { - path: path.clone(), - nodes: deleted_nodes, - }); - self - } - - fn get_deleted_node_data(&self, node_tree: &NodeTree, node_id: NodeId) -> NodeData { - recursive_get_deleted_node_data(node_tree, node_id) - } - - pub fn push(mut self, op: NodeOperation) -> Self { - self.operations.push_op(op); - self - } - - pub fn build(self) -> Transaction { - Transaction::from_operations(self.operations) - } + pub fn build(self) -> Transaction { + Transaction::from_operations(self.operations) + } } fn recursive_get_deleted_node_data(node_tree: &NodeTree, node_id: NodeId) -> NodeData { - let node_data = node_tree.get_node(node_id).unwrap(); - let mut children = vec![]; - node_tree.get_children_ids(node_id).into_iter().for_each(|child_id| { - let child = recursive_get_deleted_node_data(node_tree, child_id); - children.push(child); + let node_data = node_tree.get_node(node_id).unwrap(); + let mut children = vec![]; + node_tree + .get_children_ids(node_id) + .into_iter() + .for_each(|child_id| { + let child = recursive_get_deleted_node_data(node_tree, child_id); + children.push(child); }); - NodeData { - node_type: node_data.node_type.clone(), - attributes: node_data.attributes.clone(), - body: node_data.body.clone(), - children, - } + NodeData { + node_type: node_data.node_type.clone(), + attributes: node_data.attributes.clone(), + body: node_data.body.clone(), + children, + } } diff --git a/shared-lib/lib-ot/src/core/node_tree/transaction_serde.rs b/shared-lib/lib-ot/src/core/node_tree/transaction_serde.rs index 5afa20e920..fa1d0ddd8c 100644 --- a/shared-lib/lib-ot/src/core/node_tree/transaction_serde.rs +++ b/shared-lib/lib-ot/src/core/node_tree/transaction_serde.rs @@ -5,25 +5,25 @@ use serde::Serializer; #[allow(dead_code)] pub fn serialize_extension(extension: &Extension, serializer: S) -> Result where - S: Serializer, + S: Serializer, { - match extension { - Extension::Empty => { - let map = serializer.serialize_map(None)?; - map.end() - } - Extension::TextSelection { - before_selection, - after_selection, - } => { - let mut map = serializer.serialize_map(Some(2))?; - map.serialize_key("before_selection")?; - map.serialize_value(before_selection)?; + match extension { + Extension::Empty => { + let map = serializer.serialize_map(None)?; + map.end() + }, + Extension::TextSelection { + before_selection, + after_selection, + } => { + let mut map = serializer.serialize_map(Some(2))?; + map.serialize_key("before_selection")?; + map.serialize_value(before_selection)?; - map.serialize_key("after_selection")?; - map.serialize_value(after_selection)?; + map.serialize_key("after_selection")?; + map.serialize_value(after_selection)?; - map.end() - } - } + map.end() + }, + } } diff --git a/shared-lib/lib-ot/src/core/node_tree/tree.rs b/shared-lib/lib-ot/src/core/node_tree/tree.rs index 78d37f0e08..d2dff890cb 100644 --- a/shared-lib/lib-ot/src/core/node_tree/tree.rs +++ b/shared-lib/lib-ot/src/core/node_tree/tree.rs @@ -9,477 +9,492 @@ pub struct NodeTreeContext {} #[derive(Debug, Clone)] pub struct NodeTree { - arena: Arena, - root: NodeId, - pub context: NodeTreeContext, + arena: Arena, + root: NodeId, + pub context: NodeTreeContext, } impl Default for NodeTree { - fn default() -> Self { - Self::new(NodeTreeContext::default()) - } + fn default() -> Self { + Self::new(NodeTreeContext::default()) + } } pub const PLACEHOLDER_NODE_TYPE: &str = ""; impl NodeTree { - pub fn new(context: NodeTreeContext) -> NodeTree { - let mut arena = Arena::new(); - let root = arena.new_node(Node::new("root")); - NodeTree { arena, root, context } + pub fn new(context: NodeTreeContext) -> NodeTree { + let mut arena = Arena::new(); + let root = arena.new_node(Node::new("root")); + NodeTree { + arena, + root, + context, + } + } + + pub fn from_node_data(node_data: NodeData, context: NodeTreeContext) -> Result { + let mut tree = Self::new(context); + tree.insert_nodes(&0_usize.into(), vec![node_data])?; + Ok(tree) + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + let tree: NodeTree = serde_json::from_slice(bytes).map_err(|e| OTError::serde().context(e))?; + Ok(tree) + } + + pub fn to_bytes(&self) -> Vec { + match serde_json::to_vec(self) { + Ok(bytes) => bytes, + Err(e) => { + tracing::error!("{}", e); + vec![] + }, + } + } + + pub fn to_json(&self, pretty: bool) -> Result { + if pretty { + match serde_json::to_string_pretty(self) { + Ok(json) => Ok(json), + Err(err) => Err(OTError::serde().context(err)), + } + } else { + match serde_json::to_string(self) { + Ok(json) => Ok(json), + Err(err) => Err(OTError::serde().context(err)), + } + } + } + + pub fn from_operations>( + operations: T, + context: NodeTreeContext, + ) -> Result { + let operations = operations.into(); + let mut node_tree = NodeTree::new(context); + for (_, operation) in operations.into_inner().into_iter().enumerate() { + node_tree.apply_op(operation)?; + } + Ok(node_tree) + } + + pub fn from_transaction>( + transaction: T, + context: NodeTreeContext, + ) -> Result { + let transaction = transaction.into(); + let mut tree = Self::new(context); + tree.apply_transaction(transaction)?; + Ok(tree) + } + + pub fn get_node(&self, node_id: NodeId) -> Option<&Node> { + if node_id.is_removed(&self.arena) { + return None; + } + Some(self.arena.get(node_id)?.get()) + } + + pub fn get_node_at_path(&self, path: &Path) -> Option<&Node> { + let node_id = self.node_id_at_path(path)?; + self.get_node(node_id) + } + + pub fn get_node_data_at_path(&self, path: &Path) -> Option { + let node_id = self.node_id_at_path(path)?; + let node_data = self.get_node_data(node_id)?; + Some(node_data) + } + + pub fn get_node_data_at_root(&self) -> Option { + self.get_node_data(self.root) + } + + pub fn get_node_data(&self, node_id: NodeId) -> Option { + let Node { + node_type, + body, + attributes, + } = self.get_node(node_id)?.clone(); + let mut node_data = NodeData::new(node_type); + for (key, value) in attributes.into_inner() { + node_data.attributes.insert(key, value); + } + node_data.body = body; + + let children = self.get_children_ids(node_id); + for child in children.into_iter() { + if let Some(child_node_data) = self.get_node_data(child) { + node_data.children.push(child_node_data); + } + } + Some(node_data) + } + + pub fn root_node_id(&self) -> NodeId { + self.root + } + + pub fn get_children(&self, node_id: NodeId) -> Vec<&Node> { + node_id + .children(&self.arena) + .flat_map(|node_id| self.get_node(node_id)) + .collect() + } + /// Returns a iterator used to iterate over the node ids whose parent node id is node_id + /// + /// * `node_id`: the children's parent node id + /// + pub fn get_children_ids(&self, node_id: NodeId) -> Vec { + node_id.children(&self.arena).collect() + } + + /// Serialize the node to JSON with node_id + pub fn serialize_node(&self, node_id: NodeId, pretty_json: bool) -> Result { + let node_data = self + .get_node_data(node_id) + .ok_or_else(|| OTError::internal().context("Node doesn't exist exist"))?; + if pretty_json { + serde_json::to_string_pretty(&node_data).map_err(|err| OTError::serde().context(err)) + } else { + serde_json::to_string(&node_data).map_err(|err| OTError::serde().context(err)) + } + } + + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// use lib_ot::core::{NodeOperation, NodeTree, NodeData, Path}; + /// let nodes = vec![NodeData::new("text".to_string())]; + /// let root_path: Path = vec![0].into(); + /// let op = NodeOperation::Insert {path: root_path.clone(),nodes }; + /// + /// let mut node_tree = NodeTree::default(); + /// node_tree.apply_op(Arc::new(op)).unwrap(); + /// let node_id = node_tree.node_id_at_path(&root_path).unwrap(); + /// let node_path = node_tree.path_from_node_id(node_id); + /// debug_assert_eq!(node_path, root_path); + /// ``` + pub fn node_id_at_path>(&self, path: T) -> Option { + let path = path.into(); + if !path.is_valid() { + return None; } - pub fn from_node_data(node_data: NodeData, context: NodeTreeContext) -> Result { - let mut tree = Self::new(context); - tree.insert_nodes(&0_usize.into(), vec![node_data])?; - Ok(tree) + let mut node_id = self.root; + for id in path.iter() { + node_id = self.node_id_from_parent_at_index(node_id, *id)?; } - pub fn from_bytes(bytes: &[u8]) -> Result { - let tree: NodeTree = serde_json::from_slice(bytes).map_err(|e| OTError::serde().context(e))?; - Ok(tree) + if node_id.is_removed(&self.arena) { + return None; + } + Some(node_id) + } + + pub fn path_from_node_id(&self, node_id: NodeId) -> Path { + let mut path = vec![]; + let mut current_node = node_id; + // Use .skip(1) on the ancestors iterator to skip the root node. + let ancestors = node_id.ancestors(&self.arena).skip(1); + for parent_node in ancestors { + let counter = self.index_of_node(parent_node, current_node); + path.push(counter); + current_node = parent_node; + } + path.reverse(); + Path(path) + } + + fn index_of_node(&self, parent_node: NodeId, child_node: NodeId) -> usize { + let mut counter: usize = 0; + let iter = parent_node.children(&self.arena); + for node in iter { + if node == child_node { + return counter; + } + counter += 1; } - pub fn to_bytes(&self) -> Vec { - match serde_json::to_vec(self) { - Ok(bytes) => bytes, - Err(e) => { - tracing::error!("{}", e); - vec![] + counter + } + + /// Returns the note_id at the index of the tree which its id is note_id + /// # Arguments + /// + /// * `node_id`: the node id of the child's parent + /// * `index`: index of the node in parent children list + /// + /// returns: Option + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// use lib_ot::core::{NodeOperation, NodeTree, NodeData, Path}; + /// let node_1 = NodeData::new("text".to_string()); + /// let inserted_path: Path = vec![0].into(); + /// + /// let mut node_tree = NodeTree::default(); + /// let op = NodeOperation::Insert {path: inserted_path.clone(),nodes: vec![node_1.clone()] }; + /// node_tree.apply_op(Arc::new(op)).unwrap(); + /// + /// let node_2 = node_tree.get_node_at_path(&inserted_path).unwrap(); + /// assert_eq!(node_2.node_type, node_1.node_type); + /// ``` + pub fn node_id_from_parent_at_index(&self, node_id: NodeId, index: usize) -> Option { + let children = node_id.children(&self.arena); + for (counter, child) in children.enumerate() { + if counter == index { + return Some(child); + } + } + + None + } + + /// + /// # Arguments + /// + /// * `node_id`: if the node_is is None, then will use root node_id. + /// + /// returns number of the children of the root node + /// + pub fn number_of_children(&self, node_id: Option) -> usize { + match node_id { + None => self.root.children(&self.arena).count(), + Some(node_id) => node_id.children(&self.arena).count(), + } + } + + pub fn following_siblings(&self, node_id: NodeId) -> FollowingSiblings<'_, Node> { + node_id.following_siblings(&self.arena) + } + + pub fn apply_transaction(&mut self, transaction: Transaction) -> Result<(), OTError> { + let operations = transaction.split().0; + for operation in operations { + self.apply_op(operation)?; + } + + Ok(()) + } + + pub fn apply_op>>(&mut self, op: T) -> Result<(), OTError> { + let op = match Arc::try_unwrap(op.into()) { + Ok(op) => op, + Err(op) => op.as_ref().clone(), + }; + + match op { + NodeOperation::Insert { path, nodes } => self.insert_nodes(&path, nodes), + NodeOperation::Update { path, changeset } => self.update(&path, changeset), + NodeOperation::Delete { path, nodes: _ } => self.delete_node(&path), + } + } + /// Inserts nodes at given path + /// root + /// 0 - A + /// 0 - A1 + /// 1 - B + /// 0 - B1 + /// 1 - B2 + /// + /// The path of each node will be: + /// A: [0] + /// A1: [0,0] + /// B: [1] + /// B1: [1,0] + /// B2: [1,1] + /// + /// When inserting multiple nodes into the same path, each of them will be appended to the root + /// node. For example. The path is [0] and the nodes are [A, B, C]. After inserting the nodes, + /// the tree will be: + /// root + /// 0: A + /// 1: B + /// 2: C + /// + /// returns error if the path is empty + /// + fn insert_nodes(&mut self, path: &Path, nodes: Vec) -> Result<(), OTError> { + if !path.is_valid() { + return Err(OTErrorCode::InvalidPath.into()); + } + + let (parent_path, last_path) = path.split_at(path.0.len() - 1); + let last_index = *last_path.first().unwrap(); + if parent_path.is_empty() { + self.insert_nodes_at_index(self.root, last_index, nodes) + } else { + let parent_node = match self.node_id_at_path(parent_path) { + None => self.create_adjacent_nodes_for_path(parent_path), + Some(parent_node) => parent_node, + }; + + self.insert_nodes_at_index(parent_node, last_index, nodes) + } + } + + /// Create the adjacent nodes for the path + /// + /// It will create a corresponding node for each node on the path if it's not existing. + /// If the path is not start from zero, it will create its siblings. + /// + /// Check out the operation_insert_test.rs for more examples. + /// * operation_insert_node_when_its_parent_is_not_exist + /// * operation_insert_node_when_multiple_parent_is_not_exist_test + /// + /// # Arguments + /// + /// * `path`: creates nodes for this path + /// + /// returns: NodeId + /// + fn create_adjacent_nodes_for_path>(&mut self, path: T) -> NodeId { + let path = path.into(); + let mut node_id = self.root; + for id in path.iter() { + match self.node_id_from_parent_at_index(node_id, *id) { + None => { + let num_of_children = node_id.children(&self.arena).count(); + if *id > num_of_children { + for _ in 0..(*id - num_of_children) { + let node: Node = placeholder_node().into(); + let sibling_node = self.arena.new_node(node); + node_id.append(sibling_node, &mut self.arena); } - } + } + + let node: Node = placeholder_node().into(); + let new_node_id = self.arena.new_node(node); + node_id.append(new_node_id, &mut self.arena); + node_id = new_node_id; + }, + Some(next_node_id) => { + node_id = next_node_id; + }, + } + } + node_id + } + + /// Inserts nodes before the node with node_id + /// + fn insert_nodes_before(&mut self, node_id: &NodeId, nodes: Vec) { + if node_id.is_removed(&self.arena) { + tracing::warn!("Node:{:?} is remove before insert", node_id); + return; + } + for node in nodes { + let (node, children) = node.split(); + let new_node_id = self.arena.new_node(node); + node_id.insert_before(new_node_id, &mut self.arena); + self.append_nodes(&new_node_id, children); + } + } + + fn insert_nodes_at_index( + &mut self, + parent: NodeId, + index: usize, + nodes: Vec, + ) -> Result<(), OTError> { + if index == 0 && parent.children(&self.arena).next().is_none() { + self.append_nodes(&parent, nodes); + return Ok(()); } - pub fn to_json(&self, pretty: bool) -> Result { - if pretty { - match serde_json::to_string_pretty(self) { - Ok(json) => Ok(json), - Err(err) => Err(OTError::serde().context(err)), - } - } else { - match serde_json::to_string(self) { - Ok(json) => Ok(json), - Err(err) => Err(OTError::serde().context(err)), - } - } + // Append the node to the end of the children list if index greater or equal to the + // length of the children. + let num_of_children = parent.children(&self.arena).count(); + if index >= num_of_children { + let mut num_of_nodes_to_insert = index - num_of_children; + while num_of_nodes_to_insert > 0 { + self.append_nodes(&parent, vec![placeholder_node()]); + num_of_nodes_to_insert -= 1; + } + + self.append_nodes(&parent, nodes); + return Ok(()); } - pub fn from_operations>(operations: T, context: NodeTreeContext) -> Result { - let operations = operations.into(); - let mut node_tree = NodeTree::new(context); - for (_, operation) in operations.into_inner().into_iter().enumerate() { - node_tree.apply_op(operation)?; - } - Ok(node_tree) + let node_to_insert = self + .node_id_from_parent_at_index(parent, index) + .ok_or_else(|| OTError::internal().context(format!("Can't find the node at {}", index)))?; + + self.insert_nodes_before(&node_to_insert, nodes); + Ok(()) + } + + fn append_nodes(&mut self, parent: &NodeId, nodes: Vec) { + for node in nodes { + let (node, children) = node.split(); + let new_node_id = self.arena.new_node(node); + parent.append(new_node_id, &mut self.arena); + + self.append_nodes(&new_node_id, children); + } + } + + /// Removes a node and its descendants from the tree + fn delete_node(&mut self, path: &Path) -> Result<(), OTError> { + if !path.is_valid() { + return Err(OTErrorCode::InvalidPath.into()); + } + match self.node_id_at_path(path) { + None => tracing::warn!("Can't find any node at path: {:?}", path), + Some(node) => { + node.remove_subtree(&mut self.arena); + }, } - pub fn from_transaction>(transaction: T, context: NodeTreeContext) -> Result { - let transaction = transaction.into(); - let mut tree = Self::new(context); - tree.apply_transaction(transaction)?; - Ok(tree) + Ok(()) + } + + /// Update the node at path with the `changeset` + /// + /// Do nothing if there is no node at the path. + /// + /// # Arguments + /// + /// * `path`: references to the node that will be applied with the changeset + /// * `changeset`: the change that will be applied to the node + /// + /// returns: Result<(), OTError> + fn update(&mut self, path: &Path, changeset: Changeset) -> Result<(), OTError> { + match self.mut_node_at_path(path, |node| node.apply_changeset(changeset)) { + Ok(_) => {}, + Err(err) => tracing::error!("{}", err), } + Ok(()) + } - pub fn get_node(&self, node_id: NodeId) -> Option<&Node> { - if node_id.is_removed(&self.arena) { - return None; - } - Some(self.arena.get(node_id)?.get()) + fn mut_node_at_path(&mut self, path: &Path, f: F) -> Result<(), OTError> + where + F: FnOnce(&mut Node) -> Result<(), OTError>, + { + if !path.is_valid() { + return Err(OTErrorCode::InvalidPath.into()); } - - pub fn get_node_at_path(&self, path: &Path) -> Option<&Node> { - let node_id = self.node_id_at_path(path)?; - self.get_node(node_id) - } - - pub fn get_node_data_at_path(&self, path: &Path) -> Option { - let node_id = self.node_id_at_path(path)?; - let node_data = self.get_node_data(node_id)?; - Some(node_data) - } - - pub fn get_node_data_at_root(&self) -> Option { - self.get_node_data(self.root) - } - - pub fn get_node_data(&self, node_id: NodeId) -> Option { - let Node { - node_type, - body, - attributes, - } = self.get_node(node_id)?.clone(); - let mut node_data = NodeData::new(node_type); - for (key, value) in attributes.into_inner() { - node_data.attributes.insert(key, value); - } - node_data.body = body; - - let children = self.get_children_ids(node_id); - for child in children.into_iter() { - if let Some(child_node_data) = self.get_node_data(child) { - node_data.children.push(child_node_data); - } - } - Some(node_data) - } - - pub fn root_node_id(&self) -> NodeId { - self.root - } - - pub fn get_children(&self, node_id: NodeId) -> Vec<&Node> { - node_id - .children(&self.arena) - .flat_map(|node_id| self.get_node(node_id)) - .collect() - } - /// Returns a iterator used to iterate over the node ids whose parent node id is node_id - /// - /// * `node_id`: the children's parent node id - /// - pub fn get_children_ids(&self, node_id: NodeId) -> Vec { - node_id.children(&self.arena).collect() - } - - /// Serialize the node to JSON with node_id - pub fn serialize_node(&self, node_id: NodeId, pretty_json: bool) -> Result { - let node_data = self - .get_node_data(node_id) - .ok_or_else(|| OTError::internal().context("Node doesn't exist exist"))?; - if pretty_json { - serde_json::to_string_pretty(&node_data).map_err(|err| OTError::serde().context(err)) - } else { - serde_json::to_string(&node_data).map_err(|err| OTError::serde().context(err)) - } - } - - /// - /// # Examples - /// - /// ``` - /// use std::sync::Arc; - /// use lib_ot::core::{NodeOperation, NodeTree, NodeData, Path}; - /// let nodes = vec![NodeData::new("text".to_string())]; - /// let root_path: Path = vec![0].into(); - /// let op = NodeOperation::Insert {path: root_path.clone(),nodes }; - /// - /// let mut node_tree = NodeTree::default(); - /// node_tree.apply_op(Arc::new(op)).unwrap(); - /// let node_id = node_tree.node_id_at_path(&root_path).unwrap(); - /// let node_path = node_tree.path_from_node_id(node_id); - /// debug_assert_eq!(node_path, root_path); - /// ``` - pub fn node_id_at_path>(&self, path: T) -> Option { - let path = path.into(); - if !path.is_valid() { - return None; - } - - let mut node_id = self.root; - for id in path.iter() { - node_id = self.node_id_from_parent_at_index(node_id, *id)?; - } - - if node_id.is_removed(&self.arena) { - return None; - } - Some(node_id) - } - - pub fn path_from_node_id(&self, node_id: NodeId) -> Path { - let mut path = vec![]; - let mut current_node = node_id; - // Use .skip(1) on the ancestors iterator to skip the root node. - let ancestors = node_id.ancestors(&self.arena).skip(1); - for parent_node in ancestors { - let counter = self.index_of_node(parent_node, current_node); - path.push(counter); - current_node = parent_node; - } - path.reverse(); - Path(path) - } - - fn index_of_node(&self, parent_node: NodeId, child_node: NodeId) -> usize { - let mut counter: usize = 0; - let iter = parent_node.children(&self.arena); - for node in iter { - if node == child_node { - return counter; - } - counter += 1; - } - - counter - } - - /// Returns the note_id at the index of the tree which its id is note_id - /// # Arguments - /// - /// * `node_id`: the node id of the child's parent - /// * `index`: index of the node in parent children list - /// - /// returns: Option - /// - /// # Examples - /// - /// ``` - /// use std::sync::Arc; - /// use lib_ot::core::{NodeOperation, NodeTree, NodeData, Path}; - /// let node_1 = NodeData::new("text".to_string()); - /// let inserted_path: Path = vec![0].into(); - /// - /// let mut node_tree = NodeTree::default(); - /// let op = NodeOperation::Insert {path: inserted_path.clone(),nodes: vec![node_1.clone()] }; - /// node_tree.apply_op(Arc::new(op)).unwrap(); - /// - /// let node_2 = node_tree.get_node_at_path(&inserted_path).unwrap(); - /// assert_eq!(node_2.node_type, node_1.node_type); - /// ``` - pub fn node_id_from_parent_at_index(&self, node_id: NodeId, index: usize) -> Option { - let children = node_id.children(&self.arena); - for (counter, child) in children.enumerate() { - if counter == index { - return Some(child); - } - } - - None - } - - /// - /// # Arguments - /// - /// * `node_id`: if the node_is is None, then will use root node_id. - /// - /// returns number of the children of the root node - /// - pub fn number_of_children(&self, node_id: Option) -> usize { - match node_id { - None => self.root.children(&self.arena).count(), - Some(node_id) => node_id.children(&self.arena).count(), - } - } - - pub fn following_siblings(&self, node_id: NodeId) -> FollowingSiblings<'_, Node> { - node_id.following_siblings(&self.arena) - } - - pub fn apply_transaction(&mut self, transaction: Transaction) -> Result<(), OTError> { - let operations = transaction.split().0; - for operation in operations { - self.apply_op(operation)?; - } - - Ok(()) - } - - pub fn apply_op>>(&mut self, op: T) -> Result<(), OTError> { - let op = match Arc::try_unwrap(op.into()) { - Ok(op) => op, - Err(op) => op.as_ref().clone(), - }; - - match op { - NodeOperation::Insert { path, nodes } => self.insert_nodes(&path, nodes), - NodeOperation::Update { path, changeset } => self.update(&path, changeset), - NodeOperation::Delete { path, nodes: _ } => self.delete_node(&path), - } - } - /// Inserts nodes at given path - /// root - /// 0 - A - /// 0 - A1 - /// 1 - B - /// 0 - B1 - /// 1 - B2 - /// - /// The path of each node will be: - /// A: [0] - /// A1: [0,0] - /// B: [1] - /// B1: [1,0] - /// B2: [1,1] - /// - /// When inserting multiple nodes into the same path, each of them will be appended to the root - /// node. For example. The path is [0] and the nodes are [A, B, C]. After inserting the nodes, - /// the tree will be: - /// root - /// 0: A - /// 1: B - /// 2: C - /// - /// returns error if the path is empty - /// - fn insert_nodes(&mut self, path: &Path, nodes: Vec) -> Result<(), OTError> { - if !path.is_valid() { - return Err(OTErrorCode::InvalidPath.into()); - } - - let (parent_path, last_path) = path.split_at(path.0.len() - 1); - let last_index = *last_path.first().unwrap(); - if parent_path.is_empty() { - self.insert_nodes_at_index(self.root, last_index, nodes) - } else { - let parent_node = match self.node_id_at_path(parent_path) { - None => self.create_adjacent_nodes_for_path(parent_path), - Some(parent_node) => parent_node, - }; - - self.insert_nodes_at_index(parent_node, last_index, nodes) - } - } - - /// Create the adjacent nodes for the path - /// - /// It will create a corresponding node for each node on the path if it's not existing. - /// If the path is not start from zero, it will create its siblings. - /// - /// Check out the operation_insert_test.rs for more examples. - /// * operation_insert_node_when_its_parent_is_not_exist - /// * operation_insert_node_when_multiple_parent_is_not_exist_test - /// - /// # Arguments - /// - /// * `path`: creates nodes for this path - /// - /// returns: NodeId - /// - fn create_adjacent_nodes_for_path>(&mut self, path: T) -> NodeId { - let path = path.into(); - let mut node_id = self.root; - for id in path.iter() { - match self.node_id_from_parent_at_index(node_id, *id) { - None => { - let num_of_children = node_id.children(&self.arena).count(); - if *id > num_of_children { - for _ in 0..(*id - num_of_children) { - let node: Node = placeholder_node().into(); - let sibling_node = self.arena.new_node(node); - node_id.append(sibling_node, &mut self.arena); - } - } - - let node: Node = placeholder_node().into(); - let new_node_id = self.arena.new_node(node); - node_id.append(new_node_id, &mut self.arena); - node_id = new_node_id; - } - Some(next_node_id) => { - node_id = next_node_id; - } - } - } - node_id - } - - /// Inserts nodes before the node with node_id - /// - fn insert_nodes_before(&mut self, node_id: &NodeId, nodes: Vec) { - if node_id.is_removed(&self.arena) { - tracing::warn!("Node:{:?} is remove before insert", node_id); - return; - } - for node in nodes { - let (node, children) = node.split(); - let new_node_id = self.arena.new_node(node); - node_id.insert_before(new_node_id, &mut self.arena); - self.append_nodes(&new_node_id, children); - } - } - - fn insert_nodes_at_index(&mut self, parent: NodeId, index: usize, nodes: Vec) -> Result<(), OTError> { - if index == 0 && parent.children(&self.arena).next().is_none() { - self.append_nodes(&parent, nodes); - return Ok(()); - } - - // Append the node to the end of the children list if index greater or equal to the - // length of the children. - let num_of_children = parent.children(&self.arena).count(); - if index >= num_of_children { - let mut num_of_nodes_to_insert = index - num_of_children; - while num_of_nodes_to_insert > 0 { - self.append_nodes(&parent, vec![placeholder_node()]); - num_of_nodes_to_insert -= 1; - } - - self.append_nodes(&parent, nodes); - return Ok(()); - } - - let node_to_insert = self - .node_id_from_parent_at_index(parent, index) - .ok_or_else(|| OTError::internal().context(format!("Can't find the node at {}", index)))?; - - self.insert_nodes_before(&node_to_insert, nodes); - Ok(()) - } - - fn append_nodes(&mut self, parent: &NodeId, nodes: Vec) { - for node in nodes { - let (node, children) = node.split(); - let new_node_id = self.arena.new_node(node); - parent.append(new_node_id, &mut self.arena); - - self.append_nodes(&new_node_id, children); - } - } - - /// Removes a node and its descendants from the tree - fn delete_node(&mut self, path: &Path) -> Result<(), OTError> { - if !path.is_valid() { - return Err(OTErrorCode::InvalidPath.into()); - } - match self.node_id_at_path(path) { - None => tracing::warn!("Can't find any node at path: {:?}", path), - Some(node) => { - node.remove_subtree(&mut self.arena); - } - } - - Ok(()) - } - - /// Update the node at path with the `changeset` - /// - /// Do nothing if there is no node at the path. - /// - /// # Arguments - /// - /// * `path`: references to the node that will be applied with the changeset - /// * `changeset`: the change that will be applied to the node - /// - /// returns: Result<(), OTError> - fn update(&mut self, path: &Path, changeset: Changeset) -> Result<(), OTError> { - match self.mut_node_at_path(path, |node| node.apply_changeset(changeset)) { - Ok(_) => {} - Err(err) => tracing::error!("{}", err), - } - Ok(()) - } - - fn mut_node_at_path(&mut self, path: &Path, f: F) -> Result<(), OTError> - where - F: FnOnce(&mut Node) -> Result<(), OTError>, - { - if !path.is_valid() { - return Err(OTErrorCode::InvalidPath.into()); - } - let node_id = self.node_id_at_path(path).ok_or_else(|| { - OTError::path_not_found().context(format!("Can't find the mutated node at path: {:?}", path)) - })?; - match self.arena.get_mut(node_id) { - None => tracing::warn!("The path: {:?} does not contain any nodes", path), - Some(node) => { - let node = node.get_mut(); - f(node)?; - } - } - Ok(()) + let node_id = self.node_id_at_path(path).ok_or_else(|| { + OTError::path_not_found().context(format!("Can't find the mutated node at path: {:?}", path)) + })?; + match self.arena.get_mut(node_id) { + None => tracing::warn!("The path: {:?} does not contain any nodes", path), + Some(node) => { + let node = node.get_mut(); + f(node)?; + }, } + Ok(()) + } } pub fn placeholder_node() -> NodeData { - NodeData::new(PLACEHOLDER_NODE_TYPE) + NodeData::new(PLACEHOLDER_NODE_TYPE) } diff --git a/shared-lib/lib-ot/src/core/node_tree/tree_serde.rs b/shared-lib/lib-ot/src/core/node_tree/tree_serde.rs index 366e1d0561..dbf87c5940 100644 --- a/shared-lib/lib-ot/src/core/node_tree/tree_serde.rs +++ b/shared-lib/lib-ot/src/core/node_tree/tree_serde.rs @@ -7,57 +7,57 @@ use std::fmt; use std::marker::PhantomData; impl Serialize for NodeTree { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let root_node_id = self.root_node_id(); - let mut children = self.get_children_ids(root_node_id); - if children.is_empty() { - return serializer.serialize_str(""); - } - if children.len() == 1 { - let node_id = children.pop().unwrap(); - match self.get_node_data(node_id) { - None => serializer.serialize_str(""), - Some(node_data) => node_data.serialize(serializer), - } - } else { - let mut seq = serializer.serialize_seq(Some(children.len()))?; - for child in children { - if let Some(child_node_data) = self.get_node_data(child) { - seq.serialize_element(&child_node_data)?; - } - } - seq.end() - } + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let root_node_id = self.root_node_id(); + let mut children = self.get_children_ids(root_node_id); + if children.is_empty() { + return serializer.serialize_str(""); } + if children.len() == 1 { + let node_id = children.pop().unwrap(); + match self.get_node_data(node_id) { + None => serializer.serialize_str(""), + Some(node_data) => node_data.serialize(serializer), + } + } else { + let mut seq = serializer.serialize_seq(Some(children.len()))?; + for child in children { + if let Some(child_node_data) = self.get_node_data(child) { + seq.serialize_element(&child_node_data)?; + } + } + seq.end() + } + } } impl<'de> Deserialize<'de> for NodeTree { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct NodeTreeVisitor(PhantomData); + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct NodeTreeVisitor(PhantomData); - impl<'de> Visitor<'de> for NodeTreeVisitor { - type Value = NodeData; + impl<'de> Visitor<'de> for NodeTreeVisitor { + type Value = NodeData; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("Expected node data tree") - } + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Expected node data tree") + } - fn visit_map(self, map: V) -> Result - where - V: MapAccess<'de>, - { - // Forward the deserialization to NodeData - Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)) - } - } - - let node_data: NodeData = deserializer.deserialize_any(NodeTreeVisitor(PhantomData))?; - Ok(NodeTree::from_node_data(node_data, NodeTreeContext::default()).unwrap()) + fn visit_map(self, map: V) -> Result + where + V: MapAccess<'de>, + { + // Forward the deserialization to NodeData + Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)) + } } + + let node_data: NodeData = deserializer.deserialize_any(NodeTreeVisitor(PhantomData))?; + Ok(NodeTree::from_node_data(node_data, NodeTreeContext::default()).unwrap()) + } } diff --git a/shared-lib/lib-ot/src/core/ot_str.rs b/shared-lib/lib-ot/src/core/ot_str.rs index 7f84de6d20..2063a713d4 100644 --- a/shared-lib/lib-ot/src/core/ot_str.rs +++ b/shared-lib/lib-ot/src/core/ot_str.rs @@ -6,327 +6,327 @@ use std::{fmt, fmt::Formatter}; pub struct OTString(pub String); impl OTString { - /// Returns the number of UTF-16 code units in this string. - /// - /// The length of strings behaves differently in different languages. For example: [Dart] string's - /// length is calculated with UTF-16 code units. The method [utf16_len] returns the length of a - /// String in UTF-16 code units. - /// - /// # Examples - /// - /// ``` - /// use lib_ot::core::OTString; - /// let utf16_len = OTString::from("👋").utf16_len(); - /// assert_eq!(utf16_len, 2); - /// let bytes_len = String::from("👋").len(); - /// assert_eq!(bytes_len, 4); - /// - /// ``` - pub fn utf16_len(&self) -> usize { - count_utf16_code_units(&self.0) + /// Returns the number of UTF-16 code units in this string. + /// + /// The length of strings behaves differently in different languages. For example: [Dart] string's + /// length is calculated with UTF-16 code units. The method [utf16_len] returns the length of a + /// String in UTF-16 code units. + /// + /// # Examples + /// + /// ``` + /// use lib_ot::core::OTString; + /// let utf16_len = OTString::from("👋").utf16_len(); + /// assert_eq!(utf16_len, 2); + /// let bytes_len = String::from("👋").len(); + /// assert_eq!(bytes_len, 4); + /// + /// ``` + pub fn utf16_len(&self) -> usize { + count_utf16_code_units(&self.0) + } + + pub fn utf16_iter(&self) -> Utf16CodeUnitIterator { + Utf16CodeUnitIterator::new(self) + } + + /// Returns a new string with the given [Interval] + /// # Examples + /// + /// ``` + /// use lib_ot::core::{OTString, Interval}; + /// let s: OTString = "你好\n😁".into(); + /// assert_eq!(s.utf16_len(), 5); + /// let output1 = s.sub_str(Interval::new(0, 2)).unwrap(); + /// assert_eq!(output1, "你好"); + /// + /// let output2 = s.sub_str(Interval::new(2, 3)).unwrap(); + /// assert_eq!(output2, "\n"); + /// + /// let output3 = s.sub_str(Interval::new(3, 5)).unwrap(); + /// assert_eq!(output3, "😁"); + /// ``` + pub fn sub_str(&self, interval: Interval) -> Option { + let mut iter = Utf16CodeUnitIterator::new(self); + let mut buf = vec![]; + while let Some((byte, _len)) = iter.next() { + if iter.utf16_offset >= interval.start && iter.utf16_offset < interval.end { + buf.extend_from_slice(byte); + } } - pub fn utf16_iter(&self) -> Utf16CodeUnitIterator { - Utf16CodeUnitIterator::new(self) + if buf.is_empty() { + return None; } - /// Returns a new string with the given [Interval] - /// # Examples - /// - /// ``` - /// use lib_ot::core::{OTString, Interval}; - /// let s: OTString = "你好\n😁".into(); - /// assert_eq!(s.utf16_len(), 5); - /// let output1 = s.sub_str(Interval::new(0, 2)).unwrap(); - /// assert_eq!(output1, "你好"); - /// - /// let output2 = s.sub_str(Interval::new(2, 3)).unwrap(); - /// assert_eq!(output2, "\n"); - /// - /// let output3 = s.sub_str(Interval::new(3, 5)).unwrap(); - /// assert_eq!(output3, "😁"); - /// ``` - pub fn sub_str(&self, interval: Interval) -> Option { - let mut iter = Utf16CodeUnitIterator::new(self); - let mut buf = vec![]; - while let Some((byte, _len)) = iter.next() { - if iter.utf16_offset >= interval.start && iter.utf16_offset < interval.end { - buf.extend_from_slice(byte); - } - } - - if buf.is_empty() { - return None; - } - - match str::from_utf8(&buf) { - Ok(item) => Some(item.to_owned()), - Err(_e) => None, - } + match str::from_utf8(&buf) { + Ok(item) => Some(item.to_owned()), + Err(_e) => None, } + } - /// Return a new string with the given [Interval] - /// # Examples - /// - /// ``` - /// use lib_ot::core::OTString; - /// let s: OTString = "👋😁👋".into(); /// - /// let mut iter = s.utf16_code_point_iter(); - /// assert_eq!(iter.next().unwrap(), "👋".to_string()); - /// assert_eq!(iter.next().unwrap(), "😁".to_string()); - /// assert_eq!(iter.next().unwrap(), "👋".to_string()); - /// assert_eq!(iter.next(), None); - /// - /// let s: OTString = "👋12ab一二👋".into(); /// - /// let mut iter = s.utf16_code_point_iter(); - /// assert_eq!(iter.next().unwrap(), "👋".to_string()); - /// assert_eq!(iter.next().unwrap(), "1".to_string()); - /// assert_eq!(iter.next().unwrap(), "2".to_string()); - /// - /// assert_eq!(iter.skip(OTString::from("ab一二").utf16_len()).next().unwrap(), "👋".to_string()); - /// ``` - #[allow(dead_code)] - pub fn utf16_code_point_iter(&self) -> OTUtf16CodePointIterator { - OTUtf16CodePointIterator::new(self, 0) - } + /// Return a new string with the given [Interval] + /// # Examples + /// + /// ``` + /// use lib_ot::core::OTString; + /// let s: OTString = "👋😁👋".into(); /// + /// let mut iter = s.utf16_code_point_iter(); + /// assert_eq!(iter.next().unwrap(), "👋".to_string()); + /// assert_eq!(iter.next().unwrap(), "😁".to_string()); + /// assert_eq!(iter.next().unwrap(), "👋".to_string()); + /// assert_eq!(iter.next(), None); + /// + /// let s: OTString = "👋12ab一二👋".into(); /// + /// let mut iter = s.utf16_code_point_iter(); + /// assert_eq!(iter.next().unwrap(), "👋".to_string()); + /// assert_eq!(iter.next().unwrap(), "1".to_string()); + /// assert_eq!(iter.next().unwrap(), "2".to_string()); + /// + /// assert_eq!(iter.skip(OTString::from("ab一二").utf16_len()).next().unwrap(), "👋".to_string()); + /// ``` + #[allow(dead_code)] + pub fn utf16_code_point_iter(&self) -> OTUtf16CodePointIterator { + OTUtf16CodePointIterator::new(self, 0) + } } impl std::ops::Deref for OTString { - type Target = String; + type Target = String; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } impl std::ops::DerefMut for OTString { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } } impl std::convert::From for OTString { - fn from(s: String) -> Self { - OTString(s) - } + fn from(s: String) -> Self { + OTString(s) + } } impl std::convert::From<&str> for OTString { - fn from(s: &str) -> Self { - s.to_owned().into() - } + fn from(s: &str) -> Self { + s.to_owned().into() + } } impl std::fmt::Display for OTString { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0) - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) + } } impl std::ops::Add<&str> for OTString { - type Output = OTString; + type Output = OTString; - fn add(self, rhs: &str) -> OTString { - let new_value = self.0 + rhs; - new_value.into() - } + fn add(self, rhs: &str) -> OTString { + let new_value = self.0 + rhs; + new_value.into() + } } impl std::ops::AddAssign<&str> for OTString { - fn add_assign(&mut self, rhs: &str) { - self.0 += rhs; - } + fn add_assign(&mut self, rhs: &str) { + self.0 += rhs; + } } impl Serialize for OTString { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.0) - } + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.0) + } } impl<'de> Deserialize<'de> for OTString { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct OTStringVisitor; + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct OTStringVisitor; - impl<'de> Visitor<'de> for OTStringVisitor { - type Value = OTString; + impl<'de> Visitor<'de> for OTStringVisitor { + type Value = OTString; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a str") - } + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a str") + } - fn visit_str(self, s: &str) -> Result - where - E: de::Error, - { - Ok(s.into()) - } - } - deserializer.deserialize_str(OTStringVisitor) + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + Ok(s.into()) + } } + deserializer.deserialize_str(OTStringVisitor) + } } pub struct Utf16CodeUnitIterator<'a> { - s: &'a OTString, - byte_offset: usize, - utf16_offset: usize, - utf16_count: usize, + s: &'a OTString, + byte_offset: usize, + utf16_offset: usize, + utf16_count: usize, } impl<'a> Utf16CodeUnitIterator<'a> { - pub fn new(s: &'a OTString) -> Self { - Utf16CodeUnitIterator { - s, - byte_offset: 0, - utf16_offset: 0, - utf16_count: 0, - } + pub fn new(s: &'a OTString) -> Self { + Utf16CodeUnitIterator { + s, + byte_offset: 0, + utf16_offset: 0, + utf16_count: 0, } + } } impl<'a> Iterator for Utf16CodeUnitIterator<'a> { - type Item = (&'a [u8], usize); + type Item = (&'a [u8], usize); - fn next(&mut self) -> Option { - let _len = self.s.len(); - if self.byte_offset == self.s.len() { - None - } else { - let b = self.s.as_bytes()[self.byte_offset]; - let start = self.byte_offset; - let end = self.byte_offset + len_utf8_from_first_byte(b); - if (b as i8) >= -0x40 { - self.utf16_count += 1; - } - if b >= 0xf0 { - self.utf16_count += 1; - } + fn next(&mut self) -> Option { + let _len = self.s.len(); + if self.byte_offset == self.s.len() { + None + } else { + let b = self.s.as_bytes()[self.byte_offset]; + let start = self.byte_offset; + let end = self.byte_offset + len_utf8_from_first_byte(b); + if (b as i8) >= -0x40 { + self.utf16_count += 1; + } + if b >= 0xf0 { + self.utf16_count += 1; + } - if self.utf16_count > 0 { - self.utf16_offset = self.utf16_count - 1; - } - self.byte_offset = end; - let byte = &self.s.as_bytes()[start..end]; - Some((byte, end - start)) - } + if self.utf16_count > 0 { + self.utf16_offset = self.utf16_count - 1; + } + self.byte_offset = end; + let byte = &self.s.as_bytes()[start..end]; + Some((byte, end - start)) } + } } pub struct OTUtf16CodePointIterator<'a> { - s: &'a OTString, - offset: usize, + s: &'a OTString, + offset: usize, } impl<'a> OTUtf16CodePointIterator<'a> { - pub fn new(s: &'a OTString, offset: usize) -> Self { - OTUtf16CodePointIterator { s, offset } - } + pub fn new(s: &'a OTString, offset: usize) -> Self { + OTUtf16CodePointIterator { s, offset } + } } use crate::core::interval::Interval; use std::str; impl<'a> Iterator for OTUtf16CodePointIterator<'a> { - type Item = String; + type Item = String; - fn next(&mut self) -> Option { - if self.offset == self.s.len() { - None - } else { - let byte = self.s.as_bytes()[self.offset]; - let end = len_utf8_from_first_byte(byte); - let buf = &self.s.as_bytes()[self.offset..self.offset + end]; - self.offset += end; - match str::from_utf8(buf) { - Ok(item) => Some(item.to_string()), - Err(_e) => None, - } - } + fn next(&mut self) -> Option { + if self.offset == self.s.len() { + None + } else { + let byte = self.s.as_bytes()[self.offset]; + let end = len_utf8_from_first_byte(byte); + let buf = &self.s.as_bytes()[self.offset..self.offset + end]; + self.offset += end; + match str::from_utf8(buf) { + Ok(item) => Some(item.to_string()), + Err(_e) => None, + } } + } } pub fn count_utf16_code_units(s: &str) -> usize { - let mut utf16_count = 0; - for &b in s.as_bytes() { - if (b as i8) >= -0x40 { - utf16_count += 1; - } - if b >= 0xf0 { - utf16_count += 1; - } + let mut utf16_count = 0; + for &b in s.as_bytes() { + if (b as i8) >= -0x40 { + utf16_count += 1; } - utf16_count + if b >= 0xf0 { + utf16_count += 1; + } + } + utf16_count } /// Given the initial byte of a UTF-8 codepoint, returns the number of /// bytes required to represent the codepoint. /// RFC reference : https://tools.ietf.org/html/rfc3629#section-4 pub fn len_utf8_from_first_byte(b: u8) -> usize { - match b { - b if b < 0x80 => 1, - b if b < 0xe0 => 2, - b if b < 0xf0 => 3, - _ => 4, - } + match b { + b if b < 0x80 => 1, + b if b < 0xe0 => 2, + b if b < 0xf0 => 3, + _ => 4, + } } #[cfg(test)] mod tests { - use crate::core::interval::Interval; - use crate::core::ot_str::OTString; + use crate::core::interval::Interval; + use crate::core::ot_str::OTString; - #[test] - fn flowy_str_code_unit() { - let size = OTString::from("👋").utf16_len(); - assert_eq!(size, 2); + #[test] + fn flowy_str_code_unit() { + let size = OTString::from("👋").utf16_len(); + assert_eq!(size, 2); - let s: OTString = "👋 \n👋".into(); - let output = s.sub_str(Interval::new(0, size)).unwrap(); - assert_eq!(output, "👋"); + let s: OTString = "👋 \n👋".into(); + let output = s.sub_str(Interval::new(0, size)).unwrap(); + assert_eq!(output, "👋"); - let output = s.sub_str(Interval::new(2, 3)).unwrap(); - assert_eq!(output, " "); + let output = s.sub_str(Interval::new(2, 3)).unwrap(); + assert_eq!(output, " "); - let output = s.sub_str(Interval::new(3, 4)).unwrap(); - assert_eq!(output, "\n"); + let output = s.sub_str(Interval::new(3, 4)).unwrap(); + assert_eq!(output, "\n"); - let output = s.sub_str(Interval::new(4, 4 + size)).unwrap(); - assert_eq!(output, "👋"); - } + let output = s.sub_str(Interval::new(4, 4 + size)).unwrap(); + assert_eq!(output, "👋"); + } - #[test] - fn flowy_str_sub_str_in_chinese2() { - let s: OTString = "😁 \n".into(); - let size = s.utf16_len(); - assert_eq!(size, 4); + #[test] + fn flowy_str_sub_str_in_chinese2() { + let s: OTString = "😁 \n".into(); + let size = s.utf16_len(); + assert_eq!(size, 4); - let output1 = s.sub_str(Interval::new(0, 3)).unwrap(); - let output2 = s.sub_str(Interval::new(3, 4)).unwrap(); - assert_eq!(output1, "😁 "); - assert_eq!(output2, "\n"); - } + let output1 = s.sub_str(Interval::new(0, 3)).unwrap(); + let output2 = s.sub_str(Interval::new(3, 4)).unwrap(); + assert_eq!(output1, "😁 "); + assert_eq!(output2, "\n"); + } - #[test] - fn flowy_str_sub_str_in_english() { - let s: OTString = "ab".into(); - let size = s.utf16_len(); - assert_eq!(size, 2); + #[test] + fn flowy_str_sub_str_in_english() { + let s: OTString = "ab".into(); + let size = s.utf16_len(); + assert_eq!(size, 2); - let output = s.sub_str(Interval::new(0, 2)).unwrap(); - assert_eq!(output, "ab"); - } + let output = s.sub_str(Interval::new(0, 2)).unwrap(); + assert_eq!(output, "ab"); + } - #[test] - fn flowy_str_utf16_code_point_iter_test2() { - let s: OTString = "👋😁👋".into(); - let iter = s.utf16_code_point_iter(); - let result = iter.skip(1).take(1).collect::(); - assert_eq!(result, "😁".to_string()); - } + #[test] + fn flowy_str_utf16_code_point_iter_test2() { + let s: OTString = "👋😁👋".into(); + let iter = s.utf16_code_point_iter(); + let result = iter.skip(1).take(1).collect::(); + assert_eq!(result, "😁".to_string()); + } } diff --git a/shared-lib/lib-ot/src/errors.rs b/shared-lib/lib-ot/src/errors.rs index 86b01bec44..58a846f0c3 100644 --- a/shared-lib/lib-ot/src/errors.rs +++ b/shared-lib/lib-ot/src/errors.rs @@ -2,112 +2,116 @@ use std::{fmt, fmt::Debug, str::Utf8Error}; #[derive(thiserror::Error, Clone, Debug)] pub struct OTError { - pub code: OTErrorCode, - pub msg: String, + pub code: OTErrorCode, + pub msg: String, } macro_rules! static_ot_error { - ($name:ident, $code:expr) => { - #[allow(non_snake_case, missing_docs)] - pub fn $name() -> OTError { - $code.into() - } - }; + ($name:ident, $code:expr) => { + #[allow(non_snake_case, missing_docs)] + pub fn $name() -> OTError { + $code.into() + } + }; } impl std::convert::From for OTError { - fn from(code: OTErrorCode) -> Self { - OTError { - code: code.clone(), - msg: format!("{:?}", code), - } + fn from(code: OTErrorCode) -> Self { + OTError { + code: code.clone(), + msg: format!("{:?}", code), } + } } impl OTError { - pub fn new(code: OTErrorCode, msg: String) -> OTError { - Self { code, msg } - } + pub fn new(code: OTErrorCode, msg: String) -> OTError { + Self { code, msg } + } - pub fn context(mut self, error: T) -> Self { - self.msg = format!("{:?}", error); - self - } + pub fn context(mut self, error: T) -> Self { + self.msg = format!("{:?}", error); + self + } - static_ot_error!(duplicate_revision, OTErrorCode::DuplicatedRevision); - static_ot_error!(revision_id_conflict, OTErrorCode::RevisionIDConflict); - static_ot_error!(internal, OTErrorCode::Internal); - static_ot_error!(serde, OTErrorCode::SerdeError); - static_ot_error!(path_not_found, OTErrorCode::PathNotFound); - static_ot_error!(compose, OTErrorCode::ComposeOperationFail); - static_ot_error!(record_not_found, OTErrorCode::RecordNotFound); + static_ot_error!(duplicate_revision, OTErrorCode::DuplicatedRevision); + static_ot_error!(revision_id_conflict, OTErrorCode::RevisionIDConflict); + static_ot_error!(internal, OTErrorCode::Internal); + static_ot_error!(serde, OTErrorCode::SerdeError); + static_ot_error!(path_not_found, OTErrorCode::PathNotFound); + static_ot_error!(compose, OTErrorCode::ComposeOperationFail); + static_ot_error!(record_not_found, OTErrorCode::RecordNotFound); } impl fmt::Display for OTError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}: {}", self.code, self.msg) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}: {}", self.code, self.msg) + } } impl std::convert::From for OTError { - fn from(error: serde_json::Error) -> Self { - ErrorBuilder::new(OTErrorCode::SerdeError).error(error).build() - } + fn from(error: serde_json::Error) -> Self { + ErrorBuilder::new(OTErrorCode::SerdeError) + .error(error) + .build() + } } impl std::convert::From for OTError { - fn from(error: Utf8Error) -> Self { - ErrorBuilder::new(OTErrorCode::SerdeError).error(error).build() - } + fn from(error: Utf8Error) -> Self { + ErrorBuilder::new(OTErrorCode::SerdeError) + .error(error) + .build() + } } #[derive(Debug, Clone, Eq, PartialEq)] pub enum OTErrorCode { - IncompatibleLength, - ApplyInsertFail, - ApplyDeleteFail, - ApplyFormatFail, - ComposeOperationFail, - IntervalOutOfBound, - UndoFail, - RedoFail, - SerdeError, - DuplicatedRevision, - RevisionIDConflict, - Internal, - PathNotFound, - PathIsEmpty, - InvalidPath, - RecordNotFound, + IncompatibleLength, + ApplyInsertFail, + ApplyDeleteFail, + ApplyFormatFail, + ComposeOperationFail, + IntervalOutOfBound, + UndoFail, + RedoFail, + SerdeError, + DuplicatedRevision, + RevisionIDConflict, + Internal, + PathNotFound, + PathIsEmpty, + InvalidPath, + RecordNotFound, } pub struct ErrorBuilder { - pub code: OTErrorCode, - pub msg: Option, + pub code: OTErrorCode, + pub msg: Option, } impl ErrorBuilder { - pub fn new(code: OTErrorCode) -> Self { - ErrorBuilder { code, msg: None } - } + pub fn new(code: OTErrorCode) -> Self { + ErrorBuilder { code, msg: None } + } - pub fn msg(mut self, msg: T) -> Self - where - T: Into, - { - self.msg = Some(msg.into()); - self - } + pub fn msg(mut self, msg: T) -> Self + where + T: Into, + { + self.msg = Some(msg.into()); + self + } - pub fn error(mut self, msg: T) -> Self - where - T: std::fmt::Debug, - { - self.msg = Some(format!("{:?}", msg)); - self - } + pub fn error(mut self, msg: T) -> Self + where + T: std::fmt::Debug, + { + self.msg = Some(format!("{:?}", msg)); + self + } - pub fn build(mut self) -> OTError { - OTError::new(self.code, self.msg.take().unwrap_or_else(|| "".to_owned())) - } + pub fn build(mut self) -> OTError { + OTError::new(self.code, self.msg.take().unwrap_or_else(|| "".to_owned())) + } } diff --git a/shared-lib/lib-ot/src/text_delta/attributes.rs b/shared-lib/lib-ot/src/text_delta/attributes.rs index 97d5335fed..ceb20a8ad7 100644 --- a/shared-lib/lib-ot/src/text_delta/attributes.rs +++ b/shared-lib/lib-ot/src/text_delta/attributes.rs @@ -9,133 +9,146 @@ use strum_macros::{AsRefStr, Display, EnumString}; #[inline(always)] pub fn empty_attributes() -> AttributeHashMap { - AttributeHashMap::default() + AttributeHashMap::default() } pub fn attributes_except_header(op: &DeltaTextOperation) -> AttributeHashMap { - let mut attributes = op.get_attributes(); - attributes.remove_key(BuildInTextAttributeKey::Header); - attributes + let mut attributes = op.get_attributes(); + attributes.remove_key(BuildInTextAttributeKey::Header); + attributes } #[derive(Debug, Clone)] pub struct BuildInTextAttribute(); impl BuildInTextAttribute { - inline_attribute_entry!(Bold, bool); - inline_attribute_entry!(Italic, bool); - inline_attribute_entry!(Underline, bool); - inline_attribute_entry!(StrikeThrough, bool); - inline_attribute_entry!(Link, &str); - inline_attribute_entry!(Color, String); - inline_attribute_entry!(Font, usize); - inline_attribute_entry!(Size, usize); - inline_attribute_entry!(Background, String); - inline_attribute_entry!(InlineCode, bool); + inline_attribute_entry!(Bold, bool); + inline_attribute_entry!(Italic, bool); + inline_attribute_entry!(Underline, bool); + inline_attribute_entry!(StrikeThrough, bool); + inline_attribute_entry!(Link, &str); + inline_attribute_entry!(Color, String); + inline_attribute_entry!(Font, usize); + inline_attribute_entry!(Size, usize); + inline_attribute_entry!(Background, String); + inline_attribute_entry!(InlineCode, bool); - inline_attribute_entry!(Header, usize); - inline_attribute_entry!(Indent, usize); - inline_attribute_entry!(Align, String); - inline_attribute_entry!(List, &str); - inline_attribute_entry!(CodeBlock, bool); - inline_attribute_entry!(BlockQuote, bool); + inline_attribute_entry!(Header, usize); + inline_attribute_entry!(Indent, usize); + inline_attribute_entry!(Align, String); + inline_attribute_entry!(List, &str); + inline_attribute_entry!(CodeBlock, bool); + inline_attribute_entry!(BlockQuote, bool); - inline_attribute_entry!(Width, usize); - inline_attribute_entry!(Height, usize); + inline_attribute_entry!(Width, usize); + inline_attribute_entry!(Height, usize); - // List extension - inline_list_attribute_entry!(Bullet, "bullet"); - inline_list_attribute_entry!(Ordered, "ordered"); - inline_list_attribute_entry!(Checked, "checked"); - inline_list_attribute_entry!(UnChecked, "unchecked"); + // List extension + inline_list_attribute_entry!(Bullet, "bullet"); + inline_list_attribute_entry!(Ordered, "ordered"); + inline_list_attribute_entry!(Checked, "checked"); + inline_list_attribute_entry!(UnChecked, "unchecked"); } -#[derive(Clone, Debug, Display, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize, AsRefStr, EnumString)] +#[derive( + Clone, + Debug, + Display, + Hash, + Eq, + PartialEq, + serde::Serialize, + serde::Deserialize, + AsRefStr, + EnumString, +)] #[strum(serialize_all = "snake_case")] pub enum BuildInTextAttributeKey { - #[serde(rename = "bold")] - Bold, - #[serde(rename = "italic")] - Italic, - #[serde(rename = "underline")] - Underline, - #[serde(rename = "strike")] - StrikeThrough, - #[serde(rename = "font")] - Font, - #[serde(rename = "size")] - Size, - #[serde(rename = "link")] - Link, - #[serde(rename = "color")] - Color, - #[serde(rename = "background")] - Background, - #[serde(rename = "indent")] - Indent, - #[serde(rename = "align")] - Align, - #[serde(rename = "code_block")] - CodeBlock, - #[serde(rename = "code")] - InlineCode, - #[serde(rename = "list")] - List, - #[serde(rename = "blockquote")] - BlockQuote, - #[serde(rename = "width")] - Width, - #[serde(rename = "height")] - Height, - #[serde(rename = "header")] - Header, + #[serde(rename = "bold")] + Bold, + #[serde(rename = "italic")] + Italic, + #[serde(rename = "underline")] + Underline, + #[serde(rename = "strike")] + StrikeThrough, + #[serde(rename = "font")] + Font, + #[serde(rename = "size")] + Size, + #[serde(rename = "link")] + Link, + #[serde(rename = "color")] + Color, + #[serde(rename = "background")] + Background, + #[serde(rename = "indent")] + Indent, + #[serde(rename = "align")] + Align, + #[serde(rename = "code_block")] + CodeBlock, + #[serde(rename = "code")] + InlineCode, + #[serde(rename = "list")] + List, + #[serde(rename = "blockquote")] + BlockQuote, + #[serde(rename = "width")] + Width, + #[serde(rename = "height")] + Height, + #[serde(rename = "header")] + Header, } pub fn is_block(k: &AttributeKey) -> bool { - if let Ok(key) = BuildInTextAttributeKey::from_str(k) { - BLOCK_KEYS.contains(&key) - } else { - false - } + if let Ok(key) = BuildInTextAttributeKey::from_str(k) { + BLOCK_KEYS.contains(&key) + } else { + false + } } pub fn is_inline(k: &AttributeKey) -> bool { - if let Ok(key) = BuildInTextAttributeKey::from_str(k) { - INLINE_KEYS.contains(&key) - } else { - false - } + if let Ok(key) = BuildInTextAttributeKey::from_str(k) { + INLINE_KEYS.contains(&key) + } else { + false + } } lazy_static! { - static ref BLOCK_KEYS: HashSet = HashSet::from_iter(vec![ - BuildInTextAttributeKey::Header, - BuildInTextAttributeKey::Indent, - BuildInTextAttributeKey::Align, - BuildInTextAttributeKey::CodeBlock, - BuildInTextAttributeKey::List, - BuildInTextAttributeKey::BlockQuote, - ]); - static ref INLINE_KEYS: HashSet = HashSet::from_iter(vec![ - BuildInTextAttributeKey::Bold, - BuildInTextAttributeKey::Italic, - BuildInTextAttributeKey::Underline, - BuildInTextAttributeKey::StrikeThrough, - BuildInTextAttributeKey::Link, - BuildInTextAttributeKey::Color, - BuildInTextAttributeKey::Font, - BuildInTextAttributeKey::Size, - BuildInTextAttributeKey::Background, - BuildInTextAttributeKey::InlineCode, - ]); - static ref INGORE_KEYS: HashSet = - HashSet::from_iter(vec![BuildInTextAttributeKey::Width, BuildInTextAttributeKey::Height,]); + static ref BLOCK_KEYS: HashSet = HashSet::from_iter(vec![ + BuildInTextAttributeKey::Header, + BuildInTextAttributeKey::Indent, + BuildInTextAttributeKey::Align, + BuildInTextAttributeKey::CodeBlock, + BuildInTextAttributeKey::List, + BuildInTextAttributeKey::BlockQuote, + ]); + static ref INLINE_KEYS: HashSet = HashSet::from_iter(vec![ + BuildInTextAttributeKey::Bold, + BuildInTextAttributeKey::Italic, + BuildInTextAttributeKey::Underline, + BuildInTextAttributeKey::StrikeThrough, + BuildInTextAttributeKey::Link, + BuildInTextAttributeKey::Color, + BuildInTextAttributeKey::Font, + BuildInTextAttributeKey::Size, + BuildInTextAttributeKey::Background, + BuildInTextAttributeKey::InlineCode, + ]); + static ref INGORE_KEYS: HashSet = HashSet::from_iter(vec![ + BuildInTextAttributeKey::Width, + BuildInTextAttributeKey::Height, + ]); } #[derive(Debug, PartialEq, Eq, Clone)] pub enum AttributeScope { - Inline, - Block, - Embeds, - Ignore, + Inline, + Block, + Embeds, + Ignore, } diff --git a/shared-lib/lib-ot/src/text_delta/macros.rs b/shared-lib/lib-ot/src/text_delta/macros.rs index a93c2f4dc0..5dcae70fc4 100644 --- a/shared-lib/lib-ot/src/text_delta/macros.rs +++ b/shared-lib/lib-ot/src/text_delta/macros.rs @@ -1,34 +1,34 @@ #[macro_export] macro_rules! inline_attribute_entry { - ( + ( $key: ident, $value: ty ) => { - pub fn $key(value: $value) -> $crate::core::AttributeEntry { - AttributeEntry { - key: BuildInTextAttributeKey::$key.as_ref().to_string(), - value: value.into(), - } - } - }; + pub fn $key(value: $value) -> $crate::core::AttributeEntry { + AttributeEntry { + key: BuildInTextAttributeKey::$key.as_ref().to_string(), + value: value.into(), + } + } + }; } #[macro_export] macro_rules! inline_list_attribute_entry { - ( + ( $key: ident, $value: expr ) => { - pub fn $key(b: bool) -> $crate::core::AttributeEntry { - let value = match b { - true => $value.into(), - false => AttributeValue::none(), - }; + pub fn $key(b: bool) -> $crate::core::AttributeEntry { + let value = match b { + true => $value.into(), + false => AttributeValue::none(), + }; - AttributeEntry { - key: BuildInTextAttributeKey::List.as_ref().to_string(), - value, - } - } - }; + AttributeEntry { + key: BuildInTextAttributeKey::List.as_ref().to_string(), + value, + } + } + }; } diff --git a/shared-lib/lib-ot/tests/node/changeset_compose_test.rs b/shared-lib/lib-ot/tests/node/changeset_compose_test.rs index 7e800952be..4e5a4b1de0 100644 --- a/shared-lib/lib-ot/tests/node/changeset_compose_test.rs +++ b/shared-lib/lib-ot/tests/node/changeset_compose_test.rs @@ -5,190 +5,202 @@ use lib_ot::text_delta::DeltaTextOperationBuilder; #[test] fn changeset_delta_compose_delta_test() { - // delta 1 - let delta_1 = DeltaTextOperationBuilder::new().insert("Hello world").build(); - let inverted_1 = delta_1.inverted(); - let mut changeset_1 = Changeset::Delta { - delta: delta_1.clone(), - inverted: inverted_1, - }; + // delta 1 + let delta_1 = DeltaTextOperationBuilder::new() + .insert("Hello world") + .build(); + let inverted_1 = delta_1.inverted(); + let mut changeset_1 = Changeset::Delta { + delta: delta_1.clone(), + inverted: inverted_1, + }; - // delta 2 - let delta_2 = DeltaTextOperationBuilder::new() - .retain(delta_1.utf16_target_len) - .insert("!") - .build(); - let inverted_2 = delta_2.inverted(); - let changeset_2 = Changeset::Delta { - delta: delta_2, - inverted: inverted_2, - }; + // delta 2 + let delta_2 = DeltaTextOperationBuilder::new() + .retain(delta_1.utf16_target_len) + .insert("!") + .build(); + let inverted_2 = delta_2.inverted(); + let changeset_2 = Changeset::Delta { + delta: delta_2, + inverted: inverted_2, + }; - // compose - changeset_1.compose(&changeset_2).unwrap(); + // compose + changeset_1.compose(&changeset_2).unwrap(); - if let Changeset::Delta { delta, inverted } = changeset_1 { - assert_eq!(delta.content().unwrap(), "Hello world!"); - let new_delta = delta.compose(&inverted).unwrap(); - assert_eq!(new_delta.content().unwrap(), ""); - } + if let Changeset::Delta { delta, inverted } = changeset_1 { + assert_eq!(delta.content().unwrap(), "Hello world!"); + let new_delta = delta.compose(&inverted).unwrap(); + assert_eq!(new_delta.content().unwrap(), ""); + } } #[test] fn operation_compose_delta_changeset_then_invert_test() { - let delta = DeltaTextOperationBuilder::new().insert("Hello world").build(); - let inverted = delta.inverted(); - let changeset = Changeset::Delta { - delta: delta.clone(), - inverted: inverted.clone(), - }; + let delta = DeltaTextOperationBuilder::new() + .insert("Hello world") + .build(); + let inverted = delta.inverted(); + let changeset = Changeset::Delta { + delta: delta.clone(), + inverted: inverted.clone(), + }; - let mut test = NodeTest::new(); - let text_node = NodeData::new("text"); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_node, - rev_id: 1, - }, - UpdateBody { - path: 0.into(), - changeset: changeset.clone(), - }, - AssertNodeDelta { - path: 0.into(), - expected: delta.clone(), - }, - UpdateBody { - path: 0.into(), - changeset: changeset.inverted(), - }, - AssertNodeDelta { - path: 0.into(), - expected: delta.compose(&inverted).unwrap(), - }, - ]; - test.run_scripts(scripts); + let mut test = NodeTest::new(); + let text_node = NodeData::new("text"); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node, + rev_id: 1, + }, + UpdateBody { + path: 0.into(), + changeset: changeset.clone(), + }, + AssertNodeDelta { + path: 0.into(), + expected: delta.clone(), + }, + UpdateBody { + path: 0.into(), + changeset: changeset.inverted(), + }, + AssertNodeDelta { + path: 0.into(), + expected: delta.compose(&inverted).unwrap(), + }, + ]; + test.run_scripts(scripts); } #[test] fn operation_compose_multiple_delta_changeset_then_invert_test() { - // delta 1 - let delta_1 = DeltaTextOperationBuilder::new().insert("Hello world").build(); - let inverted_1 = delta_1.inverted(); - let changeset_1 = Changeset::Delta { - delta: delta_1.clone(), - inverted: inverted_1, - }; + // delta 1 + let delta_1 = DeltaTextOperationBuilder::new() + .insert("Hello world") + .build(); + let inverted_1 = delta_1.inverted(); + let changeset_1 = Changeset::Delta { + delta: delta_1.clone(), + inverted: inverted_1, + }; - // delta 2 - let delta_2 = DeltaTextOperationBuilder::new() - .retain(delta_1.utf16_target_len) - .insert("!") - .build(); - let inverted_2 = delta_2.inverted(); - let changeset_2 = Changeset::Delta { - delta: delta_2.clone(), - inverted: inverted_2, - }; + // delta 2 + let delta_2 = DeltaTextOperationBuilder::new() + .retain(delta_1.utf16_target_len) + .insert("!") + .build(); + let inverted_2 = delta_2.inverted(); + let changeset_2 = Changeset::Delta { + delta: delta_2.clone(), + inverted: inverted_2, + }; - // delta 3 - let delta_3 = DeltaTextOperationBuilder::new() - .retain(delta_2.utf16_target_len) - .insert("AppFlowy") - .build(); - let inverted_3 = delta_3.inverted(); - let changeset_3 = Changeset::Delta { - delta: delta_3.clone(), - inverted: inverted_3, - }; + // delta 3 + let delta_3 = DeltaTextOperationBuilder::new() + .retain(delta_2.utf16_target_len) + .insert("AppFlowy") + .build(); + let inverted_3 = delta_3.inverted(); + let changeset_3 = Changeset::Delta { + delta: delta_3.clone(), + inverted: inverted_3, + }; - let mut test = NodeTest::new(); - let text_node = NodeData::new("text"); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_node, - rev_id: 1, - }, - UpdateBody { - path: 0.into(), - changeset: changeset_1.clone(), - }, - UpdateBody { - path: 0.into(), - changeset: changeset_2.clone(), - }, - UpdateBody { - path: 0.into(), - changeset: changeset_3.clone(), - }, - AssertNodeDelta { - path: 0.into(), - expected: delta_1.compose(&delta_2).unwrap().compose(&delta_3).unwrap(), - }, - UpdateBody { - path: 0.into(), - changeset: changeset_3.inverted(), - }, - AssertNodeDeltaContent { - path: 0.into(), - expected: r#"Hello world!"#, - }, - UpdateBody { - path: 0.into(), - changeset: changeset_2.inverted(), - }, - AssertNodeDeltaContent { - path: 0.into(), - expected: r#"Hello world"#, - }, - UpdateBody { - path: 0.into(), - changeset: changeset_1.inverted(), - }, - AssertNodeDeltaContent { - path: 0.into(), - expected: r#""#, - }, - ]; - test.run_scripts(scripts); + let mut test = NodeTest::new(); + let text_node = NodeData::new("text"); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node, + rev_id: 1, + }, + UpdateBody { + path: 0.into(), + changeset: changeset_1.clone(), + }, + UpdateBody { + path: 0.into(), + changeset: changeset_2.clone(), + }, + UpdateBody { + path: 0.into(), + changeset: changeset_3.clone(), + }, + AssertNodeDelta { + path: 0.into(), + expected: delta_1 + .compose(&delta_2) + .unwrap() + .compose(&delta_3) + .unwrap(), + }, + UpdateBody { + path: 0.into(), + changeset: changeset_3.inverted(), + }, + AssertNodeDeltaContent { + path: 0.into(), + expected: r#"Hello world!"#, + }, + UpdateBody { + path: 0.into(), + changeset: changeset_2.inverted(), + }, + AssertNodeDeltaContent { + path: 0.into(), + expected: r#"Hello world"#, + }, + UpdateBody { + path: 0.into(), + changeset: changeset_1.inverted(), + }, + AssertNodeDeltaContent { + path: 0.into(), + expected: r#""#, + }, + ]; + test.run_scripts(scripts); } #[test] #[should_panic] fn changeset_delta_compose_attributes_test() { - // delta 1 - let delta = DeltaTextOperationBuilder::new().insert("Hello world").build(); - let inverted = delta.inverted(); - let mut delta_changeset = Changeset::Delta { delta, inverted }; + // delta 1 + let delta = DeltaTextOperationBuilder::new() + .insert("Hello world") + .build(); + let inverted = delta.inverted(); + let mut delta_changeset = Changeset::Delta { delta, inverted }; - // attributes - let attribute_changeset = Changeset::Attributes { - new: Default::default(), - old: Default::default(), - }; + // attributes + let attribute_changeset = Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }; - // compose - delta_changeset.compose(&attribute_changeset).unwrap(); + // compose + delta_changeset.compose(&attribute_changeset).unwrap(); } #[test] fn changeset_attributes_compose_attributes_test() { - // attributes - let mut changeset_1 = Changeset::Attributes { - new: AttributeEntry::new("bold", true).into(), - old: Default::default(), - }; + // attributes + let mut changeset_1 = Changeset::Attributes { + new: AttributeEntry::new("bold", true).into(), + old: Default::default(), + }; - let changeset_2 = Changeset::Attributes { - new: AttributeEntry::new("Italic", true).into(), - old: Default::default(), - }; - // compose - changeset_1.compose(&changeset_2).unwrap(); + let changeset_2 = Changeset::Attributes { + new: AttributeEntry::new("Italic", true).into(), + old: Default::default(), + }; + // compose + changeset_1.compose(&changeset_2).unwrap(); - if let Changeset::Attributes { new, old: _ } = changeset_1 { - assert_eq!(new, AttributeEntry::new("Italic", true).into()); - } + if let Changeset::Attributes { new, old: _ } = changeset_1 { + assert_eq!(new, AttributeEntry::new("Italic", true).into()); + } } diff --git a/shared-lib/lib-ot/tests/node/operation_attribute_test.rs b/shared-lib/lib-ot/tests/node/operation_attribute_test.rs index cb7308d9d6..80f44348eb 100644 --- a/shared-lib/lib-ot/tests/node/operation_attribute_test.rs +++ b/shared-lib/lib-ot/tests/node/operation_attribute_test.rs @@ -4,61 +4,61 @@ use lib_ot::core::{AttributeEntry, AttributeValue, Changeset, NodeData}; #[test] fn operation_update_attribute_with_float_value_test() { - let mut test = NodeTest::new(); - let text_node = NodeData::new("text"); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_node, - rev_id: 1, - }, - UpdateBody { - path: 0.into(), - changeset: Changeset::Attributes { - new: AttributeEntry::new("value", 12.2).into(), - old: Default::default(), - }, - }, - AssertNodeAttributes { - path: 0.into(), - expected: r#"{"value":12.2}"#, - }, - ]; - test.run_scripts(scripts); + let mut test = NodeTest::new(); + let text_node = NodeData::new("text"); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node, + rev_id: 1, + }, + UpdateBody { + path: 0.into(), + changeset: Changeset::Attributes { + new: AttributeEntry::new("value", 12.2).into(), + old: Default::default(), + }, + }, + AssertNodeAttributes { + path: 0.into(), + expected: r#"{"value":12.2}"#, + }, + ]; + test.run_scripts(scripts); } #[test] fn operation_update_attribute_with_negative_value_test() { - let mut test = NodeTest::new(); - let text_node = NodeData::new("text"); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_node, - rev_id: 1, - }, - UpdateBody { - path: 0.into(), - changeset: Changeset::Attributes { - new: AttributeEntry::new("value", -12.2).into(), - old: Default::default(), - }, - }, - AssertNodeAttributes { - path: 0.into(), - expected: r#"{"value":-12.2}"#, - }, - UpdateBody { - path: 0.into(), - changeset: Changeset::Attributes { - new: AttributeEntry::new("value", AttributeValue::from_int(-12)).into(), - old: Default::default(), - }, - }, - AssertNodeAttributes { - path: 0.into(), - expected: r#"{"value":-12}"#, - }, - ]; - test.run_scripts(scripts); + let mut test = NodeTest::new(); + let text_node = NodeData::new("text"); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node, + rev_id: 1, + }, + UpdateBody { + path: 0.into(), + changeset: Changeset::Attributes { + new: AttributeEntry::new("value", -12.2).into(), + old: Default::default(), + }, + }, + AssertNodeAttributes { + path: 0.into(), + expected: r#"{"value":-12.2}"#, + }, + UpdateBody { + path: 0.into(), + changeset: Changeset::Attributes { + new: AttributeEntry::new("value", AttributeValue::from_int(-12)).into(), + old: Default::default(), + }, + }, + AssertNodeAttributes { + path: 0.into(), + expected: r#"{"value":-12}"#, + }, + ]; + test.run_scripts(scripts); } diff --git a/shared-lib/lib-ot/tests/node/operation_compose_test.rs b/shared-lib/lib-ot/tests/node/operation_compose_test.rs index 698add3387..c2bfd0d7c2 100644 --- a/shared-lib/lib-ot/tests/node/operation_compose_test.rs +++ b/shared-lib/lib-ot/tests/node/operation_compose_test.rs @@ -2,134 +2,134 @@ use lib_ot::core::{Changeset, NodeOperation}; #[test] fn operation_insert_compose_delta_update_test() { - let insert_operation = NodeOperation::Insert { - path: 0.into(), - nodes: vec![], - }; + let insert_operation = NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + }; - let update_operation = NodeOperation::Update { - path: 0.into(), - changeset: Changeset::Delta { - delta: Default::default(), - inverted: Default::default(), - }, - }; + let update_operation = NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Delta { + delta: Default::default(), + inverted: Default::default(), + }, + }; - assert!(insert_operation.can_compose(&update_operation)) + assert!(insert_operation.can_compose(&update_operation)) } #[test] fn operation_insert_compose_attribute_update_test() { - let insert_operation = NodeOperation::Insert { - path: 0.into(), - nodes: vec![], - }; + let insert_operation = NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + }; - let update_operation = NodeOperation::Update { - path: 0.into(), - changeset: Changeset::Attributes { - new: Default::default(), - old: Default::default(), - }, - }; + let update_operation = NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }, + }; - assert!(!insert_operation.can_compose(&update_operation)) + assert!(!insert_operation.can_compose(&update_operation)) } #[test] fn operation_insert_compose_update_with_diff_path_test() { - let insert_operation = NodeOperation::Insert { - path: 0.into(), - nodes: vec![], - }; + let insert_operation = NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + }; - let update_operation = NodeOperation::Update { - path: 1.into(), - changeset: Changeset::Attributes { - new: Default::default(), - old: Default::default(), - }, - }; + let update_operation = NodeOperation::Update { + path: 1.into(), + changeset: Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }, + }; - assert!(!insert_operation.can_compose(&update_operation)) + assert!(!insert_operation.can_compose(&update_operation)) } #[test] fn operation_insert_compose_insert_operation_test() { - let insert_operation = NodeOperation::Insert { - path: 0.into(), - nodes: vec![], - }; + let insert_operation = NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + }; - assert!(!insert_operation.can_compose(&NodeOperation::Insert { - path: 0.into(), - nodes: vec![], - }),) + assert!(!insert_operation.can_compose(&NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + }),) } #[test] fn operation_update_compose_insert_operation_test() { - let update_operation = NodeOperation::Update { - path: 0.into(), - changeset: Changeset::Attributes { - new: Default::default(), - old: Default::default(), - }, - }; + let update_operation = NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }, + }; - assert!(!update_operation.can_compose(&NodeOperation::Insert { - path: 0.into(), - nodes: vec![], - })) + assert!(!update_operation.can_compose(&NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + })) } #[test] fn operation_update_compose_update_test() { - let update_operation_1 = NodeOperation::Update { - path: 0.into(), - changeset: Changeset::Attributes { - new: Default::default(), - old: Default::default(), - }, - }; - let update_operation_2 = NodeOperation::Update { - path: 0.into(), - changeset: Changeset::Attributes { - new: Default::default(), - old: Default::default(), - }, - }; + let update_operation_1 = NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }, + }; + let update_operation_2 = NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }, + }; - assert!(update_operation_1.can_compose(&update_operation_2)); + assert!(update_operation_1.can_compose(&update_operation_2)); } #[test] fn operation_update_compose_update_with_diff_path_test() { - let update_operation_1 = NodeOperation::Update { - path: 0.into(), - changeset: Changeset::Attributes { - new: Default::default(), - old: Default::default(), - }, - }; - let update_operation_2 = NodeOperation::Update { - path: 1.into(), - changeset: Changeset::Attributes { - new: Default::default(), - old: Default::default(), - }, - }; + let update_operation_1 = NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }, + }; + let update_operation_2 = NodeOperation::Update { + path: 1.into(), + changeset: Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }, + }; - assert!(!update_operation_1.can_compose(&update_operation_2)); + assert!(!update_operation_1.can_compose(&update_operation_2)); } #[test] fn operation_insert_compose_insert_test() { - let insert_operation_1 = NodeOperation::Insert { - path: 0.into(), - nodes: vec![], - }; - let insert_operation_2 = NodeOperation::Insert { - path: 0.into(), - nodes: vec![], - }; + let insert_operation_1 = NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + }; + let insert_operation_2 = NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + }; - assert!(!insert_operation_1.can_compose(&insert_operation_2)); + assert!(!insert_operation_1.can_compose(&insert_operation_2)); } diff --git a/shared-lib/lib-ot/tests/node/operation_delete_test.rs b/shared-lib/lib-ot/tests/node/operation_delete_test.rs index 145699c9b2..b42fdd8927 100644 --- a/shared-lib/lib-ot/tests/node/operation_delete_test.rs +++ b/shared-lib/lib-ot/tests/node/operation_delete_test.rs @@ -5,174 +5,176 @@ use lib_ot::core::{Changeset, NodeData, NodeDataBuilder}; #[test] fn operation_delete_nested_node_test() { - let mut test = NodeTest::new(); - let image_a = NodeData::new("image_a"); - let image_b = NodeData::new("image_b"); + let mut test = NodeTest::new(); + let image_a = NodeData::new("image_a"); + let image_b = NodeData::new("image_b"); - let video_a = NodeData::new("video_a"); - let video_b = NodeData::new("video_b"); + let video_a = NodeData::new("video_a"); + let video_b = NodeData::new("video_b"); - let image_1 = NodeDataBuilder::new("image_1") - .add_node_data(image_a.clone()) - .add_node_data(image_b.clone()) - .build(); - let video_1 = NodeDataBuilder::new("video_1") - .add_node_data(video_a.clone()) - .add_node_data(video_b) - .build(); + let image_1 = NodeDataBuilder::new("image_1") + .add_node_data(image_a.clone()) + .add_node_data(image_b.clone()) + .build(); + let video_1 = NodeDataBuilder::new("video_1") + .add_node_data(video_a.clone()) + .add_node_data(video_b) + .build(); - let text_node_1 = NodeDataBuilder::new("text_1") - .add_node_data(image_1) - .add_node_data(video_1.clone()) - .build(); + let text_node_1 = NodeDataBuilder::new("text_1") + .add_node_data(image_1) + .add_node_data(video_1.clone()) + .build(); - let image_2 = NodeDataBuilder::new("image_2") - .add_node_data(image_a) - .add_node_data(image_b.clone()) - .build(); - let text_node_2 = NodeDataBuilder::new("text_2").add_node_data(image_2).build(); + let image_2 = NodeDataBuilder::new("image_2") + .add_node_data(image_a) + .add_node_data(image_b.clone()) + .build(); + let text_node_2 = NodeDataBuilder::new("text_2") + .add_node_data(image_2) + .build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_node_1, - rev_id: 1, - }, - InsertNode { - path: 1.into(), - node_data: text_node_2, - rev_id: 2, - }, - // 0:text_1 - // 0:image_1 - // 0:image_a - // 1:image_b - // 1:video_1 - // 0:video_a - // 1:video_b - // 1:text_2 - // 0:image_2 - // 0:image_a - // 1:image_b - DeleteNode { - path: vec![0, 0, 0].into(), - rev_id: 3, - }, - AssertNode { - path: vec![0, 0, 0].into(), - expected: Some(image_b), - }, - AssertNode { - path: vec![0, 1].into(), - expected: Some(video_1), - }, - DeleteNode { - path: vec![0, 1, 1].into(), - rev_id: 4, - }, - AssertNode { - path: vec![0, 1, 0].into(), - expected: Some(video_a), - }, - ]; - test.run_scripts(scripts); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node_1, + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: text_node_2, + rev_id: 2, + }, + // 0:text_1 + // 0:image_1 + // 0:image_a + // 1:image_b + // 1:video_1 + // 0:video_a + // 1:video_b + // 1:text_2 + // 0:image_2 + // 0:image_a + // 1:image_b + DeleteNode { + path: vec![0, 0, 0].into(), + rev_id: 3, + }, + AssertNode { + path: vec![0, 0, 0].into(), + expected: Some(image_b), + }, + AssertNode { + path: vec![0, 1].into(), + expected: Some(video_1), + }, + DeleteNode { + path: vec![0, 1, 1].into(), + rev_id: 4, + }, + AssertNode { + path: vec![0, 1, 0].into(), + expected: Some(video_a), + }, + ]; + test.run_scripts(scripts); } #[test] fn operation_delete_node_with_revision_conflict_test() { - let mut test = NodeTest::new(); - let text_1 = NodeDataBuilder::new("text_1").build(); - let text_2 = NodeDataBuilder::new("text_2").build(); - let text_3 = NodeDataBuilder::new("text_3").build(); + let mut test = NodeTest::new(); + let text_1 = NodeDataBuilder::new("text_1").build(); + let text_2 = NodeDataBuilder::new("text_2").build(); + let text_3 = NodeDataBuilder::new("text_3").build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_1.clone(), - rev_id: 1, - }, - InsertNode { - path: 1.into(), - node_data: text_2, - rev_id: 2, - }, - // The node's in the tree will be: - // 0: text_1 - // 2: text_2 - // - // The insert action is happened concurrently with the delete action, because they - // share the same rev_id. aka, 3. The delete action is want to delete the node at index 1, - // but it was moved to index 2. - InsertNode { - path: 1.into(), - node_data: text_3.clone(), - rev_id: 3, - }, - // 0: text_1 - // 1: text_3 - // 2: text_2 - // - // The path of the delete action will be transformed to a new path that point to the text_2. - // 1 -> 2 - DeleteNode { - path: 1.into(), - rev_id: 3, - }, - // After perform the delete action, the tree will be: - // 0: text_1 - // 1: text_3 - AssertNumberOfChildrenAtPath { - path: None, - expected: 2, - }, - AssertNode { - path: 0.into(), - expected: Some(text_1), - }, - AssertNode { - path: 1.into(), - expected: Some(text_3), - }, - AssertNode { - path: 2.into(), - expected: None, - }, - ]; - test.run_scripts(scripts); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_1.clone(), + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: text_2, + rev_id: 2, + }, + // The node's in the tree will be: + // 0: text_1 + // 2: text_2 + // + // The insert action is happened concurrently with the delete action, because they + // share the same rev_id. aka, 3. The delete action is want to delete the node at index 1, + // but it was moved to index 2. + InsertNode { + path: 1.into(), + node_data: text_3.clone(), + rev_id: 3, + }, + // 0: text_1 + // 1: text_3 + // 2: text_2 + // + // The path of the delete action will be transformed to a new path that point to the text_2. + // 1 -> 2 + DeleteNode { + path: 1.into(), + rev_id: 3, + }, + // After perform the delete action, the tree will be: + // 0: text_1 + // 1: text_3 + AssertNumberOfChildrenAtPath { + path: None, + expected: 2, + }, + AssertNode { + path: 0.into(), + expected: Some(text_1), + }, + AssertNode { + path: 1.into(), + expected: Some(text_3), + }, + AssertNode { + path: 2.into(), + expected: None, + }, + ]; + test.run_scripts(scripts); } #[test] fn operation_update_node_after_delete_test() { - let mut test = NodeTest::new(); - let text_1 = NodeDataBuilder::new("text_1").build(); - let text_2 = NodeDataBuilder::new("text_2").build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_1, - rev_id: 1, - }, - InsertNode { - path: 1.into(), - node_data: text_2, - rev_id: 2, - }, - DeleteNode { - path: 0.into(), - rev_id: 3, - }, - // The node at path 1 is not exist. The following UpdateBody script will do nothing - AssertNode { - path: 1.into(), - expected: None, - }, - UpdateBody { - path: 1.into(), - changeset: Changeset::Delta { - delta: Default::default(), - inverted: Default::default(), - }, - }, - ]; - test.run_scripts(scripts); + let mut test = NodeTest::new(); + let text_1 = NodeDataBuilder::new("text_1").build(); + let text_2 = NodeDataBuilder::new("text_2").build(); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_1, + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: text_2, + rev_id: 2, + }, + DeleteNode { + path: 0.into(), + rev_id: 3, + }, + // The node at path 1 is not exist. The following UpdateBody script will do nothing + AssertNode { + path: 1.into(), + expected: None, + }, + UpdateBody { + path: 1.into(), + changeset: Changeset::Delta { + delta: Default::default(), + inverted: Default::default(), + }, + }, + ]; + test.run_scripts(scripts); } diff --git a/shared-lib/lib-ot/tests/node/operation_delta_test.rs b/shared-lib/lib-ot/tests/node/operation_delta_test.rs index bdc2812158..2cf782b76e 100644 --- a/shared-lib/lib-ot/tests/node/operation_delta_test.rs +++ b/shared-lib/lib-ot/tests/node/operation_delta_test.rs @@ -5,37 +5,39 @@ use lib_ot::text_delta::DeltaTextOperationBuilder; #[test] fn operation_update_delta_test() { - let mut test = NodeTest::new(); - let initial_delta = DeltaTextOperationBuilder::new().build(); - let new_delta = DeltaTextOperationBuilder::new() - .retain(initial_delta.utf16_base_len) - .insert("Hello, world") - .build(); - let (changeset, expected) = edit_node_delta(&initial_delta, new_delta); - let node = NodeDataBuilder::new("text").insert_delta(initial_delta.clone()).build(); + let mut test = NodeTest::new(); + let initial_delta = DeltaTextOperationBuilder::new().build(); + let new_delta = DeltaTextOperationBuilder::new() + .retain(initial_delta.utf16_base_len) + .insert("Hello, world") + .build(); + let (changeset, expected) = edit_node_delta(&initial_delta, new_delta); + let node = NodeDataBuilder::new("text") + .insert_delta(initial_delta.clone()) + .build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: node, - rev_id: 1, - }, - UpdateBody { - path: 0.into(), - changeset: changeset.clone(), - }, - AssertNodeDelta { - path: 0.into(), - expected, - }, - UpdateBody { - path: 0.into(), - changeset: changeset.inverted(), - }, - AssertNodeDelta { - path: 0.into(), - expected: initial_delta, - }, - ]; - test.run_scripts(scripts); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node, + rev_id: 1, + }, + UpdateBody { + path: 0.into(), + changeset: changeset.clone(), + }, + AssertNodeDelta { + path: 0.into(), + expected, + }, + UpdateBody { + path: 0.into(), + changeset: changeset.inverted(), + }, + AssertNodeDelta { + path: 0.into(), + expected: initial_delta, + }, + ]; + test.run_scripts(scripts); } diff --git a/shared-lib/lib-ot/tests/node/operation_insert_test.rs b/shared-lib/lib-ot/tests/node/operation_insert_test.rs index 50cc57d130..1192face1e 100644 --- a/shared-lib/lib-ot/tests/node/operation_insert_test.rs +++ b/shared-lib/lib-ot/tests/node/operation_insert_test.rs @@ -1,460 +1,463 @@ -use crate::node::script::NodeScript::*; -use crate::node::script::NodeTest; - -use lib_ot::core::{placeholder_node, NodeData, NodeDataBuilder, NodeOperation, Path}; - -#[test] -fn operation_insert_op_transform_test() { - let node_1 = NodeDataBuilder::new("text_1").build(); - let node_2 = NodeDataBuilder::new("text_2").build(); - let op_1 = NodeOperation::Insert { - path: Path(vec![0, 1]), - nodes: vec![node_1], - }; - - let mut insert_2 = NodeOperation::Insert { - path: Path(vec![0, 1]), - nodes: vec![node_2], - }; - - // let mut node_tree = NodeTree::new("root"); - // node_tree.apply_op(insert_1.clone()).unwrap(); - - op_1.transform(&mut insert_2); - let json = serde_json::to_string(&insert_2).unwrap(); - assert_eq!(json, r#"{"op":"insert","path":[0,2],"nodes":[{"type":"text_2"}]}"#); -} - -#[test] -fn operation_insert_one_level_path_test() { - let node_data_1 = NodeDataBuilder::new("text_1").build(); - let node_data_2 = NodeDataBuilder::new("text_2").build(); - let node_data_3 = NodeDataBuilder::new("text_3").build(); - let node_3 = node_data_3.clone(); - // 0: text_1 - // 1: text_2 - // - // Insert a new operation with rev_id 2 to index 1,but the index was already taken, so - // it needs to be transformed. - // - // 0: text_1 - // 1: text_2 - // 2: text_3 - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: node_data_1.clone(), - rev_id: 1, - }, - InsertNode { - path: 1.into(), - node_data: node_data_2.clone(), - rev_id: 2, - }, - InsertNode { - path: 1.into(), - node_data: node_data_3.clone(), - rev_id: 2, - }, - AssertNode { - path: 2.into(), - expected: Some(node_3.clone()), - }, - ]; - NodeTest::new().run_scripts(scripts); - - // If the rev_id of the node_data_3 is 3. then the tree will be: - // 0: text_1 - // 1: text_3 - // 2: text_2 - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: node_data_1, - rev_id: 1, - }, - InsertNode { - path: 1.into(), - node_data: node_data_2, - rev_id: 2, - }, - InsertNode { - path: 1.into(), - node_data: node_data_3, - rev_id: 3, - }, - AssertNode { - path: 1.into(), - expected: Some(node_3), - }, - ]; - NodeTest::new().run_scripts(scripts); -} - -#[test] -fn operation_insert_with_multiple_level_path_test() { - let mut test = NodeTest::new(); - let node_data_1 = NodeDataBuilder::new("text_1") - .add_node_data(NodeDataBuilder::new("text_1_1").build()) - .add_node_data(NodeDataBuilder::new("text_1_2").build()) - .build(); - - let node_data_2 = NodeDataBuilder::new("text_2") - .add_node_data(NodeDataBuilder::new("text_2_1").build()) - .add_node_data(NodeDataBuilder::new("text_2_2").build()) - .build(); - - let node_data_3 = NodeDataBuilder::new("text_3").build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: node_data_1, - rev_id: 1, - }, - InsertNode { - path: 1.into(), - node_data: node_data_2, - rev_id: 2, - }, - InsertNode { - path: 1.into(), - node_data: node_data_3.clone(), - rev_id: 2, - }, - AssertNode { - path: 2.into(), - expected: Some(node_data_3), - }, - ]; - test.run_scripts(scripts); -} - -#[test] -fn operation_insert_node_out_of_bound_test() { - let mut test = NodeTest::new(); - let image_a = NodeData::new("image_a"); - let image_b = NodeData::new("image_b"); - let image = NodeDataBuilder::new("image_1") - .add_node_data(image_a) - .add_node_data(image_b) - .build(); - let text_node = NodeDataBuilder::new("text_1").add_node_data(image).build(); - let image_c = NodeData::new("image_c"); - - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_node, - rev_id: 1, - }, - // 0:text_1 - // 0:image_1 - // 0:image_a - // 1:image_b - InsertNode { - path: vec![0, 0, 3].into(), - node_data: image_c.clone(), - rev_id: 2, - }, - // 0:text_1 - // 0:image_1 - // 0:image_a - // 1:image_b - // 2:placeholder node - // 3:image_c - AssertNode { - path: vec![0, 0, 2].into(), - expected: Some(placeholder_node()), - }, - AssertNode { - path: vec![0, 0, 3].into(), - expected: Some(image_c), - }, - AssertNode { - path: vec![0, 0, 10].into(), - expected: None, - }, - ]; - test.run_scripts(scripts); -} -#[test] -fn operation_insert_node_when_parent_is_not_exist_test1() { - let mut test = NodeTest::new(); - let text_1 = NodeDataBuilder::new("text_1").build(); - let text_2 = NodeDataBuilder::new("text_2").build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_1, - rev_id: 1, - }, - // The node at path 1 is not existing when inserting the text_2 to path 2. - InsertNode { - path: 2.into(), - node_data: text_2.clone(), - rev_id: 2, - }, - AssertNode { - path: 1.into(), - expected: Some(placeholder_node()), - }, - AssertNode { - path: 2.into(), - expected: Some(text_2), - }, - ]; - test.run_scripts(scripts); -} - -#[test] -fn operation_insert_node_when_parent_is_not_exist_test2() { - let mut test = NodeTest::new(); - let text_1 = NodeDataBuilder::new("text_1").build(); - let text_2 = NodeDataBuilder::new("text_2").build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_1, - rev_id: 1, - }, - // The node at path 1 is not existing when inserting the text_2 to path 2. - InsertNode { - path: 3.into(), - node_data: text_2.clone(), - rev_id: 2, - }, - AssertNode { - path: 1.into(), - expected: Some(placeholder_node()), - }, - AssertNode { - path: 2.into(), - expected: Some(placeholder_node()), - }, - AssertNode { - path: 3.into(), - expected: Some(text_2), - }, - ]; - test.run_scripts(scripts); -} - -#[test] -fn operation_insert_node_when_its_parent_is_not_exist_test3() { - let mut test = NodeTest::new(); - let text_1 = NodeDataBuilder::new("text_1").build(); - let text_2 = NodeDataBuilder::new("text_2").build(); - - let mut placeholder_node = placeholder_node(); - placeholder_node.children.push(text_2.clone()); - - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_1, - rev_id: 1, - }, - // The node at path 1 is not existing when inserting the text_2 to path 2. - InsertNode { - path: vec![1, 0].into(), - node_data: text_2.clone(), - rev_id: 2, - }, - AssertNode { - path: 1.into(), - expected: Some(placeholder_node), - }, - AssertNode { - path: vec![1, 0].into(), - expected: Some(text_2), - }, - ]; - test.run_scripts(scripts); -} - -#[test] -fn operation_insert_node_to_the_end_when_parent_is_not_exist_test() { - let mut test = NodeTest::new(); - let node_0 = NodeData::new("0"); - let node_1 = NodeData::new("1"); - let node_1_1 = NodeData::new("1_1"); - let text_node = NodeData::new("text"); - let mut ghost = placeholder_node(); - ghost.children.push(text_node.clone()); - // 0:0 - // 1:1 - // 0:1_1 - // 1:ghost - // 0:text - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: node_0, - rev_id: 1, - }, - InsertNode { - path: 1.into(), - node_data: node_1, - rev_id: 2, - }, - InsertNode { - path: vec![1, 0].into(), - node_data: node_1_1.clone(), - rev_id: 3, - }, - InsertNode { - path: vec![1, 1, 0].into(), - node_data: text_node.clone(), - rev_id: 4, - }, - AssertNode { - path: vec![1, 0].into(), - expected: Some(node_1_1), - }, - AssertNode { - path: vec![1, 1].into(), - expected: Some(ghost), - }, - AssertNode { - path: vec![1, 1, 0].into(), - expected: Some(text_node), - }, - ]; - test.run_scripts(scripts); -} -#[test] -fn operation_insert_node_when_multiple_parent_is_not_exist_test() { - let mut test = NodeTest::new(); - let text_1 = NodeDataBuilder::new("text_1").build(); - let text_2 = NodeDataBuilder::new("text_2").build(); - - let path = vec![1, 0, 0, 0, 0, 0]; - let mut auto_fill_node = placeholder_node(); - let mut iter_node: &mut NodeData = &mut auto_fill_node; - let insert_path = path.split_at(1).1; - for (index, _) in insert_path.iter().enumerate() { - if index == insert_path.len() - 1 { - iter_node.children.push(text_2.clone()); - } else { - iter_node.children.push(placeholder_node()); - iter_node = iter_node.children.last_mut().unwrap(); - } - } - - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_1, - rev_id: 1, - }, - InsertNode { - path: path.clone().into(), - node_data: text_2.clone(), - rev_id: 2, - }, - AssertNode { - path: vec![1].into(), - expected: Some(auto_fill_node), - }, - AssertNode { - path: path.into(), - expected: Some(text_2), - }, - ]; - test.run_scripts(scripts); -} - -#[test] -fn operation_insert_node_when_multiple_parent_is_not_exist_test2() { - let mut test = NodeTest::new(); - // 0:ghost - // 0:ghost - // 1:ghost - // 0:text - let mut text_node_parent = placeholder_node(); - let text_node = NodeDataBuilder::new("text").build(); - text_node_parent.children.push(text_node.clone()); - - let mut ghost = placeholder_node(); - ghost.children.push(placeholder_node()); - ghost.children.push(text_node_parent.clone()); - - let path = vec![1, 1, 0]; - let scripts = vec![ - InsertNode { - path: path.into(), - node_data: text_node.clone(), - rev_id: 1, - }, - // 0:ghost - // 1:ghost - // 0:ghost - // 1:ghost - // 0:text - AssertNode { - path: 0.into(), - expected: Some(placeholder_node()), - }, - AssertNode { - path: 1.into(), - expected: Some(ghost), - }, - AssertNumberOfChildrenAtPath { - path: Some(1.into()), - expected: 2, - }, - AssertNode { - path: vec![1, 1].into(), - expected: Some(text_node_parent), - }, - AssertNode { - path: vec![1, 1, 0].into(), - expected: Some(text_node), - }, - ]; - test.run_scripts(scripts); -} - -#[test] -fn operation_insert_node_when_multiple_parent_is_not_exist_test3() { - let mut test = NodeTest::new(); - let text_node = NodeDataBuilder::new("text").build(); - let path = vec![3, 3, 0]; - let scripts = vec![ - InsertNode { - path: path.clone().into(), - node_data: text_node.clone(), - rev_id: 1, - }, - // 0:ghost - // 1:ghost - // 2:ghost - // 3:ghost - // 0:ghost - // 1:ghost - // 2:ghost - // 3:ghost - // 0:text - AssertNode { - path: 0.into(), - expected: Some(placeholder_node()), - }, - AssertNode { - path: 1.into(), - expected: Some(placeholder_node()), - }, - AssertNode { - path: 2.into(), - expected: Some(placeholder_node()), - }, - AssertNumberOfChildrenAtPath { - path: Some(3.into()), - expected: 4, - }, - AssertNode { - path: path.into(), - expected: Some(text_node), - }, - ]; - test.run_scripts(scripts); -} +use crate::node::script::NodeScript::*; +use crate::node::script::NodeTest; + +use lib_ot::core::{placeholder_node, NodeData, NodeDataBuilder, NodeOperation, Path}; + +#[test] +fn operation_insert_op_transform_test() { + let node_1 = NodeDataBuilder::new("text_1").build(); + let node_2 = NodeDataBuilder::new("text_2").build(); + let op_1 = NodeOperation::Insert { + path: Path(vec![0, 1]), + nodes: vec![node_1], + }; + + let mut insert_2 = NodeOperation::Insert { + path: Path(vec![0, 1]), + nodes: vec![node_2], + }; + + // let mut node_tree = NodeTree::new("root"); + // node_tree.apply_op(insert_1.clone()).unwrap(); + + op_1.transform(&mut insert_2); + let json = serde_json::to_string(&insert_2).unwrap(); + assert_eq!( + json, + r#"{"op":"insert","path":[0,2],"nodes":[{"type":"text_2"}]}"# + ); +} + +#[test] +fn operation_insert_one_level_path_test() { + let node_data_1 = NodeDataBuilder::new("text_1").build(); + let node_data_2 = NodeDataBuilder::new("text_2").build(); + let node_data_3 = NodeDataBuilder::new("text_3").build(); + let node_3 = node_data_3.clone(); + // 0: text_1 + // 1: text_2 + // + // Insert a new operation with rev_id 2 to index 1,but the index was already taken, so + // it needs to be transformed. + // + // 0: text_1 + // 1: text_2 + // 2: text_3 + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node_data_1.clone(), + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: node_data_2.clone(), + rev_id: 2, + }, + InsertNode { + path: 1.into(), + node_data: node_data_3.clone(), + rev_id: 2, + }, + AssertNode { + path: 2.into(), + expected: Some(node_3.clone()), + }, + ]; + NodeTest::new().run_scripts(scripts); + + // If the rev_id of the node_data_3 is 3. then the tree will be: + // 0: text_1 + // 1: text_3 + // 2: text_2 + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node_data_1, + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: node_data_2, + rev_id: 2, + }, + InsertNode { + path: 1.into(), + node_data: node_data_3, + rev_id: 3, + }, + AssertNode { + path: 1.into(), + expected: Some(node_3), + }, + ]; + NodeTest::new().run_scripts(scripts); +} + +#[test] +fn operation_insert_with_multiple_level_path_test() { + let mut test = NodeTest::new(); + let node_data_1 = NodeDataBuilder::new("text_1") + .add_node_data(NodeDataBuilder::new("text_1_1").build()) + .add_node_data(NodeDataBuilder::new("text_1_2").build()) + .build(); + + let node_data_2 = NodeDataBuilder::new("text_2") + .add_node_data(NodeDataBuilder::new("text_2_1").build()) + .add_node_data(NodeDataBuilder::new("text_2_2").build()) + .build(); + + let node_data_3 = NodeDataBuilder::new("text_3").build(); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node_data_1, + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: node_data_2, + rev_id: 2, + }, + InsertNode { + path: 1.into(), + node_data: node_data_3.clone(), + rev_id: 2, + }, + AssertNode { + path: 2.into(), + expected: Some(node_data_3), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_insert_node_out_of_bound_test() { + let mut test = NodeTest::new(); + let image_a = NodeData::new("image_a"); + let image_b = NodeData::new("image_b"); + let image = NodeDataBuilder::new("image_1") + .add_node_data(image_a) + .add_node_data(image_b) + .build(); + let text_node = NodeDataBuilder::new("text_1").add_node_data(image).build(); + let image_c = NodeData::new("image_c"); + + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node, + rev_id: 1, + }, + // 0:text_1 + // 0:image_1 + // 0:image_a + // 1:image_b + InsertNode { + path: vec![0, 0, 3].into(), + node_data: image_c.clone(), + rev_id: 2, + }, + // 0:text_1 + // 0:image_1 + // 0:image_a + // 1:image_b + // 2:placeholder node + // 3:image_c + AssertNode { + path: vec![0, 0, 2].into(), + expected: Some(placeholder_node()), + }, + AssertNode { + path: vec![0, 0, 3].into(), + expected: Some(image_c), + }, + AssertNode { + path: vec![0, 0, 10].into(), + expected: None, + }, + ]; + test.run_scripts(scripts); +} +#[test] +fn operation_insert_node_when_parent_is_not_exist_test1() { + let mut test = NodeTest::new(); + let text_1 = NodeDataBuilder::new("text_1").build(); + let text_2 = NodeDataBuilder::new("text_2").build(); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_1, + rev_id: 1, + }, + // The node at path 1 is not existing when inserting the text_2 to path 2. + InsertNode { + path: 2.into(), + node_data: text_2.clone(), + rev_id: 2, + }, + AssertNode { + path: 1.into(), + expected: Some(placeholder_node()), + }, + AssertNode { + path: 2.into(), + expected: Some(text_2), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_insert_node_when_parent_is_not_exist_test2() { + let mut test = NodeTest::new(); + let text_1 = NodeDataBuilder::new("text_1").build(); + let text_2 = NodeDataBuilder::new("text_2").build(); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_1, + rev_id: 1, + }, + // The node at path 1 is not existing when inserting the text_2 to path 2. + InsertNode { + path: 3.into(), + node_data: text_2.clone(), + rev_id: 2, + }, + AssertNode { + path: 1.into(), + expected: Some(placeholder_node()), + }, + AssertNode { + path: 2.into(), + expected: Some(placeholder_node()), + }, + AssertNode { + path: 3.into(), + expected: Some(text_2), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_insert_node_when_its_parent_is_not_exist_test3() { + let mut test = NodeTest::new(); + let text_1 = NodeDataBuilder::new("text_1").build(); + let text_2 = NodeDataBuilder::new("text_2").build(); + + let mut placeholder_node = placeholder_node(); + placeholder_node.children.push(text_2.clone()); + + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_1, + rev_id: 1, + }, + // The node at path 1 is not existing when inserting the text_2 to path 2. + InsertNode { + path: vec![1, 0].into(), + node_data: text_2.clone(), + rev_id: 2, + }, + AssertNode { + path: 1.into(), + expected: Some(placeholder_node), + }, + AssertNode { + path: vec![1, 0].into(), + expected: Some(text_2), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_insert_node_to_the_end_when_parent_is_not_exist_test() { + let mut test = NodeTest::new(); + let node_0 = NodeData::new("0"); + let node_1 = NodeData::new("1"); + let node_1_1 = NodeData::new("1_1"); + let text_node = NodeData::new("text"); + let mut ghost = placeholder_node(); + ghost.children.push(text_node.clone()); + // 0:0 + // 1:1 + // 0:1_1 + // 1:ghost + // 0:text + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node_0, + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: node_1, + rev_id: 2, + }, + InsertNode { + path: vec![1, 0].into(), + node_data: node_1_1.clone(), + rev_id: 3, + }, + InsertNode { + path: vec![1, 1, 0].into(), + node_data: text_node.clone(), + rev_id: 4, + }, + AssertNode { + path: vec![1, 0].into(), + expected: Some(node_1_1), + }, + AssertNode { + path: vec![1, 1].into(), + expected: Some(ghost), + }, + AssertNode { + path: vec![1, 1, 0].into(), + expected: Some(text_node), + }, + ]; + test.run_scripts(scripts); +} +#[test] +fn operation_insert_node_when_multiple_parent_is_not_exist_test() { + let mut test = NodeTest::new(); + let text_1 = NodeDataBuilder::new("text_1").build(); + let text_2 = NodeDataBuilder::new("text_2").build(); + + let path = vec![1, 0, 0, 0, 0, 0]; + let mut auto_fill_node = placeholder_node(); + let mut iter_node: &mut NodeData = &mut auto_fill_node; + let insert_path = path.split_at(1).1; + for (index, _) in insert_path.iter().enumerate() { + if index == insert_path.len() - 1 { + iter_node.children.push(text_2.clone()); + } else { + iter_node.children.push(placeholder_node()); + iter_node = iter_node.children.last_mut().unwrap(); + } + } + + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_1, + rev_id: 1, + }, + InsertNode { + path: path.clone().into(), + node_data: text_2.clone(), + rev_id: 2, + }, + AssertNode { + path: vec![1].into(), + expected: Some(auto_fill_node), + }, + AssertNode { + path: path.into(), + expected: Some(text_2), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_insert_node_when_multiple_parent_is_not_exist_test2() { + let mut test = NodeTest::new(); + // 0:ghost + // 0:ghost + // 1:ghost + // 0:text + let mut text_node_parent = placeholder_node(); + let text_node = NodeDataBuilder::new("text").build(); + text_node_parent.children.push(text_node.clone()); + + let mut ghost = placeholder_node(); + ghost.children.push(placeholder_node()); + ghost.children.push(text_node_parent.clone()); + + let path = vec![1, 1, 0]; + let scripts = vec![ + InsertNode { + path: path.into(), + node_data: text_node.clone(), + rev_id: 1, + }, + // 0:ghost + // 1:ghost + // 0:ghost + // 1:ghost + // 0:text + AssertNode { + path: 0.into(), + expected: Some(placeholder_node()), + }, + AssertNode { + path: 1.into(), + expected: Some(ghost), + }, + AssertNumberOfChildrenAtPath { + path: Some(1.into()), + expected: 2, + }, + AssertNode { + path: vec![1, 1].into(), + expected: Some(text_node_parent), + }, + AssertNode { + path: vec![1, 1, 0].into(), + expected: Some(text_node), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_insert_node_when_multiple_parent_is_not_exist_test3() { + let mut test = NodeTest::new(); + let text_node = NodeDataBuilder::new("text").build(); + let path = vec![3, 3, 0]; + let scripts = vec![ + InsertNode { + path: path.clone().into(), + node_data: text_node.clone(), + rev_id: 1, + }, + // 0:ghost + // 1:ghost + // 2:ghost + // 3:ghost + // 0:ghost + // 1:ghost + // 2:ghost + // 3:ghost + // 0:text + AssertNode { + path: 0.into(), + expected: Some(placeholder_node()), + }, + AssertNode { + path: 1.into(), + expected: Some(placeholder_node()), + }, + AssertNode { + path: 2.into(), + expected: Some(placeholder_node()), + }, + AssertNumberOfChildrenAtPath { + path: Some(3.into()), + expected: 4, + }, + AssertNode { + path: path.into(), + expected: Some(text_node), + }, + ]; + test.run_scripts(scripts); +} diff --git a/shared-lib/lib-ot/tests/node/script.rs b/shared-lib/lib-ot/tests/node/script.rs index 7fff043653..22f911508a 100644 --- a/shared-lib/lib-ot/tests/node/script.rs +++ b/shared-lib/lib-ot/tests/node/script.rs @@ -2,232 +2,242 @@ use lib_ot::core::{NodeTreeContext, OperationTransform, Transaction}; use lib_ot::text_delta::DeltaTextOperationBuilder; use lib_ot::{ - core::attributes::AttributeHashMap, - core::{Body, Changeset, NodeData, NodeTree, Path, TransactionBuilder}, - text_delta::DeltaTextOperations, + core::attributes::AttributeHashMap, + core::{Body, Changeset, NodeData, NodeTree, Path, TransactionBuilder}, + text_delta::DeltaTextOperations, }; use std::collections::HashMap; pub enum NodeScript { - InsertNode { - path: Path, - node_data: NodeData, - rev_id: usize, - }, - InsertNodes { - path: Path, - node_data_list: Vec, - rev_id: usize, - }, - UpdateAttributes { - path: Path, - attributes: AttributeHashMap, - }, - UpdateBody { - path: Path, - changeset: Changeset, - }, - DeleteNode { - path: Path, - rev_id: usize, - }, - AssertNumberOfChildrenAtPath { - path: Option, - expected: usize, - }, - AssertNodesAtRoot { - expected: Vec, - }, - #[allow(dead_code)] - AssertNodesAtPath { - path: Path, - expected: Vec, - }, - AssertNode { - path: Path, - expected: Option, - }, - AssertNodeAttributes { - path: Path, - expected: &'static str, - }, - AssertNodeDelta { - path: Path, - expected: DeltaTextOperations, - }, - AssertNodeDeltaContent { - path: Path, - expected: &'static str, - }, - #[allow(dead_code)] - AssertTreeJSON { - expected: String, - }, + InsertNode { + path: Path, + node_data: NodeData, + rev_id: usize, + }, + InsertNodes { + path: Path, + node_data_list: Vec, + rev_id: usize, + }, + UpdateAttributes { + path: Path, + attributes: AttributeHashMap, + }, + UpdateBody { + path: Path, + changeset: Changeset, + }, + DeleteNode { + path: Path, + rev_id: usize, + }, + AssertNumberOfChildrenAtPath { + path: Option, + expected: usize, + }, + AssertNodesAtRoot { + expected: Vec, + }, + #[allow(dead_code)] + AssertNodesAtPath { + path: Path, + expected: Vec, + }, + AssertNode { + path: Path, + expected: Option, + }, + AssertNodeAttributes { + path: Path, + expected: &'static str, + }, + AssertNodeDelta { + path: Path, + expected: DeltaTextOperations, + }, + AssertNodeDeltaContent { + path: Path, + expected: &'static str, + }, + #[allow(dead_code)] + AssertTreeJSON { + expected: String, + }, } pub struct NodeTest { - rev_id: usize, - rev_operations: HashMap, - node_tree: NodeTree, + rev_id: usize, + rev_operations: HashMap, + node_tree: NodeTree, } impl NodeTest { - pub fn new() -> Self { - Self { - rev_id: 0, - rev_operations: HashMap::new(), - node_tree: NodeTree::new(NodeTreeContext::default()), - } + pub fn new() -> Self { + Self { + rev_id: 0, + rev_operations: HashMap::new(), + node_tree: NodeTree::new(NodeTreeContext::default()), } + } - pub fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script); - } + pub fn run_scripts(&mut self, scripts: Vec) { + for script in scripts { + self.run_script(script); } + } - pub fn run_script(&mut self, script: NodeScript) { - match script { - NodeScript::InsertNode { - path, - node_data: node, - rev_id, - } => { - let mut transaction = TransactionBuilder::new().insert_node_at_path(path, node).build(); - self.transform_transaction_if_need(&mut transaction, rev_id); - self.apply_transaction(transaction); - } - NodeScript::InsertNodes { - path, - node_data_list, - rev_id, - } => { - let mut transaction = TransactionBuilder::new() - .insert_nodes_at_path(path, node_data_list) - .build(); - self.transform_transaction_if_need(&mut transaction, rev_id); - self.apply_transaction(transaction); - } - NodeScript::UpdateAttributes { path, attributes } => { - let node = self.node_tree.get_node_data_at_path(&path).unwrap(); - let transaction = TransactionBuilder::new() - .update_node_at_path( - &path, - Changeset::Attributes { - new: attributes, - old: node.attributes, - }, - ) - .build(); - self.apply_transaction(transaction); - } - NodeScript::UpdateBody { path, changeset } => { - // - let transaction = TransactionBuilder::new().update_node_at_path(&path, changeset).build(); - self.apply_transaction(transaction); - } - NodeScript::DeleteNode { path, rev_id } => { - let mut transaction = TransactionBuilder::new() - .delete_node_at_path(&self.node_tree, &path) - .build(); - self.transform_transaction_if_need(&mut transaction, rev_id); - self.apply_transaction(transaction); - } - - NodeScript::AssertNode { path, expected } => { - let node = self.node_tree.get_node_data_at_path(&path); - assert_eq!(node, expected.map(|e| e.into())); - } - NodeScript::AssertNodeAttributes { path, expected } => { - let node = self.node_tree.get_node_data_at_path(&path).unwrap(); - assert_eq!(node.attributes.to_json().unwrap(), expected); - } - NodeScript::AssertNumberOfChildrenAtPath { path, expected } => match path { - None => { - let len = self.node_tree.number_of_children(None); - assert_eq!(len, expected) - } - Some(path) => { - let node_id = self.node_tree.node_id_at_path(path).unwrap(); - let len = self.node_tree.number_of_children(Some(node_id)); - assert_eq!(len, expected) - } + pub fn run_script(&mut self, script: NodeScript) { + match script { + NodeScript::InsertNode { + path, + node_data: node, + rev_id, + } => { + let mut transaction = TransactionBuilder::new() + .insert_node_at_path(path, node) + .build(); + self.transform_transaction_if_need(&mut transaction, rev_id); + self.apply_transaction(transaction); + }, + NodeScript::InsertNodes { + path, + node_data_list, + rev_id, + } => { + let mut transaction = TransactionBuilder::new() + .insert_nodes_at_path(path, node_data_list) + .build(); + self.transform_transaction_if_need(&mut transaction, rev_id); + self.apply_transaction(transaction); + }, + NodeScript::UpdateAttributes { path, attributes } => { + let node = self.node_tree.get_node_data_at_path(&path).unwrap(); + let transaction = TransactionBuilder::new() + .update_node_at_path( + &path, + Changeset::Attributes { + new: attributes, + old: node.attributes, }, - NodeScript::AssertNodesAtRoot { expected } => { - let nodes = self.node_tree.get_node_data_at_root().unwrap().children; - assert_eq!(nodes, expected) - } - NodeScript::AssertNodesAtPath { path, expected } => { - let nodes = self.node_tree.get_node_data_at_path(&path).unwrap().children; - assert_eq!(nodes, expected) - } - NodeScript::AssertNodeDelta { path, expected } => { - let node = self.node_tree.get_node_at_path(&path).unwrap(); - if let Body::Delta(delta) = node.body.clone() { - debug_assert_eq!(delta, expected); - } else { - panic!("Node body type not match, expect Delta"); - } - } - NodeScript::AssertNodeDeltaContent { path, expected } => { - let node = self.node_tree.get_node_at_path(&path).unwrap(); - if let Body::Delta(delta) = node.body.clone() { - debug_assert_eq!(delta.content().unwrap(), expected); - } else { - panic!("Node body type not match, expect Delta"); - } - } - NodeScript::AssertTreeJSON { expected } => { - let json = serde_json::to_string(&self.node_tree).unwrap(); - assert_eq!(json, expected) - } - } - } + ) + .build(); + self.apply_transaction(transaction); + }, + NodeScript::UpdateBody { path, changeset } => { + // + let transaction = TransactionBuilder::new() + .update_node_at_path(&path, changeset) + .build(); + self.apply_transaction(transaction); + }, + NodeScript::DeleteNode { path, rev_id } => { + let mut transaction = TransactionBuilder::new() + .delete_node_at_path(&self.node_tree, &path) + .build(); + self.transform_transaction_if_need(&mut transaction, rev_id); + self.apply_transaction(transaction); + }, - fn apply_transaction(&mut self, transaction: Transaction) { - self.rev_id += 1; - self.rev_operations.insert(self.rev_id, transaction.clone()); - self.node_tree.apply_transaction(transaction).unwrap(); - } - - fn transform_transaction_if_need(&mut self, transaction: &mut Transaction, rev_id: usize) { - if self.rev_id >= rev_id { - for rev_id in rev_id..=self.rev_id { - let old_transaction = self.rev_operations.get(&rev_id).unwrap(); - *transaction = old_transaction.transform(transaction).unwrap(); - } + NodeScript::AssertNode { path, expected } => { + let node = self.node_tree.get_node_data_at_path(&path); + assert_eq!(node, expected.map(|e| e.into())); + }, + NodeScript::AssertNodeAttributes { path, expected } => { + let node = self.node_tree.get_node_data_at_path(&path).unwrap(); + assert_eq!(node.attributes.to_json().unwrap(), expected); + }, + NodeScript::AssertNumberOfChildrenAtPath { path, expected } => match path { + None => { + let len = self.node_tree.number_of_children(None); + assert_eq!(len, expected) + }, + Some(path) => { + let node_id = self.node_tree.node_id_at_path(path).unwrap(); + let len = self.node_tree.number_of_children(Some(node_id)); + assert_eq!(len, expected) + }, + }, + NodeScript::AssertNodesAtRoot { expected } => { + let nodes = self.node_tree.get_node_data_at_root().unwrap().children; + assert_eq!(nodes, expected) + }, + NodeScript::AssertNodesAtPath { path, expected } => { + let nodes = self + .node_tree + .get_node_data_at_path(&path) + .unwrap() + .children; + assert_eq!(nodes, expected) + }, + NodeScript::AssertNodeDelta { path, expected } => { + let node = self.node_tree.get_node_at_path(&path).unwrap(); + if let Body::Delta(delta) = node.body.clone() { + debug_assert_eq!(delta, expected); + } else { + panic!("Node body type not match, expect Delta"); } + }, + NodeScript::AssertNodeDeltaContent { path, expected } => { + let node = self.node_tree.get_node_at_path(&path).unwrap(); + if let Body::Delta(delta) = node.body.clone() { + debug_assert_eq!(delta.content().unwrap(), expected); + } else { + panic!("Node body type not match, expect Delta"); + } + }, + NodeScript::AssertTreeJSON { expected } => { + let json = serde_json::to_string(&self.node_tree).unwrap(); + assert_eq!(json, expected) + }, } + } + + fn apply_transaction(&mut self, transaction: Transaction) { + self.rev_id += 1; + self.rev_operations.insert(self.rev_id, transaction.clone()); + self.node_tree.apply_transaction(transaction).unwrap(); + } + + fn transform_transaction_if_need(&mut self, transaction: &mut Transaction, rev_id: usize) { + if self.rev_id >= rev_id { + for rev_id in rev_id..=self.rev_id { + let old_transaction = self.rev_operations.get(&rev_id).unwrap(); + *transaction = old_transaction.transform(transaction).unwrap(); + } + } + } } pub fn edit_node_delta( - delta: &DeltaTextOperations, - new_delta: DeltaTextOperations, + delta: &DeltaTextOperations, + new_delta: DeltaTextOperations, ) -> (Changeset, DeltaTextOperations) { - let inverted = new_delta.invert(&delta); - let expected = delta.compose(&new_delta).unwrap(); - let changeset = Changeset::Delta { - delta: new_delta.clone(), - inverted: inverted.clone(), - }; - (changeset, expected) + let inverted = new_delta.invert(&delta); + let expected = delta.compose(&new_delta).unwrap(); + let changeset = Changeset::Delta { + delta: new_delta.clone(), + inverted: inverted.clone(), + }; + (changeset, expected) } pub fn make_node_delta_changeset( - initial_content: &str, - insert_str: &str, + initial_content: &str, + insert_str: &str, ) -> (DeltaTextOperations, Changeset, DeltaTextOperations) { - let initial_content = initial_content.to_owned(); - let initial_delta = DeltaTextOperationBuilder::new().insert(&initial_content).build(); - let delta = DeltaTextOperationBuilder::new() - .retain(initial_content.len()) - .insert(insert_str) - .build(); - let inverted = delta.invert(&initial_delta); - let expected = initial_delta.compose(&delta).unwrap(); - let changeset = Changeset::Delta { - delta: delta.clone(), - inverted: inverted.clone(), - }; - (initial_delta, changeset, expected) + let initial_content = initial_content.to_owned(); + let initial_delta = DeltaTextOperationBuilder::new() + .insert(&initial_content) + .build(); + let delta = DeltaTextOperationBuilder::new() + .retain(initial_content.len()) + .insert(insert_str) + .build(); + let inverted = delta.invert(&initial_delta); + let expected = initial_delta.compose(&delta).unwrap(); + let changeset = Changeset::Delta { + delta: delta.clone(), + inverted: inverted.clone(), + }; + (initial_delta, changeset, expected) } diff --git a/shared-lib/lib-ot/tests/node/serde_test.rs b/shared-lib/lib-ot/tests/node/serde_test.rs index b3a76d06d2..0d781c393d 100644 --- a/shared-lib/lib-ot/tests/node/serde_test.rs +++ b/shared-lib/lib-ot/tests/node/serde_test.rs @@ -1,71 +1,78 @@ -use lib_ot::core::{AttributeBuilder, Changeset, NodeData, NodeDataBuilder, NodeOperation, NodeTree, Path}; +use lib_ot::core::{ + AttributeBuilder, Changeset, NodeData, NodeDataBuilder, NodeOperation, NodeTree, Path, +}; use lib_ot::text_delta::DeltaTextOperationBuilder; #[test] fn operation_insert_node_serde_test() { - let insert = NodeOperation::Insert { - path: Path(vec![0, 1]), - nodes: vec![NodeData::new("text".to_owned())], - }; - let result = serde_json::to_string(&insert).unwrap(); - assert_eq!(result, r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}"#); + let insert = NodeOperation::Insert { + path: Path(vec![0, 1]), + nodes: vec![NodeData::new("text".to_owned())], + }; + let result = serde_json::to_string(&insert).unwrap(); + assert_eq!( + result, + r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}"# + ); } #[test] fn operation_insert_node_with_children_serde_test() { - let node = NodeDataBuilder::new("text") - .add_node_data(NodeData::new("sub_text".to_owned())) - .build(); + let node = NodeDataBuilder::new("text") + .add_node_data(NodeData::new("sub_text".to_owned())) + .build(); - let insert = NodeOperation::Insert { - path: Path(vec![0, 1]), - nodes: vec![node], - }; - assert_eq!( - serde_json::to_string(&insert).unwrap(), - r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text","children":[{"type":"sub_text"}]}]}"# - ); + let insert = NodeOperation::Insert { + path: Path(vec![0, 1]), + nodes: vec![node], + }; + assert_eq!( + serde_json::to_string(&insert).unwrap(), + r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text","children":[{"type":"sub_text"}]}]}"# + ); } #[test] fn operation_update_node_attributes_serde_test() { - let operation = NodeOperation::Update { - path: Path(vec![0, 1]), - changeset: Changeset::Attributes { - new: AttributeBuilder::new().insert("bold", true).build(), - old: AttributeBuilder::new().insert("bold", false).build(), - }, - }; + let operation = NodeOperation::Update { + path: Path(vec![0, 1]), + changeset: Changeset::Attributes { + new: AttributeBuilder::new().insert("bold", true).build(), + old: AttributeBuilder::new().insert("bold", false).build(), + }, + }; - let result = serde_json::to_string(&operation).unwrap(); - assert_eq!( - result, - r#"{"op":"update","path":[0,1],"changeset":{"attributes":{"new":{"bold":true},"old":{"bold":false}}}}"# - ); + let result = serde_json::to_string(&operation).unwrap(); + assert_eq!( + result, + r#"{"op":"update","path":[0,1],"changeset":{"attributes":{"new":{"bold":true},"old":{"bold":false}}}}"# + ); } #[test] fn operation_update_node_body_serialize_test() { - let delta = DeltaTextOperationBuilder::new().insert("AppFlowy...").build(); - let inverted = delta.invert_str(""); - let changeset = Changeset::Delta { delta, inverted }; - let insert = NodeOperation::Update { - path: Path(vec![0, 1]), - changeset, - }; - let result = serde_json::to_string(&insert).unwrap(); - assert_eq!( - result, - r#"{"op":"update","path":[0,1],"changeset":{"delta":{"delta":[{"insert":"AppFlowy..."}],"inverted":[{"delete":11}]}}}"# - ); + let delta = DeltaTextOperationBuilder::new() + .insert("AppFlowy...") + .build(); + let inverted = delta.invert_str(""); + let changeset = Changeset::Delta { delta, inverted }; + let insert = NodeOperation::Update { + path: Path(vec![0, 1]), + changeset, + }; + let result = serde_json::to_string(&insert).unwrap(); + assert_eq!( + result, + r#"{"op":"update","path":[0,1],"changeset":{"delta":{"delta":[{"insert":"AppFlowy..."}],"inverted":[{"delete":11}]}}}"# + ); } #[test] fn operation_update_node_body_deserialize_test() { - let json_1 = r#"{"op":"update","path":[0,1],"changeset":{"delta":{"delta":[{"insert":"AppFlowy..."}],"inverted":[{"delete":11}]}}}"#; - let operation: NodeOperation = serde_json::from_str(json_1).unwrap(); - let json_2 = serde_json::to_string(&operation).unwrap(); - assert_eq!(json_1, json_2); + let json_1 = r#"{"op":"update","path":[0,1],"changeset":{"delta":{"delta":[{"insert":"AppFlowy..."}],"inverted":[{"delete":11}]}}}"#; + let operation: NodeOperation = serde_json::from_str(json_1).unwrap(); + let json_2 = serde_json::to_string(&operation).unwrap(); + assert_eq!(json_1, json_2); } // #[test] @@ -98,17 +105,17 @@ fn operation_update_node_body_deserialize_test() { #[test] fn node_tree_serialize_test() { - let tree: NodeTree = serde_json::from_str(TREE_JSON).unwrap(); - let json = serde_json::to_string_pretty(&tree).unwrap(); - assert_eq!(json, TREE_JSON); + let tree: NodeTree = serde_json::from_str(TREE_JSON).unwrap(); + let json = serde_json::to_string_pretty(&tree).unwrap(); + assert_eq!(json, TREE_JSON); } #[test] fn node_tree_serde_test() { - let tree: NodeTree = serde_json::from_str(TREE_JSON).unwrap(); - let bytes = tree.to_bytes(); - let tree = NodeTree::from_bytes(&bytes).unwrap(); - assert_eq!(bytes, tree.to_bytes()); + let tree: NodeTree = serde_json::from_str(TREE_JSON).unwrap(); + let bytes = tree.to_bytes(); + let tree = NodeTree::from_bytes(&bytes).unwrap(); + assert_eq!(bytes, tree.to_bytes()); } #[allow(dead_code)] diff --git a/shared-lib/lib-ot/tests/node/transaction_compose_test.rs b/shared-lib/lib-ot/tests/node/transaction_compose_test.rs index 0622b49e86..6d1c386ee7 100644 --- a/shared-lib/lib-ot/tests/node/transaction_compose_test.rs +++ b/shared-lib/lib-ot/tests/node/transaction_compose_test.rs @@ -1,104 +1,121 @@ use crate::node::script::{edit_node_delta, make_node_delta_changeset}; -use lib_ot::core::{AttributeEntry, Changeset, NodeDataBuilder, NodeOperation, Transaction, TransactionBuilder}; +use lib_ot::core::{ + AttributeEntry, Changeset, NodeDataBuilder, NodeOperation, Transaction, TransactionBuilder, +}; use lib_ot::text_delta::DeltaTextOperationBuilder; #[test] fn transaction_compose_update_after_insert_test() { - let (initial_delta, changeset, _) = make_node_delta_changeset("Hello", " world"); - let node_data = NodeDataBuilder::new("text").insert_delta(initial_delta).build(); + let (initial_delta, changeset, _) = make_node_delta_changeset("Hello", " world"); + let node_data = NodeDataBuilder::new("text") + .insert_delta(initial_delta) + .build(); - // Modify the same path, the operations will be merged after composing if possible. - let mut transaction_a = TransactionBuilder::new().insert_node_at_path(0, node_data).build(); - let transaction_b = TransactionBuilder::new().update_node_at_path(0, changeset).build(); - transaction_a.compose(transaction_b).unwrap(); + // Modify the same path, the operations will be merged after composing if possible. + let mut transaction_a = TransactionBuilder::new() + .insert_node_at_path(0, node_data) + .build(); + let transaction_b = TransactionBuilder::new() + .update_node_at_path(0, changeset) + .build(); + transaction_a.compose(transaction_b).unwrap(); - // The operations are merged into one operation - assert_eq!(transaction_a.operations.len(), 1); - assert_eq!( - transaction_a.to_json().unwrap(), - r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello world"}]}}]}]}"# - ); + // The operations are merged into one operation + assert_eq!(transaction_a.operations.len(), 1); + assert_eq!( + transaction_a.to_json().unwrap(), + r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello world"}]}}]}]}"# + ); } #[test] fn transaction_compose_multiple_update_test() { - let (initial_delta, changeset_1, final_delta) = make_node_delta_changeset("Hello", " world"); - let mut transaction = TransactionBuilder::new() - .insert_node_at_path(0, NodeDataBuilder::new("text").insert_delta(initial_delta).build()) - .build(); - let (changeset_2, _) = edit_node_delta( - &final_delta, - DeltaTextOperationBuilder::new() - .retain(final_delta.utf16_target_len) - .insert("😁") - .build(), - ); + let (initial_delta, changeset_1, final_delta) = make_node_delta_changeset("Hello", " world"); + let mut transaction = TransactionBuilder::new() + .insert_node_at_path( + 0, + NodeDataBuilder::new("text") + .insert_delta(initial_delta) + .build(), + ) + .build(); + let (changeset_2, _) = edit_node_delta( + &final_delta, + DeltaTextOperationBuilder::new() + .retain(final_delta.utf16_target_len) + .insert("😁") + .build(), + ); - let mut other_transaction = Transaction::new(); + let mut other_transaction = Transaction::new(); - // the following two update operations will be merged into one - let update_1 = TransactionBuilder::new().update_node_at_path(0, changeset_1).build(); - other_transaction.compose(update_1).unwrap(); + // the following two update operations will be merged into one + let update_1 = TransactionBuilder::new() + .update_node_at_path(0, changeset_1) + .build(); + other_transaction.compose(update_1).unwrap(); - let update_2 = TransactionBuilder::new().update_node_at_path(0, changeset_2).build(); - other_transaction.compose(update_2).unwrap(); + let update_2 = TransactionBuilder::new() + .update_node_at_path(0, changeset_2) + .build(); + other_transaction.compose(update_2).unwrap(); - let inverted = Transaction::from_operations(other_transaction.operations.inverted()); + let inverted = Transaction::from_operations(other_transaction.operations.inverted()); - // the update operation will be merged into insert operation - transaction.compose(other_transaction).unwrap(); - assert_eq!(transaction.operations.len(), 1); - assert_eq!( - transaction.to_json().unwrap(), - r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello world😁"}]}}]}]}"# - ); + // the update operation will be merged into insert operation + transaction.compose(other_transaction).unwrap(); + assert_eq!(transaction.operations.len(), 1); + assert_eq!( + transaction.to_json().unwrap(), + r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello world😁"}]}}]}]}"# + ); - transaction.compose(inverted).unwrap(); - assert_eq!( - transaction.to_json().unwrap(), - r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello"}]}}]}]}"# - ); + transaction.compose(inverted).unwrap(); + assert_eq!( + transaction.to_json().unwrap(), + r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello"}]}}]}]}"# + ); } #[test] fn transaction_compose_multiple_attribute_test() { - let delta = DeltaTextOperationBuilder::new().insert("Hello").build(); - let node = NodeDataBuilder::new("text").insert_delta(delta).build(); + let delta = DeltaTextOperationBuilder::new().insert("Hello").build(); + let node = NodeDataBuilder::new("text").insert_delta(delta).build(); - let insert_operation = NodeOperation::Insert { - path: 0.into(), - nodes: vec![node], - }; + let insert_operation = NodeOperation::Insert { + path: 0.into(), + nodes: vec![node], + }; - let mut transaction = Transaction::new(); - transaction.push_operation(insert_operation); + let mut transaction = Transaction::new(); + transaction.push_operation(insert_operation); - let new_attribute = AttributeEntry::new("subtype", "bulleted-list"); - let update_operation = NodeOperation::Update { - path: 0.into(), - changeset: Changeset::Attributes { - new: new_attribute.clone().into(), - old: Default::default(), - }, - }; - transaction.push_operation(update_operation); - assert_eq!( - transaction.to_json().unwrap(), - r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello"}]}}]},{"op":"update","path":[0],"changeset":{"attributes":{"new":{"subtype":"bulleted-list"},"old":{}}}}]}"# - ); + let new_attribute = AttributeEntry::new("subtype", "bulleted-list"); + let update_operation = NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Attributes { + new: new_attribute.clone().into(), + old: Default::default(), + }, + }; + transaction.push_operation(update_operation); + assert_eq!( + transaction.to_json().unwrap(), + r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello"}]}}]},{"op":"update","path":[0],"changeset":{"attributes":{"new":{"subtype":"bulleted-list"},"old":{}}}}]}"# + ); - let old_attribute = new_attribute; - let new_attribute = AttributeEntry::new("subtype", "number-list"); - transaction.push_operation(NodeOperation::Update { - path: 0.into(), - changeset: Changeset::Attributes { - new: new_attribute.into(), - old: old_attribute.into(), - }, - }); + let old_attribute = new_attribute; + let new_attribute = AttributeEntry::new("subtype", "number-list"); + transaction.push_operation(NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Attributes { + new: new_attribute.into(), + old: old_attribute.into(), + }, + }); - assert_eq!( - transaction.to_json().unwrap(), - r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello"}]}}]},{"op":"update","path":[0],"changeset":{"attributes":{"new":{"subtype":"number-list"},"old":{"subtype":"bulleted-list"}}}}]}"# - ); + assert_eq!( + transaction.to_json().unwrap(), + r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello"}]}}]},{"op":"update","path":[0],"changeset":{"attributes":{"new":{"subtype":"number-list"},"old":{"subtype":"bulleted-list"}}}}]}"# + ); } diff --git a/shared-lib/lib-ot/tests/node/tree_test.rs b/shared-lib/lib-ot/tests/node/tree_test.rs index 0606b88cc9..212ecf5238 100644 --- a/shared-lib/lib-ot/tests/node/tree_test.rs +++ b/shared-lib/lib-ot/tests/node/tree_test.rs @@ -5,704 +5,727 @@ use lib_ot::core::{NodeData, NodeDataBuilder, Path}; #[test] fn node_insert_test() { - let mut test = NodeTest::new(); - let node_data = NodeData::new("text"); - let path: Path = vec![0].into(); - let scripts = vec![ - InsertNode { - path: path.clone(), - node_data: node_data.clone(), - rev_id: 1, - }, - AssertNode { - path, - expected: Some(node_data), - }, - ]; - test.run_scripts(scripts); + let mut test = NodeTest::new(); + let node_data = NodeData::new("text"); + let path: Path = vec![0].into(); + let scripts = vec![ + InsertNode { + path: path.clone(), + node_data: node_data.clone(), + rev_id: 1, + }, + AssertNode { + path, + expected: Some(node_data), + }, + ]; + test.run_scripts(scripts); } #[test] #[should_panic] fn node_insert_with_empty_path_test() { - let mut test = NodeTest::new(); - let scripts = vec![InsertNode { - path: vec![].into(), - node_data: NodeData::new("text"), - rev_id: 1, - }]; - test.run_scripts(scripts); + let mut test = NodeTest::new(); + let scripts = vec![InsertNode { + path: vec![].into(), + node_data: NodeData::new("text"), + rev_id: 1, + }]; + test.run_scripts(scripts); } #[test] fn tree_insert_multiple_nodes_at_root_path_test() { - let mut test = NodeTest::new(); - let node_1 = NodeData::new("a"); - let node_2 = NodeData::new("b"); - let node_3 = NodeData::new("c"); - let node_data_list = vec![node_1, node_2, node_3]; - let path: Path = vec![0].into(); + let mut test = NodeTest::new(); + let node_1 = NodeData::new("a"); + let node_2 = NodeData::new("b"); + let node_3 = NodeData::new("c"); + let node_data_list = vec![node_1, node_2, node_3]; + let path: Path = vec![0].into(); - // Insert three nodes under the root - let scripts = vec![ - // 0:a - // 1:b - // 2:c - InsertNodes { - path, - node_data_list: node_data_list.clone(), - rev_id: 1, - }, - AssertNodesAtRoot { - expected: node_data_list, - }, - ]; - test.run_scripts(scripts); + // Insert three nodes under the root + let scripts = vec![ + // 0:a + // 1:b + // 2:c + InsertNodes { + path, + node_data_list: node_data_list.clone(), + rev_id: 1, + }, + AssertNodesAtRoot { + expected: node_data_list, + }, + ]; + test.run_scripts(scripts); } #[test] fn tree_insert_multiple_nodes_at_root_path_test2() { - let mut test = NodeTest::new(); - let node_1 = NodeData::new("a"); - let node_2 = NodeData::new("b"); - let node_3 = NodeData::new("c"); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: node_1.clone(), - rev_id: 1, - }, - InsertNode { - path: 1.into(), - node_data: node_2.clone(), - rev_id: 2, - }, - InsertNode { - path: 2.into(), - node_data: node_3.clone(), - rev_id: 3, - }, - // 0:a - // 1:b - // 2:c - AssertNode { - path: 0.into(), - expected: Some(node_1), - }, - AssertNode { - path: 1.into(), - expected: Some(node_2), - }, - AssertNode { - path: 2.into(), - expected: Some(node_3), - }, - ]; - test.run_scripts(scripts); + let mut test = NodeTest::new(); + let node_1 = NodeData::new("a"); + let node_2 = NodeData::new("b"); + let node_3 = NodeData::new("c"); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node_1.clone(), + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: node_2.clone(), + rev_id: 2, + }, + InsertNode { + path: 2.into(), + node_data: node_3.clone(), + rev_id: 3, + }, + // 0:a + // 1:b + // 2:c + AssertNode { + path: 0.into(), + expected: Some(node_1), + }, + AssertNode { + path: 1.into(), + expected: Some(node_2), + }, + AssertNode { + path: 2.into(), + expected: Some(node_3), + }, + ]; + test.run_scripts(scripts); } #[test] fn node_insert_node_with_children_test() { - let mut test = NodeTest::new(); - let image_1 = NodeData::new("image_a"); - let image_2 = NodeData::new("image_b"); + let mut test = NodeTest::new(); + let image_1 = NodeData::new("image_a"); + let image_2 = NodeData::new("image_b"); - let image = NodeDataBuilder::new("image") - .add_node_data(image_1.clone()) - .add_node_data(image_2.clone()) - .build(); - let node_data = NodeDataBuilder::new("text").add_node_data(image.clone()).build(); - let path: Path = 0.into(); - let scripts = vec![ - InsertNode { - path: path.clone(), - node_data: node_data.clone(), - rev_id: 1, - }, - // 0:text - // 0:image - // 0:image_1 - // 1:image_2 - AssertNode { - path, - expected: Some(node_data), - }, - AssertNode { - path: vec![0, 0].into(), - expected: Some(image), - }, - AssertNode { - path: vec![0, 0, 0].into(), - expected: Some(image_1), - }, - AssertNode { - path: vec![0, 0, 1].into(), - expected: Some(image_2), - }, - ]; - test.run_scripts(scripts); + let image = NodeDataBuilder::new("image") + .add_node_data(image_1.clone()) + .add_node_data(image_2.clone()) + .build(); + let node_data = NodeDataBuilder::new("text") + .add_node_data(image.clone()) + .build(); + let path: Path = 0.into(); + let scripts = vec![ + InsertNode { + path: path.clone(), + node_data: node_data.clone(), + rev_id: 1, + }, + // 0:text + // 0:image + // 0:image_1 + // 1:image_2 + AssertNode { + path, + expected: Some(node_data), + }, + AssertNode { + path: vec![0, 0].into(), + expected: Some(image), + }, + AssertNode { + path: vec![0, 0, 0].into(), + expected: Some(image_1), + }, + AssertNode { + path: vec![0, 0, 1].into(), + expected: Some(image_2), + }, + ]; + test.run_scripts(scripts); } #[test] fn node_insert_node_in_ordered_nodes_test() { - let mut test = NodeTest::new(); - let path_1: Path = 0.into(); - let node_1 = NodeData::new("text_1"); + let mut test = NodeTest::new(); + let path_1: Path = 0.into(); + let node_1 = NodeData::new("text_1"); - let path_2: Path = 1.into(); - let node_2_1 = NodeData::new("text_2_1"); - let node_2_2 = NodeData::new("text_2_2"); + let path_2: Path = 1.into(); + let node_2_1 = NodeData::new("text_2_1"); + let node_2_2 = NodeData::new("text_2_2"); - let path_3: Path = 2.into(); - let node_3 = NodeData::new("text_3"); + let path_3: Path = 2.into(); + let node_3 = NodeData::new("text_3"); - let scripts = vec![ - InsertNode { - path: path_1.clone(), - node_data: node_1.clone(), - rev_id: 1, - }, - InsertNode { - path: path_2.clone(), - node_data: node_2_1.clone(), - rev_id: 2, - }, - InsertNode { - path: path_3.clone(), - node_data: node_3, - rev_id: 3, - }, - // 0:text_1 - // 1:text_2_1 - // 2:text_3 - InsertNode { - path: path_2.clone(), - node_data: node_2_2.clone(), - rev_id: 4, - }, - // 0:text_1 - // 1:text_2_2 - // 2:text_2_1 - // 3:text_3 - AssertNode { - path: path_1, - expected: Some(node_1), - }, - AssertNode { - path: path_2, - expected: Some(node_2_2), - }, - AssertNode { - path: path_3, - expected: Some(node_2_1), - }, - AssertNumberOfChildrenAtPath { - path: None, - expected: 4, - }, - ]; - test.run_scripts(scripts); + let scripts = vec![ + InsertNode { + path: path_1.clone(), + node_data: node_1.clone(), + rev_id: 1, + }, + InsertNode { + path: path_2.clone(), + node_data: node_2_1.clone(), + rev_id: 2, + }, + InsertNode { + path: path_3.clone(), + node_data: node_3, + rev_id: 3, + }, + // 0:text_1 + // 1:text_2_1 + // 2:text_3 + InsertNode { + path: path_2.clone(), + node_data: node_2_2.clone(), + rev_id: 4, + }, + // 0:text_1 + // 1:text_2_2 + // 2:text_2_1 + // 3:text_3 + AssertNode { + path: path_1, + expected: Some(node_1), + }, + AssertNode { + path: path_2, + expected: Some(node_2_2), + }, + AssertNode { + path: path_3, + expected: Some(node_2_1), + }, + AssertNumberOfChildrenAtPath { + path: None, + expected: 4, + }, + ]; + test.run_scripts(scripts); } #[test] fn node_insert_nested_nodes_test() { - let mut test = NodeTest::new(); - let node_data_1_1 = NodeDataBuilder::new("text_1_1").build(); - let node_data_1_2 = NodeDataBuilder::new("text_1_2").build(); - let node_data_1 = NodeDataBuilder::new("text_1") - .add_node_data(node_data_1_1.clone()) - .add_node_data(node_data_1_2.clone()) - .build(); + let mut test = NodeTest::new(); + let node_data_1_1 = NodeDataBuilder::new("text_1_1").build(); + let node_data_1_2 = NodeDataBuilder::new("text_1_2").build(); + let node_data_1 = NodeDataBuilder::new("text_1") + .add_node_data(node_data_1_1.clone()) + .add_node_data(node_data_1_2.clone()) + .build(); - let node_data_2_1 = NodeDataBuilder::new("text_2_1").build(); - let node_data_2_2 = NodeDataBuilder::new("text_2_2").build(); - let node_data_2 = NodeDataBuilder::new("text_2") - .add_node_data(node_data_2_1.clone()) - .add_node_data(node_data_2_2.clone()) - .build(); + let node_data_2_1 = NodeDataBuilder::new("text_2_1").build(); + let node_data_2_2 = NodeDataBuilder::new("text_2_2").build(); + let node_data_2 = NodeDataBuilder::new("text_2") + .add_node_data(node_data_2_1.clone()) + .add_node_data(node_data_2_2.clone()) + .build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: node_data_1, - rev_id: 1, - }, - InsertNode { - path: 1.into(), - node_data: node_data_2, - rev_id: 2, - }, - // the tree will be: - // 0:text_1 - // 0:text_1_1 - // 1:text_1_2 - // 1:text_2 - // 0:text_2_1 - // 1:text_2_2 - AssertNode { - path: vec![0, 0].into(), - expected: Some(node_data_1_1), - }, - AssertNode { - path: vec![0, 1].into(), - expected: Some(node_data_1_2), - }, - AssertNode { - path: vec![1, 0].into(), - expected: Some(node_data_2_1), - }, - AssertNode { - path: vec![1, 1].into(), - expected: Some(node_data_2_2), - }, - ]; - test.run_scripts(scripts); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node_data_1, + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: node_data_2, + rev_id: 2, + }, + // the tree will be: + // 0:text_1 + // 0:text_1_1 + // 1:text_1_2 + // 1:text_2 + // 0:text_2_1 + // 1:text_2_2 + AssertNode { + path: vec![0, 0].into(), + expected: Some(node_data_1_1), + }, + AssertNode { + path: vec![0, 1].into(), + expected: Some(node_data_1_2), + }, + AssertNode { + path: vec![1, 0].into(), + expected: Some(node_data_2_1), + }, + AssertNode { + path: vec![1, 1].into(), + expected: Some(node_data_2_2), + }, + ]; + test.run_scripts(scripts); } #[test] fn node_insert_node_before_existing_nested_nodes_test() { - let mut test = NodeTest::new(); - let node_data_1_1 = NodeDataBuilder::new("text_1_1").build(); - let node_data_1_2 = NodeDataBuilder::new("text_1_2").build(); - let node_data_1 = NodeDataBuilder::new("text_1") - .add_node_data(node_data_1_1.clone()) - .add_node_data(node_data_1_2.clone()) - .build(); + let mut test = NodeTest::new(); + let node_data_1_1 = NodeDataBuilder::new("text_1_1").build(); + let node_data_1_2 = NodeDataBuilder::new("text_1_2").build(); + let node_data_1 = NodeDataBuilder::new("text_1") + .add_node_data(node_data_1_1.clone()) + .add_node_data(node_data_1_2.clone()) + .build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: node_data_1, - rev_id: 1, - }, - // 0:text_1 - // 0:text_1_1 - // 1:text_1_2 - InsertNode { - path: 0.into(), - node_data: NodeDataBuilder::new("text_0").build(), - rev_id: 2, - }, - // 0:text_0 - // 1:text_1 - // 0:text_1_1 - // 1:text_1_2 - AssertNode { - path: vec![1, 0].into(), - expected: Some(node_data_1_1), - }, - AssertNode { - path: vec![1, 1].into(), - expected: Some(node_data_1_2), - }, - ]; - test.run_scripts(scripts); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node_data_1, + rev_id: 1, + }, + // 0:text_1 + // 0:text_1_1 + // 1:text_1_2 + InsertNode { + path: 0.into(), + node_data: NodeDataBuilder::new("text_0").build(), + rev_id: 2, + }, + // 0:text_0 + // 1:text_1 + // 0:text_1_1 + // 1:text_1_2 + AssertNode { + path: vec![1, 0].into(), + expected: Some(node_data_1_1), + }, + AssertNode { + path: vec![1, 1].into(), + expected: Some(node_data_1_2), + }, + ]; + test.run_scripts(scripts); } #[test] fn node_insert_with_attributes_test() { - let mut test = NodeTest::new(); - let path: Path = 0.into(); - let mut inserted_node = NodeData::new("text"); - inserted_node.attributes.insert("bold", true); - inserted_node.attributes.insert("underline", true); + let mut test = NodeTest::new(); + let path: Path = 0.into(); + let mut inserted_node = NodeData::new("text"); + inserted_node.attributes.insert("bold", true); + inserted_node.attributes.insert("underline", true); - let scripts = vec![ - InsertNode { - path: path.clone(), - node_data: inserted_node.clone(), - rev_id: 1, - }, - UpdateAttributes { - path: path.clone(), - attributes: inserted_node.attributes.clone(), - }, - AssertNode { - path, - expected: Some(inserted_node), - }, - ]; - test.run_scripts(scripts); + let scripts = vec![ + InsertNode { + path: path.clone(), + node_data: inserted_node.clone(), + rev_id: 1, + }, + UpdateAttributes { + path: path.clone(), + attributes: inserted_node.attributes.clone(), + }, + AssertNode { + path, + expected: Some(inserted_node), + }, + ]; + test.run_scripts(scripts); } #[test] fn node_delete_test() { - let mut test = NodeTest::new(); - let inserted_node = NodeData::new("text"); - let path: Path = 0.into(); - let scripts = vec![ - InsertNode { - path: path.clone(), - node_data: inserted_node, - rev_id: 1, - }, - DeleteNode { - path: path.clone(), - rev_id: 2, - }, - AssertNode { path, expected: None }, - ]; - test.run_scripts(scripts); + let mut test = NodeTest::new(); + let inserted_node = NodeData::new("text"); + let path: Path = 0.into(); + let scripts = vec![ + InsertNode { + path: path.clone(), + node_data: inserted_node, + rev_id: 1, + }, + DeleteNode { + path: path.clone(), + rev_id: 2, + }, + AssertNode { + path, + expected: None, + }, + ]; + test.run_scripts(scripts); } #[test] fn node_delete_node_from_list_test() { - let mut test = NodeTest::new(); - let image_a = NodeData::new("image_a"); - let image_b = NodeData::new("image_b"); + let mut test = NodeTest::new(); + let image_a = NodeData::new("image_a"); + let image_b = NodeData::new("image_b"); - let image_1 = NodeDataBuilder::new("image_1") - .add_node_data(image_a.clone()) - .add_node_data(image_b.clone()) - .build(); - let text_node_1 = NodeDataBuilder::new("text_1").add_node_data(image_1).build(); - let image_2 = NodeDataBuilder::new("image_2") - .add_node_data(image_a) - .add_node_data(image_b) - .build(); - let text_node_2 = NodeDataBuilder::new("text_2").add_node_data(image_2.clone()).build(); + let image_1 = NodeDataBuilder::new("image_1") + .add_node_data(image_a.clone()) + .add_node_data(image_b.clone()) + .build(); + let text_node_1 = NodeDataBuilder::new("text_1") + .add_node_data(image_1) + .build(); + let image_2 = NodeDataBuilder::new("image_2") + .add_node_data(image_a) + .add_node_data(image_b) + .build(); + let text_node_2 = NodeDataBuilder::new("text_2") + .add_node_data(image_2.clone()) + .build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_node_1, - rev_id: 1, - }, - InsertNode { - path: 1.into(), - node_data: text_node_2.clone(), - rev_id: 2, - }, - DeleteNode { - path: 0.into(), - rev_id: 3, - }, - AssertNode { - path: 1.into(), - expected: None, - }, - AssertNode { - path: 0.into(), - expected: Some(text_node_2), - }, - AssertNode { - path: vec![0, 0].into(), - expected: Some(image_2), - }, - ]; - test.run_scripts(scripts); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node_1, + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: text_node_2.clone(), + rev_id: 2, + }, + DeleteNode { + path: 0.into(), + rev_id: 3, + }, + AssertNode { + path: 1.into(), + expected: None, + }, + AssertNode { + path: 0.into(), + expected: Some(text_node_2), + }, + AssertNode { + path: vec![0, 0].into(), + expected: Some(image_2), + }, + ]; + test.run_scripts(scripts); } #[test] fn node_delete_nested_node_test() { - let mut test = NodeTest::new(); - let image_a = NodeData::new("image_a"); - let image_b = NodeData::new("image_b"); + let mut test = NodeTest::new(); + let image_a = NodeData::new("image_a"); + let image_b = NodeData::new("image_b"); - let image_1 = NodeDataBuilder::new("image_1") - .add_node_data(image_a.clone()) - .add_node_data(image_b.clone()) - .build(); - let text_node_1 = NodeDataBuilder::new("text_1").add_node_data(image_1).build(); + let image_1 = NodeDataBuilder::new("image_1") + .add_node_data(image_a.clone()) + .add_node_data(image_b.clone()) + .build(); + let text_node_1 = NodeDataBuilder::new("text_1") + .add_node_data(image_1) + .build(); - let image_2 = NodeDataBuilder::new("image_2") - .add_node_data(image_a.clone()) - .add_node_data(image_b.clone()) - .build(); - let text_node_2 = NodeDataBuilder::new("text_2").add_node_data(image_2).build(); + let image_2 = NodeDataBuilder::new("image_2") + .add_node_data(image_a.clone()) + .add_node_data(image_b.clone()) + .build(); + let text_node_2 = NodeDataBuilder::new("text_2") + .add_node_data(image_2) + .build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_node_1, - rev_id: 1, - }, - InsertNode { - path: 1.into(), - node_data: text_node_2, - rev_id: 2, - }, - // 0:text_1 - // 0:image_1 - // 0:image_a - // 1:image_b - // 1:text_2 - // 0:image_2 - // 0:image_a - // 1:image_b - DeleteNode { - path: vec![0, 0, 0].into(), - rev_id: 3, - }, - // 0:text_1 - // 0:image_1 - // 0:image_b - // 1:text_2 - // 0:image_2 - // 0:image_a - // 1:image_b - AssertNode { - path: vec![0, 0, 0].into(), - expected: Some(image_b.clone()), - }, - DeleteNode { - path: vec![0, 0].into(), - rev_id: 4, - }, - // 0:text_1 - // 1:text_2 - // 0:image_2 - // 0:image_a - // 1:image_b - AssertNumberOfChildrenAtPath { - path: Some(0.into()), - expected: 0, - }, - AssertNode { - path: vec![0].into(), - expected: Some(NodeDataBuilder::new("text_1").build()), - }, - AssertNode { - path: vec![1, 0, 0].into(), - expected: Some(image_a), - }, - AssertNode { - path: vec![1, 0, 1].into(), - expected: Some(image_b), - }, - ]; - test.run_scripts(scripts); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node_1, + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: text_node_2, + rev_id: 2, + }, + // 0:text_1 + // 0:image_1 + // 0:image_a + // 1:image_b + // 1:text_2 + // 0:image_2 + // 0:image_a + // 1:image_b + DeleteNode { + path: vec![0, 0, 0].into(), + rev_id: 3, + }, + // 0:text_1 + // 0:image_1 + // 0:image_b + // 1:text_2 + // 0:image_2 + // 0:image_a + // 1:image_b + AssertNode { + path: vec![0, 0, 0].into(), + expected: Some(image_b.clone()), + }, + DeleteNode { + path: vec![0, 0].into(), + rev_id: 4, + }, + // 0:text_1 + // 1:text_2 + // 0:image_2 + // 0:image_a + // 1:image_b + AssertNumberOfChildrenAtPath { + path: Some(0.into()), + expected: 0, + }, + AssertNode { + path: vec![0].into(), + expected: Some(NodeDataBuilder::new("text_1").build()), + }, + AssertNode { + path: vec![1, 0, 0].into(), + expected: Some(image_a), + }, + AssertNode { + path: vec![1, 0, 1].into(), + expected: Some(image_b), + }, + ]; + test.run_scripts(scripts); } #[test] fn node_delete_children_test() { - let mut test = NodeTest::new(); - let inserted_node = NodeDataBuilder::new("text") - .add_node_data(NodeDataBuilder::new("sub_text_1").build()) - .add_node_data(NodeDataBuilder::new("sub_text_2").build()) - .add_node_data(NodeDataBuilder::new("sub_text_3").build()) - .build(); + let mut test = NodeTest::new(); + let inserted_node = NodeDataBuilder::new("text") + .add_node_data(NodeDataBuilder::new("sub_text_1").build()) + .add_node_data(NodeDataBuilder::new("sub_text_2").build()) + .add_node_data(NodeDataBuilder::new("sub_text_3").build()) + .build(); - let scripts = vec![ - InsertNode { - path: vec![0].into(), - node_data: inserted_node, - rev_id: 1, - }, - AssertNode { - path: vec![0, 0].into(), - expected: Some(NodeDataBuilder::new("sub_text_1").build()), - }, - AssertNode { - path: vec![0, 1].into(), - expected: Some(NodeDataBuilder::new("sub_text_2").build()), - }, - AssertNode { - path: vec![0, 2].into(), - expected: Some(NodeDataBuilder::new("sub_text_3").build()), - }, - AssertNumberOfChildrenAtPath { - path: Some(Path(vec![0])), - expected: 3, - }, - DeleteNode { - path: vec![0, 0].into(), - rev_id: 2, - }, - AssertNode { - path: vec![0, 0].into(), - expected: Some(NodeDataBuilder::new("sub_text_2").build()), - }, - AssertNumberOfChildrenAtPath { - path: Some(Path(vec![0])), - expected: 2, - }, - ]; - test.run_scripts(scripts); + let scripts = vec![ + InsertNode { + path: vec![0].into(), + node_data: inserted_node, + rev_id: 1, + }, + AssertNode { + path: vec![0, 0].into(), + expected: Some(NodeDataBuilder::new("sub_text_1").build()), + }, + AssertNode { + path: vec![0, 1].into(), + expected: Some(NodeDataBuilder::new("sub_text_2").build()), + }, + AssertNode { + path: vec![0, 2].into(), + expected: Some(NodeDataBuilder::new("sub_text_3").build()), + }, + AssertNumberOfChildrenAtPath { + path: Some(Path(vec![0])), + expected: 3, + }, + DeleteNode { + path: vec![0, 0].into(), + rev_id: 2, + }, + AssertNode { + path: vec![0, 0].into(), + expected: Some(NodeDataBuilder::new("sub_text_2").build()), + }, + AssertNumberOfChildrenAtPath { + path: Some(Path(vec![0])), + expected: 2, + }, + ]; + test.run_scripts(scripts); } #[test] fn node_reorder_sub_nodes_test() { - let mut test = NodeTest::new(); - let image_a = NodeData::new("image_a"); - let image_b = NodeData::new("image_b"); + let mut test = NodeTest::new(); + let image_a = NodeData::new("image_a"); + let image_b = NodeData::new("image_b"); - let child_1 = NodeDataBuilder::new("image_1") - .add_node_data(image_a.clone()) - .add_node_data(image_b.clone()) - .build(); - let text_node_1 = NodeDataBuilder::new("text_1").add_node_data(child_1).build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_node_1, - rev_id: 1, - }, - // 0:text_1 - // 0:image_1 - // 0:image_a - // 1:image_b - DeleteNode { - path: vec![0, 0, 0].into(), - rev_id: 2, - }, - // 0:text_1 - // 0:image_1 - // 0:image_b - InsertNode { - path: vec![0, 0, 1].into(), - node_data: image_a.clone(), - rev_id: 3, - }, - // 0:text_1 - // 0:image_1 - // 0:image_b - // 1:image_a - AssertNode { - path: vec![0, 0, 0].into(), - expected: Some(image_b), - }, - AssertNode { - path: vec![0, 0, 1].into(), - expected: Some(image_a), - }, - ]; - test.run_scripts(scripts); + let child_1 = NodeDataBuilder::new("image_1") + .add_node_data(image_a.clone()) + .add_node_data(image_b.clone()) + .build(); + let text_node_1 = NodeDataBuilder::new("text_1") + .add_node_data(child_1) + .build(); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node_1, + rev_id: 1, + }, + // 0:text_1 + // 0:image_1 + // 0:image_a + // 1:image_b + DeleteNode { + path: vec![0, 0, 0].into(), + rev_id: 2, + }, + // 0:text_1 + // 0:image_1 + // 0:image_b + InsertNode { + path: vec![0, 0, 1].into(), + node_data: image_a.clone(), + rev_id: 3, + }, + // 0:text_1 + // 0:image_1 + // 0:image_b + // 1:image_a + AssertNode { + path: vec![0, 0, 0].into(), + expected: Some(image_b), + }, + AssertNode { + path: vec![0, 0, 1].into(), + expected: Some(image_a), + }, + ]; + test.run_scripts(scripts); } #[test] fn node_reorder_nodes_test() { - let mut test = NodeTest::new(); - let image_a = NodeData::new("image_a"); - let image_b = NodeData::new("image_b"); + let mut test = NodeTest::new(); + let image_a = NodeData::new("image_a"); + let image_b = NodeData::new("image_b"); - let image_1 = NodeDataBuilder::new("image_1") - .add_node_data(image_a.clone()) - .add_node_data(image_b.clone()) - .build(); - let text_node_1 = NodeDataBuilder::new("text_1").add_node_data(image_1.clone()).build(); + let image_1 = NodeDataBuilder::new("image_1") + .add_node_data(image_a.clone()) + .add_node_data(image_b.clone()) + .build(); + let text_node_1 = NodeDataBuilder::new("text_1") + .add_node_data(image_1.clone()) + .build(); - let image_2 = NodeDataBuilder::new("image_2") - .add_node_data(image_a.clone()) - .add_node_data(image_b.clone()) - .build(); - let text_node_2 = NodeDataBuilder::new("text_2").add_node_data(image_2.clone()).build(); + let image_2 = NodeDataBuilder::new("image_2") + .add_node_data(image_a.clone()) + .add_node_data(image_b.clone()) + .build(); + let text_node_2 = NodeDataBuilder::new("text_2") + .add_node_data(image_2.clone()) + .build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: text_node_1.clone(), - rev_id: 1, - }, - InsertNode { - path: 0.into(), - node_data: text_node_2.clone(), - rev_id: 1, - }, - // 0:text_1 - // 0:image_1 - // 0:image_a - // 1:image_b - // 1:text_2 - // 0:image_2 - // 0:image_a - // 1:image_b - DeleteNode { - path: vec![0].into(), - rev_id: 3, - }, - AssertNode { - path: vec![0].into(), - expected: Some(text_node_2.clone()), - }, - InsertNode { - path: vec![1].into(), - node_data: text_node_1.clone(), - rev_id: 4, - }, - // 0:text_2 - // 0:image_2 - // 0:image_a - // 1:image_b - // 1:text_1 - // 0:image_1 - // 0:image_a - // 1:image_b - AssertNode { - path: vec![0].into(), - expected: Some(text_node_2), - }, - AssertNode { - path: vec![0, 0].into(), - expected: Some(image_2), - }, - AssertNode { - path: vec![0, 0, 0].into(), - expected: Some(image_a), - }, - AssertNode { - path: vec![1].into(), - expected: Some(text_node_1), - }, - AssertNode { - path: vec![1, 0].into(), - expected: Some(image_1), - }, - AssertNode { - path: vec![1, 0, 1].into(), - expected: Some(image_b), - }, - ]; - test.run_scripts(scripts); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node_1.clone(), + rev_id: 1, + }, + InsertNode { + path: 0.into(), + node_data: text_node_2.clone(), + rev_id: 1, + }, + // 0:text_1 + // 0:image_1 + // 0:image_a + // 1:image_b + // 1:text_2 + // 0:image_2 + // 0:image_a + // 1:image_b + DeleteNode { + path: vec![0].into(), + rev_id: 3, + }, + AssertNode { + path: vec![0].into(), + expected: Some(text_node_2.clone()), + }, + InsertNode { + path: vec![1].into(), + node_data: text_node_1.clone(), + rev_id: 4, + }, + // 0:text_2 + // 0:image_2 + // 0:image_a + // 1:image_b + // 1:text_1 + // 0:image_1 + // 0:image_a + // 1:image_b + AssertNode { + path: vec![0].into(), + expected: Some(text_node_2), + }, + AssertNode { + path: vec![0, 0].into(), + expected: Some(image_2), + }, + AssertNode { + path: vec![0, 0, 0].into(), + expected: Some(image_a), + }, + AssertNode { + path: vec![1].into(), + expected: Some(text_node_1), + }, + AssertNode { + path: vec![1, 0].into(), + expected: Some(image_1), + }, + AssertNode { + path: vec![1, 0, 1].into(), + expected: Some(image_b), + }, + ]; + test.run_scripts(scripts); } #[test] fn node_update_body_test() { - let mut test = NodeTest::new(); - let (initial_delta, changeset, expected) = make_node_delta_changeset("Hello", "AppFlowy"); - let node = NodeDataBuilder::new("text").insert_delta(initial_delta).build(); + let mut test = NodeTest::new(); + let (initial_delta, changeset, expected) = make_node_delta_changeset("Hello", "AppFlowy"); + let node = NodeDataBuilder::new("text") + .insert_delta(initial_delta) + .build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: node, - rev_id: 1, - }, - UpdateBody { - path: 0.into(), - changeset, - }, - AssertNodeDelta { - path: 0.into(), - expected, - }, - ]; - test.run_scripts(scripts); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node, + rev_id: 1, + }, + UpdateBody { + path: 0.into(), + changeset, + }, + AssertNodeDelta { + path: 0.into(), + expected, + }, + ]; + test.run_scripts(scripts); } #[test] fn node_inverted_body_changeset_test() { - let mut test = NodeTest::new(); - let (initial_delta, changeset, _expected) = make_node_delta_changeset("Hello", "AppFlowy"); - let node = NodeDataBuilder::new("text").insert_delta(initial_delta.clone()).build(); + let mut test = NodeTest::new(); + let (initial_delta, changeset, _expected) = make_node_delta_changeset("Hello", "AppFlowy"); + let node = NodeDataBuilder::new("text") + .insert_delta(initial_delta.clone()) + .build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: node, - rev_id: 1, - }, - UpdateBody { - path: 0.into(), - changeset: changeset.clone(), - }, - UpdateBody { - path: 0.into(), - changeset: changeset.inverted(), - }, - AssertNodeDelta { - path: 0.into(), - expected: initial_delta, - }, - ]; - test.run_scripts(scripts); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node, + rev_id: 1, + }, + UpdateBody { + path: 0.into(), + changeset: changeset.clone(), + }, + UpdateBody { + path: 0.into(), + changeset: changeset.inverted(), + }, + AssertNodeDelta { + path: 0.into(), + expected: initial_delta, + }, + ]; + test.run_scripts(scripts); } diff --git a/shared-lib/lib-ws/src/connect.rs b/shared-lib/lib-ws/src/connect.rs index 02b207282d..7863cb4859 100644 --- a/shared-lib/lib-ws/src/connect.rs +++ b/shared-lib/lib-ws/src/connect.rs @@ -1,209 +1,223 @@ #![allow(clippy::all)] use crate::{ - errors::{internal_error, WSError}, - MsgReceiver, MsgSender, + errors::{internal_error, WSError}, + MsgReceiver, MsgSender, }; use futures_core::{future::BoxFuture, ready}; use futures_util::{FutureExt, StreamExt}; use pin_project::pin_project; use std::{ - fmt, - future::Future, - pin::Pin, - task::{Context, Poll}, + fmt, + future::Future, + pin::Pin, + task::{Context, Poll}, }; use tokio::net::TcpStream; use tokio_tungstenite::{ - connect_async, - tungstenite::{handshake::client::Response, Error, Message}, - MaybeTlsStream, WebSocketStream, + connect_async, + tungstenite::{handshake::client::Response, Error, Message}, + MaybeTlsStream, WebSocketStream, }; type WsConnectResult = Result<(WebSocketStream>, Response), Error>; #[pin_project] pub struct WSConnectionFuture { - msg_tx: Option, - ws_rx: Option, - #[pin] - fut: Pin + Send + Sync>>, + msg_tx: Option, + ws_rx: Option, + #[pin] + fut: Pin + Send + Sync>>, } impl WSConnectionFuture { - pub fn new(msg_tx: MsgSender, ws_rx: MsgReceiver, addr: String) -> Self { - WSConnectionFuture { - msg_tx: Some(msg_tx), - ws_rx: Some(ws_rx), - fut: Box::pin(async move { connect_async(&addr).await }), - } + pub fn new(msg_tx: MsgSender, ws_rx: MsgReceiver, addr: String) -> Self { + WSConnectionFuture { + msg_tx: Some(msg_tx), + ws_rx: Some(ws_rx), + fut: Box::pin(async move { connect_async(&addr).await }), } + } } impl Future for WSConnectionFuture { - type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // [[pin]] - // poll async function. The following methods not work. - // 1. - // let f = connect_async(""); - // pin_mut!(f); - // ready!(Pin::new(&mut a).poll(cx)) - // - // 2.ready!(Pin::new(&mut Box::pin(connect_async(""))).poll(cx)) - // - // An async method calls poll multiple times and might return to the executor. A - // single poll call can only return to the executor once and will get - // resumed through another poll invocation. the connect_async call multiple time - // from the beginning. So I use fut to hold the future and continue to - // poll it. (Fix me if i was wrong) - loop { - return match ready!(self.as_mut().project().fut.poll(cx)) { - Ok((stream, _)) => { - tracing::debug!("[WebSocket]: connect success"); - let (msg_tx, ws_rx) = ( - self.msg_tx - .take() - .expect("[WebSocket]: WSConnection should be call once "), - self.ws_rx - .take() - .expect("[WebSocket]: WSConnection should be call once "), - ); - Poll::Ready(Ok(WSStream::new(msg_tx, ws_rx, stream))) - } - Err(error) => { - tracing::debug!("[WebSocket]: ❌ connect failed: {:?}", error); - Poll::Ready(Err(error.into())) - } - }; - } + type Output = Result; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // [[pin]] + // poll async function. The following methods not work. + // 1. + // let f = connect_async(""); + // pin_mut!(f); + // ready!(Pin::new(&mut a).poll(cx)) + // + // 2.ready!(Pin::new(&mut Box::pin(connect_async(""))).poll(cx)) + // + // An async method calls poll multiple times and might return to the executor. A + // single poll call can only return to the executor once and will get + // resumed through another poll invocation. the connect_async call multiple time + // from the beginning. So I use fut to hold the future and continue to + // poll it. (Fix me if i was wrong) + loop { + return match ready!(self.as_mut().project().fut.poll(cx)) { + Ok((stream, _)) => { + tracing::debug!("[WebSocket]: connect success"); + let (msg_tx, ws_rx) = ( + self + .msg_tx + .take() + .expect("[WebSocket]: WSConnection should be call once "), + self + .ws_rx + .take() + .expect("[WebSocket]: WSConnection should be call once "), + ); + Poll::Ready(Ok(WSStream::new(msg_tx, ws_rx, stream))) + }, + Err(error) => { + tracing::debug!("[WebSocket]: ❌ connect failed: {:?}", error); + Poll::Ready(Err(error.into())) + }, + }; } + } } type Fut = BoxFuture<'static, Result<(), WSError>>; #[pin_project] pub struct WSStream { - #[allow(dead_code)] - msg_tx: MsgSender, - #[pin] - inner: Option<(Fut, Fut)>, + #[allow(dead_code)] + msg_tx: MsgSender, + #[pin] + inner: Option<(Fut, Fut)>, } impl WSStream { - pub fn new(msg_tx: MsgSender, ws_rx: MsgReceiver, stream: WebSocketStream>) -> Self { - let (ws_write, ws_read) = stream.split(); - Self { - msg_tx: msg_tx.clone(), - inner: Some(( - Box::pin(async move { - let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); - let read = async { - ws_read - .for_each(|message| async { - match tx.send(send_message(msg_tx.clone(), message)) { - Ok(_) => {} - Err(e) => log::error!("[WebSocket]: WSStream sender closed unexpectedly: {} ", e), - } - }) - .await; - Ok(()) - }; + pub fn new( + msg_tx: MsgSender, + ws_rx: MsgReceiver, + stream: WebSocketStream>, + ) -> Self { + let (ws_write, ws_read) = stream.split(); + Self { + msg_tx: msg_tx.clone(), + inner: Some(( + Box::pin(async move { + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); + let read = async { + ws_read + .for_each(|message| async { + match tx.send(send_message(msg_tx.clone(), message)) { + Ok(_) => {}, + Err(e) => log::error!("[WebSocket]: WSStream sender closed unexpectedly: {} ", e), + } + }) + .await; + Ok(()) + }; - let read_ret = async { - loop { - match rx.recv().await { - None => { - return Err(WSError::internal() - .context("[WebSocket]: WSStream receiver closed unexpectedly")); - } - Some(result) => { - if result.is_err() { - return result; - } - } - } - } - }; - futures::pin_mut!(read); - futures::pin_mut!(read_ret); - return tokio::select! { - result = read => result, - result = read_ret => result, - }; - }), - Box::pin(async move { - let result = ws_rx.map(Ok).forward(ws_write).await.map_err(internal_error); - result - }), - )), - } + let read_ret = async { + loop { + match rx.recv().await { + None => { + return Err( + WSError::internal() + .context("[WebSocket]: WSStream receiver closed unexpectedly"), + ); + }, + Some(result) => { + if result.is_err() { + return result; + } + }, + } + } + }; + futures::pin_mut!(read); + futures::pin_mut!(read_ret); + return tokio::select! { + result = read => result, + result = read_ret => result, + }; + }), + Box::pin(async move { + let result = ws_rx + .map(Ok) + .forward(ws_write) + .await + .map_err(internal_error); + result + }), + )), } + } } impl fmt::Debug for WSStream { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("WSStream").finish() - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("WSStream").finish() + } } impl Future for WSStream { - type Output = Result<(), WSError>; + type Output = Result<(), WSError>; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let (mut ws_read, mut ws_write) = self.inner.take().unwrap(); - match ws_read.poll_unpin(cx) { - Poll::Ready(l) => Poll::Ready(l), - Poll::Pending => { - // - match ws_write.poll_unpin(cx) { - Poll::Ready(r) => Poll::Ready(r), - Poll::Pending => { - self.inner = Some((ws_read, ws_write)); - Poll::Pending - } - } - } + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let (mut ws_read, mut ws_write) = self.inner.take().unwrap(); + match ws_read.poll_unpin(cx) { + Poll::Ready(l) => Poll::Ready(l), + Poll::Pending => { + // + match ws_write.poll_unpin(cx) { + Poll::Ready(r) => Poll::Ready(r), + Poll::Pending => { + self.inner = Some((ws_read, ws_write)); + Poll::Pending + }, } + }, } + } } fn send_message(msg_tx: MsgSender, message: Result) -> Result<(), WSError> { - match message { - Ok(Message::Binary(bytes)) => msg_tx.unbounded_send(Message::Binary(bytes)).map_err(internal_error), - Ok(_) => Ok(()), - Err(e) => Err(WSError::internal().context(e)), - } + match message { + Ok(Message::Binary(bytes)) => msg_tx + .unbounded_send(Message::Binary(bytes)) + .map_err(internal_error), + Ok(_) => Ok(()), + Err(e) => Err(WSError::internal().context(e)), + } } #[allow(dead_code)] pub struct Retry { - f: F, - #[allow(dead_code)] - retry_time: usize, - addr: String, + f: F, + #[allow(dead_code)] + retry_time: usize, + addr: String, } impl Retry where - F: Fn(&str), + F: Fn(&str), { - #[allow(dead_code)] - pub fn new(addr: &str, f: F) -> Self { - Self { - f, - retry_time: 3, - addr: addr.to_owned(), - } + #[allow(dead_code)] + pub fn new(addr: &str, f: F) -> Self { + Self { + f, + retry_time: 3, + addr: addr.to_owned(), } + } } impl Future for Retry where - F: Fn(&str), + F: Fn(&str), { - type Output = (); + type Output = (); - fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { - (self.f)(&self.addr); + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + (self.f)(&self.addr); - Poll::Ready(()) - } + Poll::Ready(()) + } } diff --git a/shared-lib/lib-ws/src/errors.rs b/shared-lib/lib-ws/src/errors.rs index 1a17137ecd..35182ef8d7 100644 --- a/shared-lib/lib-ws/src/errors.rs +++ b/shared-lib/lib-ws/src/errors.rs @@ -9,97 +9,97 @@ use url::ParseError; #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct WSError { - pub code: ErrorCode, - pub msg: String, + pub code: ErrorCode, + pub msg: String, } macro_rules! static_ws_error { - ($name:ident, $status:expr) => { - #[allow(non_snake_case, missing_docs)] - pub fn $name() -> WSError { - WSError { - code: $status, - msg: format!("{}", $status), - } - } - }; + ($name:ident, $status:expr) => { + #[allow(non_snake_case, missing_docs)] + pub fn $name() -> WSError { + WSError { + code: $status, + msg: format!("{}", $status), + } + } + }; } impl WSError { - #[allow(dead_code)] - pub(crate) fn new(code: ErrorCode) -> WSError { - WSError { - code, - msg: "".to_string(), - } + #[allow(dead_code)] + pub(crate) fn new(code: ErrorCode) -> WSError { + WSError { + code, + msg: "".to_string(), } + } - pub fn context(mut self, error: T) -> Self { - self.msg = format!("{:?}", error); - self - } + pub fn context(mut self, error: T) -> Self { + self.msg = format!("{:?}", error); + self + } - static_ws_error!(internal, ErrorCode::InternalError); - static_ws_error!(unsupported_message, ErrorCode::UnsupportedMessage); - static_ws_error!(unauthorized, ErrorCode::Unauthorized); + static_ws_error!(internal, ErrorCode::InternalError); + static_ws_error!(unsupported_message, ErrorCode::UnsupportedMessage); + static_ws_error!(unauthorized, ErrorCode::Unauthorized); } pub(crate) fn internal_error(e: T) -> WSError where - T: std::fmt::Debug, + T: std::fmt::Debug, { - WSError::internal().context(e) + WSError::internal().context(e) } #[derive(Debug, Clone, Serialize_repr, Deserialize_repr, Display, PartialEq, Eq)] #[repr(u8)] pub enum ErrorCode { - InternalError = 0, - UnsupportedMessage = 1, - Unauthorized = 2, + InternalError = 0, + UnsupportedMessage = 1, + Unauthorized = 2, } impl std::default::Default for ErrorCode { - fn default() -> Self { - ErrorCode::InternalError - } + fn default() -> Self { + ErrorCode::InternalError + } } impl std::convert::From for WSError { - fn from(error: ParseError) -> Self { - WSError::internal().context(error) - } + fn from(error: ParseError) -> Self { + WSError::internal().context(error) + } } impl std::convert::From for WSError { - fn from(error: protobuf::ProtobufError) -> Self { - WSError::internal().context(error) - } + fn from(error: protobuf::ProtobufError) -> Self { + WSError::internal().context(error) + } } impl std::convert::From> for WSError { - fn from(error: TrySendError) -> Self { - WSError::internal().context(error) - } + fn from(error: TrySendError) -> Self { + WSError::internal().context(error) + } } impl std::convert::From for WSError { - fn from(error: RecvError) -> Self { - WSError::internal().context(error) - } + fn from(error: RecvError) -> Self { + WSError::internal().context(error) + } } impl std::convert::From for WSError { - fn from(error: tokio_tungstenite::tungstenite::Error) -> Self { - match error { - tokio_tungstenite::tungstenite::Error::Http(response) => { - if response.status() == StatusCode::UNAUTHORIZED { - WSError::unauthorized() - } else { - WSError::internal().context(response) - } - } - _ => WSError::internal().context(error), + fn from(error: tokio_tungstenite::tungstenite::Error) -> Self { + match error { + tokio_tungstenite::tungstenite::Error::Http(response) => { + if response.status() == StatusCode::UNAUTHORIZED { + WSError::unauthorized() + } else { + WSError::internal().context(response) } + }, + _ => WSError::internal().context(error), } + } } diff --git a/shared-lib/lib-ws/src/msg.rs b/shared-lib/lib-ws/src/msg.rs index f5695c77be..a1713c1a2b 100644 --- a/shared-lib/lib-ws/src/msg.rs +++ b/shared-lib/lib-ws/src/msg.rs @@ -4,47 +4,47 @@ use tokio_tungstenite::tungstenite::Message as TokioMessage; #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct WebSocketRawMessage { - pub channel: WSChannel, - pub data: Vec, + pub channel: WSChannel, + pub data: Vec, } impl WebSocketRawMessage { - pub fn to_bytes(&self) -> Vec { - serde_json::to_vec(&self).unwrap_or_default() - } + pub fn to_bytes(&self) -> Vec { + serde_json::to_vec(&self).unwrap_or_default() + } - pub fn from_bytes>(bytes: T) -> Self { - serde_json::from_slice(bytes.as_ref()).unwrap_or_default() - } + pub fn from_bytes>(bytes: T) -> Self { + serde_json::from_slice(bytes.as_ref()).unwrap_or_default() + } } // The lib-ws crate should not contain business logic.So WSChannel should be removed into another place. #[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Eq, PartialEq, Hash)] #[repr(u8)] pub enum WSChannel { - Document = 0, - Folder = 1, - Database = 2, + Document = 0, + Folder = 1, + Database = 2, } impl std::default::Default for WSChannel { - fn default() -> Self { - WSChannel::Document - } + fn default() -> Self { + WSChannel::Document + } } impl ToString for WSChannel { - fn to_string(&self) -> String { - match self { - WSChannel::Document => "0".to_string(), - WSChannel::Folder => "1".to_string(), - WSChannel::Database => "2".to_string(), - } + fn to_string(&self) -> String { + match self { + WSChannel::Document => "0".to_string(), + WSChannel::Folder => "1".to_string(), + WSChannel::Database => "2".to_string(), } + } } impl std::convert::From for TokioMessage { - fn from(msg: WebSocketRawMessage) -> Self { - TokioMessage::Binary(msg.to_bytes()) - } + fn from(msg: WebSocketRawMessage) -> Self { + TokioMessage::Binary(msg.to_bytes()) + } } diff --git a/shared-lib/lib-ws/src/ws.rs b/shared-lib/lib-ws/src/ws.rs index 6d1c354b29..c589bc3dac 100644 --- a/shared-lib/lib-ws/src/ws.rs +++ b/shared-lib/lib-ws/src/ws.rs @@ -1,8 +1,8 @@ #![allow(clippy::type_complexity)] use crate::{ - connect::{WSConnectionFuture, WSStream}, - errors::WSError, - WSChannel, WebSocketRawMessage, + connect::{WSConnectionFuture, WSStream}, + errors::WSError, + WSChannel, WebSocketRawMessage, }; use dashmap::DashMap; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; @@ -10,17 +10,17 @@ use futures_core::{ready, Stream}; use lib_infra::retry::{Action, FixedInterval, Retry}; use pin_project::pin_project; use std::{ - fmt::Formatter, - future::Future, - pin::Pin, - sync::Arc, - task::{Context, Poll}, - time::Duration, + fmt::Formatter, + future::Future, + pin::Pin, + sync::Arc, + task::{Context, Poll}, + time::Duration, }; use tokio::sync::{broadcast, oneshot, RwLock}; use tokio_tungstenite::tungstenite::{ - protocol::{frame::coding::CloseCode, CloseFrame}, - Message, + protocol::{frame::coding::CloseCode, CloseFrame}, + Message, }; pub type MsgReceiver = UnboundedReceiver; @@ -28,385 +28,408 @@ pub type MsgSender = UnboundedSender; type Handlers = DashMap>; pub trait WSMessageReceiver: Sync + Send + 'static { - fn source(&self) -> WSChannel; - fn receive_message(&self, msg: WebSocketRawMessage); + fn source(&self) -> WSChannel; + fn receive_message(&self, msg: WebSocketRawMessage); } pub struct WSController { - handlers: Handlers, - addr: Arc>>, - sender: Arc>>>, - conn_state_notify: Arc>, + handlers: Handlers, + addr: Arc>>, + sender: Arc>>>, + conn_state_notify: Arc>, } impl std::fmt::Display for WSController { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str("WebSocket") - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str("WebSocket") + } } impl std::default::Default for WSController { - fn default() -> Self { - Self { - handlers: DashMap::new(), - addr: Arc::new(RwLock::new(None)), - sender: Arc::new(RwLock::new(None)), - conn_state_notify: Arc::new(RwLock::new(WSConnectStateNotifier::default())), - } + fn default() -> Self { + Self { + handlers: DashMap::new(), + addr: Arc::new(RwLock::new(None)), + sender: Arc::new(RwLock::new(None)), + conn_state_notify: Arc::new(RwLock::new(WSConnectStateNotifier::default())), } + } } impl WSController { - pub fn new() -> Self { - WSController::default() - } + pub fn new() -> Self { + WSController::default() + } - pub fn add_ws_message_receiver(&self, handler: Arc) -> Result<(), WSError> { - let source = handler.source(); - if self.handlers.contains_key(&source) { - log::error!("{:?} is already registered", source); - } - self.handlers.insert(source, handler); - Ok(()) + pub fn add_ws_message_receiver( + &self, + handler: Arc, + ) -> Result<(), WSError> { + let source = handler.source(); + if self.handlers.contains_key(&source) { + log::error!("{:?} is already registered", source); } + self.handlers.insert(source, handler); + Ok(()) + } - pub async fn start(&self, addr: String) -> Result<(), WSError> { - *self.addr.write().await = Some(addr.clone()); - let strategy = FixedInterval::from_millis(5000).take(3); - self.connect(addr, strategy).await - } + pub async fn start(&self, addr: String) -> Result<(), WSError> { + *self.addr.write().await = Some(addr.clone()); + let strategy = FixedInterval::from_millis(5000).take(3); + self.connect(addr, strategy).await + } - pub async fn stop(&self) { - if self.conn_state_notify.read().await.conn_state.is_connected() { - tracing::trace!("[{}] stop", self); - self.conn_state_notify - .write() - .await - .update_state(WSConnectState::Disconnected); - } - } - - async fn connect(&self, addr: String, strategy: T) -> Result<(), WSError> - where - T: IntoIterator, - I: Iterator + Send + 'static, + pub async fn stop(&self) { + if self + .conn_state_notify + .read() + .await + .conn_state + .is_connected() { - let mut conn_state_notify = self.conn_state_notify.write().await; - let conn_state = conn_state_notify.conn_state.clone(); - if conn_state.is_connected() || conn_state.is_connecting() { - return Ok(()); - } + tracing::trace!("[{}] stop", self); + self + .conn_state_notify + .write() + .await + .update_state(WSConnectState::Disconnected); + } + } - let (ret, rx) = oneshot::channel::>(); - *self.addr.write().await = Some(addr.clone()); - let action = WSConnectAction { - addr, - handlers: self.handlers.clone(), - }; - let retry = Retry::new(strategy, action); - conn_state_notify.update_state(WSConnectState::Connecting); - drop(conn_state_notify); - - let cloned_conn_state = self.conn_state_notify.clone(); - let cloned_sender = self.sender.clone(); - tracing::trace!("[{}] start connecting", self); - tokio::spawn(async move { - match retry.await { - Ok(result) => { - let WSConnectResult { - stream, - handlers_fut, - sender, - } = result; - - cloned_conn_state.write().await.update_state(WSConnectState::Connected); - *cloned_sender.write().await = Some(Arc::new(sender)); - - let _ = ret.send(Ok(())); - spawn_stream_and_handlers(stream, handlers_fut).await; - } - Err(e) => { - cloned_conn_state - .write() - .await - .update_state(WSConnectState::Disconnected); - let _ = ret.send(Err(WSError::internal().context(e))); - } - } - }); - rx.await? + async fn connect(&self, addr: String, strategy: T) -> Result<(), WSError> + where + T: IntoIterator, + I: Iterator + Send + 'static, + { + let mut conn_state_notify = self.conn_state_notify.write().await; + let conn_state = conn_state_notify.conn_state.clone(); + if conn_state.is_connected() || conn_state.is_connecting() { + return Ok(()); } - pub async fn retry(&self, count: usize) -> Result<(), WSError> { - if !self.conn_state_notify.read().await.conn_state.is_disconnected() { - return Ok(()); - } + let (ret, rx) = oneshot::channel::>(); + *self.addr.write().await = Some(addr.clone()); + let action = WSConnectAction { + addr, + handlers: self.handlers.clone(), + }; + let retry = Retry::new(strategy, action); + conn_state_notify.update_state(WSConnectState::Connecting); + drop(conn_state_notify); - tracing::trace!("[WebSocket]: retry connect..."); - let strategy = FixedInterval::from_millis(5000).take(count); - let addr = self - .addr - .read() + let cloned_conn_state = self.conn_state_notify.clone(); + let cloned_sender = self.sender.clone(); + tracing::trace!("[{}] start connecting", self); + tokio::spawn(async move { + match retry.await { + Ok(result) => { + let WSConnectResult { + stream, + handlers_fut, + sender, + } = result; + + cloned_conn_state + .write() .await - .as_ref() - .expect("Retry web socket connection failed, should call start_connect first") - .clone(); + .update_state(WSConnectState::Connected); + *cloned_sender.write().await = Some(Arc::new(sender)); - self.connect(addr, strategy).await + let _ = ret.send(Ok(())); + spawn_stream_and_handlers(stream, handlers_fut).await; + }, + Err(e) => { + cloned_conn_state + .write() + .await + .update_state(WSConnectState::Disconnected); + let _ = ret.send(Err(WSError::internal().context(e))); + }, + } + }); + rx.await? + } + + pub async fn retry(&self, count: usize) -> Result<(), WSError> { + if !self + .conn_state_notify + .read() + .await + .conn_state + .is_disconnected() + { + return Ok(()); } - pub async fn subscribe_state(&self) -> broadcast::Receiver { - self.conn_state_notify.read().await.notify.subscribe() - } + tracing::trace!("[WebSocket]: retry connect..."); + let strategy = FixedInterval::from_millis(5000).take(count); + let addr = self + .addr + .read() + .await + .as_ref() + .expect("Retry web socket connection failed, should call start_connect first") + .clone(); - pub async fn ws_message_sender(&self) -> Result>, WSError> { - let sender = self.sender.read().await.clone(); - match sender { - None => match self.conn_state_notify.read().await.conn_state { - WSConnectState::Disconnected => { - let msg = "WebSocket is disconnected"; - Err(WSError::internal().context(msg)) - } - _ => Ok(None), - }, - Some(sender) => Ok(Some(sender)), - } + self.connect(addr, strategy).await + } + + pub async fn subscribe_state(&self) -> broadcast::Receiver { + self.conn_state_notify.read().await.notify.subscribe() + } + + pub async fn ws_message_sender(&self) -> Result>, WSError> { + let sender = self.sender.read().await.clone(); + match sender { + None => match self.conn_state_notify.read().await.conn_state { + WSConnectState::Disconnected => { + let msg = "WebSocket is disconnected"; + Err(WSError::internal().context(msg)) + }, + _ => Ok(None), + }, + Some(sender) => Ok(Some(sender)), } + } } async fn spawn_stream_and_handlers(stream: WSStream, handlers: WSHandlerFuture) { - tokio::select! { - result = stream => { - if let Err(e) = result { - tracing::error!("WSStream error: {:?}", e); - } - }, - result = handlers => tracing::debug!("handlers completed {:?}", result), - }; + tokio::select! { + result = stream => { + if let Err(e) = result { + tracing::error!("WSStream error: {:?}", e); + } + }, + result = handlers => tracing::debug!("handlers completed {:?}", result), + }; } #[pin_project] pub struct WSHandlerFuture { - #[pin] - msg_rx: MsgReceiver, - handlers: Handlers, + #[pin] + msg_rx: MsgReceiver, + handlers: Handlers, } impl WSHandlerFuture { - fn new(handlers: Handlers, msg_rx: MsgReceiver) -> Self { - Self { msg_rx, handlers } - } + fn new(handlers: Handlers, msg_rx: MsgReceiver) -> Self { + Self { msg_rx, handlers } + } - fn handler_ws_message(&self, message: Message) { - if let Message::Binary(bytes) = message { - self.handle_binary_message(bytes) - } + fn handler_ws_message(&self, message: Message) { + if let Message::Binary(bytes) = message { + self.handle_binary_message(bytes) } + } - fn handle_binary_message(&self, bytes: Vec) { - let msg = WebSocketRawMessage::from_bytes(bytes); - match self.handlers.get(&msg.channel) { - None => log::error!("Can't find any handler for message: {:?}", msg), - Some(handler) => handler.receive_message(msg), - } + fn handle_binary_message(&self, bytes: Vec) { + let msg = WebSocketRawMessage::from_bytes(bytes); + match self.handlers.get(&msg.channel) { + None => log::error!("Can't find any handler for message: {:?}", msg), + Some(handler) => handler.receive_message(msg), } + } } impl Future for WSHandlerFuture { - type Output = (); - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - loop { - match ready!(self.as_mut().project().msg_rx.poll_next(cx)) { - None => { - return Poll::Ready(()); - } - Some(message) => self.handler_ws_message(message), - } - } + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + loop { + match ready!(self.as_mut().project().msg_rx.poll_next(cx)) { + None => { + return Poll::Ready(()); + }, + Some(message) => self.handler_ws_message(message), + } } + } } #[derive(Debug, Clone)] pub struct WSSender(MsgSender); impl WSSender { - pub fn send_msg>(&self, msg: T) -> Result<(), WSError> { - let msg = msg.into(); - self.0 - .unbounded_send(msg.into()) - .map_err(|e| WSError::internal().context(e))?; - Ok(()) - } + pub fn send_msg>(&self, msg: T) -> Result<(), WSError> { + let msg = msg.into(); + self + .0 + .unbounded_send(msg.into()) + .map_err(|e| WSError::internal().context(e))?; + Ok(()) + } - pub fn send_text(&self, source: &WSChannel, text: &str) -> Result<(), WSError> { - let msg = WebSocketRawMessage { - channel: source.clone(), - data: text.as_bytes().to_vec(), - }; - self.send_msg(msg) - } + pub fn send_text(&self, source: &WSChannel, text: &str) -> Result<(), WSError> { + let msg = WebSocketRawMessage { + channel: source.clone(), + data: text.as_bytes().to_vec(), + }; + self.send_msg(msg) + } - pub fn send_binary(&self, source: &WSChannel, bytes: Vec) -> Result<(), WSError> { - let msg = WebSocketRawMessage { - channel: source.clone(), - data: bytes, - }; - self.send_msg(msg) - } + pub fn send_binary(&self, source: &WSChannel, bytes: Vec) -> Result<(), WSError> { + let msg = WebSocketRawMessage { + channel: source.clone(), + data: bytes, + }; + self.send_msg(msg) + } - pub fn send_disconnect(&self, reason: &str) -> Result<(), WSError> { - let frame = CloseFrame { - code: CloseCode::Normal, - reason: reason.to_owned().into(), - }; - let msg = Message::Close(Some(frame)); - self.0.unbounded_send(msg).map_err(|e| WSError::internal().context(e))?; - Ok(()) - } + pub fn send_disconnect(&self, reason: &str) -> Result<(), WSError> { + let frame = CloseFrame { + code: CloseCode::Normal, + reason: reason.to_owned().into(), + }; + let msg = Message::Close(Some(frame)); + self + .0 + .unbounded_send(msg) + .map_err(|e| WSError::internal().context(e))?; + Ok(()) + } } struct WSConnectAction { - addr: String, - handlers: Handlers, + addr: String, + handlers: Handlers, } impl Action for WSConnectAction { - type Future = Pin> + Send + Sync>>; - type Item = WSConnectResult; - type Error = WSError; + type Future = Pin> + Send + Sync>>; + type Item = WSConnectResult; + type Error = WSError; - fn run(&mut self) -> Self::Future { - let addr = self.addr.clone(); - let handlers = self.handlers.clone(); - Box::pin(WSConnectActionFut::new(addr, handlers)) - } + fn run(&mut self) -> Self::Future { + let addr = self.addr.clone(); + let handlers = self.handlers.clone(); + Box::pin(WSConnectActionFut::new(addr, handlers)) + } } struct WSConnectResult { - stream: WSStream, - handlers_fut: WSHandlerFuture, - sender: WSSender, + stream: WSStream, + handlers_fut: WSHandlerFuture, + sender: WSSender, } #[pin_project] struct WSConnectActionFut { - addr: String, - #[pin] - conn: WSConnectionFuture, - handlers_fut: Option, - sender: Option, + addr: String, + #[pin] + conn: WSConnectionFuture, + handlers_fut: Option, + sender: Option, } impl WSConnectActionFut { - fn new(addr: String, handlers: Handlers) -> Self { - // Stream User - // ┌───────────────┐ ┌──────────────┐ - // ┌──────┐ │ ┌─────────┐ │ ┌────────┐ │ ┌────────┐ │ - // │Server│──────┼─▶│ ws_read │──┼───▶│ msg_tx │───┼─▶│ msg_rx │ │ - // └──────┘ │ └─────────┘ │ └────────┘ │ └────────┘ │ - // ▲ │ │ │ │ - // │ │ ┌─────────┐ │ ┌────────┐ │ ┌────────┐ │ - // └─────────┼──│ws_write │◀─┼────│ ws_rx │◀──┼──│ ws_tx │ │ - // │ └─────────┘ │ └────────┘ │ └────────┘ │ - // └───────────────┘ └──────────────┘ - let (msg_tx, msg_rx) = futures_channel::mpsc::unbounded(); - let (ws_tx, ws_rx) = futures_channel::mpsc::unbounded(); - let sender = WSSender(ws_tx); - let handlers_fut = WSHandlerFuture::new(handlers, msg_rx); - let conn = WSConnectionFuture::new(msg_tx, ws_rx, addr.clone()); - Self { - addr, - conn, - handlers_fut: Some(handlers_fut), - sender: Some(sender), - } + fn new(addr: String, handlers: Handlers) -> Self { + // Stream User + // ┌───────────────┐ ┌──────────────┐ + // ┌──────┐ │ ┌─────────┐ │ ┌────────┐ │ ┌────────┐ │ + // │Server│──────┼─▶│ ws_read │──┼───▶│ msg_tx │───┼─▶│ msg_rx │ │ + // └──────┘ │ └─────────┘ │ └────────┘ │ └────────┘ │ + // ▲ │ │ │ │ + // │ │ ┌─────────┐ │ ┌────────┐ │ ┌────────┐ │ + // └─────────┼──│ws_write │◀─┼────│ ws_rx │◀──┼──│ ws_tx │ │ + // │ └─────────┘ │ └────────┘ │ └────────┘ │ + // └───────────────┘ └──────────────┘ + let (msg_tx, msg_rx) = futures_channel::mpsc::unbounded(); + let (ws_tx, ws_rx) = futures_channel::mpsc::unbounded(); + let sender = WSSender(ws_tx); + let handlers_fut = WSHandlerFuture::new(handlers, msg_rx); + let conn = WSConnectionFuture::new(msg_tx, ws_rx, addr.clone()); + Self { + addr, + conn, + handlers_fut: Some(handlers_fut), + sender: Some(sender), } + } } impl Future for WSConnectActionFut { - type Output = Result; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.project(); - match ready!(this.conn.as_mut().poll(cx)) { - Ok(stream) => { - let handlers_fut = this.handlers_fut.take().expect("Only take once"); - let sender = this.sender.take().expect("Only take once"); - Poll::Ready(Ok(WSConnectResult { - stream, - handlers_fut, - sender, - })) - } - Err(e) => Poll::Ready(Err(e)), - } + type Output = Result; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + match ready!(this.conn.as_mut().poll(cx)) { + Ok(stream) => { + let handlers_fut = this.handlers_fut.take().expect("Only take once"); + let sender = this.sender.take().expect("Only take once"); + Poll::Ready(Ok(WSConnectResult { + stream, + handlers_fut, + sender, + })) + }, + Err(e) => Poll::Ready(Err(e)), } + } } #[derive(Clone, Eq, PartialEq)] pub enum WSConnectState { - Init, - Connecting, - Connected, - Disconnected, + Init, + Connecting, + Connected, + Disconnected, } impl WSConnectState { - fn is_connected(&self) -> bool { - self == &WSConnectState::Connected - } + fn is_connected(&self) -> bool { + self == &WSConnectState::Connected + } - fn is_connecting(&self) -> bool { - self == &WSConnectState::Connecting - } + fn is_connecting(&self) -> bool { + self == &WSConnectState::Connecting + } - fn is_disconnected(&self) -> bool { - self == &WSConnectState::Disconnected || self == &WSConnectState::Init - } + fn is_disconnected(&self) -> bool { + self == &WSConnectState::Disconnected || self == &WSConnectState::Init + } } impl std::fmt::Display for WSConnectState { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - WSConnectState::Init => f.write_str("Init"), - WSConnectState::Connected => f.write_str("Connected"), - WSConnectState::Connecting => f.write_str("Connecting"), - WSConnectState::Disconnected => f.write_str("Disconnected"), - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + WSConnectState::Init => f.write_str("Init"), + WSConnectState::Connected => f.write_str("Connected"), + WSConnectState::Connecting => f.write_str("Connecting"), + WSConnectState::Disconnected => f.write_str("Disconnected"), } + } } impl std::fmt::Debug for WSConnectState { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&format!("{}", self)) - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&format!("{}", self)) + } } struct WSConnectStateNotifier { - conn_state: WSConnectState, - notify: Arc>, + conn_state: WSConnectState, + notify: Arc>, } impl std::default::Default for WSConnectStateNotifier { - fn default() -> Self { - let (state_notify, _) = broadcast::channel(16); - Self { - conn_state: WSConnectState::Init, - notify: Arc::new(state_notify), - } + fn default() -> Self { + let (state_notify, _) = broadcast::channel(16); + Self { + conn_state: WSConnectState::Init, + notify: Arc::new(state_notify), } + } } impl WSConnectStateNotifier { - fn update_state(&mut self, new_state: WSConnectState) { - if self.conn_state == new_state { - return; - } - tracing::debug!( - "WebSocket connect state did change: {} -> {}", - self.conn_state, - new_state - ); - self.conn_state = new_state.clone(); - let _ = self.notify.send(new_state); + fn update_state(&mut self, new_state: WSConnectState) { + if self.conn_state == new_state { + return; } + tracing::debug!( + "WebSocket connect state did change: {} -> {}", + self.conn_state, + new_state + ); + self.conn_state = new_state.clone(); + let _ = self.notify.send(new_state); + } } diff --git a/shared-lib/revision-model/src/revision.rs b/shared-lib/revision-model/src/revision.rs index c555dfcd1e..1861141861 100644 --- a/shared-lib/revision-model/src/revision.rs +++ b/shared-lib/revision-model/src/revision.rs @@ -4,112 +4,118 @@ use std::{convert::TryFrom, fmt::Formatter, ops::RangeInclusive}; #[derive(PartialEq, Eq, Clone, Default, Serialize, Deserialize)] pub struct Revision { - pub base_rev_id: i64, - pub rev_id: i64, - pub bytes: Vec, - pub md5: String, - pub object_id: String, + pub base_rev_id: i64, + pub rev_id: i64, + pub bytes: Vec, + pub md5: String, + pub object_id: String, } impl std::convert::From> for Revision { - fn from(data: Vec) -> Self { - let bytes = Bytes::from(data); - Revision::try_from(bytes).unwrap() - } + fn from(data: Vec) -> Self { + let bytes = Bytes::from(data); + Revision::try_from(bytes).unwrap() + } } impl std::convert::TryFrom for Revision { - type Error = serde_json::Error; + type Error = serde_json::Error; - fn try_from(bytes: Bytes) -> Result { - serde_json::from_slice(&bytes) - } + fn try_from(bytes: Bytes) -> Result { + serde_json::from_slice(&bytes) + } } impl Revision { - pub fn new>(object_id: &str, base_rev_id: i64, rev_id: i64, bytes: Bytes, md5: T) -> Revision { - let object_id = object_id.to_owned(); - let bytes = bytes.to_vec(); - let base_rev_id = base_rev_id; - let rev_id = rev_id; + pub fn new>( + object_id: &str, + base_rev_id: i64, + rev_id: i64, + bytes: Bytes, + md5: T, + ) -> Revision { + let object_id = object_id.to_owned(); + let bytes = bytes.to_vec(); + let base_rev_id = base_rev_id; + let rev_id = rev_id; - if base_rev_id != 0 { - debug_assert!(base_rev_id <= rev_id); - } - - Self { - base_rev_id, - rev_id, - bytes, - md5: md5.into(), - object_id, - } + if base_rev_id != 0 { + debug_assert!(base_rev_id <= rev_id); } - pub fn is_empty(&self) -> bool { - self.base_rev_id == self.rev_id + Self { + base_rev_id, + rev_id, + bytes, + md5: md5.into(), + object_id, } + } - pub fn pair_rev_id(&self) -> (i64, i64) { - (self.base_rev_id, self.rev_id) - } + pub fn is_empty(&self) -> bool { + self.base_rev_id == self.rev_id + } - pub fn is_initial(&self) -> bool { - self.rev_id == 0 - } + pub fn pair_rev_id(&self) -> (i64, i64) { + (self.base_rev_id, self.rev_id) + } - pub fn initial_revision(object_id: &str, bytes: Bytes) -> Self { - let md5 = format!("{:x}", md5::compute(&bytes)); - Self::new(object_id, 0, 0, bytes, md5) - } + pub fn is_initial(&self) -> bool { + self.rev_id == 0 + } - pub fn to_bytes(&self) -> Bytes { - let bytes = serde_json::to_vec(self).unwrap(); - Bytes::from(bytes) - } + pub fn initial_revision(object_id: &str, bytes: Bytes) -> Self { + let md5 = format!("{:x}", md5::compute(&bytes)); + Self::new(object_id, 0, 0, bytes, md5) + } + + pub fn to_bytes(&self) -> Bytes { + let bytes = serde_json::to_vec(self).unwrap(); + Bytes::from(bytes) + } } impl std::fmt::Debug for Revision { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - f.write_fmt(format_args!("object_id {}, ", self.object_id))?; - f.write_fmt(format_args!("base_rev_id {}, ", self.base_rev_id))?; - f.write_fmt(format_args!("rev_id {}, ", self.rev_id))?; - Ok(()) - } + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + f.write_fmt(format_args!("object_id {}, ", self.object_id))?; + f.write_fmt(format_args!("base_rev_id {}, ", self.base_rev_id))?; + f.write_fmt(format_args!("rev_id {}, ", self.rev_id))?; + Ok(()) + } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct RevisionRange { - pub start: i64, - pub end: i64, + pub start: i64, + pub end: i64, } impl std::fmt::Display for RevisionRange { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("[{},{}]", self.start, self.end)) - } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("[{},{}]", self.start, self.end)) + } } impl RevisionRange { - pub fn len(&self) -> u64 { - debug_assert!(self.end >= self.start); - if self.end >= self.start { - (self.end - self.start + 1) as u64 - } else { - 0 - } + pub fn len(&self) -> u64 { + debug_assert!(self.end >= self.start); + if self.end >= self.start { + (self.end - self.start + 1) as u64 + } else { + 0 } + } - pub fn is_empty(&self) -> bool { - self.end == self.start - } + pub fn is_empty(&self) -> bool { + self.end == self.start + } - pub fn iter(&self) -> RangeInclusive { - // debug_assert!(self.start != self.end); - RangeInclusive::new(self.start, self.end) - } + pub fn iter(&self) -> RangeInclusive { + // debug_assert!(self.start != self.end); + RangeInclusive::new(self.start, self.end) + } - pub fn to_rev_ids(&self) -> Vec { - self.iter().collect::>() - } + pub fn to_rev_ids(&self) -> Vec { + self.iter().collect::>() + } } diff --git a/shared-lib/rustfmt.toml b/shared-lib/rustfmt.toml index 2464575494..5cb0d67ee5 100644 --- a/shared-lib/rustfmt.toml +++ b/shared-lib/rustfmt.toml @@ -1,18 +1,12 @@ # https://rust-lang.github.io/rustfmt/?version=master&search= -max_width = 120 -tab_spaces = 4 -# fn_single_line = true -# match_block_trailing_comma = true -# normalize_comments = true -# wrap_comments = true -# use_field_init_shorthand = true -# use_try_shorthand = true -# normalize_doc_attributes = true -# report_todo = "Never" -# report_fixme = "Always" -# imports_layout = "HorizontalVertical" -# imports_granularity = "Crate" -# reorder_modules = true -# reorder_imports = true -# enum_discrim_align_threshold = 20 -edition = "2018" +max_width = 100 +tab_spaces = 2 +newline_style = "Auto" +match_block_trailing_comma = true +use_field_init_shorthand = true +use_try_shorthand = true +reorder_imports = true +reorder_modules = true +remove_nested_parens = true +merge_derives = true +edition = "2021" \ No newline at end of file diff --git a/shared-lib/user-model/src/errors.rs b/shared-lib/user-model/src/errors.rs index bee7dee7f0..a121297d84 100644 --- a/shared-lib/user-model/src/errors.rs +++ b/shared-lib/user-model/src/errors.rs @@ -4,45 +4,47 @@ use thiserror::Error; #[derive(Debug, Clone, PartialEq, Eq, Error, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum UserErrorCode { - #[error("Internal error")] - Internal = 0, + #[error("Internal error")] + Internal = 0, - #[error("Workspace id can not be empty or whitespace")] - WorkspaceIdInvalid = 1, + #[error("Workspace id can not be empty or whitespace")] + WorkspaceIdInvalid = 1, - #[error("Email can not be empty or whitespace")] - EmailIsEmpty = 2, + #[error("Email can not be empty or whitespace")] + EmailIsEmpty = 2, - #[error("Email format is not valid")] - EmailFormatInvalid = 3, + #[error("Email format is not valid")] + EmailFormatInvalid = 3, - #[error("user id is empty or whitespace")] - UserIdInvalid = 4, + #[error("user id is empty or whitespace")] + UserIdInvalid = 4, - #[error("User name contain forbidden characters")] - UserNameContainForbiddenCharacters = 5, + #[error("User name contain forbidden characters")] + UserNameContainForbiddenCharacters = 5, - #[error("User name can not be empty or whitespace")] - UserNameIsEmpty = 6, + #[error("User name can not be empty or whitespace")] + UserNameIsEmpty = 6, - #[error("User not exist")] - UserNotExist = 7, + #[error("User not exist")] + UserNotExist = 7, - #[error("Password can not be empty or whitespace")] - PasswordIsEmpty = 8, + #[error("Password can not be empty or whitespace")] + PasswordIsEmpty = 8, - #[error("Password format too long")] - PasswordTooLong = 9, + #[error("Password format too long")] + PasswordTooLong = 9, - #[error("Password contains forbidden characters.")] - PasswordContainsForbidCharacters = 10, + #[error("Password contains forbidden characters.")] + PasswordContainsForbidCharacters = 10, - #[error("Password should contain a minimum of 6 characters with 1 special 1 letter and 1 numeric")] - PasswordFormatInvalid = 11, + #[error( + "Password should contain a minimum of 6 characters with 1 special 1 letter and 1 numeric" + )] + PasswordFormatInvalid = 11, - #[error("Password not match")] - PasswordNotMatch = 12, + #[error("Password not match")] + PasswordNotMatch = 12, - #[error("User name is too long")] - UserNameTooLong = 13, + #[error("User name is too long")] + UserNameTooLong = 13, } diff --git a/shared-lib/user-model/src/lib.rs b/shared-lib/user-model/src/lib.rs index e18a73b36e..44983f0e00 100644 --- a/shared-lib/user-model/src/lib.rs +++ b/shared-lib/user-model/src/lib.rs @@ -7,80 +7,80 @@ use serde::{Deserialize, Serialize}; #[derive(Default, Serialize, Deserialize, Debug)] pub struct SignInParams { - pub email: String, - pub password: String, - pub name: String, + pub email: String, + pub password: String, + pub name: String, } #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct SignInResponse { - pub user_id: String, - pub name: String, - pub email: String, - pub token: String, + pub user_id: String, + pub name: String, + pub email: String, + pub token: String, } #[derive(Serialize, Deserialize, Default, Debug)] pub struct SignUpParams { - pub email: String, - pub name: String, - pub password: String, + pub email: String, + pub name: String, + pub password: String, } #[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct SignUpResponse { - pub user_id: String, - pub name: String, - pub email: String, - pub token: String, + pub user_id: String, + pub name: String, + pub email: String, + pub token: String, } #[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct UserProfile { - pub id: String, - pub email: String, - pub name: String, - pub token: String, - pub icon_url: String, + pub id: String, + pub email: String, + pub name: String, + pub token: String, + pub icon_url: String, } #[derive(Serialize, Deserialize, Default, Clone, Debug)] pub struct UpdateUserProfileParams { - pub id: String, - pub name: Option, - pub email: Option, - pub password: Option, - pub icon_url: Option, + pub id: String, + pub name: Option, + pub email: Option, + pub password: Option, + pub icon_url: Option, } impl UpdateUserProfileParams { - pub fn new(user_id: &str) -> Self { - Self { - id: user_id.to_owned(), - name: None, - email: None, - password: None, - icon_url: None, - } + pub fn new(user_id: &str) -> Self { + Self { + id: user_id.to_owned(), + name: None, + email: None, + password: None, + icon_url: None, } + } - pub fn name(mut self, name: &str) -> Self { - self.name = Some(name.to_owned()); - self - } + pub fn name(mut self, name: &str) -> Self { + self.name = Some(name.to_owned()); + self + } - pub fn email(mut self, email: &str) -> Self { - self.email = Some(email.to_owned()); - self - } + pub fn email(mut self, email: &str) -> Self { + self.email = Some(email.to_owned()); + self + } - pub fn password(mut self, password: &str) -> Self { - self.password = Some(password.to_owned()); - self - } + pub fn password(mut self, password: &str) -> Self { + self.password = Some(password.to_owned()); + self + } - pub fn icon_url(mut self, icon_url: &str) -> Self { - self.icon_url = Some(icon_url.to_owned()); - self - } + pub fn icon_url(mut self, icon_url: &str) -> Self { + self.icon_url = Some(icon_url.to_owned()); + self + } } diff --git a/shared-lib/user-model/src/parser/user_email.rs b/shared-lib/user-model/src/parser/user_email.rs index 25a21599be..d8b0ca942d 100644 --- a/shared-lib/user-model/src/parser/user_email.rs +++ b/shared-lib/user-model/src/parser/user_email.rs @@ -5,69 +5,69 @@ use validator::validate_email; pub struct UserEmail(pub String); impl UserEmail { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err(UserErrorCode::EmailIsEmpty); - } - - if validate_email(&s) { - Ok(Self(s)) - } else { - Err(UserErrorCode::EmailFormatInvalid) - } + pub fn parse(s: String) -> Result { + if s.trim().is_empty() { + return Err(UserErrorCode::EmailIsEmpty); } + + if validate_email(&s) { + Ok(Self(s)) + } else { + Err(UserErrorCode::EmailFormatInvalid) + } + } } impl AsRef for UserEmail { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } #[cfg(test)] mod tests { - use super::*; - use claim::assert_err; - use fake::{faker::internet::en::SafeEmail, Fake}; - use rand::prelude::StdRng; - use rand_core::SeedableRng; + use super::*; + use claim::assert_err; + use fake::{faker::internet::en::SafeEmail, Fake}; + use rand::prelude::StdRng; + use rand_core::SeedableRng; - #[test] - fn empty_string_is_rejected() { - let email = "".to_string(); - assert_err!(UserEmail::parse(email)); + #[test] + fn empty_string_is_rejected() { + let email = "".to_string(); + assert_err!(UserEmail::parse(email)); + } + + #[test] + fn email_missing_at_symbol_is_rejected() { + let email = "helloworld.com".to_string(); + assert_err!(UserEmail::parse(email)); + } + + #[test] + fn email_missing_subject_is_rejected() { + let email = "@domain.com".to_string(); + assert_err!(UserEmail::parse(email)); + } + + #[derive(Debug, Clone)] + struct ValidEmailFixture(pub String); + + impl quickcheck::Arbitrary for ValidEmailFixture { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let mut rand_slice: [u8; 32] = [0; 32]; + #[allow(clippy::needless_range_loop)] + for i in 0..32 { + rand_slice[i] = u8::arbitrary(g); + } + let mut seed = StdRng::from_seed(rand_slice); + let email = SafeEmail().fake_with_rng(&mut seed); + Self(email) } + } - #[test] - fn email_missing_at_symbol_is_rejected() { - let email = "helloworld.com".to_string(); - assert_err!(UserEmail::parse(email)); - } - - #[test] - fn email_missing_subject_is_rejected() { - let email = "@domain.com".to_string(); - assert_err!(UserEmail::parse(email)); - } - - #[derive(Debug, Clone)] - struct ValidEmailFixture(pub String); - - impl quickcheck::Arbitrary for ValidEmailFixture { - fn arbitrary(g: &mut quickcheck::Gen) -> Self { - let mut rand_slice: [u8; 32] = [0; 32]; - #[allow(clippy::needless_range_loop)] - for i in 0..32 { - rand_slice[i] = u8::arbitrary(g); - } - let mut seed = StdRng::from_seed(rand_slice); - let email = SafeEmail().fake_with_rng(&mut seed); - Self(email) - } - } - - #[quickcheck_macros::quickcheck] - fn valid_emails_are_parsed_successfully(valid_email: ValidEmailFixture) -> bool { - UserEmail::parse(valid_email.0).is_ok() - } + #[quickcheck_macros::quickcheck] + fn valid_emails_are_parsed_successfully(valid_email: ValidEmailFixture) -> bool { + UserEmail::parse(valid_email.0).is_ok() + } } diff --git a/shared-lib/user-model/src/parser/user_icon.rs b/shared-lib/user-model/src/parser/user_icon.rs index 71d193c0eb..9b088eda20 100644 --- a/shared-lib/user-model/src/parser/user_icon.rs +++ b/shared-lib/user-model/src/parser/user_icon.rs @@ -4,13 +4,13 @@ use crate::errors::UserErrorCode; pub struct UserIcon(pub String); impl UserIcon { - pub fn parse(s: String) -> Result { - Ok(Self(s)) - } + pub fn parse(s: String) -> Result { + Ok(Self(s)) + } } impl AsRef for UserIcon { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } diff --git a/shared-lib/user-model/src/parser/user_id.rs b/shared-lib/user-model/src/parser/user_id.rs index 5a8c7b9b3e..77f7ba624b 100644 --- a/shared-lib/user-model/src/parser/user_id.rs +++ b/shared-lib/user-model/src/parser/user_id.rs @@ -4,17 +4,17 @@ use crate::errors::UserErrorCode; pub struct UserId(pub String); impl UserId { - pub fn parse(s: String) -> Result { - let is_empty_or_whitespace = s.trim().is_empty(); - if is_empty_or_whitespace { - return Err(UserErrorCode::UserIdInvalid); - } - Ok(Self(s)) + pub fn parse(s: String) -> Result { + let is_empty_or_whitespace = s.trim().is_empty(); + if is_empty_or_whitespace { + return Err(UserErrorCode::UserIdInvalid); } + Ok(Self(s)) + } } impl AsRef for UserId { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } diff --git a/shared-lib/user-model/src/parser/user_name.rs b/shared-lib/user-model/src/parser/user_name.rs index 046bf455be..0e04b54136 100644 --- a/shared-lib/user-model/src/parser/user_name.rs +++ b/shared-lib/user-model/src/parser/user_name.rs @@ -5,80 +5,80 @@ use unicode_segmentation::UnicodeSegmentation; pub struct UserName(pub String); impl UserName { - pub fn parse(s: String) -> Result { - let is_empty_or_whitespace = s.trim().is_empty(); - if is_empty_or_whitespace { - return Err(UserErrorCode::UserNameIsEmpty); - } - // A grapheme is defined by the Unicode standard as a "user-perceived" - // character: `å` is a single grapheme, but it is composed of two characters - // (`a` and `̊`). - // - // `graphemes` returns an iterator over the graphemes in the input `s`. - // `true` specifies that we want to use the extended grapheme definition set, - // the recommended one. - let is_too_long = s.graphemes(true).count() > 256; - if is_too_long { - return Err(UserErrorCode::UserNameTooLong); - } - - let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}']; - let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g)); - - if contains_forbidden_characters { - return Err(UserErrorCode::UserNameContainForbiddenCharacters); - } - - Ok(Self(s)) + pub fn parse(s: String) -> Result { + let is_empty_or_whitespace = s.trim().is_empty(); + if is_empty_or_whitespace { + return Err(UserErrorCode::UserNameIsEmpty); } + // A grapheme is defined by the Unicode standard as a "user-perceived" + // character: `å` is a single grapheme, but it is composed of two characters + // (`a` and `̊`). + // + // `graphemes` returns an iterator over the graphemes in the input `s`. + // `true` specifies that we want to use the extended grapheme definition set, + // the recommended one. + let is_too_long = s.graphemes(true).count() > 256; + if is_too_long { + return Err(UserErrorCode::UserNameTooLong); + } + + let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}']; + let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g)); + + if contains_forbidden_characters { + return Err(UserErrorCode::UserNameContainForbiddenCharacters); + } + + Ok(Self(s)) + } } impl AsRef for UserName { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } #[cfg(test)] mod tests { - use super::UserName; - use claim::{assert_err, assert_ok}; + use super::UserName; + use claim::{assert_err, assert_ok}; - #[test] - fn a_256_grapheme_long_name_is_valid() { - let name = "a̐".repeat(256); - assert_ok!(UserName::parse(name)); - } + #[test] + fn a_256_grapheme_long_name_is_valid() { + let name = "a̐".repeat(256); + assert_ok!(UserName::parse(name)); + } - #[test] - fn a_name_longer_than_256_graphemes_is_rejected() { - let name = "a".repeat(257); - assert_err!(UserName::parse(name)); - } + #[test] + fn a_name_longer_than_256_graphemes_is_rejected() { + let name = "a".repeat(257); + assert_err!(UserName::parse(name)); + } - #[test] - fn whitespace_only_names_are_rejected() { - let name = " ".to_string(); - assert_err!(UserName::parse(name)); - } + #[test] + fn whitespace_only_names_are_rejected() { + let name = " ".to_string(); + assert_err!(UserName::parse(name)); + } - #[test] - fn empty_string_is_rejected() { - let name = "".to_string(); - assert_err!(UserName::parse(name)); - } + #[test] + fn empty_string_is_rejected() { + let name = "".to_string(); + assert_err!(UserName::parse(name)); + } - #[test] - fn names_containing_an_invalid_character_are_rejected() { - for name in &['/', '(', ')', '"', '<', '>', '\\', '{', '}'] { - let name = name.to_string(); - assert_err!(UserName::parse(name)); - } + #[test] + fn names_containing_an_invalid_character_are_rejected() { + for name in &['/', '(', ')', '"', '<', '>', '\\', '{', '}'] { + let name = name.to_string(); + assert_err!(UserName::parse(name)); } + } - #[test] - fn a_valid_name_is_parsed_successfully() { - let name = "nathan".to_string(); - assert_ok!(UserName::parse(name)); - } + #[test] + fn a_valid_name_is_parsed_successfully() { + let name = "nathan".to_string(); + assert_ok!(UserName::parse(name)); + } } diff --git a/shared-lib/user-model/src/parser/user_password.rs b/shared-lib/user-model/src/parser/user_password.rs index 22f2341ef9..9b113b5b0f 100644 --- a/shared-lib/user-model/src/parser/user_password.rs +++ b/shared-lib/user-model/src/parser/user_password.rs @@ -7,33 +7,33 @@ use unicode_segmentation::UnicodeSegmentation; pub struct UserPassword(pub String); impl UserPassword { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err(UserErrorCode::PasswordIsEmpty); - } - - if s.graphemes(true).count() > 100 { - return Err(UserErrorCode::PasswordTooLong); - } - - let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}']; - let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g)); - if contains_forbidden_characters { - return Err(UserErrorCode::PasswordContainsForbidCharacters); - } - - if !validate_password(&s) { - return Err(UserErrorCode::PasswordFormatInvalid); - } - - Ok(Self(s)) + pub fn parse(s: String) -> Result { + if s.trim().is_empty() { + return Err(UserErrorCode::PasswordIsEmpty); } + + if s.graphemes(true).count() > 100 { + return Err(UserErrorCode::PasswordTooLong); + } + + let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}']; + let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g)); + if contains_forbidden_characters { + return Err(UserErrorCode::PasswordContainsForbidCharacters); + } + + if !validate_password(&s) { + return Err(UserErrorCode::PasswordFormatInvalid); + } + + Ok(Self(s)) + } } impl AsRef for UserPassword { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } lazy_static! { @@ -54,11 +54,11 @@ lazy_static! { } pub fn validate_password(password: &str) -> bool { - match PASSWORD.is_match(password) { - Ok(is_match) => is_match, - Err(e) => { - tracing::error!("validate_password fail: {:?}", e); - false - } - } + match PASSWORD.is_match(password) { + Ok(is_match) => is_match, + Err(e) => { + tracing::error!("validate_password fail: {:?}", e); + false + }, + } } diff --git a/shared-lib/user-model/src/parser/user_workspace.rs b/shared-lib/user-model/src/parser/user_workspace.rs index 5cfd69ff4f..ecee8bccd2 100644 --- a/shared-lib/user-model/src/parser/user_workspace.rs +++ b/shared-lib/user-model/src/parser/user_workspace.rs @@ -4,17 +4,17 @@ use crate::errors::UserErrorCode; pub struct UserWorkspace(pub String); impl UserWorkspace { - pub fn parse(s: String) -> Result { - let is_empty_or_whitespace = s.trim().is_empty(); - if is_empty_or_whitespace { - return Err(UserErrorCode::WorkspaceIdInvalid); - } - Ok(Self(s)) + pub fn parse(s: String) -> Result { + let is_empty_or_whitespace = s.trim().is_empty(); + if is_empty_or_whitespace { + return Err(UserErrorCode::WorkspaceIdInvalid); } + Ok(Self(s)) + } } impl AsRef for UserWorkspace { - fn as_ref(&self) -> &str { - &self.0 - } + fn as_ref(&self) -> &str { + &self.0 + } } diff --git a/shared-lib/ws-model/src/ws_revision.rs b/shared-lib/ws-model/src/ws_revision.rs index dabf0ff59f..3737dce935 100644 --- a/shared-lib/ws-model/src/ws_revision.rs +++ b/shared-lib/ws-model/src/ws_revision.rs @@ -6,128 +6,128 @@ use serde_repr::*; #[derive(Debug, Clone, Serialize_repr, Deserialize_repr, Eq, PartialEq, Hash)] #[repr(u8)] pub enum ClientRevisionWSDataType { - ClientPushRev = 0, - ClientPing = 1, + ClientPushRev = 0, + ClientPing = 1, } impl Default for ClientRevisionWSDataType { - fn default() -> Self { - ClientRevisionWSDataType::ClientPushRev - } + fn default() -> Self { + ClientRevisionWSDataType::ClientPushRev + } } #[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct ClientRevisionWSData { - pub object_id: String, - pub ty: ClientRevisionWSDataType, - pub revisions: Vec, - pub rev_id: i64, + pub object_id: String, + pub ty: ClientRevisionWSDataType, + pub revisions: Vec, + pub rev_id: i64, } impl ClientRevisionWSData { - pub fn from_revisions(object_id: &str, revisions: Vec) -> Self { - let rev_id = match revisions.first() { - None => 0, - Some(revision) => revision.rev_id, - }; + pub fn from_revisions(object_id: &str, revisions: Vec) -> Self { + let rev_id = match revisions.first() { + None => 0, + Some(revision) => revision.rev_id, + }; - Self { - object_id: object_id.to_owned(), - ty: ClientRevisionWSDataType::ClientPushRev, - revisions, - rev_id, - } + Self { + object_id: object_id.to_owned(), + ty: ClientRevisionWSDataType::ClientPushRev, + revisions, + rev_id, } + } - pub fn ping(object_id: &str, rev_id: i64) -> Self { - Self { - object_id: object_id.to_owned(), - ty: ClientRevisionWSDataType::ClientPing, - revisions: vec![], - rev_id, - } + pub fn ping(object_id: &str, rev_id: i64) -> Self { + Self { + object_id: object_id.to_owned(), + ty: ClientRevisionWSDataType::ClientPing, + revisions: vec![], + rev_id, } + } } impl std::convert::TryFrom for ClientRevisionWSData { - type Error = serde_json::Error; + type Error = serde_json::Error; - fn try_from(bytes: Bytes) -> Result { - serde_json::from_slice(&bytes) - } + fn try_from(bytes: Bytes) -> Result { + serde_json::from_slice(&bytes) + } } impl std::convert::TryFrom for Bytes { - type Error = serde_json::Error; + type Error = serde_json::Error; - fn try_from(bytes: ClientRevisionWSData) -> Result { - serde_json::to_vec(&bytes).map(Bytes::from) - } + fn try_from(bytes: ClientRevisionWSData) -> Result { + serde_json::to_vec(&bytes).map(Bytes::from) + } } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum WSRevisionPayload { - ServerAck { rev_id: i64 }, - ServerPushRev { revisions: Vec }, - ServerPullRev { range: RevisionRange }, - UserConnect { user: NewDocumentUser }, + ServerAck { rev_id: i64 }, + ServerPushRev { revisions: Vec }, + ServerPullRev { range: RevisionRange }, + UserConnect { user: NewDocumentUser }, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ServerRevisionWSData { - pub object_id: String, - pub payload: WSRevisionPayload, + pub object_id: String, + pub payload: WSRevisionPayload, } impl std::convert::TryFrom for ServerRevisionWSData { - type Error = serde_json::Error; + type Error = serde_json::Error; - fn try_from(bytes: Bytes) -> Result { - serde_json::from_slice(&bytes) - } + fn try_from(bytes: Bytes) -> Result { + serde_json::from_slice(&bytes) + } } impl std::convert::TryFrom for Bytes { - type Error = serde_json::Error; + type Error = serde_json::Error; - fn try_from(bytes: ServerRevisionWSData) -> Result { - serde_json::to_vec(&bytes).map(Bytes::from) - } + fn try_from(bytes: ServerRevisionWSData) -> Result { + serde_json::to_vec(&bytes).map(Bytes::from) + } } impl ServerRevisionWSData { - pub fn to_bytes(&self) -> Vec { - serde_json::to_vec(&self).unwrap_or_default() - } + pub fn to_bytes(&self) -> Vec { + serde_json::to_vec(&self).unwrap_or_default() + } } pub struct ServerRevisionWSDataBuilder(); impl ServerRevisionWSDataBuilder { - pub fn build_push_message(object_id: &str, revisions: Vec) -> ServerRevisionWSData { - ServerRevisionWSData { - object_id: object_id.to_string(), - payload: WSRevisionPayload::ServerPushRev { revisions }, - } + pub fn build_push_message(object_id: &str, revisions: Vec) -> ServerRevisionWSData { + ServerRevisionWSData { + object_id: object_id.to_string(), + payload: WSRevisionPayload::ServerPushRev { revisions }, } + } - pub fn build_pull_message(object_id: &str, range: RevisionRange) -> ServerRevisionWSData { - ServerRevisionWSData { - object_id: object_id.to_string(), - payload: WSRevisionPayload::ServerPullRev { range }, - } + pub fn build_pull_message(object_id: &str, range: RevisionRange) -> ServerRevisionWSData { + ServerRevisionWSData { + object_id: object_id.to_string(), + payload: WSRevisionPayload::ServerPullRev { range }, } + } - pub fn build_ack_message(object_id: &str, rev_id: i64) -> ServerRevisionWSData { - ServerRevisionWSData { - object_id: object_id.to_string(), - payload: WSRevisionPayload::ServerAck { rev_id }, - } + pub fn build_ack_message(object_id: &str, rev_id: i64) -> ServerRevisionWSData { + ServerRevisionWSData { + object_id: object_id.to_string(), + payload: WSRevisionPayload::ServerAck { rev_id }, } + } } #[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct NewDocumentUser { - pub user_id: String, - pub doc_id: String, - pub latest_rev_id: i64, + pub user_id: String, + pub doc_id: String, + pub latest_rev_id: i64, }