mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Refactor: delete unused crates (#2543)
* refactor: delete user model * refactor: delete user model crate * refactor: rm flowy-server-sync crate * refactor: rm flowy-database and flowy-folder * refactor: rm folder-model * refactor: rm database model * refactor: rm flowy-sync * refactor: rm document-model * refactor: rm flowy-document * refactor: rm flowy-client-sync * refactor: rm ws-model * refactor: rm flowy-revisoin * refactor: rm revision-model * refactor: rm flowy-folder * refactor: rm flowy-client-ws * refactor: move crates * chore: move configuration file * ci: fix tauri build' * ci: fix flutter build * ci: rust test script * ci: tauri pnpm version conflict * ci: tauri build
This commit is contained in:
parent
2202326278
commit
bc66f43f47
2
.github/workflows/rust_ci.yaml
vendored
2
.github/workflows/rust_ci.yaml
vendored
@ -73,7 +73,7 @@ jobs:
|
||||
|
||||
- name: Run rust-lib tests
|
||||
working-directory: frontend/rust-lib
|
||||
run: RUST_LOG=info cargo test --no-default-features --features="sync,rev-sqlite"
|
||||
run: RUST_LOG=info cargo test --no-default-features --features="rev-sqlite"
|
||||
|
||||
- name: rustfmt shared-lib
|
||||
run: cargo fmt --all -- --check
|
||||
|
@ -4,7 +4,7 @@ import 'package:appflowy/plugins/document/application/share_service.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/divider_node_parser.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/math_equation_node_parser.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/code_block_node_parser.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
@ -1,8 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/entities.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
|
||||
class ShareService {
|
||||
@ -10,12 +9,13 @@ class ShareService {
|
||||
ViewPB view,
|
||||
ExportType type,
|
||||
) {
|
||||
var payload = ExportPayloadPB.create()
|
||||
..viewId = view.id
|
||||
..exportType = type
|
||||
..documentVersion = DocumentVersionPB.V1;
|
||||
// var payload = ExportPayloadPB.create()
|
||||
// ..viewId = view.id
|
||||
// ..exportType = type
|
||||
// ..documentVersion = DocumentVersionPB.V1;
|
||||
|
||||
return DocumentEventExportDocument(payload).send();
|
||||
// return DocumentEventExportDocument(payload).send();
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
Future<Either<ExportDataPB, FlowyError>> exportText(ViewPB view) {
|
||||
|
@ -6,6 +6,7 @@ import 'package:appflowy/plugins/document/application/share_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/entities.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:clipboard/clipboard.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -14,7 +15,6 @@ import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
|
@ -16,7 +16,6 @@ import 'package:appflowy_backend/ffi.dart' as ffi;
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/dart-ffi/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
|
||||
|
||||
@ -30,7 +29,6 @@ part 'dart_event/flowy-folder2/dart_event.dart';
|
||||
part 'dart_event/flowy-net/dart_event.dart';
|
||||
part 'dart_event/flowy-user/dart_event.dart';
|
||||
part 'dart_event/flowy-database2/dart_event.dart';
|
||||
part 'dart_event/flowy-document/dart_event.dart';
|
||||
part 'dart_event/flowy-document2/dart_event.dart';
|
||||
|
||||
enum FFIException {
|
||||
|
File diff suppressed because it is too large
Load Diff
282
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
282
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -117,6 +117,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"flowy-core",
|
||||
"flowy-net",
|
||||
"flowy-notification",
|
||||
"lib-dispatch",
|
||||
"serde",
|
||||
@ -1477,18 +1478,6 @@ dependencies = [
|
||||
"parking_lot_core 0.9.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "database-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"indexmap",
|
||||
"nanoid",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derivative"
|
||||
version = "2.2.0"
|
||||
@ -1598,21 +1587,6 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "dissimilar"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "210ec60ae7d710bed8683e333e9d2855a8a56a3e9892b38bad3bb0d4d29b0d5e"
|
||||
|
||||
[[package]]
|
||||
name = "document-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"revision-model",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.8"
|
||||
@ -1787,56 +1761,6 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-client-network-config"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"serde",
|
||||
"serde-aux",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-client-sync"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"chrono",
|
||||
"database-model",
|
||||
"dissimilar",
|
||||
"document-model",
|
||||
"flowy-derive",
|
||||
"flowy-sync",
|
||||
"folder-model",
|
||||
"lib-infra",
|
||||
"lib-ot",
|
||||
"parking_lot 0.12.1",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-client-ws"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"lib-infra",
|
||||
"lib-ws",
|
||||
"parking_lot 0.12.1",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-codegen"
|
||||
version = "0.1.0"
|
||||
@ -1867,15 +1791,11 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"appflowy-integrate",
|
||||
"bytes",
|
||||
"database-model",
|
||||
"flowy-client-ws",
|
||||
"flowy-database2",
|
||||
"flowy-document",
|
||||
"flowy-document2",
|
||||
"flowy-error",
|
||||
"flowy-folder2",
|
||||
"flowy-net",
|
||||
"flowy-revision",
|
||||
"flowy-sqlite",
|
||||
"flowy-task",
|
||||
"flowy-user",
|
||||
@ -1885,13 +1805,10 @@ dependencies = [
|
||||
"lib-log",
|
||||
"lib-ws",
|
||||
"parking_lot 0.12.1",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"user-model",
|
||||
"ws-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1908,7 +1825,6 @@ dependencies = [
|
||||
"collab",
|
||||
"collab-database",
|
||||
"dashmap",
|
||||
"database-model",
|
||||
"fancy-regex 0.10.0",
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
@ -1951,44 +1867,6 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-document"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"dashmap",
|
||||
"diesel",
|
||||
"diesel_derives",
|
||||
"document-model",
|
||||
"flowy-client-sync",
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
"flowy-error",
|
||||
"flowy-notification",
|
||||
"flowy-revision",
|
||||
"flowy-revision-persistence",
|
||||
"flowy-sqlite",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"lib-dispatch",
|
||||
"lib-infra",
|
||||
"lib-ot",
|
||||
"lib-ws",
|
||||
"md5",
|
||||
"protobuf",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"ws-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-document2"
|
||||
version = "0.1.0"
|
||||
@ -2021,14 +1899,11 @@ dependencies = [
|
||||
"bytes",
|
||||
"collab-database",
|
||||
"collab-document",
|
||||
"flowy-client-sync",
|
||||
"flowy-client-ws",
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
"flowy-sqlite",
|
||||
"http-error-code",
|
||||
"lib-dispatch",
|
||||
"lib-ot",
|
||||
"protobuf",
|
||||
"r2d2",
|
||||
"reqwest",
|
||||
@ -2036,7 +1911,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"thiserror",
|
||||
"user-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2050,7 +1924,6 @@ dependencies = [
|
||||
"collab-folder",
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
"flowy-document",
|
||||
"flowy-error",
|
||||
"flowy-notification",
|
||||
"lazy_static",
|
||||
@ -2075,20 +1948,12 @@ dependencies = [
|
||||
"bytes",
|
||||
"config",
|
||||
"dashmap",
|
||||
"document-model",
|
||||
"flowy-client-network-config",
|
||||
"flowy-client-sync",
|
||||
"flowy-client-ws",
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
"flowy-document",
|
||||
"flowy-document2",
|
||||
"flowy-error",
|
||||
"flowy-folder2",
|
||||
"flowy-server-sync",
|
||||
"flowy-sync",
|
||||
"flowy-user",
|
||||
"folder-model",
|
||||
"futures-util",
|
||||
"hyper",
|
||||
"lazy_static",
|
||||
@ -2099,7 +1964,6 @@ dependencies = [
|
||||
"parking_lot 0.12.1",
|
||||
"protobuf",
|
||||
"reqwest",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"serde-aux",
|
||||
"serde_json",
|
||||
@ -2108,8 +1972,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"user-model",
|
||||
"ws-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2126,58 +1988,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-revision"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"bytes",
|
||||
"dashmap",
|
||||
"flowy-error",
|
||||
"flowy-revision-persistence",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"lib-infra",
|
||||
"lib-ws",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"ws-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-revision-persistence"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"flowy-error",
|
||||
"revision-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-server-sync"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"bytes",
|
||||
"dashmap",
|
||||
"document-model",
|
||||
"flowy-sync",
|
||||
"folder-model",
|
||||
"futures",
|
||||
"lib-infra",
|
||||
"lib-ot",
|
||||
"log",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"ws-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-sqlite"
|
||||
version = "0.1.0"
|
||||
@ -2193,24 +2003,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-sync"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"document-model",
|
||||
"folder-model",
|
||||
"lib-infra",
|
||||
"lib-ot",
|
||||
"parking_lot 0.12.1",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"ws-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-task"
|
||||
version = "0.1.0"
|
||||
@ -2230,6 +2022,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"diesel",
|
||||
"diesel_derives",
|
||||
"fancy-regex 0.11.0",
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
"flowy-error",
|
||||
@ -2248,7 +2041,8 @@ dependencies = [
|
||||
"strum_macros",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"user-model",
|
||||
"unicode-segmentation",
|
||||
"validator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2257,16 +2051,6 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "folder-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"nanoid",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
@ -3025,12 +2809,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indextree"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c40411d0e5c63ef1323c3d09ce5ec6d84d71531e18daed0743fccea279d7deb6"
|
||||
|
||||
[[package]]
|
||||
name = "infer"
|
||||
version = "0.7.0"
|
||||
@ -3251,23 +3029,6 @@ dependencies = [
|
||||
"tracing-subscriber 0.2.25",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lib-ot"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"indexmap",
|
||||
"indextree",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lib-ws"
|
||||
version = "0.1.0"
|
||||
@ -4648,16 +4409,6 @@ dependencies = [
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "revision-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"md5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
@ -6279,20 +6030,6 @@ version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
|
||||
|
||||
[[package]]
|
||||
name = "user-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fancy-regex 0.11.0",
|
||||
"lazy_static",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"unicode-segmentation",
|
||||
"validator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
@ -6913,17 +6650,6 @@ dependencies = [
|
||||
"windows-implement",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ws-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11"
|
||||
version = "2.21.0"
|
||||
|
@ -23,6 +23,7 @@ tracing = { version = "0.1", features = ["log"] }
|
||||
lib-dispatch = { path = "../../rust-lib/lib-dispatch", features = ["use_serde"] }
|
||||
flowy-core = { path = "../../rust-lib/flowy-core", features = ["rev-sqlite", "ts"] }
|
||||
flowy-notification = { path = "../../rust-lib/flowy-notification", features = ["ts"] }
|
||||
flowy-net = { path = "../../rust-lib/flowy-net" }
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
|
@ -1,4 +1,5 @@
|
||||
use flowy_core::{get_client_server_configuration, AppFlowyCore, AppFlowyCoreConfig, DEFAULT_NAME};
|
||||
use flowy_core::{ AppFlowyCore, AppFlowyCoreConfig, DEFAULT_NAME};
|
||||
use flowy_net::http_server::self_host::configuration::get_client_server_configuration;
|
||||
|
||||
pub fn init_flowy_core() -> AppFlowyCore {
|
||||
let config_json = include_str!("../tauri.conf.json");
|
||||
|
@ -1,5 +1,4 @@
|
||||
export * from "./models/flowy-user";
|
||||
export * from "./models/flowy-document";
|
||||
export * from "./models/flowy-database2";
|
||||
export * from "./models/flowy-folder2";
|
||||
export * from "./models/flowy-document2";
|
||||
|
534
frontend/rust-lib/Cargo.lock
generated
534
frontend/rust-lib/Cargo.lock
generated
@ -157,17 +157,6 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "857253367827bd9d0fd973f0ef15506a96e79e41b0ad7aa691203a4e3214f6c8"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi 0.1.19",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
@ -574,15 +563,6 @@ dependencies = [
|
||||
"vsimd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "basic-toml"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c0de75129aa8d0cceaf750b89013f0e08804d6ec61416da787b35ad0d7cddf1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
@ -770,12 +750,6 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.79"
|
||||
@ -871,17 +845,6 @@ dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmd_lib"
|
||||
version = "1.3.0"
|
||||
@ -1091,19 +1054,6 @@ dependencies = [
|
||||
"yrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f1885697ee8a177096d42f158922251a41973117f6d8a234cee94b9509157b7"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"eyre",
|
||||
"indenter",
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.10.1"
|
||||
@ -1167,12 +1117,6 @@ dependencies = [
|
||||
"tracing-subscriber 0.3.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
@ -1207,42 +1151,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"cast",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"csv",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_cbor",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.8"
|
||||
@ -1296,27 +1204,6 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b015497079b9a9d69c02ad25de6c0a6edef051ea6360a327d0bd05802ef64ad"
|
||||
dependencies = [
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.94"
|
||||
@ -1372,6 +1259,7 @@ dependencies = [
|
||||
"flowy-codegen",
|
||||
"flowy-core",
|
||||
"flowy-derive",
|
||||
"flowy-net",
|
||||
"flowy-notification",
|
||||
"lazy_static",
|
||||
"lib-dispatch",
|
||||
@ -1397,18 +1285,6 @@ dependencies = [
|
||||
"parking_lot_core 0.9.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "database-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"indexmap",
|
||||
"nanoid",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derivative"
|
||||
version = "2.2.0"
|
||||
@ -1420,19 +1296,6 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deunicode"
|
||||
version = "0.4.3"
|
||||
@ -1512,21 +1375,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dissimilar"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "210ec60ae7d710bed8683e333e9d2855a8a56a3e9892b38bad3bb0d4d29b0d5e"
|
||||
|
||||
[[package]]
|
||||
name = "document-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"revision-model",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.11"
|
||||
@ -1594,16 +1442,6 @@ dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb"
|
||||
dependencies = [
|
||||
"indenter",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "faccess"
|
||||
version = "0.2.4"
|
||||
@ -1672,56 +1510,6 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-client-network-config"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"serde",
|
||||
"serde-aux",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-client-sync"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"chrono",
|
||||
"database-model",
|
||||
"dissimilar",
|
||||
"document-model",
|
||||
"flowy-derive",
|
||||
"flowy-sync",
|
||||
"folder-model",
|
||||
"lib-infra",
|
||||
"lib-ot",
|
||||
"parking_lot 0.12.1",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-client-ws"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"lib-infra",
|
||||
"lib-ws",
|
||||
"parking_lot 0.12.1",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-codegen"
|
||||
version = "0.1.0"
|
||||
@ -1753,15 +1541,11 @@ dependencies = [
|
||||
"appflowy-integrate",
|
||||
"bytes",
|
||||
"console-subscriber",
|
||||
"database-model",
|
||||
"flowy-client-ws",
|
||||
"flowy-database2",
|
||||
"flowy-document",
|
||||
"flowy-document2",
|
||||
"flowy-error",
|
||||
"flowy-folder2",
|
||||
"flowy-net",
|
||||
"flowy-revision",
|
||||
"flowy-sqlite",
|
||||
"flowy-task",
|
||||
"flowy-user",
|
||||
@ -1771,13 +1555,10 @@ dependencies = [
|
||||
"lib-log",
|
||||
"lib-ws",
|
||||
"parking_lot 0.12.1",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"user-model",
|
||||
"ws-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1794,7 +1575,6 @@ dependencies = [
|
||||
"collab",
|
||||
"collab-database",
|
||||
"dashmap",
|
||||
"database-model",
|
||||
"fancy-regex 0.10.0",
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
@ -1831,62 +1611,13 @@ dependencies = [
|
||||
"flowy-ast",
|
||||
"flowy-codegen",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_json",
|
||||
"syn 1.0.109",
|
||||
"tokio",
|
||||
"trybuild",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-document"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"color-eyre",
|
||||
"criterion",
|
||||
"dashmap",
|
||||
"derive_more",
|
||||
"diesel",
|
||||
"diesel_derives",
|
||||
"document-model",
|
||||
"flowy-client-sync",
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
"flowy-document",
|
||||
"flowy-error",
|
||||
"flowy-notification",
|
||||
"flowy-revision",
|
||||
"flowy-revision-persistence",
|
||||
"flowy-sqlite",
|
||||
"flowy-test",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"lib-dispatch",
|
||||
"lib-infra",
|
||||
"lib-ot",
|
||||
"lib-ws",
|
||||
"md5",
|
||||
"protobuf",
|
||||
"rand 0.8.5",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber 0.2.25",
|
||||
"unicode-segmentation",
|
||||
"url",
|
||||
"ws-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-document2"
|
||||
version = "0.1.0"
|
||||
@ -1921,14 +1652,11 @@ dependencies = [
|
||||
"bytes",
|
||||
"collab-database",
|
||||
"collab-document",
|
||||
"flowy-client-sync",
|
||||
"flowy-client-ws",
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
"flowy-sqlite",
|
||||
"http-error-code",
|
||||
"lib-dispatch",
|
||||
"lib-ot",
|
||||
"protobuf",
|
||||
"r2d2",
|
||||
"reqwest",
|
||||
@ -1936,7 +1664,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"thiserror",
|
||||
"user-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1950,7 +1677,6 @@ dependencies = [
|
||||
"collab-folder",
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
"flowy-document",
|
||||
"flowy-error",
|
||||
"flowy-folder2",
|
||||
"flowy-notification",
|
||||
@ -1977,20 +1703,12 @@ dependencies = [
|
||||
"bytes",
|
||||
"config",
|
||||
"dashmap",
|
||||
"document-model",
|
||||
"flowy-client-network-config",
|
||||
"flowy-client-sync",
|
||||
"flowy-client-ws",
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
"flowy-document",
|
||||
"flowy-document2",
|
||||
"flowy-error",
|
||||
"flowy-folder2",
|
||||
"flowy-server-sync",
|
||||
"flowy-sync",
|
||||
"flowy-user",
|
||||
"folder-model",
|
||||
"futures-util",
|
||||
"hyper",
|
||||
"lazy_static",
|
||||
@ -2001,7 +1719,6 @@ dependencies = [
|
||||
"parking_lot 0.12.1",
|
||||
"protobuf",
|
||||
"reqwest",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"serde-aux",
|
||||
"serde_json",
|
||||
@ -2010,8 +1727,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"user-model",
|
||||
"ws-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2028,61 +1743,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-revision"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"bytes",
|
||||
"dashmap",
|
||||
"flowy-error",
|
||||
"flowy-revision",
|
||||
"flowy-revision-persistence",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"lib-infra",
|
||||
"lib-ws",
|
||||
"nanoid",
|
||||
"parking_lot 0.12.1",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"ws-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-revision-persistence"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"flowy-error",
|
||||
"revision-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-server-sync"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"bytes",
|
||||
"dashmap",
|
||||
"document-model",
|
||||
"flowy-sync",
|
||||
"folder-model",
|
||||
"futures",
|
||||
"lib-infra",
|
||||
"lib-ot",
|
||||
"log",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"ws-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-sqlite"
|
||||
version = "0.1.0"
|
||||
@ -2100,24 +1760,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-sync"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"document-model",
|
||||
"folder-model",
|
||||
"lib-infra",
|
||||
"lib-ot",
|
||||
"parking_lot 0.12.1",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"ws-model",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flowy-task"
|
||||
version = "0.1.0"
|
||||
@ -2137,9 +1779,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fake",
|
||||
"flowy-client-sync",
|
||||
"flowy-core",
|
||||
"flowy-document",
|
||||
"flowy-folder2",
|
||||
"flowy-net",
|
||||
"flowy-user",
|
||||
@ -2152,7 +1792,7 @@ dependencies = [
|
||||
"nanoid",
|
||||
"protobuf",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"quickcheck_macros 0.9.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serial_test",
|
||||
@ -2168,6 +1808,8 @@ dependencies = [
|
||||
"bytes",
|
||||
"diesel",
|
||||
"diesel_derives",
|
||||
"fake",
|
||||
"fancy-regex 0.11.0",
|
||||
"flowy-codegen",
|
||||
"flowy-derive",
|
||||
"flowy-error",
|
||||
@ -2182,13 +1824,18 @@ dependencies = [
|
||||
"once_cell",
|
||||
"parking_lot 0.12.1",
|
||||
"protobuf",
|
||||
"quickcheck",
|
||||
"quickcheck_macros 1.0.0",
|
||||
"rand 0.8.5",
|
||||
"rand_core 0.6.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"user-model",
|
||||
"unicode-segmentation",
|
||||
"validator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2197,16 +1844,6 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "folder-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"nanoid",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
@ -2438,12 +2075,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@ -2484,15 +2115,6 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.2.6"
|
||||
@ -2708,12 +2330,6 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
@ -3238,12 +2854,6 @@ version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
@ -3326,12 +2936,6 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
@ -3604,34 +3208,6 @@ version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
@ -3847,6 +3423,17 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quickcheck_macros"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.26"
|
||||
@ -4077,16 +3664,6 @@ dependencies = [
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "revision-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"md5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
@ -4377,16 +3954,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_cbor"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
|
||||
dependencies = [
|
||||
"half",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.160"
|
||||
@ -4709,15 +4276,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.40"
|
||||
@ -4795,16 +4353,6 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
@ -5156,21 +4704,6 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||
|
||||
[[package]]
|
||||
name = "trybuild"
|
||||
version = "1.0.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501dbdbb99861e4ab6b60eb6a7493956a9defb644fd034bc4a5ef27c693c8a3a"
|
||||
dependencies = [
|
||||
"basic-toml",
|
||||
"glob",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.14.0"
|
||||
@ -5336,20 +4869,6 @@ version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
|
||||
|
||||
[[package]]
|
||||
name = "user-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fancy-regex 0.11.0",
|
||||
"lazy_static",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"unicode-segmentation",
|
||||
"validator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
@ -5726,17 +5245,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ws-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"revision-model",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xmlparser"
|
||||
version = "0.13.5"
|
||||
|
@ -8,21 +8,12 @@ members = [
|
||||
"flowy-user",
|
||||
"flowy-test",
|
||||
"flowy-sqlite",
|
||||
# "flowy-folder",r
|
||||
"flowy-folder2",
|
||||
"flowy-notification",
|
||||
"flowy-document2",
|
||||
"flowy-document",
|
||||
"flowy-error",
|
||||
"flowy-revision",
|
||||
"flowy-revision-persistence",
|
||||
# "flowy-database",
|
||||
"flowy-database2",
|
||||
"flowy-task",
|
||||
"flowy-client-sync",
|
||||
"flowy-derive",
|
||||
"flowy-ast",
|
||||
"flowy-codegen",
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
|
@ -29,7 +29,8 @@ tracing = { version = "0.1", features = ["log"] }
|
||||
lib-dispatch = { path = "../lib-dispatch" }
|
||||
flowy-core = { path = "../flowy-core" }
|
||||
flowy-notification = { path = "../flowy-notification" }
|
||||
flowy-derive = { path = "../flowy-derive" }
|
||||
flowy-net = { path = "../flowy-net" }
|
||||
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
|
||||
|
||||
[features]
|
||||
default = ["dart", "rev-sqlite"]
|
||||
@ -39,4 +40,4 @@ http_sync = ["flowy-core/http_sync", "flowy-core/use_bunyan"]
|
||||
openssl_vendored = ["flowy-core/openssl_vendored"]
|
||||
|
||||
[build-dependencies]
|
||||
flowy-codegen = { path = "../flowy-codegen", features = ["dart"] }
|
||||
flowy-codegen = { path = "../../../shared-lib/flowy-codegen", features = ["dart"] }
|
||||
|
@ -1,23 +1,27 @@
|
||||
#![allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
mod c;
|
||||
mod model;
|
||||
mod notification;
|
||||
mod protobuf;
|
||||
mod util;
|
||||
|
||||
use std::{ffi::CStr, os::raw::c_char};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use flowy_core::*;
|
||||
use flowy_net::http_server::self_host::configuration::get_client_server_configuration;
|
||||
use flowy_notification::register_notification_sender;
|
||||
use lib_dispatch::prelude::ToBytes;
|
||||
use lib_dispatch::prelude::*;
|
||||
|
||||
use crate::notification::DartNotificationSender;
|
||||
use crate::{
|
||||
c::{extend_front_four_bytes_into_bytes, forget_rust},
|
||||
model::{FFIRequest, FFIResponse},
|
||||
};
|
||||
use flowy_core::get_client_server_configuration;
|
||||
use flowy_core::*;
|
||||
use flowy_notification::register_notification_sender;
|
||||
use lazy_static::lazy_static;
|
||||
use lib_dispatch::prelude::ToBytes;
|
||||
use lib_dispatch::prelude::*;
|
||||
use parking_lot::RwLock;
|
||||
use std::{ffi::CStr, os::raw::c_char};
|
||||
|
||||
mod c;
|
||||
mod model;
|
||||
mod notification;
|
||||
mod protobuf;
|
||||
mod util;
|
||||
|
||||
lazy_static! {
|
||||
static ref APPFLOWY_CORE: RwLock<Option<AppFlowyCore>> = RwLock::new(None);
|
||||
|
@ -1,27 +0,0 @@
|
||||
[package]
|
||||
name = "flowy-client-sync"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
lib-ot = { path = "../../../shared-lib/lib-ot" }
|
||||
lib-infra = { path = "../../../shared-lib/lib-infra" }
|
||||
flowy-derive = { path = "../flowy-derive" }
|
||||
folder-model = { path = "../../../shared-lib/folder-model" }
|
||||
database-model = { path = "../../../shared-lib/database-model" }
|
||||
revision-model = { path = "../../../shared-lib/revision-model" }
|
||||
document-model = { path = "../../../shared-lib/document-model" }
|
||||
flowy-sync = { path = "../../../shared-lib/flowy-sync" }
|
||||
bytes = "1.4"
|
||||
tokio = { version = "1.26", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = {version = "1.0"}
|
||||
dissimilar = "1.0"
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
url = "2.3"
|
||||
strum = "0.21"
|
||||
strum_macros = "0.21"
|
||||
chrono = "0.4.23"
|
||||
parking_lot = "0.12.1"
|
@ -1,491 +0,0 @@
|
||||
use crate::errors::{SyncError, SyncResult};
|
||||
use crate::util::cal_diff;
|
||||
use database_model::{
|
||||
gen_block_id, gen_row_id, CellRevision, DatabaseBlockRevision, RowChangeset, RowRevision,
|
||||
};
|
||||
use flowy_sync::util::make_operations_from_revisions;
|
||||
use lib_infra::util::md5;
|
||||
use lib_ot::core::{DeltaBuilder, DeltaOperations, EmptyAttributes, OperationTransform};
|
||||
use revision_model::Revision;
|
||||
use std::any::type_name;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type DatabaseBlockOperations = DeltaOperations<EmptyAttributes>;
|
||||
pub type DatabaseBlockOperationsBuilder = DeltaBuilder;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DatabaseBlockRevisionPad {
|
||||
block: DatabaseBlockRevision,
|
||||
operations: DatabaseBlockOperations,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for DatabaseBlockRevisionPad {
|
||||
type Target = DatabaseBlockRevision;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.block
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseBlockRevisionPad {
|
||||
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::<Vec<Arc<RowRevision>>>();
|
||||
DatabaseBlockRevision {
|
||||
block_id: duplicated_block_id.to_string(),
|
||||
rows: duplicated_rows,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_operations(operations: DatabaseBlockOperations) -> SyncResult<Self> {
|
||||
let s = operations.content()?;
|
||||
let revision: DatabaseBlockRevision = serde_json::from_str(&s).map_err(|e| {
|
||||
let msg = format!(
|
||||
"Deserialize operations to {} failed: {}",
|
||||
type_name::<DatabaseBlockRevision>(),
|
||||
e
|
||||
);
|
||||
tracing::error!("{}", s);
|
||||
SyncError::internal().context(msg)
|
||||
})?;
|
||||
Ok(Self {
|
||||
block: revision,
|
||||
operations,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_revisions(revisions: Vec<Revision>) -> SyncResult<Self> {
|
||||
let operations: DatabaseBlockOperations = 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<String>,
|
||||
) -> SyncResult<Option<DatabaseBlockRevisionChangeset>> {
|
||||
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<Cow<'_, String>>,
|
||||
) -> SyncResult<Option<DatabaseBlockRevisionChangeset>> {
|
||||
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<RowRevision>)> {
|
||||
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<T>(
|
||||
&self,
|
||||
row_ids: Option<Vec<Cow<'_, T>>>,
|
||||
) -> SyncResult<Vec<Arc<RowRevision>>>
|
||||
where
|
||||
T: AsRef<str> + 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::<HashMap<&str, Arc<RowRevision>>>();
|
||||
|
||||
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::<Vec<_>>(),
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cell_revs(
|
||||
&self,
|
||||
field_id: &str,
|
||||
row_ids: Option<Vec<Cow<'_, String>>>,
|
||||
) -> SyncResult<Vec<CellRevision>> {
|
||||
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::<Vec<CellRevision>>();
|
||||
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<usize> {
|
||||
self.block.rows.iter().position(|row| row.id == row_id)
|
||||
}
|
||||
|
||||
pub fn update_row(
|
||||
&mut self,
|
||||
changeset: RowChangeset,
|
||||
) -> SyncResult<Option<DatabaseBlockRevisionChangeset>> {
|
||||
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<Option<DatabaseBlockRevisionChangeset>> {
|
||||
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<F>(&mut self, f: F) -> SyncResult<Option<DatabaseBlockRevisionChangeset>>
|
||||
where
|
||||
F: for<'a> FnOnce(&'a mut Vec<Arc<RowRevision>>) -> SyncResult<Option<()>>,
|
||||
{
|
||||
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::<EmptyAttributes>(old, new) {
|
||||
None => Ok(None),
|
||||
Some(operations) => {
|
||||
tracing::trace!(
|
||||
"[{}] Composing operations {}",
|
||||
type_name::<DatabaseBlockRevision>(),
|
||||
operations.json_str()
|
||||
);
|
||||
self.operations = self.operations.compose(&operations)?;
|
||||
Ok(Some(DatabaseBlockRevisionChangeset {
|
||||
operations,
|
||||
md5: md5(&self.operations.json_bytes()),
|
||||
}))
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_row<F>(
|
||||
&mut self,
|
||||
row_id: &str,
|
||||
f: F,
|
||||
) -> SyncResult<Option<DatabaseBlockRevisionChangeset>>
|
||||
where
|
||||
F: FnOnce(&mut RowRevision) -> SyncResult<Option<()>>,
|
||||
{
|
||||
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<String> {
|
||||
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 struct DatabaseBlockRevisionChangeset {
|
||||
pub operations: DatabaseBlockOperations,
|
||||
/// md5: the md5 of the grid after applying the change.
|
||||
pub md5: String,
|
||||
}
|
||||
|
||||
pub fn make_database_block_operations(
|
||||
block_rev: &DatabaseBlockRevision,
|
||||
) -> DatabaseBlockOperations {
|
||||
let json = serde_json::to_string(&block_rev).unwrap();
|
||||
DatabaseBlockOperationsBuilder::new().insert(&json).build()
|
||||
}
|
||||
|
||||
pub fn make_database_block_revisions(
|
||||
_user_id: &str,
|
||||
database_block_meta_data: &DatabaseBlockRevision,
|
||||
) -> Vec<Revision> {
|
||||
let operations = make_database_block_operations(database_block_meta_data);
|
||||
let bytes = operations.json_bytes();
|
||||
let revision = Revision::initial_revision(&database_block_meta_data.block_id, bytes);
|
||||
vec![revision]
|
||||
}
|
||||
|
||||
impl std::default::Default for DatabaseBlockRevisionPad {
|
||||
fn default() -> Self {
|
||||
let block_revision = DatabaseBlockRevision {
|
||||
block_id: gen_block_id(),
|
||||
rows: vec![],
|
||||
};
|
||||
|
||||
let operations = make_database_block_operations(&block_revision);
|
||||
DatabaseBlockRevisionPad {
|
||||
block: block_revision,
|
||||
operations,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::client_database::{DatabaseBlockOperations, DatabaseBlockRevisionPad};
|
||||
use database_model::{RowChangeset, RowRevision};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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: &DatabaseBlockRevisionPad) -> RowRevision {
|
||||
RowRevision {
|
||||
id: id.to_string(),
|
||||
block_id: pad.block_id.clone(),
|
||||
cells: Default::default(),
|
||||
height: 0,
|
||||
visibility: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[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 _ = 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();
|
||||
|
||||
assert_eq!(*pad.rows[0], row_1);
|
||||
assert_eq!(*pad.rows[1], row_3);
|
||||
assert_eq!(*pad.rows[2], row_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);
|
||||
|
||||
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();
|
||||
|
||||
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_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.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.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() -> DatabaseBlockRevisionPad {
|
||||
let operations =
|
||||
DatabaseBlockOperations::from_json(r#"[{"insert":"{\"block_id\":\"1\",\"rows\":[]}"}]"#)
|
||||
.unwrap();
|
||||
DatabaseBlockRevisionPad::from_operations(operations).unwrap()
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
use crate::errors::{SyncError, SyncResult};
|
||||
use database_model::{
|
||||
BuildDatabaseContext, DatabaseBlockMetaRevision, DatabaseBlockRevision, FieldRevision,
|
||||
LayoutSetting, RowRevision,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct DatabaseBuilder {
|
||||
build_context: BuildDatabaseContext,
|
||||
}
|
||||
|
||||
impl std::default::Default for DatabaseBuilder {
|
||||
fn default() -> Self {
|
||||
let mut build_context = BuildDatabaseContext::new();
|
||||
|
||||
let block_meta = DatabaseBlockMetaRevision::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);
|
||||
|
||||
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 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 field_revs(&self) -> &Vec<Arc<FieldRevision>> {
|
||||
&self.build_context.field_revs
|
||||
}
|
||||
|
||||
pub fn block_id(&self) -> &str {
|
||||
&self.build_context.block_metas.first().unwrap().block_id
|
||||
}
|
||||
|
||||
pub fn set_layout_setting(&mut self, layout_setting: LayoutSetting) {
|
||||
self.build_context.layout_setting = layout_setting;
|
||||
}
|
||||
|
||||
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::<Vec<&String>>();
|
||||
for row in rows {
|
||||
let cell_field_ids = row.cells.keys().into_iter().collect::<Vec<&String>>();
|
||||
if cell_field_ids != field_ids {
|
||||
let msg = format!("{:?} contains invalid cells", row);
|
||||
return Err(SyncError::internal().context(msg));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
@ -1,488 +0,0 @@
|
||||
use crate::errors::{internal_sync_error, SyncError, SyncResult};
|
||||
use crate::util::cal_diff;
|
||||
use database_model::{
|
||||
gen_block_id, gen_database_id, DatabaseBlockMetaRevision, DatabaseBlockMetaRevisionChangeset,
|
||||
DatabaseRevision, FieldRevision, FieldTypeRevision,
|
||||
};
|
||||
use flowy_sync::util::make_operations_from_revisions;
|
||||
use lib_infra::util::md5;
|
||||
use lib_infra::util::move_vec_element;
|
||||
use lib_ot::core::{DeltaOperationBuilder, DeltaOperations, EmptyAttributes, OperationTransform};
|
||||
use revision_model::Revision;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type DatabaseOperations = DeltaOperations<EmptyAttributes>;
|
||||
pub type DatabaseOperationsBuilder = DeltaOperationBuilder<EmptyAttributes>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DatabaseRevisionPad {
|
||||
database_rev: Arc<DatabaseRevision>,
|
||||
operations: DatabaseOperations,
|
||||
}
|
||||
|
||||
pub trait JsonDeserializer {
|
||||
fn deserialize(&self, type_option_data: Vec<u8>) -> SyncResult<String>;
|
||||
}
|
||||
|
||||
impl DatabaseRevisionPad {
|
||||
pub fn database_id(&self) -> String {
|
||||
self.database_rev.database_id.clone()
|
||||
}
|
||||
|
||||
pub async fn duplicate_database_block_meta(
|
||||
&self,
|
||||
) -> (Vec<FieldRevision>, Vec<DatabaseBlockMetaRevision>) {
|
||||
let fields = self
|
||||
.database_rev
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field_rev| field_rev.as_ref().clone())
|
||||
.collect();
|
||||
|
||||
let blocks = self
|
||||
.database_rev
|
||||
.blocks
|
||||
.iter()
|
||||
.map(|block| {
|
||||
let mut duplicated_block = (**block).clone();
|
||||
duplicated_block.block_id = gen_block_id();
|
||||
duplicated_block
|
||||
})
|
||||
.collect::<Vec<DatabaseBlockMetaRevision>>();
|
||||
|
||||
(fields, blocks)
|
||||
}
|
||||
|
||||
pub fn from_operations(operations: DatabaseOperations) -> SyncResult<Self> {
|
||||
let content = operations.content()?;
|
||||
let database_rev: DatabaseRevision = serde_json::from_str(&content).map_err(|e| {
|
||||
let msg = format!("Deserialize operations to database failed: {}", e);
|
||||
SyncError::internal().context(msg)
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
database_rev: Arc::new(database_rev),
|
||||
operations,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_revisions(revisions: Vec<Revision>) -> SyncResult<Self> {
|
||||
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<String>,
|
||||
) -> SyncResult<Option<DatabaseRevisionChangeset>> {
|
||||
self.modify_database(|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),
|
||||
}
|
||||
Ok(Some(()))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_field_rev(
|
||||
&mut self,
|
||||
field_id: &str,
|
||||
) -> SyncResult<Option<DatabaseRevisionChangeset>> {
|
||||
self.modify_database(|database| {
|
||||
match database
|
||||
.fields
|
||||
.iter()
|
||||
.position(|field| field.id == field_id)
|
||||
{
|
||||
None => Ok(None),
|
||||
Some(index) => {
|
||||
if database.fields[index].is_primary {
|
||||
Err(SyncError::can_not_delete_primary_field())
|
||||
} else {
|
||||
database.fields.remove(index);
|
||||
Ok(Some(()))
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn duplicate_field_rev(
|
||||
&mut self,
|
||||
field_id: &str,
|
||||
duplicated_field_id: &str,
|
||||
) -> SyncResult<Option<DatabaseRevisionChangeset>> {
|
||||
self.modify_database(|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<DT, TT, T>(
|
||||
&mut self,
|
||||
field_id: &str,
|
||||
new_field_type: T,
|
||||
make_default_type_option: DT,
|
||||
type_option_transform: TT,
|
||||
) -> SyncResult<Option<DatabaseRevisionChangeset>>
|
||||
where
|
||||
DT: FnOnce() -> String,
|
||||
TT: FnOnce(FieldTypeRevision, Option<String>, String) -> String,
|
||||
T: Into<FieldTypeRevision>,
|
||||
{
|
||||
let new_field_type = new_field_type.into();
|
||||
self.modify_database(|database_rev| {
|
||||
match database_rev
|
||||
.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<FieldRevision>,
|
||||
) -> SyncResult<Option<DatabaseRevisionChangeset>> {
|
||||
self.modify_database(|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<Option<DatabaseRevisionChangeset>> {
|
||||
self.modify_database(|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
|
||||
.database_rev
|
||||
.fields
|
||||
.iter()
|
||||
.any(|field| field.id == field_id)
|
||||
}
|
||||
|
||||
pub fn get_field_rev(&self, field_id: &str) -> Option<(usize, &Arc<FieldRevision>)> {
|
||||
self
|
||||
.database_rev
|
||||
.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, field)| field.id == field_id)
|
||||
}
|
||||
|
||||
pub fn get_field_revs(
|
||||
&self,
|
||||
field_ids: Option<Vec<String>>,
|
||||
) -> SyncResult<Vec<Arc<FieldRevision>>> {
|
||||
match field_ids {
|
||||
None => Ok(self.database_rev.fields.clone()),
|
||||
Some(field_ids) => {
|
||||
let field_by_field_id = self
|
||||
.database_rev
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| (&field.id, field))
|
||||
.collect::<HashMap<&String, &Arc<FieldRevision>>>();
|
||||
|
||||
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::<Vec<Arc<FieldRevision>>>();
|
||||
Ok(fields)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_block_meta_rev(
|
||||
&mut self,
|
||||
block: DatabaseBlockMetaRevision,
|
||||
) -> SyncResult<Option<DatabaseRevisionChangeset>> {
|
||||
self.modify_database(|grid_meta| {
|
||||
if grid_meta.blocks.iter().any(|b| b.block_id == block.block_id) {
|
||||
tracing::warn!("Duplicate grid block");
|
||||
Ok(None)
|
||||
} else {
|
||||
match grid_meta.blocks.last() {
|
||||
None => grid_meta.blocks.push(Arc::new(block)),
|
||||
Some(last_block) => {
|
||||
if last_block.start_row_index > block.start_row_index
|
||||
&& last_block.len() > block.start_row_index
|
||||
{
|
||||
let msg = "GridBlock's start_row_index should be greater than the last_block's start_row_index and its len".to_string();
|
||||
return Err(SyncError::internal().context(msg))
|
||||
}
|
||||
grid_meta.blocks.push(Arc::new(block));
|
||||
}
|
||||
}
|
||||
Ok(Some(()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_block_meta_revs(&self) -> Vec<Arc<DatabaseBlockMetaRevision>> {
|
||||
self.database_rev.blocks.clone()
|
||||
}
|
||||
|
||||
pub fn update_block_rev(
|
||||
&mut self,
|
||||
changeset: DatabaseBlockMetaRevisionChangeset,
|
||||
) -> SyncResult<Option<DatabaseRevisionChangeset>> {
|
||||
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(start_row_index) = changeset.start_row_index {
|
||||
block.start_row_index = start_row_index;
|
||||
is_changed = Some(());
|
||||
}
|
||||
|
||||
Ok(is_changed)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn database_md5(&self) -> String {
|
||||
md5(&self.operations.json_bytes())
|
||||
}
|
||||
|
||||
pub fn operations_json_str(&self) -> String {
|
||||
self.operations.json_str()
|
||||
}
|
||||
|
||||
pub fn get_fields(&self) -> &[Arc<FieldRevision>] {
|
||||
&self.database_rev.fields
|
||||
}
|
||||
|
||||
fn modify_database<F>(&mut self, f: F) -> SyncResult<Option<DatabaseRevisionChangeset>>
|
||||
where
|
||||
F: FnOnce(&mut DatabaseRevision) -> SyncResult<Option<()>>,
|
||||
{
|
||||
let cloned_database = self.database_rev.clone();
|
||||
match f(Arc::make_mut(&mut self.database_rev))? {
|
||||
None => Ok(None),
|
||||
Some(_) => {
|
||||
let old = make_database_rev_json_str(&cloned_database)?;
|
||||
let new = self.json_str()?;
|
||||
match cal_diff::<EmptyAttributes>(old, new) {
|
||||
None => Ok(None),
|
||||
Some(operations) => {
|
||||
self.operations = self.operations.compose(&operations)?;
|
||||
Ok(Some(DatabaseRevisionChangeset {
|
||||
operations,
|
||||
md5: self.database_md5(),
|
||||
}))
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_block<F>(
|
||||
&mut self,
|
||||
block_id: &str,
|
||||
f: F,
|
||||
) -> SyncResult<Option<DatabaseRevisionChangeset>>
|
||||
where
|
||||
F: FnOnce(&mut DatabaseBlockMetaRevision) -> SyncResult<Option<()>>,
|
||||
{
|
||||
self.modify_database(|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<F>(
|
||||
&mut self,
|
||||
field_id: &str,
|
||||
f: F,
|
||||
) -> SyncResult<Option<DatabaseRevisionChangeset>>
|
||||
where
|
||||
F: FnOnce(&mut FieldRevision) -> SyncResult<Option<()>>,
|
||||
{
|
||||
self.modify_database(|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<String> {
|
||||
make_database_rev_json_str(&self.database_rev)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_database_rev_json_str(grid_revision: &DatabaseRevision) -> SyncResult<String> {
|
||||
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 fn make_database_operations(grid_rev: &DatabaseRevision) -> DatabaseOperations {
|
||||
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<Revision> {
|
||||
let operations = make_database_operations(grid_rev);
|
||||
let bytes = operations.json_bytes();
|
||||
let revision = Revision::initial_revision(&grid_rev.database_id, bytes);
|
||||
vec![revision]
|
||||
}
|
||||
|
||||
impl std::default::Default for DatabaseRevisionPad {
|
||||
fn default() -> Self {
|
||||
let database = DatabaseRevision::new(&gen_database_id());
|
||||
let operations = make_database_operations(&database);
|
||||
DatabaseRevisionPad {
|
||||
database_rev: Arc::new(database),
|
||||
operations,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,381 +0,0 @@
|
||||
use crate::errors::{internal_sync_error, SyncError, SyncResult};
|
||||
use crate::util::cal_diff;
|
||||
use database_model::{
|
||||
DatabaseViewRevision, FieldRevision, FieldTypeRevision, FilterRevision,
|
||||
GroupConfigurationRevision, LayoutRevision, SortRevision,
|
||||
};
|
||||
use flowy_sync::util::make_operations_from_revisions;
|
||||
use lib_infra::util::md5;
|
||||
use lib_ot::core::{DeltaBuilder, DeltaOperations, EmptyAttributes, OperationTransform};
|
||||
use revision_model::Revision;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type DatabaseViewOperations = DeltaOperations<EmptyAttributes>;
|
||||
pub type DatabaseViewOperationsBuilder = DeltaBuilder;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DatabaseViewRevisionPad {
|
||||
view: Arc<DatabaseViewRevision>,
|
||||
operations: DatabaseViewOperations,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for DatabaseViewRevisionPad {
|
||||
type Target = DatabaseViewRevision;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.view
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseViewRevisionPad {
|
||||
// For the moment, the view_id is equal to grid_id. The database_id represents the database id.
|
||||
// A database can be referenced by multiple views.
|
||||
pub fn new(database_id: String, view_id: String, name: String, layout: LayoutRevision) -> Self {
|
||||
let view = Arc::new(DatabaseViewRevision::new(
|
||||
database_id,
|
||||
view_id,
|
||||
true,
|
||||
name,
|
||||
layout,
|
||||
));
|
||||
let json = serde_json::to_string(&view).unwrap();
|
||||
let operations = DatabaseViewOperationsBuilder::new().insert(&json).build();
|
||||
Self { view, operations }
|
||||
}
|
||||
|
||||
pub fn from_operations(operations: DatabaseViewOperations) -> SyncResult<Self> {
|
||||
if operations.is_empty() {
|
||||
return Err(SyncError::record_not_found().context("Unexpected empty operations"));
|
||||
}
|
||||
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(revisions: Vec<Revision>) -> SyncResult<Self> {
|
||||
let operations: DatabaseViewOperations = make_operations_from_revisions(revisions)?;
|
||||
Self::from_operations(operations)
|
||||
}
|
||||
|
||||
pub fn get_groups_by_field_revs(
|
||||
&self,
|
||||
field_revs: &[Arc<FieldRevision>],
|
||||
) -> Vec<Arc<GroupConfigurationRevision>> {
|
||||
self.groups.get_objects_by_field_revs(field_revs)
|
||||
}
|
||||
|
||||
pub fn get_all_groups(&self) -> Vec<Arc<GroupConfigurationRevision>> {
|
||||
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<Option<DatabaseViewRevisionChangeset>> {
|
||||
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<F: FnOnce(&mut GroupConfigurationRevision)>(
|
||||
&mut self,
|
||||
field_id: &str,
|
||||
field_type: &FieldTypeRevision,
|
||||
configuration_id: &str,
|
||||
mut_configuration_fn: F,
|
||||
) -> SyncResult<Option<DatabaseViewRevisionChangeset>> {
|
||||
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<Option<DatabaseViewRevisionChangeset>> {
|
||||
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<FieldRevision>]) -> Vec<Arc<SortRevision>> {
|
||||
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<Arc<SortRevision>> {
|
||||
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<Arc<SortRevision>> {
|
||||
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<Option<DatabaseViewRevisionChangeset>> {
|
||||
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<Option<DatabaseViewRevisionChangeset>> {
|
||||
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<T: Into<FieldTypeRevision>>(
|
||||
&mut self,
|
||||
sort_id: &str,
|
||||
field_id: &str,
|
||||
field_type: T,
|
||||
) -> SyncResult<Option<DatabaseViewRevisionChangeset>> {
|
||||
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<Option<DatabaseViewRevisionChangeset>> {
|
||||
self.modify(|view| {
|
||||
view.sorts.clear();
|
||||
Ok(Some(()))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_all_filters(&self, field_revs: &[Arc<FieldRevision>]) -> Vec<Arc<FilterRevision>> {
|
||||
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<Arc<FilterRevision>> {
|
||||
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<Arc<FilterRevision>> {
|
||||
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<Option<DatabaseViewRevisionChangeset>> {
|
||||
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<Option<DatabaseViewRevisionChangeset>> {
|
||||
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<T: Into<FieldTypeRevision>>(
|
||||
&mut self,
|
||||
filter_id: &str,
|
||||
field_id: &str,
|
||||
field_type: T,
|
||||
) -> SyncResult<Option<DatabaseViewRevisionChangeset>> {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the settings for the given layout. If it's not exists then will return the
|
||||
/// default settings for the given layout.
|
||||
/// Each [database view](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/database-view) has its own settings.
|
||||
pub fn get_layout_setting<T>(&self, layout: &LayoutRevision) -> Option<T>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
let settings_str = self.view.layout_settings.get(layout)?;
|
||||
serde_json::from_str::<T>(settings_str).ok()
|
||||
}
|
||||
|
||||
/// updates the settings for the given layout type
|
||||
pub fn set_layout_setting<T>(
|
||||
&mut self,
|
||||
layout: &LayoutRevision,
|
||||
settings: &T,
|
||||
) -> SyncResult<Option<DatabaseViewRevisionChangeset>>
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
let settings_str = serde_json::to_string(settings).map_err(internal_sync_error)?;
|
||||
self.modify(|view| {
|
||||
view.layout_settings.insert(layout.clone(), settings_str);
|
||||
Ok(Some(()))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn json_str(&self) -> SyncResult<String> {
|
||||
make_database_view_rev_json_str(&self.view)
|
||||
}
|
||||
|
||||
pub fn layout(&self) -> LayoutRevision {
|
||||
self.layout.clone()
|
||||
}
|
||||
|
||||
fn modify<F>(&mut self, f: F) -> SyncResult<Option<DatabaseViewRevisionChangeset>>
|
||||
where
|
||||
F: FnOnce(&mut DatabaseViewRevision) -> SyncResult<Option<()>>,
|
||||
{
|
||||
let cloned_view = self.view.clone();
|
||||
match f(Arc::make_mut(&mut self.view))? {
|
||||
None => Ok(None),
|
||||
Some(_) => {
|
||||
let old = make_database_view_rev_json_str(&cloned_view)?;
|
||||
let new = self.json_str()?;
|
||||
match cal_diff::<EmptyAttributes>(old, new) {
|
||||
None => Ok(None),
|
||||
Some(operations) => {
|
||||
self.operations = self.operations.compose(&operations)?;
|
||||
let md5 = md5(&self.operations.json_bytes());
|
||||
Ok(Some(DatabaseViewRevisionChangeset { operations, md5 }))
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DatabaseViewRevisionChangeset {
|
||||
pub operations: DatabaseViewOperations,
|
||||
pub md5: String,
|
||||
}
|
||||
|
||||
pub fn make_database_view_rev_json_str(
|
||||
database_view_rev: &DatabaseViewRevision,
|
||||
) -> SyncResult<String> {
|
||||
let json = serde_json::to_string(database_view_rev).map_err(|err| {
|
||||
internal_sync_error(format!("Serialize grid view to json str failed. {:?}", err))
|
||||
})?;
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
pub fn make_database_view_operations(
|
||||
database_view_rev: &DatabaseViewRevision,
|
||||
) -> DatabaseViewOperations {
|
||||
let json = serde_json::to_string(database_view_rev).unwrap();
|
||||
DatabaseViewOperationsBuilder::new().insert(&json).build()
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
mod block_revision_pad;
|
||||
mod database_builder;
|
||||
mod database_revision_pad;
|
||||
mod database_view_revision_pad;
|
||||
|
||||
pub use block_revision_pad::*;
|
||||
pub use database_builder::*;
|
||||
pub use database_revision_pad::*;
|
||||
pub use database_view_revision_pad::*;
|
@ -1,263 +0,0 @@
|
||||
use crate::{
|
||||
client_document::{
|
||||
history::{History, UndoResult},
|
||||
view::{ViewExtensions, RECORD_THRESHOLD},
|
||||
},
|
||||
errors::SyncError,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use lib_infra::util::md5;
|
||||
use lib_ot::text_delta::DeltaTextOperationBuilder;
|
||||
use lib_ot::{core::*, text_delta::DeltaTextOperations};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub trait InitialDocument {
|
||||
fn json_str() -> String;
|
||||
}
|
||||
|
||||
pub struct EmptyDocument();
|
||||
impl InitialDocument for EmptyDocument {
|
||||
fn json_str() -> String {
|
||||
DeltaTextOperations::default().json_str()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NewlineDocument();
|
||||
impl InitialDocument for NewlineDocument {
|
||||
fn json_str() -> String {
|
||||
initial_delta_document_content()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initial_delta_document_content() -> String {
|
||||
DeltaTextOperationBuilder::new()
|
||||
.insert("\n")
|
||||
.build()
|
||||
.json_str()
|
||||
}
|
||||
|
||||
pub struct ClientDocument {
|
||||
operations: DeltaTextOperations,
|
||||
history: History,
|
||||
view: ViewExtensions,
|
||||
last_edit_time: usize,
|
||||
notify: Option<mpsc::UnboundedSender<()>>,
|
||||
}
|
||||
|
||||
impl ClientDocument {
|
||||
pub fn new<C: InitialDocument>() -> 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<Self, SyncError> {
|
||||
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<T: ToString>(
|
||||
&mut self,
|
||||
index: usize,
|
||||
data: T,
|
||||
) -> Result<DeltaTextOperations, SyncError> {
|
||||
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<DeltaTextOperations, SyncError> {
|
||||
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<DeltaTextOperations, SyncError> {
|
||||
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<T: ToString>(
|
||||
&mut self,
|
||||
interval: Interval,
|
||||
data: T,
|
||||
) -> Result<DeltaTextOperations, SyncError> {
|
||||
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<UndoResult, SyncError> {
|
||||
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<UndoResult, SyncError> {
|
||||
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(())
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
use crate::client_document::DeleteExt;
|
||||
use lib_ot::{
|
||||
core::{DeltaOperationBuilder, Interval},
|
||||
text_delta::DeltaTextOperations,
|
||||
};
|
||||
|
||||
pub struct DefaultDelete {}
|
||||
impl DeleteExt for DefaultDelete {
|
||||
fn ext_name(&self) -> &str {
|
||||
"DefaultDelete"
|
||||
}
|
||||
|
||||
fn apply(&self, _delta: &DeltaTextOperations, interval: Interval) -> Option<DeltaTextOperations> {
|
||||
Some(
|
||||
DeltaOperationBuilder::new()
|
||||
.retain(interval.start)
|
||||
.delete(interval.size())
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
mod default_delete;
|
||||
mod preserve_line_format_merge;
|
||||
|
||||
pub use default_delete::*;
|
||||
pub use preserve_line_format_merge::*;
|
@ -1,65 +0,0 @@
|
||||
use crate::{client_document::DeleteExt, util::is_newline};
|
||||
use lib_ot::{
|
||||
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 apply(&self, delta: &DeltaTextOperations, interval: Interval) -> Option<DeltaTextOperations> {
|
||||
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);
|
||||
|
||||
// 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::<Utf16CodeUnitMetric>(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)
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
// use crate::{
|
||||
// client::extensions::FormatExt,
|
||||
// core::{Attribute, AttributeKey, Delta, DeltaBuilder, DeltaIter,
|
||||
// Interval}, };
|
||||
//
|
||||
// pub struct FormatLinkAtCaretPositionExt {}
|
||||
//
|
||||
// impl FormatExt for FormatLinkAtCaretPositionExt {
|
||||
// fn ext_name(&self) -> &str {
|
||||
// std::any::type_name::<FormatLinkAtCaretPositionExt>() }
|
||||
//
|
||||
// fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute)
|
||||
// -> Option<Delta> { if attribute.key != AttributeKey::Link ||
|
||||
// interval.size() != 0 { return None;
|
||||
// }
|
||||
//
|
||||
// let mut iter = DeltaIter::from_offset(delta, interval.start);
|
||||
// let (before, after) = (iter.next_op_with_len(interval.size()),
|
||||
// iter.next_op()); let mut start = interval.end;
|
||||
// let mut retain = 0;
|
||||
//
|
||||
// if let Some(before) = before {
|
||||
// if before.contain_attribute(attribute) {
|
||||
// start -= before.len();
|
||||
// retain += before.len();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if let Some(after) = after {
|
||||
// if after.contain_attribute(attribute) {
|
||||
// if retain != 0 {
|
||||
// retain += after.len();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if retain == 0 {
|
||||
// return None;
|
||||
// }
|
||||
//
|
||||
// Some(
|
||||
// DeltaBuilder::new()
|
||||
// .retain(start)
|
||||
// .retain_with_attributes(retain, (attribute.clone()).into())
|
||||
// .build(),
|
||||
// )
|
||||
// }
|
||||
// }
|
@ -1,7 +0,0 @@
|
||||
pub use format_at_position::*;
|
||||
pub use resolve_block_format::*;
|
||||
pub use resolve_inline_format::*;
|
||||
|
||||
mod format_at_position;
|
||||
mod resolve_block_format;
|
||||
mod resolve_inline_format;
|
@ -1,63 +0,0 @@
|
||||
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},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
client_document::{extensions::helper::line_break, FormatExt},
|
||||
util::find_newline,
|
||||
};
|
||||
|
||||
pub struct ResolveBlockFormat {}
|
||||
impl FormatExt for ResolveBlockFormat {
|
||||
fn ext_name(&self) -> &str {
|
||||
"ResolveBlockFormat"
|
||||
}
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
delta: &DeltaTextOperations,
|
||||
interval: Interval,
|
||||
attribute: &AttributeEntry,
|
||||
) -> Option<DeltaTextOperations> {
|
||||
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);
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
use lib_ot::core::AttributeEntry;
|
||||
use lib_ot::text_delta::is_inline;
|
||||
use lib_ot::{
|
||||
core::{DeltaOperationBuilder, Interval, OperationIterator},
|
||||
text_delta::{AttributeScope, DeltaTextOperations},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
client_document::{extensions::helper::line_break, FormatExt},
|
||||
util::find_newline,
|
||||
};
|
||||
|
||||
pub struct ResolveInlineFormat {}
|
||||
impl FormatExt for ResolveInlineFormat {
|
||||
fn ext_name(&self) -> &str {
|
||||
"ResolveInlineFormat"
|
||||
}
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
delta: &DeltaTextOperations,
|
||||
interval: Interval,
|
||||
attribute: &AttributeEntry,
|
||||
) -> Option<DeltaTextOperations> {
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
use crate::util::find_newline;
|
||||
use lib_ot::core::AttributeEntry;
|
||||
use lib_ot::text_delta::{
|
||||
empty_attributes, AttributeScope, DeltaTextOperation, DeltaTextOperations,
|
||||
};
|
||||
|
||||
pub(crate) fn line_break(
|
||||
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();
|
||||
|
||||
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()];
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
use crate::{client_document::InsertExt, util::is_newline};
|
||||
use lib_ot::core::{is_empty_line_at_index, DeltaOperationBuilder, OperationIterator};
|
||||
use lib_ot::text_delta::{attributes_except_header, BuildInTextAttributeKey, DeltaTextOperations};
|
||||
|
||||
pub struct AutoExitBlock {}
|
||||
|
||||
impl InsertExt for AutoExitBlock {
|
||||
fn ext_name(&self) -> &str {
|
||||
"AutoExitBlock"
|
||||
}
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
delta: &DeltaTextOperations,
|
||||
replace_len: usize,
|
||||
text: &str,
|
||||
index: usize,
|
||||
) -> Option<DeltaTextOperations> {
|
||||
// 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(),
|
||||
)
|
||||
}
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
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},
|
||||
};
|
||||
use std::cmp::min;
|
||||
use url::Url;
|
||||
|
||||
pub struct AutoFormatExt {}
|
||||
impl InsertExt for AutoFormatExt {
|
||||
fn ext_name(&self) -> &str {
|
||||
"AutoFormatExt"
|
||||
}
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
delta: &DeltaTextOperations,
|
||||
replace_len: usize,
|
||||
text: &str,
|
||||
index: usize,
|
||||
) -> Option<DeltaTextOperations> {
|
||||
// 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());
|
||||
|
||||
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(),
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AutoFormatter {
|
||||
Url(Url),
|
||||
}
|
||||
|
||||
impl AutoFormatter {
|
||||
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(),
|
||||
};
|
||||
|
||||
count_utf16_code_units(&s)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AutoFormat {}
|
||||
impl AutoFormat {
|
||||
fn parse(s: &str) -> Option<AutoFormatter> {
|
||||
if let Ok(url) = Url::parse(s) {
|
||||
return Some(AutoFormatter::Url(url));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
use crate::client_document::InsertExt;
|
||||
use lib_ot::core::AttributeHashMap;
|
||||
use lib_ot::{
|
||||
core::{DeltaOperationBuilder, OperationAttributes, OperationIterator, NEW_LINE},
|
||||
text_delta::{BuildInTextAttributeKey, DeltaTextOperations},
|
||||
};
|
||||
|
||||
pub struct DefaultInsertAttribute {}
|
||||
impl InsertExt for DefaultInsertAttribute {
|
||||
fn ext_name(&self) -> &str {
|
||||
"DefaultInsertAttribute"
|
||||
}
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
delta: &DeltaTextOperations,
|
||||
replace_len: usize,
|
||||
text: &str,
|
||||
index: usize,
|
||||
) -> Option<DeltaTextOperations> {
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
use crate::client_document::InsertExt;
|
||||
pub use auto_exit_block::*;
|
||||
pub use auto_format::*;
|
||||
pub use default_insert::*;
|
||||
use lib_ot::text_delta::DeltaTextOperations;
|
||||
pub use preserve_block_format::*;
|
||||
pub use preserve_inline_format::*;
|
||||
pub use reset_format_on_new_line::*;
|
||||
|
||||
mod auto_exit_block;
|
||||
mod auto_format;
|
||||
mod default_insert;
|
||||
mod preserve_block_format;
|
||||
mod preserve_inline_format;
|
||||
mod reset_format_on_new_line;
|
||||
|
||||
pub struct InsertEmbedsExt {}
|
||||
impl InsertExt for InsertEmbedsExt {
|
||||
fn ext_name(&self) -> &str {
|
||||
"InsertEmbedsExt"
|
||||
}
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
_delta: &DeltaTextOperations,
|
||||
_replace_len: usize,
|
||||
_text: &str,
|
||||
_index: usize,
|
||||
) -> Option<DeltaTextOperations> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ForceNewlineForInsertsAroundEmbedExt {}
|
||||
impl InsertExt for ForceNewlineForInsertsAroundEmbedExt {
|
||||
fn ext_name(&self) -> &str {
|
||||
"ForceNewlineForInsertsAroundEmbedExt"
|
||||
}
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
_delta: &DeltaTextOperations,
|
||||
_replace_len: usize,
|
||||
_text: &str,
|
||||
_index: usize,
|
||||
) -> Option<DeltaTextOperations> {
|
||||
None
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct PreserveBlockFormatOnInsert {}
|
||||
impl InsertExt for PreserveBlockFormatOnInsert {
|
||||
fn ext_name(&self) -> &str {
|
||||
"PreserveBlockFormatOnInsert"
|
||||
}
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
delta: &DeltaTextOperations,
|
||||
replace_len: usize,
|
||||
text: &str,
|
||||
index: usize,
|
||||
) -> Option<DeltaTextOperations> {
|
||||
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 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);
|
||||
},
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
use crate::{
|
||||
client_document::InsertExt,
|
||||
util::{contain_newline, is_newline},
|
||||
};
|
||||
use lib_ot::{
|
||||
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 apply(
|
||||
&self,
|
||||
delta: &DeltaTextOperations,
|
||||
replace_len: usize,
|
||||
text: &str,
|
||||
index: usize,
|
||||
) -> Option<DeltaTextOperations> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PreserveLineFormatOnSplit {}
|
||||
impl InsertExt for PreserveLineFormatOnSplit {
|
||||
fn ext_name(&self) -> &str {
|
||||
"PreserveLineFormatOnSplit"
|
||||
}
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
delta: &DeltaTextOperations,
|
||||
replace_len: usize,
|
||||
text: &str,
|
||||
index: usize,
|
||||
) -> Option<DeltaTextOperations> {
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
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},
|
||||
};
|
||||
|
||||
pub struct ResetLineFormatOnNewLine {}
|
||||
impl InsertExt for ResetLineFormatOnNewLine {
|
||||
fn ext_name(&self) -> &str {
|
||||
"ResetLineFormatOnNewLine"
|
||||
}
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
delta: &DeltaTextOperations,
|
||||
replace_len: usize,
|
||||
text: &str,
|
||||
index: usize,
|
||||
) -> Option<DeltaTextOperations> {
|
||||
if !is_newline(text) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut iter = OperationIterator::new(delta);
|
||||
iter.seek::<Utf16CodeUnitMetric>(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(),
|
||||
)
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
pub use delete::*;
|
||||
pub use format::*;
|
||||
pub use insert::*;
|
||||
use lib_ot::core::AttributeEntry;
|
||||
use lib_ot::{core::Interval, text_delta::DeltaTextOperations};
|
||||
|
||||
mod delete;
|
||||
mod format;
|
||||
mod helper;
|
||||
mod insert;
|
||||
|
||||
pub type InsertExtension = Box<dyn InsertExt + Send + Sync>;
|
||||
pub type FormatExtension = Box<dyn FormatExt + Send + Sync>;
|
||||
pub type DeleteExtension = Box<dyn DeleteExt + Send + Sync>;
|
||||
|
||||
pub trait InsertExt {
|
||||
fn ext_name(&self) -> &str;
|
||||
fn apply(
|
||||
&self,
|
||||
delta: &DeltaTextOperations,
|
||||
replace_len: usize,
|
||||
text: &str,
|
||||
index: usize,
|
||||
) -> Option<DeltaTextOperations>;
|
||||
}
|
||||
|
||||
pub trait FormatExt {
|
||||
fn ext_name(&self) -> &str;
|
||||
fn apply(
|
||||
&self,
|
||||
delta: &DeltaTextOperations,
|
||||
interval: Interval,
|
||||
attribute: &AttributeEntry,
|
||||
) -> Option<DeltaTextOperations>;
|
||||
}
|
||||
|
||||
pub trait DeleteExt {
|
||||
fn ext_name(&self) -> &str;
|
||||
fn apply(&self, delta: &DeltaTextOperations, interval: Interval) -> Option<DeltaTextOperations>;
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
use lib_ot::text_delta::DeltaTextOperations;
|
||||
|
||||
const MAX_UNDOES: usize = 20;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UndoResult {
|
||||
pub operations: DeltaTextOperations,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct History {
|
||||
#[allow(dead_code)]
|
||||
cur_undo: usize,
|
||||
undoes: Vec<DeltaTextOperations>,
|
||||
redoes: Vec<DeltaTextOperations>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl std::default::Default for History {
|
||||
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 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;
|
||||
}
|
||||
|
||||
self.redoes.clear();
|
||||
self.add_undo(delta);
|
||||
|
||||
if self.undoes.len() > self.capacity {
|
||||
self.undoes.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn undo(&mut self) -> Option<DeltaTextOperations> {
|
||||
if !self.can_undo() {
|
||||
return None;
|
||||
}
|
||||
let delta = self.undoes.pop().unwrap();
|
||||
Some(delta)
|
||||
}
|
||||
|
||||
pub fn redo(&mut self) -> Option<DeltaTextOperations> {
|
||||
if !self.can_redo() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let delta = self.redoes.pop().unwrap();
|
||||
Some(delta)
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
#![allow(clippy::module_inception)]
|
||||
|
||||
pub use document_pad::*;
|
||||
pub(crate) use extensions::*;
|
||||
pub use view::*;
|
||||
|
||||
mod document_pad;
|
||||
mod extensions;
|
||||
pub mod history;
|
||||
mod view;
|
@ -1,119 +0,0 @@
|
||||
use crate::client_document::*;
|
||||
use lib_ot::core::AttributeEntry;
|
||||
use lib_ot::{
|
||||
core::{trim, Interval},
|
||||
errors::{ErrorBuilder, OTError, OTErrorCode},
|
||||
text_delta::DeltaTextOperations,
|
||||
};
|
||||
|
||||
pub const RECORD_THRESHOLD: usize = 400; // in milliseconds
|
||||
|
||||
pub struct ViewExtensions {
|
||||
insert_exts: Vec<InsertExtension>,
|
||||
format_exts: Vec<FormatExtension>,
|
||||
delete_exts: Vec<DeleteExtension>,
|
||||
}
|
||||
|
||||
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 insert(
|
||||
&self,
|
||||
operations: &DeltaTextOperations,
|
||||
text: &str,
|
||||
interval: Interval,
|
||||
) -> Result<DeltaTextOperations, OTError> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn delete(
|
||||
&self,
|
||||
delta: &DeltaTextOperations,
|
||||
interval: Interval,
|
||||
) -> Result<DeltaTextOperations, OTError> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn format(
|
||||
&self,
|
||||
operations: &DeltaTextOperations,
|
||||
attribute: AttributeEntry,
|
||||
interval: Interval,
|
||||
) -> Result<DeltaTextOperations, OTError> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn construct_insert_exts() -> Vec<InsertExtension> {
|
||||
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<FormatExtension> {
|
||||
vec![
|
||||
// Box::new(FormatLinkAtCaretPositionExt {}),
|
||||
Box::new(ResolveBlockFormat {}),
|
||||
Box::new(ResolveInlineFormat {}),
|
||||
]
|
||||
}
|
||||
|
||||
fn construct_delete_exts() -> Vec<DeleteExtension> {
|
||||
vec![
|
||||
Box::new(PreserveLineFormatOnMerge {}),
|
||||
Box::new(DefaultDelete {}),
|
||||
]
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
use crate::client_folder::FolderOperations;
|
||||
use crate::{
|
||||
client_folder::{default_folder_operations, FolderPad},
|
||||
errors::SyncResult,
|
||||
};
|
||||
use flowy_sync::util::make_operations_from_revisions;
|
||||
use folder_model::{TrashRevision, WorkspaceRevision};
|
||||
use revision_model::Revision;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub(crate) struct FolderPadBuilder {
|
||||
workspaces: Vec<WorkspaceRevision>,
|
||||
trash: Vec<TrashRevision>,
|
||||
}
|
||||
|
||||
impl FolderPadBuilder {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
workspaces: vec![],
|
||||
trash: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn with_workspace(mut self, workspaces: Vec<WorkspaceRevision>) -> Self {
|
||||
self.workspaces = workspaces;
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn with_trash(mut self, trash: Vec<TrashRevision>) -> Self {
|
||||
self.trash = trash;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn build_with_revisions(self, revisions: Vec<Revision>) -> SyncResult<FolderPad> {
|
||||
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> {
|
||||
FolderPad::new(self.workspaces, self.trash)
|
||||
}
|
||||
}
|
@ -1,147 +0,0 @@
|
||||
use crate::client_folder::trash_node::TrashNode;
|
||||
use crate::client_folder::workspace_node::WorkspaceNode;
|
||||
use crate::errors::{SyncError, SyncResult};
|
||||
use flowy_derive::Node;
|
||||
use lib_ot::core::NodeTree;
|
||||
use lib_ot::core::*;
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type AtomicNodeTree = RwLock<NodeTree>;
|
||||
|
||||
pub struct FolderNodePad {
|
||||
pub tree: Arc<AtomicNodeTree>,
|
||||
pub node_id: NodeId,
|
||||
pub workspaces: WorkspaceList,
|
||||
pub trash: TrashList,
|
||||
}
|
||||
|
||||
#[derive(Clone, Node)]
|
||||
#[node_type = "workspaces"]
|
||||
pub struct WorkspaceList {
|
||||
pub tree: Arc<AtomicNodeTree>,
|
||||
pub node_id: Option<NodeId>,
|
||||
|
||||
#[node(child_name = "workspace")]
|
||||
inner: Vec<WorkspaceNode>,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for WorkspaceList {
|
||||
type Target = Vec<WorkspaceNode>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for WorkspaceList {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Node)]
|
||||
#[node_type = "trash"]
|
||||
pub struct TrashList {
|
||||
pub tree: Arc<AtomicNodeTree>,
|
||||
pub node_id: Option<NodeId>,
|
||||
|
||||
#[node(child_name = "trash")]
|
||||
inner: Vec<TrashNode>,
|
||||
}
|
||||
|
||||
impl FolderNodePad {
|
||||
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_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();
|
||||
|
||||
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<String> {
|
||||
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()));
|
||||
|
||||
// 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();
|
||||
|
||||
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());
|
||||
|
||||
Self {
|
||||
tree,
|
||||
node_id,
|
||||
workspaces,
|
||||
trash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn folder_path() -> Path {
|
||||
vec![0].into()
|
||||
}
|
||||
|
||||
fn workspaces_path() -> Path {
|
||||
folder_path().clone_with(0)
|
||||
}
|
||||
|
||||
fn trash_path() -> Path {
|
||||
folder_path().clone_with(1)
|
||||
}
|
@ -1,985 +0,0 @@
|
||||
use crate::errors::internal_sync_error;
|
||||
use crate::util::cal_diff;
|
||||
use crate::{
|
||||
client_folder::builder::FolderPadBuilder,
|
||||
errors::{SyncError, SyncResult},
|
||||
};
|
||||
use folder_model::{AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision};
|
||||
use lib_infra::util::md5;
|
||||
use lib_infra::util::move_vec_element;
|
||||
use lib_ot::core::*;
|
||||
use revision_model::Revision;
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type FolderOperations = DeltaOperations<EmptyAttributes>;
|
||||
pub type FolderOperationsBuilder = DeltaOperationBuilder<EmptyAttributes>;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct FolderPad {
|
||||
folder_rev: FolderRevision,
|
||||
operations: FolderOperations,
|
||||
}
|
||||
|
||||
impl FolderPad {
|
||||
pub fn new(workspaces: Vec<WorkspaceRevision>, trash: Vec<TrashRevision>) -> SyncResult<Self> {
|
||||
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<Self> {
|
||||
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<Revision>) -> SyncResult<Self> {
|
||||
FolderPadBuilder::new().build_with_revisions(revisions)
|
||||
}
|
||||
|
||||
pub fn from_operations(operations: FolderOperations) -> SyncResult<Self> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
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<Option<FolderChangeset>> {
|
||||
let workspace = Arc::new(workspace_rev);
|
||||
if self.folder_rev.workspaces.contains(&workspace) {
|
||||
tracing::warn!("[RootFolder]: Duplicate workspace");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
self.modify_workspaces(move |workspaces| {
|
||||
workspaces.push(workspace);
|
||||
Ok(Some(()))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_workspace(
|
||||
&mut self,
|
||||
workspace_id: &str,
|
||||
name: Option<String>,
|
||||
desc: Option<String>,
|
||||
) -> SyncResult<Option<FolderChangeset>> {
|
||||
self.with_workspace(workspace_id, |workspace| {
|
||||
if let Some(name) = name {
|
||||
workspace.name = name;
|
||||
}
|
||||
|
||||
if let Some(desc) = desc {
|
||||
workspace.desc = desc;
|
||||
}
|
||||
Ok(Some(()))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_workspaces(
|
||||
&self,
|
||||
workspace_id: Option<String>,
|
||||
) -> SyncResult<Vec<WorkspaceRevision>> {
|
||||
match workspace_id {
|
||||
None => {
|
||||
let workspaces = self
|
||||
.folder_rev
|
||||
.workspaces
|
||||
.iter()
|
||||
.map(|workspace| workspace.as_ref().clone())
|
||||
.collect::<Vec<WorkspaceRevision>>();
|
||||
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)),
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub fn delete_workspace(&mut self, workspace_id: &str) -> SyncResult<Option<FolderChangeset>> {
|
||||
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<Option<FolderChangeset>> {
|
||||
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<AppRevision> {
|
||||
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 update_app(
|
||||
&mut self,
|
||||
app_id: &str,
|
||||
name: Option<String>,
|
||||
desc: Option<String>,
|
||||
) -> SyncResult<Option<FolderChangeset>> {
|
||||
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<Option<FolderChangeset>> {
|
||||
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<Option<FolderChangeset>> {
|
||||
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<Option<FolderChangeset>> {
|
||||
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<ViewRevision> {
|
||||
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)))
|
||||
}
|
||||
|
||||
pub fn read_views(&self, belong_to_id: &str) -> SyncResult<Vec<ViewRevision>> {
|
||||
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 update_view(
|
||||
&mut self,
|
||||
view_id: &str,
|
||||
name: Option<String>,
|
||||
desc: Option<String>,
|
||||
modified_time: i64,
|
||||
) -> SyncResult<Option<FolderChangeset>> {
|
||||
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 {
|
||||
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<Option<FolderChangeset>> {
|
||||
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<Option<FolderChangeset>> {
|
||||
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<TrashRevision>) -> SyncResult<Option<FolderChangeset>> {
|
||||
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::<Vec<Arc<TrashRevision>>>();
|
||||
if new_trash.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
original_trash.append(&mut new_trash);
|
||||
Ok(Some(()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_trash(&self, trash_id: Option<String>) -> SyncResult<Vec<TrashRevision>> {
|
||||
match trash_id {
|
||||
None => {
|
||||
// Removes the duplicate items if exist
|
||||
let mut trash_items = Vec::<TrashRevision>::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<Vec<String>>,
|
||||
) -> SyncResult<Option<FolderChangeset>> {
|
||||
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<String> {
|
||||
make_folder_rev_json_str(&self.folder_rev)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_folder_rev_json_str(folder_rev: &FolderRevision) -> SyncResult<String> {
|
||||
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<F>(&mut self, f: F) -> SyncResult<Option<FolderChangeset>>
|
||||
where
|
||||
F: FnOnce(&mut Vec<Arc<WorkspaceRevision>>) -> SyncResult<Option<()>>,
|
||||
{
|
||||
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::<EmptyAttributes>(old, new) {
|
||||
None => Ok(None),
|
||||
Some(operations) => {
|
||||
self.operations = self.operations.compose(&operations)?;
|
||||
Ok(Some(FolderChangeset {
|
||||
operations,
|
||||
md5: self.folder_md5(),
|
||||
}))
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn with_workspace<F>(&mut self, workspace_id: &str, f: F) -> SyncResult<Option<FolderChangeset>>
|
||||
where
|
||||
F: FnOnce(&mut WorkspaceRevision) -> SyncResult<Option<()>>,
|
||||
{
|
||||
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<F>(&mut self, f: F) -> SyncResult<Option<FolderChangeset>>
|
||||
where
|
||||
F: FnOnce(&mut Vec<Arc<TrashRevision>>) -> SyncResult<Option<()>>,
|
||||
{
|
||||
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::<EmptyAttributes>(old, new) {
|
||||
None => Ok(None),
|
||||
Some(operations) => {
|
||||
self.operations = self.operations.compose(&operations)?;
|
||||
Ok(Some(FolderChangeset {
|
||||
operations,
|
||||
md5: self.folder_md5(),
|
||||
}))
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn with_app<F>(&mut self, app_id: &str, f: F) -> SyncResult<Option<FolderChangeset>>
|
||||
where
|
||||
F: FnOnce(&mut AppRevision) -> SyncResult<Option<()>>,
|
||||
{
|
||||
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(),
|
||||
};
|
||||
|
||||
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<F>(
|
||||
&mut self,
|
||||
belong_to_id: &str,
|
||||
view_id: &str,
|
||||
f: F,
|
||||
) -> SyncResult<Option<FolderChangeset>>
|
||||
where
|
||||
F: FnOnce(&mut ViewRevision) -> SyncResult<Option<()>>,
|
||||
{
|
||||
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()
|
||||
}
|
||||
|
||||
pub fn initial_folder_operations(folder_pad: &FolderPad) -> SyncResult<FolderOperations> {
|
||||
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,
|
||||
}
|
||||
|
||||
#[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;
|
||||
|
||||
#[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 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);
|
||||
}
|
||||
|
||||
#[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":[]}"#,
|
||||
);
|
||||
|
||||
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":[]}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[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",
|
||||
"name": "😁 my first workspace",
|
||||
"desc": "",
|
||||
"apps": [
|
||||
{
|
||||
"id": "",
|
||||
"workspace_id": "1",
|
||||
"name": "😁 my first app",
|
||||
"desc": "",
|
||||
"belongings": [],
|
||||
"version": 0,
|
||||
"modified_time": 0,
|
||||
"create_time": 0
|
||||
}
|
||||
],
|
||||
"modified_time": 0,
|
||||
"create_time": 0
|
||||
}
|
||||
],
|
||||
"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;
|
||||
|
||||
let new_folder = make_folder_from_operations(initial_operations, vec![operations]);
|
||||
assert_folder_equal(
|
||||
&folder,
|
||||
&new_folder,
|
||||
r#"{
|
||||
"workspaces": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "😁 my first workspace",
|
||||
"desc": "",
|
||||
"apps": [
|
||||
{
|
||||
"id": "",
|
||||
"workspace_id": "1",
|
||||
"name": "🤪 rename app",
|
||||
"desc": "",
|
||||
"belongings": [],
|
||||
"version": 0,
|
||||
"modified_time": 0,
|
||||
"create_time": 0
|
||||
}
|
||||
],
|
||||
"modified_time": 0,
|
||||
"create_time": 0
|
||||
}
|
||||
],
|
||||
"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#"{
|
||||
"workspaces": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "😁 my first workspace",
|
||||
"desc": "",
|
||||
"apps": [],
|
||||
"modified_time": 0,
|
||||
"create_time": 0
|
||||
}
|
||||
],
|
||||
"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#"
|
||||
{
|
||||
"workspaces": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "😁 my first workspace",
|
||||
"desc": "",
|
||||
"apps": [
|
||||
{
|
||||
"id": "",
|
||||
"workspace_id": "1",
|
||||
"name": "😁 my first app",
|
||||
"desc": "",
|
||||
"belongings": [
|
||||
{
|
||||
"id": "",
|
||||
"belong_to_id": "",
|
||||
"name": "🎃 my first view",
|
||||
"desc": "",
|
||||
"view_type": "Blank",
|
||||
"version": 0,
|
||||
"belongings": [],
|
||||
"modified_time": 0,
|
||||
"create_time": 0
|
||||
}
|
||||
],
|
||||
"version": 0,
|
||||
"modified_time": 0,
|
||||
"create_time": 0
|
||||
}
|
||||
],
|
||||
"modified_time": 0,
|
||||
"create_time": 0
|
||||
}
|
||||
],
|
||||
"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;
|
||||
|
||||
let new_folder = make_folder_from_operations(initial_operations, vec![operations]);
|
||||
assert_folder_equal(
|
||||
&folder,
|
||||
&new_folder,
|
||||
r#"{
|
||||
"workspaces": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "😁 my first workspace",
|
||||
"desc": "",
|
||||
"apps": [
|
||||
{
|
||||
"id": "",
|
||||
"workspace_id": "1",
|
||||
"name": "😁 my first app",
|
||||
"desc": "",
|
||||
"belongings": [
|
||||
{
|
||||
"id": "",
|
||||
"belong_to_id": "",
|
||||
"name": "😦 rename view",
|
||||
"desc": "",
|
||||
"view_type": "Blank",
|
||||
"version": 0,
|
||||
"belongings": [],
|
||||
"modified_time": 123,
|
||||
"create_time": 0
|
||||
}
|
||||
],
|
||||
"version": 0,
|
||||
"modified_time": 0,
|
||||
"create_time": 0
|
||||
}
|
||||
],
|
||||
"modified_time": 0,
|
||||
"create_time": 0
|
||||
}
|
||||
],
|
||||
"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;
|
||||
|
||||
let new_folder = make_folder_from_operations(initial_operations, vec![operations]);
|
||||
assert_folder_equal(
|
||||
&folder,
|
||||
&new_folder,
|
||||
r#"{
|
||||
"workspaces": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "😁 my first workspace",
|
||||
"desc": "",
|
||||
"apps": [
|
||||
{
|
||||
"id": "",
|
||||
"workspace_id": "1",
|
||||
"name": "😁 my first app",
|
||||
"desc": "",
|
||||
"belongings": [],
|
||||
"version": 0,
|
||||
"modified_time": 0,
|
||||
"create_time": 0
|
||||
}
|
||||
],
|
||||
"modified_time": 0,
|
||||
"create_time": 0
|
||||
}
|
||||
],
|
||||
"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#"{
|
||||
"workspaces": [],
|
||||
"trash": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "🚽 my first trash",
|
||||
"modified_time": 0,
|
||||
"create_time": 0,
|
||||
"ty": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[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<FolderOperations>,
|
||||
) -> 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);
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
mod builder;
|
||||
mod folder_node;
|
||||
mod folder_pad;
|
||||
mod trash_node;
|
||||
mod util;
|
||||
mod workspace_node;
|
||||
|
||||
pub use folder_node::*;
|
||||
pub use folder_node::*;
|
||||
pub use folder_pad::*;
|
||||
pub use workspace_node::*;
|
@ -1,20 +0,0 @@
|
||||
use crate::client_folder::util::*;
|
||||
use crate::client_folder::AtomicNodeTree;
|
||||
use flowy_derive::Node;
|
||||
use lib_ot::core::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Node)]
|
||||
#[node_type = "trash"]
|
||||
pub struct TrashNode {
|
||||
pub tree: Arc<AtomicNodeTree>,
|
||||
pub node_id: Option<NodeId>,
|
||||
|
||||
#[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,
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
use crate::client_folder::AtomicNodeTree;
|
||||
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<AtomicNodeTree>,
|
||||
node_id: &NodeId,
|
||||
key: &str,
|
||||
) -> Option<String> {
|
||||
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<AtomicNodeTree>,
|
||||
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(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_attributes_int_value(
|
||||
tree: Arc<AtomicNodeTree>,
|
||||
node_id: &NodeId,
|
||||
key: &str,
|
||||
) -> Option<i64> {
|
||||
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<AtomicNodeTree>, node_id: &NodeId) -> Option<AttributeHashMap> {
|
||||
tree
|
||||
.read()
|
||||
.get_node(*node_id)
|
||||
.map(|node| node.attributes.clone())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_attributes_value(
|
||||
tree: Arc<AtomicNodeTree>,
|
||||
node_id: &NodeId,
|
||||
key: &str,
|
||||
) -> Option<AttributeValue> {
|
||||
tree
|
||||
.read()
|
||||
.get_node(*node_id)
|
||||
.and_then(|node| node.attributes.get(key).cloned())
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
use crate::client_folder::util::*;
|
||||
use crate::client_folder::AtomicNodeTree;
|
||||
use flowy_derive::Node;
|
||||
use lib_ot::core::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Node)]
|
||||
#[node_type = "workspace"]
|
||||
pub struct WorkspaceNode {
|
||||
pub tree: Arc<AtomicNodeTree>,
|
||||
pub node_id: Option<NodeId>,
|
||||
|
||||
#[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(child_name = "app")]
|
||||
pub apps: Vec<AppNode>,
|
||||
}
|
||||
|
||||
impl WorkspaceNode {
|
||||
pub fn new(tree: Arc<AtomicNodeTree>, 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<AtomicNodeTree>,
|
||||
pub node_id: Option<NodeId>,
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
impl AppNode {
|
||||
pub fn new(tree: Arc<AtomicNodeTree>, id: String, name: String) -> Self {
|
||||
Self {
|
||||
tree,
|
||||
node_id: None,
|
||||
id,
|
||||
name,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
pub mod client_database;
|
||||
pub mod client_document;
|
||||
pub mod client_folder;
|
||||
pub mod errors {
|
||||
pub use flowy_sync::errors::*;
|
||||
}
|
||||
pub mod util;
|
||||
|
||||
pub use flowy_sync::util::*;
|
||||
pub use lib_ot::text_delta::DeltaTextOperations;
|
@ -1,129 +0,0 @@
|
||||
use crate::errors::SyncError;
|
||||
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,
|
||||
};
|
||||
use revision_model::Revision;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
#[inline]
|
||||
pub fn find_newline(s: &str) -> Option<usize> {
|
||||
s.find(NEW_LINE)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_newline(s: &str) -> bool {
|
||||
s == NEW_LINE
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_whitespace(s: &str) -> bool {
|
||||
s == WHITESPACE
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contain_newline(s: &str) -> bool {
|
||||
s.contains(NEW_LINE)
|
||||
}
|
||||
|
||||
pub fn recover_operation_from_revisions<T>(
|
||||
revisions: Vec<Revision>,
|
||||
validator: impl Fn(&DeltaOperations<T>) -> bool,
|
||||
) -> Option<(DeltaOperations<T>, i64)>
|
||||
where
|
||||
T: OperationAttributes + DeserializeOwned + OperationAttributes,
|
||||
{
|
||||
let mut new_operations = DeltaOperations::<T>::new();
|
||||
let mut rev_id = 0;
|
||||
for revision in revisions {
|
||||
if let Ok(operations) = DeltaOperations::<T>::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 {
|
||||
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<Revision>,
|
||||
) -> Result<Option<DocumentInfo>, SyncError> {
|
||||
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 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,
|
||||
}))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rev_id_from_str(s: &str) -> Result<i64, SyncError> {
|
||||
let rev_id = s
|
||||
.to_owned()
|
||||
.parse::<i64>()
|
||||
.map_err(|e| SyncError::internal().context(format!("Parse rev_id from {} failed. {}", s, e)))?;
|
||||
Ok(rev_id)
|
||||
}
|
||||
|
||||
pub fn cal_diff<T: OperationAttributes>(old: String, new: String) -> Option<DeltaOperations<T>> {
|
||||
let chunks = dissimilar::diff(&old, &new);
|
||||
let mut delta_builder = DeltaOperationBuilder::<T>::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)
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
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"}]}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[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"}]}"#
|
||||
);
|
||||
|
||||
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"}]}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[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());
|
||||
|
||||
assert_eq!(
|
||||
folder_pad
|
||||
.workspaces
|
||||
.get_workspace("1")
|
||||
.unwrap()
|
||||
.get_name()
|
||||
.unwrap(),
|
||||
"my first workspace"
|
||||
);
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
mod folder_test;
|
||||
mod script;
|
||||
mod workspace_test;
|
@ -1,117 +0,0 @@
|
||||
use flowy_client_sync::client_folder::{AppNode, FolderNodePad, WorkspaceNode};
|
||||
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<AppRevision>,
|
||||
},
|
||||
AssertAppContent {
|
||||
id: String,
|
||||
name: String,
|
||||
},
|
||||
// AssertNumberOfApps { expected: usize },
|
||||
}
|
||||
|
||||
pub struct FolderNodePadTest {
|
||||
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 run_scripts(&mut self, scripts: Vec<FolderNodePadScript>) {
|
||||
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);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
use crate::client_folder::script::FolderNodePadScript::*;
|
||||
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(),
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
#[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(),
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
#[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,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
#[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(),
|
||||
},
|
||||
]);
|
||||
}
|
@ -1 +0,0 @@
|
||||
mod client_folder;
|
@ -13,14 +13,11 @@ flowy-net = { path = "../flowy-net" }
|
||||
flowy-folder2 = { path = "../flowy-folder2" }
|
||||
#flowy-database = { path = "../flowy-database" }
|
||||
flowy-database2 = { path = "../flowy-database2" }
|
||||
database-model = { path = "../../../shared-lib/database-model" }
|
||||
user-model = { path = "../../../shared-lib/user-model" }
|
||||
flowy-client-ws = { path = "../../../shared-lib/flowy-client-ws" }
|
||||
flowy-sqlite = { path = "../flowy-sqlite", optional = true }
|
||||
flowy-document = { path = "../flowy-document" }
|
||||
#flowy-document = { path = "../flowy-document" }
|
||||
flowy-document2 = { path = "../flowy-document2" }
|
||||
flowy-revision = { path = "../flowy-revision" }
|
||||
flowy-error = { path = "../flowy-error", features = ["adaptor_ws"] }
|
||||
#flowy-revision = { path = "../flowy-revision" }
|
||||
flowy-error = { path = "../flowy-error" }
|
||||
flowy-task = { path = "../flowy-task" }
|
||||
appflowy-integrate = { version = "0.1.0" }
|
||||
|
||||
@ -31,8 +28,6 @@ tokio = { version = "1.26", features = ["full"] }
|
||||
console-subscriber = { version = "0.1.8", optional = true }
|
||||
parking_lot = "0.12.1"
|
||||
|
||||
revision-model = { path = "../../../shared-lib/revision-model" }
|
||||
ws-model = { path = "../../../shared-lib/ws-model" }
|
||||
lib-ws = { path = "../../../shared-lib/lib-ws" }
|
||||
lib-infra = { path = "../../../shared-lib/lib-infra" }
|
||||
serde = "1.0"
|
||||
@ -49,7 +44,6 @@ dart = [
|
||||
"flowy-net/dart",
|
||||
"flowy-folder2/dart",
|
||||
"flowy-database2/dart",
|
||||
"flowy-document/dart",
|
||||
"flowy-document2/dart",
|
||||
]
|
||||
ts = [
|
||||
@ -57,12 +51,10 @@ ts = [
|
||||
"flowy-net/ts",
|
||||
"flowy-folder2/ts",
|
||||
"flowy-database2/ts",
|
||||
"flowy-document/ts",
|
||||
"flowy-document2/ts",
|
||||
]
|
||||
rev-sqlite = [
|
||||
"flowy-sqlite",
|
||||
"flowy-user/rev-sqlite",
|
||||
"flowy-document/rev-sqlite",
|
||||
]
|
||||
openssl_vendored = ["flowy-sqlite/openssl_vendored"]
|
||||
|
@ -4,7 +4,6 @@ use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||
use appflowy_integrate::RocksCollabDB;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use flowy_client_ws::FlowyWebSocketConnect;
|
||||
use flowy_database2::{DatabaseManager2, DatabaseUser2};
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_task::TaskDispatcher;
|
||||
@ -14,7 +13,6 @@ pub struct Database2DepsResolver();
|
||||
|
||||
impl Database2DepsResolver {
|
||||
pub async fn resolve(
|
||||
_ws_conn: Arc<FlowyWebSocketConnect>,
|
||||
user_session: Arc<UserSession>,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
collab_builder: Arc<AppFlowyCollabBuilder>,
|
||||
|
@ -1,117 +0,0 @@
|
||||
use bytes::Bytes;
|
||||
use flowy_client_ws::FlowyWebSocketConnect;
|
||||
use flowy_document::{
|
||||
DocumentCloudService, DocumentConfig, DocumentDatabase, DocumentManager, DocumentUser,
|
||||
};
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_net::ClientServerConfiguration;
|
||||
use flowy_net::{http_server::document::DocumentCloudServiceImpl, local_server::LocalServer};
|
||||
use flowy_revision::{RevisionWebSocket, WSStateReceiver};
|
||||
use flowy_sqlite::ConnectionPool;
|
||||
use flowy_user::services::UserSession;
|
||||
use futures_core::future::BoxFuture;
|
||||
use lib_infra::future::BoxResultFuture;
|
||||
use lib_ws::{WSChannel, WSMessageReceiver, WebSocketRawMessage};
|
||||
use std::{convert::TryInto, path::Path, sync::Arc};
|
||||
use ws_model::ws_revision::ClientRevisionWSData;
|
||||
|
||||
pub struct DocumentDepsResolver();
|
||||
impl DocumentDepsResolver {
|
||||
pub fn resolve(
|
||||
local_server: Option<Arc<LocalServer>>,
|
||||
ws_conn: Arc<FlowyWebSocketConnect>,
|
||||
user_session: Arc<UserSession>,
|
||||
server_config: &ClientServerConfiguration,
|
||||
document_config: &DocumentConfig,
|
||||
) -> Arc<DocumentManager> {
|
||||
let user = Arc::new(BlockUserImpl(user_session.clone()));
|
||||
let rev_web_socket = Arc::new(DocumentRevisionWebSocket(ws_conn.clone()));
|
||||
let cloud_service: Arc<dyn DocumentCloudService> = 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();
|
||||
|
||||
manager
|
||||
}
|
||||
}
|
||||
|
||||
struct BlockUserImpl(Arc<UserSession>);
|
||||
impl DocumentUser for BlockUserImpl {
|
||||
fn user_dir(&self) -> Result<String, FlowyError> {
|
||||
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)
|
||||
}
|
||||
|
||||
fn user_id(&self) -> Result<i64, FlowyError> {
|
||||
self.0.user_id()
|
||||
}
|
||||
|
||||
fn token(&self) -> Result<String, FlowyError> {
|
||||
self.0.token()
|
||||
}
|
||||
}
|
||||
|
||||
struct DocumentDatabaseImpl(Arc<UserSession>);
|
||||
impl DocumentDatabase for DocumentDatabaseImpl {
|
||||
fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError> {
|
||||
self.0.db_pool()
|
||||
}
|
||||
}
|
||||
|
||||
struct DocumentRevisionWebSocket(Arc<FlowyWebSocketConnect>);
|
||||
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 subscribe_state_changed(&self) -> BoxFuture<WSStateReceiver> {
|
||||
let ws_conn = self.0.clone();
|
||||
Box::pin(async move { ws_conn.subscribe_websocket_state().await })
|
||||
}
|
||||
}
|
||||
|
||||
struct DocumentWSMessageReceiverImpl(Arc<DocumentManager>);
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
pub use database_deps::*;
|
||||
pub use document2_deps::*;
|
||||
pub use folder2_deps::*;
|
||||
pub use user_deps::*;
|
||||
|
||||
mod document2_deps;
|
||||
mod document_deps;
|
||||
mod folder2_deps;
|
||||
mod user_deps;
|
||||
mod util;
|
||||
|
||||
pub use document2_deps::*;
|
||||
pub use document_deps::*;
|
||||
pub use folder2_deps::*;
|
||||
pub use user_deps::*;
|
||||
|
||||
mod database_deps;
|
||||
|
@ -1,9 +1,10 @@
|
||||
use flowy_net::ClientServerConfiguration;
|
||||
use flowy_net::{http_server::user::UserHttpCloudService, local_server::LocalServer};
|
||||
use flowy_user::event_map::UserCloudService;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use flowy_net::http_server::self_host::configuration::ClientServerConfiguration;
|
||||
use flowy_net::http_server::self_host::user::UserHttpCloudService;
|
||||
use flowy_net::local_server::LocalServer;
|
||||
use flowy_user::event_map::UserCloudService;
|
||||
|
||||
pub struct UserDepsResolver();
|
||||
impl UserDepsResolver {
|
||||
pub fn resolve(
|
||||
|
@ -10,20 +10,17 @@ use std::{
|
||||
|
||||
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||
use appflowy_integrate::config::{AWSDynamoDBConfig, AppFlowyCollabConfig};
|
||||
use tokio::sync::{broadcast, RwLock};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use flowy_client_ws::{listen_on_websocket, FlowyWebSocketConnect, NetworkType};
|
||||
use flowy_database2::DatabaseManager2;
|
||||
use flowy_document::entities::DocumentVersionPB;
|
||||
use flowy_document::{DocumentConfig, DocumentManager};
|
||||
use flowy_document2::manager::DocumentManager as DocumentManager2;
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_folder2::manager::Folder2Manager;
|
||||
pub use flowy_net::get_client_server_configuration;
|
||||
use flowy_net::http_server::self_host::configuration::ClientServerConfiguration;
|
||||
use flowy_net::local_server::LocalServer;
|
||||
use flowy_net::ClientServerConfiguration;
|
||||
use flowy_sqlite::kv::KV;
|
||||
use flowy_task::{TaskDispatcher, TaskRunner};
|
||||
use flowy_user::entities::UserProfile;
|
||||
use flowy_user::event_map::UserStatusCallback;
|
||||
use flowy_user::services::{UserSession, UserSessionConfig};
|
||||
use lib_dispatch::prelude::*;
|
||||
@ -31,7 +28,6 @@ use lib_dispatch::runtime::tokio_default_runtime;
|
||||
use lib_infra::future::{to_fut, Fut};
|
||||
use module::make_plugins;
|
||||
pub use module::*;
|
||||
use user_model::UserProfile;
|
||||
|
||||
use crate::deps_resolve::*;
|
||||
|
||||
@ -52,7 +48,6 @@ pub struct AppFlowyCoreConfig {
|
||||
storage_path: String,
|
||||
log_filter: String,
|
||||
server_config: ClientServerConfiguration,
|
||||
pub document: DocumentConfig,
|
||||
}
|
||||
|
||||
impl fmt::Debug for AppFlowyCoreConfig {
|
||||
@ -60,7 +55,6 @@ impl fmt::Debug for AppFlowyCoreConfig {
|
||||
f.debug_struct("AppFlowyCoreConfig")
|
||||
.field("storage_path", &self.storage_path)
|
||||
.field("server-config", &self.server_config)
|
||||
.field("document-config", &self.document)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@ -72,15 +66,9 @@ impl AppFlowyCoreConfig {
|
||||
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 log_filter(mut self, level: &str, with_crates: Vec<String>) -> Self {
|
||||
self.log_filter = create_log_filter(level.to_owned(), with_crates);
|
||||
self
|
||||
@ -94,26 +82,19 @@ fn create_log_filter(level: String, with_crates: Vec<String>) -> String {
|
||||
.map(|crate_name| format!("{}={}", crate_name, level))
|
||||
.collect::<Vec<String>>();
|
||||
filters.push(format!("flowy_core={}", level));
|
||||
filters.push(format!("flowy_folder={}", level));
|
||||
filters.push(format!("flowy_folder2={}", level));
|
||||
filters.push(format!("collab_folder={}", level));
|
||||
// filters.push(format!("collab_persistence={}", level));
|
||||
filters.push(format!("collab_database={}", level));
|
||||
filters.push(format!("collab_plugins={}", level));
|
||||
filters.push(format!("appflowy_integrate={}", level));
|
||||
filters.push(format!("collab={}", level));
|
||||
filters.push(format!("flowy_user={}", level));
|
||||
filters.push(format!("flowy_document={}", level));
|
||||
filters.push(format!("flowy_document2={}", level));
|
||||
filters.push(format!("flowy_database={}", level));
|
||||
filters.push(format!("flowy_database2={}", 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));
|
||||
|
||||
@ -134,13 +115,11 @@ pub struct AppFlowyCore {
|
||||
#[allow(dead_code)]
|
||||
pub config: AppFlowyCoreConfig,
|
||||
pub user_session: Arc<UserSession>,
|
||||
pub document_manager: Arc<DocumentManager>,
|
||||
pub document_manager2: Arc<DocumentManager2>,
|
||||
pub folder_manager: Arc<Folder2Manager>,
|
||||
// pub database_manager: Arc<DatabaseManager>,
|
||||
pub database_manager: Arc<DatabaseManager2>,
|
||||
pub event_dispatcher: Arc<AFPluginDispatcher>,
|
||||
pub ws_conn: Arc<FlowyWebSocketConnect>,
|
||||
pub local_server: Option<Arc<LocalServer>>,
|
||||
pub task_dispatcher: Arc<RwLock<TaskDispatcher>>,
|
||||
}
|
||||
@ -162,65 +141,44 @@ impl AppFlowyCore {
|
||||
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,
|
||||
database_manager,
|
||||
document_manager2,
|
||||
) = runtime.block_on(async {
|
||||
let user_session = mk_user_session(&config, &local_server, &config.server_config);
|
||||
let local_server = mk_local_server(&config.server_config);
|
||||
let (user_session, folder_manager, local_server, database_manager, document_manager2) = 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 database_manager2 = Database2DepsResolver::resolve(
|
||||
ws_conn.clone(),
|
||||
user_session.clone(),
|
||||
task_dispatcher.clone(),
|
||||
collab_builder.clone(),
|
||||
)
|
||||
.await;
|
||||
let database_manager2 = Database2DepsResolver::resolve(
|
||||
user_session.clone(),
|
||||
task_dispatcher.clone(),
|
||||
collab_builder.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let document_manager2 = Document2DepsResolver::resolve(
|
||||
user_session.clone(),
|
||||
&database_manager2,
|
||||
collab_builder.clone(),
|
||||
);
|
||||
let document_manager2 = Document2DepsResolver::resolve(
|
||||
user_session.clone(),
|
||||
&database_manager2,
|
||||
collab_builder.clone(),
|
||||
);
|
||||
|
||||
let folder_manager = Folder2DepsResolver::resolve(
|
||||
user_session.clone(),
|
||||
&document_manager2,
|
||||
&database_manager2,
|
||||
collab_builder.clone(),
|
||||
)
|
||||
.await;
|
||||
let folder_manager = Folder2DepsResolver::resolve(
|
||||
user_session.clone(),
|
||||
&document_manager2,
|
||||
&database_manager2,
|
||||
collab_builder.clone(),
|
||||
)
|
||||
.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,
|
||||
database_manager2,
|
||||
document_manager2,
|
||||
)
|
||||
});
|
||||
(
|
||||
user_session,
|
||||
folder_manager,
|
||||
local_server,
|
||||
database_manager2,
|
||||
document_manager2,
|
||||
)
|
||||
});
|
||||
|
||||
let user_status_listener = UserStatusListener {
|
||||
document_manager: document_manager.clone(),
|
||||
folder_manager: folder_manager.clone(),
|
||||
database_manager: database_manager.clone(),
|
||||
ws_conn: ws_conn.clone(),
|
||||
config: config.clone(),
|
||||
};
|
||||
let user_status_callback = UserStatusCallbackImpl {
|
||||
@ -233,25 +191,20 @@ impl AppFlowyCore {
|
||||
|
||||
let event_dispatcher = Arc::new(AFPluginDispatcher::construct(runtime, || {
|
||||
make_plugins(
|
||||
&ws_conn,
|
||||
&folder_manager,
|
||||
&database_manager,
|
||||
&user_session,
|
||||
&document_manager,
|
||||
&document_manager2,
|
||||
)
|
||||
}));
|
||||
_start_listening(&event_dispatcher, &ws_conn, &folder_manager);
|
||||
|
||||
Self {
|
||||
config,
|
||||
user_session,
|
||||
document_manager,
|
||||
document_manager2,
|
||||
folder_manager,
|
||||
database_manager,
|
||||
event_dispatcher,
|
||||
ws_conn,
|
||||
local_server,
|
||||
task_dispatcher,
|
||||
}
|
||||
@ -262,43 +215,15 @@ impl AppFlowyCore {
|
||||
}
|
||||
}
|
||||
|
||||
fn _start_listening(
|
||||
event_dispatcher: &AFPluginDispatcher,
|
||||
ws_conn: &Arc<FlowyWebSocketConnect>,
|
||||
folder_manager: &Arc<Folder2Manager>,
|
||||
) {
|
||||
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_network_status(subscribe_network_type).await;
|
||||
});
|
||||
}
|
||||
|
||||
fn mk_local_server(
|
||||
server_config: &ClientServerConfiguration,
|
||||
) -> (Option<Arc<LocalServer>>, Arc<FlowyWebSocketConnect>) {
|
||||
let ws_addr = server_config.ws_addr();
|
||||
fn mk_local_server(server_config: &ClientServerConfiguration) -> Option<Arc<LocalServer>> {
|
||||
// let ws_addr = server_config.ws_addr();
|
||||
if cfg!(feature = "http_sync") {
|
||||
let ws_conn = Arc::new(FlowyWebSocketConnect::new(ws_addr));
|
||||
(None, ws_conn)
|
||||
// let ws_conn = Arc::new(FlowyWebSocketConnect::new(ws_addr));
|
||||
None
|
||||
} 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<NetworkType>) {
|
||||
while let Ok(_new_type) = subscribe.recv().await {
|
||||
// core.network_state_changed(new_type);
|
||||
// let ws_conn = Arc::new(FlowyWebSocketConnect::from_local(ws_addr, local_ws));
|
||||
Some(Arc::new(context.local_server))
|
||||
}
|
||||
}
|
||||
|
||||
@ -347,10 +272,8 @@ fn mk_user_session(
|
||||
}
|
||||
|
||||
struct UserStatusListener {
|
||||
document_manager: Arc<DocumentManager>,
|
||||
folder_manager: Arc<Folder2Manager>,
|
||||
database_manager: Arc<DatabaseManager2>,
|
||||
ws_conn: Arc<FlowyWebSocketConnect>,
|
||||
#[allow(dead_code)]
|
||||
config: AppFlowyCoreConfig,
|
||||
}
|
||||
@ -358,12 +281,11 @@ struct UserStatusListener {
|
||||
impl UserStatusListener {
|
||||
async fn did_sign_in(&self, token: &str, user_id: i64) -> FlowyResult<()> {
|
||||
self.folder_manager.initialize(user_id).await?;
|
||||
self.document_manager.initialize(user_id).await?;
|
||||
self.database_manager.initialize(user_id, token).await?;
|
||||
self
|
||||
.ws_conn
|
||||
.start(token.to_owned(), user_id.to_owned())
|
||||
.await?;
|
||||
// self
|
||||
// .ws_conn
|
||||
// .start(token.to_owned(), user_id.to_owned())
|
||||
// .await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -372,26 +294,17 @@ impl UserStatusListener {
|
||||
.folder_manager
|
||||
.initialize_with_new_user(user_profile.id, &user_profile.token)
|
||||
.await?;
|
||||
self
|
||||
.document_manager
|
||||
.initialize_with_new_user(user_profile.id, &user_profile.token)
|
||||
.await?;
|
||||
|
||||
self
|
||||
.database_manager
|
||||
.initialize_with_new_user(user_profile.id, &user_profile.token)
|
||||
.await?;
|
||||
|
||||
self
|
||||
.ws_conn
|
||||
.start(user_profile.token.clone(), user_profile.id)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn did_expired(&self, _token: &str, user_id: i64) -> FlowyResult<()> {
|
||||
self.folder_manager.clear(user_id).await;
|
||||
self.ws_conn.stop().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,27 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use flowy_client_ws::FlowyWebSocketConnect;
|
||||
use flowy_database2::DatabaseManager2;
|
||||
use flowy_document::DocumentManager;
|
||||
use flowy_document2::manager::DocumentManager as DocumentManager2;
|
||||
use flowy_folder2::manager::Folder2Manager;
|
||||
use flowy_user::services::UserSession;
|
||||
use lib_dispatch::prelude::AFPlugin;
|
||||
|
||||
pub fn make_plugins(
|
||||
ws_conn: &Arc<FlowyWebSocketConnect>,
|
||||
folder_manager: &Arc<Folder2Manager>,
|
||||
database_manager: &Arc<DatabaseManager2>,
|
||||
user_session: &Arc<UserSession>,
|
||||
document_manager: &Arc<DocumentManager>,
|
||||
document_manager2: &Arc<DocumentManager2>,
|
||||
) -> Vec<AFPlugin> {
|
||||
let user_plugin = flowy_user::event_map::init(user_session.clone());
|
||||
let folder_plugin = flowy_folder2::event_map::init(folder_manager.clone());
|
||||
let network_plugin = flowy_net::event_map::init(ws_conn.clone());
|
||||
let network_plugin = flowy_net::event_map::init();
|
||||
let database_plugin = flowy_database2::event_map::init(database_manager.clone());
|
||||
let document_plugin = flowy_document::event_map::init(document_manager.clone());
|
||||
let document_plugin2 = flowy_document2::event_map::init(document_manager2.clone());
|
||||
vec![
|
||||
user_plugin,
|
||||
folder_plugin,
|
||||
network_plugin,
|
||||
database_plugin,
|
||||
document_plugin,
|
||||
document_plugin2,
|
||||
]
|
||||
}
|
||||
|
@ -1,63 +0,0 @@
|
||||
[package]
|
||||
name = "flowy-database"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
lib-dispatch = { path = "../lib-dispatch" }
|
||||
flowy-notification = { path = "../flowy-notification" }
|
||||
flowy-revision = { path = "../flowy-revision" }
|
||||
flowy-revision-persistence = { path = "../flowy-revision-persistence" }
|
||||
flowy-task= { path = "../flowy-task" }
|
||||
flowy-error = { path = "../flowy-error", features = ["adaptor_database", "adaptor_dispatch"]}
|
||||
flowy-derive = { path = "../flowy-derive" }
|
||||
lib-ot = { path = "../../../shared-lib/lib-ot" }
|
||||
lib-infra = { path = "../../../shared-lib/lib-infra" }
|
||||
database-model = { path = "../../../shared-lib/database-model" }
|
||||
flowy-client-sync = { path = "../flowy-client-sync"}
|
||||
revision-model = { path = "../../../shared-lib/revision-model" }
|
||||
flowy-sqlite = { path = "../flowy-sqlite", optional = true }
|
||||
anyhow = "1.0"
|
||||
|
||||
strum = "0.21"
|
||||
strum_macros = "0.21"
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
protobuf = {version = "2.28.0"}
|
||||
rust_decimal = "1.28.1"
|
||||
rusty-money = {version = "0.4.1", features = ["iso"]}
|
||||
lazy_static = "1.4.0"
|
||||
chrono = "0.4.23"
|
||||
nanoid = "0.4.0"
|
||||
bytes = { version = "1.4" }
|
||||
diesel = {version = "1.4.8", features = ["sqlite"]}
|
||||
dashmap = "5"
|
||||
tokio = { version = "1.26", features = ["sync"]}
|
||||
rayon = "1.6.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = {version = "1.0"}
|
||||
serde_repr = "0.1"
|
||||
indexmap = {version = "1.9.2", features = ["serde"]}
|
||||
fancy-regex = "0.10.0"
|
||||
regex = "1.7.1"
|
||||
url = { version = "2"}
|
||||
futures = "0.3.26"
|
||||
atomic_refcell = "0.1.9"
|
||||
crossbeam-utils = "0.8.15"
|
||||
async-stream = "0.3.4"
|
||||
parking_lot = "0.12.1"
|
||||
|
||||
[dev-dependencies]
|
||||
flowy-test = { path = "../flowy-test" }
|
||||
#flowy-database = { path = "", features = ["flowy_unit_test"]}
|
||||
|
||||
[build-dependencies]
|
||||
flowy-codegen = { path = "../flowy-codegen"}
|
||||
|
||||
[features]
|
||||
default = ["rev-sqlite"]
|
||||
rev-sqlite = ["flowy-sqlite"]
|
||||
dart = ["flowy-codegen/dart", "flowy-notification/dart"]
|
||||
ts = ["flowy-codegen/ts", "flowy-notification/ts"]
|
||||
flowy_unit_test = ["flowy-revision/flowy_unit_test"]
|
@ -1,8 +0,0 @@
|
||||
# Check out the FlowyConfig (located in flowy_toml.rs) for more details.
|
||||
proto_input = [
|
||||
"src/event_map.rs",
|
||||
"src/services/field/type_options",
|
||||
"src/entities",
|
||||
"src/notification.rs"
|
||||
]
|
||||
event_files = ["src/event_map.rs"]
|
@ -1,10 +0,0 @@
|
||||
fn main() {
|
||||
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 = "ts")]
|
||||
flowy_codegen::ts_event::gen(crate_name);
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use database_model::{CalendarLayout, CalendarLayoutSetting};
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default, ProtoBuf)]
|
||||
pub struct CalendarLayoutSettingsPB {
|
||||
#[pb(index = 1)]
|
||||
pub layout_field_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub layout_ty: CalendarLayoutPB,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub first_day_of_week: i32,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub show_weekends: bool,
|
||||
|
||||
#[pb(index = 5)]
|
||||
pub show_week_numbers: bool,
|
||||
}
|
||||
|
||||
impl std::convert::From<CalendarLayoutSettingsPB> for CalendarLayoutSetting {
|
||||
fn from(pb: CalendarLayoutSettingsPB) -> Self {
|
||||
CalendarLayoutSetting {
|
||||
layout_ty: pb.layout_ty.into(),
|
||||
first_day_of_week: pb.first_day_of_week,
|
||||
show_weekends: pb.show_weekends,
|
||||
show_week_numbers: pb.show_week_numbers,
|
||||
layout_field_id: pb.layout_field_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<CalendarLayoutSetting> for CalendarLayoutSettingsPB {
|
||||
fn from(params: CalendarLayoutSetting) -> Self {
|
||||
CalendarLayoutSettingsPB {
|
||||
layout_field_id: params.layout_field_id,
|
||||
layout_ty: params.layout_ty.into(),
|
||||
first_day_of_week: params.first_day_of_week,
|
||||
show_weekends: params.show_weekends,
|
||||
show_week_numbers: params.show_week_numbers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default, ProtoBuf_Enum)]
|
||||
#[repr(u8)]
|
||||
pub enum CalendarLayoutPB {
|
||||
#[default]
|
||||
MonthLayout = 0,
|
||||
WeekLayout = 1,
|
||||
DayLayout = 2,
|
||||
}
|
||||
|
||||
impl std::convert::From<CalendarLayoutPB> for CalendarLayout {
|
||||
fn from(pb: CalendarLayoutPB) -> Self {
|
||||
match pb {
|
||||
CalendarLayoutPB::MonthLayout => CalendarLayout::MonthLayout,
|
||||
CalendarLayoutPB::WeekLayout => CalendarLayout::WeekLayout,
|
||||
CalendarLayoutPB::DayLayout => CalendarLayout::DayLayout,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::convert::From<CalendarLayout> for CalendarLayoutPB {
|
||||
fn from(layout: CalendarLayout) -> Self {
|
||||
match layout {
|
||||
CalendarLayout::MonthLayout => CalendarLayoutPB::MonthLayout,
|
||||
CalendarLayout::WeekLayout => CalendarLayoutPB::WeekLayout,
|
||||
CalendarLayout::DayLayout => CalendarLayoutPB::DayLayout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct CalendarEventRequestPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
// Currently, requesting the events within the specified month
|
||||
// is not supported
|
||||
#[pb(index = 2)]
|
||||
pub month: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CalendarEventRequestParams {
|
||||
pub view_id: String,
|
||||
pub month: String,
|
||||
}
|
||||
|
||||
impl TryInto<CalendarEventRequestParams> for CalendarEventRequestPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<CalendarEventRequestParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
|
||||
Ok(CalendarEventRequestParams {
|
||||
view_id: view_id.0,
|
||||
month: self.month,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct CalendarEventPB {
|
||||
#[pb(index = 1)]
|
||||
pub row_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub date_field_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub title: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct RepeatedCalendarEventPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<CalendarEventPB>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct MoveCalendarEventPB {
|
||||
#[pb(index = 1)]
|
||||
pub row_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub timestamp: i64,
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::FieldType;
|
||||
use database_model::{CellRevision, RowChangeset};
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct CreateSelectOptionPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub option_name: String,
|
||||
}
|
||||
|
||||
pub struct CreateSelectOptionParams {
|
||||
pub field_id: String,
|
||||
pub view_id: String,
|
||||
pub option_name: String,
|
||||
}
|
||||
|
||||
impl TryInto<CreateSelectOptionParams> for CreateSelectOptionPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<CreateSelectOptionParams, Self::Error> {
|
||||
let option_name =
|
||||
NotEmptyStr::parse(self.option_name).map_err(|_| ErrorCode::SelectOptionNameIsEmpty)?;
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
|
||||
let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
|
||||
Ok(CreateSelectOptionParams {
|
||||
field_id: field_id.0,
|
||||
option_name: option_name.0,
|
||||
view_id: view_id.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct CellIdPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_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 view_id: String,
|
||||
pub field_id: String,
|
||||
pub row_id: String,
|
||||
}
|
||||
|
||||
impl TryInto<CellIdParams> for CellIdPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<CellIdParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_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 {
|
||||
view_id: view_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 = 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<u8>,
|
||||
|
||||
/// the field_type will be None if the field with field_id is not found
|
||||
#[pb(index = 4, one_of)]
|
||||
pub field_type: Option<FieldType>,
|
||||
}
|
||||
|
||||
impl CellPB {
|
||||
pub fn new(field_id: &str, row_id: &str, field_type: FieldType, data: Vec<u8>) -> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct RepeatedCellPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<CellPB>,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for RepeatedCellPB {
|
||||
type Target = Vec<CellPB>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.items
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for RepeatedCellPB {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.items
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<CellPB>> for RepeatedCellPB {
|
||||
fn from(items: Vec<CellPB>) -> Self {
|
||||
Self { items }
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct CellChangesetPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub row_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub type_cell_data: String,
|
||||
}
|
||||
|
||||
impl std::convert::From<CellChangesetPB> 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);
|
||||
|
||||
RowChangeset {
|
||||
row_id: changeset.row_id,
|
||||
height: None,
|
||||
visibility: None,
|
||||
cell_by_field_id,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,205 +0,0 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{DatabaseLayoutPB, FieldIdPB, RowPB};
|
||||
use flowy_derive::ProtoBuf;
|
||||
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 = 2)]
|
||||
pub fields: Vec<FieldIdPB>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub rows: Vec<RowPB>,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct CreateDatabasePayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, ProtoBuf, Default, Debug)]
|
||||
pub struct DatabaseViewIdPB {
|
||||
#[pb(index = 1)]
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl AsRef<str> for DatabaseViewIdPB {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct MoveFieldPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub from_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,
|
||||
}
|
||||
|
||||
impl TryInto<MoveFieldParams> for MoveFieldPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<MoveFieldParams, Self::Error> {
|
||||
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 = 2)]
|
||||
pub from_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,
|
||||
}
|
||||
|
||||
impl TryInto<MoveRowParams> for MoveRowPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<MoveRowParams, Self::Error> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct MoveGroupRowPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub from_row_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub to_group_id: String,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub to_row_id: Option<String>,
|
||||
}
|
||||
|
||||
pub struct MoveGroupRowParams {
|
||||
pub view_id: String,
|
||||
pub from_row_id: String,
|
||||
pub to_group_id: String,
|
||||
pub to_row_id: Option<String>,
|
||||
}
|
||||
|
||||
impl TryInto<MoveGroupRowParams> for MoveGroupRowPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<MoveGroupRowParams, Self::Error> {
|
||||
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,
|
||||
),
|
||||
};
|
||||
|
||||
Ok(MoveGroupRowParams {
|
||||
view_id: view_id.0,
|
||||
from_row_id: from_row_id.0,
|
||||
to_group_id: to_group_id.0,
|
||||
to_row_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct DatabaseDescriptionPB {
|
||||
#[pb(index = 1)]
|
||||
pub name: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub database_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct RepeatedDatabaseDescriptionPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<DatabaseDescriptionPB>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct DatabaseGroupIdPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub group_id: String,
|
||||
}
|
||||
|
||||
pub struct DatabaseGroupIdParams {
|
||||
pub view_id: String,
|
||||
pub group_id: String,
|
||||
}
|
||||
|
||||
impl TryInto<DatabaseGroupIdParams> for DatabaseGroupIdPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<DatabaseGroupIdParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
|
||||
let group_id = NotEmptyStr::parse(self.group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?;
|
||||
Ok(DatabaseGroupIdParams {
|
||||
view_id: view_id.0,
|
||||
group_id: group_id.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
#[derive(Clone, ProtoBuf, Default, Debug)]
|
||||
pub struct DatabaseLayoutIdPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub layout: DatabaseLayoutPB,
|
||||
}
|
@ -1,671 +0,0 @@
|
||||
use database_model::{FieldRevision, FieldTypeRevision};
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use serde_repr::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
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 = 2)]
|
||||
pub name: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub desc: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[pb(index = 5)]
|
||||
pub frozen: bool,
|
||||
|
||||
#[pb(index = 6)]
|
||||
pub visibility: bool,
|
||||
|
||||
#[pb(index = 7)]
|
||||
pub width: i32,
|
||||
|
||||
#[pb(index = 8)]
|
||||
pub is_primary: bool,
|
||||
}
|
||||
|
||||
impl std::convert::From<FieldRevision> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Arc<FieldRevision>> for FieldPB {
|
||||
fn from(field_rev: Arc<FieldRevision>) -> 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,
|
||||
}
|
||||
|
||||
impl std::convert::From<&str> for FieldIdPB {
|
||||
fn from(s: &str) -> Self {
|
||||
FieldIdPB {
|
||||
field_id: s.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<String> for FieldIdPB {
|
||||
fn from(s: String) -> Self {
|
||||
FieldIdPB { field_id: s }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&Arc<FieldRevision>> for FieldIdPB {
|
||||
fn from(field_rev: &Arc<FieldRevision>) -> Self {
|
||||
Self {
|
||||
field_id: field_rev.id.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct DatabaseFieldChangesetPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub inserted_fields: Vec<IndexFieldPB>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub deleted_fields: Vec<FieldIdPB>,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub updated_fields: Vec<FieldPB>,
|
||||
}
|
||||
|
||||
impl DatabaseFieldChangesetPB {
|
||||
pub fn insert(database_id: &str, inserted_fields: Vec<IndexFieldPB>) -> Self {
|
||||
Self {
|
||||
view_id: database_id.to_owned(),
|
||||
inserted_fields,
|
||||
deleted_fields: vec![],
|
||||
updated_fields: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(database_id: &str, deleted_fields: Vec<FieldIdPB>) -> Self {
|
||||
Self {
|
||||
view_id: database_id.to_string(),
|
||||
inserted_fields: vec![],
|
||||
deleted_fields,
|
||||
updated_fields: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(database_id: &str, updated_fields: Vec<FieldPB>) -> Self {
|
||||
Self {
|
||||
view_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 = 2)]
|
||||
pub index: i32,
|
||||
}
|
||||
|
||||
impl IndexFieldPB {
|
||||
pub fn from_field_rev(field_rev: &Arc<FieldRevision>, 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 view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub type_option_data: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CreateFieldParams {
|
||||
pub view_id: String,
|
||||
pub field_type: FieldType,
|
||||
pub type_option_data: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl TryInto<CreateFieldParams> for CreateFieldPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<CreateFieldParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
|
||||
Ok(CreateFieldParams {
|
||||
view_id: view_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 view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub create_if_not_exist: bool,
|
||||
}
|
||||
|
||||
pub struct EditFieldParams {
|
||||
pub view_id: String,
|
||||
pub field_id: String,
|
||||
pub field_type: FieldType,
|
||||
}
|
||||
|
||||
impl TryInto<EditFieldParams> for UpdateFieldTypePayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<EditFieldParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
|
||||
let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
|
||||
Ok(EditFieldParams {
|
||||
view_id: view_id.0,
|
||||
field_id: field_id.0,
|
||||
field_type: self.field_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct TypeOptionPathPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub field_type: FieldType,
|
||||
}
|
||||
|
||||
pub struct TypeOptionPathParams {
|
||||
pub view_id: String,
|
||||
pub field_id: String,
|
||||
pub field_type: FieldType,
|
||||
}
|
||||
|
||||
impl TryInto<TypeOptionPathParams> for TypeOptionPathPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<TypeOptionPathParams, Self::Error> {
|
||||
let database_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
|
||||
let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
|
||||
Ok(TypeOptionPathParams {
|
||||
view_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 view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field: FieldPB,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub type_option_data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Collection of the [FieldPB]
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct RepeatedFieldPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<FieldPB>,
|
||||
}
|
||||
impl std::ops::Deref for RepeatedFieldPB {
|
||||
type Target = Vec<FieldPB>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.items
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for RepeatedFieldPB {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.items
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<FieldPB>> for RepeatedFieldPB {
|
||||
fn from(items: Vec<FieldPB>) -> Self {
|
||||
Self { items }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct RepeatedFieldIdPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<FieldIdPB>,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for RepeatedFieldIdPB {
|
||||
type Target = Vec<FieldIdPB>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.items
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<FieldIdPB>> for RepeatedFieldIdPB {
|
||||
fn from(items: Vec<FieldIdPB>) -> Self {
|
||||
RepeatedFieldIdPB { items }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<String> for RepeatedFieldIdPB {
|
||||
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 view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_id: String,
|
||||
|
||||
/// Check out [TypeOptionPB] for more details.
|
||||
#[pb(index = 3)]
|
||||
pub type_option_data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TypeOptionChangesetParams {
|
||||
pub view_id: String,
|
||||
pub field_id: String,
|
||||
pub type_option_data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl TryInto<TypeOptionChangesetParams> for TypeOptionChangesetPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<TypeOptionChangesetParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
|
||||
let _ = NotEmptyStr::parse(self.field_id.clone()).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
|
||||
|
||||
Ok(TypeOptionChangesetParams {
|
||||
view_id: view_id.0,
|
||||
field_id: self.field_id,
|
||||
type_option_data: self.type_option_data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct GetFieldPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub field_ids: Option<RepeatedFieldIdPB>,
|
||||
}
|
||||
|
||||
pub struct GetFieldParams {
|
||||
pub view_id: String,
|
||||
pub field_ids: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl TryInto<GetFieldParams> for GetFieldPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<GetFieldParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
|
||||
let field_ids = self.field_ids.map(|repeated| {
|
||||
repeated
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|item| item.field_id)
|
||||
.collect::<Vec<String>>()
|
||||
});
|
||||
|
||||
Ok(GetFieldParams {
|
||||
view_id: view_id.0,
|
||||
field_ids,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// [FieldChangesetPB] is used to modify the corresponding field. It defines which properties of
|
||||
/// the field can be modified.
|
||||
///
|
||||
/// Pass in None if you don't want to modify a property
|
||||
/// Pass in Some(Value) if you want to modify a property
|
||||
///
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct FieldChangesetPB {
|
||||
#[pb(index = 1)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub name: Option<String>,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub desc: Option<String>,
|
||||
|
||||
#[pb(index = 5, one_of)]
|
||||
pub field_type: Option<FieldType>,
|
||||
|
||||
#[pb(index = 6, one_of)]
|
||||
pub frozen: Option<bool>,
|
||||
|
||||
#[pb(index = 7, one_of)]
|
||||
pub visibility: Option<bool>,
|
||||
|
||||
#[pb(index = 8, one_of)]
|
||||
pub width: Option<i32>,
|
||||
// #[pb(index = 9, one_of)]
|
||||
// pub type_option_data: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl TryInto<FieldChangesetParams> for FieldChangesetPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<FieldChangesetParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_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,
|
||||
view_id: view_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 view_id: String,
|
||||
|
||||
pub name: Option<String>,
|
||||
|
||||
pub desc: Option<String>,
|
||||
|
||||
pub field_type: Option<FieldTypeRevision>,
|
||||
|
||||
pub frozen: Option<bool>,
|
||||
|
||||
pub visibility: Option<bool>,
|
||||
|
||||
pub width: Option<i32>,
|
||||
// pub type_option_data: Option<Vec<u8>>,
|
||||
}
|
||||
/// 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.
|
||||
///
|
||||
/// You could check [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid#fieldtype)
|
||||
/// for more information.
|
||||
///
|
||||
/// 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,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum FieldType {
|
||||
RichText = 0,
|
||||
Number = 1,
|
||||
DateTime = 2,
|
||||
SingleSelect = 3,
|
||||
MultiSelect = 4,
|
||||
Checkbox = 5,
|
||||
URL = 6,
|
||||
Checklist = 7,
|
||||
}
|
||||
|
||||
pub const RICH_TEXT_FIELD: FieldType = FieldType::RichText;
|
||||
pub const NUMBER_FIELD: FieldType = FieldType::Number;
|
||||
pub const DATE_FIELD: FieldType = FieldType::DateTime;
|
||||
pub const SINGLE_SELECT_FIELD: FieldType = FieldType::SingleSelect;
|
||||
pub const MULTI_SELECT_FIELD: FieldType = FieldType::MultiSelect;
|
||||
pub const CHECKBOX_FIELD: FieldType = FieldType::Checkbox;
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<FieldType> for FieldType {
|
||||
fn as_ref(&self) -> &FieldType {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&FieldType> for FieldType {
|
||||
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 default_cell_width(&self) -> i32 {
|
||||
match self {
|
||||
FieldType::DateTime => 180,
|
||||
_ => 150,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_number(&self) -> bool {
|
||||
self == &NUMBER_FIELD
|
||||
}
|
||||
|
||||
pub fn is_text(&self) -> bool {
|
||||
self == &RICH_TEXT_FIELD
|
||||
}
|
||||
|
||||
pub fn is_checkbox(&self) -> bool {
|
||||
self == &CHECKBOX_FIELD
|
||||
}
|
||||
|
||||
pub fn is_date(&self) -> bool {
|
||||
self == &DATE_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_url(&self) -> bool {
|
||||
self == &URL_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 can_be_group(&self) -> bool {
|
||||
self.is_select_option() || self.is_checkbox() || self.is_url()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&FieldType> for FieldTypeRevision {
|
||||
fn from(ty: &FieldType) -> Self {
|
||||
ty.clone() as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<FieldType> for FieldTypeRevision {
|
||||
fn from(ty: FieldType) -> Self {
|
||||
ty as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&FieldTypeRevision> for FieldType {
|
||||
fn from(ty: &FieldTypeRevision) -> Self {
|
||||
FieldType::from(*ty)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<FieldTypeRevision> 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
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct DuplicateFieldPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub view_id: String,
|
||||
}
|
||||
|
||||
// #[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
// pub struct GridFieldIdentifierPayloadPB {
|
||||
// #[pb(index = 1)]
|
||||
// pub field_id: String,
|
||||
//
|
||||
// #[pb(index = 2)]
|
||||
// pub view_id: String,
|
||||
// }
|
||||
|
||||
impl TryInto<FieldIdParams> for DuplicateFieldPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<FieldIdParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
|
||||
let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
|
||||
Ok(FieldIdParams {
|
||||
view_id: view_id.0,
|
||||
field_id: field_id.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct DeleteFieldPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub view_id: String,
|
||||
}
|
||||
|
||||
impl TryInto<FieldIdParams> for DeleteFieldPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<FieldIdParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
|
||||
let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
|
||||
Ok(FieldIdParams {
|
||||
view_id: view_id.0,
|
||||
field_id: field_id.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FieldIdParams {
|
||||
pub field_id: String,
|
||||
pub view_id: String,
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
use crate::services::filter::FromFilterString;
|
||||
use database_model::FilterRevision;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct CheckboxFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: CheckboxFilterConditionPB,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
#[repr(u8)]
|
||||
pub enum CheckboxFilterConditionPB {
|
||||
IsChecked = 0,
|
||||
IsUnChecked = 1,
|
||||
}
|
||||
|
||||
impl std::convert::From<CheckboxFilterConditionPB> for u32 {
|
||||
fn from(value: CheckboxFilterConditionPB) -> Self {
|
||||
value as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for CheckboxFilterConditionPB {
|
||||
fn default() -> Self {
|
||||
CheckboxFilterConditionPB::IsChecked
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<u8> for CheckboxFilterConditionPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&FilterRevision> for CheckboxFilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
CheckboxFilterPB {
|
||||
condition: CheckboxFilterConditionPB::try_from(rev.condition)
|
||||
.unwrap_or(CheckboxFilterConditionPB::IsChecked),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
use crate::services::filter::FromFilterString;
|
||||
use database_model::FilterRevision;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct ChecklistFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: ChecklistFilterConditionPB,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
#[repr(u8)]
|
||||
pub enum ChecklistFilterConditionPB {
|
||||
IsComplete = 0,
|
||||
IsIncomplete = 1,
|
||||
}
|
||||
|
||||
impl std::convert::From<ChecklistFilterConditionPB> for u32 {
|
||||
fn from(value: ChecklistFilterConditionPB) -> Self {
|
||||
value as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for ChecklistFilterConditionPB {
|
||||
fn default() -> Self {
|
||||
ChecklistFilterConditionPB::IsIncomplete
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<u8> for ChecklistFilterConditionPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&FilterRevision> for ChecklistFilterPB {
|
||||
fn from(rev: &FilterRevision) -> Self {
|
||||
ChecklistFilterPB {
|
||||
condition: ChecklistFilterConditionPB::try_from(rev.condition)
|
||||
.unwrap_or(ChecklistFilterConditionPB::IsIncomplete),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
use crate::services::filter::FromFilterString;
|
||||
use database_model::FilterRevision;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct DateFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: DateFilterConditionPB,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub start: Option<i64>,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub end: Option<i64>,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub timestamp: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Default, Clone, Debug)]
|
||||
pub struct DateFilterContentPB {
|
||||
pub start: Option<i64>,
|
||||
pub end: Option<i64>,
|
||||
pub timestamp: Option<i64>,
|
||||
}
|
||||
|
||||
impl ToString for DateFilterContentPB {
|
||||
fn to_string(&self) -> String {
|
||||
serde_json::to_string(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for DateFilterContentPB {
|
||||
type Err = serde_json::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
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,
|
||||
}
|
||||
|
||||
impl std::convert::From<DateFilterConditionPB> for u32 {
|
||||
fn from(value: DateFilterConditionPB) -> Self {
|
||||
value as u32
|
||||
}
|
||||
}
|
||||
impl std::default::Default for DateFilterConditionPB {
|
||||
fn default() -> Self {
|
||||
DateFilterConditionPB::DateIs
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<u8> for DateFilterConditionPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
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()
|
||||
};
|
||||
|
||||
if let Ok(content) = DateFilterContentPB::from_str(&filter_rev.content) {
|
||||
filter.start = content.start;
|
||||
filter.end = content.end;
|
||||
filter.timestamp = content.timestamp;
|
||||
};
|
||||
|
||||
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()
|
||||
};
|
||||
|
||||
if let Ok(content) = DateFilterContentPB::from_str(&rev.content) {
|
||||
filter.start = content.start;
|
||||
filter.end = content.end;
|
||||
filter.timestamp = content.timestamp;
|
||||
};
|
||||
|
||||
filter
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
use crate::entities::FilterPB;
|
||||
use flowy_derive::ProtoBuf;
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct FilterChangesetNotificationPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub insert_filters: Vec<FilterPB>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub delete_filters: Vec<FilterPB>,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub update_filters: Vec<UpdatedFilter>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct UpdatedFilter {
|
||||
#[pb(index = 1)]
|
||||
pub filter_id: String,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub filter: Option<FilterPB>,
|
||||
}
|
||||
|
||||
impl FilterChangesetNotificationPB {
|
||||
pub fn from_insert(view_id: &str, filters: Vec<FilterPB>) -> 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<FilterPB>) -> 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<UpdatedFilter>) -> Self {
|
||||
Self {
|
||||
view_id: view_id.to_string(),
|
||||
insert_filters: Default::default(),
|
||||
delete_filters: Default::default(),
|
||||
update_filters: filters,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
mod checkbox_filter;
|
||||
mod checklist_filter;
|
||||
mod date_filter;
|
||||
mod filter_changeset;
|
||||
mod number_filter;
|
||||
mod select_option_filter;
|
||||
mod text_filter;
|
||||
mod util;
|
||||
|
||||
pub use checkbox_filter::*;
|
||||
pub use checklist_filter::*;
|
||||
pub use date_filter::*;
|
||||
pub use filter_changeset::*;
|
||||
pub use number_filter::*;
|
||||
pub use select_option_filter::*;
|
||||
pub use text_filter::*;
|
||||
pub use util::*;
|
@ -1,77 +0,0 @@
|
||||
use crate::services::filter::FromFilterString;
|
||||
use database_model::FilterRevision;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct NumberFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: NumberFilterConditionPB,
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
impl std::default::Default for NumberFilterConditionPB {
|
||||
fn default() -> Self {
|
||||
NumberFilterConditionPB::Equal
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<NumberFilterConditionPB> for u32 {
|
||||
fn from(value: NumberFilterConditionPB) -> Self {
|
||||
value as u32
|
||||
}
|
||||
}
|
||||
impl std::convert::TryFrom<u8> for NumberFilterConditionPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_from(n: u8) -> Result<Self, Self::Error> {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
use crate::services::field::SelectOptionIds;
|
||||
use crate::services::filter::FromFilterString;
|
||||
use database_model::FilterRevision;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct SelectOptionFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: SelectOptionConditionPB,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub option_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
#[repr(u8)]
|
||||
pub enum SelectOptionConditionPB {
|
||||
OptionIs = 0,
|
||||
OptionIsNot = 1,
|
||||
OptionIsEmpty = 2,
|
||||
OptionIsNotEmpty = 3,
|
||||
}
|
||||
|
||||
impl std::convert::From<SelectOptionConditionPB> for u32 {
|
||||
fn from(value: SelectOptionConditionPB) -> Self {
|
||||
value as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for SelectOptionConditionPB {
|
||||
fn default() -> Self {
|
||||
SelectOptionConditionPB::OptionIs
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<u8> for SelectOptionConditionPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
use crate::services::filter::FromFilterString;
|
||||
use database_model::FilterRevision;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct TextFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: TextFilterConditionPB,
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
impl std::convert::From<TextFilterConditionPB> for u32 {
|
||||
fn from(value: TextFilterConditionPB) -> Self {
|
||||
value as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for TextFilterConditionPB {
|
||||
fn default() -> Self {
|
||||
TextFilterConditionPB::Is
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<u8> for TextFilterConditionPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,234 +0,0 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{
|
||||
CheckboxFilterPB, ChecklistFilterPB, DateFilterContentPB, DateFilterPB, FieldType,
|
||||
NumberFilterPB, SelectOptionFilterPB, TextFilterPB,
|
||||
};
|
||||
use crate::services::field::SelectOptionIds;
|
||||
use crate::services::filter::FilterType;
|
||||
use bytes::Bytes;
|
||||
use database_model::{FieldRevision, FieldTypeRevision, FilterRevision};
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct FilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct RepeatedFilterPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<FilterPB>,
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<Arc<FilterRevision>>> for RepeatedFilterPB {
|
||||
fn from(revs: Vec<Arc<FilterRevision>>) -> Self {
|
||||
RepeatedFilterPB {
|
||||
items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<FilterPB>> for RepeatedFilterPB {
|
||||
fn from(items: Vec<FilterPB>) -> Self {
|
||||
Self { items }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct DeleteFilterPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub filter_id: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub view_id: String,
|
||||
}
|
||||
|
||||
impl TryInto<DeleteFilterParams> for DeleteFilterPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<DeleteFilterParams, Self::Error> {
|
||||
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_type = FilterType {
|
||||
field_id,
|
||||
field_type: self.field_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,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct AlterFilterPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub field_id: String,
|
||||
|
||||
#[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<String>,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub data: Vec<u8>,
|
||||
|
||||
#[pb(index = 5)]
|
||||
pub view_id: String,
|
||||
}
|
||||
|
||||
impl AlterFilterPayloadPB {
|
||||
#[allow(dead_code)]
|
||||
pub fn new<T: TryInto<Bytes, Error = ::protobuf::ProtobufError>>(
|
||||
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<AlterFilterParams> for AlterFilterPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<AlterFilterParams, Self::Error> {
|
||||
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();
|
||||
|
||||
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();
|
||||
},
|
||||
}
|
||||
|
||||
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<String>,
|
||||
pub field_type: FieldTypeRevision,
|
||||
pub condition: u8,
|
||||
pub content: String,
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
use database_model::{GroupRevision, SelectOptionGroupConfigurationRevision};
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct UrlGroupConfigurationPB {
|
||||
#[pb(index = 1)]
|
||||
hide_empty: bool,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct TextGroupConfigurationPB {
|
||||
#[pb(index = 1)]
|
||||
hide_empty: bool,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct SelectOptionGroupConfigurationPB {
|
||||
#[pb(index = 1)]
|
||||
hide_empty: bool,
|
||||
}
|
||||
|
||||
impl std::convert::From<SelectOptionGroupConfigurationRevision>
|
||||
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 = 2)]
|
||||
visible: bool,
|
||||
}
|
||||
|
||||
impl std::convert::From<GroupRevision> for GroupRecordPB {
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct DateGroupConfigurationPB {
|
||||
#[pb(index = 1)]
|
||||
pub condition: DateCondition,
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
impl std::default::Default for DateCondition {
|
||||
fn default() -> Self {
|
||||
DateCondition::Relative
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct CheckboxGroupConfigurationPB {
|
||||
#[pb(index = 1)]
|
||||
pub(crate) hide_empty: bool,
|
||||
}
|
@ -1,182 +0,0 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{FieldType, RowPB};
|
||||
use crate::services::group::Group;
|
||||
use database_model::{FieldTypeRevision, GroupConfigurationRevision};
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct GroupConfigurationPB {
|
||||
#[pb(index = 1)]
|
||||
pub 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct RepeatedGroupPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<GroupPB>,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for RepeatedGroupPB {
|
||||
type Target = Vec<GroupPB>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.items
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for RepeatedGroupPB {
|
||||
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 = 2)]
|
||||
pub group_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub desc: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub rows: Vec<RowPB>,
|
||||
|
||||
#[pb(index = 5)]
|
||||
pub is_default: bool,
|
||||
|
||||
#[pb(index = 6)]
|
||||
pub is_visible: bool,
|
||||
}
|
||||
|
||||
impl std::convert::From<Group> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct RepeatedGroupConfigurationPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<GroupConfigurationPB>,
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<GroupConfigurationPB>> for RepeatedGroupConfigurationPB {
|
||||
fn from(items: Vec<GroupConfigurationPB>) -> Self {
|
||||
Self { items }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<Arc<GroupConfigurationRevision>>> for RepeatedGroupConfigurationPB {
|
||||
fn from(revs: Vec<Arc<GroupConfigurationRevision>>) -> 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 = 2)]
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub view_id: String,
|
||||
}
|
||||
|
||||
impl TryInto<InsertGroupParams> for InsertGroupPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<InsertGroupParams, Self::Error> {
|
||||
let field_id = NotEmptyStr::parse(self.field_id)
|
||||
.map_err(|_| ErrorCode::FieldIdIsEmpty)?
|
||||
.0;
|
||||
|
||||
let view_id = NotEmptyStr::parse(self.view_id)
|
||||
.map_err(|_| ErrorCode::ViewIdIsInvalid)?
|
||||
.0;
|
||||
|
||||
Ok(InsertGroupParams {
|
||||
field_id,
|
||||
field_type_rev: self.field_type.into(),
|
||||
view_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InsertGroupParams {
|
||||
pub view_id: String,
|
||||
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 = 2)]
|
||||
pub group_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub view_id: String,
|
||||
}
|
||||
|
||||
impl TryInto<DeleteGroupParams> for DeleteGroupPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<DeleteGroupParams, Self::Error> {
|
||||
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;
|
||||
let view_id = NotEmptyStr::parse(self.view_id)
|
||||
.map_err(|_| ErrorCode::ViewIdIsInvalid)?
|
||||
.0;
|
||||
|
||||
Ok(DeleteGroupParams {
|
||||
field_id,
|
||||
field_type_rev: self.field_type.into(),
|
||||
group_id,
|
||||
view_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeleteGroupParams {
|
||||
pub view_id: String,
|
||||
pub field_id: String,
|
||||
pub group_id: String,
|
||||
pub field_type_rev: FieldTypeRevision,
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{GroupPB, InsertedRowPB, RowPB};
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct GroupRowsNotificationPB {
|
||||
#[pb(index = 1)]
|
||||
pub group_id: String,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub group_name: Option<String>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub inserted_rows: Vec<InsertedRowPB>,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub deleted_rows: Vec<String>,
|
||||
|
||||
#[pb(index = 5)]
|
||||
pub updated_rows: Vec<RowPB>,
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
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 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 insert(group_id: String, inserted_rows: Vec<InsertedRowPB>) -> Self {
|
||||
Self {
|
||||
group_id,
|
||||
inserted_rows,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(group_id: String, deleted_rows: Vec<String>) -> Self {
|
||||
Self {
|
||||
group_id,
|
||||
deleted_rows,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(group_id: String, updated_rows: Vec<RowPB>) -> Self {
|
||||
Self {
|
||||
group_id,
|
||||
updated_rows,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct MoveGroupPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub from_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,
|
||||
}
|
||||
|
||||
impl TryInto<MoveGroupParams> for MoveGroupPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<MoveGroupParams, Self::Error> {
|
||||
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 = 2)]
|
||||
pub inserted_groups: Vec<InsertedGroupPB>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub initial_groups: Vec<GroupPB>,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub deleted_groups: Vec<String>,
|
||||
|
||||
#[pb(index = 5)]
|
||||
pub update_groups: Vec<GroupPB>,
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct InsertedGroupPB {
|
||||
#[pb(index = 1)]
|
||||
pub group: GroupPB,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub index: i32,
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
mod configuration;
|
||||
mod group;
|
||||
mod group_changeset;
|
||||
|
||||
pub use configuration::*;
|
||||
pub use group::*;
|
||||
pub use group_changeset::*;
|
@ -1,23 +0,0 @@
|
||||
mod calendar_entities;
|
||||
mod cell_entities;
|
||||
mod database_entities;
|
||||
mod field_entities;
|
||||
pub mod filter_entities;
|
||||
mod group_entities;
|
||||
pub mod parser;
|
||||
mod row_entities;
|
||||
pub mod setting_entities;
|
||||
mod sort_entities;
|
||||
mod view_entities;
|
||||
|
||||
pub use calendar_entities::*;
|
||||
pub use cell_entities::*;
|
||||
pub use database_entities::*;
|
||||
pub use database_entities::*;
|
||||
pub use field_entities::*;
|
||||
pub use filter_entities::*;
|
||||
pub use group_entities::*;
|
||||
pub use row_entities::*;
|
||||
pub use setting_entities::*;
|
||||
pub use sort_entities::*;
|
||||
pub use view_entities::*;
|
@ -1,17 +0,0 @@
|
||||
#[derive(Debug)]
|
||||
pub struct NotEmptyStr(pub String);
|
||||
|
||||
impl NotEmptyStr {
|
||||
pub fn parse(s: String) -> Result<Self, String> {
|
||||
if s.trim().is_empty() {
|
||||
return Err("Input string is empty".to_owned());
|
||||
}
|
||||
Ok(Self(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for NotEmptyStr {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
@ -1,224 +0,0 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
|
||||
use database_model::RowRevision;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
use std::collections::HashMap;
|
||||
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 = 2)]
|
||||
pub id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub height: i32,
|
||||
}
|
||||
|
||||
impl RowPB {
|
||||
pub fn row_id(&self) -> &str {
|
||||
&self.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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&Arc<RowRevision>> for RowPB {
|
||||
fn from(rev: &Arc<RowRevision>) -> 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<RowPB>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct RepeatedRowPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<RowPB>,
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<RowPB>> for RepeatedRowPB {
|
||||
fn from(items: Vec<RowPB>) -> Self {
|
||||
Self { items }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct InsertedRowPB {
|
||||
#[pb(index = 1)]
|
||||
pub row: RowPB,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub index: Option<i32>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub is_new: bool,
|
||||
}
|
||||
|
||||
impl InsertedRowPB {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<RowPB> for InsertedRowPB {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||
pub struct UpdatedRowPB {
|
||||
#[pb(index = 1)]
|
||||
pub row: RowPB,
|
||||
|
||||
// represents as the cells that were updated in this row.
|
||||
#[pb(index = 2)]
|
||||
pub field_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, ProtoBuf)]
|
||||
pub struct RowIdPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub row_id: String,
|
||||
}
|
||||
|
||||
pub struct RowIdParams {
|
||||
pub view_id: String,
|
||||
pub row_id: String,
|
||||
}
|
||||
|
||||
impl TryInto<RowIdParams> for RowIdPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<RowIdParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
|
||||
let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
|
||||
|
||||
Ok(RowIdParams {
|
||||
view_id: view_id.0,
|
||||
row_id: row_id.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, ProtoBuf)]
|
||||
pub struct BlockRowIdPB {
|
||||
#[pb(index = 1)]
|
||||
pub block_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub row_id: String,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct CreateRowPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub start_row_id: Option<String>,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub group_id: Option<String>,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub data: Option<RowDataPB>,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct RowDataPB {
|
||||
#[pb(index = 1)]
|
||||
pub cell_data_by_field_id: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CreateRowParams {
|
||||
pub view_id: String,
|
||||
pub start_row_id: Option<String>,
|
||||
pub group_id: Option<String>,
|
||||
pub cell_data_by_field_id: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl TryInto<CreateRowParams> for CreateRowPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<CreateRowParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
|
||||
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 {
|
||||
view_id: view_id.0,
|
||||
start_row_id,
|
||||
group_id: self.group_id,
|
||||
cell_data_by_field_id: self.data.map(|data| data.cell_data_by_field_id),
|
||||
})
|
||||
}
|
||||
}
|
@ -1,225 +0,0 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{
|
||||
AlterFilterParams, AlterFilterPayloadPB, AlterSortParams, AlterSortPayloadPB,
|
||||
CalendarLayoutSettingsPB, DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams,
|
||||
DeleteGroupPayloadPB, DeleteSortParams, DeleteSortPayloadPB, InsertGroupParams,
|
||||
InsertGroupPayloadPB, RepeatedFilterPB, RepeatedGroupConfigurationPB, RepeatedSortPB,
|
||||
};
|
||||
use database_model::{CalendarLayoutSetting, LayoutRevision};
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use std::convert::TryInto;
|
||||
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 current_layout: DatabaseLayoutPB,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub layout_setting: LayoutSettingPB,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub filters: RepeatedFilterPB,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub group_configurations: RepeatedGroupConfigurationPB,
|
||||
|
||||
#[pb(index = 5)]
|
||||
pub sorts: RepeatedSortPB,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum, EnumIter)]
|
||||
#[repr(u8)]
|
||||
pub enum DatabaseLayoutPB {
|
||||
Grid = 0,
|
||||
Board = 1,
|
||||
Calendar = 2,
|
||||
}
|
||||
|
||||
impl std::default::Default for DatabaseLayoutPB {
|
||||
fn default() -> Self {
|
||||
DatabaseLayoutPB::Grid
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<LayoutRevision> for DatabaseLayoutPB {
|
||||
fn from(rev: LayoutRevision) -> Self {
|
||||
match rev {
|
||||
LayoutRevision::Grid => DatabaseLayoutPB::Grid,
|
||||
LayoutRevision::Board => DatabaseLayoutPB::Board,
|
||||
LayoutRevision::Calendar => DatabaseLayoutPB::Calendar,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<DatabaseLayoutPB> for LayoutRevision {
|
||||
fn from(layout: DatabaseLayoutPB) -> Self {
|
||||
match layout {
|
||||
DatabaseLayoutPB::Grid => LayoutRevision::Grid,
|
||||
DatabaseLayoutPB::Board => LayoutRevision::Board,
|
||||
DatabaseLayoutPB::Calendar => LayoutRevision::Calendar,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct DatabaseSettingChangesetPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub layout_type: DatabaseLayoutPB,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub alter_filter: Option<AlterFilterPayloadPB>,
|
||||
|
||||
#[pb(index = 4, one_of)]
|
||||
pub delete_filter: Option<DeleteFilterPayloadPB>,
|
||||
|
||||
#[pb(index = 5, one_of)]
|
||||
pub insert_group: Option<InsertGroupPayloadPB>,
|
||||
|
||||
#[pb(index = 6, one_of)]
|
||||
pub delete_group: Option<DeleteGroupPayloadPB>,
|
||||
|
||||
#[pb(index = 7, one_of)]
|
||||
pub alter_sort: Option<AlterSortPayloadPB>,
|
||||
|
||||
#[pb(index = 8, one_of)]
|
||||
pub delete_sort: Option<DeleteSortPayloadPB>,
|
||||
}
|
||||
|
||||
impl TryInto<DatabaseSettingChangesetParams> for DatabaseSettingChangesetPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<DatabaseSettingChangesetParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id)
|
||||
.map_err(|_| ErrorCode::ViewIdIsInvalid)?
|
||||
.0;
|
||||
|
||||
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 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 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()?),
|
||||
};
|
||||
|
||||
Ok(DatabaseSettingChangesetParams {
|
||||
view_id,
|
||||
layout_type: self.layout_type.into(),
|
||||
insert_filter,
|
||||
delete_filter,
|
||||
insert_group,
|
||||
delete_group,
|
||||
alert_sort,
|
||||
delete_sort,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DatabaseSettingChangesetParams {
|
||||
pub view_id: String,
|
||||
pub layout_type: LayoutRevision,
|
||||
pub insert_filter: Option<AlterFilterParams>,
|
||||
pub delete_filter: Option<DeleteFilterParams>,
|
||||
pub insert_group: Option<InsertGroupParams>,
|
||||
pub delete_group: Option<DeleteGroupParams>,
|
||||
pub alert_sort: Option<AlterSortParams>,
|
||||
pub delete_sort: Option<DeleteSortParams>,
|
||||
}
|
||||
|
||||
impl DatabaseSettingChangesetParams {
|
||||
pub fn is_filter_changed(&self) -> bool {
|
||||
self.insert_filter.is_some() || self.delete_filter.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Default, ProtoBuf, Clone)]
|
||||
pub struct UpdateLayoutSettingPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub layout_setting: LayoutSettingPB,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UpdateLayoutSettingParams {
|
||||
pub view_id: String,
|
||||
pub layout_setting: LayoutSettingParams,
|
||||
}
|
||||
|
||||
impl TryInto<UpdateLayoutSettingParams> for UpdateLayoutSettingPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<UpdateLayoutSettingParams, Self::Error> {
|
||||
let view_id = NotEmptyStr::parse(self.view_id)
|
||||
.map_err(|_| ErrorCode::ViewIdIsInvalid)?
|
||||
.0;
|
||||
|
||||
let layout_setting: LayoutSettingParams = self.layout_setting.into();
|
||||
|
||||
Ok(UpdateLayoutSettingParams {
|
||||
view_id,
|
||||
layout_setting,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Default, ProtoBuf, Clone)]
|
||||
pub struct LayoutSettingPB {
|
||||
#[pb(index = 1, one_of)]
|
||||
pub calendar: Option<CalendarLayoutSettingsPB>,
|
||||
}
|
||||
|
||||
impl LayoutSettingPB {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<LayoutSettingParams> for LayoutSettingPB {
|
||||
fn from(params: LayoutSettingParams) -> Self {
|
||||
Self {
|
||||
calendar: params.calendar.map(|calender| calender.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<LayoutSettingPB> for LayoutSettingParams {
|
||||
fn from(params: LayoutSettingPB) -> Self {
|
||||
Self {
|
||||
calendar: params.calendar.map(|calender| calender.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct LayoutSettingParams {
|
||||
pub calendar: Option<CalendarLayoutSetting>,
|
||||
}
|
@ -1,239 +0,0 @@
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::sort::SortType;
|
||||
use std::sync::Arc;
|
||||
|
||||
use database_model::{FieldTypeRevision, SortCondition, SortRevision};
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct SortPB {
|
||||
#[pb(index = 1)]
|
||||
pub id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct RepeatedSortPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<SortPB>,
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<Arc<SortRevision>>> for RepeatedSortPB {
|
||||
fn from(revs: Vec<Arc<SortRevision>>) -> Self {
|
||||
RepeatedSortPB {
|
||||
items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Vec<SortPB>> for RepeatedSortPB {
|
||||
fn from(items: Vec<SortPB>) -> Self {
|
||||
Self { items }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
|
||||
#[repr(u8)]
|
||||
pub enum SortConditionPB {
|
||||
Ascending = 0,
|
||||
Descending = 1,
|
||||
}
|
||||
impl std::default::Default for SortConditionPB {
|
||||
fn default() -> Self {
|
||||
Self::Ascending
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<SortCondition> for SortConditionPB {
|
||||
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 = 2)]
|
||||
pub field_id: String,
|
||||
|
||||
#[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<String>,
|
||||
|
||||
#[pb(index = 5)]
|
||||
pub condition: SortConditionPB,
|
||||
}
|
||||
|
||||
impl TryInto<AlterSortParams> for AlterSortPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<AlterSortParams, Self::Error> {
|
||||
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 = 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
pub field_type: FieldTypeRevision,
|
||||
pub condition: u8,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct DeleteSortPayloadPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub field_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub field_type: FieldType,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub sort_id: String,
|
||||
}
|
||||
|
||||
impl TryInto<DeleteSortParams> for DeleteSortPayloadPB {
|
||||
type Error = ErrorCode;
|
||||
|
||||
fn try_into(self) -> Result<DeleteSortParams, Self::Error> {
|
||||
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_type = SortType {
|
||||
field_id,
|
||||
field_type: self.field_type,
|
||||
};
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct SortChangesetNotificationPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub insert_sorts: Vec<SortPB>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub delete_sorts: Vec<SortPB>,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub update_sorts: Vec<SortPB>,
|
||||
}
|
||||
|
||||
impl SortChangesetNotificationPB {
|
||||
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 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<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ProtoBuf)]
|
||||
pub struct ReorderSingleRowPB {
|
||||
#[pb(index = 1)]
|
||||
pub row_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub old_index: i32,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub new_index: i32,
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
use crate::entities::{InsertedRowPB, UpdatedRowPB};
|
||||
use flowy_derive::ProtoBuf;
|
||||
|
||||
#[derive(Debug, Default, Clone, ProtoBuf)]
|
||||
pub struct RowsVisibilityChangesetPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 5)]
|
||||
pub visible_rows: Vec<InsertedRowPB>,
|
||||
|
||||
#[pb(index = 6)]
|
||||
pub invisible_rows: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, ProtoBuf)]
|
||||
pub struct RowsChangesetPB {
|
||||
#[pb(index = 1)]
|
||||
pub view_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub inserted_rows: Vec<InsertedRowPB>,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub deleted_rows: Vec<String>,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub updated_rows: Vec<UpdatedRowPB>,
|
||||
}
|
||||
|
||||
impl RowsChangesetPB {
|
||||
pub fn from_insert(view_id: String, inserted_rows: Vec<InsertedRowPB>) -> Self {
|
||||
Self {
|
||||
view_id,
|
||||
inserted_rows,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_delete(view_id: String, deleted_rows: Vec<String>) -> Self {
|
||||
Self {
|
||||
view_id,
|
||||
deleted_rows,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_update(view_id: String, updated_rows: Vec<UpdatedRowPB>) -> Self {
|
||||
Self {
|
||||
view_id,
|
||||
updated_rows,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_move(
|
||||
view_id: String,
|
||||
deleted_rows: Vec<String>,
|
||||
inserted_rows: Vec<InsertedRowPB>,
|
||||
) -> Self {
|
||||
Self {
|
||||
view_id,
|
||||
inserted_rows,
|
||||
deleted_rows,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,646 +0,0 @@
|
||||
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,
|
||||
};
|
||||
use crate::services::row::make_row_from_row_rev;
|
||||
use database_model::FieldRevision;
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn get_database_data_handler(
|
||||
data: AFPluginData<DatabaseViewIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<DatabasePB, FlowyError> {
|
||||
let view_id: DatabaseViewIdPB = data.into_inner();
|
||||
let editor = manager.open_database_view(view_id.as_ref()).await?;
|
||||
let database = editor.get_database(view_id.as_ref()).await?;
|
||||
data_result_ok(database)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn get_database_setting_handler(
|
||||
data: AFPluginData<DatabaseViewIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<DatabaseViewSettingPB, FlowyError> {
|
||||
let view_id: DatabaseViewIdPB = data.into_inner();
|
||||
let editor = manager.open_database_view(view_id.as_ref()).await?;
|
||||
let database_setting = editor.get_setting(view_id.as_ref()).await?;
|
||||
data_result_ok(database_setting)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn update_database_setting_handler(
|
||||
data: AFPluginData<DatabaseSettingChangesetPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let params: DatabaseSettingChangesetParams = data.into_inner().try_into()?;
|
||||
|
||||
let editor = manager.get_database_editor(¶ms.view_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(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(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<DatabaseViewIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<RepeatedFilterPB, FlowyError> {
|
||||
let view_id: DatabaseViewIdPB = data.into_inner();
|
||||
let editor = manager.open_database_view(view_id.as_ref()).await?;
|
||||
let filters = RepeatedFilterPB {
|
||||
items: editor.get_all_filters(view_id.as_ref()).await?,
|
||||
};
|
||||
data_result_ok(filters)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn get_all_sorts_handler(
|
||||
data: AFPluginData<DatabaseViewIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<RepeatedSortPB, FlowyError> {
|
||||
let view_id: DatabaseViewIdPB = data.into_inner();
|
||||
let editor = manager.open_database_view(view_id.as_ref()).await?;
|
||||
let sorts = RepeatedSortPB {
|
||||
items: editor.get_all_sorts(view_id.as_ref()).await?,
|
||||
};
|
||||
data_result_ok(sorts)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn delete_all_sorts_handler(
|
||||
data: AFPluginData<DatabaseViewIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let view_id: DatabaseViewIdPB = data.into_inner();
|
||||
let editor = manager.open_database_view(view_id.as_ref()).await?;
|
||||
editor.delete_all_sorts(view_id.as_ref()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn get_fields_handler(
|
||||
data: AFPluginData<GetFieldPayloadPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<RepeatedFieldPB, FlowyError> {
|
||||
let params: GetFieldParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_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::<Vec<_>>()
|
||||
.into();
|
||||
data_result_ok(repeated_field)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn update_field_handler(
|
||||
data: AFPluginData<FieldChangesetPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let changeset: FieldChangesetParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(&changeset.view_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<TypeOptionChangesetPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let params: TypeOptionChangesetParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_id).await?;
|
||||
let old_field_rev = editor.get_field_rev(¶ms.field_id).await;
|
||||
editor
|
||||
.update_field_type_option(
|
||||
¶ms.view_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<DeleteFieldPayloadPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let params: FieldIdParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_id).await?;
|
||||
editor.delete_field(¶ms.field_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn switch_to_field_handler(
|
||||
data: AFPluginData<UpdateFieldTypePayloadPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let params: EditFieldParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_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?));
|
||||
|
||||
// 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.view_id,
|
||||
&new_field_rev.id,
|
||||
type_option_data,
|
||||
old_field_rev,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn duplicate_field_handler(
|
||||
data: AFPluginData<DuplicateFieldPayloadPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let params: FieldIdParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_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<TypeOptionPathPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<TypeOptionPB, FlowyError> {
|
||||
let params: TypeOptionPathParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_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 {
|
||||
view_id: params.view_id,
|
||||
field: field_rev.into(),
|
||||
type_option_data,
|
||||
};
|
||||
data_result_ok(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<CreateFieldPayloadPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<TypeOptionPB, FlowyError> {
|
||||
let params: CreateFieldParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_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_ok(TypeOptionPB {
|
||||
view_id: params.view_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<MoveFieldPayloadPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> 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(())
|
||||
}
|
||||
|
||||
/// 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<Vec<u8>> {
|
||||
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)
|
||||
}
|
||||
|
||||
// #[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn get_row_handler(
|
||||
data: AFPluginData<RowIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<OptionalRowPB, FlowyError> {
|
||||
let params: RowIdParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_id).await?;
|
||||
let row = editor
|
||||
.get_row_rev(¶ms.row_id)
|
||||
.await?
|
||||
.map(make_row_from_row_rev);
|
||||
|
||||
data_result_ok(OptionalRowPB { row })
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn delete_row_handler(
|
||||
data: AFPluginData<RowIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let params: RowIdParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_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<RowIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let params: RowIdParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_id).await?;
|
||||
editor
|
||||
.duplicate_row(¶ms.view_id, ¶ms.row_id)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn move_row_handler(
|
||||
data: AFPluginData<MoveRowPayloadPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> 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(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn create_row_handler(
|
||||
data: AFPluginData<CreateRowPayloadPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<RowPB, FlowyError> {
|
||||
let params: CreateRowParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(params.view_id.as_ref()).await?;
|
||||
let row = editor.create_row(params).await?;
|
||||
data_result_ok(row)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub(crate) async fn get_cell_handler(
|
||||
data: AFPluginData<CellIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<CellPB, FlowyError> {
|
||||
let params: CellIdParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_id).await?;
|
||||
match editor.get_cell(¶ms).await {
|
||||
None => data_result_ok(CellPB::empty(¶ms.field_id, ¶ms.row_id)),
|
||||
Some(cell) => data_result_ok(cell),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub(crate) async fn update_cell_handler(
|
||||
data: AFPluginData<CellChangesetPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let changeset: CellChangesetPB = data.into_inner();
|
||||
let editor = manager.get_database_editor(&changeset.view_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<CreateSelectOptionPayloadPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<SelectOptionPB, FlowyError> {
|
||||
let params: CreateSelectOptionParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_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_ok(select_option)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub(crate) async fn update_select_option_handler(
|
||||
data: AFPluginData<SelectOptionChangesetPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let changeset: SelectOptionChangeset = data.into_inner().try_into()?;
|
||||
let editor = manager
|
||||
.get_database_editor(&changeset.cell_path.view_id)
|
||||
.await?;
|
||||
let field_id = changeset.cell_path.field_id.clone();
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
editor
|
||||
.modify_field_rev(&changeset.cell_path.view_id, &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.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(());
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(data, manager), err)]
|
||||
pub(crate) async fn get_select_option_handler(
|
||||
data: AFPluginData<CellIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<SelectOptionCellDataPB, FlowyError> {
|
||||
let params: CellIdParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_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_ok(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_ok(selected_options)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub(crate) async fn update_select_option_cell_handler(
|
||||
data: AFPluginData<SelectOptionCellChangesetPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let params: SelectOptionCellChangesetParams = data.into_inner().try_into()?;
|
||||
let editor = manager
|
||||
.get_database_editor(¶ms.cell_identifier.view_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(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub(crate) async fn update_date_cell_handler(
|
||||
data: AFPluginData<DateChangesetPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> 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,
|
||||
include_time: data.include_time,
|
||||
is_utc: data.is_utc,
|
||||
};
|
||||
|
||||
let editor = manager.get_database_editor(&cell_path.view_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<DatabaseViewIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<RepeatedGroupPB, FlowyError> {
|
||||
let params: DatabaseViewIdPB = data.into_inner();
|
||||
let editor = manager.get_database_editor(¶ms.value).await?;
|
||||
let groups = editor.load_groups(¶ms.value).await?;
|
||||
data_result_ok(groups)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub(crate) async fn get_group_handler(
|
||||
data: AFPluginData<DatabaseGroupIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<GroupPB, FlowyError> {
|
||||
let params: DatabaseGroupIdParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_id).await?;
|
||||
let group = editor.get_group(¶ms.view_id, ¶ms.group_id).await?;
|
||||
data_result_ok(group)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn move_group_handler(
|
||||
data: AFPluginData<MoveGroupPayloadPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> 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(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn move_group_row_handler(
|
||||
data: AFPluginData<MoveGroupRowPayloadPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> 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(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(manager), err)]
|
||||
pub(crate) async fn get_databases_handler(
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<RepeatedDatabaseDescriptionPB, FlowyError> {
|
||||
let items = manager
|
||||
.get_databases()
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|database_info| DatabaseDescriptionPB {
|
||||
name: database_info.name,
|
||||
database_id: database_info.database_id,
|
||||
})
|
||||
.collect::<Vec<DatabaseDescriptionPB>>();
|
||||
data_result_ok(RepeatedDatabaseDescriptionPB { items })
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn set_layout_setting_handler(
|
||||
data: AFPluginData<UpdateLayoutSettingPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> FlowyResult<()> {
|
||||
let params: UpdateLayoutSettingParams = data.into_inner().try_into()?;
|
||||
let database_editor = manager.get_database_editor(params.view_id.as_ref()).await?;
|
||||
database_editor
|
||||
.set_layout_setting(¶ms.view_id, params.layout_setting)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn get_layout_setting_handler(
|
||||
data: AFPluginData<DatabaseLayoutIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<LayoutSettingPB, FlowyError> {
|
||||
let params = data.into_inner();
|
||||
let database_editor = manager.get_database_editor(¶ms.view_id).await?;
|
||||
let layout_setting = database_editor
|
||||
.get_layout_setting(¶ms.view_id, params.layout)
|
||||
.await?;
|
||||
data_result_ok(layout_setting.into())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn get_calendar_events_handler(
|
||||
data: AFPluginData<CalendarEventRequestPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<RepeatedCalendarEventPB, FlowyError> {
|
||||
let params: CalendarEventRequestParams = data.into_inner().try_into()?;
|
||||
let database_editor = manager.get_database_editor(¶ms.view_id).await?;
|
||||
let events = database_editor
|
||||
.get_all_calendar_events(¶ms.view_id)
|
||||
.await;
|
||||
data_result_ok(RepeatedCalendarEventPB { items: events })
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
pub(crate) async fn get_calendar_event_handler(
|
||||
data: AFPluginData<RowIdPB>,
|
||||
manager: AFPluginState<Arc<DatabaseManager>>,
|
||||
) -> DataResult<CalendarEventPB, FlowyError> {
|
||||
let params: RowIdParams = data.into_inner().try_into()?;
|
||||
let database_editor = manager.get_database_editor(¶ms.view_id).await?;
|
||||
let event = database_editor
|
||||
.get_calendar_event(¶ms.view_id, ¶ms.row_id)
|
||||
.await;
|
||||
match event {
|
||||
None => Err(FlowyError::record_not_found()),
|
||||
Some(event) => data_result_ok(event),
|
||||
}
|
||||
}
|
@ -1,259 +0,0 @@
|
||||
use crate::event_handler::*;
|
||||
use crate::manager::DatabaseManager;
|
||||
use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
|
||||
use lib_dispatch::prelude::*;
|
||||
use std::sync::Arc;
|
||||
use strum_macros::Display;
|
||||
|
||||
pub fn init(database_manager: Arc<DatabaseManager>) -> AFPlugin {
|
||||
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)
|
||||
.event(DatabaseEvent::UpdateDatabaseSetting, update_database_setting_handler)
|
||||
.event(DatabaseEvent::GetAllFilters, get_all_filters_handler)
|
||||
.event(DatabaseEvent::GetAllSorts, get_all_sorts_handler)
|
||||
.event(DatabaseEvent::DeleteAllSorts, delete_all_sorts_handler)
|
||||
// Field
|
||||
.event(DatabaseEvent::GetFields, get_fields_handler)
|
||||
.event(DatabaseEvent::UpdateField, update_field_handler)
|
||||
.event(DatabaseEvent::UpdateFieldTypeOption, update_field_type_option_handler)
|
||||
.event(DatabaseEvent::DeleteField, delete_field_handler)
|
||||
.event(DatabaseEvent::UpdateFieldType, switch_to_field_handler)
|
||||
.event(DatabaseEvent::DuplicateField, duplicate_field_handler)
|
||||
.event(DatabaseEvent::MoveField, move_field_handler)
|
||||
.event(DatabaseEvent::GetTypeOption, get_field_type_option_data_handler)
|
||||
.event(DatabaseEvent::CreateTypeOption, create_field_type_option_data_handler)
|
||||
// Row
|
||||
.event(DatabaseEvent::CreateRow, create_row_handler)
|
||||
.event(DatabaseEvent::GetRow, get_row_handler)
|
||||
.event(DatabaseEvent::DeleteRow, delete_row_handler)
|
||||
.event(DatabaseEvent::DuplicateRow, duplicate_row_handler)
|
||||
.event(DatabaseEvent::MoveRow, move_row_handler)
|
||||
// Cell
|
||||
.event(DatabaseEvent::GetCell, get_cell_handler)
|
||||
.event(DatabaseEvent::UpdateCell, update_cell_handler)
|
||||
// SelectOption
|
||||
.event(DatabaseEvent::CreateSelectOption, new_select_option_handler)
|
||||
.event(DatabaseEvent::UpdateSelectOption, update_select_option_handler)
|
||||
.event(DatabaseEvent::GetSelectOptionCellData, get_select_option_handler)
|
||||
.event(DatabaseEvent::UpdateSelectOptionCell, update_select_option_cell_handler)
|
||||
// Date
|
||||
.event(DatabaseEvent::UpdateDateCell, update_date_cell_handler)
|
||||
// Group
|
||||
.event(DatabaseEvent::MoveGroup, move_group_handler)
|
||||
.event(DatabaseEvent::MoveGroupRow, move_group_row_handler)
|
||||
.event(DatabaseEvent::GetGroups, get_groups_handler)
|
||||
.event(DatabaseEvent::GetGroup, get_group_handler)
|
||||
// Database
|
||||
.event(DatabaseEvent::GetDatabases, get_databases_handler)
|
||||
// Calendar
|
||||
.event(DatabaseEvent::GetAllCalendarEvents, get_calendar_events_handler)
|
||||
.event(DatabaseEvent::GetCalendarEvent, get_calendar_event_handler)
|
||||
// Layout setting
|
||||
.event(DatabaseEvent::SetLayoutSetting, set_layout_setting_handler)
|
||||
.event(DatabaseEvent::GetLayoutSetting, get_layout_setting_handler);
|
||||
|
||||
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)
|
||||
/// out, it includes how to use these annotations: input, output, etc.
|
||||
#[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 [DatabaseViewIdPB] and returns a [DatabasePB] if there are no errors.
|
||||
#[event(input = "DatabaseViewIdPB", output = "DatabasePB")]
|
||||
GetDatabase = 0,
|
||||
|
||||
/// [GetDatabaseSetting] event is used to get the database's settings.
|
||||
///
|
||||
/// The event handler accepts [DatabaseViewIdPB] and return [DatabaseViewSettingPB]
|
||||
/// if there is no errors.
|
||||
#[event(input = "DatabaseViewIdPB", 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,
|
||||
|
||||
#[event(input = "DatabaseViewIdPB", output = "RepeatedFilterPB")]
|
||||
GetAllFilters = 4,
|
||||
|
||||
#[event(input = "DatabaseViewIdPB", output = "RepeatedSortPB")]
|
||||
GetAllSorts = 5,
|
||||
|
||||
#[event(input = "DatabaseViewIdPB")]
|
||||
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,
|
||||
|
||||
/// [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,
|
||||
|
||||
/// [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,
|
||||
|
||||
/// [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,
|
||||
|
||||
/// [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,
|
||||
|
||||
/// [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,
|
||||
|
||||
/// [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,
|
||||
|
||||
/// [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")]
|
||||
DuplicateRow = 53,
|
||||
|
||||
#[event(input = "MoveRowPayloadPB")]
|
||||
MoveRow = 54,
|
||||
|
||||
#[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,
|
||||
|
||||
/// [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,
|
||||
|
||||
#[event(input = "DatabaseViewIdPB", output = "RepeatedGroupPB")]
|
||||
GetGroups = 100,
|
||||
|
||||
#[event(input = "DatabaseGroupIdPB", output = "GroupPB")]
|
||||
GetGroup = 101,
|
||||
|
||||
#[event(input = "MoveGroupPayloadPB")]
|
||||
MoveGroup = 111,
|
||||
|
||||
#[event(input = "MoveGroupRowPayloadPB")]
|
||||
MoveGroupRow = 112,
|
||||
|
||||
#[event(input = "MoveGroupRowPayloadPB")]
|
||||
GroupByField = 113,
|
||||
|
||||
/// Returns all the databases
|
||||
#[event(output = "RepeatedDatabaseDescriptionPB")]
|
||||
GetDatabases = 114,
|
||||
|
||||
#[event(input = "UpdateLayoutSettingPB")]
|
||||
SetLayoutSetting = 115,
|
||||
|
||||
#[event(input = "DatabaseLayoutIdPB", output = "LayoutSettingPB")]
|
||||
GetLayoutSetting = 116,
|
||||
|
||||
#[event(input = "CalendarEventRequestPB", output = "RepeatedCalendarEventPB")]
|
||||
GetAllCalendarEvents = 117,
|
||||
|
||||
#[event(input = "RowIdPB", output = "CalendarEventPB")]
|
||||
GetCalendarEvent = 118,
|
||||
|
||||
#[event(input = "MoveCalendarEventPB")]
|
||||
MoveCalendarEvent = 119,
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
extern crate core;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod event_handler;
|
||||
pub mod event_map;
|
||||
pub mod manager;
|
||||
|
||||
pub mod entities;
|
||||
mod notification;
|
||||
mod protobuf;
|
||||
pub mod services;
|
||||
pub mod util;
|
@ -1,92 +0,0 @@
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&std::sync::Arc<FieldRevision>> for $target {
|
||||
fn from(field_rev: &std::sync::Arc<FieldRevision>) -> $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 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()
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -1,438 +0,0 @@
|
||||
use crate::entities::DatabaseLayoutPB;
|
||||
use crate::services::database::{
|
||||
make_database_block_rev_manager, DatabaseEditor, DatabaseRefIndexerQuery,
|
||||
DatabaseRevisionCloudService, DatabaseRevisionMergeable, DatabaseRevisionSerde,
|
||||
};
|
||||
use crate::services::database_view::{
|
||||
make_database_view_rev_manager, make_database_view_revision_pad, DatabaseViewEditor,
|
||||
};
|
||||
use crate::services::persistence::block_index::BlockRowIndexer;
|
||||
use crate::services::persistence::database_ref::{DatabaseInfo, DatabaseRefs, DatabaseViewRef};
|
||||
use crate::services::persistence::kv::DatabaseKVPersistence;
|
||||
use crate::services::persistence::migration::DatabaseMigration;
|
||||
use crate::services::persistence::rev_sqlite::{
|
||||
SQLiteDatabaseRevisionPersistence, SQLiteDatabaseRevisionSnapshotPersistence,
|
||||
};
|
||||
use crate::services::persistence::DatabaseDBConnection;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use database_model::{
|
||||
gen_database_id, BuildDatabaseContext, DatabaseRevision, DatabaseViewRevision,
|
||||
};
|
||||
use flowy_client_sync::client_database::{
|
||||
make_database_block_operations, make_database_operations, make_database_view_operations,
|
||||
};
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_revision::{
|
||||
RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, RevisionWebSocket,
|
||||
};
|
||||
use flowy_sqlite::ConnectionPool;
|
||||
use flowy_task::TaskDispatcher;
|
||||
|
||||
use lib_infra::future::Fut;
|
||||
use revision_model::Revision;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub trait DatabaseUser: Send + Sync {
|
||||
fn user_id(&self) -> Result<i64, FlowyError>;
|
||||
fn token(&self) -> Result<String, FlowyError>;
|
||||
fn db_pool(&self) -> Result<Arc<ConnectionPool>, FlowyError>;
|
||||
}
|
||||
|
||||
pub struct DatabaseManager {
|
||||
editors_by_database_id: RwLock<HashMap<String, Arc<DatabaseEditor>>>,
|
||||
database_user: Arc<dyn DatabaseUser>,
|
||||
block_indexer: Arc<BlockRowIndexer>,
|
||||
database_refs: Arc<DatabaseRefs>,
|
||||
#[allow(dead_code)]
|
||||
kv_persistence: Arc<DatabaseKVPersistence>,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
#[allow(dead_code)]
|
||||
migration: DatabaseMigration,
|
||||
}
|
||||
|
||||
impl DatabaseManager {
|
||||
pub fn new(
|
||||
database_user: Arc<dyn DatabaseUser>,
|
||||
_rev_web_socket: Arc<dyn RevisionWebSocket>,
|
||||
task_scheduler: Arc<RwLock<TaskDispatcher>>,
|
||||
database_db: Arc<dyn DatabaseDBConnection>,
|
||||
) -> Self {
|
||||
let editors_by_database_id = RwLock::new(HashMap::new());
|
||||
let kv_persistence = Arc::new(DatabaseKVPersistence::new(database_db.clone()));
|
||||
let block_indexer = Arc::new(BlockRowIndexer::new(database_db.clone()));
|
||||
let database_refs = Arc::new(DatabaseRefs::new(database_db));
|
||||
let migration = DatabaseMigration::new(database_user.clone(), database_refs.clone());
|
||||
Self {
|
||||
editors_by_database_id,
|
||||
database_user,
|
||||
kv_persistence,
|
||||
block_indexer,
|
||||
database_refs,
|
||||
task_scheduler,
|
||||
migration,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn initialize_with_new_user(&self, _user_id: i64, _token: &str) -> FlowyResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn initialize(
|
||||
&self,
|
||||
user_id: i64,
|
||||
_token: &str,
|
||||
get_views_fn: Fut<Vec<(String, String, DatabaseLayoutPB)>>,
|
||||
) -> FlowyResult<()> {
|
||||
self.migration.run(user_id, get_views_fn).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub async fn create_database<T: AsRef<str>>(
|
||||
&self,
|
||||
database_id: &str,
|
||||
view_id: T,
|
||||
name: &str,
|
||||
revisions: Vec<Revision>,
|
||||
) -> FlowyResult<()> {
|
||||
let db_pool = self.database_user.db_pool()?;
|
||||
let _ = self
|
||||
.database_refs
|
||||
.bind(database_id, view_id.as_ref(), true, name);
|
||||
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<T: AsRef<str>>(
|
||||
&self,
|
||||
view_id: T,
|
||||
revisions: Vec<Revision>,
|
||||
) -> FlowyResult<()> {
|
||||
let view_id = view_id.as_ref();
|
||||
let pool = self.database_user.db_pool()?;
|
||||
let rev_manager = make_database_view_rev_manager(pool, view_id).await?;
|
||||
rev_manager.reset_object(revisions).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_database_block<T: AsRef<str>>(
|
||||
&self,
|
||||
block_id: T,
|
||||
revisions: Vec<Revision>,
|
||||
) -> 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(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, err)]
|
||||
pub async fn open_database_view<T: AsRef<str>>(
|
||||
&self,
|
||||
view_id: T,
|
||||
) -> FlowyResult<Arc<DatabaseEditor>> {
|
||||
let view_id = view_id.as_ref();
|
||||
let database_info = self.database_refs.get_database_with_view(view_id)?;
|
||||
self
|
||||
.get_or_create_database_editor(&database_info.database_id, view_id)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn close_database_view<T: AsRef<str>>(&self, view_id: T) -> FlowyResult<()> {
|
||||
let view_id = view_id.as_ref();
|
||||
let database_info = self.database_refs.get_database_with_view(view_id)?;
|
||||
tracing::Span::current().record("database_id", &database_info.database_id);
|
||||
|
||||
// Create a temporary reference database_editor in case of holding the write lock
|
||||
// of editors_by_database_id too long.
|
||||
let database_editor = self
|
||||
.editors_by_database_id
|
||||
.write()
|
||||
.await
|
||||
.remove(&database_info.database_id);
|
||||
|
||||
if let Some(database_editor) = database_editor {
|
||||
database_editor.close_view_editor(view_id).await;
|
||||
if database_editor.number_of_ref_views().await == 0 {
|
||||
database_editor.dispose().await;
|
||||
} else {
|
||||
self
|
||||
.editors_by_database_id
|
||||
.write()
|
||||
.await
|
||||
.insert(database_info.database_id, database_editor);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// #[tracing::instrument(level = "debug", skip(self), err)]
|
||||
pub async fn get_database_editor(&self, view_id: &str) -> FlowyResult<Arc<DatabaseEditor>> {
|
||||
let database_info = self.database_refs.get_database_with_view(view_id)?;
|
||||
let database_editor = self
|
||||
.editors_by_database_id
|
||||
.read()
|
||||
.await
|
||||
.get(&database_info.database_id)
|
||||
.cloned();
|
||||
match database_editor {
|
||||
None => {
|
||||
// Drop the read_guard ASAP in case of the following read/write lock
|
||||
self.open_database_view(view_id).await
|
||||
},
|
||||
Some(editor) => Ok(editor),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_databases(&self) -> FlowyResult<Vec<DatabaseInfo>> {
|
||||
self.database_refs.get_all_databases()
|
||||
}
|
||||
|
||||
pub async fn get_database_ref_views(
|
||||
&self,
|
||||
database_id: &str,
|
||||
) -> FlowyResult<Vec<DatabaseViewRef>> {
|
||||
self.database_refs.get_ref_views_with_database(database_id)
|
||||
}
|
||||
|
||||
async fn get_or_create_database_editor(
|
||||
&self,
|
||||
database_id: &str,
|
||||
view_id: &str,
|
||||
) -> FlowyResult<Arc<DatabaseEditor>> {
|
||||
let user = self.database_user.clone();
|
||||
let create_view_editor = |database_editor: Arc<DatabaseEditor>| async move {
|
||||
let (view_pad, view_rev_manager) = make_database_view_revision_pad(view_id, user).await?;
|
||||
DatabaseViewEditor::from_pad(
|
||||
database_editor.database_view_data.clone(),
|
||||
database_editor.cell_data_cache.clone(),
|
||||
view_rev_manager,
|
||||
view_pad,
|
||||
)
|
||||
.await
|
||||
};
|
||||
|
||||
let database_editor = self
|
||||
.editors_by_database_id
|
||||
.read()
|
||||
.await
|
||||
.get(database_id)
|
||||
.cloned();
|
||||
|
||||
match database_editor {
|
||||
None => {
|
||||
let mut editors_by_database_id = self.editors_by_database_id.write().await;
|
||||
let db_pool = self.database_user.db_pool()?;
|
||||
let database_editor = self.make_database_rev_editor(view_id, db_pool).await?;
|
||||
editors_by_database_id.insert(database_id.to_string(), database_editor.clone());
|
||||
Ok(database_editor)
|
||||
},
|
||||
Some(database_editor) => {
|
||||
let is_open = database_editor.is_view_open(view_id).await;
|
||||
if !is_open {
|
||||
let database_view_editor = create_view_editor(database_editor.clone()).await?;
|
||||
database_editor.open_view_editor(database_view_editor).await;
|
||||
}
|
||||
|
||||
Ok(database_editor)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self, pool), err)]
|
||||
async fn make_database_rev_editor(
|
||||
&self,
|
||||
view_id: &str,
|
||||
pool: Arc<ConnectionPool>,
|
||||
) -> Result<Arc<DatabaseEditor>, FlowyError> {
|
||||
let user = self.database_user.clone();
|
||||
let (base_view_pad, base_view_rev_manager) =
|
||||
make_database_view_revision_pad(view_id, user.clone()).await?;
|
||||
let mut database_id = base_view_pad.database_id.clone();
|
||||
tracing::debug!("Open database: {} with view: {}", database_id, view_id);
|
||||
if database_id.is_empty() {
|
||||
// Before the database_id concept comes up, we used the view_id directly. So if
|
||||
// the database_id is empty, which means we can used the view_id. After the version 0.1.1,
|
||||
// we start to used the database_id that enables binding different views to the same database.
|
||||
database_id = view_id.to_owned();
|
||||
}
|
||||
|
||||
let token = user.token()?;
|
||||
let cloud = Arc::new(DatabaseRevisionCloudService::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::<DatabaseRevisionSerde>(Some(cloud))
|
||||
.await?,
|
||||
));
|
||||
let database_editor = DatabaseEditor::new(
|
||||
&database_id,
|
||||
user,
|
||||
database_pad,
|
||||
rev_manager,
|
||||
self.block_indexer.clone(),
|
||||
self.database_refs.clone(),
|
||||
self.task_scheduler.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let base_view_editor = DatabaseViewEditor::from_pad(
|
||||
database_editor.database_view_data.clone(),
|
||||
database_editor.cell_data_cache.clone(),
|
||||
base_view_rev_manager,
|
||||
base_view_pad,
|
||||
)
|
||||
.await?;
|
||||
database_editor.open_view_editor(base_view_editor).await;
|
||||
|
||||
Ok(database_editor)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self, pool), err)]
|
||||
pub fn make_database_rev_manager(
|
||||
&self,
|
||||
database_id: &str,
|
||||
pool: Arc<ConnectionPool>,
|
||||
) -> FlowyResult<RevisionManager<Arc<ConnectionPool>>> {
|
||||
// Create revision persistence
|
||||
let disk_cache = SQLiteDatabaseRevisionPersistence::new(pool.clone());
|
||||
let configuration = RevisionPersistenceConfiguration::new(6, false);
|
||||
let rev_persistence = RevisionPersistence::new(database_id, disk_cache, configuration);
|
||||
|
||||
// Create snapshot persistence
|
||||
const DATABASE_SP_PREFIX: &str = "grid";
|
||||
let snapshot_object_id = format!("{}:{}", DATABASE_SP_PREFIX, database_id);
|
||||
let snapshot_persistence =
|
||||
SQLiteDatabaseRevisionSnapshotPersistence::new(&snapshot_object_id, pool);
|
||||
|
||||
let rev_compress = DatabaseRevisionMergeable();
|
||||
let rev_manager = RevisionManager::new(
|
||||
database_id,
|
||||
rev_persistence,
|
||||
rev_compress,
|
||||
snapshot_persistence,
|
||||
);
|
||||
Ok(rev_manager)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn link_existing_database(
|
||||
view_id: &str,
|
||||
name: String,
|
||||
database_id: &str,
|
||||
layout: DatabaseLayoutPB,
|
||||
database_manager: Arc<DatabaseManager>,
|
||||
) -> FlowyResult<()> {
|
||||
tracing::trace!(
|
||||
"Link database view: {} with database: {}",
|
||||
view_id,
|
||||
database_id
|
||||
);
|
||||
let database_view_rev = DatabaseViewRevision::new(
|
||||
database_id.to_string(),
|
||||
view_id.to_owned(),
|
||||
false,
|
||||
name.clone(),
|
||||
layout.into(),
|
||||
);
|
||||
let database_view_ops = make_database_view_operations(&database_view_rev);
|
||||
let database_view_bytes = database_view_ops.json_bytes();
|
||||
let revision = Revision::initial_revision(view_id, database_view_bytes);
|
||||
database_manager
|
||||
.create_database_view(view_id, vec![revision])
|
||||
.await?;
|
||||
|
||||
let _ = database_manager
|
||||
.database_refs
|
||||
.bind(database_id, view_id, false, &name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_new_database(
|
||||
view_id: &str,
|
||||
name: String,
|
||||
layout: DatabaseLayoutPB,
|
||||
database_manager: Arc<DatabaseManager>,
|
||||
build_context: BuildDatabaseContext,
|
||||
) -> FlowyResult<()> {
|
||||
let BuildDatabaseContext {
|
||||
field_revs,
|
||||
block_metas,
|
||||
blocks,
|
||||
database_view_data,
|
||||
layout_setting,
|
||||
} = 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_indexer
|
||||
.insert(&row.block_id, &row.id);
|
||||
});
|
||||
|
||||
// Create database's block
|
||||
let database_block_ops = make_database_block_operations(block_meta_data);
|
||||
let database_block_bytes = database_block_ops.json_bytes();
|
||||
let revision = Revision::initial_revision(block_id, database_block_bytes);
|
||||
database_manager
|
||||
.create_database_block(&block_id, vec![revision])
|
||||
.await?;
|
||||
}
|
||||
|
||||
let database_id = gen_database_id();
|
||||
let database_rev = DatabaseRevision::from_build_context(&database_id, field_revs, block_metas);
|
||||
|
||||
// Create database
|
||||
tracing::trace!("Create new database: {}", database_id);
|
||||
let database_ops = make_database_operations(&database_rev);
|
||||
let database_bytes = database_ops.json_bytes();
|
||||
let revision = Revision::initial_revision(&database_id, database_bytes);
|
||||
database_manager
|
||||
.create_database(&database_id, &view_id, &name, vec![revision])
|
||||
.await?;
|
||||
|
||||
// Create database view
|
||||
tracing::trace!("Create new database view: {}", view_id);
|
||||
let database_view = if database_view_data.is_empty() {
|
||||
let mut database_view =
|
||||
DatabaseViewRevision::new(database_id, view_id.to_owned(), true, name, layout.into());
|
||||
database_view.layout_settings = layout_setting;
|
||||
database_view
|
||||
} else {
|
||||
let mut database_view = DatabaseViewRevision::from_json(database_view_data)?;
|
||||
database_view.database_id = database_id;
|
||||
// Replace the view id with the new one. This logic will be removed in the future.
|
||||
database_view.view_id = view_id.to_owned();
|
||||
database_view
|
||||
};
|
||||
|
||||
let database_view_ops = make_database_view_operations(&database_view);
|
||||
let database_view_bytes = database_view_ops.json_bytes();
|
||||
let revision = Revision::initial_revision(view_id, database_view_bytes);
|
||||
database_manager
|
||||
.create_database_view(view_id, vec![revision])
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl DatabaseRefIndexerQuery for DatabaseRefs {
|
||||
fn get_ref_views(&self, database_id: &str) -> FlowyResult<Vec<DatabaseViewRef>> {
|
||||
self.get_ref_views_with_database(database_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseRefIndexerQuery for Arc<DatabaseRefs> {
|
||||
fn get_ref_views(&self, database_id: &str) -> FlowyResult<Vec<DatabaseViewRef>> {
|
||||
(**self).get_ref_views(database_id)
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
use flowy_derive::ProtoBuf_Enum;
|
||||
use flowy_notification::NotificationBuilder;
|
||||
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,
|
||||
// Trigger when the layout setting of the database is updated
|
||||
DidUpdateLayoutSettings = 80,
|
||||
// Trigger when the layout field of the database is changed
|
||||
DidSetNewLayoutField = 81,
|
||||
}
|
||||
|
||||
impl std::default::Default for DatabaseNotification {
|
||||
fn default() -> Self {
|
||||
DatabaseNotification::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<DatabaseNotification> for 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)
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
use parking_lot::RwLock;
|
||||
use std::any::{type_name, Any};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::services::filter::FilterType;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type AtomicCellDataCache = Arc<RwLock<AnyTypeCache<u64>>>;
|
||||
pub type AtomicCellFilterCache = Arc<RwLock<AnyTypeCache<FilterType>>>;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct AnyTypeCache<TypeValueKey>(HashMap<TypeValueKey, TypeValue>);
|
||||
|
||||
impl<TypeValueKey> AnyTypeCache<TypeValueKey>
|
||||
where
|
||||
TypeValueKey: Clone + Hash + Eq,
|
||||
{
|
||||
pub fn new() -> Arc<RwLock<AnyTypeCache<TypeValueKey>>> {
|
||||
Arc::new(RwLock::new(AnyTypeCache(HashMap::default())))
|
||||
}
|
||||
|
||||
pub fn insert<T>(&mut self, key: &TypeValueKey, val: T) -> Option<T>
|
||||
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<T, K: AsRef<TypeValueKey>>(&mut self, key: K) -> Option<T>
|
||||
// where
|
||||
// T: 'static + Send + Sync,
|
||||
// {
|
||||
// self.0.remove(key.as_ref()).and_then(downcast_owned)
|
||||
// }
|
||||
|
||||
pub fn get<T>(&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<T>(&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 is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn downcast_owned<T: 'static + Send + Sync>(type_value: TypeValue) -> Option<T> {
|
||||
type_value.boxed.downcast().ok().map(|boxed| *boxed)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TypeValue {
|
||||
boxed: Box<dyn Any + Send + Sync + 'static>,
|
||||
#[allow(dead_code)]
|
||||
ty: &'static str,
|
||||
}
|
||||
|
||||
impl TypeValue {
|
||||
pub fn new<T>(value: T) -> Self
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
Self {
|
||||
boxed: Box::new(value),
|
||||
ty: type_name::<T>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for TypeValue {
|
||||
type Target = Box<dyn Any + Send + Sync + 'static>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.boxed
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for TypeValue {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.boxed
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use crate::services::cell::CellDataCache;
|
||||
//
|
||||
// #[test]
|
||||
// fn test() {
|
||||
// let mut ext = CellDataCache::new();
|
||||
// ext.insert("1", "a".to_string());
|
||||
// ext.insert("2", 2);
|
||||
//
|
||||
// let a: &String = ext.get("1").unwrap();
|
||||
// assert_eq!(a, "a");
|
||||
//
|
||||
// let a: Option<&usize> = ext.get("1");
|
||||
// assert!(a.is_none());
|
||||
// }
|
||||
// }
|
@ -1,346 +0,0 @@
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::{AtomicCellDataCache, CellProtobufBlob, TypeCellData};
|
||||
use crate::services::field::*;
|
||||
|
||||
use crate::services::group::make_no_status_group;
|
||||
use database_model::{CellRevision, FieldRevision};
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
|
||||
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<<Self as TypeOption>::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: <Self as TypeOption>::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: <Self as TypeOption>::CellChangeset,
|
||||
type_cell_data: Option<TypeCellData>,
|
||||
) -> FlowyResult<(String, <Self as TypeOption>::CellData)>;
|
||||
}
|
||||
|
||||
/// changeset: It will be deserialized into specific data base on the FieldType.
|
||||
/// For example,
|
||||
/// FieldType::RichText => String
|
||||
/// FieldType::SingleSelect => SelectOptionChangeset
|
||||
///
|
||||
/// cell_rev: It will be None if the cell does not contain any data.
|
||||
pub fn apply_cell_data_changeset<C: ToCellChangesetString, T: AsRef<FieldRevision>>(
|
||||
changeset: C,
|
||||
cell_rev: Option<CellRevision>,
|
||||
field_rev: T,
|
||||
cell_data_cache: Option<AtomicCellDataCache>,
|
||||
) -> Result<String, FlowyError> {
|
||||
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 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<T: TryInto<TypeCellData, Error = FlowyError> + Debug>(
|
||||
data: T,
|
||||
field_rev: &FieldRevision,
|
||||
cell_data_cache: Option<AtomicCellDataCache>,
|
||||
) -> (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())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type_cell_data<CellData, Output>(
|
||||
data: CellData,
|
||||
field_rev: &FieldRevision,
|
||||
cell_data_cache: Option<AtomicCellDataCache>,
|
||||
) -> Option<Output>
|
||||
where
|
||||
CellData: TryInto<TypeCellData, Error = FlowyError> + 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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode the opaque cell data from one field type to another using the corresponding `TypeOption`
|
||||
///
|
||||
/// The cell data might become an empty string depends on the to_field_type's `TypeOption`
|
||||
/// support transform the from_field_type's cell data or not.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cell_str`: the opaque cell string that can be decoded by corresponding structs that implement the
|
||||
/// `FromCellString` trait.
|
||||
/// * `from_field_type`: the original field type of the passed-in cell data. Check the `TypeCellData`
|
||||
/// that is used to save the origin field type of the cell data.
|
||||
/// * `to_field_type`: decode the passed-in cell data to this field type. It will use the to_field_type's
|
||||
/// TypeOption to decode this cell data.
|
||||
/// * `field_rev`: used to get the corresponding TypeOption for the specified field type.
|
||||
///
|
||||
/// 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<AtomicCellDataCache>,
|
||||
) -> FlowyResult<CellProtobufBlob> {
|
||||
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<T: Default + 'static>(
|
||||
cell_str: String,
|
||||
from_field_type: &FieldType,
|
||||
to_field_type: &FieldType,
|
||||
field_rev: &FieldRevision,
|
||||
cell_data_cache: Option<AtomicCellDataCache>,
|
||||
) -> Option<T> {
|
||||
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::<T>()
|
||||
}
|
||||
/// 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
|
||||
/// separated by a comma.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cell_str`: the opaque cell string that can be decoded by corresponding structs that implement the
|
||||
/// `FromCellString` trait.
|
||||
/// * `decoded_field_type`: the field_type of the cell_str
|
||||
/// * `field_type`: use this field type's `TypeOption` to stringify this cell_str
|
||||
/// * `field_rev`: used to get the corresponding TypeOption for the specified field type.
|
||||
///
|
||||
/// returns: String
|
||||
pub fn stringify_cell_data(
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn insert_url_cell(url: String, field_rev: &FieldRevision) -> CellRevision {
|
||||
// checking if url is equal to group id of no status group because everywhere
|
||||
// except group of rows with empty url the group id is equal to the url
|
||||
// so then on the case that url is equal to empty url group id we should change
|
||||
// the url to empty string
|
||||
let _no_status_group_id = make_no_status_group(field_rev).id;
|
||||
let url = match url {
|
||||
a if a == _no_status_group_id => "".to_owned(),
|
||||
_ => url,
|
||||
};
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn insert_date_cell(date_cell_data: DateCellData, field_rev: &FieldRevision) -> CellRevision {
|
||||
let cell_data = serde_json::to_string(&DateCellChangeset {
|
||||
date: date_cell_data.timestamp.map(|t| t.to_string()),
|
||||
time: None,
|
||||
include_time: Some(date_cell_data.include_time),
|
||||
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<String>,
|
||||
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<String>,
|
||||
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<Self>
|
||||
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<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl FromCellChangesetString for String {
|
||||
fn from_changeset(changeset: String) -> FlowyResult<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Ok(changeset)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToCellChangesetString: Debug {
|
||||
fn to_cell_changeset_str(&self) -> String;
|
||||
}
|
||||
|
||||
impl ToCellChangesetString for String {
|
||||
fn to_cell_changeset_str(&self) -> String {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AnyCellChangeset<T>(pub Option<T>);
|
||||
|
||||
impl<T> AnyCellChangeset<T> {
|
||||
pub fn try_into_inner(self) -> FlowyResult<T> {
|
||||
match self.0 {
|
||||
None => Err(ErrorCode::InvalidData.into()),
|
||||
Some(data) => Ok(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C: ToString> std::convert::From<C> for AnyCellChangeset<T>
|
||||
where
|
||||
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)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
// impl std::convert::From<String> for AnyCellChangeset<String> {
|
||||
// fn from(s: String) -> Self {
|
||||
// AnyCellChangeset(Some(s))
|
||||
// }
|
||||
// }
|
@ -1,7 +0,0 @@
|
||||
mod cell_data_cache;
|
||||
mod cell_operation;
|
||||
mod type_cell_data;
|
||||
|
||||
pub use cell_data_cache::*;
|
||||
pub use cell_operation::*;
|
||||
pub use type_cell_data::*;
|
@ -1,209 +0,0 @@
|
||||
use crate::entities::FieldType;
|
||||
|
||||
use bytes::Bytes;
|
||||
use database_model::CellRevision;
|
||||
use flowy_error::{internal_error, FlowyError, FlowyResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// TypeCellData is a generic CellData, you can parse the type_cell_data according to the field_type.
|
||||
/// The `data` is encoded by JSON format. You can use `IntoCellData` to decode the opaque data to
|
||||
/// concrete cell type.
|
||||
/// TypeCellData -> IntoCellData<T> -> T
|
||||
///
|
||||
/// The `TypeCellData` is the same as the cell data that was saved to disk except it carries the
|
||||
/// field_type. The field_type indicates the cell data original `FieldType`. The field_type will
|
||||
/// be changed if the current Field's type switch from one to another.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct TypeCellData {
|
||||
#[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_json_str(s: &str) -> FlowyResult<Self> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<String> for TypeCellData {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
TypeCellData::from_json_str(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for TypeCellData {
|
||||
fn to_string(&self) -> String {
|
||||
self.cell_str.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<&CellRevision> for TypeCellData {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(value: &CellRevision) -> Result<Self, Self::Error> {
|
||||
Self::from_json_str(&value.type_cell_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<CellRevision> for TypeCellData {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(value: CellRevision) -> Result<Self, Self::Error> {
|
||||
Self::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeCellData {
|
||||
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 is_number(&self) -> bool {
|
||||
self.field_type == FieldType::Number
|
||||
}
|
||||
|
||||
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_date(&self) -> bool {
|
||||
self.field_type == FieldType::DateTime
|
||||
}
|
||||
|
||||
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_checklist(&self) -> bool {
|
||||
self.field_type == FieldType::Checklist
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// The data is encoded by protobuf or utf8. You should choose the corresponding decode struct to parse it.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// * Use DateCellDataPB to parse the data when the FieldType is Date.
|
||||
/// * Use URLCellDataPB to parse the data when the FieldType is URL.
|
||||
/// * Use String to parse the data when the FieldType is RichText, Number, or Checkbox.
|
||||
/// * Check out the implementation of CellDataOperation trait for more information.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct CellProtobufBlob(pub Bytes);
|
||||
|
||||
pub trait DecodedCellData {
|
||||
type Object;
|
||||
fn is_empty(&self) -> bool;
|
||||
}
|
||||
|
||||
pub trait CellProtobufBlobParser {
|
||||
type Object: DecodedCellData;
|
||||
fn parser(bytes: &Bytes) -> FlowyResult<Self::Object>;
|
||||
}
|
||||
|
||||
pub trait CellStringParser {
|
||||
type Object;
|
||||
fn parser_cell_str(&self, s: &str) -> Option<Self::Object>;
|
||||
}
|
||||
|
||||
pub trait CellBytesCustomParser {
|
||||
type Object;
|
||||
fn parse(&self, bytes: &Bytes) -> FlowyResult<Self::Object>;
|
||||
}
|
||||
|
||||
impl CellProtobufBlob {
|
||||
pub fn new<T: AsRef<[u8]>>(data: T) -> Self {
|
||||
let bytes = Bytes::from(data.as_ref().to_vec());
|
||||
Self(bytes)
|
||||
}
|
||||
|
||||
pub fn from<T: TryInto<Bytes>>(bytes: T) -> FlowyResult<Self>
|
||||
where
|
||||
<T as TryInto<Bytes>>::Error: std::fmt::Debug,
|
||||
{
|
||||
let bytes = bytes.try_into().map_err(internal_error)?;
|
||||
Ok(Self(bytes))
|
||||
}
|
||||
|
||||
pub fn parser<P>(&self) -> FlowyResult<P::Object>
|
||||
where
|
||||
P: CellProtobufBlobParser,
|
||||
{
|
||||
P::parser(&self.0)
|
||||
}
|
||||
|
||||
pub fn custom_parser<P>(&self, parser: P) -> FlowyResult<P::Object>
|
||||
where
|
||||
P: CellBytesCustomParser,
|
||||
{
|
||||
parser.parse(&self.0)
|
||||
}
|
||||
|
||||
// pub fn parse<'a, T: TryFrom<&'a [u8]>>(&'a self) -> FlowyResult<T>
|
||||
// where
|
||||
// <T as TryFrom<&'a [u8]>>::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()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for CellProtobufBlob {
|
||||
type Target = Bytes;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
@ -1,234 +0,0 @@
|
||||
use crate::services::database::retry::GetRowDataRetryAction;
|
||||
use bytes::Bytes;
|
||||
use database_model::{CellRevision, DatabaseBlockRevision, RowChangeset, RowRevision};
|
||||
use flowy_client_sync::client_database::{
|
||||
DatabaseBlockRevisionChangeset, DatabaseBlockRevisionPad,
|
||||
};
|
||||
use flowy_client_sync::make_operations_from_revisions;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_revision::{
|
||||
RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer,
|
||||
RevisionObjectSerializer,
|
||||
};
|
||||
use flowy_sqlite::ConnectionPool;
|
||||
use lib_infra::future::FutureResult;
|
||||
use lib_infra::retry::spawn_retry;
|
||||
use lib_ot::core::EmptyAttributes;
|
||||
use revision_model::Revision;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub struct DatabaseBlockEditor {
|
||||
pub block_id: String,
|
||||
pad: Arc<RwLock<DatabaseBlockRevisionPad>>,
|
||||
rev_manager: Arc<RevisionManager<Arc<ConnectionPool>>>,
|
||||
}
|
||||
|
||||
impl DatabaseBlockEditor {
|
||||
pub async fn new(
|
||||
token: &str,
|
||||
block_id: &str,
|
||||
mut rev_manager: RevisionManager<Arc<ConnectionPool>>,
|
||||
) -> FlowyResult<Self> {
|
||||
let cloud = Arc::new(DatabaseBlockRevisionCloudService {
|
||||
token: token.to_owned(),
|
||||
});
|
||||
let block_revision_pad = rev_manager
|
||||
.initialize::<DatabaseBlockRevisionSerde>(Some(cloud))
|
||||
.await?;
|
||||
let pad = Arc::new(RwLock::new(block_revision_pad));
|
||||
let rev_manager = Arc::new(rev_manager);
|
||||
let block_id = block_id.to_owned();
|
||||
Ok(Self {
|
||||
block_id,
|
||||
pad,
|
||||
rev_manager,
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
/// 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<String>,
|
||||
) -> FlowyResult<(i32, Option<i32>)> {
|
||||
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<Cow<'_, String>>) -> FlowyResult<i32> {
|
||||
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<usize> {
|
||||
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<Option<(usize, Arc<RowRevision>)>> {
|
||||
if let Ok(pad) = self.pad.try_read() {
|
||||
Ok(pad.get_row_rev(row_id))
|
||||
} else {
|
||||
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(_) => {
|
||||
tracing::error!("Required database block read lock failed");
|
||||
Ok(None)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_row_revs<T>(
|
||||
&self,
|
||||
row_ids: Option<Vec<Cow<'_, T>>>,
|
||||
) -> FlowyResult<Vec<Arc<RowRevision>>>
|
||||
where
|
||||
T: AsRef<str> + 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<Vec<Cow<'_, String>>>,
|
||||
) -> FlowyResult<Vec<CellRevision>> {
|
||||
let cell_revs = self.pad.read().await.get_cell_revs(field_id, row_ids)?;
|
||||
Ok(cell_revs)
|
||||
}
|
||||
|
||||
async fn modify<F>(&self, f: F) -> FlowyResult<()>
|
||||
where
|
||||
F: for<'a> FnOnce(
|
||||
&'a mut DatabaseBlockRevisionPad,
|
||||
) -> FlowyResult<Option<DatabaseBlockRevisionChangeset>>,
|
||||
{
|
||||
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: DatabaseBlockRevisionChangeset) -> FlowyResult<()> {
|
||||
let DatabaseBlockRevisionChangeset {
|
||||
operations: delta,
|
||||
md5,
|
||||
} = change;
|
||||
let data = delta.json_bytes();
|
||||
let _ = self.rev_manager.add_local_revision(data, md5).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct DatabaseBlockRevisionCloudService {
|
||||
#[allow(dead_code)]
|
||||
token: String,
|
||||
}
|
||||
|
||||
impl RevisionCloudService for DatabaseBlockRevisionCloudService {
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
fn fetch_object(
|
||||
&self,
|
||||
_user_id: &str,
|
||||
_object_id: &str,
|
||||
) -> FutureResult<Vec<Revision>, FlowyError> {
|
||||
FutureResult::new(async move { Ok(vec![]) })
|
||||
}
|
||||
}
|
||||
|
||||
struct DatabaseBlockRevisionSerde();
|
||||
impl RevisionObjectDeserializer for DatabaseBlockRevisionSerde {
|
||||
type Output = DatabaseBlockRevisionPad;
|
||||
|
||||
fn deserialize_revisions(
|
||||
_object_id: &str,
|
||||
revisions: Vec<Revision>,
|
||||
) -> FlowyResult<Self::Output> {
|
||||
let pad = DatabaseBlockRevisionPad::from_revisions(revisions)?;
|
||||
Ok(pad)
|
||||
}
|
||||
|
||||
fn recover_from_revisions(_revisions: Vec<Revision>) -> Option<(Self::Output, i64)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl RevisionObjectSerializer for DatabaseBlockRevisionSerde {
|
||||
fn combine_revisions(revisions: Vec<Revision>) -> FlowyResult<Bytes> {
|
||||
let operations = make_operations_from_revisions::<EmptyAttributes>(revisions)?;
|
||||
Ok(operations.json_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DatabaseBlockRevisionMergeable();
|
||||
impl RevisionMergeable for DatabaseBlockRevisionMergeable {
|
||||
fn combine_revisions(&self, revisions: Vec<Revision>) -> FlowyResult<Bytes> {
|
||||
DatabaseBlockRevisionSerde::combine_revisions(revisions)
|
||||
}
|
||||
}
|
@ -1,353 +0,0 @@
|
||||
use crate::entities::{CellChangesetPB, InsertedRowPB, UpdatedRowPB};
|
||||
use crate::manager::DatabaseUser;
|
||||
use crate::notification::{send_notification, DatabaseNotification};
|
||||
use crate::services::database::{DatabaseBlockEditor, DatabaseBlockRevisionMergeable};
|
||||
use crate::services::persistence::block_index::BlockRowIndexer;
|
||||
use crate::services::persistence::rev_sqlite::{
|
||||
SQLiteDatabaseBlockRevisionPersistence, SQLiteDatabaseRevisionSnapshotPersistence,
|
||||
};
|
||||
use crate::services::row::{make_row_from_row_rev, DatabaseBlockRow, DatabaseBlockRowRevision};
|
||||
use dashmap::DashMap;
|
||||
use database_model::{
|
||||
DatabaseBlockMetaRevision, DatabaseBlockMetaRevisionChangeset, RowChangeset, RowRevision,
|
||||
};
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_revision::{RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration};
|
||||
use flowy_sqlite::ConnectionPool;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
type BlockId = String;
|
||||
pub(crate) struct DatabaseBlocks {
|
||||
user: Arc<dyn DatabaseUser>,
|
||||
persistence: Arc<BlockRowIndexer>,
|
||||
block_editors: DashMap<BlockId, Arc<DatabaseBlockEditor>>,
|
||||
event_notifier: broadcast::Sender<DatabaseBlockEvent>,
|
||||
}
|
||||
|
||||
impl DatabaseBlocks {
|
||||
pub(crate) async fn new(
|
||||
user: &Arc<dyn DatabaseUser>,
|
||||
block_meta_revs: Vec<Arc<DatabaseBlockMetaRevision>>,
|
||||
persistence: Arc<BlockRowIndexer>,
|
||||
event_notifier: broadcast::Sender<DatabaseBlockEvent>,
|
||||
) -> FlowyResult<Self> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// #[tracing::instrument(level = "trace", skip(self))]
|
||||
pub(crate) async fn get_or_create_block_editor(
|
||||
&self,
|
||||
block_id: &str,
|
||||
) -> FlowyResult<Arc<DatabaseBlockEditor>> {
|
||||
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<Arc<DatabaseBlockEditor>> {
|
||||
let block_id = self.persistence.get_block_id(row_id)?;
|
||||
self.get_or_create_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<String>,
|
||||
) -> FlowyResult<i32> {
|
||||
let block_id = row_rev.block_id.clone();
|
||||
self.persistence.insert(&row_rev.block_id, &row_rev.id)?;
|
||||
let editor = self.get_or_create_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<String, Vec<RowRevision>>,
|
||||
) -> FlowyResult<Vec<DatabaseBlockMetaRevisionChangeset>> {
|
||||
let mut changesets = vec![];
|
||||
for (block_id, row_revs) in rows_by_block_id {
|
||||
let editor = self.get_or_create_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(DatabaseBlockMetaRevisionChangeset::from_row_count(
|
||||
block_id.clone(),
|
||||
editor.number_of_rows().await,
|
||||
));
|
||||
}
|
||||
|
||||
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::<Vec<String>>();
|
||||
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<Option<Arc<RowRevision>>> {
|
||||
let row_id = row_id.to_owned();
|
||||
let block_id = self.persistence.get_block_id(&row_id)?;
|
||||
let editor = self.get_or_create_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<DatabaseBlockRow>,
|
||||
) -> FlowyResult<Vec<DatabaseBlockMetaRevisionChangeset>> {
|
||||
let mut changesets = vec![];
|
||||
for block_row in block_rows {
|
||||
let editor = self.get_or_create_block_editor(&block_row.block_id).await?;
|
||||
let row_ids = block_row
|
||||
.row_ids
|
||||
.into_iter()
|
||||
.map(Cow::Owned)
|
||||
.collect::<Vec<Cow<String>>>();
|
||||
let row_count = editor.delete_rows(row_ids).await?;
|
||||
let changeset =
|
||||
DatabaseBlockMetaRevisionChangeset::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<RowRevision>,
|
||||
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,
|
||||
};
|
||||
|
||||
let _ = self.event_notifier.send(DatabaseBlockEvent::Move {
|
||||
block_id: editor.block_id.clone(),
|
||||
deleted_row_id: delete_row_id,
|
||||
inserted_row: insert_row,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This function will be moved to GridViewRevisionEditor.
|
||||
pub async fn index_of_row(&self, row_id: &str) -> Option<usize> {
|
||||
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<Option<(usize, Arc<RowRevision>)>> {
|
||||
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<Vec<Arc<RowRevision>>> {
|
||||
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<Vec<String>>,
|
||||
) -> FlowyResult<Vec<DatabaseBlockRowRevision>> {
|
||||
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_or_create_block_editor(&block_id).await?;
|
||||
let row_revs = editor.get_row_revs::<&str>(None).await?;
|
||||
blocks.push(DatabaseBlockRowRevision { block_id, row_revs });
|
||||
}
|
||||
},
|
||||
}
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize each block editor
|
||||
async fn make_block_editors(
|
||||
user: &Arc<dyn DatabaseUser>,
|
||||
block_meta_revs: Vec<Arc<DatabaseBlockMetaRevision>>,
|
||||
) -> FlowyResult<DashMap<String, Arc<DatabaseBlockEditor>>> {
|
||||
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)
|
||||
}
|
||||
|
||||
async fn make_database_block_editor(
|
||||
user: &Arc<dyn DatabaseUser>,
|
||||
block_id: &str,
|
||||
) -> FlowyResult<DatabaseBlockEditor> {
|
||||
tracing::trace!("Open block:{} editor", block_id);
|
||||
let token = user.token()?;
|
||||
let rev_manager = make_database_block_rev_manager(user, block_id)?;
|
||||
DatabaseBlockEditor::new(&token, block_id, rev_manager).await
|
||||
}
|
||||
|
||||
pub fn make_database_block_rev_manager(
|
||||
user: &Arc<dyn DatabaseUser>,
|
||||
block_id: &str,
|
||||
) -> FlowyResult<RevisionManager<Arc<ConnectionPool>>> {
|
||||
// Create revision persistence
|
||||
let pool = user.db_pool()?;
|
||||
let disk_cache = SQLiteDatabaseBlockRevisionPersistence::new(pool.clone());
|
||||
let configuration = RevisionPersistenceConfiguration::new(4, false);
|
||||
let rev_persistence = RevisionPersistence::new(block_id, disk_cache, configuration);
|
||||
|
||||
// Create snapshot persistence
|
||||
const DATABASE_BLOCK_SP_PREFIX: &str = "grid_block";
|
||||
let snapshot_object_id = format!("{}:{}", DATABASE_BLOCK_SP_PREFIX, block_id);
|
||||
let snapshot_persistence =
|
||||
SQLiteDatabaseRevisionSnapshotPersistence::new(&snapshot_object_id, pool);
|
||||
|
||||
let rev_compress = DatabaseBlockRevisionMergeable();
|
||||
let rev_manager = RevisionManager::new(
|
||||
block_id,
|
||||
rev_persistence,
|
||||
rev_compress,
|
||||
snapshot_persistence,
|
||||
);
|
||||
Ok(rev_manager)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user