diff --git a/.github/workflows/rust_ci.yaml b/.github/workflows/rust_ci.yaml index fe4a1e9cb3..d918691086 100644 --- a/.github/workflows/rust_ci.yaml +++ b/.github/workflows/rust_ci.yaml @@ -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 diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/share_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/application/share_bloc.dart index 0e0c6d0881..d47272e59f 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/application/share_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/application/share_bloc.dart @@ -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'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/share_service.dart b/frontend/appflowy_flutter/lib/plugins/document/application/share_service.dart index 5bd7c3e509..025a2e9d18 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/application/share_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/application/share_service.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> exportText(ViewPB view) { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart index ffdd9a5f23..76c2af030e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart @@ -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'; diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/dispatch.dart b/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/dispatch.dart index 69671c2b47..b194a09c42 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/dispatch.dart +++ b/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/dispatch.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 { diff --git a/frontend/appflowy_tauri/pnpm-lock.yaml b/frontend/appflowy_tauri/pnpm-lock.yaml index 8ef1eba8f3..f5b351706d 100644 --- a/frontend/appflowy_tauri/pnpm-lock.yaml +++ b/frontend/appflowy_tauri/pnpm-lock.yaml @@ -9,25 +9,25 @@ dependencies: version: 1.1.1(emoji-mart@5.5.2)(react@18.2.0) '@emotion/react': specifier: ^11.10.6 - version: 11.10.6(@types/react@18.0.28)(react@18.2.0) + version: 11.11.0(@types/react@18.2.6)(react@18.2.0) '@emotion/styled': specifier: ^11.10.6 - version: 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.28)(react@18.2.0) + version: 11.11.0(@emotion/react@11.11.0)(@types/react@18.2.6)(react@18.2.0) '@mui/icons-material': specifier: ^5.11.11 - version: 5.11.11(@mui/material@5.11.12)(@types/react@18.0.28)(react@18.2.0) + version: 5.11.16(@mui/material@5.13.0)(@types/react@18.2.6)(react@18.2.0) '@mui/material': specifier: ^5.11.12 - version: 5.11.12(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) + version: 5.13.0(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) '@reduxjs/toolkit': specifier: ^1.9.2 - version: 1.9.3(react-redux@8.0.5)(react@18.2.0) + version: 1.9.5(react-redux@8.0.5)(react@18.2.0) '@tanstack/react-virtual': specifier: 3.0.0-beta.54 version: 3.0.0-beta.54(react@18.2.0) '@tauri-apps/api': specifier: ^1.2.0 - version: 1.2.0 + version: 1.3.0 dayjs: specifier: ^1.11.7 version: 1.11.7 @@ -42,7 +42,7 @@ dependencies: version: 3.21.2 i18next: specifier: ^22.4.10 - version: 22.4.10 + version: 22.4.15 i18next-browser-languagedetector: specifier: ^7.0.1 version: 7.0.1 @@ -51,10 +51,10 @@ dependencies: version: 0.2.0 jest: specifier: ^29.5.0 - version: 29.5.0(@types/node@18.14.6) + version: 29.5.0(@types/node@18.16.9) nanoid: specifier: ^4.0.0 - version: 4.0.1 + version: 4.0.2 prismjs: specifier: ^1.29.0 version: 1.29.0 @@ -78,28 +78,28 @@ dependencies: version: 3.1.4(react@18.2.0) react-i18next: specifier: ^12.2.0 - version: 12.2.0(i18next@22.4.10)(react-dom@18.2.0)(react@18.2.0) + version: 12.2.2(i18next@22.4.15)(react-dom@18.2.0)(react@18.2.0) react-redux: specifier: ^8.0.5 - version: 8.0.5(@types/react-dom@18.0.11)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) + version: 8.0.5(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) react-router-dom: specifier: ^6.8.0 - version: 6.8.2(react-dom@18.2.0)(react@18.2.0) + version: 6.11.1(react-dom@18.2.0)(react@18.2.0) react18-input-otp: specifier: ^1.1.2 - version: 1.1.2(react-dom@18.2.0)(react@18.2.0) + version: 1.1.3(react-dom@18.2.0)(react@18.2.0) redux: specifier: ^4.2.1 version: 4.2.1 rxjs: specifier: ^7.8.0 - version: 7.8.0 + version: 7.8.1 slate: specifier: ^0.91.4 version: 0.91.4 slate-react: specifier: ^0.91.9 - version: 0.91.9(react-dom@18.2.0)(react@18.2.0)(slate@0.91.4) + version: 0.91.11(react-dom@18.2.0)(react@18.2.0)(slate@0.91.4) ts-results: specifier: ^3.3.0 version: 3.3.0 @@ -108,15 +108,15 @@ dependencies: version: 3.0.0 y-indexeddb: specifier: ^9.0.9 - version: 9.0.9(yjs@13.5.51) + version: 9.0.11(yjs@13.6.1) yjs: specifier: ^13.5.51 - version: 13.5.51 + version: 13.6.1 devDependencies: '@tauri-apps/cli': specifier: ^1.2.2 - version: 1.2.3 + version: 1.3.1 '@types/google-protobuf': specifier: ^3.15.6 version: 3.15.6 @@ -125,19 +125,19 @@ devDependencies: version: 0.1.7 '@types/node': specifier: ^18.7.10 - version: 18.14.6 + version: 18.16.9 '@types/prismjs': specifier: ^1.26.0 version: 1.26.0 '@types/react': specifier: ^18.0.15 - version: 18.0.28 + version: 18.2.6 '@types/react-beautiful-dnd': specifier: ^13.1.3 version: 13.1.4 '@types/react-dom': specifier: ^18.0.6 - version: 18.0.11 + version: 18.2.4 '@types/utf8': specifier: ^3.0.1 version: 3.0.1 @@ -146,37 +146,37 @@ devDependencies: version: 9.0.1 '@typescript-eslint/eslint-plugin': specifier: ^5.51.0 - version: 5.54.0(@typescript-eslint/parser@5.54.0)(eslint@8.35.0)(typescript@4.9.5) + version: 5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@4.9.5) '@typescript-eslint/parser': specifier: ^5.51.0 - version: 5.54.0(eslint@8.35.0)(typescript@4.9.5) + version: 5.59.5(eslint@8.40.0)(typescript@4.9.5) '@vitejs/plugin-react': specifier: ^3.0.0 - version: 3.1.0(vite@4.1.4) + version: 3.1.0(vite@4.3.5) autoprefixer: specifier: ^10.4.13 - version: 10.4.13(postcss@8.4.21) + version: 10.4.14(postcss@8.4.23) eslint: specifier: ^8.34.0 - version: 8.35.0 + version: 8.40.0 eslint-plugin-react: specifier: ^7.32.2 - version: 7.32.2(eslint@8.35.0) + version: 7.32.2(eslint@8.40.0) eslint-plugin-react-hooks: specifier: ^4.6.0 - version: 4.6.0(eslint@8.35.0) + version: 4.6.0(eslint@8.40.0) postcss: specifier: ^8.4.21 - version: 8.4.21 + version: 8.4.23 prettier: specifier: 2.8.4 version: 2.8.4 prettier-plugin-tailwindcss: specifier: ^0.2.2 - version: 0.2.4(prettier@2.8.4) + version: 0.2.8(prettier@2.8.4) tailwindcss: specifier: ^3.2.7 - version: 3.2.7(postcss@8.4.21) + version: 3.3.2 typescript: specifier: ^4.6.4 version: 4.9.5 @@ -185,41 +185,46 @@ devDependencies: version: 9.0.0 vite: specifier: ^4.0.0 - version: 4.1.4(@types/node@18.14.6) + version: 4.3.5(@types/node@18.16.9) packages: - /@ampproject/remapping@2.2.0: - resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} + /@alloc/quick-lru@5.2.0: + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + dev: true + + /@ampproject/remapping@2.2.1: + resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} dependencies: - '@jridgewell/gen-mapping': 0.1.1 - '@jridgewell/trace-mapping': 0.3.17 + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.18 - /@babel/code-frame@7.18.6: - resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} + /@babel/code-frame@7.21.4: + resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==} engines: {node: '>=6.9.0'} dependencies: '@babel/highlight': 7.18.6 - /@babel/compat-data@7.21.0: - resolution: {integrity: sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==} + /@babel/compat-data@7.21.7: + resolution: {integrity: sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==} engines: {node: '>=6.9.0'} - /@babel/core@7.21.0: - resolution: {integrity: sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA==} + /@babel/core@7.21.8: + resolution: {integrity: sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==} engines: {node: '>=6.9.0'} dependencies: - '@ampproject/remapping': 2.2.0 - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.21.1 - '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.21.0) - '@babel/helper-module-transforms': 7.21.2 - '@babel/helpers': 7.21.0 - '@babel/parser': 7.21.2 + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.21.4 + '@babel/generator': 7.21.5 + '@babel/helper-compilation-targets': 7.21.5(@babel/core@7.21.8) + '@babel/helper-module-transforms': 7.21.5 + '@babel/helpers': 7.21.5 + '@babel/parser': 7.21.8 '@babel/template': 7.20.7 - '@babel/traverse': 7.21.2 - '@babel/types': 7.21.2 + '@babel/traverse': 7.21.5 + '@babel/types': 7.21.5 convert-source-map: 1.9.0 debug: 4.3.4 gensync: 1.0.0-beta.2 @@ -228,30 +233,30 @@ packages: transitivePeerDependencies: - supports-color - /@babel/generator@7.21.1: - resolution: {integrity: sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==} + /@babel/generator@7.21.5: + resolution: {integrity: sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.2 - '@jridgewell/gen-mapping': 0.3.2 - '@jridgewell/trace-mapping': 0.3.17 + '@babel/types': 7.21.5 + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.18 jsesc: 2.5.2 - /@babel/helper-compilation-targets@7.20.7(@babel/core@7.21.0): - resolution: {integrity: sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==} + /@babel/helper-compilation-targets@7.21.5(@babel/core@7.21.8): + resolution: {integrity: sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/compat-data': 7.21.0 - '@babel/core': 7.21.0 + '@babel/compat-data': 7.21.7 + '@babel/core': 7.21.8 '@babel/helper-validator-option': 7.21.0 browserslist: 4.21.5 lru-cache: 5.1.1 semver: 6.3.0 - /@babel/helper-environment-visitor@7.18.9: - resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} + /@babel/helper-environment-visitor@7.21.5: + resolution: {integrity: sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==} engines: {node: '>=6.9.0'} /@babel/helper-function-name@7.21.0: @@ -259,53 +264,53 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.20.7 - '@babel/types': 7.21.2 + '@babel/types': 7.21.5 /@babel/helper-hoist-variables@7.18.6: resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.2 + '@babel/types': 7.21.5 - /@babel/helper-module-imports@7.18.6: - resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} + /@babel/helper-module-imports@7.21.4: + resolution: {integrity: sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.2 + '@babel/types': 7.21.5 - /@babel/helper-module-transforms@7.21.2: - resolution: {integrity: sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==} + /@babel/helper-module-transforms@7.21.5: + resolution: {integrity: sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-module-imports': 7.18.6 - '@babel/helper-simple-access': 7.20.2 + '@babel/helper-environment-visitor': 7.21.5 + '@babel/helper-module-imports': 7.21.4 + '@babel/helper-simple-access': 7.21.5 '@babel/helper-split-export-declaration': 7.18.6 '@babel/helper-validator-identifier': 7.19.1 '@babel/template': 7.20.7 - '@babel/traverse': 7.21.2 - '@babel/types': 7.21.2 + '@babel/traverse': 7.21.5 + '@babel/types': 7.21.5 transitivePeerDependencies: - supports-color - /@babel/helper-plugin-utils@7.20.2: - resolution: {integrity: sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==} + /@babel/helper-plugin-utils@7.21.5: + resolution: {integrity: sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==} engines: {node: '>=6.9.0'} - /@babel/helper-simple-access@7.20.2: - resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==} + /@babel/helper-simple-access@7.21.5: + resolution: {integrity: sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.2 + '@babel/types': 7.21.5 /@babel/helper-split-export-declaration@7.18.6: resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.2 + '@babel/types': 7.21.5 - /@babel/helper-string-parser@7.19.4: - resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} + /@babel/helper-string-parser@7.21.5: + resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==} engines: {node: '>=6.9.0'} /@babel/helper-validator-identifier@7.19.1: @@ -316,13 +321,13 @@ packages: resolution: {integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==} engines: {node: '>=6.9.0'} - /@babel/helpers@7.21.0: - resolution: {integrity: sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==} + /@babel/helpers@7.21.5: + resolution: {integrity: sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==} engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.20.7 - '@babel/traverse': 7.21.2 - '@babel/types': 7.21.2 + '@babel/traverse': 7.21.5 + '@babel/types': 7.21.5 transitivePeerDependencies: - supports-color @@ -334,164 +339,164 @@ packages: chalk: 2.4.2 js-tokens: 4.0.0 - /@babel/parser@7.21.2: - resolution: {integrity: sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==} + /@babel/parser@7.21.8: + resolution: {integrity: sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.21.2 + '@babel/types': 7.21.5 - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.21.0): + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.21.8): resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: false - /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.21.0): + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.21.8): resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: false - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.21.0): + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.21.8): resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: false - /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.21.0): + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.21.8): resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: false - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.21.0): + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.21.8): resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: false - /@babel/plugin-syntax-jsx@7.18.6(@babel/core@7.21.0): - resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} + /@babel/plugin-syntax-jsx@7.21.4(@babel/core@7.21.8): + resolution: {integrity: sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: false - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.21.0): + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.21.8): resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: false - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.21.0): + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.21.8): resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: false - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.21.0): + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.21.8): resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: false - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.21.0): + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.21.8): resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: false - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.21.0): + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.21.8): resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: false - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.21.0): + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.21.8): resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: false - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.21.0): + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.21.8): resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: false - /@babel/plugin-syntax-typescript@7.20.0(@babel/core@7.21.0): - resolution: {integrity: sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==} + /@babel/plugin-syntax-typescript@7.21.4(@babel/core@7.21.8): + resolution: {integrity: sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: false - /@babel/plugin-transform-react-jsx-self@7.21.0(@babel/core@7.21.0): + /@babel/plugin-transform-react-jsx-self@7.21.0(@babel/core@7.21.8): resolution: {integrity: sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: true - /@babel/plugin-transform-react-jsx-source@7.19.6(@babel/core@7.21.0): + /@babel/plugin-transform-react-jsx-source@7.19.6(@babel/core@7.21.8): resolution: {integrity: sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.0 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/core': 7.21.8 + '@babel/helper-plugin-utils': 7.21.5 dev: true - /@babel/runtime@7.21.0: - resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==} + /@babel/runtime@7.21.5: + resolution: {integrity: sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 @@ -501,32 +506,32 @@ packages: resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.18.6 - '@babel/parser': 7.21.2 - '@babel/types': 7.21.2 + '@babel/code-frame': 7.21.4 + '@babel/parser': 7.21.8 + '@babel/types': 7.21.5 - /@babel/traverse@7.21.2: - resolution: {integrity: sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==} + /@babel/traverse@7.21.5: + resolution: {integrity: sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.21.1 - '@babel/helper-environment-visitor': 7.18.9 + '@babel/code-frame': 7.21.4 + '@babel/generator': 7.21.5 + '@babel/helper-environment-visitor': 7.21.5 '@babel/helper-function-name': 7.21.0 '@babel/helper-hoist-variables': 7.18.6 '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.21.2 - '@babel/types': 7.21.2 + '@babel/parser': 7.21.8 + '@babel/types': 7.21.5 debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: - supports-color - /@babel/types@7.21.2: - resolution: {integrity: sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==} + /@babel/types@7.21.5: + resolution: {integrity: sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-string-parser': 7.19.4 + '@babel/helper-string-parser': 7.21.5 '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 @@ -548,48 +553,48 @@ packages: react: 18.2.0 dev: false - /@emotion/babel-plugin@11.10.6: - resolution: {integrity: sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==} + /@emotion/babel-plugin@11.11.0: + resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} dependencies: - '@babel/helper-module-imports': 7.18.6 - '@babel/runtime': 7.21.0 - '@emotion/hash': 0.9.0 - '@emotion/memoize': 0.8.0 - '@emotion/serialize': 1.1.1 + '@babel/helper-module-imports': 7.21.4 + '@babel/runtime': 7.21.5 + '@emotion/hash': 0.9.1 + '@emotion/memoize': 0.8.1 + '@emotion/serialize': 1.1.2 babel-plugin-macros: 3.1.0 convert-source-map: 1.9.0 escape-string-regexp: 4.0.0 find-root: 1.1.0 source-map: 0.5.7 - stylis: 4.1.3 + stylis: 4.2.0 dev: false - /@emotion/cache@11.10.5: - resolution: {integrity: sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==} + /@emotion/cache@11.11.0: + resolution: {integrity: sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==} dependencies: - '@emotion/memoize': 0.8.0 - '@emotion/sheet': 1.2.1 - '@emotion/utils': 1.2.0 - '@emotion/weak-memoize': 0.3.0 - stylis: 4.1.3 + '@emotion/memoize': 0.8.1 + '@emotion/sheet': 1.2.2 + '@emotion/utils': 1.2.1 + '@emotion/weak-memoize': 0.3.1 + stylis: 4.2.0 dev: false - /@emotion/hash@0.9.0: - resolution: {integrity: sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==} + /@emotion/hash@0.9.1: + resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==} dev: false - /@emotion/is-prop-valid@1.2.0: - resolution: {integrity: sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==} + /@emotion/is-prop-valid@1.2.1: + resolution: {integrity: sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==} dependencies: - '@emotion/memoize': 0.8.0 + '@emotion/memoize': 0.8.1 dev: false - /@emotion/memoize@0.8.0: - resolution: {integrity: sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==} + /@emotion/memoize@0.8.1: + resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} dev: false - /@emotion/react@11.10.6(@types/react@18.0.28)(react@18.2.0): - resolution: {integrity: sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==} + /@emotion/react@11.11.0(@types/react@18.2.6)(react@18.2.0): + resolution: {integrity: sha512-ZSK3ZJsNkwfjT3JpDAWJZlrGD81Z3ytNDsxw1LKq1o+xkmO5pnWfr6gmCC8gHEFf3nSSX/09YrG67jybNPxSUw==} peerDependencies: '@types/react': '*' react: '>=16.8.0' @@ -597,34 +602,34 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.21.0 - '@emotion/babel-plugin': 11.10.6 - '@emotion/cache': 11.10.5 - '@emotion/serialize': 1.1.1 - '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) - '@emotion/utils': 1.2.0 - '@emotion/weak-memoize': 0.3.0 - '@types/react': 18.0.28 + '@babel/runtime': 7.21.5 + '@emotion/babel-plugin': 11.11.0 + '@emotion/cache': 11.11.0 + '@emotion/serialize': 1.1.2 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) + '@emotion/utils': 1.2.1 + '@emotion/weak-memoize': 0.3.1 + '@types/react': 18.2.6 hoist-non-react-statics: 3.3.2 react: 18.2.0 dev: false - /@emotion/serialize@1.1.1: - resolution: {integrity: sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==} + /@emotion/serialize@1.1.2: + resolution: {integrity: sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==} dependencies: - '@emotion/hash': 0.9.0 - '@emotion/memoize': 0.8.0 - '@emotion/unitless': 0.8.0 - '@emotion/utils': 1.2.0 - csstype: 3.1.1 + '@emotion/hash': 0.9.1 + '@emotion/memoize': 0.8.1 + '@emotion/unitless': 0.8.1 + '@emotion/utils': 1.2.1 + csstype: 3.1.2 dev: false - /@emotion/sheet@1.2.1: - resolution: {integrity: sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==} + /@emotion/sheet@1.2.2: + resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==} dev: false - /@emotion/styled@11.10.6(@emotion/react@11.10.6)(@types/react@18.0.28)(react@18.2.0): - resolution: {integrity: sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==} + /@emotion/styled@11.11.0(@emotion/react@11.11.0)(@types/react@18.2.6)(react@18.2.0): + resolution: {integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==} peerDependencies: '@emotion/react': ^11.0.0-rc.0 '@types/react': '*' @@ -633,39 +638,39 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.21.0 - '@emotion/babel-plugin': 11.10.6 - '@emotion/is-prop-valid': 1.2.0 - '@emotion/react': 11.10.6(@types/react@18.0.28)(react@18.2.0) - '@emotion/serialize': 1.1.1 - '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) - '@emotion/utils': 1.2.0 - '@types/react': 18.0.28 + '@babel/runtime': 7.21.5 + '@emotion/babel-plugin': 11.11.0 + '@emotion/is-prop-valid': 1.2.1 + '@emotion/react': 11.11.0(@types/react@18.2.6)(react@18.2.0) + '@emotion/serialize': 1.1.2 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) + '@emotion/utils': 1.2.1 + '@types/react': 18.2.6 react: 18.2.0 dev: false - /@emotion/unitless@0.8.0: - resolution: {integrity: sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==} + /@emotion/unitless@0.8.1: + resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} dev: false - /@emotion/use-insertion-effect-with-fallbacks@1.0.0(react@18.2.0): - resolution: {integrity: sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==} + /@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0): + resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==} peerDependencies: react: '>=16.8.0' dependencies: react: 18.2.0 dev: false - /@emotion/utils@1.2.0: - resolution: {integrity: sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==} + /@emotion/utils@1.2.1: + resolution: {integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==} dev: false - /@emotion/weak-memoize@0.3.0: - resolution: {integrity: sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==} + /@emotion/weak-memoize@0.3.1: + resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==} dev: false - /@esbuild/android-arm64@0.16.17: - resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==} + /@esbuild/android-arm64@0.17.19: + resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -673,8 +678,8 @@ packages: dev: true optional: true - /@esbuild/android-arm@0.16.17: - resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==} + /@esbuild/android-arm@0.17.19: + resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -682,8 +687,8 @@ packages: dev: true optional: true - /@esbuild/android-x64@0.16.17: - resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==} + /@esbuild/android-x64@0.17.19: + resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -691,8 +696,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64@0.16.17: - resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==} + /@esbuild/darwin-arm64@0.17.19: + resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -700,8 +705,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64@0.16.17: - resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==} + /@esbuild/darwin-x64@0.17.19: + resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -709,8 +714,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64@0.16.17: - resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==} + /@esbuild/freebsd-arm64@0.17.19: + resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -718,8 +723,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64@0.16.17: - resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==} + /@esbuild/freebsd-x64@0.17.19: + resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -727,8 +732,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64@0.16.17: - resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==} + /@esbuild/linux-arm64@0.17.19: + resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -736,8 +741,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm@0.16.17: - resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==} + /@esbuild/linux-arm@0.17.19: + resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -745,8 +750,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32@0.16.17: - resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==} + /@esbuild/linux-ia32@0.17.19: + resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -754,8 +759,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64@0.16.17: - resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==} + /@esbuild/linux-loong64@0.17.19: + resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -763,8 +768,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el@0.16.17: - resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==} + /@esbuild/linux-mips64el@0.17.19: + resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -772,8 +777,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64@0.16.17: - resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==} + /@esbuild/linux-ppc64@0.17.19: + resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -781,8 +786,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64@0.16.17: - resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==} + /@esbuild/linux-riscv64@0.17.19: + resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -790,8 +795,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x@0.16.17: - resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==} + /@esbuild/linux-s390x@0.17.19: + resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -799,8 +804,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64@0.16.17: - resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==} + /@esbuild/linux-x64@0.17.19: + resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -808,8 +813,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64@0.16.17: - resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==} + /@esbuild/netbsd-x64@0.17.19: + resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -817,8 +822,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64@0.16.17: - resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==} + /@esbuild/openbsd-x64@0.17.19: + resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -826,8 +831,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64@0.16.17: - resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==} + /@esbuild/sunos-x64@0.17.19: + resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -835,8 +840,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64@0.16.17: - resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==} + /@esbuild/win32-arm64@0.17.19: + resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -844,8 +849,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32@0.16.17: - resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==} + /@esbuild/win32-ia32@0.17.19: + resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -853,8 +858,8 @@ packages: dev: true optional: true - /@esbuild/win32-x64@0.16.17: - resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==} + /@esbuild/win32-x64@0.17.19: + resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -862,13 +867,28 @@ packages: dev: true optional: true - /@eslint/eslintrc@2.0.0: - resolution: {integrity: sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==} + /@eslint-community/eslint-utils@4.4.0(eslint@8.40.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.40.0 + eslint-visitor-keys: 3.4.1 + dev: true + + /@eslint-community/regexpp@4.5.1: + resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + + /@eslint/eslintrc@2.0.3: + resolution: {integrity: sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.4 - espree: 9.4.1 + espree: 9.5.2 globals: 13.20.0 ignore: 5.2.4 import-fresh: 3.3.0 @@ -879,8 +899,8 @@ packages: - supports-color dev: true - /@eslint/js@8.35.0: - resolution: {integrity: sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==} + /@eslint/js@8.40.0: + resolution: {integrity: sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -925,7 +945,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.5.0 - '@types/node': 18.14.6 + '@types/node': 18.16.9 chalk: 4.1.2 jest-message-util: 29.5.0 jest-util: 29.5.0 @@ -946,14 +966,14 @@ packages: '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.14.6 + '@types/node': 18.16.9 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.8.0 exit: 0.1.2 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-changed-files: 29.5.0 - jest-config: 29.5.0(@types/node@18.14.6) + jest-config: 29.5.0(@types/node@18.16.9) jest-haste-map: 29.5.0 jest-message-util: 29.5.0 jest-regex-util: 29.4.3 @@ -980,7 +1000,7 @@ packages: dependencies: '@jest/fake-timers': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.14.6 + '@types/node': 18.16.9 jest-mock: 29.5.0 dev: false @@ -1006,8 +1026,8 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.5.0 - '@sinonjs/fake-timers': 10.0.2 - '@types/node': 18.14.6 + '@sinonjs/fake-timers': 10.1.0 + '@types/node': 18.16.9 jest-message-util: 29.5.0 jest-mock: 29.5.0 jest-util: 29.5.0 @@ -1039,13 +1059,13 @@ packages: '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@jridgewell/trace-mapping': 0.3.17 - '@types/node': 18.14.6 + '@jridgewell/trace-mapping': 0.3.18 + '@types/node': 18.16.9 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 glob: 7.2.3 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 istanbul-lib-coverage: 3.2.0 istanbul-lib-instrument: 5.2.1 istanbul-lib-report: 3.0.0 @@ -1073,9 +1093,9 @@ packages: resolution: {integrity: sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jridgewell/trace-mapping': 0.3.17 + '@jridgewell/trace-mapping': 0.3.18 callsites: 3.1.0 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 dev: false /@jest/test-result@29.5.0: @@ -1093,7 +1113,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/test-result': 29.5.0 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-haste-map: 29.5.0 slash: 3.0.0 dev: false @@ -1102,14 +1122,14 @@ packages: resolution: {integrity: sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.21.0 + '@babel/core': 7.21.8 '@jest/types': 29.5.0 - '@jridgewell/trace-mapping': 0.3.17 + '@jridgewell/trace-mapping': 0.3.18 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-haste-map: 29.5.0 jest-regex-util: 29.4.3 jest-util: 29.5.0 @@ -1128,25 +1148,18 @@ packages: '@jest/schemas': 29.4.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.14.6 - '@types/yargs': 17.0.22 + '@types/node': 18.16.9 + '@types/yargs': 17.0.24 chalk: 4.1.2 dev: false - /@jridgewell/gen-mapping@0.1.1: - resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} + /@jridgewell/gen-mapping@0.3.3: + resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} dependencies: '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 - - /@jridgewell/gen-mapping@0.3.2: - resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 - '@jridgewell/trace-mapping': 0.3.17 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.18 /@jridgewell/resolve-uri@3.1.0: resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} @@ -1159,8 +1172,11 @@ packages: /@jridgewell/sourcemap-codec@1.4.14: resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - /@jridgewell/trace-mapping@0.3.17: - resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + /@jridgewell/trace-mapping@0.3.18: + resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} dependencies: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 @@ -1169,8 +1185,8 @@ packages: resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} dev: false - /@mui/base@5.0.0-alpha.119(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-XA5zhlYfXi67u613eIF0xRmktkatx6ERy3h+PwrMN5IcWFbgiL1guz8VpdXON+GWb8+G7B8t5oqTFIaCqaSAeA==} + /@mui/base@5.0.0-beta.0(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-ap+juKvt8R8n3cBqd/pGtZydQ4v2I/hgJKnvJRGjpSh3RvsvnDHO4rXov8MHQlH6VqpOekwgilFLGxMZjNTucA==} engines: {node: '>=12.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 @@ -1180,12 +1196,12 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.21.0 - '@emotion/is-prop-valid': 1.2.0 - '@mui/types': 7.2.3(@types/react@18.0.28) - '@mui/utils': 5.11.12(react@18.2.0) - '@popperjs/core': 2.11.6 - '@types/react': 18.0.28 + '@babel/runtime': 7.21.5 + '@emotion/is-prop-valid': 1.2.1 + '@mui/types': 7.2.4(@types/react@18.2.6) + '@mui/utils': 5.12.3(react@18.2.0) + '@popperjs/core': 2.11.7 + '@types/react': 18.2.6 clsx: 1.2.1 prop-types: 15.8.1 react: 18.2.0 @@ -1193,12 +1209,12 @@ packages: react-is: 18.2.0 dev: false - /@mui/core-downloads-tracker@5.11.12: - resolution: {integrity: sha512-LHh8HZQ5nPVcW5QnyLwkAZ40txc/S2bzKMQ3bTO+5mjuwAJ2AzQrjZINLVy1geY7ei1pHXVqO1hcWHg/QdT44w==} + /@mui/core-downloads-tracker@5.13.0: + resolution: {integrity: sha512-5nXz2k8Rv2ZjtQY6kXirJVyn2+ODaQuAJmXSJtLDUQDKWp3PFUj6j3bILqR0JGOs9R5ejgwz3crLKsl6GwjwkQ==} dev: false - /@mui/icons-material@5.11.11(@mui/material@5.11.12)(@types/react@18.0.28)(react@18.2.0): - resolution: {integrity: sha512-Eell3ADmQVE8HOpt/LZ3zIma8JSvPh3XgnhwZLT0k5HRqZcd6F/QDHc7xsWtgz09t+UEFvOYJXjtrwKmLdwwpw==} + /@mui/icons-material@5.11.16(@mui/material@5.13.0)(@types/react@18.2.6)(react@18.2.0): + resolution: {integrity: sha512-oKkx9z9Kwg40NtcIajF9uOXhxiyTZrrm9nmIJ4UjkU2IdHpd4QVLbCc/5hZN/y0C6qzi2Zlxyr9TGddQx2vx2A==} engines: {node: '>=12.0.0'} peerDependencies: '@mui/material': ^5.0.0 @@ -1208,14 +1224,14 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.21.0 - '@mui/material': 5.11.12(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.0.28 + '@babel/runtime': 7.21.5 + '@mui/material': 5.13.0(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.6 react: 18.2.0 dev: false - /@mui/material@5.11.12(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-M6BiIeJjySeEzWeiFJQ9pIjJy6mx5mHPWeMT99wjQdAmA2GxCQhE9A0fh6jQP4jMmYzxhOIhjsGcp0vSdpseXg==} + /@mui/material@5.13.0(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-ckS+9tCpAzpdJdaTF+btF0b6mF9wbXg/EVKtnoAWYi0UKXoXBAVvEUMNpLGA5xdpCdf+A6fPbVUEHs9TsfU+Yw==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -1231,18 +1247,18 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.21.0 - '@emotion/react': 11.10.6(@types/react@18.0.28)(react@18.2.0) - '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.28)(react@18.2.0) - '@mui/base': 5.0.0-alpha.119(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) - '@mui/core-downloads-tracker': 5.11.12 - '@mui/system': 5.11.12(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.28)(react@18.2.0) - '@mui/types': 7.2.3(@types/react@18.0.28) - '@mui/utils': 5.11.12(react@18.2.0) - '@types/react': 18.0.28 - '@types/react-transition-group': 4.4.5 + '@babel/runtime': 7.21.5 + '@emotion/react': 11.11.0(@types/react@18.2.6)(react@18.2.0) + '@emotion/styled': 11.11.0(@emotion/react@11.11.0)(@types/react@18.2.6)(react@18.2.0) + '@mui/base': 5.0.0-beta.0(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) + '@mui/core-downloads-tracker': 5.13.0 + '@mui/system': 5.12.3(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react@18.2.0) + '@mui/types': 7.2.4(@types/react@18.2.6) + '@mui/utils': 5.12.3(react@18.2.0) + '@types/react': 18.2.6 + '@types/react-transition-group': 4.4.6 clsx: 1.2.1 - csstype: 3.1.1 + csstype: 3.1.2 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -1250,8 +1266,8 @@ packages: react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) dev: false - /@mui/private-theming@5.11.12(@types/react@18.0.28)(react@18.2.0): - resolution: {integrity: sha512-hnJ0svNI1TPeWZ18E6DvES8PB4NyMLwal6EyXf69rTrYqT6wZPLjB+HiCYfSOCqU/fwArhupSqIIkQpDs8CkAw==} + /@mui/private-theming@5.12.3(@types/react@18.2.6)(react@18.2.0): + resolution: {integrity: sha512-o1e7Z1Bp27n4x2iUHhegV4/Jp6H3T6iBKHJdLivS5GbwsuAE/5l4SnZ+7+K+e5u9TuhwcAKZLkjvqzkDe8zqfA==} engines: {node: '>=12.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 @@ -1260,15 +1276,15 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.21.0 - '@mui/utils': 5.11.12(react@18.2.0) - '@types/react': 18.0.28 + '@babel/runtime': 7.21.5 + '@mui/utils': 5.12.3(react@18.2.0) + '@types/react': 18.2.6 prop-types: 15.8.1 react: 18.2.0 dev: false - /@mui/styled-engine@5.11.11(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0): - resolution: {integrity: sha512-wV0UgW4lN5FkDBXefN8eTYeuE9sjyQdg5h94vtwZCUamGQEzmCOtir4AakgmbWMy0x8OLjdEUESn9wnf5J9MOg==} + /@mui/styled-engine@5.12.3(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(react@18.2.0): + resolution: {integrity: sha512-AhZtiRyT8Bjr7fufxE/mLS+QJ3LxwX1kghIcM2B2dvJzSSg9rnIuXDXM959QfUVIM3C8U4x3mgVoPFMQJvc4/g==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.4.1 @@ -1280,17 +1296,17 @@ packages: '@emotion/styled': optional: true dependencies: - '@babel/runtime': 7.21.0 - '@emotion/cache': 11.10.5 - '@emotion/react': 11.10.6(@types/react@18.0.28)(react@18.2.0) - '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.28)(react@18.2.0) - csstype: 3.1.1 + '@babel/runtime': 7.21.5 + '@emotion/cache': 11.11.0 + '@emotion/react': 11.11.0(@types/react@18.2.6)(react@18.2.0) + '@emotion/styled': 11.11.0(@emotion/react@11.11.0)(@types/react@18.2.6)(react@18.2.0) + csstype: 3.1.2 prop-types: 15.8.1 react: 18.2.0 dev: false - /@mui/system@5.11.12(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.28)(react@18.2.0): - resolution: {integrity: sha512-sYjsXkiwKpZDC3aS6O/6KTjji0jGINLQcrD5EJ5NTkIDiLf19I4HJhnufgKqlTWNfoDBlRohuTf3TzfM06c4ug==} + /@mui/system@5.12.3(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react@18.2.0): + resolution: {integrity: sha512-JB/6sypHqeJCqwldWeQ1MKkijH829EcZAKKizxbU2MJdxGG5KSwZvTBa5D9qiJUA1hJFYYupjiuy9ZdJt6rV6w==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -1305,40 +1321,40 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.21.0 - '@emotion/react': 11.10.6(@types/react@18.0.28)(react@18.2.0) - '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.28)(react@18.2.0) - '@mui/private-theming': 5.11.12(@types/react@18.0.28)(react@18.2.0) - '@mui/styled-engine': 5.11.11(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0) - '@mui/types': 7.2.3(@types/react@18.0.28) - '@mui/utils': 5.11.12(react@18.2.0) - '@types/react': 18.0.28 + '@babel/runtime': 7.21.5 + '@emotion/react': 11.11.0(@types/react@18.2.6)(react@18.2.0) + '@emotion/styled': 11.11.0(@emotion/react@11.11.0)(@types/react@18.2.6)(react@18.2.0) + '@mui/private-theming': 5.12.3(@types/react@18.2.6)(react@18.2.0) + '@mui/styled-engine': 5.12.3(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(react@18.2.0) + '@mui/types': 7.2.4(@types/react@18.2.6) + '@mui/utils': 5.12.3(react@18.2.0) + '@types/react': 18.2.6 clsx: 1.2.1 - csstype: 3.1.1 + csstype: 3.1.2 prop-types: 15.8.1 react: 18.2.0 dev: false - /@mui/types@7.2.3(@types/react@18.0.28): - resolution: {integrity: sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==} + /@mui/types@7.2.4(@types/react@18.2.6): + resolution: {integrity: sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==} peerDependencies: '@types/react': '*' peerDependenciesMeta: '@types/react': optional: true dependencies: - '@types/react': 18.0.28 + '@types/react': 18.2.6 dev: false - /@mui/utils@5.11.12(react@18.2.0): - resolution: {integrity: sha512-5vH9B/v8pzkpEPO2HvGM54ToXV6cFdAn8UrvdN8TMEEwpn/ycW0jLiyBcgUlPsQ+xha7hqXCPQYHaYFDIcwaiw==} + /@mui/utils@5.12.3(react@18.2.0): + resolution: {integrity: sha512-D/Z4Ub3MRl7HiUccid7sQYclTr24TqUAQFFlxHQF8FR177BrCTQ0JJZom7EqYjZCdXhwnSkOj2ph685MSKNtIA==} engines: {node: '>=12.0.0'} peerDependencies: react: ^17.0.0 || ^18.0.0 dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.21.5 '@types/prop-types': 15.7.5 - '@types/react-is': 17.0.3 + '@types/react-is': 17.0.4 prop-types: 15.8.1 react: 18.2.0 react-is: 18.2.0 @@ -1365,12 +1381,12 @@ packages: fastq: 1.15.0 dev: true - /@popperjs/core@2.11.6: - resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==} + /@popperjs/core@2.11.7: + resolution: {integrity: sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==} dev: false - /@reduxjs/toolkit@1.9.3(react-redux@8.0.5)(react@18.2.0): - resolution: {integrity: sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==} + /@reduxjs/toolkit@1.9.5(react-redux@8.0.5)(react@18.2.0): + resolution: {integrity: sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 react-redux: ^7.2.1 || ^8.0.2 @@ -1380,16 +1396,16 @@ packages: react-redux: optional: true dependencies: - immer: 9.0.19 + immer: 9.0.21 react: 18.2.0 - react-redux: 8.0.5(@types/react-dom@18.0.11)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) + react-redux: 8.0.5(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) redux: 4.2.1 redux-thunk: 2.4.2(redux@4.2.1) - reselect: 4.1.7 + reselect: 4.1.8 dev: false - /@remix-run/router@1.3.3: - resolution: {integrity: sha512-YRHie1yQEj0kqqCTCJEfHqYSSNlZQ696QJG+MMiW4mxSl9I0ojz/eRhJS4fs88Z5i6D1SmoF9d3K99/QOhI8/w==} + /@remix-run/router@1.6.1: + resolution: {integrity: sha512-YUkWj+xs0oOzBe74OgErsuR3wVn+efrFhXBWrit50kOiED+pvQe2r6MWY0iJMQU/mSVKxvNzL4ZaYvjdX+G7ZA==} engines: {node: '>=14'} dev: false @@ -1397,16 +1413,16 @@ packages: resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==} dev: false - /@sinonjs/commons@2.0.0: - resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==} + /@sinonjs/commons@3.0.0: + resolution: {integrity: sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==} dependencies: type-detect: 4.0.8 dev: false - /@sinonjs/fake-timers@10.0.2: - resolution: {integrity: sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==} + /@sinonjs/fake-timers@10.1.0: + resolution: {integrity: sha512-w1qd368vtrwttm1PRJWPW1QHlbmHrVDGs1eBH/jZvRPUFS4MNXV9Q33EQdjOdeAxZ7O8+3wM7zxztm2nfUSyKw==} dependencies: - '@sinonjs/commons': 2.0.0 + '@sinonjs/commons': 3.0.0 dev: false /@tanstack/react-virtual@3.0.0-beta.54(react@18.2.0): @@ -1422,13 +1438,13 @@ packages: resolution: {integrity: sha512-jtkwqdP2rY2iCCDVAFuaNBH3fiEi29aTn2RhtIoky8DTTiCdc48plpHHreLwmv1PICJ4AJUUESaq3xa8fZH8+g==} dev: false - /@tauri-apps/api@1.2.0: - resolution: {integrity: sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw==} + /@tauri-apps/api@1.3.0: + resolution: {integrity: sha512-AH+3FonkKZNtfRtGrObY38PrzEj4d+1emCbwNGu0V2ENbXjlLHMZQlUh+Bhu/CRmjaIwZMGJ3yFvWaZZgTHoog==} engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} dev: false - /@tauri-apps/cli-darwin-arm64@1.2.3: - resolution: {integrity: sha512-phJN3fN8FtZZwqXg08bcxfq1+X1JSDglLvRxOxB7VWPq+O5SuB8uLyssjJsu+PIhyZZnIhTGdjhzLSFhSXfLsw==} + /@tauri-apps/cli-darwin-arm64@1.3.1: + resolution: {integrity: sha512-QlepYVPgOgspcwA/u4kGG4ZUijlXfdRtno00zEy+LxinN/IRXtk+6ErVtsmoLi1ZC9WbuMwzAcsRvqsD+RtNAg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -1436,8 +1452,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-darwin-x64@1.2.3: - resolution: {integrity: sha512-jFZ/y6z8z6v4yliIbXKBXA7BJgtZVMsITmEXSuD6s5+eCOpDhQxbRkr6CA+FFfr+/r96rWSDSgDenDQuSvPAKw==} + /@tauri-apps/cli-darwin-x64@1.3.1: + resolution: {integrity: sha512-fKcAUPVFO3jfDKXCSDGY0MhZFF/wDtx3rgFnogWYu4knk38o9RaqRkvMvqJhLYPuWaEM5h6/z1dRrr9KKCbrVg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -1445,8 +1461,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-arm-gnueabihf@1.2.3: - resolution: {integrity: sha512-C7h5vqAwXzY0kRGSU00Fj8PudiDWFCiQqqUNI1N+fhCILrzWZB9TPBwdx33ZfXKt/U4+emdIoo/N34v3TiAOmQ==} + /@tauri-apps/cli-linux-arm-gnueabihf@1.3.1: + resolution: {integrity: sha512-+4H0dv8ltJHYu/Ma1h9ixUPUWka9EjaYa8nJfiMsdCI4LJLNE6cPveE7RmhZ59v9GW1XB108/k083JUC/OtGvA==} engines: {node: '>= 10'} cpu: [arm] os: [linux] @@ -1454,8 +1470,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-arm64-gnu@1.2.3: - resolution: {integrity: sha512-buf1c8sdkuUzVDkGPQpyUdAIIdn5r0UgXU6+H5fGPq/Xzt5K69JzXaeo6fHsZEZghbV0hOK+taKV4J0m30UUMQ==} + /@tauri-apps/cli-linux-arm64-gnu@1.3.1: + resolution: {integrity: sha512-Pj3odVO1JAxLjYmoXKxcrpj/tPxcA8UP8N06finhNtBtBaxAjrjjxKjO4968KB0BUH7AASIss9EL4Tr0FGnDuw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1463,8 +1479,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-arm64-musl@1.2.3: - resolution: {integrity: sha512-x88wPS9W5xAyk392vc4uNHcKBBvCp0wf4H9JFMF9OBwB7vfd59LbQCFcPSu8f0BI7bPrOsyHqspWHuFL8ojQEA==} + /@tauri-apps/cli-linux-arm64-musl@1.3.1: + resolution: {integrity: sha512-tA0JdDLPFaj42UDIVcF2t8V0tSha40rppcmAR/MfQpTCxih6399iMjwihz9kZE1n4b5O4KTq9GliYo50a8zYlQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1472,8 +1488,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-x64-gnu@1.2.3: - resolution: {integrity: sha512-ZMz1jxEVe0B4/7NJnlPHmwmSIuwiD6ViXKs8F+OWWz2Y4jn5TGxWKFg7DLx5OwQTRvEIZxxT7lXHi5CuTNAxKg==} + /@tauri-apps/cli-linux-x64-gnu@1.3.1: + resolution: {integrity: sha512-FDU+Mnvk6NLkqQimcNojdKpMN4Y3W51+SQl+NqG9AFCWprCcSg62yRb84751ujZuf2MGT8HQOfmd0i77F4Q3tQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1481,8 +1497,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-linux-x64-musl@1.2.3: - resolution: {integrity: sha512-B/az59EjJhdbZDzawEVox0LQu2ZHCZlk8rJf85AMIktIUoAZPFbwyiUv7/zjzA/sY6Nb58OSJgaPL2/IBy7E0A==} + /@tauri-apps/cli-linux-x64-musl@1.3.1: + resolution: {integrity: sha512-MpO3akXFmK8lZYEbyQRDfhdxz1JkTBhonVuz5rRqxwA7gnGWHa1aF1+/2zsy7ahjB2tQ9x8DDFDMdVE20o9HrA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1490,8 +1506,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-win32-ia32-msvc@1.2.3: - resolution: {integrity: sha512-ypdO1OdC5ugNJAKO2m3sb1nsd+0TSvMS9Tr5qN/ZSMvtSduaNwrcZ3D7G/iOIanrqu/Nl8t3LYlgPZGBKlw7Ng==} + /@tauri-apps/cli-win32-ia32-msvc@1.3.1: + resolution: {integrity: sha512-9Boeo3K5sOrSBAZBuYyGkpV2RfnGQz3ZhGJt4hE6P+HxRd62lS6+qDKAiw1GmkZ0l1drc2INWrNeT50gwOKwIQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -1499,8 +1515,8 @@ packages: dev: true optional: true - /@tauri-apps/cli-win32-x64-msvc@1.2.3: - resolution: {integrity: sha512-CsbHQ+XhnV/2csOBBDVfH16cdK00gNyNYUW68isedmqcn8j+s0e9cQ1xXIqi+Hue3awp8g3ImYN5KPepf3UExw==} + /@tauri-apps/cli-win32-x64-msvc@1.3.1: + resolution: {integrity: sha512-wMrTo91hUu5CdpbElrOmcZEoJR4aooTG+fbtcc87SMyPGQy1Ux62b+ZdwLvL1sVTxnIm//7v6QLRIWGiUjCPwA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1508,49 +1524,49 @@ packages: dev: true optional: true - /@tauri-apps/cli@1.2.3: - resolution: {integrity: sha512-erxtXuPhMEGJPBtnhPILD4AjuT81GZsraqpFvXAmEJZ2p8P6t7MVBifCL8LznRknznM3jn90D3M8RNBP3wcXTw==} + /@tauri-apps/cli@1.3.1: + resolution: {integrity: sha512-o4I0JujdITsVRm3/0spfJX7FcKYrYV1DXJqzlWIn6IY25/RltjU6qbC1TPgVww3RsRX63jyVUTcWpj5wwFl+EQ==} engines: {node: '>= 10'} hasBin: true optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 1.2.3 - '@tauri-apps/cli-darwin-x64': 1.2.3 - '@tauri-apps/cli-linux-arm-gnueabihf': 1.2.3 - '@tauri-apps/cli-linux-arm64-gnu': 1.2.3 - '@tauri-apps/cli-linux-arm64-musl': 1.2.3 - '@tauri-apps/cli-linux-x64-gnu': 1.2.3 - '@tauri-apps/cli-linux-x64-musl': 1.2.3 - '@tauri-apps/cli-win32-ia32-msvc': 1.2.3 - '@tauri-apps/cli-win32-x64-msvc': 1.2.3 + '@tauri-apps/cli-darwin-arm64': 1.3.1 + '@tauri-apps/cli-darwin-x64': 1.3.1 + '@tauri-apps/cli-linux-arm-gnueabihf': 1.3.1 + '@tauri-apps/cli-linux-arm64-gnu': 1.3.1 + '@tauri-apps/cli-linux-arm64-musl': 1.3.1 + '@tauri-apps/cli-linux-x64-gnu': 1.3.1 + '@tauri-apps/cli-linux-x64-musl': 1.3.1 + '@tauri-apps/cli-win32-ia32-msvc': 1.3.1 + '@tauri-apps/cli-win32-x64-msvc': 1.3.1 dev: true /@types/babel__core@7.20.0: resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==} dependencies: - '@babel/parser': 7.21.2 - '@babel/types': 7.21.2 + '@babel/parser': 7.21.8 + '@babel/types': 7.21.5 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 - '@types/babel__traverse': 7.18.3 + '@types/babel__traverse': 7.18.5 dev: false /@types/babel__generator@7.6.4: resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} dependencies: - '@babel/types': 7.21.2 + '@babel/types': 7.21.5 dev: false /@types/babel__template@7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: - '@babel/parser': 7.21.2 - '@babel/types': 7.21.2 + '@babel/parser': 7.21.8 + '@babel/types': 7.21.5 dev: false - /@types/babel__traverse@7.18.3: - resolution: {integrity: sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==} + /@types/babel__traverse@7.18.5: + resolution: {integrity: sha512-enCvTL8m/EHS/zIvJno9nE+ndYPh1/oNFzRYRmtUqJICG2VnCSBzMLW5VN2KCQU91f23tsNKR8v7VJJQMatl7Q==} dependencies: - '@babel/types': 7.21.2 + '@babel/types': 7.21.5 dev: false /@types/google-protobuf@3.15.6: @@ -1560,13 +1576,13 @@ packages: /@types/graceful-fs@4.1.6: resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} dependencies: - '@types/node': 18.14.6 + '@types/node': 18.16.9 dev: false /@types/hoist-non-react-statics@3.3.1: resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==} dependencies: - '@types/react': 18.0.28 + '@types/react': 18.2.6 hoist-non-react-statics: 3.3.2 dev: false @@ -1596,15 +1612,15 @@ packages: /@types/lodash.memoize@4.1.7: resolution: {integrity: sha512-lGN7WeO4vO6sICVpf041Q7BX/9k1Y24Zo3FY0aUezr1QlKznpjzsDk3T3wvH8ofYzoK0QupN9TWcFAFZlyPwQQ==} dependencies: - '@types/lodash': 4.14.191 + '@types/lodash': 4.14.194 dev: false - /@types/lodash@4.14.191: - resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==} + /@types/lodash@4.14.194: + resolution: {integrity: sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g==} dev: false - /@types/node@18.14.6: - resolution: {integrity: sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==} + /@types/node@18.16.9: + resolution: {integrity: sha512-IeB32oIV4oGArLrd7znD2rkHQ6EDCM+2Sr76dJnrHwv9OHBTTM6nuDLK9bmikXzPa0ZlWMWtRGo/Uw4mrzQedA==} /@types/parse-json@4.0.0: resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} @@ -1624,47 +1640,55 @@ packages: /@types/react-beautiful-dnd@13.1.4: resolution: {integrity: sha512-4bIBdzOr0aavN+88q3C7Pgz+xkb7tz3whORYrmSj77wfVEMfiWiooIwVWFR7KM2e+uGTe5BVrXqSfb0aHeflJA==} dependencies: - '@types/react': 18.0.28 + '@types/react': 18.2.6 dev: true - /@types/react-dom@18.0.11: - resolution: {integrity: sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==} + /@types/react-dom@18.2.4: + resolution: {integrity: sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==} dependencies: - '@types/react': 18.0.28 + '@types/react': 18.2.6 - /@types/react-is@17.0.3: - resolution: {integrity: sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==} + /@types/react-is@17.0.4: + resolution: {integrity: sha512-FLzd0K9pnaEvKz4D1vYxK9JmgQPiGk1lu23o1kqGsLeT0iPbRSF7b76+S5T9fD8aRa0B8bY7I/3DebEj+1ysBA==} dependencies: - '@types/react': 18.0.28 + '@types/react': 17.0.59 dev: false /@types/react-redux@7.1.25: resolution: {integrity: sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==} dependencies: '@types/hoist-non-react-statics': 3.3.1 - '@types/react': 18.0.28 + '@types/react': 18.2.6 hoist-non-react-statics: 3.3.2 redux: 4.2.1 dev: false - /@types/react-transition-group@4.4.5: - resolution: {integrity: sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==} + /@types/react-transition-group@4.4.6: + resolution: {integrity: sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==} dependencies: - '@types/react': 18.0.28 + '@types/react': 18.2.6 dev: false - /@types/react@18.0.28: - resolution: {integrity: sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==} + /@types/react@17.0.59: + resolution: {integrity: sha512-gSON5zWYIGyoBcycCE75E9+r6dCC2dHdsrVkOEiIYNU5+Q28HcBAuqvDuxHcCbMfHBHdeT5Tva/AFn3rnMKE4g==} dependencies: '@types/prop-types': 15.7.5 - '@types/scheduler': 0.16.2 - csstype: 3.1.1 + '@types/scheduler': 0.16.3 + csstype: 3.1.2 + dev: false - /@types/scheduler@0.16.2: - resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} + /@types/react@18.2.6: + resolution: {integrity: sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==} + dependencies: + '@types/prop-types': 15.7.5 + '@types/scheduler': 0.16.3 + csstype: 3.1.2 - /@types/semver@7.3.13: - resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} + /@types/scheduler@0.16.3: + resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} + + /@types/semver@7.5.0: + resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} dev: true /@types/stack-utils@2.0.1: @@ -1687,14 +1711,14 @@ packages: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} dev: false - /@types/yargs@17.0.22: - resolution: {integrity: sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==} + /@types/yargs@17.0.24: + resolution: {integrity: sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==} dependencies: '@types/yargs-parser': 21.0.0 dev: false - /@typescript-eslint/eslint-plugin@5.54.0(@typescript-eslint/parser@5.54.0)(eslint@8.35.0)(typescript@4.9.5): - resolution: {integrity: sha512-+hSN9BdSr629RF02d7mMtXhAJvDTyCbprNYJKrXETlul/Aml6YZwd90XioVbjejQeHbb3R8Dg0CkRgoJDxo8aw==} + /@typescript-eslint/eslint-plugin@5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@4.9.5): + resolution: {integrity: sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: '@typescript-eslint/parser': ^5.0.0 @@ -1704,25 +1728,25 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': 5.54.0(eslint@8.35.0)(typescript@4.9.5) - '@typescript-eslint/scope-manager': 5.54.0 - '@typescript-eslint/type-utils': 5.54.0(eslint@8.35.0)(typescript@4.9.5) - '@typescript-eslint/utils': 5.54.0(eslint@8.35.0)(typescript@4.9.5) + '@eslint-community/regexpp': 4.5.1 + '@typescript-eslint/parser': 5.59.5(eslint@8.40.0)(typescript@4.9.5) + '@typescript-eslint/scope-manager': 5.59.5 + '@typescript-eslint/type-utils': 5.59.5(eslint@8.40.0)(typescript@4.9.5) + '@typescript-eslint/utils': 5.59.5(eslint@8.40.0)(typescript@4.9.5) debug: 4.3.4 - eslint: 8.35.0 + eslint: 8.40.0 grapheme-splitter: 1.0.4 ignore: 5.2.4 natural-compare-lite: 1.4.0 - regexpp: 3.2.0 - semver: 7.3.8 + semver: 7.5.1 tsutils: 3.21.0(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@5.54.0(eslint@8.35.0)(typescript@4.9.5): - resolution: {integrity: sha512-aAVL3Mu2qTi+h/r04WI/5PfNWvO6pdhpeMRWk9R7rEV4mwJNzoWf5CCU5vDKBsPIFQFjEq1xg7XBI2rjiMXQbQ==} + /@typescript-eslint/parser@5.59.5(eslint@8.40.0)(typescript@4.9.5): + resolution: {integrity: sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1731,26 +1755,26 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.54.0 - '@typescript-eslint/types': 5.54.0 - '@typescript-eslint/typescript-estree': 5.54.0(typescript@4.9.5) + '@typescript-eslint/scope-manager': 5.59.5 + '@typescript-eslint/types': 5.59.5 + '@typescript-eslint/typescript-estree': 5.59.5(typescript@4.9.5) debug: 4.3.4 - eslint: 8.35.0 + eslint: 8.40.0 typescript: 4.9.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager@5.54.0: - resolution: {integrity: sha512-VTPYNZ7vaWtYna9M4oD42zENOBrb+ZYyCNdFs949GcN8Miwn37b8b7eMj+EZaq7VK9fx0Jd+JhmkhjFhvnovhg==} + /@typescript-eslint/scope-manager@5.59.5: + resolution: {integrity: sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.54.0 - '@typescript-eslint/visitor-keys': 5.54.0 + '@typescript-eslint/types': 5.59.5 + '@typescript-eslint/visitor-keys': 5.59.5 dev: true - /@typescript-eslint/type-utils@5.54.0(eslint@8.35.0)(typescript@4.9.5): - resolution: {integrity: sha512-WI+WMJ8+oS+LyflqsD4nlXMsVdzTMYTxl16myXPaCXnSgc7LWwMsjxQFZCK/rVmTZ3FN71Ct78ehO9bRC7erYQ==} + /@typescript-eslint/type-utils@5.59.5(eslint@8.40.0)(typescript@4.9.5): + resolution: {integrity: sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -1759,23 +1783,23 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.54.0(typescript@4.9.5) - '@typescript-eslint/utils': 5.54.0(eslint@8.35.0)(typescript@4.9.5) + '@typescript-eslint/typescript-estree': 5.59.5(typescript@4.9.5) + '@typescript-eslint/utils': 5.59.5(eslint@8.40.0)(typescript@4.9.5) debug: 4.3.4 - eslint: 8.35.0 + eslint: 8.40.0 tsutils: 3.21.0(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@5.54.0: - resolution: {integrity: sha512-nExy+fDCBEgqblasfeE3aQ3NuafBUxZxgxXcYfzYRZFHdVvk5q60KhCSkG0noHgHRo/xQ/BOzURLZAafFpTkmQ==} + /@typescript-eslint/types@5.59.5: + resolution: {integrity: sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree@5.54.0(typescript@4.9.5): - resolution: {integrity: sha512-X2rJG97Wj/VRo5YxJ8Qx26Zqf0RRKsVHd4sav8NElhbZzhpBI8jU54i6hfo9eheumj4oO4dcRN1B/zIVEqR/MQ==} + /@typescript-eslint/typescript-estree@5.59.5(typescript@4.9.5): + resolution: {integrity: sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -1783,58 +1807,58 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.54.0 - '@typescript-eslint/visitor-keys': 5.54.0 + '@typescript-eslint/types': 5.59.5 + '@typescript-eslint/visitor-keys': 5.59.5 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.3.8 + semver: 7.5.1 tsutils: 3.21.0(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@5.54.0(eslint@8.35.0)(typescript@4.9.5): - resolution: {integrity: sha512-cuwm8D/Z/7AuyAeJ+T0r4WZmlnlxQ8wt7C7fLpFlKMR+dY6QO79Cq1WpJhvZbMA4ZeZGHiRWnht7ZJ8qkdAunw==} + /@typescript-eslint/utils@5.59.5(eslint@8.40.0)(typescript@4.9.5): + resolution: {integrity: sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.40.0) '@types/json-schema': 7.0.11 - '@types/semver': 7.3.13 - '@typescript-eslint/scope-manager': 5.54.0 - '@typescript-eslint/types': 5.54.0 - '@typescript-eslint/typescript-estree': 5.54.0(typescript@4.9.5) - eslint: 8.35.0 + '@types/semver': 7.5.0 + '@typescript-eslint/scope-manager': 5.59.5 + '@typescript-eslint/types': 5.59.5 + '@typescript-eslint/typescript-estree': 5.59.5(typescript@4.9.5) + eslint: 8.40.0 eslint-scope: 5.1.1 - eslint-utils: 3.0.0(eslint@8.35.0) - semver: 7.3.8 + semver: 7.5.1 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys@5.54.0: - resolution: {integrity: sha512-xu4wT7aRCakGINTLGeyGqDn+78BwFlggwBjnHa1ar/KaGagnmwLYmlrXIrgAaQ3AE1Vd6nLfKASm7LrFHNbKGA==} + /@typescript-eslint/visitor-keys@5.59.5: + resolution: {integrity: sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.54.0 - eslint-visitor-keys: 3.3.0 + '@typescript-eslint/types': 5.59.5 + eslint-visitor-keys: 3.4.1 dev: true - /@vitejs/plugin-react@3.1.0(vite@4.1.4): + /@vitejs/plugin-react@3.1.0(vite@4.3.5): resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.1.0-beta.0 dependencies: - '@babel/core': 7.21.0 - '@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.21.0) - '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.0) + '@babel/core': 7.21.8 + '@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.21.8) + '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.8) magic-string: 0.27.0 react-refresh: 0.14.0 - vite: 4.1.4(@types/node@18.14.6) + vite: 4.3.5(@types/node@18.16.9) transitivePeerDependencies: - supports-color dev: true @@ -1851,25 +1875,6 @@ packages: acorn: 8.8.2 dev: true - /acorn-node@1.8.2: - resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==} - dependencies: - acorn: 7.4.1 - acorn-walk: 7.2.0 - xtend: 4.0.2 - dev: true - - /acorn-walk@7.2.0: - resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} - engines: {node: '>=0.4.0'} - dev: true - - /acorn@7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - /acorn@8.8.2: resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} @@ -1913,6 +1918,10 @@ packages: engines: {node: '>=10'} dev: false + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: true + /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1934,14 +1943,21 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true + /array-buffer-byte-length@1.0.0: + resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + dependencies: + call-bind: 1.0.2 + is-array-buffer: 3.0.2 + dev: true + /array-includes@3.1.6: resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.1 - get-intrinsic: 1.2.0 + es-abstract: 1.21.2 + get-intrinsic: 1.2.1 is-string: 1.0.7 dev: true @@ -1956,7 +1972,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.1 + es-abstract: 1.21.2 es-shim-unscopables: 1.0.0 dev: true @@ -1965,24 +1981,24 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.1 + es-abstract: 1.21.2 es-shim-unscopables: 1.0.0 - get-intrinsic: 1.2.0 + get-intrinsic: 1.2.1 dev: true - /autoprefixer@10.4.13(postcss@8.4.21): - resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} + /autoprefixer@10.4.14(postcss@8.4.23): + resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: postcss: ^8.1.0 dependencies: browserslist: 4.21.5 - caniuse-lite: 1.0.30001460 + caniuse-lite: 1.0.30001487 fraction.js: 4.2.0 normalize-range: 0.1.2 picocolors: 1.0.0 - postcss: 8.4.21 + postcss: 8.4.23 postcss-value-parser: 4.2.0 dev: true @@ -1991,19 +2007,19 @@ packages: engines: {node: '>= 0.4'} dev: true - /babel-jest@29.5.0(@babel/core@7.21.0): + /babel-jest@29.5.0(@babel/core@7.21.8): resolution: {integrity: sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.8.0 dependencies: - '@babel/core': 7.21.0 + '@babel/core': 7.21.8 '@jest/transform': 29.5.0 '@types/babel__core': 7.20.0 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.5.0(@babel/core@7.21.0) + babel-preset-jest: 29.5.0(@babel/core@7.21.8) chalk: 4.1.2 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 slash: 3.0.0 transitivePeerDependencies: - supports-color @@ -2013,7 +2029,7 @@ packages: resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} engines: {node: '>=8'} dependencies: - '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-plugin-utils': 7.21.5 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.1 @@ -2027,49 +2043,49 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/template': 7.20.7 - '@babel/types': 7.21.2 + '@babel/types': 7.21.5 '@types/babel__core': 7.20.0 - '@types/babel__traverse': 7.18.3 + '@types/babel__traverse': 7.18.5 dev: false /babel-plugin-macros@3.1.0: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.21.5 cosmiconfig: 7.1.0 - resolve: 1.22.1 + resolve: 1.22.2 dev: false - /babel-preset-current-node-syntax@1.0.1(@babel/core@7.21.0): + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.21.8): resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.21.0 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.21.0) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.21.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.21.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.21.0) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.21.0) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.21.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.21.0) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.21.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.21.0) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.21.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.21.0) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.21.0) + '@babel/core': 7.21.8 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.21.8) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.21.8) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.21.8) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.21.8) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.21.8) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.21.8) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.21.8) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.21.8) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.21.8) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.21.8) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.21.8) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.21.8) dev: false - /babel-preset-jest@29.5.0(@babel/core@7.21.0): + /babel-preset-jest@29.5.0(@babel/core@7.21.8): resolution: {integrity: sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.21.0 + '@babel/core': 7.21.8 babel-plugin-jest-hoist: 29.5.0 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.21.0) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.21.8) dev: false /balanced-match@1.0.2: @@ -2097,10 +2113,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001460 - electron-to-chromium: 1.4.320 + caniuse-lite: 1.0.30001487 + electron-to-chromium: 1.4.394 node-releases: 2.0.10 - update-browserslist-db: 1.0.10(browserslist@4.21.5) + update-browserslist-db: 1.0.11(browserslist@4.21.5) /bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} @@ -2116,7 +2132,7 @@ packages: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: function-bind: 1.1.1 - get-intrinsic: 1.2.0 + get-intrinsic: 1.2.1 dev: true /callsites@3.1.0: @@ -2138,8 +2154,8 @@ packages: engines: {node: '>=10'} dev: false - /caniuse-lite@1.0.30001460: - resolution: {integrity: sha512-Bud7abqjvEjipUkpLs4D7gR0l8hBYBHoa+tGtKJHvT2AYzLp1z7EmVkUT4ERpVUfca8S2HGIVs883D8pUH1ZzQ==} + /caniuse-lite@1.0.30001487: + resolution: {integrity: sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==} /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -2225,6 +2241,11 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: true + /compute-scroll-into-view@1.0.20: resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} dev: false @@ -2261,7 +2282,7 @@ packages: /css-box-model@1.2.1: resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==} dependencies: - tiny-invariant: 1.0.6 + tiny-invariant: 1.3.1 dev: false /cssesc@3.0.0: @@ -2270,8 +2291,8 @@ packages: hasBin: true dev: true - /csstype@3.1.1: - resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} + /csstype@3.1.2: + resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} /dayjs@1.11.7: resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==} @@ -2296,8 +2317,8 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true - /deepmerge@4.3.0: - resolution: {integrity: sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==} + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} dev: false @@ -2309,25 +2330,11 @@ packages: object-keys: 1.1.1 dev: true - /defined@1.0.1: - resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} - dev: true - /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} dev: false - /detective@5.2.1: - resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} - engines: {node: '>=0.8.0'} - hasBin: true - dependencies: - acorn-node: 1.8.2 - defined: 1.0.1 - minimist: 1.2.8 - dev: true - /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true @@ -2370,12 +2377,12 @@ packages: /dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dependencies: - '@babel/runtime': 7.21.0 - csstype: 3.1.1 + '@babel/runtime': 7.21.5 + csstype: 3.1.2 dev: false - /electron-to-chromium@1.4.320: - resolution: {integrity: sha512-h70iRscrNluMZPVICXYl5SSB+rBKo22XfuIS1ER0OQxQZpKTnFpuS6coj7wY9M/3trv7OR88rRMOlKmRvDty7Q==} + /electron-to-chromium@1.4.394: + resolution: {integrity: sha512-0IbC2cfr8w5LxTz+nmn2cJTGafsK9iauV2r5A5scfzyovqLrxuLoxOHE5OBobP3oVIggJT+0JfKnw9sm87c8Hw==} /emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -2396,17 +2403,17 @@ packages: is-arrayish: 0.2.1 dev: false - /es-abstract@1.21.1: - resolution: {integrity: sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==} + /es-abstract@1.21.2: + resolution: {integrity: sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==} engines: {node: '>= 0.4'} dependencies: + array-buffer-byte-length: 1.0.0 available-typed-arrays: 1.0.5 call-bind: 1.0.2 es-set-tostringtag: 2.0.1 es-to-primitive: 1.2.1 - function-bind: 1.1.1 function.prototype.name: 1.1.5 - get-intrinsic: 1.2.0 + get-intrinsic: 1.2.1 get-symbol-description: 1.0.0 globalthis: 1.0.3 gopd: 1.0.1 @@ -2426,8 +2433,9 @@ packages: object-inspect: 1.12.3 object-keys: 1.1.1 object.assign: 4.1.4 - regexp.prototype.flags: 1.4.3 + regexp.prototype.flags: 1.5.0 safe-regex-test: 1.0.0 + string.prototype.trim: 1.2.7 string.prototype.trimend: 1.0.6 string.prototype.trimstart: 1.0.6 typed-array-length: 1.0.4 @@ -2439,7 +2447,7 @@ packages: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} engines: {node: '>= 0.4'} dependencies: - get-intrinsic: 1.2.0 + get-intrinsic: 1.2.1 has: 1.0.3 has-tostringtag: 1.0.0 dev: true @@ -2459,34 +2467,34 @@ packages: is-symbol: 1.0.4 dev: true - /esbuild@0.16.17: - resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==} + /esbuild@0.17.19: + resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.16.17 - '@esbuild/android-arm64': 0.16.17 - '@esbuild/android-x64': 0.16.17 - '@esbuild/darwin-arm64': 0.16.17 - '@esbuild/darwin-x64': 0.16.17 - '@esbuild/freebsd-arm64': 0.16.17 - '@esbuild/freebsd-x64': 0.16.17 - '@esbuild/linux-arm': 0.16.17 - '@esbuild/linux-arm64': 0.16.17 - '@esbuild/linux-ia32': 0.16.17 - '@esbuild/linux-loong64': 0.16.17 - '@esbuild/linux-mips64el': 0.16.17 - '@esbuild/linux-ppc64': 0.16.17 - '@esbuild/linux-riscv64': 0.16.17 - '@esbuild/linux-s390x': 0.16.17 - '@esbuild/linux-x64': 0.16.17 - '@esbuild/netbsd-x64': 0.16.17 - '@esbuild/openbsd-x64': 0.16.17 - '@esbuild/sunos-x64': 0.16.17 - '@esbuild/win32-arm64': 0.16.17 - '@esbuild/win32-ia32': 0.16.17 - '@esbuild/win32-x64': 0.16.17 + '@esbuild/android-arm': 0.17.19 + '@esbuild/android-arm64': 0.17.19 + '@esbuild/android-x64': 0.17.19 + '@esbuild/darwin-arm64': 0.17.19 + '@esbuild/darwin-x64': 0.17.19 + '@esbuild/freebsd-arm64': 0.17.19 + '@esbuild/freebsd-x64': 0.17.19 + '@esbuild/linux-arm': 0.17.19 + '@esbuild/linux-arm64': 0.17.19 + '@esbuild/linux-ia32': 0.17.19 + '@esbuild/linux-loong64': 0.17.19 + '@esbuild/linux-mips64el': 0.17.19 + '@esbuild/linux-ppc64': 0.17.19 + '@esbuild/linux-riscv64': 0.17.19 + '@esbuild/linux-s390x': 0.17.19 + '@esbuild/linux-x64': 0.17.19 + '@esbuild/netbsd-x64': 0.17.19 + '@esbuild/openbsd-x64': 0.17.19 + '@esbuild/sunos-x64': 0.17.19 + '@esbuild/win32-arm64': 0.17.19 + '@esbuild/win32-ia32': 0.17.19 + '@esbuild/win32-x64': 0.17.19 dev: true /escalade@3.1.1: @@ -2506,16 +2514,16 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - /eslint-plugin-react-hooks@4.6.0(eslint@8.35.0): + /eslint-plugin-react-hooks@4.6.0(eslint@8.40.0): resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 dependencies: - eslint: 8.35.0 + eslint: 8.40.0 dev: true - /eslint-plugin-react@7.32.2(eslint@8.35.0): + /eslint-plugin-react@7.32.2(eslint@8.40.0): resolution: {integrity: sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==} engines: {node: '>=4'} peerDependencies: @@ -2525,7 +2533,7 @@ packages: array.prototype.flatmap: 1.3.1 array.prototype.tosorted: 1.1.1 doctrine: 2.1.0 - eslint: 8.35.0 + eslint: 8.40.0 estraverse: 5.3.0 jsx-ast-utils: 3.3.3 minimatch: 3.1.2 @@ -2547,41 +2555,28 @@ packages: estraverse: 4.3.0 dev: true - /eslint-scope@7.1.1: - resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} + /eslint-scope@7.2.0: + resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 dev: true - /eslint-utils@3.0.0(eslint@8.35.0): - resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} - engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} - peerDependencies: - eslint: '>=5' - dependencies: - eslint: 8.35.0 - eslint-visitor-keys: 2.1.0 - dev: true - - /eslint-visitor-keys@2.1.0: - resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} - engines: {node: '>=10'} - dev: true - - /eslint-visitor-keys@3.3.0: - resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} + /eslint-visitor-keys@3.4.1: + resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.35.0: - resolution: {integrity: sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==} + /eslint@8.40.0: + resolution: {integrity: sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint/eslintrc': 2.0.0 - '@eslint/js': 8.35.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.40.0) + '@eslint-community/regexpp': 4.5.1 + '@eslint/eslintrc': 2.0.3 + '@eslint/js': 8.40.0 '@humanwhocodes/config-array': 0.11.8 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -2591,10 +2586,9 @@ packages: debug: 4.3.4 doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.1.1 - eslint-utils: 3.0.0(eslint@8.35.0) - eslint-visitor-keys: 3.3.0 - espree: 9.4.1 + eslint-scope: 7.2.0 + eslint-visitor-keys: 3.4.1 + espree: 9.5.2 esquery: 1.5.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -2608,7 +2602,7 @@ packages: imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 - js-sdsl: 4.3.0 + js-sdsl: 4.4.0 js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 @@ -2616,7 +2610,6 @@ packages: minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.1 - regexpp: 3.2.0 strip-ansi: 6.0.1 strip-json-comments: 3.1.1 text-table: 0.2.0 @@ -2624,13 +2617,13 @@ packages: - supports-color dev: true - /espree@9.4.1: - resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} + /espree@9.5.2: + resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: acorn: 8.8.2 acorn-jsx: 5.3.2(acorn@8.8.2) - eslint-visitor-keys: 3.3.0 + eslint-visitor-keys: 3.4.1 dev: true /esprima@4.0.1: @@ -2812,7 +2805,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.1 + es-abstract: 1.21.2 functions-have-names: 1.2.3 dev: true @@ -2829,11 +2822,12 @@ packages: engines: {node: 6.* || 8.* || >= 10.*} dev: false - /get-intrinsic@1.2.0: - resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} + /get-intrinsic@1.2.1: + resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} dependencies: function-bind: 1.1.1 has: 1.0.3 + has-proto: 1.0.1 has-symbols: 1.0.3 dev: true @@ -2852,7 +2846,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - get-intrinsic: 1.2.0 + get-intrinsic: 1.2.1 dev: true /get-user-locale@2.2.1: @@ -2876,6 +2870,17 @@ packages: is-glob: 4.0.3 dev: true + /glob@7.1.6: + resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} dependencies: @@ -2923,11 +2928,11 @@ packages: /gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: - get-intrinsic: 1.2.0 + get-intrinsic: 1.2.1 dev: true - /graceful-fs@4.2.10: - resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} dev: false /grapheme-splitter@1.0.4: @@ -2949,7 +2954,7 @@ packages: /has-property-descriptors@1.0.0: resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} dependencies: - get-intrinsic: 1.2.0 + get-intrinsic: 1.2.1 dev: true /has-proto@1.0.1: @@ -2999,13 +3004,13 @@ packages: /i18next-browser-languagedetector@7.0.1: resolution: {integrity: sha512-Pa5kFwaczXJAeHE56CHG2aWzFBMJNUNghf0Pm4SwSrEMps/PTKqW90EYWlIvhuYStf3Sn1K0vw+gH3+TLdkH1g==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.21.5 dev: false - /i18next@22.4.10: - resolution: {integrity: sha512-3EqgGK6fAJRjnGgfkNSStl4mYLCjUoJID338yVyLMj5APT67HUtWoqSayZewiiC5elzMUB1VEUwcmSCoeQcNEA==} + /i18next@22.4.15: + resolution: {integrity: sha512-yYudtbFrrmWKLEhl6jvKUYyYunj4bTBCe2qIUYAxbXoPusY7YmdwPvOE6fx6UIfWvmlbCWDItr7wIs8KEBZ5Zg==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.21.5 dev: false /ignore@5.2.4: @@ -3013,8 +3018,8 @@ packages: engines: {node: '>= 4'} dev: true - /immer@9.0.19: - resolution: {integrity: sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==} + /immer@9.0.21: + resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} dev: false /import-fresh@3.3.0: @@ -3050,7 +3055,7 @@ packages: resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} engines: {node: '>= 0.4'} dependencies: - get-intrinsic: 1.2.0 + get-intrinsic: 1.2.1 has: 1.0.3 side-channel: 1.0.4 dev: true @@ -3059,7 +3064,7 @@ packages: resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} dependencies: call-bind: 1.0.2 - get-intrinsic: 1.2.0 + get-intrinsic: 1.2.1 is-typed-array: 1.1.10 dev: true @@ -3093,8 +3098,8 @@ packages: engines: {node: '>= 0.4'} dev: true - /is-core-module@2.11.0: - resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} + /is-core-module@2.12.0: + resolution: {integrity: sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==} dependencies: has: 1.0.3 @@ -3227,8 +3232,8 @@ packages: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} dependencies: - '@babel/core': 7.21.0 - '@babel/parser': 7.21.2 + '@babel/core': 7.21.8 + '@babel/parser': 7.21.8 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 6.3.0 @@ -3280,7 +3285,7 @@ packages: '@jest/expect': 29.5.0 '@jest/test-result': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.14.6 + '@types/node': 18.16.9 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 @@ -3293,14 +3298,14 @@ packages: jest-util: 29.5.0 p-limit: 3.1.0 pretty-format: 29.5.0 - pure-rand: 6.0.1 + pure-rand: 6.0.2 slash: 3.0.0 stack-utils: 2.0.6 transitivePeerDependencies: - supports-color dev: false - /jest-cli@29.5.0(@types/node@18.14.6): + /jest-cli@29.5.0(@types/node@18.16.9): resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -3315,20 +3320,20 @@ packages: '@jest/types': 29.5.0 chalk: 4.1.2 exit: 0.1.2 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 import-local: 3.1.0 - jest-config: 29.5.0(@types/node@18.14.6) + jest-config: 29.5.0(@types/node@18.16.9) jest-util: 29.5.0 jest-validate: 29.5.0 prompts: 2.4.2 - yargs: 17.7.1 + yargs: 17.7.2 transitivePeerDependencies: - '@types/node' - supports-color - ts-node dev: false - /jest-config@29.5.0(@types/node@18.14.6): + /jest-config@29.5.0(@types/node@18.16.9): resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -3340,16 +3345,16 @@ packages: ts-node: optional: true dependencies: - '@babel/core': 7.21.0 + '@babel/core': 7.21.8 '@jest/test-sequencer': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.14.6 - babel-jest: 29.5.0(@babel/core@7.21.0) + '@types/node': 18.16.9 + babel-jest: 29.5.0(@babel/core@7.21.8) chalk: 4.1.2 ci-info: 3.8.0 - deepmerge: 4.3.0 + deepmerge: 4.3.1 glob: 7.2.3 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-circus: 29.5.0 jest-environment-node: 29.5.0 jest-get-type: 29.4.3 @@ -3402,7 +3407,7 @@ packages: '@jest/environment': 29.5.0 '@jest/fake-timers': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.14.6 + '@types/node': 18.16.9 jest-mock: 29.5.0 jest-util: 29.5.0 dev: false @@ -3418,10 +3423,10 @@ packages: dependencies: '@jest/types': 29.5.0 '@types/graceful-fs': 4.1.6 - '@types/node': 18.14.6 + '@types/node': 18.16.9 anymatch: 3.1.3 fb-watchman: 2.0.2 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-regex-util: 29.4.3 jest-util: 29.5.0 jest-worker: 29.5.0 @@ -3453,11 +3458,11 @@ packages: resolution: {integrity: sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/code-frame': 7.18.6 + '@babel/code-frame': 7.21.4 '@jest/types': 29.5.0 '@types/stack-utils': 2.0.1 chalk: 4.1.2 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 micromatch: 4.0.5 pretty-format: 29.5.0 slash: 3.0.0 @@ -3469,7 +3474,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.5.0 - '@types/node': 18.14.6 + '@types/node': 18.16.9 jest-util: 29.5.0 dev: false @@ -3505,13 +3510,13 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: chalk: 4.1.2 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-haste-map: 29.5.0 jest-pnp-resolver: 1.2.3(jest-resolve@29.5.0) jest-util: 29.5.0 jest-validate: 29.5.0 - resolve: 1.22.1 - resolve.exports: 2.0.0 + resolve: 1.22.2 + resolve.exports: 2.0.2 slash: 3.0.0 dev: false @@ -3524,10 +3529,10 @@ packages: '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.14.6 + '@types/node': 18.16.9 chalk: 4.1.2 emittery: 0.13.1 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-docblock: 29.4.3 jest-environment-node: 29.5.0 jest-haste-map: 29.5.0 @@ -3555,12 +3560,12 @@ packages: '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.14.6 + '@types/node': 18.16.9 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 glob: 7.2.3 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-haste-map: 29.5.0 jest-message-util: 29.5.0 jest-mock: 29.5.0 @@ -3578,21 +3583,21 @@ packages: resolution: {integrity: sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.21.0 - '@babel/generator': 7.21.1 - '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.21.0) - '@babel/plugin-syntax-typescript': 7.20.0(@babel/core@7.21.0) - '@babel/traverse': 7.21.2 - '@babel/types': 7.21.2 + '@babel/core': 7.21.8 + '@babel/generator': 7.21.5 + '@babel/plugin-syntax-jsx': 7.21.4(@babel/core@7.21.8) + '@babel/plugin-syntax-typescript': 7.21.4(@babel/core@7.21.8) + '@babel/traverse': 7.21.5 + '@babel/types': 7.21.5 '@jest/expect-utils': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@types/babel__traverse': 7.18.3 + '@types/babel__traverse': 7.18.5 '@types/prettier': 2.7.2 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.21.0) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.21.8) chalk: 4.1.2 expect: 29.5.0 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jest-diff: 29.5.0 jest-get-type: 29.4.3 jest-matcher-utils: 29.5.0 @@ -3600,7 +3605,7 @@ packages: jest-util: 29.5.0 natural-compare: 1.4.0 pretty-format: 29.5.0 - semver: 7.3.8 + semver: 7.5.1 transitivePeerDependencies: - supports-color dev: false @@ -3610,10 +3615,10 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.5.0 - '@types/node': 18.14.6 + '@types/node': 18.16.9 chalk: 4.1.2 ci-info: 3.8.0 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 picomatch: 2.3.1 dev: false @@ -3635,7 +3640,7 @@ packages: dependencies: '@jest/test-result': 29.5.0 '@jest/types': 29.5.0 - '@types/node': 18.14.6 + '@types/node': 18.16.9 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -3647,13 +3652,13 @@ packages: resolution: {integrity: sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 18.14.6 + '@types/node': 18.16.9 jest-util: 29.5.0 merge-stream: 2.0.0 supports-color: 8.1.1 dev: false - /jest@29.5.0(@types/node@18.14.6): + /jest@29.5.0(@types/node@18.16.9): resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -3666,15 +3671,20 @@ packages: '@jest/core': 29.5.0 '@jest/types': 29.5.0 import-local: 3.1.0 - jest-cli: 29.5.0(@types/node@18.14.6) + jest-cli: 29.5.0(@types/node@18.16.9) transitivePeerDependencies: - '@types/node' - supports-color - ts-node dev: false - /js-sdsl@4.3.0: - resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} + /jiti@1.18.2: + resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==} + hasBin: true + dev: true + + /js-sdsl@4.4.0: + resolution: {integrity: sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==} dev: true /js-tokens@4.0.0: @@ -3743,8 +3753,8 @@ packages: type-check: 0.4.0 dev: true - /lib0@0.2.73: - resolution: {integrity: sha512-aJJIElCLWnHMcYZPtsM07QoSfHwpxCy4VUzBYGXFYEmh/h2QS5uZNbCCfL0CqnkOE30b7Tp9DVfjXag+3qzZjQ==} + /lib0@0.2.74: + resolution: {integrity: sha512-roj9i46/JwG5ik5KNTkxP2IytlnrssAkD/OhlAVtE+GqectrdkfR+pttszVLrOzMDeXNs1MPt6yo66MUolWSiA==} engines: {node: '>=14'} hasBin: true dependencies: @@ -3758,7 +3768,6 @@ packages: /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: false /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} @@ -3807,7 +3816,7 @@ packages: resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} engines: {node: '>=12'} dependencies: - '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/sourcemap-codec': 1.4.15 dev: true /make-dir@3.1.0: @@ -3853,21 +3862,25 @@ packages: dependencies: brace-expansion: 1.1.11 - /minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: true - /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - /nanoid@3.3.4: - resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + dev: true + + /nanoid@3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true dev: true - /nanoid@4.0.1: - resolution: {integrity: sha512-udKGtCCUafD3nQtJg9wBhRP3KMbPglUsgV5JVsXhvyBs/oefqb4sqMEhKBBgqZncYowu58p1prsZQBYvAj/Gww==} + /nanoid@4.0.2: + resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==} engines: {node: ^14 || ^16 || >=18} hasBin: true dev: false @@ -3936,7 +3949,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.1 + es-abstract: 1.21.2 dev: true /object.fromentries@2.0.6: @@ -3945,14 +3958,14 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.1 + es-abstract: 1.21.2 dev: true /object.hasown@1.1.2: resolution: {integrity: sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==} dependencies: define-properties: 1.2.0 - es-abstract: 1.21.1 + es-abstract: 1.21.2 dev: true /object.values@1.1.6: @@ -3961,7 +3974,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.1 + es-abstract: 1.21.2 dev: true /once@1.4.0: @@ -4030,7 +4043,7 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.18.6 + '@babel/code-frame': 7.21.4 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -4070,7 +4083,6 @@ packages: /pirates@4.0.5: resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} engines: {node: '>= 6'} - dev: false /pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} @@ -4079,31 +4091,31 @@ packages: find-up: 4.1.0 dev: false - /postcss-import@14.1.0(postcss@8.4.21): - resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} - engines: {node: '>=10.0.0'} + /postcss-import@15.1.0(postcss@8.4.23): + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} peerDependencies: postcss: ^8.0.0 dependencies: - postcss: 8.4.21 + postcss: 8.4.23 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.1 + resolve: 1.22.2 dev: true - /postcss-js@4.0.1(postcss@8.4.21): + /postcss-js@4.0.1(postcss@8.4.23): resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} engines: {node: ^12 || ^14 || >= 16} peerDependencies: postcss: ^8.4.21 dependencies: camelcase-css: 2.0.1 - postcss: 8.4.21 + postcss: 8.4.23 dev: true - /postcss-load-config@3.1.4(postcss@8.4.21): - resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} - engines: {node: '>= 10'} + /postcss-load-config@4.0.1(postcss@8.4.23): + resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} + engines: {node: '>= 14'} peerDependencies: postcss: '>=8.0.9' ts-node: '>=9.0.0' @@ -4114,22 +4126,22 @@ packages: optional: true dependencies: lilconfig: 2.1.0 - postcss: 8.4.21 - yaml: 1.10.2 + postcss: 8.4.23 + yaml: 2.2.2 dev: true - /postcss-nested@6.0.0(postcss@8.4.21): - resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} + /postcss-nested@6.0.1(postcss@8.4.23): + resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 dependencies: - postcss: 8.4.21 - postcss-selector-parser: 6.0.11 + postcss: 8.4.23 + postcss-selector-parser: 6.0.12 dev: true - /postcss-selector-parser@6.0.11: - resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==} + /postcss-selector-parser@6.0.12: + resolution: {integrity: sha512-NdxGCAZdRrwVI1sy59+Wzrh+pMMHxapGnpfenDVlMEXoOcvt4pGE0JLK9YY2F5dLxcFYA/YbVQKhcGU+FtSYQg==} engines: {node: '>=4'} dependencies: cssesc: 3.0.0 @@ -4140,11 +4152,11 @@ packages: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} dev: true - /postcss@8.4.21: - resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + /postcss@8.4.23: + resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==} engines: {node: ^10 || ^12 || >=14} dependencies: - nanoid: 3.3.4 + nanoid: 3.3.6 picocolors: 1.0.0 source-map-js: 1.0.2 dev: true @@ -4154,12 +4166,11 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /prettier-plugin-tailwindcss@0.2.4(prettier@2.8.4): - resolution: {integrity: sha512-wMyugRI2yD8gqmMpZSS8kTA0gGeKozX/R+w8iWE+yiCZL09zY0SvfiHfHabNhjGhzxlQ2S2VuTxPE3T72vppCQ==} + /prettier-plugin-tailwindcss@0.2.8(prettier@2.8.4): + resolution: {integrity: sha512-KgPcEnJeIijlMjsA6WwYgRs5rh3/q76oInqtMXBA/EMcamrcYJpyhtRhyX1ayT9hnHlHTuO8sIifHF10WuSDKg==} engines: {node: '>=12.17.0'} peerDependencies: '@ianvs/prettier-plugin-sort-imports': '*' - '@prettier/plugin-php': '*' '@prettier/plugin-pug': '*' '@shopify/prettier-plugin-liquid': '*' '@shufo/prettier-plugin-blade': '*' @@ -4177,8 +4188,6 @@ packages: peerDependenciesMeta: '@ianvs/prettier-plugin-sort-imports': optional: true - '@prettier/plugin-php': - optional: true '@prettier/plugin-pug': optional: true '@shopify/prettier-plugin-liquid': @@ -4260,19 +4269,14 @@ packages: engines: {node: '>=6'} dev: true - /pure-rand@6.0.1: - resolution: {integrity: sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg==} + /pure-rand@6.0.2: + resolution: {integrity: sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==} dev: false /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true - /quick-lru@5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - dev: true - /raf-schd@4.0.3: resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==} dev: false @@ -4283,7 +4287,7 @@ packages: react: ^16.8.5 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.5 || ^17.0.0 || ^18.0.0 dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.21.5 css-box-model: 1.2.1 memoize-one: 5.2.1 raf-schd: 4.0.3 @@ -4302,7 +4306,7 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@types/react': 18.0.28 + '@types/react': 18.2.6 '@wojtekmaj/date-utils': 1.1.3 clsx: 1.2.1 get-user-locale: 2.2.1 @@ -4327,12 +4331,12 @@ packages: peerDependencies: react: '>=16.13.1' dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.21.5 react: 18.2.0 dev: false - /react-i18next@12.2.0(i18next@22.4.10)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-5XeVgSygaGfyFmDd2WcXvINRw2WEC1XviW1LXY/xLOEMzsCFRwKqfnHN+hUjla8ZipbVJR27GCMSuTr0BhBBBQ==} + /react-i18next@12.2.2(i18next@22.4.15)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-KBB6buBmVKXUWNxXHdnthp+38gPyBT46hJCAIQ8rX19NFL/m2ahte2KARfIDf2tMnSAL7wwck6eDOd/9zn6aFg==} peerDependencies: i18next: '>= 19.0.0' react: '>= 16.8.0' @@ -4344,9 +4348,9 @@ packages: react-native: optional: true dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.21.5 html-parse-stringify: 3.0.1 - i18next: 22.4.10 + i18next: 22.4.15 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false @@ -4374,7 +4378,7 @@ packages: react-native: optional: true dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.21.5 '@types/react-redux': 7.1.25 hoist-non-react-statics: 3.3.2 loose-envify: 1.4.0 @@ -4384,7 +4388,7 @@ packages: react-is: 17.0.2 dev: false - /react-redux@8.0.5(@types/react-dom@18.0.11)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1): + /react-redux@8.0.5(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1): resolution: {integrity: sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==} peerDependencies: '@types/react': ^16.8 || ^17.0 || ^18.0 @@ -4405,10 +4409,10 @@ packages: redux: optional: true dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.21.5 '@types/hoist-non-react-statics': 3.3.1 - '@types/react': 18.0.28 - '@types/react-dom': 18.0.11 + '@types/react': 18.2.6 + '@types/react-dom': 18.2.4 '@types/use-sync-external-store': 0.0.3 hoist-non-react-statics: 3.3.2 react: 18.2.0 @@ -4423,26 +4427,26 @@ packages: engines: {node: '>=0.10.0'} dev: true - /react-router-dom@6.8.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-N/oAF1Shd7g4tWy+75IIufCGsHBqT74tnzHQhbiUTYILYF0Blk65cg+HPZqwC+6SqEyx033nKqU7by38v3lBZg==} + /react-router-dom@6.11.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-dPC2MhoPeTQ1YUOt5uIK376SMNWbwUxYRWk2ZmTT4fZfwlOvabF8uduRKKJIyfkCZvMgiF0GSCQckmkGGijIrg==} engines: {node: '>=14'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' dependencies: - '@remix-run/router': 1.3.3 + '@remix-run/router': 1.6.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-router: 6.8.2(react@18.2.0) + react-router: 6.11.1(react@18.2.0) dev: false - /react-router@6.8.2(react@18.2.0): - resolution: {integrity: sha512-lF7S0UmXI5Pd8bmHvMdPKI4u4S5McxmHnzJhrYi9ZQ6wE+DA8JN5BzVC5EEBuduWWDaiJ8u6YhVOCmThBli+rw==} + /react-router@6.11.1(react@18.2.0): + resolution: {integrity: sha512-OZINSdjJ2WgvAi7hgNLazrEV8SGn6xrKA+MkJe9wVDMZ3zQ6fdJocUjpCUCI0cNrelWjcvon0S/QK/j0NzL3KA==} engines: {node: '>=14'} peerDependencies: react: '>=16.8' dependencies: - '@remix-run/router': 1.3.3 + '@remix-run/router': 1.6.1 react: 18.2.0 dev: false @@ -4452,7 +4456,7 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.21.5 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -4460,8 +4464,8 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /react18-input-otp@1.1.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-E21NiPh/KH67Bq/uEAm78E8H+croiGAyX5WcXfX49qh0im1iKrk/3RCKCTESG6WUoJYyh/fj5JY0UrHm+Mm0eQ==} + /react18-input-otp@1.1.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-55dZMVX61In2ngUhA4Fv0NMY4j5RZjxrJaSOAnJGJmkAhxKB6puVHYEmipyy2+W2CPydFF7pv+0NKzPUA03EVg==} peerDependencies: react: 16.2.0 - 18 react-dom: 16.2.0 - 18 @@ -4501,15 +4505,15 @@ packages: /redux@4.2.1: resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.21.5 dev: false /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} dev: false - /regexp.prototype.flags@1.4.3: - resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} + /regexp.prototype.flags@1.5.0: + resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 @@ -4517,18 +4521,13 @@ packages: functions-have-names: 1.2.3 dev: true - /regexpp@3.2.0: - resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} - engines: {node: '>=8'} - dev: true - /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} dev: false - /reselect@4.1.7: - resolution: {integrity: sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==} + /reselect@4.1.8: + resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==} dev: false /resolve-cwd@3.0.0: @@ -4547,16 +4546,16 @@ packages: engines: {node: '>=8'} dev: false - /resolve.exports@2.0.0: - resolution: {integrity: sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==} + /resolve.exports@2.0.2: + resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} engines: {node: '>=10'} dev: false - /resolve@1.22.1: - resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + /resolve@1.22.2: + resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} hasBin: true dependencies: - is-core-module: 2.11.0 + is-core-module: 2.12.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -4564,7 +4563,7 @@ packages: resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} hasBin: true dependencies: - is-core-module: 2.11.0 + is-core-module: 2.12.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: true @@ -4581,8 +4580,8 @@ packages: glob: 7.2.3 dev: true - /rollup@3.18.0: - resolution: {integrity: sha512-J8C6VfEBjkvYPESMQYxKHxNOh4A5a3FlP+0BETGo34HEcE4eTlgCrO2+eWzlu2a/sHs2QUkZco+wscH7jhhgWg==} + /rollup@3.21.7: + resolution: {integrity: sha512-KXPaEuR8FfUoK2uHwNjxTmJ18ApyvD6zJpYv9FOJSqLStmt6xOY84l1IjK2dSolQmoXknrhEFRaPRgOPdqCT5w==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: @@ -4595,8 +4594,8 @@ packages: queue-microtask: 1.2.3 dev: true - /rxjs@7.8.0: - resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==} + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: tslib: 2.5.0 dev: false @@ -4605,7 +4604,7 @@ packages: resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} dependencies: call-bind: 1.0.2 - get-intrinsic: 1.2.0 + get-intrinsic: 1.2.1 is-regex: 1.1.4 dev: true @@ -4625,8 +4624,8 @@ packages: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} hasBin: true - /semver@7.3.8: - resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} + /semver@7.5.1: + resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==} engines: {node: '>=10'} hasBin: true dependencies: @@ -4646,7 +4645,7 @@ packages: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: call-bind: 1.0.2 - get-intrinsic: 1.2.0 + get-intrinsic: 1.2.1 object-inspect: 1.12.3 dev: true @@ -4662,8 +4661,8 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - /slate-react@0.91.9(react-dom@18.2.0)(react@18.2.0)(slate@0.91.4): - resolution: {integrity: sha512-e3DvmZuUEKKrIB8Kb4RmJ+qzIL2IVJjN2NM/IHRi8W5MmjAtd8yhv46Ewz1sCf0F7uRODS5xvrQsJJAftQhH4Q==} + /slate-react@0.91.11(react-dom@18.2.0)(react@18.2.0)(slate@0.91.4): + resolution: {integrity: sha512-2nS29rc2kuTTJrEUOXGyTkFATmTEw/R9KuUXadUYiz+UVwuFOUMnBKuwJWyuIBOsFipS+06SkIayEf5CKdARRQ==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' @@ -4671,7 +4670,7 @@ packages: dependencies: '@juggle/resize-observer': 3.4.0 '@types/is-hotkey': 0.1.7 - '@types/lodash': 4.14.191 + '@types/lodash': 4.14.194 direction: 1.0.4 is-hotkey: 0.1.8 is-plain-object: 5.0.0 @@ -4686,7 +4685,7 @@ packages: /slate@0.91.4: resolution: {integrity: sha512-aUJ3rpjrdi5SbJ5G1Qjr3arytfRkEStTmHjBfWq2A2Q8MybacIzkScSvGJjQkdTk3djCK9C9SEOt39sSeZFwTw==} dependencies: - immer: 9.0.19 + immer: 9.0.21 is-plain-object: 5.0.0 tiny-warning: 1.0.3 dev: false @@ -4746,20 +4745,29 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.1 - get-intrinsic: 1.2.0 + es-abstract: 1.21.2 + get-intrinsic: 1.2.1 has-symbols: 1.0.3 internal-slot: 1.0.5 - regexp.prototype.flags: 1.4.3 + regexp.prototype.flags: 1.5.0 side-channel: 1.0.4 dev: true + /string.prototype.trim@1.2.7: + resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.2 + dev: true + /string.prototype.trimend@1.0.6: resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.1 + es-abstract: 1.21.2 dev: true /string.prototype.trimstart@1.0.6: @@ -4767,7 +4775,7 @@ packages: dependencies: call-bind: 1.0.2 define-properties: 1.2.0 - es-abstract: 1.21.1 + es-abstract: 1.21.2 dev: true /strip-ansi@6.0.1: @@ -4790,10 +4798,24 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - /stylis@4.1.3: - resolution: {integrity: sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==} + /stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} dev: false + /sucrase@3.32.0: + resolution: {integrity: sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==} + engines: {node: '>=8'} + hasBin: true + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + commander: 4.1.1 + glob: 7.1.6 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.5 + ts-interface-checker: 0.1.13 + dev: true + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -4817,36 +4839,34 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - /tailwindcss@3.2.7(postcss@8.4.21): - resolution: {integrity: sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==} - engines: {node: '>=12.13.0'} + /tailwindcss@3.3.2: + resolution: {integrity: sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==} + engines: {node: '>=14.0.0'} hasBin: true - peerDependencies: - postcss: ^8.0.9 dependencies: + '@alloc/quick-lru': 5.2.0 arg: 5.0.2 chokidar: 3.5.3 - color-name: 1.1.4 - detective: 5.2.1 didyoumean: 1.2.2 dlv: 1.1.3 fast-glob: 3.2.12 glob-parent: 6.0.2 is-glob: 4.0.3 + jiti: 1.18.2 lilconfig: 2.1.0 micromatch: 4.0.5 normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.0 - postcss: 8.4.21 - postcss-import: 14.1.0(postcss@8.4.21) - postcss-js: 4.0.1(postcss@8.4.21) - postcss-load-config: 3.1.4(postcss@8.4.21) - postcss-nested: 6.0.0(postcss@8.4.21) - postcss-selector-parser: 6.0.11 + postcss: 8.4.23 + postcss-import: 15.1.0(postcss@8.4.23) + postcss-js: 4.0.1(postcss@8.4.23) + postcss-load-config: 4.0.1(postcss@8.4.23) + postcss-nested: 6.0.1(postcss@8.4.23) + postcss-selector-parser: 6.0.12 postcss-value-parser: 4.2.0 - quick-lru: 5.1.1 - resolve: 1.22.1 + resolve: 1.22.2 + sucrase: 3.32.0 transitivePeerDependencies: - ts-node dev: true @@ -4864,10 +4884,27 @@ packages: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + dev: true + + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + dev: true + /tiny-invariant@1.0.6: resolution: {integrity: sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==} dev: false + /tiny-invariant@1.3.1: + resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} + dev: false + /tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} dev: false @@ -4886,6 +4923,10 @@ packages: dependencies: is-number: 7.0.0 + /ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: true + /ts-results@3.3.0: resolution: {integrity: sha512-FWqxGX2NHp5oCyaMd96o2y2uMQmSu8Dey6kvyuFdRJ2AzfmWo3kWa4UsPlCGlfQ/qu03m09ZZtppMoY8EMHuiA==} dev: false @@ -4952,8 +4993,8 @@ packages: which-boxed-primitive: 1.0.2 dev: true - /update-browserslist-db@1.0.10(browserslist@4.21.5): - resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} + /update-browserslist-db@1.0.11(browserslist@4.21.5): + resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -5001,13 +5042,13 @@ packages: resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} engines: {node: '>=10.12.0'} dependencies: - '@jridgewell/trace-mapping': 0.3.17 + '@jridgewell/trace-mapping': 0.3.18 '@types/istanbul-lib-coverage': 2.0.4 convert-source-map: 1.9.0 dev: false - /vite@4.1.4(@types/node@18.14.6): - resolution: {integrity: sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==} + /vite@4.3.5(@types/node@18.16.9): + resolution: {integrity: sha512-0gEnL9wiRFxgz40o/i/eTBwm+NEbpUeTWhzKrZDSdKm6nplj+z4lKz8ANDgildxHm47Vg8EUia0aicKbawUVVA==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -5031,11 +5072,10 @@ packages: terser: optional: true dependencies: - '@types/node': 18.14.6 - esbuild: 0.16.17 - postcss: 8.4.21 - resolve: 1.22.1 - rollup: 3.18.0 + '@types/node': 18.16.9 + esbuild: 0.17.19 + postcss: 8.4.23 + rollup: 3.21.7 optionalDependencies: fsevents: 2.3.2 dev: true @@ -5105,18 +5145,13 @@ packages: signal-exit: 3.0.7 dev: false - /xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - dev: true - - /y-indexeddb@9.0.9(yjs@13.5.51): - resolution: {integrity: sha512-GcJbiJa2eD5hankj46Hea9z4hbDnDjvh1fT62E5SpZRsv8GcEemw34l1hwI2eknGcv5Ih9JfusT37JLx9q3LFg==} + /y-indexeddb@9.0.11(yjs@13.6.1): + resolution: {integrity: sha512-HOKQ70qW1h2WJGtOKu9rE8fbX86ExVZedecndMuhwax3yM4DQsQzCTGHt/jvTrFZr/9Ahvd8neD6aZ4dMMjtdg==} peerDependencies: yjs: ^13.0.0 dependencies: - lib0: 0.2.73 - yjs: 13.5.51 + lib0: 0.2.74 + yjs: 13.6.1 dev: false /y18n@5.0.8: @@ -5133,14 +5168,20 @@ packages: /yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} + dev: false + + /yaml@2.2.2: + resolution: {integrity: sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==} + engines: {node: '>= 14'} + dev: true /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} dev: false - /yargs@17.7.1: - resolution: {integrity: sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==} + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} dependencies: cliui: 8.0.1 @@ -5152,11 +5193,11 @@ packages: yargs-parser: 21.1.1 dev: false - /yjs@13.5.51: - resolution: {integrity: sha512-F1Nb3z3TdandD80IAeQqgqy/2n9AhDLcXoBhZvCUX1dNVe0ef7fIwi6MjSYaGAYF2Ev8VcLcsGnmuGGOl7AWbw==} + /yjs@13.6.1: + resolution: {integrity: sha512-IyyHL+/v9N2S4YLSjGHMa0vMAfFxq8RDG5Nvb77raTTHJPweU3L/fRlqw6ElZvZUuHWnax3ufHR0Tx0ntfG63Q==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} dependencies: - lib0: 0.2.73 + lib0: 0.2.74 dev: false /yocto-queue@0.1.0: diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index 3ca02d9bbb..b045b28fc4 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -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" diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml index 3d45f5a80b..5b8fc4e4fe 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.toml +++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml @@ -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 diff --git a/frontend/appflowy_tauri/src-tauri/src/init.rs b/frontend/appflowy_tauri/src-tauri/src/init.rs index c2fdddf85c..fbffd072c5 100644 --- a/frontend/appflowy_tauri/src-tauri/src/init.rs +++ b/frontend/appflowy_tauri/src-tauri/src/init.rs @@ -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"); diff --git a/frontend/appflowy_tauri/src/services/backend/index.ts b/frontend/appflowy_tauri/src/services/backend/index.ts index ff33bdefca..d9d87d088c 100644 --- a/frontend/appflowy_tauri/src/services/backend/index.ts +++ b/frontend/appflowy_tauri/src/services/backend/index.ts @@ -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"; diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index c666ec7158..641c3f04db 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -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" diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 409d179036..f97fb9a283 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -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] diff --git a/frontend/rust-lib/dart-ffi/Cargo.toml b/frontend/rust-lib/dart-ffi/Cargo.toml index d537b1a015..5c2d0b71e9 100644 --- a/frontend/rust-lib/dart-ffi/Cargo.toml +++ b/frontend/rust-lib/dart-ffi/Cargo.toml @@ -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"] } diff --git a/frontend/rust-lib/dart-ffi/src/lib.rs b/frontend/rust-lib/dart-ffi/src/lib.rs index d05c5d6445..e86227aa1e 100644 --- a/frontend/rust-lib/dart-ffi/src/lib.rs +++ b/frontend/rust-lib/dart-ffi/src/lib.rs @@ -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> = RwLock::new(None); diff --git a/frontend/rust-lib/flowy-client-sync/Cargo.toml b/frontend/rust-lib/flowy-client-sync/Cargo.toml deleted file mode 100644 index 137af96835..0000000000 --- a/frontend/rust-lib/flowy-client-sync/Cargo.toml +++ /dev/null @@ -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" diff --git a/frontend/rust-lib/flowy-client-sync/src/client_database/block_revision_pad.rs b/frontend/rust-lib/flowy-client-sync/src/client_database/block_revision_pad.rs deleted file mode 100644 index 700abb369d..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_database/block_revision_pad.rs +++ /dev/null @@ -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; -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::>>(); - DatabaseBlockRevision { - block_id: duplicated_block_id.to_string(), - rows: duplicated_rows, - } - } - - pub fn from_operations(operations: DatabaseBlockOperations) -> SyncResult { - let s = operations.content()?; - let revision: DatabaseBlockRevision = serde_json::from_str(&s).map_err(|e| { - let msg = format!( - "Deserialize operations to {} failed: {}", - type_name::(), - e - ); - tracing::error!("{}", s); - SyncError::internal().context(msg) - })?; - Ok(Self { - block: revision, - operations, - }) - } - - pub fn from_revisions(revisions: Vec) -> SyncResult { - 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, - ) -> SyncResult> { - self.modify(|rows| { - if let Some(start_row_id) = start_row_id { - if !start_row_id.is_empty() { - if let Some(index) = rows.iter().position(|row| row.id == start_row_id) { - rows.insert(index + 1, Arc::new(row)); - return Ok(Some(())); - } - } - } - - rows.push(Arc::new(row)); - Ok(Some(())) - }) - } - - pub fn delete_rows( - &mut self, - row_ids: Vec>, - ) -> SyncResult> { - self.modify(|rows| { - rows.retain(|row| !row_ids.contains(&Cow::Borrowed(&row.id))); - Ok(Some(())) - }) - } - - pub fn get_row_rev(&self, row_id: &str) -> Option<(usize, Arc)> { - for (index, row) in self.block.rows.iter().enumerate() { - if row.id == row_id { - return Some((index, row.clone())); - } - } - None - } - - pub fn get_row_revs( - &self, - row_ids: Option>>, - ) -> SyncResult>> - where - T: AsRef + ToOwned + ?Sized, - { - match row_ids { - None => Ok(self.block.rows.clone()), - Some(row_ids) => { - let row_map = self - .block - .rows - .iter() - .map(|row| (row.id.as_str(), row.clone())) - .collect::>>(); - - Ok( - row_ids - .iter() - .flat_map(|row_id| { - let row_id = row_id.as_ref().as_ref(); - match row_map.get(row_id) { - None => { - tracing::error!("Can't find the row with id: {}", row_id); - None - }, - Some(row) => Some(row.clone()), - } - }) - .collect::>(), - ) - }, - } - } - - pub fn get_cell_revs( - &self, - field_id: &str, - row_ids: Option>>, - ) -> SyncResult> { - let rows = self.get_row_revs(row_ids)?; - let cell_revs = rows - .iter() - .flat_map(|row| { - let cell_rev = row.cells.get(field_id)?; - Some(cell_rev.clone()) - }) - .collect::>(); - Ok(cell_revs) - } - - pub fn number_of_rows(&self) -> i32 { - self.block.rows.len() as i32 - } - - pub fn index_of_row(&self, row_id: &str) -> Option { - self.block.rows.iter().position(|row| row.id == row_id) - } - - pub fn update_row( - &mut self, - changeset: RowChangeset, - ) -> SyncResult> { - let row_id = changeset.row_id.clone(); - self.modify_row(&row_id, |row| { - let mut is_changed = None; - if let Some(height) = changeset.height { - row.height = height; - is_changed = Some(()); - } - - if let Some(visibility) = changeset.visibility { - row.visibility = visibility; - is_changed = Some(()); - } - - if !changeset.cell_by_field_id.is_empty() { - is_changed = Some(()); - changeset - .cell_by_field_id - .into_iter() - .for_each(|(field_id, cell)| { - row.cells.insert(field_id, cell); - }) - } - - Ok(is_changed) - }) - } - - pub fn move_row( - &mut self, - row_id: &str, - from: usize, - to: usize, - ) -> SyncResult> { - self.modify(|row_revs| { - if let Some(position) = row_revs.iter().position(|row_rev| row_rev.id == row_id) { - debug_assert_eq!(from, position); - let row_rev = row_revs.remove(position); - if to > row_revs.len() { - Err(SyncError::out_of_bound()) - } else { - row_revs.insert(to, row_rev); - Ok(Some(())) - } - } else { - Ok(None) - } - }) - } - - pub fn modify(&mut self, f: F) -> SyncResult> - where - F: for<'a> FnOnce(&'a mut Vec>) -> SyncResult>, - { - let cloned_self = self.clone(); - match f(&mut self.block.rows)? { - None => Ok(None), - Some(_) => { - let old = cloned_self.revision_json()?; - let new = self.revision_json()?; - match cal_diff::(old, new) { - None => Ok(None), - Some(operations) => { - tracing::trace!( - "[{}] Composing operations {}", - type_name::(), - operations.json_str() - ); - self.operations = self.operations.compose(&operations)?; - Ok(Some(DatabaseBlockRevisionChangeset { - operations, - md5: md5(&self.operations.json_bytes()), - })) - }, - } - }, - } - } - - fn modify_row( - &mut self, - row_id: &str, - f: F, - ) -> SyncResult> - where - F: FnOnce(&mut RowRevision) -> SyncResult>, - { - self.modify(|rows| { - if let Some(row_rev) = rows.iter_mut().find(|row_rev| row_id == row_rev.id) { - f(Arc::make_mut(row_rev)) - } else { - tracing::warn!("[BlockMetaPad]: Can't find any row with id: {}", row_id); - Ok(None) - } - }) - } - - pub fn revision_json(&self) -> SyncResult { - serde_json::to_string(&self.block) - .map_err(|e| SyncError::internal().context(format!("serial block to json failed: {}", e))) - } - - pub fn operations_json_str(&self) -> String { - self.operations.json_str() - } -} - -pub 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 { - 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() - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_database/database_builder.rs b/frontend/rust-lib/flowy-client-sync/src/client_database/database_builder.rs deleted file mode 100644 index 1dd12be853..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_database/database_builder.rs +++ /dev/null @@ -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> { - &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::>(); - for row in rows { - let cell_field_ids = row.cells.keys().into_iter().collect::>(); - if cell_field_ids != field_ids { - let msg = format!("{:?} contains invalid cells", row); - return Err(SyncError::internal().context(msg)); - } - } - Ok(()) -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_database/database_revision_pad.rs b/frontend/rust-lib/flowy-client-sync/src/client_database/database_revision_pad.rs deleted file mode 100644 index f540c3f8bc..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_database/database_revision_pad.rs +++ /dev/null @@ -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; -pub type DatabaseOperationsBuilder = DeltaOperationBuilder; - -#[derive(Clone)] -pub struct DatabaseRevisionPad { - database_rev: Arc, - operations: DatabaseOperations, -} - -pub trait JsonDeserializer { - fn deserialize(&self, type_option_data: Vec) -> SyncResult; -} - -impl DatabaseRevisionPad { - pub fn database_id(&self) -> String { - self.database_rev.database_id.clone() - } - - pub async fn duplicate_database_block_meta( - &self, - ) -> (Vec, Vec) { - 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::>(); - - (fields, blocks) - } - - pub fn from_operations(operations: DatabaseOperations) -> SyncResult { - 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) -> SyncResult { - let operations: DatabaseOperations = make_operations_from_revisions(revisions)?; - Self::from_operations(operations) - } - - #[tracing::instrument(level = "debug", skip_all, err)] - pub fn create_field_rev( - &mut self, - new_field_rev: FieldRevision, - start_field_id: Option, - ) -> SyncResult> { - self.modify_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> { - 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> { - 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( - &mut self, - field_id: &str, - new_field_type: T, - make_default_type_option: DT, - type_option_transform: TT, - ) -> SyncResult> - where - DT: FnOnce() -> String, - TT: FnOnce(FieldTypeRevision, Option, String) -> String, - T: Into, - { - let new_field_type = new_field_type.into(); - self.modify_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, - ) -> SyncResult> { - 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> { - 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)> { - self - .database_rev - .fields - .iter() - .enumerate() - .find(|(_, field)| field.id == field_id) - } - - pub fn get_field_revs( - &self, - field_ids: Option>, - ) -> SyncResult>> { - match field_ids { - None => Ok(self.database_rev.fields.clone()), - Some(field_ids) => { - let field_by_field_id = self - .database_rev - .fields - .iter() - .map(|field| (&field.id, field)) - .collect::>>(); - - let fields = field_ids - .iter() - .flat_map(|field_id| match field_by_field_id.get(&field_id) { - None => { - tracing::error!("Can't find the field with id: {}", field_id); - None - }, - Some(field) => Some((*field).clone()), - }) - .collect::>>(); - Ok(fields) - }, - } - } - - pub fn create_block_meta_rev( - &mut self, - block: DatabaseBlockMetaRevision, - ) -> SyncResult> { - 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> { - self.database_rev.blocks.clone() - } - - pub fn update_block_rev( - &mut self, - changeset: DatabaseBlockMetaRevisionChangeset, - ) -> SyncResult> { - let block_id = changeset.block_id.clone(); - self.modify_block(&block_id, |block| { - let mut is_changed = None; - - if let Some(row_count) = changeset.row_count { - block.row_count = row_count; - is_changed = Some(()); - } - - if let Some(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] { - &self.database_rev.fields - } - - fn modify_database(&mut self, f: F) -> SyncResult> - where - F: FnOnce(&mut DatabaseRevision) -> SyncResult>, - { - 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::(old, new) { - None => Ok(None), - Some(operations) => { - self.operations = self.operations.compose(&operations)?; - Ok(Some(DatabaseRevisionChangeset { - operations, - md5: self.database_md5(), - })) - }, - } - }, - } - } - - fn modify_block( - &mut self, - block_id: &str, - f: F, - ) -> SyncResult> - where - F: FnOnce(&mut DatabaseBlockMetaRevision) -> SyncResult>, - { - 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( - &mut self, - field_id: &str, - f: F, - ) -> SyncResult> - where - F: FnOnce(&mut FieldRevision) -> SyncResult>, - { - 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 { - make_database_rev_json_str(&self.database_rev) - } -} - -pub fn make_database_rev_json_str(grid_revision: &DatabaseRevision) -> SyncResult { - let json = serde_json::to_string(grid_revision) - .map_err(|err| internal_sync_error(format!("Serialize grid to json str failed. {:?}", err)))?; - Ok(json) -} - -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 { - 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, - } - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_database/database_view_revision_pad.rs b/frontend/rust-lib/flowy-client-sync/src/client_database/database_view_revision_pad.rs deleted file mode 100644 index 3cd6b7a50a..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_database/database_view_revision_pad.rs +++ /dev/null @@ -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; -pub type DatabaseViewOperationsBuilder = DeltaBuilder; - -#[derive(Debug, Clone)] -pub struct DatabaseViewRevisionPad { - view: Arc, - 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 { - 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) -> SyncResult { - let operations: DatabaseViewOperations = make_operations_from_revisions(revisions)?; - Self::from_operations(operations) - } - - pub fn get_groups_by_field_revs( - &self, - field_revs: &[Arc], - ) -> Vec> { - self.groups.get_objects_by_field_revs(field_revs) - } - - pub fn get_all_groups(&self) -> Vec> { - self.groups.get_all_objects() - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub fn insert_or_update_group_configuration( - &mut self, - field_id: &str, - field_type: &FieldTypeRevision, - group_configuration_rev: GroupConfigurationRevision, - ) -> SyncResult> { - self.modify(|view| { - // Only save one group - view.groups.clear(); - view - .groups - .add_object(field_id, field_type, group_configuration_rev); - Ok(Some(())) - }) - } - - #[tracing::instrument(level = "trace", skip_all)] - pub fn contains_group(&self, field_id: &str, field_type: &FieldTypeRevision) -> bool { - self.view.groups.get_objects(field_id, field_type).is_some() - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub fn with_mut_group( - &mut self, - field_id: &str, - field_type: &FieldTypeRevision, - configuration_id: &str, - mut_configuration_fn: F, - ) -> SyncResult> { - self.modify( - |view| match view.groups.get_mut_objects(field_id, field_type) { - None => Ok(None), - Some(configurations_revs) => { - for configuration_rev in configurations_revs { - if configuration_rev.id == configuration_id { - mut_configuration_fn(Arc::make_mut(configuration_rev)); - return Ok(Some(())); - } - } - Ok(None) - }, - }, - ) - } - - pub fn delete_group( - &mut self, - group_id: &str, - field_id: &str, - field_type: &FieldTypeRevision, - ) -> SyncResult> { - self.modify(|view| { - if let Some(groups) = view.groups.get_mut_objects(field_id, field_type) { - groups.retain(|group| group.id != group_id); - Ok(Some(())) - } else { - Ok(None) - } - }) - } - - pub fn get_all_sorts(&self, _field_revs: &[Arc]) -> Vec> { - self.sorts.get_all_objects() - } - - /// For the moment, a field type only have one filter. - pub fn get_sorts( - &self, - field_id: &str, - field_type_rev: &FieldTypeRevision, - ) -> Vec> { - self - .sorts - .get_objects(field_id, field_type_rev) - .unwrap_or_default() - } - - pub fn get_sort( - &self, - field_id: &str, - field_type_rev: &FieldTypeRevision, - sort_id: &str, - ) -> Option> { - self - .sorts - .get_object(field_id, field_type_rev, |sort| sort.id == sort_id) - } - - pub fn insert_sort( - &mut self, - field_id: &str, - sort_rev: SortRevision, - ) -> SyncResult> { - self.modify(|view| { - let field_type = sort_rev.field_type; - view.sorts.add_object(field_id, &field_type, sort_rev); - Ok(Some(())) - }) - } - - pub fn update_sort( - &mut self, - field_id: &str, - sort_rev: SortRevision, - ) -> SyncResult> { - self.modify(|view| { - if let Some(sort) = view - .sorts - .get_mut_object(field_id, &sort_rev.field_type, |sort| { - sort.id == sort_rev.id - }) - { - let sort = Arc::make_mut(sort); - sort.condition = sort_rev.condition; - Ok(Some(())) - } else { - Ok(None) - } - }) - } - - pub fn delete_sort>( - &mut self, - sort_id: &str, - field_id: &str, - field_type: T, - ) -> SyncResult> { - let field_type = field_type.into(); - self.modify(|view| { - if let Some(sorts) = view.sorts.get_mut_objects(field_id, &field_type) { - sorts.retain(|sort| sort.id != sort_id); - Ok(Some(())) - } else { - Ok(None) - } - }) - } - - pub fn delete_all_sorts(&mut self) -> SyncResult> { - self.modify(|view| { - view.sorts.clear(); - Ok(Some(())) - }) - } - - pub fn get_all_filters(&self, field_revs: &[Arc]) -> Vec> { - self.filters.get_objects_by_field_revs(field_revs) - } - - /// For the moment, a field type only have one filter. - pub fn get_filters( - &self, - field_id: &str, - field_type_rev: &FieldTypeRevision, - ) -> Vec> { - self - .filters - .get_objects(field_id, field_type_rev) - .unwrap_or_default() - } - - pub fn get_filter( - &self, - field_id: &str, - field_type_rev: &FieldTypeRevision, - filter_id: &str, - ) -> Option> { - self - .filters - .get_object(field_id, field_type_rev, |filter| filter.id == filter_id) - } - - pub fn insert_filter( - &mut self, - field_id: &str, - filter_rev: FilterRevision, - ) -> SyncResult> { - self.modify(|view| { - let field_type = filter_rev.field_type; - view.filters.add_object(field_id, &field_type, filter_rev); - Ok(Some(())) - }) - } - - pub fn update_filter( - &mut self, - field_id: &str, - filter_rev: FilterRevision, - ) -> SyncResult> { - self.modify(|view| { - if let Some(filter) = - view - .filters - .get_mut_object(field_id, &filter_rev.field_type, |filter| { - filter.id == filter_rev.id - }) - { - let filter = Arc::make_mut(filter); - filter.condition = filter_rev.condition; - filter.content = filter_rev.content; - Ok(Some(())) - } else { - Ok(None) - } - }) - } - - pub fn delete_filter>( - &mut self, - filter_id: &str, - field_id: &str, - field_type: T, - ) -> SyncResult> { - let field_type = field_type.into(); - self.modify(|view| { - if let Some(filters) = view.filters.get_mut_objects(field_id, &field_type) { - filters.retain(|filter| filter.id != filter_id); - Ok(Some(())) - } else { - Ok(None) - } - }) - } - - /// 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(&self, layout: &LayoutRevision) -> Option - where - T: serde::de::DeserializeOwned, - { - let settings_str = self.view.layout_settings.get(layout)?; - serde_json::from_str::(settings_str).ok() - } - - /// updates the settings for the given layout type - pub fn set_layout_setting( - &mut self, - layout: &LayoutRevision, - settings: &T, - ) -> SyncResult> - 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 { - make_database_view_rev_json_str(&self.view) - } - - pub fn layout(&self) -> LayoutRevision { - self.layout.clone() - } - - fn modify(&mut self, f: F) -> SyncResult> - where - F: FnOnce(&mut DatabaseViewRevision) -> SyncResult>, - { - let cloned_view = self.view.clone(); - match f(Arc::make_mut(&mut self.view))? { - None => Ok(None), - Some(_) => { - let old = make_database_view_rev_json_str(&cloned_view)?; - let new = self.json_str()?; - match cal_diff::(old, new) { - None => Ok(None), - Some(operations) => { - self.operations = self.operations.compose(&operations)?; - let md5 = md5(&self.operations.json_bytes()); - Ok(Some(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 { - 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() -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_database/mod.rs b/frontend/rust-lib/flowy-client-sync/src/client_database/mod.rs deleted file mode 100644 index 2ae1fd58ae..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_database/mod.rs +++ /dev/null @@ -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::*; diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/document_pad.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/document_pad.rs deleted file mode 100644 index c6d460e2b9..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/document_pad.rs +++ /dev/null @@ -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>, -} - -impl ClientDocument { - pub fn new() -> Self { - let content = C::json_str(); - Self::from_json(&content).unwrap() - } - - pub fn from_operations(operations: DeltaTextOperations) -> Self { - ClientDocument { - operations, - history: History::new(), - view: ViewExtensions::new(), - last_edit_time: 0, - notify: None, - } - } - - pub fn from_json(json: &str) -> Result { - let operations = DeltaTextOperations::from_json(json)?; - Ok(Self::from_operations(operations)) - } - - pub fn get_operations_json(&self) -> String { - self.operations.json_str() - } - - pub fn to_bytes(&self) -> Bytes { - self.operations.json_bytes() - } - - pub fn to_content(&self) -> String { - self.operations.content().unwrap() - } - - pub fn get_operations(&self) -> &DeltaTextOperations { - &self.operations - } - - pub fn document_md5(&self) -> String { - let bytes = self.to_bytes(); - md5(&bytes) - } - - pub fn set_notify(&mut self, notify: mpsc::UnboundedSender<()>) { - self.notify = Some(notify); - } - - pub fn set_operations(&mut self, operations: DeltaTextOperations) { - tracing::trace!("document: {}", operations.json_str()); - self.operations = operations; - - match &self.notify { - None => {}, - Some(notify) => { - let _ = notify.send(()); - }, - } - } - - pub fn compose_operations(&mut self, operations: DeltaTextOperations) -> Result<(), SyncError> { - tracing::trace!( - "{} compose {}", - &self.operations.json_str(), - operations.json_str() - ); - let composed_operations = self.operations.compose(&operations)?; - let mut undo_operations = operations.invert(&self.operations); - - let now = chrono::Utc::now().timestamp_millis() as usize; - if now - self.last_edit_time < RECORD_THRESHOLD { - if let Some(last_operation) = self.history.undo() { - tracing::trace!("compose previous change"); - tracing::trace!("current = {}", undo_operations); - tracing::trace!("previous = {}", last_operation); - undo_operations = undo_operations.compose(&last_operation)?; - } - } else { - self.last_edit_time = now; - } - - if !undo_operations.is_empty() { - tracing::trace!("add history operations: {}", undo_operations); - self.history.record(undo_operations); - } - - self.set_operations(composed_operations); - Ok(()) - } - - pub fn insert( - &mut self, - index: usize, - data: T, - ) -> Result { - let text = data.to_string(); - let interval = Interval::new(index, index); - validate_interval(&self.operations, &interval)?; - let operations = self.view.insert(&self.operations, &text, interval)?; - self.compose_operations(operations.clone())?; - Ok(operations) - } - - pub fn delete(&mut self, interval: Interval) -> Result { - validate_interval(&self.operations, &interval)?; - debug_assert!(!interval.is_empty()); - let operations = self.view.delete(&self.operations, interval)?; - if !operations.is_empty() { - self.compose_operations(operations.clone())?; - } - Ok(operations) - } - - pub fn format( - &mut self, - interval: Interval, - attribute: AttributeEntry, - ) -> Result { - validate_interval(&self.operations, &interval)?; - tracing::trace!("format {} with {:?}", interval, attribute); - let operations = self - .view - .format(&self.operations, attribute, interval) - .unwrap(); - self.compose_operations(operations.clone())?; - Ok(operations) - } - - pub fn replace( - &mut self, - interval: Interval, - data: T, - ) -> Result { - validate_interval(&self.operations, &interval)?; - let mut operations = DeltaTextOperations::default(); - let text = data.to_string(); - if !text.is_empty() { - operations = self.view.insert(&self.operations, &text, interval)?; - self.compose_operations(operations.clone())?; - } - - if !interval.is_empty() { - let delete = self.delete(interval)?; - operations = operations.compose(&delete)?; - } - - Ok(operations) - } - - pub fn can_undo(&self) -> bool { - self.history.can_undo() - } - - pub fn can_redo(&self) -> bool { - self.history.can_redo() - } - - pub fn undo(&mut self) -> Result { - match self.history.undo() { - None => Err(SyncError::undo().context("Undo stack is empty")), - Some(undo_operations) => { - let (new_operations, inverted_operations) = self.invert(&undo_operations)?; - self.set_operations(new_operations); - self.history.add_redo(inverted_operations); - Ok(UndoResult { - operations: undo_operations, - }) - }, - } - } - - pub fn redo(&mut self) -> Result { - match self.history.redo() { - None => Err(SyncError::redo()), - Some(redo_operations) => { - let (new_operations, inverted_operations) = self.invert(&redo_operations)?; - self.set_operations(new_operations); - self.history.add_undo(inverted_operations); - Ok(UndoResult { - operations: redo_operations, - }) - }, - } - } - - pub fn is_empty(&self) -> bool { - // The document is empty if its text is equal to the initial text. - self.operations.json_str() == NewlineDocument::json_str() - } -} - -impl ClientDocument { - fn invert( - &self, - operations: &DeltaTextOperations, - ) -> Result<(DeltaTextOperations, DeltaTextOperations), SyncError> { - // c = a.compose(b) - // d = b.invert(a) - // a = c.compose(d) - let new_operations = self.operations.compose(operations)?; - let inverted_operations = operations.invert(&self.operations); - Ok((new_operations, inverted_operations)) - } -} - -fn validate_interval( - operations: &DeltaTextOperations, - interval: &Interval, -) -> Result<(), SyncError> { - if operations.utf16_target_len < interval.end { - tracing::error!( - "{:?} out of bounds. should 0..{}", - interval, - operations.utf16_target_len - ); - return Err(SyncError::out_of_bound()); - } - Ok(()) -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/default_delete.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/default_delete.rs deleted file mode 100644 index 8f4f6e3872..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/default_delete.rs +++ /dev/null @@ -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 { - Some( - DeltaOperationBuilder::new() - .retain(interval.start) - .delete(interval.size()) - .build(), - ) - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/mod.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/mod.rs deleted file mode 100644 index 061369bb13..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod default_delete; -mod preserve_line_format_merge; - -pub use default_delete::*; -pub use preserve_line_format_merge::*; diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/preserve_line_format_merge.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/preserve_line_format_merge.rs deleted file mode 100644 index e414a3f49b..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/delete/preserve_line_format_merge.rs +++ /dev/null @@ -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 { - 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::(interval.size() - 1); - let mut new_delta = DeltaOperationBuilder::new() - .retain(interval.start) - .delete(interval.size()) - .build(); - - while iter.has_next() { - match iter.next() { - None => tracing::error!("op must be not None when has_next() return true"), - Some(op) => { - // - match op.get_data().find(NEW_LINE) { - None => { - new_delta.retain(op.len(), empty_attributes()); - continue; - }, - Some(line_break) => { - let mut attributes = op.get_attributes(); - attributes.remove_all_value(); - - if newline_op.has_attribute() { - attributes.extend(newline_op.get_attributes()); - } - - new_delta.retain(line_break, empty_attributes()); - new_delta.retain(1, attributes); - break; - }, - } - }, - } - } - - Some(new_delta) - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/format_at_position.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/format_at_position.rs deleted file mode 100644 index 48ec7992f6..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/format_at_position.rs +++ /dev/null @@ -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::() } -// -// fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -// -> Option { 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(), -// ) -// } -// } diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/mod.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/mod.rs deleted file mode 100644 index 52df2dd8b9..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/mod.rs +++ /dev/null @@ -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; diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/resolve_block_format.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/resolve_block_format.rs deleted file mode 100644 index 6e2dfa0984..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/resolve_block_format.rs +++ /dev/null @@ -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 { - 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) - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/resolve_inline_format.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/resolve_inline_format.rs deleted file mode 100644 index eb8a4055b6..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/format/resolve_inline_format.rs +++ /dev/null @@ -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 { - 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) - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/helper.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/helper.rs deleted file mode 100644 index 4d9a4473f6..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/helper.rs +++ /dev/null @@ -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 -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/auto_exit_block.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/auto_exit_block.rs deleted file mode 100644 index 14416cd5cf..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/auto_exit_block.rs +++ /dev/null @@ -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 { - // 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(), - ) - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/auto_format.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/auto_format.rs deleted file mode 100644 index d5f416caa9..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/auto_format.rs +++ /dev/null @@ -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 { - // 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 { - if let Ok(url) = Url::parse(s) { - return Some(AutoFormatter::Url(url)); - } - - None - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/default_insert.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/default_insert.rs deleted file mode 100644 index 62b5fcc066..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/default_insert.rs +++ /dev/null @@ -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 { - 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(), - ) - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/mod.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/mod.rs deleted file mode 100644 index c703bd5dc5..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/mod.rs +++ /dev/null @@ -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 { - 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 { - None - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/preserve_block_format.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/preserve_block_format.rs deleted file mode 100644 index 54a7cd24e3..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/preserve_block_format.rs +++ /dev/null @@ -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 { - 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 - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/preserve_inline_format.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/preserve_inline_format.rs deleted file mode 100644 index c6f4738d63..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/preserve_inline_format.rs +++ /dev/null @@ -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 { - 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 { - 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) - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/reset_format_on_new_line.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/reset_format_on_new_line.rs deleted file mode 100644 index ecd5593e1e..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/insert/reset_format_on_new_line.rs +++ /dev/null @@ -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 { - if !is_newline(text) { - return None; - } - - let mut iter = OperationIterator::new(delta); - iter.seek::(index); - let next_op = iter.next_op()?; - if !next_op.get_data().starts_with(NEW_LINE) { - return None; - } - - let mut reset_attribute = AttributeHashMap::new(); - if next_op - .get_attributes() - .contains_key(BuildInTextAttributeKey::Header.as_ref()) - { - reset_attribute.remove_value(BuildInTextAttributeKey::Header); - } - - let len = index + replace_len; - Some( - DeltaOperationBuilder::new() - .retain(len) - .insert_with_attributes(NEW_LINE, next_op.get_attributes()) - .retain_with_attributes(1, reset_attribute) - .trim() - .build(), - ) - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/mod.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/mod.rs deleted file mode 100644 index 8a1ef23403..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/extensions/mod.rs +++ /dev/null @@ -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; -pub type FormatExtension = Box; -pub type DeleteExtension = Box; - -pub trait InsertExt { - fn ext_name(&self) -> &str; - fn apply( - &self, - delta: &DeltaTextOperations, - replace_len: usize, - text: &str, - index: usize, - ) -> Option; -} - -pub trait FormatExt { - fn ext_name(&self) -> &str; - fn apply( - &self, - delta: &DeltaTextOperations, - interval: Interval, - attribute: &AttributeEntry, - ) -> Option; -} - -pub trait DeleteExt { - fn ext_name(&self) -> &str; - fn apply(&self, delta: &DeltaTextOperations, interval: Interval) -> Option; -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/history.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/history.rs deleted file mode 100644 index c668125b22..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/history.rs +++ /dev/null @@ -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, - redoes: Vec, - capacity: usize, -} - -impl std::default::Default for History { - fn default() -> Self { - History { - cur_undo: 1, - undoes: Vec::new(), - redoes: Vec::new(), - capacity: MAX_UNDOES, - } - } -} - -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 { - if !self.can_undo() { - return None; - } - let delta = self.undoes.pop().unwrap(); - Some(delta) - } - - pub fn redo(&mut self) -> Option { - if !self.can_redo() { - return None; - } - - let delta = self.redoes.pop().unwrap(); - Some(delta) - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/mod.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/mod.rs deleted file mode 100644 index d571d5447b..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/mod.rs +++ /dev/null @@ -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; diff --git a/frontend/rust-lib/flowy-client-sync/src/client_document/view.rs b/frontend/rust-lib/flowy-client-sync/src/client_document/view.rs deleted file mode 100644 index e1804c9eff..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_document/view.rs +++ /dev/null @@ -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, - format_exts: Vec, - delete_exts: Vec, -} - -impl ViewExtensions { - pub(crate) fn new() -> Self { - Self { - insert_exts: construct_insert_exts(), - format_exts: construct_format_exts(), - delete_exts: construct_delete_exts(), - } - } - - pub(crate) fn insert( - &self, - operations: &DeltaTextOperations, - text: &str, - interval: Interval, - ) -> Result { - let mut new_operations = None; - for ext in &self.insert_exts { - if let Some(mut operations) = ext.apply(operations, interval.size(), text, interval.start) { - trim(&mut operations); - tracing::trace!("[{}] applied, delta: {}", ext.ext_name(), operations); - new_operations = Some(operations); - break; - } - } - - match new_operations { - None => Err(ErrorBuilder::new(OTErrorCode::ApplyInsertFail).build()), - Some(new_operations) => Ok(new_operations), - } - } - - pub(crate) fn delete( - &self, - delta: &DeltaTextOperations, - interval: Interval, - ) -> Result { - let mut new_delta = None; - for ext in &self.delete_exts { - if let Some(mut delta) = ext.apply(delta, interval) { - trim(&mut delta); - tracing::trace!("[{}] applied, delta: {}", ext.ext_name(), delta); - new_delta = Some(delta); - break; - } - } - - match new_delta { - None => Err(ErrorBuilder::new(OTErrorCode::ApplyDeleteFail).build()), - Some(new_delta) => Ok(new_delta), - } - } - - pub(crate) fn format( - &self, - operations: &DeltaTextOperations, - attribute: AttributeEntry, - interval: Interval, - ) -> Result { - let mut new_operations = None; - for ext in &self.format_exts { - if let Some(mut operations) = ext.apply(operations, interval, &attribute) { - trim(&mut operations); - tracing::trace!("[{}] applied, delta: {}", ext.ext_name(), operations); - new_operations = Some(operations); - break; - } - } - - match new_operations { - None => Err(ErrorBuilder::new(OTErrorCode::ApplyFormatFail).build()), - Some(new_operations) => Ok(new_operations), - } - } -} - -fn construct_insert_exts() -> Vec { - vec![ - Box::new(InsertEmbedsExt {}), - Box::new(ForceNewlineForInsertsAroundEmbedExt {}), - Box::new(AutoExitBlock {}), - Box::new(PreserveBlockFormatOnInsert {}), - Box::new(PreserveLineFormatOnSplit {}), - Box::new(ResetLineFormatOnNewLine {}), - Box::new(AutoFormatExt {}), - Box::new(PreserveInlineFormat {}), - Box::new(DefaultInsertAttribute {}), - ] -} - -fn construct_format_exts() -> Vec { - vec![ - // Box::new(FormatLinkAtCaretPositionExt {}), - Box::new(ResolveBlockFormat {}), - Box::new(ResolveInlineFormat {}), - ] -} - -fn construct_delete_exts() -> Vec { - vec![ - Box::new(PreserveLineFormatOnMerge {}), - Box::new(DefaultDelete {}), - ] -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_folder/builder.rs b/frontend/rust-lib/flowy-client-sync/src/client_folder/builder.rs deleted file mode 100644 index 606cb92eb2..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_folder/builder.rs +++ /dev/null @@ -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, - trash: Vec, -} - -impl FolderPadBuilder { - pub(crate) fn new() -> Self { - Self { - workspaces: vec![], - trash: vec![], - } - } - - #[allow(dead_code)] - pub(crate) fn with_workspace(mut self, workspaces: Vec) -> Self { - self.workspaces = workspaces; - self - } - - #[allow(dead_code)] - pub(crate) fn with_trash(mut self, trash: Vec) -> Self { - self.trash = trash; - self - } - - pub(crate) fn build_with_revisions(self, revisions: Vec) -> SyncResult { - let mut operations: FolderOperations = make_operations_from_revisions(revisions)?; - if operations.is_empty() { - operations = default_folder_operations(); - } - FolderPad::from_operations(operations) - } - - #[allow(dead_code)] - pub(crate) fn build(self) -> SyncResult { - FolderPad::new(self.workspaces, self.trash) - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_folder/folder_node.rs b/frontend/rust-lib/flowy-client-sync/src/client_folder/folder_node.rs deleted file mode 100644 index 891f7d2e02..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_folder/folder_node.rs +++ /dev/null @@ -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; - -pub struct FolderNodePad { - pub tree: Arc, - pub node_id: NodeId, - pub workspaces: WorkspaceList, - pub trash: TrashList, -} - -#[derive(Clone, Node)] -#[node_type = "workspaces"] -pub struct WorkspaceList { - pub tree: Arc, - pub node_id: Option, - - #[node(child_name = "workspace")] - inner: Vec, -} - -impl std::ops::Deref for WorkspaceList { - type Target = Vec; - - 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, - pub node_id: Option, - - #[node(child_name = "trash")] - inner: Vec, -} - -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 { - 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) -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_folder/folder_pad.rs b/frontend/rust-lib/flowy-client-sync/src/client_folder/folder_pad.rs deleted file mode 100644 index 3c4eae04dd..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_folder/folder_pad.rs +++ /dev/null @@ -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; -pub type FolderOperationsBuilder = DeltaOperationBuilder; - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct FolderPad { - folder_rev: FolderRevision, - operations: FolderOperations, -} - -impl FolderPad { - pub fn new(workspaces: Vec, trash: Vec) -> SyncResult { - let folder_rev = FolderRevision { - workspaces: workspaces.into_iter().map(Arc::new).collect(), - trash: trash.into_iter().map(Arc::new).collect(), - }; - Self::from_folder_rev(folder_rev) - } - - pub fn from_folder_rev(folder_rev: FolderRevision) -> SyncResult { - let json = serde_json::to_string(&folder_rev).map_err(|e| { - SyncError::internal().context(format!("Serialize to folder json str failed: {}", e)) - })?; - let operations = FolderOperationsBuilder::new().insert(&json).build(); - - Ok(Self { - folder_rev, - operations, - }) - } - - pub fn from_revisions(revisions: Vec) -> SyncResult { - FolderPadBuilder::new().build_with_revisions(revisions) - } - - pub fn from_operations(operations: FolderOperations) -> SyncResult { - let content = operations.content()?; - let mut deserializer = serde_json::Deserializer::from_reader(content.as_bytes()); - - let folder_rev = FolderRevision::deserialize(&mut deserializer).map_err(|e| { - tracing::error!("Deserialize folder from {} failed", content); - SyncError::internal().context(format!("Deserialize operations to folder failed: {}", e)) - })?; - - Ok(Self { - folder_rev, - operations, - }) - } - - pub fn get_operations(&self) -> &FolderOperations { - &self.operations - } - - pub fn reset_folder(&mut self, operations: FolderOperations) -> SyncResult { - let folder = FolderPad::from_operations(operations)?; - self.folder_rev = folder.folder_rev; - self.operations = folder.operations; - - Ok(self.folder_md5()) - } - - pub fn compose_remote_operations(&mut self, operations: FolderOperations) -> SyncResult { - let composed_operations = self.operations.compose(&operations)?; - self.reset_folder(composed_operations) - } - - pub fn is_empty(&self) -> bool { - self.folder_rev.workspaces.is_empty() && self.folder_rev.trash.is_empty() - } - - #[tracing::instrument(level = "trace", skip(self, workspace_rev), fields(workspace_name=%workspace_rev.name), err)] - pub fn create_workspace( - &mut self, - workspace_rev: WorkspaceRevision, - ) -> SyncResult> { - let workspace = Arc::new(workspace_rev); - if self.folder_rev.workspaces.contains(&workspace) { - tracing::warn!("[RootFolder]: Duplicate workspace"); - return Ok(None); - } - - self.modify_workspaces(move |workspaces| { - workspaces.push(workspace); - Ok(Some(())) - }) - } - - pub fn update_workspace( - &mut self, - workspace_id: &str, - name: Option, - desc: Option, - ) -> SyncResult> { - self.with_workspace(workspace_id, |workspace| { - if let Some(name) = name { - workspace.name = name; - } - - if let Some(desc) = desc { - workspace.desc = desc; - } - Ok(Some(())) - }) - } - - pub fn read_workspaces( - &self, - workspace_id: Option, - ) -> SyncResult> { - match workspace_id { - None => { - let workspaces = self - .folder_rev - .workspaces - .iter() - .map(|workspace| workspace.as_ref().clone()) - .collect::>(); - Ok(workspaces) - }, - Some(workspace_id) => { - if let Some(workspace) = self - .folder_rev - .workspaces - .iter() - .find(|workspace| workspace.id == workspace_id) - { - Ok(vec![workspace.as_ref().clone()]) - } else { - Err( - SyncError::record_not_found() - .context(format!("Can't find workspace with id {}", workspace_id)), - ) - } - }, - } - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub fn delete_workspace(&mut self, workspace_id: &str) -> SyncResult> { - self.modify_workspaces(|workspaces| { - workspaces.retain(|w| w.id != workspace_id); - Ok(Some(())) - }) - } - - #[tracing::instrument(level = "trace", skip(self), fields(app_name=%app_rev.name), err)] - pub fn create_app(&mut self, app_rev: AppRevision) -> SyncResult> { - let workspace_id = app_rev.workspace_id.clone(); - self.with_workspace(&workspace_id, move |workspace| { - if workspace.apps.contains(&app_rev) { - tracing::warn!("[RootFolder]: Duplicate app"); - return Ok(None); - } - workspace.apps.push(app_rev); - Ok(Some(())) - }) - } - - pub fn read_app(&self, app_id: &str) -> SyncResult { - for workspace in &self.folder_rev.workspaces { - if let Some(app) = workspace.apps.iter().find(|app| app.id == app_id) { - return Ok(app.clone()); - } - } - 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, - desc: Option, - ) -> SyncResult> { - self.with_app(app_id, move |app| { - if let Some(name) = name { - app.name = name; - } - - if let Some(desc) = desc { - app.desc = desc; - } - Ok(Some(())) - }) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub fn delete_app(&mut self, app_id: &str) -> SyncResult> { - let app = self.read_app(app_id)?; - self.with_workspace(&app.workspace_id, |workspace| { - workspace.apps.retain(|app| app.id != app_id); - Ok(Some(())) - }) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub fn move_app( - &mut self, - app_id: &str, - from: usize, - to: usize, - ) -> SyncResult> { - let app = self.read_app(app_id)?; - self.with_workspace(&app.workspace_id, |workspace| { - match move_vec_element(&mut workspace.apps, |app| app.id == app_id, from, to) - .map_err(internal_sync_error)? - { - true => Ok(Some(())), - false => Ok(None), - } - }) - } - - #[tracing::instrument(level = "trace", skip(self), fields(view_name=%view_rev.name), err)] - pub fn create_view(&mut self, view_rev: ViewRevision) -> SyncResult> { - let app_id = view_rev.app_id.clone(); - self.with_app(&app_id, move |app| { - if app.belongings.contains(&view_rev) { - tracing::warn!("[RootFolder]: Duplicate view"); - return Ok(None); - } - app.belongings.push(view_rev); - Ok(Some(())) - }) - } - - pub fn read_view(&self, view_id: &str) -> SyncResult { - for workspace in &self.folder_rev.workspaces { - for app in &(*workspace.apps) { - if let Some(view) = app.belongings.iter().find(|b| b.id == view_id) { - return Ok(view.clone()); - } - } - } - Err(SyncError::record_not_found().context(format!("Can't find view with id {}", view_id))) - } - - pub fn read_views(&self, belong_to_id: &str) -> SyncResult> { - for workspace in &self.folder_rev.workspaces { - for app in &(*workspace.apps) { - if app.id == belong_to_id { - return Ok(app.belongings.to_vec()); - } - } - } - Ok(vec![]) - } - - pub fn update_view( - &mut self, - view_id: &str, - name: Option, - desc: Option, - modified_time: i64, - ) -> SyncResult> { - let view = self.read_view(view_id)?; - self.with_view(&view.app_id, view_id, |view| { - if let Some(name) = name { - view.name = name; - } - - if let Some(desc) = desc { - view.desc = desc; - } - - view.modified_time = modified_time; - Ok(Some(())) - }) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub fn delete_view( - &mut self, - app_id: &str, - view_id: &str, - ) -> SyncResult> { - self.with_app(app_id, |app| { - app.belongings.retain(|view| view.id != view_id); - Ok(Some(())) - }) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub fn move_view( - &mut self, - view_id: &str, - from: usize, - to: usize, - ) -> SyncResult> { - let view = self.read_view(view_id)?; - self.with_app(&view.app_id, |app| { - match move_vec_element(&mut app.belongings, |view| view.id == view_id, from, to) - .map_err(internal_sync_error)? - { - true => Ok(Some(())), - false => Ok(None), - } - }) - } - - pub fn create_trash(&mut self, trash: Vec) -> SyncResult> { - self.with_trash(|original_trash| { - let mut new_trash = trash - .into_iter() - .flat_map(|new_trash| { - if original_trash - .iter() - .any(|old_trash| old_trash.id == new_trash.id) - { - None - } else { - Some(Arc::new(new_trash)) - } - }) - .collect::>>(); - if new_trash.is_empty() { - Ok(None) - } else { - original_trash.append(&mut new_trash); - Ok(Some(())) - } - }) - } - - pub fn read_trash(&self, trash_id: Option) -> SyncResult> { - match trash_id { - None => { - // Removes the duplicate items if exist - let mut trash_items = Vec::::with_capacity(self.folder_rev.trash.len()); - for trash_item in self.folder_rev.trash.iter() { - if !trash_items.iter().any(|item| item.id == trash_item.id) { - trash_items.push(trash_item.as_ref().clone()); - } - } - Ok(trash_items) - }, - Some(trash_id) => match self.folder_rev.trash.iter().find(|t| t.id == trash_id) { - Some(trash) => Ok(vec![trash.as_ref().clone()]), - None => Ok(vec![]), - }, - } - } - - pub fn delete_trash( - &mut self, - trash_ids: Option>, - ) -> SyncResult> { - match trash_ids { - None => self.with_trash(|trash| { - trash.clear(); - Ok(Some(())) - }), - Some(trash_ids) => self.with_trash(|trash| { - trash.retain(|t| !trash_ids.contains(&t.id)); - Ok(Some(())) - }), - } - } - - pub fn folder_md5(&self) -> String { - md5(&self.operations.json_bytes()) - } - - pub fn to_json(&self) -> SyncResult { - make_folder_rev_json_str(&self.folder_rev) - } -} - -pub fn make_folder_rev_json_str(folder_rev: &FolderRevision) -> SyncResult { - let json = serde_json::to_string(folder_rev).map_err(|err| { - internal_sync_error(format!("Serialize folder to json str failed. {:?}", err)) - })?; - Ok(json) -} - -impl FolderPad { - fn modify_workspaces(&mut self, f: F) -> SyncResult> - where - F: FnOnce(&mut Vec>) -> SyncResult>, - { - let cloned_self = self.clone(); - match f(&mut self.folder_rev.workspaces)? { - None => Ok(None), - Some(_) => { - let old = cloned_self.to_json()?; - let new = self.to_json()?; - match cal_diff::(old, new) { - None => Ok(None), - Some(operations) => { - self.operations = self.operations.compose(&operations)?; - Ok(Some(FolderChangeset { - operations, - md5: self.folder_md5(), - })) - }, - } - }, - } - } - - fn with_workspace(&mut self, workspace_id: &str, f: F) -> SyncResult> - where - F: FnOnce(&mut WorkspaceRevision) -> SyncResult>, - { - self.modify_workspaces(|workspaces| { - if let Some(workspace) = workspaces - .iter_mut() - .find(|workspace| workspace_id == workspace.id) - { - f(Arc::make_mut(workspace)) - } else { - tracing::warn!( - "[FolderPad]: Can't find any workspace with id: {}", - workspace_id - ); - Ok(None) - } - }) - } - - fn with_trash(&mut self, f: F) -> SyncResult> - where - F: FnOnce(&mut Vec>) -> SyncResult>, - { - let cloned_self = self.clone(); - match f(&mut self.folder_rev.trash)? { - None => Ok(None), - Some(_) => { - let old = cloned_self.to_json()?; - let new = self.to_json()?; - match cal_diff::(old, new) { - None => Ok(None), - Some(operations) => { - self.operations = self.operations.compose(&operations)?; - Ok(Some(FolderChangeset { - operations, - md5: self.folder_md5(), - })) - }, - } - }, - } - } - - fn with_app(&mut self, app_id: &str, f: F) -> SyncResult> - where - F: FnOnce(&mut AppRevision) -> SyncResult>, - { - let workspace_id = match self - .folder_rev - .workspaces - .iter() - .find(|workspace| workspace.apps.iter().any(|app| app.id == app_id)) - { - 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( - &mut self, - belong_to_id: &str, - view_id: &str, - f: F, - ) -> SyncResult> - where - F: FnOnce(&mut ViewRevision) -> SyncResult>, - { - self.with_app(belong_to_id, |app| { - match app.belongings.iter_mut().find(|view| view_id == view.id) { - None => { - tracing::warn!("[FolderPad]: Can't find any view with id: {}", view_id); - Ok(None) - }, - Some(view) => f(view), - } - }) - } -} - -pub fn default_folder_operations() -> FolderOperations { - FolderOperationsBuilder::new() - .insert(r#"{"workspaces":[],"trash":[]}"#) - .build() -} - -pub fn initial_folder_operations(folder_pad: &FolderPad) -> SyncResult { - 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, - ) -> 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); - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_folder/mod.rs b/frontend/rust-lib/flowy-client-sync/src/client_folder/mod.rs deleted file mode 100644 index 355880c899..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_folder/mod.rs +++ /dev/null @@ -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::*; diff --git a/frontend/rust-lib/flowy-client-sync/src/client_folder/trash_node.rs b/frontend/rust-lib/flowy-client-sync/src/client_folder/trash_node.rs deleted file mode 100644 index da6caa118d..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_folder/trash_node.rs +++ /dev/null @@ -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, - pub node_id: Option, - - #[node(get_value_with = "get_attributes_str_value")] - #[node(set_value_with = "set_attributes_str_value")] - pub id: String, - - #[node(get_value_with = "get_attributes_str_value")] - #[node(set_value_with = "set_attributes_str_value")] - pub name: String, -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_folder/util.rs b/frontend/rust-lib/flowy-client-sync/src/client_folder/util.rs deleted file mode 100644 index d45eab2a16..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_folder/util.rs +++ /dev/null @@ -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, - node_id: &NodeId, - key: &str, -) -> Option { - tree - .read() - .get_node(*node_id) - .and_then(|node| node.attributes.get(key).cloned()) - .and_then(|value| value.str_value()) -} - -pub fn set_attributes_str_value( - tree: Arc, - node_id: &NodeId, - key: &str, - value: String, -) -> 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, - node_id: &NodeId, - key: &str, -) -> Option { - tree - .read() - .get_node(*node_id) - .and_then(|node| node.attributes.get(key).cloned()) - .and_then(|value| value.int_value()) -} - -pub fn get_attributes(tree: Arc, node_id: &NodeId) -> Option { - tree - .read() - .get_node(*node_id) - .map(|node| node.attributes.clone()) -} - -#[allow(dead_code)] -pub fn get_attributes_value( - tree: Arc, - node_id: &NodeId, - key: &str, -) -> Option { - tree - .read() - .get_node(*node_id) - .and_then(|node| node.attributes.get(key).cloned()) -} diff --git a/frontend/rust-lib/flowy-client-sync/src/client_folder/workspace_node.rs b/frontend/rust-lib/flowy-client-sync/src/client_folder/workspace_node.rs deleted file mode 100644 index ff07b5f8e2..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/client_folder/workspace_node.rs +++ /dev/null @@ -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, - pub node_id: Option, - - #[node(get_value_with = "get_attributes_str_value")] - #[node(set_value_with = "set_attributes_str_value")] - pub id: String, - - #[node(get_value_with = "get_attributes_str_value")] - #[node(set_value_with = "set_attributes_str_value")] - pub name: String, - - #[node(child_name = "app")] - pub apps: Vec, -} - -impl WorkspaceNode { - pub fn new(tree: Arc, id: String, name: String) -> Self { - Self { - tree, - node_id: None, - id, - name, - apps: vec![], - } - } -} - -#[derive(Clone, Node)] -#[node_type = "app"] -pub struct AppNode { - pub tree: Arc, - pub node_id: Option, - - #[node(get_value_with = "get_attributes_str_value")] - #[node(set_value_with = "set_attributes_str_value")] - pub id: String, - - #[node(get_value_with = "get_attributes_str_value")] - #[node(set_value_with = "set_attributes_str_value")] - pub name: String, -} - -impl AppNode { - pub fn new(tree: Arc, id: String, name: String) -> Self { - Self { - tree, - node_id: None, - id, - name, - } - } -} diff --git a/frontend/rust-lib/flowy-client-sync/src/lib.rs b/frontend/rust-lib/flowy-client-sync/src/lib.rs deleted file mode 100644 index 7fe27011bc..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/lib.rs +++ /dev/null @@ -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; diff --git a/frontend/rust-lib/flowy-client-sync/src/util.rs b/frontend/rust-lib/flowy-client-sync/src/util.rs deleted file mode 100644 index 7195e44888..0000000000 --- a/frontend/rust-lib/flowy-client-sync/src/util.rs +++ /dev/null @@ -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 { - 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( - revisions: Vec, - validator: impl Fn(&DeltaOperations) -> bool, -) -> Option<(DeltaOperations, i64)> -where - T: OperationAttributes + DeserializeOwned + OperationAttributes, -{ - let mut new_operations = DeltaOperations::::new(); - let mut rev_id = 0; - for revision in revisions { - if let Ok(operations) = DeltaOperations::::from_bytes(revision.bytes) { - match new_operations.compose(&operations) { - Ok(composed_operations) => { - if validator(&composed_operations) { - rev_id = revision.rev_id; - new_operations = composed_operations; - } else { - break; - } - }, - Err(_) => break, - } - } else { - 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, -) -> Result, 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 { - let rev_id = s - .to_owned() - .parse::() - .map_err(|e| SyncError::internal().context(format!("Parse rev_id from {} failed. {}", s, e)))?; - Ok(rev_id) -} - -pub fn cal_diff(old: String, new: String) -> Option> { - let chunks = dissimilar::diff(&old, &new); - let mut delta_builder = DeltaOperationBuilder::::new(); - for chunk in &chunks { - match chunk { - Chunk::Equal(s) => { - delta_builder = delta_builder.retain(OTString::from(*s).utf16_len()); - }, - Chunk::Delete(s) => { - delta_builder = delta_builder.delete(OTString::from(*s).utf16_len()); - }, - Chunk::Insert(s) => { - delta_builder = delta_builder.insert(s); - }, - } - } - - let delta = delta_builder.build(); - if delta.is_empty() { - None - } else { - Some(delta) - } -} diff --git a/frontend/rust-lib/flowy-client-sync/tests/client_folder/folder_test.rs b/frontend/rust-lib/flowy-client-sync/tests/client_folder/folder_test.rs deleted file mode 100644 index 3ac50a25c3..0000000000 --- a/frontend/rust-lib/flowy-client-sync/tests/client_folder/folder_test.rs +++ /dev/null @@ -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" - ); -} diff --git a/frontend/rust-lib/flowy-client-sync/tests/client_folder/mod.rs b/frontend/rust-lib/flowy-client-sync/tests/client_folder/mod.rs deleted file mode 100644 index 727fee521b..0000000000 --- a/frontend/rust-lib/flowy-client-sync/tests/client_folder/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod folder_test; -mod script; -mod workspace_test; diff --git a/frontend/rust-lib/flowy-client-sync/tests/client_folder/script.rs b/frontend/rust-lib/flowy-client-sync/tests/client_folder/script.rs deleted file mode 100644 index 8d54b91fe6..0000000000 --- a/frontend/rust-lib/flowy-client-sync/tests/client_folder/script.rs +++ /dev/null @@ -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, - }, - 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) { - 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); - // } - } - } -} diff --git a/frontend/rust-lib/flowy-client-sync/tests/client_folder/workspace_test.rs b/frontend/rust-lib/flowy-client-sync/tests/client_folder/workspace_test.rs deleted file mode 100644 index bd54528191..0000000000 --- a/frontend/rust-lib/flowy-client-sync/tests/client_folder/workspace_test.rs +++ /dev/null @@ -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(), - }, - ]); -} diff --git a/frontend/rust-lib/flowy-client-sync/tests/main.rs b/frontend/rust-lib/flowy-client-sync/tests/main.rs deleted file mode 100644 index 6eb34d1302..0000000000 --- a/frontend/rust-lib/flowy-client-sync/tests/main.rs +++ /dev/null @@ -1 +0,0 @@ -mod client_folder; diff --git a/frontend/rust-lib/flowy-core/Cargo.toml b/frontend/rust-lib/flowy-core/Cargo.toml index f514f994bc..4fc512cb72 100644 --- a/frontend/rust-lib/flowy-core/Cargo.toml +++ b/frontend/rust-lib/flowy-core/Cargo.toml @@ -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"] diff --git a/frontend/rust-lib/flowy-core/src/deps_resolve/database_deps.rs b/frontend/rust-lib/flowy-core/src/deps_resolve/database_deps.rs index 431a52047f..db8bc84e45 100644 --- a/frontend/rust-lib/flowy-core/src/deps_resolve/database_deps.rs +++ b/frontend/rust-lib/flowy-core/src/deps_resolve/database_deps.rs @@ -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, user_session: Arc, task_scheduler: Arc>, collab_builder: Arc, diff --git a/frontend/rust-lib/flowy-core/src/deps_resolve/document_deps.rs b/frontend/rust-lib/flowy-core/src/deps_resolve/document_deps.rs deleted file mode 100644 index 2560964c27..0000000000 --- a/frontend/rust-lib/flowy-core/src/deps_resolve/document_deps.rs +++ /dev/null @@ -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>, - ws_conn: Arc, - user_session: Arc, - server_config: &ClientServerConfiguration, - document_config: &DocumentConfig, - ) -> Arc { - let user = Arc::new(BlockUserImpl(user_session.clone())); - let rev_web_socket = Arc::new(DocumentRevisionWebSocket(ws_conn.clone())); - let cloud_service: Arc = match local_server { - None => Arc::new(DocumentCloudServiceImpl::new(server_config.clone())), - Some(local_server) => local_server, - }; - let database = Arc::new(DocumentDatabaseImpl(user_session)); - - let manager = Arc::new(DocumentManager::new( - cloud_service, - user, - database, - rev_web_socket, - document_config.clone(), - )); - let receiver = Arc::new(DocumentWSMessageReceiverImpl(manager.clone())); - ws_conn.add_ws_message_receiver(receiver).unwrap(); - - manager - } -} - -struct BlockUserImpl(Arc); -impl DocumentUser for BlockUserImpl { - fn user_dir(&self) -> Result { - let dir = self - .0 - .user_dir() - .map_err(|e| FlowyError::unauthorized().context(e))?; - - let doc_dir = format!("{}/document", dir); - if !Path::new(&doc_dir).exists() { - std::fs::create_dir_all(&doc_dir)?; - } - Ok(doc_dir) - } - - fn user_id(&self) -> Result { - self.0.user_id() - } - - fn token(&self) -> Result { - self.0.token() - } -} - -struct DocumentDatabaseImpl(Arc); -impl DocumentDatabase for DocumentDatabaseImpl { - fn db_pool(&self) -> Result, FlowyError> { - self.0.db_pool() - } -} - -struct DocumentRevisionWebSocket(Arc); -impl RevisionWebSocket for DocumentRevisionWebSocket { - fn send(&self, data: ClientRevisionWSData) -> BoxResultFuture<(), FlowyError> { - let bytes: Bytes = data.try_into().unwrap(); - let _msg = WebSocketRawMessage { - channel: WSChannel::Document, - data: bytes.to_vec(), - }; - let _ws_conn = self.0.clone(); - Box::pin(async move { - // match ws_conn.web_socket().await? { - // None => {}, - // Some(sender) => { - // sender.send(msg).map_err(internal_error)?; - // }, - // } - Ok(()) - }) - } - - fn subscribe_state_changed(&self) -> BoxFuture { - let ws_conn = self.0.clone(); - Box::pin(async move { ws_conn.subscribe_websocket_state().await }) - } -} - -struct DocumentWSMessageReceiverImpl(Arc); -impl WSMessageReceiver for DocumentWSMessageReceiverImpl { - fn source(&self) -> WSChannel { - WSChannel::Document - } - fn receive_message(&self, msg: WebSocketRawMessage) { - let handler = self.0.clone(); - tokio::spawn(async move { - handler.receive_ws_data(Bytes::from(msg.data)).await; - }); - } -} diff --git a/frontend/rust-lib/flowy-core/src/deps_resolve/mod.rs b/frontend/rust-lib/flowy-core/src/deps_resolve/mod.rs index 6682961e31..06fd4a9cef 100644 --- a/frontend/rust-lib/flowy-core/src/deps_resolve/mod.rs +++ b/frontend/rust-lib/flowy-core/src/deps_resolve/mod.rs @@ -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; diff --git a/frontend/rust-lib/flowy-core/src/deps_resolve/user_deps.rs b/frontend/rust-lib/flowy-core/src/deps_resolve/user_deps.rs index e4a82ee75c..66b3922070 100644 --- a/frontend/rust-lib/flowy-core/src/deps_resolve/user_deps.rs +++ b/frontend/rust-lib/flowy-core/src/deps_resolve/user_deps.rs @@ -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( diff --git a/frontend/rust-lib/flowy-core/src/lib.rs b/frontend/rust-lib/flowy-core/src/lib.rs index ae7a38e2e0..a10630c7ba 100644 --- a/frontend/rust-lib/flowy-core/src/lib.rs +++ b/frontend/rust-lib/flowy-core/src/lib.rs @@ -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) -> 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 { .map(|crate_name| format!("{}={}", crate_name, level)) .collect::>(); 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, - pub document_manager: Arc, pub document_manager2: Arc, pub folder_manager: Arc, // pub database_manager: Arc, pub database_manager: Arc, pub event_dispatcher: Arc, - pub ws_conn: Arc, pub local_server: Option>, pub task_dispatcher: Arc>, } @@ -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, - folder_manager: &Arc, -) { - let subscribe_network_type = ws_conn.subscribe_network_ty(); - let folder_manager = folder_manager.clone(); - let _cloned_folder_manager = folder_manager; - let ws_conn = ws_conn.clone(); - - 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) { - let ws_addr = server_config.ws_addr(); +fn mk_local_server(server_config: &ClientServerConfiguration) -> Option> { + // 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) { - 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, folder_manager: Arc, database_manager: Arc, - ws_conn: Arc, #[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(()) } } diff --git a/frontend/rust-lib/flowy-core/src/module.rs b/frontend/rust-lib/flowy-core/src/module.rs index ef0720033f..fbd6df748d 100644 --- a/frontend/rust-lib/flowy-core/src/module.rs +++ b/frontend/rust-lib/flowy-core/src/module.rs @@ -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, folder_manager: &Arc, database_manager: &Arc, user_session: &Arc, - document_manager: &Arc, document_manager2: &Arc, ) -> Vec { 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, ] } diff --git a/frontend/rust-lib/flowy-database/Cargo.toml b/frontend/rust-lib/flowy-database/Cargo.toml deleted file mode 100644 index 904035380a..0000000000 --- a/frontend/rust-lib/flowy-database/Cargo.toml +++ /dev/null @@ -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"] diff --git a/frontend/rust-lib/flowy-database/Flowy.toml b/frontend/rust-lib/flowy-database/Flowy.toml deleted file mode 100644 index 37dd8da743..0000000000 --- a/frontend/rust-lib/flowy-database/Flowy.toml +++ /dev/null @@ -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"] \ No newline at end of file diff --git a/frontend/rust-lib/flowy-database/build.rs b/frontend/rust-lib/flowy-database/build.rs deleted file mode 100644 index 06388d2a02..0000000000 --- a/frontend/rust-lib/flowy-database/build.rs +++ /dev/null @@ -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); -} diff --git a/frontend/rust-lib/flowy-database/src/entities/calendar_entities.rs b/frontend/rust-lib/flowy-database/src/entities/calendar_entities.rs deleted file mode 100644 index f03b8c9a26..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/calendar_entities.rs +++ /dev/null @@ -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 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 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 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 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 for CalendarEventRequestPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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, -} - -#[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, -} diff --git a/frontend/rust-lib/flowy-database/src/entities/cell_entities.rs b/frontend/rust-lib/flowy-database/src/entities/cell_entities.rs deleted file mode 100644 index f1556005b2..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/cell_entities.rs +++ /dev/null @@ -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 for CreateSelectOptionPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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 for CellIdPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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, - - /// the field_type will be None if the field with field_id is not found - #[pb(index = 4, one_of)] - pub field_type: Option, -} - -impl CellPB { - pub fn new(field_id: &str, row_id: &str, field_type: FieldType, data: Vec) -> Self { - Self { - field_id: field_id.to_owned(), - row_id: row_id.to_string(), - data, - field_type: Some(field_type), - } - } - - pub fn 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, -} - -impl std::ops::Deref for RepeatedCellPB { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.items - } -} - -impl std::ops::DerefMut for RepeatedCellPB { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.items - } -} - -impl std::convert::From> for RepeatedCellPB { - fn from(items: Vec) -> 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 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, - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/entities/database_entities.rs b/frontend/rust-lib/flowy-database/src/entities/database_entities.rs deleted file mode 100644 index 59026ebaee..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/database_entities.rs +++ /dev/null @@ -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, - - #[pb(index = 3)] - pub rows: Vec, -} - -#[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 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 for MoveFieldPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?; - let item_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::InvalidData)?; - Ok(MoveFieldParams { - view_id: view_id.0, - field_id: item_id.0, - from_index: self.from_index, - to_index: self.to_index, - }) - } -} - -#[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 for MoveRowPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?; - let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; - let to_row_id = NotEmptyStr::parse(self.to_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; - - 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, -} - -pub struct MoveGroupRowParams { - pub view_id: String, - pub from_row_id: String, - pub to_group_id: String, - pub to_row_id: Option, -} - -impl TryInto for MoveGroupRowPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?; - let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; - let to_group_id = - NotEmptyStr::parse(self.to_group_id).map_err(|_| ErrorCode::GroupIdIsEmpty)?; - - let to_row_id = match self.to_row_id { - None => None, - Some(to_row_id) => Some( - NotEmptyStr::parse(to_row_id) - .map_err(|_| ErrorCode::RowIdIsEmpty)? - .0, - ), - }; - - 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, -} - -#[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 for DatabaseGroupIdPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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, -} diff --git a/frontend/rust-lib/flowy-database/src/entities/field_entities.rs b/frontend/rust-lib/flowy-database/src/entities/field_entities.rs deleted file mode 100644 index d8ef22cb38..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/field_entities.rs +++ /dev/null @@ -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 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> for FieldPB { - fn from(field_rev: Arc) -> Self { - let field_rev = field_rev.as_ref().clone(); - FieldPB::from(field_rev) - } -} - -/// [FieldIdPB] id of the [Field] -#[derive(Debug, Clone, Default, ProtoBuf)] -pub struct FieldIdPB { - #[pb(index = 1)] - pub field_id: String, -} - -impl std::convert::From<&str> for FieldIdPB { - fn from(s: &str) -> Self { - FieldIdPB { - field_id: s.to_owned(), - } - } -} - -impl std::convert::From for FieldIdPB { - fn from(s: String) -> Self { - FieldIdPB { field_id: s } - } -} - -impl std::convert::From<&Arc> for FieldIdPB { - fn from(field_rev: &Arc) -> Self { - Self { - field_id: field_rev.id.clone(), - } - } -} -#[derive(Debug, Clone, Default, ProtoBuf)] -pub struct DatabaseFieldChangesetPB { - #[pb(index = 1)] - pub view_id: String, - - #[pb(index = 2)] - pub inserted_fields: Vec, - - #[pb(index = 3)] - pub deleted_fields: Vec, - - #[pb(index = 4)] - pub updated_fields: Vec, -} - -impl DatabaseFieldChangesetPB { - pub fn insert(database_id: &str, inserted_fields: Vec) -> 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) -> 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) -> 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, 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>, -} - -#[derive(Clone)] -pub struct CreateFieldParams { - pub view_id: String, - pub field_type: FieldType, - pub type_option_data: Option>, -} - -impl TryInto for CreateFieldPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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 for UpdateFieldTypePayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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 for TypeOptionPathPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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, -} - -/// Collection of the [FieldPB] -#[derive(Debug, Default, ProtoBuf)] -pub struct RepeatedFieldPB { - #[pb(index = 1)] - pub items: Vec, -} -impl std::ops::Deref for RepeatedFieldPB { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.items - } -} - -impl std::ops::DerefMut for RepeatedFieldPB { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.items - } -} - -impl std::convert::From> for RepeatedFieldPB { - fn from(items: Vec) -> Self { - Self { items } - } -} - -#[derive(Debug, Clone, Default, ProtoBuf)] -pub struct RepeatedFieldIdPB { - #[pb(index = 1)] - pub items: Vec, -} - -impl std::ops::Deref for RepeatedFieldIdPB { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.items - } -} - -impl std::convert::From> for RepeatedFieldIdPB { - fn from(items: Vec) -> Self { - RepeatedFieldIdPB { items } - } -} - -impl std::convert::From 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, -} - -#[derive(Clone)] -pub struct TypeOptionChangesetParams { - pub view_id: String, - pub field_id: String, - pub type_option_data: Vec, -} - -impl TryInto for TypeOptionChangesetPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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, -} - -pub struct GetFieldParams { - pub view_id: String, - pub field_ids: Option>, -} - -impl TryInto for GetFieldPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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::>() - }); - - 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, - - #[pb(index = 4, one_of)] - pub desc: Option, - - #[pb(index = 5, one_of)] - pub field_type: Option, - - #[pb(index = 6, one_of)] - pub frozen: Option, - - #[pb(index = 7, one_of)] - pub visibility: Option, - - #[pb(index = 8, one_of)] - pub width: Option, - // #[pb(index = 9, one_of)] - // pub type_option_data: Option>, -} - -impl TryInto for FieldChangesetPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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, - - pub desc: Option, - - pub field_type: Option, - - pub frozen: Option, - - pub visibility: Option, - - pub width: Option, - // pub type_option_data: Option>, -} -/// Certain field types have user-defined options such as color, date format, number format, -/// or a list of values for a multi-select list. These options are defined within a specialization -/// 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 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 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 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 for DuplicateFieldPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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 for DeleteFieldPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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, -} diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/checkbox_filter.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/checkbox_filter.rs deleted file mode 100644 index 544ab2574d..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/checkbox_filter.rs +++ /dev/null @@ -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 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 for CheckboxFilterConditionPB { - type Error = ErrorCode; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(CheckboxFilterConditionPB::IsChecked), - 1 => Ok(CheckboxFilterConditionPB::IsUnChecked), - _ => Err(ErrorCode::InvalidData), - } - } -} - -impl FromFilterString for CheckboxFilterPB { - fn from_filter_rev(filter_rev: &FilterRevision) -> Self - where - Self: Sized, - { - CheckboxFilterPB { - condition: CheckboxFilterConditionPB::try_from(filter_rev.condition) - .unwrap_or(CheckboxFilterConditionPB::IsChecked), - } - } -} - -impl std::convert::From<&FilterRevision> for CheckboxFilterPB { - fn from(rev: &FilterRevision) -> Self { - CheckboxFilterPB { - condition: CheckboxFilterConditionPB::try_from(rev.condition) - .unwrap_or(CheckboxFilterConditionPB::IsChecked), - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/checklist_filter.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/checklist_filter.rs deleted file mode 100644 index 60c70273e0..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/checklist_filter.rs +++ /dev/null @@ -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 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 for ChecklistFilterConditionPB { - type Error = ErrorCode; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(ChecklistFilterConditionPB::IsComplete), - 1 => Ok(ChecklistFilterConditionPB::IsIncomplete), - _ => Err(ErrorCode::InvalidData), - } - } -} - -impl FromFilterString for ChecklistFilterPB { - fn from_filter_rev(filter_rev: &FilterRevision) -> Self - where - Self: Sized, - { - ChecklistFilterPB { - condition: ChecklistFilterConditionPB::try_from(filter_rev.condition) - .unwrap_or(ChecklistFilterConditionPB::IsIncomplete), - } - } -} - -impl std::convert::From<&FilterRevision> for ChecklistFilterPB { - fn from(rev: &FilterRevision) -> Self { - ChecklistFilterPB { - condition: ChecklistFilterConditionPB::try_from(rev.condition) - .unwrap_or(ChecklistFilterConditionPB::IsIncomplete), - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/date_filter.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/date_filter.rs deleted file mode 100644 index 5eb962a1fa..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/date_filter.rs +++ /dev/null @@ -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, - - #[pb(index = 3, one_of)] - pub end: Option, - - #[pb(index = 4, one_of)] - pub timestamp: Option, -} - -#[derive(Deserialize, Serialize, Default, Clone, Debug)] -pub struct DateFilterContentPB { - pub start: Option, - pub end: Option, - pub timestamp: Option, -} - -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 { - 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 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 for DateFilterConditionPB { - type Error = ErrorCode; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(DateFilterConditionPB::DateIs), - 1 => Ok(DateFilterConditionPB::DateBefore), - 2 => Ok(DateFilterConditionPB::DateAfter), - 3 => Ok(DateFilterConditionPB::DateOnOrBefore), - 4 => Ok(DateFilterConditionPB::DateOnOrAfter), - 5 => Ok(DateFilterConditionPB::DateWithIn), - 6 => Ok(DateFilterConditionPB::DateIsEmpty), - _ => Err(ErrorCode::InvalidData), - } - } -} -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 - } -} diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/filter_changeset.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/filter_changeset.rs deleted file mode 100644 index 05a0fbd4ea..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/filter_changeset.rs +++ /dev/null @@ -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, - - #[pb(index = 3)] - pub delete_filters: Vec, - - #[pb(index = 4)] - pub update_filters: Vec, -} - -#[derive(Debug, Default, ProtoBuf)] -pub struct UpdatedFilter { - #[pb(index = 1)] - pub filter_id: String, - - #[pb(index = 2, one_of)] - pub filter: Option, -} - -impl FilterChangesetNotificationPB { - pub fn from_insert(view_id: &str, filters: Vec) -> Self { - Self { - view_id: view_id.to_string(), - insert_filters: filters, - delete_filters: Default::default(), - update_filters: Default::default(), - } - } - pub fn from_delete(view_id: &str, filters: Vec) -> Self { - Self { - view_id: view_id.to_string(), - insert_filters: Default::default(), - delete_filters: filters, - update_filters: Default::default(), - } - } - - pub fn from_update(view_id: &str, filters: Vec) -> Self { - Self { - view_id: view_id.to_string(), - insert_filters: Default::default(), - delete_filters: Default::default(), - update_filters: filters, - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/mod.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/mod.rs deleted file mode 100644 index d628a13801..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/mod.rs +++ /dev/null @@ -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::*; diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/number_filter.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/number_filter.rs deleted file mode 100644 index 8d8c32cc4d..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/number_filter.rs +++ /dev/null @@ -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 for u32 { - fn from(value: NumberFilterConditionPB) -> Self { - value as u32 - } -} -impl std::convert::TryFrom for NumberFilterConditionPB { - type Error = ErrorCode; - - fn try_from(n: u8) -> Result { - match n { - 0 => Ok(NumberFilterConditionPB::Equal), - 1 => Ok(NumberFilterConditionPB::NotEqual), - 2 => Ok(NumberFilterConditionPB::GreaterThan), - 3 => Ok(NumberFilterConditionPB::LessThan), - 4 => Ok(NumberFilterConditionPB::GreaterThanOrEqualTo), - 5 => Ok(NumberFilterConditionPB::LessThanOrEqualTo), - 6 => Ok(NumberFilterConditionPB::NumberIsEmpty), - 7 => Ok(NumberFilterConditionPB::NumberIsNotEmpty), - _ => Err(ErrorCode::InvalidData), - } - } -} - -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(), - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/select_option_filter.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/select_option_filter.rs deleted file mode 100644 index 868ea95345..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/select_option_filter.rs +++ /dev/null @@ -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, -} - -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] -#[repr(u8)] -pub enum SelectOptionConditionPB { - OptionIs = 0, - OptionIsNot = 1, - OptionIsEmpty = 2, - OptionIsNotEmpty = 3, -} - -impl std::convert::From 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 for SelectOptionConditionPB { - type Error = ErrorCode; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(SelectOptionConditionPB::OptionIs), - 1 => Ok(SelectOptionConditionPB::OptionIsNot), - 2 => Ok(SelectOptionConditionPB::OptionIsEmpty), - 3 => Ok(SelectOptionConditionPB::OptionIsNotEmpty), - _ => Err(ErrorCode::InvalidData), - } - } -} -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(), - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/text_filter.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/text_filter.rs deleted file mode 100644 index 5b468bd0cc..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/text_filter.rs +++ /dev/null @@ -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 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 for TextFilterConditionPB { - type Error = ErrorCode; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(TextFilterConditionPB::Is), - 1 => Ok(TextFilterConditionPB::IsNot), - 2 => Ok(TextFilterConditionPB::Contains), - 3 => Ok(TextFilterConditionPB::DoesNotContain), - 4 => Ok(TextFilterConditionPB::StartsWith), - 5 => Ok(TextFilterConditionPB::EndsWith), - 6 => Ok(TextFilterConditionPB::TextIsEmpty), - 7 => Ok(TextFilterConditionPB::TextIsNotEmpty), - _ => Err(ErrorCode::InvalidData), - } - } -} - -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(), - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/entities/filter_entities/util.rs b/frontend/rust-lib/flowy-database/src/entities/filter_entities/util.rs deleted file mode 100644 index ec5a5afb77..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/filter_entities/util.rs +++ /dev/null @@ -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, -} - -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, -} - -impl std::convert::From>> for RepeatedFilterPB { - fn from(revs: Vec>) -> Self { - RepeatedFilterPB { - items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(), - } - } -} - -impl std::convert::From> for RepeatedFilterPB { - fn from(items: Vec) -> Self { - Self { items } - } -} - -#[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 for DeleteFilterPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id) - .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? - .0; - let field_id = NotEmptyStr::parse(self.field_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; - - 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, - - #[pb(index = 4)] - pub data: Vec, - - #[pb(index = 5)] - pub view_id: String, -} - -impl AlterFilterPayloadPB { - #[allow(dead_code)] - pub fn new>( - view_id: &str, - field_rev: &FieldRevision, - data: T, - ) -> Self { - let data = data.try_into().unwrap_or_else(|_| Bytes::new()); - Self { - view_id: view_id.to_owned(), - field_id: field_rev.id.clone(), - field_type: field_rev.ty.into(), - filter_id: None, - data: data.to_vec(), - } - } -} - -impl TryInto for AlterFilterPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id) - .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? - .0; - - let field_id = NotEmptyStr::parse(self.field_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; - 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, - pub field_type: FieldTypeRevision, - pub condition: u8, - pub content: String, -} diff --git a/frontend/rust-lib/flowy-database/src/entities/group_entities/configuration.rs b/frontend/rust-lib/flowy-database/src/entities/group_entities/configuration.rs deleted file mode 100644 index 0d8f724b8e..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/group_entities/configuration.rs +++ /dev/null @@ -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 - 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 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, -} diff --git a/frontend/rust-lib/flowy-database/src/entities/group_entities/group.rs b/frontend/rust-lib/flowy-database/src/entities/group_entities/group.rs deleted file mode 100644 index 06eb1dade2..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/group_entities/group.rs +++ /dev/null @@ -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, -} - -impl std::ops::Deref for RepeatedGroupPB { - type Target = Vec; - fn deref(&self) -> &Self::Target { - &self.items - } -} - -impl std::ops::DerefMut for RepeatedGroupPB { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.items - } -} - -#[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, - - #[pb(index = 5)] - pub is_default: bool, - - #[pb(index = 6)] - pub is_visible: bool, -} - -impl std::convert::From for GroupPB { - fn from(group: Group) -> Self { - Self { - field_id: group.field_id, - group_id: group.id, - desc: group.name, - rows: group.rows, - is_default: group.is_default, - is_visible: group.is_visible, - } - } -} - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct RepeatedGroupConfigurationPB { - #[pb(index = 1)] - pub items: Vec, -} - -impl std::convert::From> for RepeatedGroupConfigurationPB { - fn from(items: Vec) -> Self { - Self { items } - } -} - -impl std::convert::From>> for RepeatedGroupConfigurationPB { - fn from(revs: Vec>) -> Self { - RepeatedGroupConfigurationPB { - items: revs.iter().map(|rev| rev.as_ref().into()).collect(), - } - } -} - -#[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 for InsertGroupPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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 for DeleteGroupPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let field_id = NotEmptyStr::parse(self.field_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; - let group_id = NotEmptyStr::parse(self.group_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; - 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, -} diff --git a/frontend/rust-lib/flowy-database/src/entities/group_entities/group_changeset.rs b/frontend/rust-lib/flowy-database/src/entities/group_entities/group_changeset.rs deleted file mode 100644 index 1fe5385fb8..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/group_entities/group_changeset.rs +++ /dev/null @@ -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, - - #[pb(index = 3)] - pub inserted_rows: Vec, - - #[pb(index = 4)] - pub deleted_rows: Vec, - - #[pb(index = 5)] - pub updated_rows: Vec, -} - -impl std::fmt::Display for GroupRowsNotificationPB { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - for inserted_row in &self.inserted_rows { - f.write_fmt(format_args!( - "Insert: {} row at {:?}", - inserted_row.row.id, inserted_row.index - ))?; - } - - for deleted_row in &self.deleted_rows { - f.write_fmt(format_args!("Delete: {} row", deleted_row))?; - } - - Ok(()) - } -} - -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) -> Self { - Self { - group_id, - inserted_rows, - ..Default::default() - } - } - - pub fn delete(group_id: String, deleted_rows: Vec) -> Self { - Self { - group_id, - deleted_rows, - ..Default::default() - } - } - - pub fn update(group_id: String, updated_rows: Vec) -> Self { - Self { - group_id, - updated_rows, - ..Default::default() - } - } -} -#[derive(Debug, Default, ProtoBuf)] -pub struct MoveGroupPayloadPB { - #[pb(index = 1)] - pub view_id: String, - - #[pb(index = 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 for MoveGroupPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id) - .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? - .0; - let from_group_id = NotEmptyStr::parse(self.from_group_id) - .map_err(|_| ErrorCode::GroupIdIsEmpty)? - .0; - let to_group_id = NotEmptyStr::parse(self.to_group_id) - .map_err(|_| ErrorCode::GroupIdIsEmpty)? - .0; - Ok(MoveGroupParams { - view_id, - from_group_id, - to_group_id, - }) - } -} - -#[derive(Debug, Default, ProtoBuf)] -pub struct GroupChangesetPB { - #[pb(index = 1)] - pub view_id: String, - - #[pb(index = 2)] - pub inserted_groups: Vec, - - #[pb(index = 3)] - pub initial_groups: Vec, - - #[pb(index = 4)] - pub deleted_groups: Vec, - - #[pb(index = 5)] - pub update_groups: Vec, -} - -impl GroupChangesetPB { - pub fn is_empty(&self) -> bool { - self.initial_groups.is_empty() - && self.inserted_groups.is_empty() - && self.deleted_groups.is_empty() - && self.update_groups.is_empty() - } -} - -#[derive(Debug, Default, ProtoBuf)] -pub struct InsertedGroupPB { - #[pb(index = 1)] - pub group: GroupPB, - - #[pb(index = 2)] - pub index: i32, -} diff --git a/frontend/rust-lib/flowy-database/src/entities/group_entities/mod.rs b/frontend/rust-lib/flowy-database/src/entities/group_entities/mod.rs deleted file mode 100644 index 778eff4cc9..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/group_entities/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod configuration; -mod group; -mod group_changeset; - -pub use configuration::*; -pub use group::*; -pub use group_changeset::*; diff --git a/frontend/rust-lib/flowy-database/src/entities/mod.rs b/frontend/rust-lib/flowy-database/src/entities/mod.rs deleted file mode 100644 index e3d02d9ffc..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/mod.rs +++ /dev/null @@ -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::*; diff --git a/frontend/rust-lib/flowy-database/src/entities/parser.rs b/frontend/rust-lib/flowy-database/src/entities/parser.rs deleted file mode 100644 index edad3ee6b8..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/parser.rs +++ /dev/null @@ -1,17 +0,0 @@ -#[derive(Debug)] -pub struct NotEmptyStr(pub String); - -impl NotEmptyStr { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err("Input string is empty".to_owned()); - } - Ok(Self(s)) - } -} - -impl AsRef for NotEmptyStr { - fn as_ref(&self) -> &str { - &self.0 - } -} diff --git a/frontend/rust-lib/flowy-database/src/entities/row_entities.rs b/frontend/rust-lib/flowy-database/src/entities/row_entities.rs deleted file mode 100644 index 97dae4c727..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/row_entities.rs +++ /dev/null @@ -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> for RowPB { - fn from(rev: &Arc) -> Self { - Self { - block_id: rev.block_id.clone(), - id: rev.id.clone(), - height: rev.height, - } - } -} - -#[derive(Debug, Default, ProtoBuf)] -pub struct OptionalRowPB { - #[pb(index = 1, one_of)] - pub row: Option, -} - -#[derive(Debug, Default, ProtoBuf)] -pub struct RepeatedRowPB { - #[pb(index = 1)] - pub items: Vec, -} - -impl std::convert::From> for RepeatedRowPB { - fn from(items: Vec) -> 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, - - #[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 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, -} - -#[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 for RowIdPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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, - - #[pb(index = 3, one_of)] - pub group_id: Option, - - #[pb(index = 4, one_of)] - pub data: Option, -} - -#[derive(ProtoBuf, Default)] -pub struct RowDataPB { - #[pb(index = 1)] - pub cell_data_by_field_id: HashMap, -} - -#[derive(Default)] -pub struct CreateRowParams { - pub view_id: String, - pub start_row_id: Option, - pub group_id: Option, - pub cell_data_by_field_id: Option>, -} - -impl TryInto for CreateRowPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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), - }) - } -} diff --git a/frontend/rust-lib/flowy-database/src/entities/setting_entities.rs b/frontend/rust-lib/flowy-database/src/entities/setting_entities.rs deleted file mode 100644 index 78ffd0e82f..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/setting_entities.rs +++ /dev/null @@ -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 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 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, - - #[pb(index = 4, one_of)] - pub delete_filter: Option, - - #[pb(index = 5, one_of)] - pub insert_group: Option, - - #[pb(index = 6, one_of)] - pub delete_group: Option, - - #[pb(index = 7, one_of)] - pub alter_sort: Option, - - #[pb(index = 8, one_of)] - pub delete_sort: Option, -} - -impl TryInto for DatabaseSettingChangesetPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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, - pub delete_filter: Option, - pub insert_group: Option, - pub delete_group: Option, - pub alert_sort: Option, - pub delete_sort: Option, -} - -impl DatabaseSettingChangesetParams { - pub fn is_filter_changed(&self) -> bool { - self.insert_filter.is_some() || self.delete_filter.is_some() - } -} - -#[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 for UpdateLayoutSettingPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - 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, -} - -impl LayoutSettingPB { - pub fn new() -> Self { - Self::default() - } -} - -impl std::convert::From for LayoutSettingPB { - fn from(params: LayoutSettingParams) -> Self { - Self { - calendar: params.calendar.map(|calender| calender.into()), - } - } -} - -impl std::convert::From 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, -} diff --git a/frontend/rust-lib/flowy-database/src/entities/sort_entities.rs b/frontend/rust-lib/flowy-database/src/entities/sort_entities.rs deleted file mode 100644 index ff99b8a853..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/sort_entities.rs +++ /dev/null @@ -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, -} - -impl std::convert::From>> for RepeatedSortPB { - fn from(revs: Vec>) -> Self { - RepeatedSortPB { - items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(), - } - } -} - -impl std::convert::From> for RepeatedSortPB { - fn from(items: Vec) -> Self { - Self { items } - } -} - -#[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 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, - - #[pb(index = 5)] - pub condition: SortConditionPB, -} - -impl TryInto for AlterSortPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id) - .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? - .0; - - let field_id = NotEmptyStr::parse(self.field_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; - - 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, - 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 for DeleteSortPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id) - .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? - .0; - let field_id = NotEmptyStr::parse(self.field_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; - - 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, - - #[pb(index = 3)] - pub delete_sorts: Vec, - - #[pb(index = 4)] - pub update_sorts: Vec, -} - -impl SortChangesetNotificationPB { - pub fn new(view_id: String) -> Self { - Self { - view_id, - insert_sorts: vec![], - delete_sorts: vec![], - update_sorts: vec![], - } - } - - pub fn 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, -} - -#[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, -} diff --git a/frontend/rust-lib/flowy-database/src/entities/view_entities.rs b/frontend/rust-lib/flowy-database/src/entities/view_entities.rs deleted file mode 100644 index 0200f3a4a9..0000000000 --- a/frontend/rust-lib/flowy-database/src/entities/view_entities.rs +++ /dev/null @@ -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, - - #[pb(index = 6)] - pub invisible_rows: Vec, -} - -#[derive(Debug, Default, Clone, ProtoBuf)] -pub struct RowsChangesetPB { - #[pb(index = 1)] - pub view_id: String, - - #[pb(index = 2)] - pub inserted_rows: Vec, - - #[pb(index = 3)] - pub deleted_rows: Vec, - - #[pb(index = 4)] - pub updated_rows: Vec, -} - -impl RowsChangesetPB { - pub fn from_insert(view_id: String, inserted_rows: Vec) -> Self { - Self { - view_id, - inserted_rows, - ..Default::default() - } - } - - pub fn from_delete(view_id: String, deleted_rows: Vec) -> Self { - Self { - view_id, - deleted_rows, - ..Default::default() - } - } - - pub fn from_update(view_id: String, updated_rows: Vec) -> Self { - Self { - view_id, - updated_rows, - ..Default::default() - } - } - - pub fn from_move( - view_id: String, - deleted_rows: Vec, - inserted_rows: Vec, - ) -> Self { - Self { - view_id, - inserted_rows, - deleted_rows, - ..Default::default() - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/event_handler.rs b/frontend/rust-lib/flowy-database/src/event_handler.rs deleted file mode 100644 index d189c1f6fd..0000000000 --- a/frontend/rust-lib/flowy-database/src/event_handler.rs +++ /dev/null @@ -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, - manager: AFPluginState>, -) -> DataResult { - 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, - manager: AFPluginState>, -) -> DataResult { - 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, - manager: AFPluginState>, -) -> 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, - manager: AFPluginState>, -) -> DataResult { - 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, - manager: AFPluginState>, -) -> DataResult { - 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, - manager: AFPluginState>, -) -> 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, - manager: AFPluginState>, -) -> DataResult { - 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::>() - .into(); - data_result_ok(repeated_field) -} - -#[tracing::instrument(level = "trace", skip(data, manager), err)] -pub(crate) async fn update_field_handler( - data: AFPluginData, - manager: AFPluginState>, -) -> 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, - manager: AFPluginState>, -) -> 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, - manager: AFPluginState>, -) -> 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, - manager: AFPluginState>, -) -> 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, - manager: AFPluginState>, -) -> 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, - manager: AFPluginState>, -) -> DataResult { - 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, - manager: AFPluginState>, -) -> DataResult { - 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, - manager: AFPluginState>, -) -> Result<(), FlowyError> { - let params: MoveFieldParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.view_id).await?; - editor.move_field(params).await?; - Ok(()) -} - -/// The [FieldRevision] contains multiple data, each of them belongs to a specific FieldType. -async fn get_type_option_data( - field_rev: &FieldRevision, - field_type: &FieldType, -) -> FlowyResult> { - let s = field_rev - .get_type_option_str(field_type) - .map(|value| value.to_owned()) - .unwrap_or_else(|| { - default_type_option_builder_from_type(field_type) - .serializer() - .json_str() - }); - let field_type: FieldType = field_rev.ty.into(); - let builder = type_option_builder_from_json_str(&s, &field_type); - let type_option_data = builder.serializer().protobuf_bytes().to_vec(); - - Ok(type_option_data) -} - -// #[tracing::instrument(level = "debug", skip(data, manager), err)] -pub(crate) async fn get_row_handler( - data: AFPluginData, - manager: AFPluginState>, -) -> DataResult { - 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, - manager: AFPluginState>, -) -> 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, - manager: AFPluginState>, -) -> 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, - manager: AFPluginState>, -) -> Result<(), FlowyError> { - let params: MoveRowParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(¶ms.view_id).await?; - editor.move_row(params).await?; - Ok(()) -} - -#[tracing::instrument(level = "debug", skip(data, manager), err)] -pub(crate) async fn create_row_handler( - data: AFPluginData, - manager: AFPluginState>, -) -> DataResult { - 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, - manager: AFPluginState>, -) -> DataResult { - 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, - manager: AFPluginState>, -) -> 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, - manager: AFPluginState>, -) -> DataResult { - 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, - manager: AFPluginState>, -) -> 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, - manager: AFPluginState>, -) -> DataResult { - 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, - manager: AFPluginState>, -) -> 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, - manager: AFPluginState>, -) -> Result<(), FlowyError> { - let data = data.into_inner(); - let cell_path: CellIdParams = data.cell_path.try_into()?; - let cell_changeset = DateCellChangeset { - date: data.date, - time: data.time, - 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, - manager: AFPluginState>, -) -> DataResult { - 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, - manager: AFPluginState>, -) -> DataResult { - 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, - manager: AFPluginState>, -) -> FlowyResult<()> { - let params: MoveGroupParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(params.view_id.as_ref()).await?; - editor.move_group(params).await?; - Ok(()) -} - -#[tracing::instrument(level = "debug", skip(data, manager), err)] -pub(crate) async fn move_group_row_handler( - data: AFPluginData, - manager: AFPluginState>, -) -> FlowyResult<()> { - let params: MoveGroupRowParams = data.into_inner().try_into()?; - let editor = manager.get_database_editor(params.view_id.as_ref()).await?; - editor.move_group_row(params).await?; - Ok(()) -} - -#[tracing::instrument(level = "debug", skip(manager), err)] -pub(crate) async fn get_databases_handler( - manager: AFPluginState>, -) -> DataResult { - let items = manager - .get_databases() - .await? - .into_iter() - .map(|database_info| DatabaseDescriptionPB { - name: database_info.name, - database_id: database_info.database_id, - }) - .collect::>(); - data_result_ok(RepeatedDatabaseDescriptionPB { items }) -} - -#[tracing::instrument(level = "debug", skip(data, manager), err)] -pub(crate) async fn set_layout_setting_handler( - data: AFPluginData, - manager: AFPluginState>, -) -> 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, - manager: AFPluginState>, -) -> DataResult { - 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, - manager: AFPluginState>, -) -> DataResult { - 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, - manager: AFPluginState>, -) -> DataResult { - 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), - } -} diff --git a/frontend/rust-lib/flowy-database/src/event_map.rs b/frontend/rust-lib/flowy-database/src/event_map.rs deleted file mode 100644 index 401bcc3b19..0000000000 --- a/frontend/rust-lib/flowy-database/src/event_map.rs +++ /dev/null @@ -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) -> 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, -} diff --git a/frontend/rust-lib/flowy-database/src/lib.rs b/frontend/rust-lib/flowy-database/src/lib.rs deleted file mode 100644 index 977330d5cd..0000000000 --- a/frontend/rust-lib/flowy-database/src/lib.rs +++ /dev/null @@ -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; diff --git a/frontend/rust-lib/flowy-database/src/macros.rs b/frontend/rust-lib/flowy-database/src/macros.rs deleted file mode 100644 index 18afe49544..0000000000 --- a/frontend/rust-lib/flowy-database/src/macros.rs +++ /dev/null @@ -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> for $target { - fn from(field_rev: &std::sync::Arc) -> $target { - match field_rev.get_type_option::<$target>($field_type.into()) { - None => $target::default(), - Some(target) => target, - } - } - } - - impl std::convert::From<$target> for String { - fn from(type_option: $target) -> String { - type_option.json_str() - } - } - - impl 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()) - } - } - }; -} diff --git a/frontend/rust-lib/flowy-database/src/manager.rs b/frontend/rust-lib/flowy-database/src/manager.rs deleted file mode 100644 index f4f12a6081..0000000000 --- a/frontend/rust-lib/flowy-database/src/manager.rs +++ /dev/null @@ -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; - fn token(&self) -> Result; - fn db_pool(&self) -> Result, FlowyError>; -} - -pub struct DatabaseManager { - editors_by_database_id: RwLock>>, - database_user: Arc, - block_indexer: Arc, - database_refs: Arc, - #[allow(dead_code)] - kv_persistence: Arc, - task_scheduler: Arc>, - #[allow(dead_code)] - migration: DatabaseMigration, -} - -impl DatabaseManager { - pub fn new( - database_user: Arc, - _rev_web_socket: Arc, - task_scheduler: Arc>, - database_db: Arc, - ) -> 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>, - ) -> FlowyResult<()> { - self.migration.run(user_id, get_views_fn).await?; - Ok(()) - } - - #[tracing::instrument(level = "debug", skip_all, err)] - pub async fn create_database>( - &self, - database_id: &str, - view_id: T, - name: &str, - revisions: Vec, - ) -> 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>( - &self, - view_id: T, - revisions: Vec, - ) -> 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>( - &self, - block_id: T, - revisions: Vec, - ) -> FlowyResult<()> { - let block_id = block_id.as_ref(); - let rev_manager = make_database_block_rev_manager(&self.database_user, block_id)?; - rev_manager.reset_object(revisions).await?; - Ok(()) - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn open_database_view>( - &self, - view_id: T, - ) -> FlowyResult> { - 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>(&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> { - 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> { - self.database_refs.get_all_databases() - } - - pub async fn get_database_ref_views( - &self, - database_id: &str, - ) -> FlowyResult> { - 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> { - let user = self.database_user.clone(); - let create_view_editor = |database_editor: Arc| 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, - ) -> Result, 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::(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, - ) -> FlowyResult>> { - // 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, -) -> 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, - 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> { - self.get_ref_views_with_database(database_id) - } -} - -impl DatabaseRefIndexerQuery for Arc { - fn get_ref_views(&self, database_id: &str) -> FlowyResult> { - (**self).get_ref_views(database_id) - } -} diff --git a/frontend/rust-lib/flowy-database/src/notification.rs b/frontend/rust-lib/flowy-database/src/notification.rs deleted file mode 100644 index 0a693d4ebe..0000000000 --- a/frontend/rust-lib/flowy-database/src/notification.rs +++ /dev/null @@ -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 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) -} diff --git a/frontend/rust-lib/flowy-database/src/services/cell/cell_data_cache.rs b/frontend/rust-lib/flowy-database/src/services/cell/cell_data_cache.rs deleted file mode 100644 index 7fdf5755bd..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/cell/cell_data_cache.rs +++ /dev/null @@ -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>>; -pub type AtomicCellFilterCache = Arc>>; - -#[derive(Default, Debug)] -pub struct AnyTypeCache(HashMap); - -impl AnyTypeCache -where - TypeValueKey: Clone + Hash + Eq, -{ - pub fn new() -> Arc>> { - Arc::new(RwLock::new(AnyTypeCache(HashMap::default()))) - } - - pub fn insert(&mut self, key: &TypeValueKey, val: T) -> Option - where - T: 'static + Send + Sync, - { - self - .0 - .insert(key.clone(), TypeValue::new(val)) - .and_then(downcast_owned) - } - - pub fn remove(&mut self, key: &TypeValueKey) { - self.0.remove(key); - } - - // pub fn remove>(&mut self, key: K) -> Option - // where - // T: 'static + Send + Sync, - // { - // self.0.remove(key.as_ref()).and_then(downcast_owned) - // } - - pub fn get(&self, key: &TypeValueKey) -> Option<&T> - where - T: 'static + Send + Sync, - { - self - .0 - .get(key) - .and_then(|type_value| type_value.boxed.downcast_ref()) - } - - pub fn get_mut(&mut self, key: &TypeValueKey) -> Option<&mut T> - where - T: 'static + Send + Sync, - { - self - .0 - .get_mut(key) - .and_then(|type_value| type_value.boxed.downcast_mut()) - } - - pub fn contains(&self, key: &TypeValueKey) -> bool { - self.0.contains_key(key) - } - - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -fn downcast_owned(type_value: TypeValue) -> Option { - type_value.boxed.downcast().ok().map(|boxed| *boxed) -} - -#[derive(Debug)] -struct TypeValue { - boxed: Box, - #[allow(dead_code)] - ty: &'static str, -} - -impl TypeValue { - pub fn new(value: T) -> Self - where - T: Send + Sync + 'static, - { - Self { - boxed: Box::new(value), - ty: type_name::(), - } - } -} - -impl std::ops::Deref for TypeValue { - type Target = Box; - - 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()); -// } -// } diff --git a/frontend/rust-lib/flowy-database/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-database/src/services/cell/cell_operation.rs deleted file mode 100644 index 054bd70820..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/cell/cell_operation.rs +++ /dev/null @@ -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<::CellData>; - - /// Same as `decode_cell_data` does but Decode the cell data to readable `String` - /// For example, The string of the Multi-Select cell will be a list of the option's name - /// separated by a comma. - fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String; -} - -pub trait CellDataChangeset: TypeOption { - /// The changeset is able to parse into the concrete data struct if `TypeOption::CellChangeset` - /// implements the `FromCellChangesetString` trait. - /// For example,the SelectOptionCellChangeset,DateCellChangeset. etc. - /// - fn apply_changeset( - &self, - changeset: ::CellChangeset, - type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)>; -} - -/// 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>( - changeset: C, - cell_rev: Option, - field_rev: T, - cell_data_cache: Option, -) -> Result { - let field_rev = field_rev.as_ref(); - let changeset = changeset.to_cell_changeset_str(); - let field_type: FieldType = field_rev.ty.into(); - - let 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 + Debug>( - data: T, - field_rev: &FieldRevision, - cell_data_cache: Option, -) -> (FieldType, CellProtobufBlob) { - let to_field_type = field_rev.ty.into(); - match data.try_into() { - Ok(type_cell_data) => { - let TypeCellData { - cell_str, - field_type, - } = type_cell_data; - match try_decode_cell_str_to_cell_protobuf( - cell_str, - &field_type, - &to_field_type, - field_rev, - cell_data_cache, - ) { - Ok(cell_bytes) => (field_type, cell_bytes), - Err(e) => { - tracing::error!("Decode cell data failed, {:?}", e); - (field_type, CellProtobufBlob::default()) - }, - } - }, - Err(_err) => { - // It's okay to ignore this error, because it's okay that the current cell can't - // display the existing cell data. For example, the UI of the text cell will be blank if - // the type of the data of cell is Number. - (to_field_type, CellProtobufBlob::default()) - }, - } -} - -pub fn get_type_cell_data( - data: CellData, - field_rev: &FieldRevision, - cell_data_cache: Option, -) -> Option -where - CellData: TryInto + Debug, - Output: Default + 'static, -{ - let to_field_type = field_rev.ty.into(); - match data.try_into() { - Ok(type_cell_data) => { - let TypeCellData { - cell_str, - field_type, - } = type_cell_data; - try_decode_cell_str_to_cell_data( - cell_str, - &field_type, - &to_field_type, - field_rev, - cell_data_cache, - ) - }, - Err(_err) => None, - } -} - -/// 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, -) -> FlowyResult { - match TypeOptionCellExt::new_with_cell_data_cache(field_rev, cell_data_cache) - .get_type_option_cell_data_handler(to_field_type) - { - None => Ok(CellProtobufBlob::default()), - Some(handler) => handler.handle_cell_str(cell_str, from_field_type, field_rev), - } -} - -pub fn try_decode_cell_str_to_cell_data( - cell_str: String, - from_field_type: &FieldType, - to_field_type: &FieldType, - field_rev: &FieldRevision, - cell_data_cache: Option, -) -> Option { - let handler = TypeOptionCellExt::new_with_cell_data_cache(field_rev, cell_data_cache) - .get_type_option_cell_data_handler(to_field_type)?; - handler - .get_cell_data(cell_str, from_field_type, field_rev) - .ok()? - .unbox_or_none::() -} -/// 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, - field_rev: &FieldRevision, -) -> CellRevision { - let changeset = - SelectOptionCellChangeset::from_insert_options(option_ids).to_cell_changeset_str(); - let data = apply_cell_data_changeset(changeset, None, field_rev, None).unwrap(); - CellRevision::new(data) -} - -pub fn delete_select_option_cell( - option_ids: Vec, - field_rev: &FieldRevision, -) -> CellRevision { - let changeset = - SelectOptionCellChangeset::from_delete_options(option_ids).to_cell_changeset_str(); - let data = apply_cell_data_changeset(changeset, None, field_rev, None).unwrap(); - CellRevision::new(data) -} - -/// Deserialize the String into cell specific data type. -pub trait FromCellString { - fn from_cell_str(s: &str) -> FlowyResult - where - Self: Sized; -} - -/// If the changeset applying to the cell is not String type, it should impl this trait. -/// Deserialize the string into cell specific changeset. -pub trait FromCellChangesetString { - fn from_changeset(changeset: String) -> FlowyResult - where - Self: Sized; -} - -impl FromCellChangesetString for String { - fn from_changeset(changeset: String) -> FlowyResult - 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(pub Option); - -impl AnyCellChangeset { - pub fn try_into_inner(self) -> FlowyResult { - match self.0 { - None => Err(ErrorCode::InvalidData.into()), - Some(data) => Ok(data), - } - } -} - -impl std::convert::From for AnyCellChangeset -where - T: FromCellChangesetString, -{ - fn from(changeset: C) -> Self { - match T::from_changeset(changeset.to_string()) { - Ok(data) => AnyCellChangeset(Some(data)), - Err(e) => { - tracing::error!("Deserialize CellDataChangeset failed: {}", e); - AnyCellChangeset(None) - }, - } - } -} -// impl std::convert::From for AnyCellChangeset { -// fn from(s: String) -> Self { -// AnyCellChangeset(Some(s)) -// } -// } diff --git a/frontend/rust-lib/flowy-database/src/services/cell/mod.rs b/frontend/rust-lib/flowy-database/src/services/cell/mod.rs deleted file mode 100644 index fecc08a024..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/cell/mod.rs +++ /dev/null @@ -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::*; diff --git a/frontend/rust-lib/flowy-database/src/services/cell/type_cell_data.rs b/frontend/rust-lib/flowy-database/src/services/cell/type_cell_data.rs deleted file mode 100644 index 3b002dacce..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/cell/type_cell_data.rs +++ /dev/null @@ -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 -/// -/// 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 { - 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 for TypeCellData { - type Error = FlowyError; - - fn try_from(value: String) -> Result { - 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::from_json_str(&value.type_cell_data) - } -} - -impl std::convert::TryFrom for TypeCellData { - type Error = FlowyError; - - fn try_from(value: CellRevision) -> Result { - Self::try_from(&value) - } -} - -impl TypeCellData { - pub fn new(cell_str: String, field_type: FieldType) -> Self { - TypeCellData { - cell_str, - field_type, - } - } - - pub fn 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; -} - -pub trait CellStringParser { - type Object; - fn parser_cell_str(&self, s: &str) -> Option; -} - -pub trait CellBytesCustomParser { - type Object; - fn parse(&self, bytes: &Bytes) -> FlowyResult; -} - -impl CellProtobufBlob { - pub fn new>(data: T) -> Self { - let bytes = Bytes::from(data.as_ref().to_vec()); - Self(bytes) - } - - pub fn from>(bytes: T) -> FlowyResult - where - >::Error: std::fmt::Debug, - { - let bytes = bytes.try_into().map_err(internal_error)?; - Ok(Self(bytes)) - } - - pub fn parser

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

(&self, parser: P) -> FlowyResult - where - P: CellBytesCustomParser, - { - parser.parse(&self.0) - } - - // pub fn parse<'a, T: TryFrom<&'a [u8]>>(&'a self) -> FlowyResult - // where - // >::Error: std::fmt::Debug, - // { - // T::try_from(self.0.as_ref()).map_err(internal_error) - // } -} - -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 - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/database/block_editor.rs b/frontend/rust-lib/flowy-database/src/services/database/block_editor.rs deleted file mode 100644 index 2df275a135..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/database/block_editor.rs +++ /dev/null @@ -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>, - rev_manager: Arc>>, -} - -impl DatabaseBlockEditor { - pub async fn new( - token: &str, - block_id: &str, - mut rev_manager: RevisionManager>, - ) -> FlowyResult { - let cloud = Arc::new(DatabaseBlockRevisionCloudService { - token: token.to_owned(), - }); - let block_revision_pad = rev_manager - .initialize::(Some(cloud)) - .await?; - let pad = Arc::new(RwLock::new(block_revision_pad)); - let rev_manager = Arc::new(rev_manager); - let 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, - ) -> FlowyResult<(i32, Option)> { - let mut row_count = 0; - let mut row_index = None; - self - .modify(|block_pad| { - if let Some(start_row_id) = prev_row_id.as_ref() { - match block_pad.index_of_row(start_row_id) { - None => {}, - Some(index) => row_index = Some(index as i32 + 1), - } - } - - let change = block_pad.add_row_rev(row, prev_row_id)?; - row_count = block_pad.number_of_rows(); - - if row_index.is_none() { - row_index = Some(row_count - 1); - } - Ok(change) - }) - .await?; - - Ok((row_count, row_index)) - } - - pub async fn delete_rows(&self, ids: Vec>) -> FlowyResult { - let mut row_count = 0; - self - .modify(|block_pad| { - let changeset = block_pad.delete_rows(ids)?; - row_count = block_pad.number_of_rows(); - Ok(changeset) - }) - .await?; - Ok(row_count) - } - - pub async fn update_row(&self, changeset: RowChangeset) -> FlowyResult<()> { - self - .modify(|block_pad| Ok(block_pad.update_row(changeset)?)) - .await?; - Ok(()) - } - - pub async fn move_row(&self, row_id: &str, from: usize, to: usize) -> FlowyResult<()> { - self - .modify(|block_pad| Ok(block_pad.move_row(row_id, from, to)?)) - .await?; - Ok(()) - } - - pub async fn index_of_row(&self, row_id: &str) -> Option { - self.pad.read().await.index_of_row(row_id) - } - - pub async fn number_of_rows(&self) -> i32 { - self.pad.read().await.rows.len() as i32 - } - - pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult)>> { - if let Ok(pad) = self.pad.try_read() { - Ok(pad.get_row_rev(row_id)) - } else { - 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( - &self, - row_ids: Option>>, - ) -> FlowyResult>> - where - T: AsRef + ToOwned + ?Sized, - { - let row_revs = self.pad.read().await.get_row_revs(row_ids)?; - Ok(row_revs) - } - - pub async fn get_cell_revs( - &self, - field_id: &str, - row_ids: Option>>, - ) -> FlowyResult> { - let cell_revs = self.pad.read().await.get_cell_revs(field_id, row_ids)?; - Ok(cell_revs) - } - - async fn modify(&self, f: F) -> FlowyResult<()> - where - F: for<'a> FnOnce( - &'a mut DatabaseBlockRevisionPad, - ) -> FlowyResult>, - { - let mut write_guard = self.pad.write().await; - let changeset = f(&mut write_guard)?; - match changeset { - None => {}, - Some(changeset) => { - self.apply_change(changeset).await?; - }, - } - Ok(()) - } - - async fn apply_change(&self, change: 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, FlowyError> { - FutureResult::new(async move { Ok(vec![]) }) - } -} - -struct DatabaseBlockRevisionSerde(); -impl RevisionObjectDeserializer for DatabaseBlockRevisionSerde { - type Output = DatabaseBlockRevisionPad; - - fn deserialize_revisions( - _object_id: &str, - revisions: Vec, - ) -> FlowyResult { - let pad = DatabaseBlockRevisionPad::from_revisions(revisions)?; - Ok(pad) - } - - fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { - None - } -} - -impl RevisionObjectSerializer for DatabaseBlockRevisionSerde { - fn combine_revisions(revisions: Vec) -> FlowyResult { - let operations = make_operations_from_revisions::(revisions)?; - Ok(operations.json_bytes()) - } -} - -pub struct DatabaseBlockRevisionMergeable(); -impl RevisionMergeable for DatabaseBlockRevisionMergeable { - fn combine_revisions(&self, revisions: Vec) -> FlowyResult { - DatabaseBlockRevisionSerde::combine_revisions(revisions) - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/database/block_manager.rs b/frontend/rust-lib/flowy-database/src/services/database/block_manager.rs deleted file mode 100644 index d98979c5ef..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/database/block_manager.rs +++ /dev/null @@ -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, - persistence: Arc, - block_editors: DashMap>, - event_notifier: broadcast::Sender, -} - -impl DatabaseBlocks { - pub(crate) async fn new( - user: &Arc, - block_meta_revs: Vec>, - persistence: Arc, - event_notifier: broadcast::Sender, - ) -> FlowyResult { - let block_editors = make_block_editors(user, block_meta_revs).await?; - let user = user.clone(); - let manager = Self { - user, - block_editors, - persistence, - event_notifier, - }; - Ok(manager) - } - - pub async fn close(&self) { - for block_editor in self.block_editors.iter() { - block_editor.close().await; - } - } - - // #[tracing::instrument(level = "trace", skip(self))] - pub(crate) async fn get_or_create_block_editor( - &self, - block_id: &str, - ) -> FlowyResult> { - debug_assert!(!block_id.is_empty()); - match self.block_editors.get(block_id) { - None => { - tracing::error!( - "This is a fatal error, block with id:{} is not exist", - block_id - ); - let editor = Arc::new(make_database_block_editor(&self.user, block_id).await?); - self - .block_editors - .insert(block_id.to_owned(), editor.clone()); - Ok(editor) - }, - Some(editor) => Ok(editor.clone()), - } - } - - pub(crate) async fn get_editor_from_row_id( - &self, - row_id: &str, - ) -> FlowyResult> { - let block_id = self.persistence.get_block_id(row_id)?; - self.get_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, - ) -> FlowyResult { - 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>, - ) -> FlowyResult> { - 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::>(); - let row = UpdatedRowPB { - row: make_row_from_row_rev(row_rev), - field_ids: changed_field_ids, - }; - - let _ = self.event_notifier.send(DatabaseBlockEvent::UpdateRow { - block_id: editor.block_id.clone(), - row, - }); - }, - } - Ok(()) - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn delete_row(&self, row_id: &str) -> FlowyResult>> { - let row_id = row_id.to_owned(); - let block_id = self.persistence.get_block_id(&row_id)?; - let editor = self.get_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, - ) -> FlowyResult> { - 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::>>(); - 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, - 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 { - match self.get_editor_from_row_id(row_id).await { - Ok(editor) => editor.index_of_row(row_id).await, - Err(_) => None, - } - } - - pub async fn update_cell(&self, changeset: CellChangesetPB) -> FlowyResult<()> { - let row_changeset: RowChangeset = changeset.clone().into(); - self.update_row(row_changeset).await?; - self.notify_did_update_cell(changeset).await?; - Ok(()) - } - - pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult)>> { - let editor = self.get_editor_from_row_id(row_id).await?; - editor.get_row_rev(row_id).await - } - - #[allow(dead_code)] - pub async fn get_row_revs(&self) -> FlowyResult>> { - let mut row_revs = vec![]; - for iter in self.block_editors.iter() { - let editor = iter.value(); - row_revs.extend(editor.get_row_revs::<&str>(None).await?); - } - Ok(row_revs) - } - - pub(crate) async fn get_blocks( - &self, - block_ids: Option>, - ) -> FlowyResult> { - let mut blocks = vec![]; - match block_ids { - None => { - for iter in self.block_editors.iter() { - let editor = iter.value(); - 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, - block_meta_revs: Vec>, -) -> FlowyResult>> { - let editor_map = DashMap::new(); - for block_meta_rev in block_meta_revs { - let editor = make_database_block_editor(user, &block_meta_rev.block_id).await?; - editor_map.insert(block_meta_rev.block_id.clone(), Arc::new(editor)); - } - - Ok(editor_map) -} - -async fn make_database_block_editor( - user: &Arc, - block_id: &str, -) -> FlowyResult { - 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, - block_id: &str, -) -> FlowyResult>> { - // 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) -} diff --git a/frontend/rust-lib/flowy-database/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database/src/services/database/database_editor.rs deleted file mode 100644 index d8b2b55368..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/database/database_editor.rs +++ /dev/null @@ -1,1196 +0,0 @@ -use crate::entities::CellIdParams; -use crate::entities::*; -use crate::manager::DatabaseUser; -use crate::notification::{send_notification, DatabaseNotification}; -use crate::services::cell::{ - apply_cell_data_changeset, get_type_cell_protobuf, stringify_cell_data, AnyTypeCache, - AtomicCellDataCache, CellProtobufBlob, ToCellChangesetString, TypeCellData, -}; -use crate::services::database::DatabaseBlocks; -use crate::services::field::{ - default_type_option_builder_from_type, transform_type_option, type_option_builder_from_bytes, - FieldBuilder, RowSingleCellData, -}; - -use crate::services::database::DatabaseViewDataImpl; -use crate::services::database_view::{ - DatabaseViewChanged, DatabaseViewData, DatabaseViewEditor, DatabaseViews, -}; -use crate::services::filter::FilterType; -use crate::services::persistence::block_index::BlockRowIndexer; -use crate::services::persistence::database_ref::DatabaseViewRef; -use crate::services::row::{DatabaseBlockRow, DatabaseBlockRowRevision, RowRevisionBuilder}; -use bytes::Bytes; -use database_model::*; -use flowy_client_sync::client_database::{ - DatabaseRevisionChangeset, DatabaseRevisionPad, JsonDeserializer, -}; -use flowy_client_sync::errors::{SyncError, SyncResult}; -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 flowy_task::TaskDispatcher; -use lib_infra::future::{to_fut, FutureResult}; -use lib_ot::core::EmptyAttributes; -use revision_model::Revision; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::{broadcast, RwLock}; - -pub trait DatabaseRefIndexerQuery: Send + Sync + 'static { - fn get_ref_views(&self, database_id: &str) -> FlowyResult>; -} - -pub struct DatabaseEditor { - pub database_id: String, - database_pad: Arc>, - rev_manager: Arc>>, - database_views: Arc, - database_blocks: Arc, - pub database_view_data: Arc, - pub cell_data_cache: AtomicCellDataCache, - database_ref_query: Arc, -} - -impl Drop for DatabaseEditor { - fn drop(&mut self) { - tracing::trace!("Drop DatabaseRevisionEditor"); - } -} - -impl DatabaseEditor { - #[allow(clippy::too_many_arguments)] - pub async fn new( - database_id: &str, - user: Arc, - database_pad: Arc>, - rev_manager: RevisionManager>, - persistence: Arc, - database_ref_query: Arc, - task_scheduler: Arc>, - ) -> FlowyResult> { - let rev_manager = Arc::new(rev_manager); - let cell_data_cache = AnyTypeCache::::new(); - - // Block manager - let (block_event_tx, block_event_rx) = broadcast::channel(100); - let block_meta_revs = database_pad.read().await.get_block_meta_revs(); - let database_blocks = - Arc::new(DatabaseBlocks::new(&user, block_meta_revs, persistence, block_event_tx).await?); - - let database_view_data = Arc::new(DatabaseViewDataImpl { - pad: database_pad.clone(), - blocks: database_blocks.clone(), - task_scheduler, - cell_data_cache: cell_data_cache.clone(), - }); - - // View manager - let database_views = DatabaseViews::new( - user.clone(), - database_view_data.clone(), - cell_data_cache.clone(), - block_event_rx, - ) - .await?; - let database_views = Arc::new(database_views); - let editor = Arc::new(Self { - database_id: database_id.to_owned(), - database_pad, - rev_manager, - database_blocks, - database_views, - cell_data_cache, - database_ref_query, - database_view_data, - }); - - Ok(editor) - } - - pub async fn open_view_editor(&self, view_editor: DatabaseViewEditor) { - self.database_views.open(view_editor).await - } - - #[tracing::instrument(level = "debug", skip_all)] - pub async fn close_view_editor(&self, view_id: &str) { - self.database_views.close(view_id).await; - } - - pub async fn dispose(&self) { - self.rev_manager.generate_snapshot().await; - self.database_blocks.close().await; - self.rev_manager.close().await; - } - - pub async fn number_of_ref_views(&self) -> usize { - self.database_views.number_of_views().await - } - - pub async fn is_view_open(&self, view_id: &str) -> bool { - self.database_views.is_view_exist(view_id).await - } - /// Save the type-option data to disk and send a `DatabaseNotification::DidUpdateField` notification - /// to dart side. - /// - /// It will do nothing if the passed-in type_option_data is empty - /// # Arguments - /// - /// * `field_id`: the id of the field - /// * `type_option_data`: the updated type-option data. The `type-option` data might be empty - /// if there is no type-option config for that field. For example, the `RichTextTypeOptionPB`. - /// - pub async fn update_field_type_option( - &self, - view_id: &str, - field_id: &str, - type_option_data: Vec, - old_field_rev: Option>, - ) -> FlowyResult<()> { - let result = self.get_field_rev(field_id).await; - if result.is_none() { - tracing::warn!("Can't find the field with id: {}", field_id); - return Ok(()); - } - let field_rev = result.unwrap(); - self - .modify(|pad| { - let changeset = pad.modify_field(field_id, |field| { - let deserializer = TypeOptionJsonDeserializer(field_rev.ty.into()); - match deserializer.deserialize(type_option_data) { - Ok(json_str) => { - let field_type = field.ty; - field.insert_type_option_str(&field_type, json_str); - }, - Err(err) => { - tracing::error!("Deserialize data to type option json failed: {}", err); - }, - } - Ok(Some(())) - })?; - Ok(changeset) - }) - .await?; - - self - .database_views - .did_update_field_type_option(view_id, field_id, old_field_rev) - .await?; - self.notify_did_update_database_field(field_id).await?; - Ok(()) - } - - pub async fn next_field_rev(&self, field_type: &FieldType) -> FlowyResult { - let name = format!( - "Property {}", - self.database_pad.read().await.get_fields().len() + 1 - ); - let field_rev = FieldBuilder::from_field_type(field_type) - .name(&name) - .build(); - Ok(field_rev) - } - - pub async fn create_new_field_rev(&self, field_rev: FieldRevision) -> FlowyResult<()> { - let field_id = field_rev.id.clone(); - self - .modify(|pad| Ok(pad.create_field_rev(field_rev, None)?)) - .await?; - self.notify_did_insert_database_field(&field_id).await?; - - Ok(()) - } - - pub async fn create_new_field_rev_with_type_option( - &self, - field_type: &FieldType, - type_option_data: Option>, - ) -> FlowyResult { - let mut field_rev = self.next_field_rev(field_type).await?; - if let Some(type_option_data) = type_option_data { - let type_option_builder = type_option_builder_from_bytes(type_option_data, field_type); - field_rev.insert_type_option(type_option_builder.serializer()); - } - self - .modify(|pad| Ok(pad.create_field_rev(field_rev.clone(), None)?)) - .await?; - self.notify_did_insert_database_field(&field_rev.id).await?; - - Ok(field_rev) - } - - pub async fn contain_field(&self, field_id: &str) -> bool { - self.database_pad.read().await.contain_field(field_id) - } - - pub async fn update_field(&self, params: FieldChangesetParams) -> FlowyResult<()> { - let field_id = params.field_id.clone(); - self - .modify(|pad| { - let changeset = pad.modify_field(¶ms.field_id, |field| { - if let Some(name) = params.name { - field.name = name; - } - if let Some(desc) = params.desc { - field.desc = desc; - } - if let Some(field_type) = params.field_type { - field.ty = field_type; - } - if let Some(frozen) = params.frozen { - field.frozen = frozen; - } - if let Some(visibility) = params.visibility { - field.visibility = visibility; - } - if let Some(width) = params.width { - field.width = width; - } - Ok(Some(())) - })?; - Ok(changeset) - }) - .await?; - self.notify_did_update_database_field(&field_id).await?; - Ok(()) - } - - pub async fn modify_field_rev(&self, view_id: &str, field_id: &str, f: F) -> FlowyResult<()> - where - F: for<'a> FnOnce(&'a mut FieldRevision) -> FlowyResult>, - { - let mut is_changed = false; - let old_field_rev = self.get_field_rev(field_id).await; - self - .modify(|pad| { - let changeset = pad.modify_field(field_id, |field_rev| { - f(field_rev).map_err(|e| SyncError::internal().context(e)) - })?; - is_changed = changeset.is_some(); - Ok(changeset) - }) - .await?; - - if is_changed { - match self - .database_views - .did_update_field_type_option(view_id, field_id, old_field_rev) - .await - { - Ok(_) => {}, - Err(e) => tracing::error!("View manager update field failed: {:?}", e), - } - self.notify_did_update_database_field(field_id).await?; - } - Ok(()) - } - - pub async fn delete_field(&self, field_id: &str) -> FlowyResult<()> { - self - .modify(|pad| Ok(pad.delete_field_rev(field_id)?)) - .await?; - let field_order = FieldIdPB::from(field_id); - let notified_changeset = DatabaseFieldChangesetPB::delete(&self.database_id, vec![field_order]); - self.notify_did_update_database(notified_changeset).await?; - Ok(()) - } - - pub async fn group_by_field(&self, view_id: &str, field_id: &str) -> FlowyResult<()> { - self - .database_views - .group_by_field(view_id, field_id) - .await?; - Ok(()) - } - - /// Switch the field with id to a new field type. - /// - /// If the field type is not exist before, the default type-option data will be created. - /// Each field type has its corresponding data, aka, the type-option data. Check out [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid#fieldtype) - /// for more information - /// - /// # Arguments - /// - /// * `field_id`: the id of the field - /// * `new_field_type`: the new field type of the field - /// - pub async fn switch_to_field_type( - &self, - field_id: &str, - new_field_type: &FieldType, - ) -> FlowyResult<()> { - // - let make_default_type_option = || -> String { - return default_type_option_builder_from_type(new_field_type) - .serializer() - .json_str(); - }; - - let type_option_transform = |old_field_type: FieldTypeRevision, - old_type_option: Option, - new_type_option: String| { - let old_field_type: FieldType = old_field_type.into(); - transform_type_option( - &new_type_option, - new_field_type, - old_type_option, - old_field_type, - ) - }; - - self - .modify(|pad| { - Ok(pad.switch_to_field( - field_id, - new_field_type.clone(), - make_default_type_option, - type_option_transform, - )?) - }) - .await?; - - self.notify_did_update_database_field(field_id).await?; - - Ok(()) - } - - pub async fn duplicate_field(&self, field_id: &str) -> FlowyResult<()> { - let duplicated_field_id = gen_field_id(); - self - .modify(|pad| Ok(pad.duplicate_field_rev(field_id, &duplicated_field_id)?)) - .await?; - - self - .notify_did_insert_database_field(&duplicated_field_id) - .await?; - Ok(()) - } - - pub async fn get_field_rev(&self, field_id: &str) -> Option> { - let field_rev = self - .database_pad - .read() - .await - .get_field_rev(field_id)? - .1 - .clone(); - Some(field_rev) - } - - pub async fn get_field_revs( - &self, - field_ids: Option>, - ) -> FlowyResult>> { - if field_ids.is_none() { - let field_revs = self.database_pad.read().await.get_field_revs(None)?; - return Ok(field_revs); - } - - let field_ids = field_ids.unwrap_or_default(); - let expected_len = field_ids.len(); - let field_revs = self - .database_pad - .read() - .await - .get_field_revs(Some(field_ids))?; - if expected_len != 0 && field_revs.len() != expected_len { - tracing::error!( - "This is a bug. The len of the field_revs should equal to {}", - expected_len - ); - debug_assert!(field_revs.len() == expected_len); - } - Ok(field_revs) - } - - pub async fn create_block(&self, block_meta_rev: DatabaseBlockMetaRevision) -> FlowyResult<()> { - self - .modify(|pad| Ok(pad.create_block_meta_rev(block_meta_rev)?)) - .await?; - Ok(()) - } - - pub async fn update_block( - &self, - changeset: DatabaseBlockMetaRevisionChangeset, - ) -> FlowyResult<()> { - self - .modify(|pad| Ok(pad.update_block_rev(changeset)?)) - .await?; - Ok(()) - } - - pub async fn create_row(&self, params: CreateRowParams) -> FlowyResult { - let mut row_rev = self - .create_row_rev(params.cell_data_by_field_id.clone()) - .await?; - - self - .database_views - .will_create_row(&mut row_rev, ¶ms) - .await; - - let row_pb = self - .create_row_pb(row_rev, params.start_row_id.clone()) - .await?; - - self.database_views.did_create_row(&row_pb, ¶ms).await; - Ok(row_pb) - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> { - self.database_views.move_group(params).await?; - Ok(()) - } - - pub async fn insert_rows(&self, row_revs: Vec) -> FlowyResult> { - let block_id = self.block_id().await?; - let mut rows_by_block_id: HashMap> = HashMap::new(); - let mut row_orders = vec![]; - for row_rev in row_revs { - row_orders.push(RowPB::from(&row_rev)); - rows_by_block_id - .entry(block_id.clone()) - .or_insert_with(Vec::new) - .push(row_rev); - } - let changesets = self.database_blocks.insert_row(rows_by_block_id).await?; - for changeset in changesets { - self.update_block(changeset).await?; - } - Ok(row_orders) - } - - pub async fn update_row(&self, changeset: RowChangeset) -> FlowyResult<()> { - let row_id = changeset.row_id.clone(); - let old_row = self.get_row_rev(&row_id).await?; - self.database_blocks.update_row(changeset).await?; - self.database_views.did_update_row(old_row, &row_id).await; - Ok(()) - } - - /// Returns all the rows in this block. - pub async fn get_row_pbs(&self, view_id: &str, block_id: &str) -> FlowyResult> { - let rows = self.database_views.get_row_revs(view_id, block_id).await?; - let rows = rows - .into_iter() - .map(|row_rev| RowPB::from(&row_rev)) - .collect(); - Ok(rows) - } - - pub async fn get_all_row_revs(&self, view_id: &str) -> FlowyResult>> { - let mut all_rows = vec![]; - let blocks = self.database_blocks.get_blocks(None).await?; - for block in blocks { - let rows = self - .database_views - .get_row_revs(view_id, &block.block_id) - .await?; - all_rows.extend(rows); - } - Ok(all_rows) - } - - pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult>> { - match self.database_blocks.get_row_rev(row_id).await? { - None => Ok(None), - Some((_, row_rev)) => Ok(Some(row_rev)), - } - } - - pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> { - let row_rev = self.database_blocks.delete_row(row_id).await?; - tracing::trace!("Did delete row:{:?}", row_rev); - if let Some(row_rev) = row_rev { - self.database_views.did_delete_row(row_rev).await; - } - Ok(()) - } - - pub async fn subscribe_view_changed( - &self, - view_id: &str, - ) -> FlowyResult> { - self.database_views.subscribe_view_changed(view_id).await - } - - pub async fn duplicate_row(&self, view_id: &str, row_id: &str) -> FlowyResult<()> { - if let Some(row) = self.get_row_rev(row_id).await? { - let cell_data_by_field_id = row - .cells - .iter() - .map(|(field_id, cell)| { - ( - field_id.clone(), - TypeCellData::try_from(cell) - .map(|value| value.cell_str) - .unwrap_or_default(), - ) - }) - .collect::>(); - - tracing::trace!("cell_data_by_field_id :{:?}", cell_data_by_field_id); - let params = CreateRowParams { - view_id: view_id.to_string(), - start_row_id: Some(row.id.clone()), - group_id: None, - cell_data_by_field_id: Some(cell_data_by_field_id), - }; - - self.create_row(params).await?; - } - Ok(()) - } - - /// Returns the cell data that encoded in protobuf. - pub async fn get_cell(&self, params: &CellIdParams) -> Option { - let (field_type, cell_bytes) = self.get_type_cell_protobuf(params).await?; - Some(CellPB::new( - ¶ms.field_id, - ¶ms.row_id, - field_type, - cell_bytes.to_vec(), - )) - } - - /// Returns a string that represents the current field_type's cell data. - /// For example: - /// Multi-Select: list of the option's name separated by a comma. - /// Number: 123 => $123 if the currency set. - /// Date: 1653609600 => May 27,2022 - /// - pub async fn get_cell_display_str(&self, params: &CellIdParams) -> String { - let display_str = || async { - let field_rev = self.get_field_rev(¶ms.field_id).await?; - let field_type: FieldType = field_rev.ty.into(); - let cell_rev = self - .get_cell_rev(¶ms.row_id, ¶ms.field_id) - .await - .ok()??; - let type_cell_data: TypeCellData = cell_rev.try_into().ok()?; - Some(stringify_cell_data( - type_cell_data.cell_str, - &field_type, - &field_type, - &field_rev, - )) - }; - - display_str().await.unwrap_or_default() - } - - pub async fn get_cell_protobuf(&self, params: &CellIdParams) -> Option { - let (_, cell_data) = self.get_type_cell_protobuf(params).await?; - Some(cell_data) - } - - async fn get_type_cell_protobuf( - &self, - params: &CellIdParams, - ) -> Option<(FieldType, CellProtobufBlob)> { - let field_rev = self.get_field_rev(¶ms.field_id).await?; - let (_, row_rev) = self - .database_blocks - .get_row_rev(¶ms.row_id) - .await - .ok()??; - let cell_rev = row_rev.cells.get(¶ms.field_id)?.clone(); - Some(get_type_cell_protobuf( - cell_rev.type_cell_data, - &field_rev, - Some(self.cell_data_cache.clone()), - )) - } - - pub async fn get_cell_rev( - &self, - row_id: &str, - field_id: &str, - ) -> FlowyResult> { - match self.database_blocks.get_row_rev(row_id).await? { - None => Ok(None), - Some((_, row_rev)) => { - let cell_rev = row_rev.cells.get(field_id).cloned(); - Ok(cell_rev) - }, - } - } - - /// Returns the list of cells corresponding to the given field. - pub async fn get_cells_for_field( - &self, - view_id: &str, - field_id: &str, - ) -> FlowyResult> { - let view_editor = self.database_views.get_view_editor(view_id).await?; - view_editor.v_get_cells_for_field(field_id).await - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn update_cell_with_changeset( - &self, - row_id: &str, - field_id: &str, - cell_changeset: T, - ) -> FlowyResult<()> { - match self.database_pad.read().await.get_field_rev(field_id) { - None => { - let msg = format!("Field with id:{} not found", &field_id); - Err(FlowyError::internal().context(msg)) - }, - Some((_, field_rev)) => { - tracing::trace!( - "Cell changeset: id:{} / value:{:?}", - &field_id, - cell_changeset - ); - let old_row_rev = self.get_row_rev(row_id).await?.clone(); - let cell_rev = self.get_cell_rev(row_id, field_id).await?; - // Update the changeset.data property with the return value. - let type_cell_data = apply_cell_data_changeset( - cell_changeset, - cell_rev, - field_rev, - Some(self.cell_data_cache.clone()), - )?; - let cell_changeset = CellChangesetPB { - view_id: self.database_id.clone(), - row_id: row_id.to_owned(), - field_id: field_id.to_owned(), - type_cell_data, - }; - self.database_blocks.update_cell(cell_changeset).await?; - self - .database_views - .did_update_row(old_row_rev, row_id) - .await; - Ok(()) - }, - } - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn update_cell( - &self, - row_id: String, - field_id: String, - cell_changeset: T, - ) -> FlowyResult<()> { - self - .update_cell_with_changeset(&row_id, &field_id, cell_changeset) - .await - } - - pub async fn get_block_meta_revs(&self) -> FlowyResult>> { - let block_meta_revs = self.database_pad.read().await.get_block_meta_revs(); - Ok(block_meta_revs) - } - - pub async fn get_blocks( - &self, - block_ids: Option>, - ) -> FlowyResult> { - let block_ids = match block_ids { - None => self - .database_pad - .read() - .await - .get_block_meta_revs() - .iter() - .map(|block_rev| block_rev.block_id.clone()) - .collect::>(), - Some(block_ids) => block_ids, - }; - let blocks = self.database_blocks.get_blocks(Some(block_ids)).await?; - Ok(blocks) - } - - pub async fn delete_rows(&self, block_rows: Vec) -> FlowyResult<()> { - let changesets = self.database_blocks.delete_rows(block_rows).await?; - for changeset in changesets { - self.update_block(changeset).await?; - } - Ok(()) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn get_database(&self, view_id: &str) -> FlowyResult { - let pad = self.database_pad.read().await; - let fields = pad - .get_field_revs(None)? - .iter() - .map(FieldIdPB::from) - .collect(); - let mut all_rows = vec![]; - for block_rev in pad.get_block_meta_revs() { - if let Ok(rows) = self.get_row_pbs(view_id, &block_rev.block_id).await { - all_rows.extend(rows); - } - } - - Ok(DatabasePB { - id: self.database_id.clone(), - fields, - rows: all_rows, - }) - } - - pub async fn get_setting(&self, view_id: &str) -> FlowyResult { - self.database_views.get_setting(view_id).await - } - - pub async fn get_all_filters(&self, view_id: &str) -> FlowyResult> { - Ok( - self - .database_views - .get_all_filters(view_id) - .await? - .into_iter() - .map(|filter| FilterPB::from(filter.as_ref())) - .collect(), - ) - } - - pub async fn get_filters( - &self, - view_id: &str, - filter_id: FilterType, - ) -> FlowyResult>> { - self.database_views.get_filters(view_id, &filter_id).await - } - - pub async fn create_or_update_filter(&self, params: AlterFilterParams) -> FlowyResult<()> { - self.database_views.create_or_update_filter(params).await?; - Ok(()) - } - - pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { - self.database_views.delete_filter(params).await?; - Ok(()) - } - - pub async fn get_all_sorts(&self, view_id: &str) -> FlowyResult> { - Ok( - self - .database_views - .get_all_sorts(view_id) - .await? - .into_iter() - .map(|sort| SortPB::from(sort.as_ref())) - .collect(), - ) - } - - pub async fn delete_all_sorts(&self, view_id: &str) -> FlowyResult<()> { - self.database_views.delete_all_sorts(view_id).await - } - - pub async fn delete_sort(&self, params: DeleteSortParams) -> FlowyResult<()> { - self.database_views.delete_sort(params).await?; - Ok(()) - } - - pub async fn create_or_update_sort(&self, params: AlterSortParams) -> FlowyResult { - let sort_rev = self.database_views.create_or_update_sort(params).await?; - Ok(sort_rev) - } - - pub async fn insert_group(&self, params: InsertGroupParams) -> FlowyResult<()> { - self.database_views.insert_or_update_group(params).await - } - - pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { - self.database_views.delete_group(params).await - } - - pub async fn move_row(&self, params: MoveRowParams) -> FlowyResult<()> { - let MoveRowParams { - view_id: _, - from_row_id, - to_row_id, - } = params; - - match self.database_blocks.get_row_rev(&from_row_id).await? { - None => tracing::warn!("Move row failed, can not find the row:{}", from_row_id), - Some((_, row_rev)) => { - match ( - self.database_blocks.index_of_row(&from_row_id).await, - self.database_blocks.index_of_row(&to_row_id).await, - ) { - (Some(from_index), Some(to_index)) => { - tracing::trace!("Move row from {} to {}", from_index, to_index); - self - .database_blocks - .move_row(row_rev.clone(), from_index, to_index) - .await?; - }, - (_, None) => tracing::warn!("Can not find the from row id: {}", from_row_id), - (None, _) => tracing::warn!("Can not find the to row id: {}", to_row_id), - } - }, - } - Ok(()) - } - - pub async fn move_group_row(&self, params: MoveGroupRowParams) -> FlowyResult<()> { - let MoveGroupRowParams { - view_id, - from_row_id, - to_group_id, - to_row_id, - } = params; - - match self.database_blocks.get_row_rev(&from_row_id).await? { - None => tracing::warn!("Move row failed, can not find the row:{}", from_row_id), - Some((_, row_rev)) => { - let block_manager = self.database_blocks.clone(); - self - .database_views - .move_group_row( - &view_id.clone(), - row_rev, - to_group_id, - to_row_id.clone(), - |row_changeset| { - to_fut(async move { - tracing::trace!("Row data changed: {:?}", row_changeset); - let cell_changesets = row_changeset - .cell_by_field_id - .into_iter() - .map(|(field_id, cell_rev)| CellChangesetPB { - view_id: view_id.clone(), - row_id: row_changeset.row_id.clone(), - field_id, - type_cell_data: cell_rev.type_cell_data, - }) - .collect::>(); - - for cell_changeset in cell_changesets { - match block_manager.update_cell(cell_changeset).await { - Ok(_) => {}, - Err(e) => tracing::error!("Apply cell changeset error:{:?}", e), - } - } - }) - }, - ) - .await?; - }, - } - Ok(()) - } - - pub async fn move_field(&self, params: MoveFieldParams) -> FlowyResult<()> { - let MoveFieldParams { - view_id: _, - field_id, - from_index, - to_index, - } = params; - - self - .modify(|pad| Ok(pad.move_field(&field_id, from_index as usize, to_index as usize)?)) - .await?; - if let Some((index, field_rev)) = self.database_pad.read().await.get_field_rev(&field_id) { - let delete_field_order = FieldIdPB::from(field_id); - let insert_field = IndexFieldPB::from_field_rev(field_rev, index); - let notified_changeset = DatabaseFieldChangesetPB { - view_id: self.database_id.clone(), - inserted_fields: vec![insert_field], - deleted_fields: vec![delete_field_order], - updated_fields: vec![], - }; - - self.notify_did_update_database(notified_changeset).await?; - } - Ok(()) - } - - pub async fn duplicate_database(&self, view_id: &str) -> FlowyResult { - let database_pad = self.database_pad.read().await; - let database_view_data = self.database_views.duplicate_database_view(view_id).await?; - let original_blocks = database_pad.get_block_meta_revs(); - let (duplicated_fields, duplicated_blocks) = database_pad.duplicate_database_block_meta().await; - - let mut blocks_meta_data = vec![]; - if original_blocks.len() == duplicated_blocks.len() { - for (index, original_block_meta) in original_blocks.iter().enumerate() { - let database_block_meta_editor = self - .database_blocks - .get_or_create_block_editor(&original_block_meta.block_id) - .await?; - let duplicated_block_id = &duplicated_blocks[index].block_id; - - tracing::trace!("Duplicate block:{} meta data", duplicated_block_id); - let duplicated_block_meta_data = database_block_meta_editor - .duplicate_block(duplicated_block_id) - .await; - blocks_meta_data.push(duplicated_block_meta_data); - } - } else { - debug_assert_eq!(original_blocks.len(), duplicated_blocks.len()); - } - drop(database_pad); - - Ok(BuildDatabaseContext { - field_revs: duplicated_fields.into_iter().map(Arc::new).collect(), - block_metas: duplicated_blocks, - blocks: blocks_meta_data, - layout_setting: Default::default(), - database_view_data, - }) - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn load_groups(&self, view_id: &str) -> FlowyResult { - self.database_views.load_groups(view_id).await - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn get_group(&self, view_id: &str, group_id: &str) -> FlowyResult { - self.database_views.get_group(view_id, group_id).await - } - - pub async fn get_layout_setting>( - &self, - view_id: &str, - layout_ty: T, - ) -> FlowyResult { - let layout_ty = layout_ty.into(); - self - .database_views - .get_layout_setting(view_id, &layout_ty) - .await - } - - pub async fn set_layout_setting( - &self, - view_id: &str, - layout_setting: LayoutSettingParams, - ) -> FlowyResult<()> { - self - .database_views - .set_layout_setting(view_id, layout_setting) - .await - } - - pub async fn get_all_calendar_events(&self, view_id: &str) -> Vec { - match self.database_views.get_view_editor(view_id).await { - Ok(view_editor) => view_editor - .v_get_all_calendar_events() - .await - .unwrap_or_default(), - Err(err) => { - tracing::error!("Get calendar event failed: {}", err); - vec![] - }, - } - } - - #[tracing::instrument(level = "trace", skip(self))] - pub async fn get_calendar_event(&self, view_id: &str, row_id: &str) -> Option { - let view_editor = self.database_views.get_view_editor(view_id).await.ok()?; - view_editor.v_get_calendar_event(row_id).await - } - - async fn create_row_rev( - &self, - cell_data_by_field_id: Option>, - ) -> FlowyResult { - let field_revs = self.database_pad.read().await.get_field_revs(None)?; - let block_id = self.block_id().await?; - - // insert empty row below the row whose id is upper_row_id - let builder = match cell_data_by_field_id { - None => RowRevisionBuilder::new(&block_id, field_revs), - Some(cell_data_by_field_id) => { - RowRevisionBuilder::new_with_data(&block_id, field_revs, cell_data_by_field_id) - }, - }; - - let row_rev = builder.build(); - Ok(row_rev) - } - - async fn create_row_pb( - &self, - row_rev: RowRevision, - start_row_id: Option, - ) -> FlowyResult { - let row_pb = RowPB::from(&row_rev); - let block_id = row_rev.block_id.clone(); - - // insert the row - let row_count = self - .database_blocks - .create_row(row_rev, start_row_id) - .await?; - - // update block row count - let changeset = DatabaseBlockMetaRevisionChangeset::from_row_count(block_id, row_count); - self.update_block(changeset).await?; - Ok(row_pb) - } - - async fn modify(&self, f: F) -> FlowyResult<()> - where - F: - for<'a> FnOnce(&'a mut DatabaseRevisionPad) -> FlowyResult>, - { - let mut write_guard = self.database_pad.write().await; - if let Some(changeset) = f(&mut write_guard)? { - self.apply_change(changeset).await?; - } - Ok(()) - } - - async fn apply_change(&self, change: DatabaseRevisionChangeset) -> FlowyResult<()> { - let DatabaseRevisionChangeset { - operations: delta, - md5, - } = change; - let data = delta.json_bytes(); - let _ = self.rev_manager.add_local_revision(data, md5).await?; - Ok(()) - } - - async fn block_id(&self) -> FlowyResult { - match self.database_pad.read().await.get_block_meta_revs().last() { - None => Err(FlowyError::internal().context("There is no block in this database")), - Some(database_block) => Ok(database_block.block_id.clone()), - } - } - - #[tracing::instrument(level = "trace", skip_all, err)] - async fn notify_did_insert_database_field(&self, field_id: &str) -> FlowyResult<()> { - if let Some((index, field_rev)) = self.database_pad.read().await.get_field_rev(field_id) { - let index_field = IndexFieldPB::from_field_rev(field_rev, index); - if let Ok(views) = self.database_ref_query.get_ref_views(&self.database_id) { - for view in views { - let notified_changeset = - DatabaseFieldChangesetPB::insert(&view.view_id, vec![index_field.clone()]); - self.notify_did_update_database(notified_changeset).await?; - } - } - } - Ok(()) - } - - #[tracing::instrument(level = "trace", skip_all, err)] - async fn notify_did_update_database_field(&self, field_id: &str) -> FlowyResult<()> { - if let Some((_, field_rev)) = self - .database_pad - .read() - .await - .get_field_rev(field_id) - .map(|(index, field)| (index, field.clone())) - { - let updated_field = FieldPB::from(field_rev); - let notified_changeset = - DatabaseFieldChangesetPB::update(&self.database_id, vec![updated_field.clone()]); - self.notify_did_update_database(notified_changeset).await?; - - send_notification(field_id, DatabaseNotification::DidUpdateField) - .payload(updated_field) - .send(); - } - - Ok(()) - } - - async fn notify_did_update_database( - &self, - changeset: DatabaseFieldChangesetPB, - ) -> FlowyResult<()> { - if let Ok(views) = self.database_ref_query.get_ref_views(&self.database_id) { - for view in views { - send_notification(&view.view_id, DatabaseNotification::DidUpdateFields) - .payload(changeset.clone()) - .send(); - } - } - - Ok(()) - } -} - -#[cfg(feature = "flowy_unit_test")] -impl DatabaseEditor { - pub fn rev_manager(&self) -> Arc>> { - self.rev_manager.clone() - } - - pub fn database_pad(&self) -> Arc> { - self.database_pad.clone() - } -} - -pub struct DatabaseRevisionSerde(); -impl RevisionObjectDeserializer for DatabaseRevisionSerde { - type Output = DatabaseRevisionPad; - - fn deserialize_revisions( - _object_id: &str, - revisions: Vec, - ) -> FlowyResult { - let pad = DatabaseRevisionPad::from_revisions(revisions)?; - Ok(pad) - } - - fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { - None - } -} -impl RevisionObjectSerializer for DatabaseRevisionSerde { - fn combine_revisions(revisions: Vec) -> FlowyResult { - let operations = make_operations_from_revisions::(revisions)?; - Ok(operations.json_bytes()) - } -} - -pub struct DatabaseRevisionCloudService { - #[allow(dead_code)] - token: String, -} - -impl DatabaseRevisionCloudService { - pub fn new(token: String) -> Self { - Self { token } - } -} - -impl RevisionCloudService for DatabaseRevisionCloudService { - #[tracing::instrument(level = "trace", skip(self))] - fn fetch_object( - &self, - _user_id: &str, - _object_id: &str, - ) -> FutureResult, FlowyError> { - FutureResult::new(async move { Ok(vec![]) }) - } -} - -pub struct DatabaseRevisionMergeable(); - -impl RevisionMergeable for DatabaseRevisionMergeable { - fn combine_revisions(&self, revisions: Vec) -> FlowyResult { - DatabaseRevisionSerde::combine_revisions(revisions) - } -} - -struct TypeOptionJsonDeserializer(FieldType); -impl JsonDeserializer for TypeOptionJsonDeserializer { - fn deserialize(&self, type_option_data: Vec) -> SyncResult { - // The type_option_data sent from Dart is serialized by protobuf. - let builder = type_option_builder_from_bytes(type_option_data, &self.0); - let json = builder.serializer().json_str(); - tracing::trace!("Deserialize type-option data to: {}", json); - Ok(json) - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/database/mod.rs b/frontend/rust-lib/flowy-database/src/services/database/mod.rs deleted file mode 100644 index 00e2011aaf..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/database/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -mod block_editor; -mod block_manager; -mod database_editor; -mod retry; -mod trait_impl; - -pub use block_editor::*; -pub use block_manager::*; -pub use database_editor::*; -pub use trait_impl::*; diff --git a/frontend/rust-lib/flowy-database/src/services/database/retry.rs b/frontend/rust-lib/flowy-database/src/services/database/retry.rs deleted file mode 100644 index 521b23e188..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/database/retry.rs +++ /dev/null @@ -1,30 +0,0 @@ -use database_model::RowRevision; -use flowy_client_sync::client_database::DatabaseBlockRevisionPad; -use flowy_error::FlowyError; -use lib_infra::retry::Action; -use std::future::Future; -use std::pin::Pin; -use std::sync::Arc; -use tokio::sync::RwLock; - -pub struct GetRowDataRetryAction { - pub row_id: String, - pub pad: Arc>, -} - -impl Action for GetRowDataRetryAction { - type Future = Pin> + Send + Sync>>; - type Item = Option<(usize, Arc)>; - type Error = FlowyError; - - fn run(&mut self) -> Self::Future { - let pad = self.pad.clone(); - let row_id = self.row_id.clone(); - Box::pin(async move { - match pad.try_read() { - Err(_) => Ok(None), - Ok(read_guard) => Ok(read_guard.get_row_rev(&row_id)), - } - }) - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/database/trait_impl.rs b/frontend/rust-lib/flowy-database/src/services/database/trait_impl.rs deleted file mode 100644 index 53777b3046..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/database/trait_impl.rs +++ /dev/null @@ -1,106 +0,0 @@ -use crate::entities::FieldType; -use crate::services::cell::AtomicCellDataCache; -use crate::services::database::DatabaseBlocks; -use crate::services::database_view::DatabaseViewData; -use crate::services::field::{TypeOptionCellDataHandler, TypeOptionCellExt}; -use crate::services::row::DatabaseBlockRowRevision; - -use database_model::{FieldRevision, RowRevision}; -use flowy_client_sync::client_database::DatabaseRevisionPad; -use flowy_task::TaskDispatcher; -use lib_infra::future::{to_fut, Fut}; -use std::any::type_name; -use std::sync::Arc; -use tokio::sync::RwLock; - -pub struct DatabaseViewDataImpl { - pub(crate) pad: Arc>, - pub(crate) blocks: Arc, - pub(crate) task_scheduler: Arc>, - pub(crate) cell_data_cache: AtomicCellDataCache, -} - -impl DatabaseViewData for DatabaseViewDataImpl { - fn get_field_revs(&self, field_ids: Option>) -> Fut>> { - let pad = self.pad.clone(); - to_fut(async move { - match pad.read().await.get_field_revs(field_ids) { - Ok(field_revs) => field_revs, - Err(e) => { - tracing::error!( - "[{}] get field revisions failed: {}", - type_name::(), - e - ); - vec![] - }, - } - }) - } - fn get_field_rev(&self, field_id: &str) -> Fut>> { - let pad = self.pad.clone(); - let field_id = field_id.to_owned(); - to_fut(async move { Some(pad.read().await.get_field_rev(&field_id)?.1.clone()) }) - } - - fn get_primary_field_rev(&self) -> Fut>> { - let pad = self.pad.clone(); - to_fut(async move { - let field_revs = pad.read().await.get_field_revs(None).ok()?; - field_revs - .into_iter() - .find(|field_rev| field_rev.is_primary) - }) - } - - fn index_of_row(&self, row_id: &str) -> Fut> { - let block_manager = self.blocks.clone(); - let row_id = row_id.to_owned(); - to_fut(async move { block_manager.index_of_row(&row_id).await }) - } - - fn get_row_rev(&self, row_id: &str) -> Fut)>> { - let block_manager = self.blocks.clone(); - let row_id = row_id.to_owned(); - to_fut(async move { - match block_manager.get_row_rev(&row_id).await { - Ok(indexed_row) => indexed_row, - Err(_) => None, - } - }) - } - - fn get_row_revs(&self, block_id: Option>) -> Fut>> { - let block_manager = self.blocks.clone(); - - to_fut(async move { - let blocks = block_manager.get_blocks(block_id).await.unwrap(); - blocks - .into_iter() - .flat_map(|block| block.row_revs) - .collect::>>() - }) - } - - // /// Returns the list of cells corresponding to the given field. - // pub async fn get_cells_for_field(&self, field_id: &str) -> FlowyResult> { - // } - - fn get_blocks(&self) -> Fut> { - let block_manager = self.blocks.clone(); - to_fut(async move { block_manager.get_blocks(None).await.unwrap_or_default() }) - } - - fn get_task_scheduler(&self) -> Arc> { - self.task_scheduler.clone() - } - - fn get_type_option_cell_handler( - &self, - field_rev: &FieldRevision, - field_type: &FieldType, - ) -> Option> { - TypeOptionCellExt::new_with_cell_data_cache(field_rev, Some(self.cell_data_cache.clone())) - .get_type_option_cell_data_handler(field_type) - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/database_view/editor.rs b/frontend/rust-lib/flowy-database/src/services/database_view/editor.rs deleted file mode 100644 index 67f1444901..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/database_view/editor.rs +++ /dev/null @@ -1,1212 +0,0 @@ -use crate::entities::*; -use crate::notification::{send_notification, DatabaseNotification}; -use crate::services::cell::{AtomicCellDataCache, TypeCellData}; -use crate::services::database::DatabaseBlockEvent; -use crate::services::database_view::notifier::DatabaseViewChangedNotifier; -use crate::services::database_view::trait_impl::*; -use crate::services::database_view::DatabaseViewChangedReceiverRunner; -use crate::services::field::{RowSingleCellData, TypeOptionCellDataHandler}; -use crate::services::filter::{ - FilterChangeset, FilterController, FilterTaskHandler, FilterType, UpdatedFilterType, -}; -use crate::services::group::{ - default_group_configuration, find_grouping_field, make_group_controller, Group, - GroupConfigurationReader, GroupController, MoveGroupRowContext, -}; -use crate::services::row::DatabaseBlockRowRevision; -use crate::services::sort::{ - DeletedSortType, SortChangeset, SortController, SortTaskHandler, SortType, -}; -use database_model::{ - gen_database_filter_id, gen_database_id, gen_database_sort_id, CalendarLayoutSetting, - FieldRevision, FieldTypeRevision, FilterRevision, LayoutRevision, RowChangeset, RowRevision, - SortRevision, -}; -use flowy_client_sync::client_database::{ - make_database_view_operations, DatabaseViewRevisionChangeset, DatabaseViewRevisionPad, -}; -use flowy_error::{FlowyError, FlowyResult}; -use flowy_revision::RevisionManager; -use flowy_sqlite::ConnectionPool; -use flowy_task::TaskDispatcher; -use lib_infra::future::Fut; -use nanoid::nanoid; -use revision_model::Revision; -use std::borrow::Cow; -use std::collections::HashMap; -use std::future::Future; -use std::sync::Arc; -use tokio::sync::{broadcast, RwLock}; - -pub trait DatabaseViewData: Send + Sync + 'static { - /// If the field_ids is None, then it will return all the field revisions - fn get_field_revs(&self, field_ids: Option>) -> Fut>>; - - /// Returns the field with the field_id - fn get_field_rev(&self, field_id: &str) -> Fut>>; - - fn get_primary_field_rev(&self) -> Fut>>; - - /// Returns the index of the row with row_id - fn index_of_row(&self, row_id: &str) -> Fut>; - - /// Returns the `index` and `RowRevision` with row_id - fn get_row_rev(&self, row_id: &str) -> Fut)>>; - - /// Returns all the rows that the block has. If the passed-in block_ids is None, then will return all the rows - /// The relationship between the grid and the block is: - /// A grid has a list of blocks - /// A block has a list of rows - /// A row has a list of cells - /// - fn get_row_revs(&self, block_ids: Option>) -> Fut>>; - - /// Get all the blocks that the current Grid has. - /// One grid has a list of blocks - fn get_blocks(&self) -> Fut>; - - /// Returns a `TaskDispatcher` used to poll a `Task` - fn get_task_scheduler(&self) -> Arc>; - - fn get_type_option_cell_handler( - &self, - field_rev: &FieldRevision, - field_type: &FieldType, - ) -> Option>; -} - -pub struct DatabaseViewEditor { - pub view_id: String, - pad: Arc>, - rev_manager: Arc>>, - delegate: Arc, - group_controller: Arc>>, - filter_controller: Arc, - sort_controller: Arc>, - pub notifier: DatabaseViewChangedNotifier, -} - -impl Drop for DatabaseViewEditor { - fn drop(&mut self) { - tracing::trace!("Drop {}", std::any::type_name::()); - } -} - -impl DatabaseViewEditor { - pub async fn from_pad( - delegate: Arc, - cell_data_cache: AtomicCellDataCache, - rev_manager: RevisionManager>, - view_rev_pad: DatabaseViewRevisionPad, - ) -> FlowyResult { - let view_id = view_rev_pad.view_id.clone(); - let (notifier, _) = broadcast::channel(100); - tokio::spawn(DatabaseViewChangedReceiverRunner(Some(notifier.subscribe())).run()); - - let view_rev_pad = Arc::new(RwLock::new(view_rev_pad)); - let rev_manager = Arc::new(rev_manager); - let group_controller = new_group_controller( - view_id.clone(), - view_rev_pad.clone(), - rev_manager.clone(), - delegate.clone(), - ) - .await?; - - let group_controller = Arc::new(RwLock::new(group_controller)); - let filter_controller = make_filter_controller( - &view_id, - delegate.clone(), - notifier.clone(), - cell_data_cache.clone(), - view_rev_pad.clone(), - ) - .await; - - let sort_controller = make_sort_controller( - &view_id, - delegate.clone(), - notifier.clone(), - filter_controller.clone(), - view_rev_pad.clone(), - cell_data_cache, - ) - .await; - Ok(Self { - pad: view_rev_pad, - view_id, - rev_manager, - delegate, - group_controller, - filter_controller, - sort_controller, - notifier, - }) - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn new( - token: &str, - view_id: String, - delegate: Arc, - cell_data_cache: AtomicCellDataCache, - mut rev_manager: RevisionManager>, - ) -> FlowyResult { - let cloud = Arc::new(DatabaseViewRevisionCloudService { - token: token.to_owned(), - }); - - let view_rev_pad = match rev_manager - .initialize::(Some(cloud)) - .await - { - Ok(pad) => pad, - Err(err) => { - // It shouldn't be here, because the snapshot should come to recue. - tracing::error!("Deserialize database view revisions failed: {}", err); - let (view, reset_revision) = generate_restore_view(&view_id).await; - let _ = rev_manager.reset_object(vec![reset_revision]).await; - view - }, - }; - - Self::from_pad(delegate, cell_data_cache, rev_manager, view_rev_pad).await - } - - #[tracing::instrument(name = "close database view editor", level = "trace", skip_all)] - pub async fn close(&self) { - self.rev_manager.generate_snapshot().await; - self.rev_manager.close().await; - self.sort_controller.write().await.close().await; - self.filter_controller.close().await; - } - - pub async fn handle_block_event(&self, event: Cow<'_, DatabaseBlockEvent>) { - let changeset = match event.into_owned() { - DatabaseBlockEvent::InsertRow { block_id: _, row } => { - // - RowsChangesetPB::from_insert(self.view_id.clone(), vec![row]) - }, - DatabaseBlockEvent::UpdateRow { block_id: _, row } => { - // - RowsChangesetPB::from_update(self.view_id.clone(), vec![row]) - }, - DatabaseBlockEvent::DeleteRow { - block_id: _, - row_id, - } => { - // - RowsChangesetPB::from_delete(self.view_id.clone(), vec![row_id]) - }, - DatabaseBlockEvent::Move { - block_id: _, - deleted_row_id, - inserted_row, - } => { - // - RowsChangesetPB::from_move( - self.view_id.clone(), - vec![deleted_row_id], - vec![inserted_row], - ) - }, - }; - - send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows) - .payload(changeset) - .send(); - } - - pub async fn v_sort_rows(&self, rows: &mut Vec>) { - self.sort_controller.write().await.sort_rows(rows).await - } - - pub async fn v_filter_rows(&self, _block_id: &str, rows: &mut Vec>) { - self.filter_controller.filter_row_revs(rows).await; - } - - pub async fn v_duplicate_database_view(&self) -> FlowyResult { - let json_str = self.pad.read().await.json_str()?; - Ok(json_str) - } - - pub async fn v_will_create_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) { - if params.group_id.is_none() { - return; - } - let group_id = params.group_id.as_ref().unwrap(); - let _ = self - .mut_group_controller(|group_controller, field_rev| { - group_controller.will_create_row(row_rev, &field_rev, group_id); - Ok(()) - }) - .await; - } - - pub async fn v_did_create_row(&self, row_pb: &RowPB, params: &CreateRowParams) { - // Send the group notification if the current view has groups - match params.group_id.as_ref() { - None => {}, - Some(group_id) => { - let index = match params.start_row_id { - None => Some(0), - Some(_) => None, - }; - - self - .group_controller - .write() - .await - .did_create_row(row_pb, group_id); - let inserted_row = InsertedRowPB { - row: row_pb.clone(), - index, - is_new: true, - }; - let changeset = GroupRowsNotificationPB::insert(group_id.clone(), vec![inserted_row]); - self.notify_did_update_group_rows(changeset).await; - }, - } - } - - #[tracing::instrument(level = "trace", skip_all)] - pub async fn v_did_delete_row(&self, row_rev: &RowRevision) { - // Send the group notification if the current view has groups; - let result = self - .mut_group_controller(|group_controller, field_rev| { - group_controller.did_delete_delete_row(row_rev, &field_rev) - }) - .await; - - if let Some(result) = result { - tracing::trace!("Delete row in view changeset: {:?}", result.row_changesets); - for changeset in result.row_changesets { - self.notify_did_update_group_rows(changeset).await; - } - } - } - - pub async fn v_did_update_row( - &self, - old_row_rev: Option>, - row_rev: &RowRevision, - ) { - let result = self - .mut_group_controller(|group_controller, field_rev| { - Ok(group_controller.did_update_group_row(&old_row_rev, row_rev, &field_rev)) - }) - .await; - - if let Some(Ok(result)) = result { - let mut changeset = GroupChangesetPB { - view_id: self.view_id.clone(), - ..Default::default() - }; - if let Some(inserted_group) = result.inserted_group { - tracing::trace!("Create group after editing the row: {:?}", inserted_group); - changeset.inserted_groups.push(inserted_group); - } - if let Some(delete_group) = result.deleted_group { - tracing::trace!("Delete group after editing the row: {:?}", delete_group); - changeset.deleted_groups.push(delete_group.group_id); - } - self.notify_did_update_groups(changeset).await; - - tracing::trace!( - "Group changesets after editing the row: {:?}", - result.row_changesets - ); - for changeset in result.row_changesets { - self.notify_did_update_group_rows(changeset).await; - } - } - - let filter_controller = self.filter_controller.clone(); - let sort_controller = self.sort_controller.clone(); - let row_id = row_rev.id.clone(); - tokio::spawn(async move { - filter_controller.did_receive_row_changed(&row_id).await; - sort_controller - .read() - .await - .did_receive_row_changed(&row_id) - .await; - }); - } - - pub async fn v_move_group_row( - &self, - row_rev: &RowRevision, - row_changeset: &mut RowChangeset, - to_group_id: &str, - to_row_id: Option, - ) { - let result = self - .mut_group_controller(|group_controller, field_rev| { - let move_row_context = MoveGroupRowContext { - row_rev, - row_changeset, - field_rev: field_rev.as_ref(), - to_group_id, - to_row_id, - }; - group_controller.move_group_row(move_row_context) - }) - .await; - - if let Some(result) = result { - let mut changeset = GroupChangesetPB { - view_id: self.view_id.clone(), - ..Default::default() - }; - if let Some(delete_group) = result.deleted_group { - tracing::info!("Delete group after moving the row: {:?}", delete_group); - changeset.deleted_groups.push(delete_group.group_id); - } - self.notify_did_update_groups(changeset).await; - - for changeset in result.row_changesets { - self.notify_did_update_group_rows(changeset).await; - } - } - } - /// Only call once after database view editor initialized - #[tracing::instrument(level = "trace", skip(self))] - pub async fn v_load_groups(&self) -> FlowyResult> { - let groups = self - .group_controller - .read() - .await - .groups() - .into_iter() - .cloned() - .collect::>(); - tracing::trace!("Number of groups: {}", groups.len()); - Ok(groups.into_iter().map(GroupPB::from).collect()) - } - - #[tracing::instrument(level = "trace", skip(self))] - pub async fn v_get_group(&self, group_id: &str) -> FlowyResult { - match self.group_controller.read().await.get_group(group_id) { - None => Err(FlowyError::record_not_found().context("Can't find the group")), - Some((_, group)) => Ok(GroupPB::from(group)), - } - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn v_move_group(&self, params: MoveGroupParams) -> FlowyResult<()> { - self - .group_controller - .write() - .await - .move_group(¶ms.from_group_id, ¶ms.to_group_id)?; - match self - .group_controller - .read() - .await - .get_group(¶ms.from_group_id) - { - None => tracing::warn!("Can not find the group with id: {}", params.from_group_id), - Some((index, group)) => { - let inserted_group = InsertedGroupPB { - group: GroupPB::from(group), - index: index as i32, - }; - - let changeset = GroupChangesetPB { - view_id: self.view_id.clone(), - inserted_groups: vec![inserted_group], - deleted_groups: vec![params.from_group_id.clone()], - update_groups: vec![], - initial_groups: vec![], - }; - - self.notify_did_update_groups(changeset).await; - }, - } - Ok(()) - } - - pub async fn group_id(&self) -> String { - self.group_controller.read().await.field_id().to_string() - } - - /// Initialize new group when grouping by a new field - /// - pub async fn v_initialize_new_group(&self, params: InsertGroupParams) -> FlowyResult<()> { - if let Some(field_rev) = self.delegate.get_field_rev(¶ms.field_id).await { - self - .modify(|pad| { - let configuration = default_group_configuration(&field_rev); - let changeset = pad.insert_or_update_group_configuration( - ¶ms.field_id, - ¶ms.field_type_rev, - configuration, - )?; - Ok(changeset) - }) - .await?; - } - if self.group_controller.read().await.field_id() != params.field_id { - self.v_update_group_setting(¶ms.field_id).await?; - self.notify_did_update_setting().await; - } - Ok(()) - } - - pub async fn v_delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { - self - .modify(|pad| { - let changeset = - pad.delete_group(¶ms.group_id, ¶ms.field_id, ¶ms.field_type_rev)?; - Ok(changeset) - }) - .await - } - - pub async fn v_get_setting(&self) -> DatabaseViewSettingPB { - let field_revs = self.delegate.get_field_revs(None).await; - make_database_view_setting(&*self.pad.read().await, &field_revs) - } - - pub async fn v_get_all_sorts(&self) -> Vec> { - let field_revs = self.delegate.get_field_revs(None).await; - self.pad.read().await.get_all_sorts(&field_revs) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn v_insert_sort(&self, params: AlterSortParams) -> FlowyResult { - let sort_type = SortType::from(¶ms); - let is_exist = params.sort_id.is_some(); - let sort_id = match params.sort_id { - None => gen_database_sort_id(), - Some(sort_id) => sort_id, - }; - - let sort_rev = SortRevision { - id: sort_id, - field_id: params.field_id.clone(), - field_type: params.field_type, - condition: params.condition.into(), - }; - - let mut sort_controller = self.sort_controller.write().await; - let changeset = if is_exist { - self - .modify(|pad| { - let changeset = pad.update_sort(¶ms.field_id, sort_rev.clone())?; - Ok(changeset) - }) - .await?; - sort_controller - .did_receive_changes(SortChangeset::from_update(sort_type)) - .await - } else { - self - .modify(|pad| { - let changeset = pad.insert_sort(¶ms.field_id, sort_rev.clone())?; - Ok(changeset) - }) - .await?; - sort_controller - .did_receive_changes(SortChangeset::from_insert(sort_type)) - .await - }; - drop(sort_controller); - self.notify_did_update_sort(changeset).await; - Ok(sort_rev) - } - - pub async fn v_delete_sort(&self, params: DeleteSortParams) -> FlowyResult<()> { - let notification = self - .sort_controller - .write() - .await - .did_receive_changes(SortChangeset::from_delete(DeletedSortType::from( - params.clone(), - ))) - .await; - - let sort_type = params.sort_type; - self - .modify(|pad| { - let changeset = - pad.delete_sort(¶ms.sort_id, &sort_type.field_id, sort_type.field_type)?; - Ok(changeset) - }) - .await?; - - self.notify_did_update_sort(notification).await; - Ok(()) - } - - pub async fn v_delete_all_sorts(&self) -> FlowyResult<()> { - let all_sorts = self.v_get_all_sorts().await; - // self.sort_controller.write().await.delete_all_sorts().await; - self - .modify(|pad| { - let changeset = pad.delete_all_sorts()?; - Ok(changeset) - }) - .await?; - - let mut notification = SortChangesetNotificationPB::new(self.view_id.clone()); - notification.delete_sorts = all_sorts - .into_iter() - .map(|sort| SortPB::from(sort.as_ref())) - .collect(); - self.notify_did_update_sort(notification).await; - Ok(()) - } - - pub async fn v_get_all_filters(&self) -> Vec> { - let field_revs = self.delegate.get_field_revs(None).await; - self.pad.read().await.get_all_filters(&field_revs) - } - - pub async fn v_get_filters(&self, filter_type: &FilterType) -> Vec> { - let field_type_rev: FieldTypeRevision = filter_type.field_type.clone().into(); - self - .pad - .read() - .await - .get_filters(&filter_type.field_id, &field_type_rev) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn v_insert_filter(&self, params: AlterFilterParams) -> FlowyResult<()> { - let filter_type = FilterType::from(¶ms); - let is_exist = params.filter_id.is_some(); - let filter_id = match params.filter_id { - None => gen_database_filter_id(), - Some(filter_id) => filter_id, - }; - let filter_rev = FilterRevision { - id: filter_id.clone(), - field_id: params.field_id.clone(), - field_type: params.field_type, - condition: params.condition, - content: params.content, - }; - let filter_controller = self.filter_controller.clone(); - let changeset = if is_exist { - let old_filter_type = self - .delegate - .get_field_rev(¶ms.field_id) - .await - .map(|field| FilterType::from(&field)); - self - .modify(|pad| { - let changeset = pad.update_filter(¶ms.field_id, filter_rev)?; - Ok(changeset) - }) - .await?; - filter_controller - .did_receive_changes(FilterChangeset::from_update(UpdatedFilterType::new( - old_filter_type, - filter_type, - ))) - .await - } else { - self - .modify(|pad| { - let changeset = pad.insert_filter(¶ms.field_id, filter_rev)?; - Ok(changeset) - }) - .await?; - filter_controller - .did_receive_changes(FilterChangeset::from_insert(filter_type)) - .await - }; - drop(filter_controller); - - if let Some(changeset) = changeset { - self.notify_did_update_filter(changeset).await; - } - Ok(()) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn v_delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { - let filter_type = params.filter_type; - let changeset = self - .filter_controller - .did_receive_changes(FilterChangeset::from_delete(filter_type.clone())) - .await; - - self - .modify(|pad| { - let changeset = pad.delete_filter( - ¶ms.filter_id, - &filter_type.field_id, - filter_type.field_type, - )?; - Ok(changeset) - }) - .await?; - - if changeset.is_some() { - self.notify_did_update_filter(changeset.unwrap()).await; - } - Ok(()) - } - - /// Returns the current calendar settings - #[tracing::instrument(level = "debug", skip(self), err)] - pub async fn v_get_layout_settings( - &self, - layout_ty: &LayoutRevision, - ) -> FlowyResult { - let mut layout_setting = LayoutSettingParams::default(); - match layout_ty { - LayoutRevision::Grid => {}, - LayoutRevision::Board => {}, - LayoutRevision::Calendar => { - if let Some(calendar) = self - .pad - .read() - .await - .get_layout_setting::(layout_ty) - { - // Check the field exist or not - if let Some(field_rev) = self.delegate.get_field_rev(&calendar.layout_field_id).await { - let field_type: FieldType = field_rev.ty.into(); - - // Check the type of field is Datetime or not - if field_type == FieldType::DateTime { - layout_setting.calendar = Some(calendar); - } - } - } - }, - } - - tracing::debug!("{:?}", layout_setting); - Ok(layout_setting) - } - - /// Update the calendar settings and send the notification to refresh the UI - pub async fn v_set_layout_settings(&self, params: LayoutSettingParams) -> FlowyResult<()> { - // Maybe it needs no send notification to refresh the UI - if let Some(new_calendar_setting) = params.calendar { - if let Some(field_rev) = self - .delegate - .get_field_rev(&new_calendar_setting.layout_field_id) - .await - { - let field_type: FieldType = field_rev.ty.into(); - if field_type != FieldType::DateTime { - return Err(FlowyError::unexpect_calendar_field_type()); - } - - let layout_ty = LayoutRevision::Calendar; - let old_calender_setting = self.v_get_layout_settings(&layout_ty).await?.calendar; - self - .modify(|pad| Ok(pad.set_layout_setting(&layout_ty, &new_calendar_setting)?)) - .await?; - - let new_field_id = new_calendar_setting.layout_field_id.clone(); - let layout_setting_pb: LayoutSettingPB = LayoutSettingParams { - calendar: Some(new_calendar_setting), - } - .into(); - - if let Some(old_calendar_setting) = old_calender_setting { - // compare the new layout field id is equal to old layout field id - // if not equal, send the DidSetNewLayoutField notification - // if equal, send the DidUpdateLayoutSettings notification - if old_calendar_setting.layout_field_id != new_field_id { - send_notification(&self.view_id, DatabaseNotification::DidSetNewLayoutField) - .payload(layout_setting_pb) - .send(); - } else { - send_notification(&self.view_id, DatabaseNotification::DidUpdateLayoutSettings) - .payload(layout_setting_pb) - .send(); - } - } else { - tracing::warn!("Calendar setting should not be empty") - } - } - } - - Ok(()) - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn v_did_update_field_type_option( - &self, - field_id: &str, - old_field_rev: Option>, - ) -> FlowyResult<()> { - if let Some(field_rev) = self.delegate.get_field_rev(field_id).await { - let old = old_field_rev.map(|old_field_rev| FilterType::from(&old_field_rev)); - let new = FilterType::from(&field_rev); - let filter_type = UpdatedFilterType::new(old, new); - let filter_changeset = FilterChangeset::from_update(filter_type); - - self - .sort_controller - .read() - .await - .did_update_view_field_type_option(&field_rev) - .await; - - let filter_controller = self.filter_controller.clone(); - let _ = tokio::spawn(async move { - if let Some(notification) = filter_controller - .did_receive_changes(filter_changeset) - .await - { - send_notification(¬ification.view_id, DatabaseNotification::DidUpdateFilter) - .payload(notification) - .send(); - } - }); - } - Ok(()) - } - - /// - /// - /// # Arguments - /// - /// * `field_id`: - /// - #[tracing::instrument(level = "debug", skip_all, err)] - pub async fn v_update_group_setting(&self, field_id: &str) -> FlowyResult<()> { - if let Some(field_rev) = self.delegate.get_field_rev(field_id).await { - let row_revs = self.delegate.get_row_revs(None).await; - let configuration_reader = GroupConfigurationReaderImpl { - pad: self.pad.clone(), - view_editor_delegate: self.delegate.clone(), - }; - let new_group_controller = new_group_controller_with_field_rev( - self.view_id.clone(), - self.pad.clone(), - self.rev_manager.clone(), - field_rev, - row_revs, - configuration_reader, - ) - .await?; - - let new_groups = new_group_controller - .groups() - .into_iter() - .map(|group| GroupPB::from(group.clone())) - .collect(); - - *self.group_controller.write().await = new_group_controller; - let changeset = GroupChangesetPB { - view_id: self.view_id.clone(), - initial_groups: new_groups, - ..Default::default() - }; - - debug_assert!(!changeset.is_empty()); - if !changeset.is_empty() { - send_notification(&changeset.view_id, DatabaseNotification::DidGroupByField) - .payload(changeset) - .send(); - } - } - Ok(()) - } - - pub(crate) async fn v_get_cells_for_field( - &self, - field_id: &str, - ) -> FlowyResult> { - get_cells_for_field(self.delegate.clone(), field_id).await - } - - pub async fn v_get_calendar_event(&self, row_id: &str) -> Option { - let layout_ty = LayoutRevision::Calendar; - let calendar_setting = self - .v_get_layout_settings(&layout_ty) - .await - .ok()? - .calendar?; - - // Text - let primary_field = self.delegate.get_primary_field_rev().await?; - let text_cell = get_cell_for_row(self.delegate.clone(), &primary_field.id, row_id).await?; - - // Date - let date_field = self - .delegate - .get_field_rev(&calendar_setting.layout_field_id) - .await?; - - let date_cell = get_cell_for_row(self.delegate.clone(), &date_field.id, row_id).await?; - let title = text_cell - .into_text_field_cell_data() - .unwrap_or_default() - .into(); - - let timestamp = date_cell - .into_date_field_cell_data() - .unwrap_or_default() - .timestamp - .unwrap_or_default(); - - Some(CalendarEventPB { - row_id: row_id.to_string(), - date_field_id: date_field.id.clone(), - title, - timestamp, - }) - } - - pub async fn v_get_all_calendar_events(&self) -> Option> { - let layout_ty = LayoutRevision::Calendar; - let calendar_setting = self - .v_get_layout_settings(&layout_ty) - .await - .ok()? - .calendar?; - - // Text - let primary_field = self.delegate.get_primary_field_rev().await?; - let text_cells = self.v_get_cells_for_field(&primary_field.id).await.ok()?; - - // Date - let timestamp_by_row_id = self - .v_get_cells_for_field(&calendar_setting.layout_field_id) - .await - .ok()? - .into_iter() - .map(|date_cell| { - let row_id = date_cell.row_id.clone(); - - // timestamp - let timestamp = date_cell - .into_date_field_cell_data() - .map(|date_cell_data| date_cell_data.timestamp.unwrap_or_default()) - .unwrap_or_default(); - - (row_id, timestamp) - }) - .collect::>(); - - let mut events: Vec = vec![]; - for text_cell in text_cells { - let row_id = text_cell.row_id.clone(); - let timestamp = timestamp_by_row_id - .get(&row_id) - .cloned() - .unwrap_or_default(); - - let title = text_cell - .into_text_field_cell_data() - .unwrap_or_default() - .into(); - - let event = CalendarEventPB { - row_id, - date_field_id: calendar_setting.layout_field_id.clone(), - title, - timestamp, - }; - events.push(event); - } - - Some(events) - } - - async fn notify_did_update_setting(&self) { - let setting = self.v_get_setting().await; - send_notification(&self.view_id, DatabaseNotification::DidUpdateSettings) - .payload(setting) - .send(); - } - - pub async fn notify_did_update_group_rows(&self, payload: GroupRowsNotificationPB) { - send_notification(&payload.group_id, DatabaseNotification::DidUpdateGroupRow) - .payload(payload) - .send(); - } - - pub async fn notify_did_update_filter(&self, notification: FilterChangesetNotificationPB) { - send_notification(¬ification.view_id, DatabaseNotification::DidUpdateFilter) - .payload(notification) - .send(); - } - - pub async fn notify_did_update_sort(&self, notification: SortChangesetNotificationPB) { - if !notification.is_empty() { - send_notification(¬ification.view_id, DatabaseNotification::DidUpdateSort) - .payload(notification) - .send(); - } - } - - async fn notify_did_update_groups(&self, changeset: GroupChangesetPB) { - send_notification(&self.view_id, DatabaseNotification::DidUpdateGroups) - .payload(changeset) - .send(); - } - - async fn modify(&self, f: F) -> FlowyResult<()> - where - F: for<'a> FnOnce( - &'a mut DatabaseViewRevisionPad, - ) -> FlowyResult>, - { - let mut write_guard = self.pad.write().await; - match f(&mut write_guard)? { - None => {}, - Some(change) => { - apply_change(self.rev_manager.clone(), change).await?; - }, - } - Ok(()) - } - - async fn mut_group_controller(&self, f: F) -> Option - where - F: FnOnce(&mut Box, Arc) -> FlowyResult, - { - let group_field_id = self.group_controller.read().await.field_id().to_owned(); - match self.delegate.get_field_rev(&group_field_id).await { - None => None, - Some(field_rev) => { - let mut write_guard = self.group_controller.write().await; - f(&mut write_guard, field_rev).ok() - }, - } - } - - #[allow(dead_code)] - async fn async_mut_group_controller(&self, f: F) -> Option - where - F: FnOnce(Arc>>, Arc) -> O, - O: Future> + Sync + 'static, - { - let group_field_id = self.group_controller.read().await.field_id().to_owned(); - match self.delegate.get_field_rev(&group_field_id).await { - None => None, - Some(field_rev) => { - let _write_guard = self.group_controller.write().await; - f(self.group_controller.clone(), field_rev).await.ok() - }, - } - } -} - -pub(crate) async fn get_cell_for_row( - delegate: Arc, - field_id: &str, - row_id: &str, -) -> Option { - let (_, row_rev) = delegate.get_row_rev(row_id).await?; - let mut cells = get_cells_for_field_in_rows(delegate, field_id, vec![row_rev]) - .await - .ok()?; - if cells.is_empty() { - None - } else { - assert_eq!(cells.len(), 1); - Some(cells.remove(0)) - } -} - -// Returns the list of cells corresponding to the given field. -pub(crate) async fn get_cells_for_field( - delegate: Arc, - field_id: &str, -) -> FlowyResult> { - let row_revs = delegate.get_row_revs(None).await; - get_cells_for_field_in_rows(delegate, field_id, row_revs).await -} - -pub(crate) async fn get_cells_for_field_in_rows( - delegate: Arc, - field_id: &str, - row_revs: Vec>, -) -> FlowyResult> { - let field_rev = delegate.get_field_rev(field_id).await.unwrap(); - let field_type: FieldType = field_rev.ty.into(); - let mut cells = vec![]; - if let Some(handler) = delegate.get_type_option_cell_handler(&field_rev, &field_type) { - for row_rev in row_revs { - if let Some(cell_rev) = row_rev.cells.get(field_id) { - if let Ok(type_cell_data) = TypeCellData::try_from(cell_rev) { - if let Ok(cell_data) = - handler.get_cell_data(type_cell_data.cell_str, &field_type, &field_rev) - { - cells.push(RowSingleCellData { - row_id: row_rev.id.clone(), - field_id: field_rev.id.clone(), - field_type: field_type.clone(), - cell_data, - }) - } - } - } - } - } - Ok(cells) -} - -async fn new_group_controller( - view_id: String, - view_rev_pad: Arc>, - rev_manager: Arc>>, - delegate: Arc, -) -> FlowyResult> { - let configuration_reader = GroupConfigurationReaderImpl { - pad: view_rev_pad.clone(), - view_editor_delegate: delegate.clone(), - }; - let field_revs = delegate.get_field_revs(None).await; - let row_revs = delegate.get_row_revs(None).await; - let layout = view_rev_pad.read().await.layout(); - // Read the group field or find a new group field - let field_rev = configuration_reader - .get_configuration() - .await - .and_then(|configuration| { - field_revs - .iter() - .find(|field_rev| field_rev.id == configuration.field_id) - .cloned() - }) - .unwrap_or_else(|| find_grouping_field(&field_revs, &layout).unwrap()); - - new_group_controller_with_field_rev( - view_id, - view_rev_pad, - rev_manager, - field_rev, - row_revs, - configuration_reader, - ) - .await -} - -/// Returns a [GroupController] -/// -async fn new_group_controller_with_field_rev( - view_id: String, - view_rev_pad: Arc>, - rev_manager: Arc>>, - grouping_field_rev: Arc, - row_revs: Vec>, - configuration_reader: GroupConfigurationReaderImpl, -) -> FlowyResult> { - let configuration_writer = GroupConfigurationWriterImpl { - rev_manager, - view_pad: view_rev_pad, - }; - make_group_controller( - view_id, - grouping_field_rev, - row_revs, - configuration_reader, - configuration_writer, - ) - .await -} - -async fn make_filter_controller( - view_id: &str, - delegate: Arc, - notifier: DatabaseViewChangedNotifier, - cell_data_cache: AtomicCellDataCache, - pad: Arc>, -) -> Arc { - let field_revs = delegate.get_field_revs(None).await; - let filter_revs = pad.read().await.get_all_filters(&field_revs); - let task_scheduler = delegate.get_task_scheduler(); - let filter_delegate = DatabaseViewFilterDelegateImpl { - editor_delegate: delegate.clone(), - view_revision_pad: pad, - }; - let handler_id = gen_handler_id(); - let filter_controller = FilterController::new( - view_id, - &handler_id, - filter_delegate, - task_scheduler.clone(), - filter_revs, - cell_data_cache, - notifier, - ) - .await; - let filter_controller = Arc::new(filter_controller); - task_scheduler - .write() - .await - .register_handler(FilterTaskHandler::new( - handler_id, - filter_controller.clone(), - )); - filter_controller -} - -async fn make_sort_controller( - view_id: &str, - delegate: Arc, - notifier: DatabaseViewChangedNotifier, - filter_controller: Arc, - pad: Arc>, - cell_data_cache: AtomicCellDataCache, -) -> Arc> { - let handler_id = gen_handler_id(); - let field_revs = delegate.get_field_revs(None).await; - let sorts = pad.read().await.get_all_sorts(&field_revs); - let sort_delegate = DatabaseViewSortDelegateImpl { - editor_delegate: delegate.clone(), - view_revision_pad: pad, - filter_controller, - }; - let task_scheduler = delegate.get_task_scheduler(); - let sort_controller = Arc::new(RwLock::new(SortController::new( - view_id, - &handler_id, - sorts, - sort_delegate, - task_scheduler.clone(), - cell_data_cache, - notifier, - ))); - task_scheduler - .write() - .await - .register_handler(SortTaskHandler::new(handler_id, sort_controller.clone())); - - sort_controller -} - -fn gen_handler_id() -> String { - nanoid!(10) -} - -async fn generate_restore_view(view_id: &str) -> (DatabaseViewRevisionPad, Revision) { - let database_id = gen_database_id(); - let view = DatabaseViewRevisionPad::new( - database_id, - view_id.to_owned(), - "".to_string(), - LayoutRevision::Grid, - ); - let bytes = make_database_view_operations(&view).json_bytes(); - let reset_revision = Revision::initial_revision(view_id, bytes); - (view, reset_revision) -} - -#[cfg(test)] -mod tests { - use flowy_client_sync::client_database::DatabaseOperations; - - #[test] - fn test() { - let s1 = r#"[{"insert":"{\"view_id\":\"fTURELffPr\",\"grid_id\":\"fTURELffPr\",\"layout\":0,\"filters\":[],\"groups\":[]}"}]"#; - let _delta_1 = DatabaseOperations::from_json(s1).unwrap(); - - let s2 = r#"[{"retain":195},{"insert":"{\\\"group_id\\\":\\\"wD9i\\\",\\\"visible\\\":true},{\\\"group_id\\\":\\\"xZtv\\\",\\\"visible\\\":true},{\\\"group_id\\\":\\\"tFV2\\\",\\\"visible\\\":true}"},{"retain":10}]"#; - let _delta_2 = DatabaseOperations::from_json(s2).unwrap(); - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/database_view/editor_manager.rs b/frontend/rust-lib/flowy-database/src/services/database_view/editor_manager.rs deleted file mode 100644 index b7bc697895..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/database_view/editor_manager.rs +++ /dev/null @@ -1,395 +0,0 @@ -#![allow(clippy::while_let_loop)] -use crate::entities::{ - AlterFilterParams, AlterSortParams, CreateRowParams, DatabaseViewSettingPB, DeleteFilterParams, - DeleteGroupParams, DeleteSortParams, GroupPB, InsertGroupParams, LayoutSettingParams, - MoveGroupParams, RepeatedGroupPB, RowPB, -}; -use crate::manager::DatabaseUser; -use crate::services::cell::AtomicCellDataCache; -use crate::services::database::DatabaseBlockEvent; -use crate::services::database_view::notifier::*; -use crate::services::database_view::trait_impl::{ - DatabaseViewRevisionMergeable, DatabaseViewRevisionSerde, -}; -use crate::services::database_view::{DatabaseViewData, DatabaseViewEditor}; -use crate::services::filter::FilterType; -use crate::services::persistence::rev_sqlite::{ - SQLiteDatabaseRevisionSnapshotPersistence, SQLiteDatabaseViewRevisionPersistence, -}; -use database_model::{ - FieldRevision, FilterRevision, LayoutRevision, RowChangeset, RowRevision, SortRevision, -}; -use flowy_client_sync::client_database::DatabaseViewRevisionPad; -use flowy_error::FlowyResult; -use flowy_revision::{RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration}; -use flowy_sqlite::ConnectionPool; -use lib_infra::future::Fut; -use std::borrow::Cow; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::{broadcast, RwLock}; - -/// It's used to manager the list of views that reference to the same database. -pub struct DatabaseViews { - user: Arc, - delegate: Arc, - view_editors: Arc>>>, - cell_data_cache: AtomicCellDataCache, -} - -impl DatabaseViews { - pub async fn new( - user: Arc, - delegate: Arc, - cell_data_cache: AtomicCellDataCache, - block_event_rx: broadcast::Receiver, - ) -> FlowyResult { - let view_editors = Arc::new(RwLock::new(HashMap::default())); - listen_on_database_block_event(block_event_rx, view_editors.clone()); - Ok(Self { - user, - delegate, - view_editors, - cell_data_cache, - }) - } - - pub async fn open(&self, view_editor: DatabaseViewEditor) { - let view_id = view_editor.view_id.clone(); - self - .view_editors - .write() - .await - .insert(view_id, Arc::new(view_editor)); - } - - pub async fn close(&self, view_id: &str) { - if let Some(view_editor) = self.view_editors.write().await.remove(view_id) { - view_editor.close().await; - } - } - - pub async fn number_of_views(&self) -> usize { - self.view_editors.read().await.values().len() - } - - pub async fn is_view_exist(&self, view_id: &str) -> bool { - self.view_editors.read().await.get(view_id).is_some() - } - - pub async fn subscribe_view_changed( - &self, - view_id: &str, - ) -> FlowyResult> { - Ok(self.get_view_editor(view_id).await?.notifier.subscribe()) - } - - pub async fn get_row_revs( - &self, - view_id: &str, - block_id: &str, - ) -> FlowyResult>> { - let mut row_revs = self - .delegate - .get_row_revs(Some(vec![block_id.to_owned()])) - .await; - if let Ok(view_editor) = self.get_view_editor(view_id).await { - view_editor.v_filter_rows(block_id, &mut row_revs).await; - view_editor.v_sort_rows(&mut row_revs).await; - } - - Ok(row_revs) - } - - pub async fn duplicate_database_view(&self, view_id: &str) -> FlowyResult { - let editor = self.get_view_editor(view_id).await?; - let view_data = editor.v_duplicate_database_view().await?; - Ok(view_data) - } - - /// When the row was created, we may need to modify the [RowRevision] according to the [CreateRowParams]. - pub async fn will_create_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) { - for view_editor in self.view_editors.read().await.values() { - view_editor.v_will_create_row(row_rev, params).await; - } - } - - /// Notify the view that the row was created. For the moment, the view is just sending notifications. - pub async fn did_create_row(&self, row_pb: &RowPB, params: &CreateRowParams) { - for view_editor in self.view_editors.read().await.values() { - view_editor.v_did_create_row(row_pb, params).await; - } - } - - /// Insert/Delete the group's row if the corresponding cell data was changed. - pub async fn did_update_row(&self, old_row_rev: Option>, row_id: &str) { - match self.delegate.get_row_rev(row_id).await { - None => { - tracing::warn!("Can not find the row in grid view"); - }, - Some((_, row_rev)) => { - for view_editor in self.view_editors.read().await.values() { - view_editor - .v_did_update_row(old_row_rev.clone(), &row_rev) - .await; - } - }, - } - } - - pub async fn group_by_field(&self, view_id: &str, field_id: &str) -> FlowyResult<()> { - let view_editor = self.get_view_editor(view_id).await?; - view_editor.v_update_group_setting(field_id).await?; - Ok(()) - } - - pub async fn did_delete_row(&self, row_rev: Arc) { - for view_editor in self.view_editors.read().await.values() { - view_editor.v_did_delete_row(&row_rev).await; - } - } - - pub async fn get_setting(&self, view_id: &str) -> FlowyResult { - let view_editor = self.get_view_editor(view_id).await?; - Ok(view_editor.v_get_setting().await) - } - - pub async fn get_all_filters(&self, view_id: &str) -> FlowyResult>> { - let view_editor = self.get_view_editor(view_id).await?; - Ok(view_editor.v_get_all_filters().await) - } - - pub async fn get_filters( - &self, - view_id: &str, - filter_id: &FilterType, - ) -> FlowyResult>> { - let view_editor = self.get_view_editor(view_id).await?; - Ok(view_editor.v_get_filters(filter_id).await) - } - - pub async fn create_or_update_filter(&self, params: AlterFilterParams) -> FlowyResult<()> { - let view_editor = self.get_view_editor(¶ms.view_id).await?; - view_editor.v_insert_filter(params).await - } - - pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { - let view_editor = self.get_view_editor(¶ms.view_id).await?; - view_editor.v_delete_filter(params).await - } - - pub async fn get_all_sorts(&self, view_id: &str) -> FlowyResult>> { - let view_editor = self.get_view_editor(view_id).await?; - Ok(view_editor.v_get_all_sorts().await) - } - - pub async fn create_or_update_sort(&self, params: AlterSortParams) -> FlowyResult { - let view_editor = self.get_view_editor(¶ms.view_id).await?; - view_editor.v_insert_sort(params).await - } - - pub async fn delete_all_sorts(&self, view_id: &str) -> FlowyResult<()> { - let view_editor = self.get_view_editor(view_id).await?; - view_editor.v_delete_all_sorts().await - } - - pub async fn delete_sort(&self, params: DeleteSortParams) -> FlowyResult<()> { - let view_editor = self.get_view_editor(¶ms.view_id).await?; - view_editor.v_delete_sort(params).await - } - - pub async fn load_groups(&self, view_id: &str) -> FlowyResult { - let view_editor = self.get_view_editor(view_id).await?; - let groups = view_editor.v_load_groups().await?; - Ok(RepeatedGroupPB { items: groups }) - } - - pub async fn get_group(&self, view_id: &str, group_id: &str) -> FlowyResult { - let view_editor = self.get_view_editor(view_id).await?; - view_editor.v_get_group(group_id).await - } - - pub async fn get_layout_setting( - &self, - view_id: &str, - layout_ty: &LayoutRevision, - ) -> FlowyResult { - let view_editor = self.get_view_editor(view_id).await?; - view_editor.v_get_layout_settings(layout_ty).await - } - - pub async fn set_layout_setting( - &self, - view_id: &str, - layout_setting: LayoutSettingParams, - ) -> FlowyResult<()> { - let view_editor = self.get_view_editor(view_id).await?; - view_editor.v_set_layout_settings(layout_setting).await - } - - pub async fn insert_or_update_group(&self, params: InsertGroupParams) -> FlowyResult<()> { - let view_editor = self.get_view_editor(¶ms.view_id).await?; - view_editor.v_initialize_new_group(params).await - } - - pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { - let view_editor = self.get_view_editor(¶ms.view_id).await?; - view_editor.v_delete_group(params).await - } - - pub async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> { - let view_editor = self.get_view_editor(¶ms.view_id).await?; - view_editor.v_move_group(params).await?; - Ok(()) - } - - /// It may generate a RowChangeset when the Row was moved from one group to another. - /// The return value, [RowChangeset], contains the changes made by the groups. - /// - pub async fn move_group_row( - &self, - view_id: &str, - row_rev: Arc, - to_group_id: String, - to_row_id: Option, - recv_row_changeset: impl FnOnce(RowChangeset) -> Fut<()>, - ) -> FlowyResult<()> { - let mut row_changeset = RowChangeset::new(row_rev.id.clone()); - let view_editor = self.get_view_editor(view_id).await?; - view_editor - .v_move_group_row( - &row_rev, - &mut row_changeset, - &to_group_id, - to_row_id.clone(), - ) - .await; - - if !row_changeset.is_empty() { - recv_row_changeset(row_changeset).await; - } - - Ok(()) - } - - /// Notifies the view's field type-option data is changed - /// For the moment, only the groups will be generated after the type-option data changed. A - /// [FieldRevision] has a property named type_options contains a list of type-option data. - /// # Arguments - /// - /// * `field_id`: the id of the field in current view - /// - #[tracing::instrument(level = "debug", skip(self, old_field_rev), err)] - pub async fn did_update_field_type_option( - &self, - view_id: &str, - field_id: &str, - old_field_rev: Option>, - ) -> FlowyResult<()> { - let view_editor = self.get_view_editor(view_id).await?; - // If the id of the grouping field is equal to the updated field's id, then we need to - // update the group setting - if view_editor.group_id().await == field_id { - view_editor.v_update_group_setting(field_id).await?; - } - - view_editor - .v_did_update_field_type_option(field_id, old_field_rev) - .await?; - Ok(()) - } - - pub async fn get_view_editor(&self, view_id: &str) -> FlowyResult> { - debug_assert!(!view_id.is_empty()); - if let Some(editor) = self.view_editors.read().await.get(view_id) { - return Ok(editor.clone()); - } - - tracing::trace!("{:p} create view:{} editor", self, view_id); - let mut view_editors = self.view_editors.write().await; - let editor = Arc::new(self.make_view_editor(view_id).await?); - view_editors.insert(view_id.to_owned(), editor.clone()); - Ok(editor) - } - - async fn make_view_editor(&self, view_id: &str) -> FlowyResult { - let pool = self.user.db_pool()?; - let rev_manager = make_database_view_rev_manager(pool, view_id).await?; - let token = self.user.token()?; - let view_id = view_id.to_owned(); - - DatabaseViewEditor::new( - &token, - view_id, - self.delegate.clone(), - self.cell_data_cache.clone(), - rev_manager, - ) - .await - } -} - -#[tracing::instrument(level = "trace", skip(user), err)] -pub async fn make_database_view_revision_pad( - view_id: &str, - user: Arc, -) -> FlowyResult<( - DatabaseViewRevisionPad, - RevisionManager>, -)> { - let pool = user.db_pool()?; - let mut rev_manager = make_database_view_rev_manager(pool, view_id).await?; - let view_rev_pad = rev_manager - .initialize::(None) - .await?; - Ok((view_rev_pad, rev_manager)) -} - -pub async fn make_database_view_rev_manager( - pool: Arc, - view_id: &str, -) -> FlowyResult>> { - // Create revision persistence - let disk_cache = SQLiteDatabaseViewRevisionPersistence::new(pool.clone()); - let configuration = RevisionPersistenceConfiguration::new(2, false); - let rev_persistence = RevisionPersistence::new(view_id, disk_cache, configuration); - - // Create snapshot persistence - const DATABASE_VIEW_SP_PREFIX: &str = "grid_view"; - let snapshot_object_id = format!("{}:{}", DATABASE_VIEW_SP_PREFIX, view_id); - let snapshot_persistence = - SQLiteDatabaseRevisionSnapshotPersistence::new(&snapshot_object_id, pool); - - let rev_compress = DatabaseViewRevisionMergeable(); - Ok(RevisionManager::new( - view_id, - rev_persistence, - rev_compress, - snapshot_persistence, - )) -} - -fn listen_on_database_block_event( - mut block_event_rx: broadcast::Receiver, - view_editors: Arc>>>, -) { - tokio::spawn(async move { - loop { - match block_event_rx.recv().await { - Ok(event) => { - let read_guard = view_editors.read().await; - let view_editors = read_guard.values(); - let event = if view_editors.len() == 1 { - Cow::Owned(event) - } else { - Cow::Borrowed(&event) - }; - for view_editor in view_editors { - view_editor.handle_block_event(event.clone()).await; - } - }, - Err(_) => break, - } - } - }); -} diff --git a/frontend/rust-lib/flowy-database/src/services/database_view/mod.rs b/frontend/rust-lib/flowy-database/src/services/database_view/mod.rs deleted file mode 100644 index a75a142258..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/database_view/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod editor; -mod editor_manager; -mod notifier; -mod trait_impl; - -pub use editor::*; -pub use editor_manager::*; -pub use notifier::*; diff --git a/frontend/rust-lib/flowy-database/src/services/database_view/notifier.rs b/frontend/rust-lib/flowy-database/src/services/database_view/notifier.rs deleted file mode 100644 index 8020aea218..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/database_view/notifier.rs +++ /dev/null @@ -1,76 +0,0 @@ -#![allow(clippy::while_let_loop)] -use crate::entities::{ReorderAllRowsPB, ReorderSingleRowPB, RowsVisibilityChangesetPB}; -use crate::notification::{send_notification, DatabaseNotification}; -use crate::services::filter::FilterResultNotification; -use crate::services::sort::{ReorderAllRowsResult, ReorderSingleRowResult}; -use async_stream::stream; -use futures::stream::StreamExt; -use tokio::sync::broadcast; - -#[derive(Clone)] -pub enum DatabaseViewChanged { - FilterNotification(FilterResultNotification), - ReorderAllRowsNotification(ReorderAllRowsResult), - ReorderSingleRowNotification(ReorderSingleRowResult), -} - -pub type DatabaseViewChangedNotifier = broadcast::Sender; - -pub(crate) struct DatabaseViewChangedReceiverRunner( - pub(crate) Option>, -); - -impl DatabaseViewChangedReceiverRunner { - pub(crate) async fn run(mut self) { - let mut receiver = self.0.take().expect("Only take once"); - let stream = stream! { - loop { - match receiver.recv().await { - Ok(changed) => yield changed, - Err(_e) => break, - } - } - }; - stream - .for_each(|changed| async { - match changed { - DatabaseViewChanged::FilterNotification(notification) => { - let changeset = RowsVisibilityChangesetPB { - view_id: notification.view_id, - visible_rows: notification.visible_rows, - invisible_rows: notification.invisible_rows, - }; - - send_notification( - &changeset.view_id, - DatabaseNotification::DidUpdateViewRowsVisibility, - ) - .payload(changeset) - .send() - }, - DatabaseViewChanged::ReorderAllRowsNotification(notification) => { - let row_orders = ReorderAllRowsPB { - row_orders: notification.row_orders, - }; - send_notification(¬ification.view_id, DatabaseNotification::DidReorderRows) - .payload(row_orders) - .send() - }, - DatabaseViewChanged::ReorderSingleRowNotification(notification) => { - let reorder_row = ReorderSingleRowPB { - row_id: notification.row_id, - old_index: notification.old_index as i32, - new_index: notification.new_index as i32, - }; - send_notification( - ¬ification.view_id, - DatabaseNotification::DidReorderSingleRow, - ) - .payload(reorder_row) - .send() - }, - } - }) - .await; - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/database_view/trait_impl.rs b/frontend/rust-lib/flowy-database/src/services/database_view/trait_impl.rs deleted file mode 100644 index e9372646a7..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/database_view/trait_impl.rs +++ /dev/null @@ -1,252 +0,0 @@ -use crate::entities::{DatabaseViewSettingPB, LayoutSettingPB}; -use crate::services::database_view::{get_cells_for_field, DatabaseViewData}; -use crate::services::field::RowSingleCellData; -use crate::services::filter::{FilterController, FilterDelegate, FilterType}; -use crate::services::group::{GroupConfigurationReader, GroupConfigurationWriter}; -use crate::services::row::DatabaseBlockRowRevision; -use crate::services::sort::{SortDelegate, SortType}; -use bytes::Bytes; -use database_model::{ - CalendarLayoutSetting, FieldRevision, FieldTypeRevision, FilterRevision, - GroupConfigurationRevision, LayoutRevision, RowRevision, SortRevision, -}; -use flowy_client_sync::client_database::{DatabaseViewRevisionChangeset, DatabaseViewRevisionPad}; -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::{to_fut, Fut, FutureResult}; -use lib_ot::core::EmptyAttributes; -use revision_model::Revision; -use std::sync::Arc; -use tokio::sync::RwLock; - -pub(crate) struct DatabaseViewRevisionCloudService { - #[allow(dead_code)] - pub(crate) token: String, -} - -impl RevisionCloudService for DatabaseViewRevisionCloudService { - fn fetch_object( - &self, - _user_id: &str, - _object_id: &str, - ) -> FutureResult, FlowyError> { - FutureResult::new(async move { Ok(vec![]) }) - } -} - -pub(crate) struct DatabaseViewRevisionSerde(); -impl RevisionObjectDeserializer for DatabaseViewRevisionSerde { - type Output = DatabaseViewRevisionPad; - - fn deserialize_revisions( - _object_id: &str, - revisions: Vec, - ) -> FlowyResult { - let pad = DatabaseViewRevisionPad::from_revisions(revisions)?; - Ok(pad) - } - - fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { - None - } -} - -impl RevisionObjectSerializer for DatabaseViewRevisionSerde { - fn combine_revisions(revisions: Vec) -> FlowyResult { - let operations = make_operations_from_revisions::(revisions)?; - Ok(operations.json_bytes()) - } -} - -pub(crate) struct DatabaseViewRevisionMergeable(); -impl RevisionMergeable for DatabaseViewRevisionMergeable { - fn combine_revisions(&self, revisions: Vec) -> FlowyResult { - DatabaseViewRevisionSerde::combine_revisions(revisions) - } -} - -pub(crate) struct GroupConfigurationReaderImpl { - pub(crate) pad: Arc>, - pub(crate) view_editor_delegate: Arc, -} - -impl GroupConfigurationReader for GroupConfigurationReaderImpl { - fn get_configuration(&self) -> Fut>> { - let view_pad = self.pad.clone(); - to_fut(async move { - let mut groups = view_pad.read().await.get_all_groups(); - if groups.is_empty() { - None - } else { - debug_assert_eq!(groups.len(), 1); - Some(groups.pop().unwrap()) - } - }) - } - - fn get_configuration_cells(&self, field_id: &str) -> Fut>> { - let field_id = field_id.to_owned(); - let view_editor_delegate = self.view_editor_delegate.clone(); - to_fut(async move { get_cells_for_field(view_editor_delegate, &field_id).await }) - } -} - -pub(crate) struct GroupConfigurationWriterImpl { - pub(crate) rev_manager: Arc>>, - pub(crate) view_pad: Arc>, -} - -impl GroupConfigurationWriter for GroupConfigurationWriterImpl { - fn save_configuration( - &self, - field_id: &str, - field_type: FieldTypeRevision, - group_configuration: GroupConfigurationRevision, - ) -> Fut> { - let rev_manager = self.rev_manager.clone(); - let view_pad = self.view_pad.clone(); - let field_id = field_id.to_owned(); - - to_fut(async move { - let changeset = view_pad - .write() - .await - .insert_or_update_group_configuration(&field_id, &field_type, group_configuration)?; - - if let Some(changeset) = changeset { - apply_change(rev_manager, changeset).await?; - } - Ok(()) - }) - } -} - -pub(crate) async fn apply_change( - rev_manager: Arc>>, - change: DatabaseViewRevisionChangeset, -) -> FlowyResult<()> { - let DatabaseViewRevisionChangeset { - operations: delta, - md5, - } = change; - let data = delta.json_bytes(); - let _ = rev_manager.add_local_revision(data, md5).await?; - Ok(()) -} - -pub fn make_database_view_setting( - view_pad: &DatabaseViewRevisionPad, - field_revs: &[Arc], -) -> DatabaseViewSettingPB { - let layout_type: LayoutRevision = view_pad.layout.clone(); - let mut layout_settings = LayoutSettingPB::new(); - match layout_type { - LayoutRevision::Grid => {}, - LayoutRevision::Board => {}, - LayoutRevision::Calendar => { - layout_settings.calendar = view_pad - .get_layout_setting::(&layout_type) - .map(|params| params.into()); - }, - } - - let filters = view_pad.get_all_filters(field_revs); - let group_configurations = view_pad.get_groups_by_field_revs(field_revs); - let sorts = view_pad.get_all_sorts(field_revs); - DatabaseViewSettingPB { - current_layout: layout_type.into(), - layout_setting: layout_settings, - filters: filters.into(), - sorts: sorts.into(), - group_configurations: group_configurations.into(), - } -} - -pub(crate) struct DatabaseViewFilterDelegateImpl { - pub(crate) editor_delegate: Arc, - pub(crate) view_revision_pad: Arc>, -} - -impl FilterDelegate for DatabaseViewFilterDelegateImpl { - fn get_filter_rev(&self, filter_type: FilterType) -> Fut>> { - let pad = self.view_revision_pad.clone(); - to_fut(async move { - let field_type_rev: FieldTypeRevision = filter_type.field_type.into(); - let mut filters = pad - .read() - .await - .get_filters(&filter_type.field_id, &field_type_rev); - if filters.is_empty() { - None - } else { - debug_assert_eq!(filters.len(), 1); - filters.pop() - } - }) - } - - fn get_field_rev(&self, field_id: &str) -> Fut>> { - self.editor_delegate.get_field_rev(field_id) - } - - fn get_field_revs(&self, field_ids: Option>) -> Fut>> { - self.editor_delegate.get_field_revs(field_ids) - } - - fn get_blocks(&self) -> Fut> { - self.editor_delegate.get_blocks() - } - - fn get_row_rev(&self, row_id: &str) -> Fut)>> { - self.editor_delegate.get_row_rev(row_id) - } -} - -pub(crate) struct DatabaseViewSortDelegateImpl { - pub(crate) editor_delegate: Arc, - pub(crate) view_revision_pad: Arc>, - pub(crate) filter_controller: Arc, -} - -impl SortDelegate for DatabaseViewSortDelegateImpl { - fn get_sort_rev(&self, sort_type: SortType) -> Fut>> { - let pad = self.view_revision_pad.clone(); - to_fut(async move { - let field_type_rev: FieldTypeRevision = sort_type.field_type.into(); - let mut sorts = pad - .read() - .await - .get_sorts(&sort_type.field_id, &field_type_rev); - if sorts.is_empty() { - None - } else { - // Currently, one sort_type should have one sort. - debug_assert_eq!(sorts.len(), 1); - sorts.pop() - } - }) - } - - fn get_row_revs(&self) -> Fut>> { - let filter_controller = self.filter_controller.clone(); - let editor_delegate = self.editor_delegate.clone(); - to_fut(async move { - let mut row_revs = editor_delegate.get_row_revs(None).await; - filter_controller.filter_row_revs(&mut row_revs).await; - row_revs - }) - } - - fn get_field_rev(&self, field_id: &str) -> Fut>> { - self.editor_delegate.get_field_rev(field_id) - } - - fn get_field_revs(&self, field_ids: Option>) -> Fut>> { - self.editor_delegate.get_field_revs(field_ids) - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/field_builder.rs b/frontend/rust-lib/flowy-database/src/services/field/field_builder.rs deleted file mode 100644 index fea9e1f903..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/field_builder.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::entities::{FieldPB, FieldType}; - -use crate::services::field::{default_type_option_builder_from_type, TypeOptionBuilder}; - -use database_model::FieldRevision; -use indexmap::IndexMap; - -pub struct FieldBuilder { - field_rev: FieldRevision, - type_option_builder: Box, -} - -pub type BoxTypeOptionBuilder = Box; - -impl FieldBuilder { - pub fn new>(type_option_builder: T) -> Self { - let type_option_builder = type_option_builder.into(); - let field_type = type_option_builder.field_type(); - let width = field_type.default_cell_width(); - let field_rev = FieldRevision::new("", "", field_type, width, false); - Self { - field_rev, - type_option_builder, - } - } - - pub fn from_field_type(field_type: &FieldType) -> Self { - let type_option_builder = default_type_option_builder_from_type(field_type); - Self::new(type_option_builder) - } - - pub fn from_field(field: FieldPB, type_option_builder: Box) -> Self { - let field_rev = FieldRevision { - id: field.id, - name: field.name, - desc: field.desc, - ty: field.field_type.into(), - frozen: field.frozen, - visibility: field.visibility, - width: field.width, - type_options: IndexMap::default(), - is_primary: field.is_primary, - }; - Self { - field_rev, - type_option_builder, - } - } - - pub fn name(mut self, name: &str) -> Self { - self.field_rev.name = name.to_owned(); - self - } - - pub fn desc(mut self, desc: &str) -> Self { - self.field_rev.desc = desc.to_owned(); - self - } - - pub fn primary(mut self, is_primary: bool) -> Self { - self.field_rev.is_primary = is_primary; - self - } - - pub fn visibility(mut self, visibility: bool) -> Self { - self.field_rev.visibility = visibility; - self - } - - pub fn width(mut self, width: i32) -> Self { - self.field_rev.width = width; - self - } - - pub fn frozen(mut self, frozen: bool) -> Self { - self.field_rev.frozen = frozen; - self - } - - pub fn build(self) -> FieldRevision { - let mut field_rev = self.field_rev; - field_rev.insert_type_option(self.type_option_builder.serializer()); - field_rev - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/field_operation.rs b/frontend/rust-lib/flowy-database/src/services/field/field_operation.rs deleted file mode 100644 index 4153531077..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/field_operation.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::services::database::DatabaseEditor; -use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB}; -use database_model::{TypeOptionDataDeserializer, TypeOptionDataSerializer}; -use flowy_error::FlowyResult; -use std::sync::Arc; - -pub async fn edit_field_type_option( - view_id: &str, - field_id: &str, - editor: Arc, - action: impl FnOnce(&mut T), -) -> FlowyResult<()> -where - T: TypeOptionDataDeserializer + TypeOptionDataSerializer, -{ - let get_type_option = async { - let field_rev = editor.get_field_rev(field_id).await?; - field_rev.get_type_option::(field_rev.ty) - }; - - if let Some(mut type_option) = get_type_option.await { - let old_field_rev = editor.get_field_rev(field_id).await; - - action(&mut type_option); - let bytes = type_option.protobuf_bytes().to_vec(); - editor - .update_field_type_option(view_id, field_id, bytes, old_field_rev) - .await?; - } - - Ok(()) -} - -pub async fn edit_single_select_type_option( - view_id: &str, - field_id: &str, - editor: Arc, - action: impl FnOnce(&mut SingleSelectTypeOptionPB), -) -> FlowyResult<()> { - edit_field_type_option(view_id, field_id, editor, action).await -} - -pub async fn edit_multi_select_type_option( - view_id: &str, - field_id: &str, - editor: Arc, - action: impl FnOnce(&mut MultiSelectTypeOptionPB), -) -> FlowyResult<()> { - edit_field_type_option(view_id, field_id, editor, action).await -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/mod.rs b/frontend/rust-lib/flowy-database/src/services/field/mod.rs deleted file mode 100644 index 7ae2945e90..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod field_builder; -mod field_operation; -mod type_option_builder; -pub(crate) mod type_options; - -pub use field_builder::*; -pub use field_operation::*; -pub use type_option_builder::*; -pub use type_options::*; diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_option_builder.rs b/frontend/rust-lib/flowy-database/src/services/field/type_option_builder.rs deleted file mode 100644 index 91f927a1d2..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_option_builder.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::entities::FieldType; -use crate::services::field::type_options::*; -use bytes::Bytes; -use database_model::TypeOptionDataSerializer; - -pub trait TypeOptionBuilder { - /// Returns the type of the type-option data - fn field_type(&self) -> FieldType; - - /// Returns a serializer that can be used to serialize the type-option data - fn serializer(&self) -> &dyn TypeOptionDataSerializer; -} - -pub fn default_type_option_builder_from_type(field_type: &FieldType) -> Box { - let s: String = match field_type { - FieldType::RichText => RichTextTypeOptionPB::default().into(), - FieldType::Number => NumberTypeOptionPB::default().into(), - FieldType::DateTime => DateTypeOptionPB::default().into(), - FieldType::SingleSelect => SingleSelectTypeOptionPB::default().into(), - FieldType::MultiSelect => MultiSelectTypeOptionPB::default().into(), - FieldType::Checkbox => CheckboxTypeOptionPB::default().into(), - FieldType::URL => URLTypeOptionPB::default().into(), - FieldType::Checklist => ChecklistTypeOptionPB::default().into(), - }; - - type_option_builder_from_json_str(&s, field_type) -} - -pub fn type_option_builder_from_json_str( - s: &str, - field_type: &FieldType, -) -> Box { - match field_type { - FieldType::RichText => Box::new(RichTextTypeOptionBuilder::from_json_str(s)), - FieldType::Number => Box::new(NumberTypeOptionBuilder::from_json_str(s)), - FieldType::DateTime => Box::new(DateTypeOptionBuilder::from_json_str(s)), - FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_json_str(s)), - FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_json_str(s)), - FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_json_str(s)), - FieldType::URL => Box::new(URLTypeOptionBuilder::from_json_str(s)), - FieldType::Checklist => Box::new(ChecklistTypeOptionBuilder::from_json_str(s)), - } -} - -pub fn type_option_builder_from_bytes>( - bytes: T, - field_type: &FieldType, -) -> Box { - let bytes = bytes.into(); - match field_type { - FieldType::RichText => Box::new(RichTextTypeOptionBuilder::from_protobuf_bytes(bytes)), - FieldType::Number => Box::new(NumberTypeOptionBuilder::from_protobuf_bytes(bytes)), - FieldType::DateTime => Box::new(DateTypeOptionBuilder::from_protobuf_bytes(bytes)), - FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_protobuf_bytes(bytes)), - FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_protobuf_bytes(bytes)), - FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_protobuf_bytes(bytes)), - FieldType::URL => Box::new(URLTypeOptionBuilder::from_protobuf_bytes(bytes)), - FieldType::Checklist => Box::new(ChecklistTypeOptionBuilder::from_protobuf_bytes(bytes)), - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs deleted file mode 100644 index 4dbb991e3e..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::entities::{CheckboxFilterConditionPB, CheckboxFilterPB}; -use crate::services::field::CheckboxCellData; - -impl CheckboxFilterPB { - pub fn is_visible(&self, cell_data: &CheckboxCellData) -> bool { - let is_check = cell_data.is_check(); - match self.condition { - CheckboxFilterConditionPB::IsChecked => is_check, - CheckboxFilterConditionPB::IsUnChecked => !is_check, - } - } -} - -#[cfg(test)] -mod tests { - use crate::entities::{CheckboxFilterConditionPB, CheckboxFilterPB}; - use crate::services::field::CheckboxCellData; - use std::str::FromStr; - - #[test] - fn checkbox_filter_is_check_test() { - let checkbox_filter = CheckboxFilterPB { - condition: CheckboxFilterConditionPB::IsChecked, - }; - for (value, visible) in [ - ("true", true), - ("yes", true), - ("false", false), - ("no", false), - ] { - let data = CheckboxCellData::from_str(value).unwrap(); - assert_eq!(checkbox_filter.is_visible(&data), visible); - } - } - - #[test] - fn checkbox_filter_is_uncheck_test() { - let checkbox_filter = CheckboxFilterPB { - condition: CheckboxFilterConditionPB::IsUnChecked, - }; - for (value, visible) in [ - ("false", true), - ("no", true), - ("true", false), - ("yes", false), - ] { - let data = CheckboxCellData::from_str(value).unwrap(); - assert_eq!(checkbox_filter.is_visible(&data), visible); - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs deleted file mode 100644 index 416b7172db..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs +++ /dev/null @@ -1,49 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::entities::FieldType; - use crate::services::cell::CellDataDecoder; - use crate::services::field::type_options::checkbox_type_option::*; - use crate::services::field::FieldBuilder; - - use database_model::FieldRevision; - - #[test] - fn checkout_box_description_test() { - let type_option = CheckboxTypeOptionPB::default(); - let field_type = FieldType::Checkbox; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - - // the checkout value will be checked if the value is "1", "true" or "yes" - assert_checkbox(&type_option, "1", CHECK, &field_type, &field_rev); - assert_checkbox(&type_option, "true", CHECK, &field_type, &field_rev); - assert_checkbox(&type_option, "TRUE", CHECK, &field_type, &field_rev); - assert_checkbox(&type_option, "yes", CHECK, &field_type, &field_rev); - assert_checkbox(&type_option, "YES", CHECK, &field_type, &field_rev); - - // the checkout value will be uncheck if the value is "false" or "No" - assert_checkbox(&type_option, "false", UNCHECK, &field_type, &field_rev); - assert_checkbox(&type_option, "No", UNCHECK, &field_type, &field_rev); - assert_checkbox(&type_option, "NO", UNCHECK, &field_type, &field_rev); - assert_checkbox(&type_option, "0", UNCHECK, &field_type, &field_rev); - - // the checkout value will be empty if the value is letters or empty string - assert_checkbox(&type_option, "abc", "", &field_type, &field_rev); - assert_checkbox(&type_option, "", "", &field_type, &field_rev); - } - - fn assert_checkbox( - type_option: &CheckboxTypeOptionPB, - input_str: &str, - expected_str: &str, - field_type: &FieldType, - field_rev: &FieldRevision, - ) { - assert_eq!( - type_option - .decode_cell_str(input_str.to_owned(), field_type, field_rev) - .unwrap() - .to_string(), - expected_str.to_owned() - ); - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs deleted file mode 100644 index 3b7c239a41..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs +++ /dev/null @@ -1,156 +0,0 @@ -use crate::entities::{CheckboxFilterPB, FieldType}; -use crate::impl_type_option; -use crate::services::cell::{CellDataChangeset, CellDataDecoder, FromCellString, TypeCellData}; -use crate::services::field::{ - default_order, BoxTypeOptionBuilder, CheckboxCellData, TypeOption, TypeOptionBuilder, - TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, -}; -use bytes::Bytes; -use database_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; -use flowy_derive::ProtoBuf; -use flowy_error::FlowyResult; -use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; -use std::str::FromStr; - -#[derive(Default)] -pub struct CheckboxTypeOptionBuilder(CheckboxTypeOptionPB); -impl_into_box_type_option_builder!(CheckboxTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(CheckboxTypeOptionBuilder, CheckboxTypeOptionPB); - -impl CheckboxTypeOptionBuilder { - pub fn set_selected(mut self, is_selected: bool) -> Self { - self.0.is_selected = is_selected; - self - } -} - -impl TypeOptionBuilder for CheckboxTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::Checkbox - } - - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] -pub struct CheckboxTypeOptionPB { - #[pb(index = 1)] - pub is_selected: bool, -} -impl_type_option!(CheckboxTypeOptionPB, FieldType::Checkbox); - -impl TypeOption for CheckboxTypeOptionPB { - type CellData = CheckboxCellData; - type CellChangeset = CheckboxCellChangeset; - type CellProtobufType = CheckboxCellData; - type CellFilter = CheckboxFilterPB; -} - -impl TypeOptionTransform for CheckboxTypeOptionPB { - fn transformable(&self) -> bool { - true - } - - fn transform_type_option( - &mut self, - _old_type_option_field_type: FieldType, - _old_type_option_data: String, - ) { - } - - fn transform_type_option_cell_str( - &self, - cell_str: &str, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> Option<::CellData> { - if decoded_field_type.is_text() { - match CheckboxCellData::from_str(cell_str) { - Ok(cell_data) => Some(cell_data), - Err(_) => None, - } - } else { - None - } - } -} - -impl TypeOptionCellData for CheckboxTypeOptionPB { - fn convert_to_protobuf( - &self, - cell_data: ::CellData, - ) -> ::CellProtobufType { - cell_data - } - - fn decode_type_option_cell_str( - &self, - cell_str: String, - ) -> FlowyResult<::CellData> { - CheckboxCellData::from_cell_str(&cell_str) - } -} - -impl CellDataDecoder for CheckboxTypeOptionPB { - fn decode_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult<::CellData> { - if !decoded_field_type.is_checkbox() { - return Ok(Default::default()); - } - - self.decode_type_option_cell_str(cell_str) - } - - fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { - cell_data.to_string() - } -} - -pub type CheckboxCellChangeset = String; - -impl CellDataChangeset for CheckboxTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - _type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - let checkbox_cell_data = CheckboxCellData::from_str(&changeset)?; - Ok((checkbox_cell_data.to_string(), checkbox_cell_data)) - } -} - -impl TypeOptionCellDataFilter for CheckboxTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_checkbox() { - return true; - } - filter.is_visible(cell_data) - } -} - -impl TypeOptionCellDataCompare for CheckboxTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - match (cell_data.is_check(), other_cell_data.is_check()) { - (true, true) => Ordering::Equal, - (true, false) => Ordering::Greater, - (false, true) => Ordering::Less, - (false, false) => default_order(), - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs deleted file mode 100644 index d7dd7871ec..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::services::cell::{CellProtobufBlobParser, DecodedCellData, FromCellString}; -use bytes::Bytes; -use flowy_error::{FlowyError, FlowyResult}; -use protobuf::ProtobufError; -use std::str::FromStr; - -pub const CHECK: &str = "Yes"; -pub const UNCHECK: &str = "No"; - -#[derive(Default, Debug, Clone)] -pub struct CheckboxCellData(String); - -impl CheckboxCellData { - pub fn into_inner(self) -> bool { - self.is_check() - } - - pub fn is_check(&self) -> bool { - self.0 == CHECK - } - - pub fn is_uncheck(&self) -> bool { - self.0 == UNCHECK - } -} - -impl AsRef<[u8]> for CheckboxCellData { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl FromStr for CheckboxCellData { - type Err = FlowyError; - - fn from_str(s: &str) -> Result { - let lower_case_str: &str = &s.to_lowercase(); - let val = match lower_case_str { - "1" => Some(true), - "true" => Some(true), - "yes" => Some(true), - "0" => Some(false), - "false" => Some(false), - "no" => Some(false), - _ => None, - }; - - match val { - Some(true) => Ok(Self(CHECK.to_string())), - Some(false) => Ok(Self(UNCHECK.to_string())), - None => Ok(Self("".to_string())), - } - } -} - -impl std::convert::TryFrom for Bytes { - type Error = ProtobufError; - - fn try_from(value: CheckboxCellData) -> Result { - Ok(Bytes::from(value.0)) - } -} - -impl FromCellString for CheckboxCellData { - fn from_cell_str(s: &str) -> FlowyResult - where - Self: Sized, - { - Self::from_str(s) - } -} - -impl ToString for CheckboxCellData { - fn to_string(&self) -> String { - self.0.clone() - } -} - -impl DecodedCellData for CheckboxCellData { - type Object = CheckboxCellData; - - fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -pub struct CheckboxCellDataParser(); -impl CellProtobufBlobParser for CheckboxCellDataParser { - type Object = CheckboxCellData; - fn parser(bytes: &Bytes) -> FlowyResult { - match String::from_utf8(bytes.to_vec()) { - Ok(s) => CheckboxCellData::from_cell_str(&s), - Err(_) => Ok(CheckboxCellData("".to_string())), - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/mod.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/mod.rs deleted file mode 100644 index 309072caa6..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/checkbox_type_option/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![allow(clippy::module_inception)] -mod checkbox_filter; -mod checkbox_tests; -mod checkbox_type_option; -mod checkbox_type_option_entities; - -pub use checkbox_type_option::*; -pub use checkbox_type_option_entities::*; diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_filter.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_filter.rs deleted file mode 100644 index 35f30b388e..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_filter.rs +++ /dev/null @@ -1,149 +0,0 @@ -use crate::entities::{DateFilterConditionPB, DateFilterPB}; -use chrono::NaiveDateTime; - -impl DateFilterPB { - pub fn is_visible>>(&self, cell_timestamp: T) -> bool { - match cell_timestamp.into() { - None => DateFilterConditionPB::DateIsEmpty == self.condition, - Some(timestamp) => { - match self.condition { - DateFilterConditionPB::DateIsNotEmpty => { - return true; - }, - DateFilterConditionPB::DateIsEmpty => { - return false; - }, - _ => {}, - } - - let cell_time = NaiveDateTime::from_timestamp_opt(timestamp, 0); - let cell_date = cell_time.map(|time| time.date()); - match self.timestamp { - None => { - if self.start.is_none() { - return true; - } - - if self.end.is_none() { - return true; - } - - let start_time = NaiveDateTime::from_timestamp_opt(*self.start.as_ref().unwrap(), 0); - let start_date = start_time.map(|time| time.date()); - - let end_time = NaiveDateTime::from_timestamp_opt(*self.end.as_ref().unwrap(), 0); - let end_date = end_time.map(|time| time.date()); - - cell_date >= start_date && cell_date <= end_date - }, - Some(timestamp) => { - let expected_timestamp = NaiveDateTime::from_timestamp_opt(timestamp, 0); - let expected_date = expected_timestamp.map(|time| time.date()); - - // We assume that the cell_timestamp doesn't contain hours, just day. - match self.condition { - DateFilterConditionPB::DateIs => cell_date == expected_date, - DateFilterConditionPB::DateBefore => cell_date < expected_date, - DateFilterConditionPB::DateAfter => cell_date > expected_date, - DateFilterConditionPB::DateOnOrBefore => cell_date <= expected_date, - DateFilterConditionPB::DateOnOrAfter => cell_date >= expected_date, - _ => true, - } - }, - } - }, - } - } -} - -#[cfg(test)] -mod tests { - #![allow(clippy::all)] - use crate::entities::{DateFilterConditionPB, DateFilterPB}; - - #[test] - fn date_filter_is_test() { - let filter = DateFilterPB { - condition: DateFilterConditionPB::DateIs, - timestamp: Some(1668387885), - end: None, - start: None, - }; - - for (val, visible) in vec![(1668387885, true), (1647251762, false)] { - assert_eq!(filter.is_visible(val as i64), visible); - } - } - #[test] - fn date_filter_before_test() { - let filter = DateFilterPB { - condition: DateFilterConditionPB::DateBefore, - timestamp: Some(1668387885), - start: None, - end: None, - }; - - for (val, visible, msg) in vec![(1668387884, false, "1"), (1647251762, true, "2")] { - assert_eq!(filter.is_visible(val as i64), visible, "{}", msg); - } - } - - #[test] - fn date_filter_before_or_on_test() { - let filter = DateFilterPB { - condition: DateFilterConditionPB::DateOnOrBefore, - timestamp: Some(1668387885), - start: None, - end: None, - }; - - for (val, visible) in vec![(1668387884, true), (1668387885, true)] { - assert_eq!(filter.is_visible(val as i64), visible); - } - } - #[test] - fn date_filter_after_test() { - let filter = DateFilterPB { - condition: DateFilterConditionPB::DateAfter, - timestamp: Some(1668387885), - start: None, - end: None, - }; - - for (val, visible) in vec![(1668387888, false), (1668531885, true), (0, false)] { - assert_eq!(filter.is_visible(val as i64), visible); - } - } - - #[test] - fn date_filter_within_test() { - let filter = DateFilterPB { - condition: DateFilterConditionPB::DateWithIn, - start: Some(1668272685), // 11/13 - end: Some(1668618285), // 11/17 - timestamp: None, - }; - - for (val, visible, _msg) in vec![ - (1668272685, true, "11/13"), - (1668359085, true, "11/14"), - (1668704685, false, "11/18"), - ] { - assert_eq!(filter.is_visible(val as i64), visible); - } - } - - #[test] - fn date_filter_is_empty_test() { - let filter = DateFilterPB { - condition: DateFilterConditionPB::DateIsEmpty, - start: None, - end: None, - timestamp: None, - }; - - for (val, visible) in vec![(None, true), (Some(123), false)] { - assert_eq!(filter.is_visible(val), visible); - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_tests.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_tests.rs deleted file mode 100644 index 2a4ca70333..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_tests.rs +++ /dev/null @@ -1,295 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::entities::FieldType; - use crate::services::cell::{CellDataChangeset, CellDataDecoder}; - - use crate::services::field::{ - DateCellChangeset, DateFormat, DateTypeOptionPB, FieldBuilder, TimeFormat, TypeOptionCellData, - }; - use chrono::format::strftime::StrftimeItems; - use chrono::{FixedOffset, NaiveDateTime}; - use database_model::FieldRevision; - use strum::IntoEnumIterator; - - #[test] - fn date_type_option_date_format_test() { - let mut type_option = DateTypeOptionPB::default(); - let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - for date_format in DateFormat::iter() { - type_option.date_format = date_format; - match date_format { - DateFormat::Friendly => { - assert_date( - &type_option, - 1647251762, - None, - "Mar 14, 2022", - false, - &field_rev, - ); - }, - DateFormat::US => { - assert_date( - &type_option, - 1647251762, - None, - "2022/03/14", - false, - &field_rev, - ); - }, - DateFormat::ISO => { - assert_date( - &type_option, - 1647251762, - None, - "2022-03-14", - false, - &field_rev, - ); - }, - DateFormat::Local => { - assert_date( - &type_option, - 1647251762, - None, - "03/14/2022", - false, - &field_rev, - ); - }, - DateFormat::DayMonthYear => { - assert_date( - &type_option, - 1647251762, - None, - "14/03/2022", - false, - &field_rev, - ); - }, - } - } - } - - // #[test] - // fn date_type_option_different_time_format_test() { - // let mut type_option = DateTypeOptionPB::default(); - // let field_type = FieldType::DateTime; - // let field_rev = FieldBuilder::from_field_type(&field_type).build(); - // - // for time_format in TimeFormat::iter() { - // type_option.time_format = time_format; - // match time_format { - // TimeFormat::TwentyFourHour => { - // assert_date( - // &type_option, - // 1653609600, - // None, - // "May 27, 2022 00:00", - // true, - // &field_rev, - // ); - // assert_date( - // &type_option, - // 1653609600, - // Some("9:00".to_owned()), - // "May 27, 2022 09:00", - // true, - // &field_rev, - // ); - // assert_date( - // &type_option, - // 1653609600, - // Some("23:00".to_owned()), - // "May 27, 2022 23:00", - // true, - // &field_rev, - // ); - // }, - // TimeFormat::TwelveHour => { - // assert_date( - // &type_option, - // 1653609600, - // None, - // "May 27, 2022 12:00 AM", - // true, - // &field_rev, - // ); - // assert_date( - // &type_option, - // 1653609600, - // Some("9:00 AM".to_owned()), - // "May 27, 2022 09:00 AM", - // true, - // &field_rev, - // ); - // assert_date( - // &type_option, - // 1653609600, - // Some("11:23 pm".to_owned()), - // "May 27, 2022 11:23 PM", - // true, - // &field_rev, - // ); - // }, - // } - // } - // } - - #[test] - fn date_type_option_invalid_date_str_test() { - let type_option = DateTypeOptionPB::default(); - let field_type = FieldType::DateTime; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_date(&type_option, "abc", None, "", false, &field_rev); - } - - #[test] - #[should_panic] - fn date_type_option_invalid_include_time_str_test() { - let type_option = DateTypeOptionPB::new(); - let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - - assert_date( - &type_option, - 1653609600, - Some("1:".to_owned()), - "May 27, 2022 01:00", - true, - &field_rev, - ); - } - - // #[test] - // fn date_type_option_empty_include_time_str_test() { - // let type_option = DateTypeOptionPB::new(); - // let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - // - // assert_date( - // &type_option, - // 1653609600, - // Some("".to_owned()), - // "May 27, 2022 00:00", - // true, - // &field_rev, - // ); - // } - - #[test] - fn date_type_midnight_include_time_str_test() { - let type_option = DateTypeOptionPB::new(); - let field_type = FieldType::DateTime; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_date( - &type_option, - 1653609600, - Some("00:00".to_owned()), - "May 27, 2022 00:00", - true, - &field_rev, - ); - } - - /// The default time format is TwentyFourHour, so the include_time_str in twelve_hours_format will cause parser error. - #[test] - #[should_panic] - fn date_type_option_twelve_hours_include_time_str_in_twenty_four_hours_format() { - let type_option = DateTypeOptionPB::new(); - let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - - assert_date( - &type_option, - 1653609600, - Some("1:00 am".to_owned()), - "May 27, 2022 01:00 AM", - true, - &field_rev, - ); - } - - // Attempting to parse include_time_str as TwelveHour when TwentyFourHour format is given should cause parser error. - #[test] - #[should_panic] - fn date_type_option_twenty_four_hours_include_time_str_in_twelve_hours_format() { - let mut type_option = DateTypeOptionPB::new(); - type_option.time_format = TimeFormat::TwelveHour; - let field_rev = FieldBuilder::from_field_type(&FieldType::DateTime).build(); - - assert_date( - &type_option, - 1653609600, - Some("20:00".to_owned()), - "May 27, 2022 08:00 PM", - true, - &field_rev, - ); - } - - #[test] - fn utc_to_native_test() { - let native_timestamp = 1647251762; - let native = NaiveDateTime::from_timestamp_opt(native_timestamp, 0).unwrap(); - - let utc = chrono::DateTime::::from_utc(native, chrono::Utc); - // utc_timestamp doesn't carry timezone - let utc_timestamp = utc.timestamp(); - assert_eq!(native_timestamp, utc_timestamp); - - let format = "%m/%d/%Y %I:%M %p".to_string(); - let native_time_str = format!("{}", native.format_with_items(StrftimeItems::new(&format))); - let utc_time_str = format!("{}", utc.format_with_items(StrftimeItems::new(&format))); - assert_eq!(native_time_str, utc_time_str); - - // Mon Mar 14 2022 17:56:02 GMT+0800 (China Standard Time) - let gmt_8_offset = FixedOffset::east_opt(8 * 3600).unwrap(); - let china_local = chrono::DateTime::::from_utc(native, gmt_8_offset); - let china_local_time = format!( - "{}", - china_local.format_with_items(StrftimeItems::new(&format)) - ); - - assert_eq!(china_local_time, "03/14/2022 05:56 PM"); - } - - fn assert_date( - type_option: &DateTypeOptionPB, - timestamp: T, - include_time_str: Option, - expected_str: &str, - include_time: bool, - field_rev: &FieldRevision, - ) { - let changeset = DateCellChangeset { - date: Some(timestamp.to_string()), - time: include_time_str, - is_utc: false, - include_time: Some(include_time), - }; - let (cell_str, _) = type_option.apply_changeset(changeset, None).unwrap(); - - assert_eq!( - decode_cell_data(cell_str, type_option, include_time, field_rev), - expected_str.to_owned(), - ); - } - - fn decode_cell_data( - cell_str: String, - type_option: &DateTypeOptionPB, - include_time: bool, - field_rev: &FieldRevision, - ) -> String { - let decoded_data = type_option - .decode_cell_str(cell_str, &FieldType::DateTime, field_rev) - .unwrap(); - let decoded_data = type_option.convert_to_protobuf(decoded_data); - if include_time { - format!("{} {}", decoded_data.date, decoded_data.time) - .trim_end() - .to_owned() - } else { - decoded_data.date - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option.rs deleted file mode 100644 index d38cf3247f..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option.rs +++ /dev/null @@ -1,246 +0,0 @@ -use crate::entities::{DateFilterPB, FieldType}; -use crate::impl_type_option; -use crate::services::cell::{CellDataChangeset, CellDataDecoder, FromCellString, TypeCellData}; -use crate::services::field::{ - default_order, BoxTypeOptionBuilder, DateCellChangeset, DateCellData, DateCellDataPB, DateFormat, - TimeFormat, TypeOption, TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare, - TypeOptionCellDataFilter, TypeOptionTransform, -}; -use bytes::Bytes; -use chrono::format::strftime::StrftimeItems; -use chrono::{Local, NaiveDateTime}; -use database_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; -use flowy_derive::ProtoBuf; -use flowy_error::{ErrorCode, FlowyError, FlowyResult}; -use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; - -// Date -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] -pub struct DateTypeOptionPB { - #[pb(index = 1)] - pub date_format: DateFormat, - - #[pb(index = 2)] - pub time_format: TimeFormat, - - #[pb(index = 3)] - pub include_time: bool, -} -impl_type_option!(DateTypeOptionPB, FieldType::DateTime); - -impl TypeOption for DateTypeOptionPB { - type CellData = DateCellData; - type CellChangeset = DateCellChangeset; - type CellProtobufType = DateCellDataPB; - type CellFilter = DateFilterPB; -} - -impl TypeOptionCellData for DateTypeOptionPB { - fn convert_to_protobuf( - &self, - cell_data: ::CellData, - ) -> ::CellProtobufType { - self.today_desc_from_timestamp(cell_data) - } - - fn decode_type_option_cell_str( - &self, - cell_str: String, - ) -> FlowyResult<::CellData> { - DateCellData::from_cell_str(&cell_str) - } -} - -impl DateTypeOptionPB { - #[allow(dead_code)] - pub fn new() -> Self { - Self::default() - } - - fn today_desc_from_timestamp(&self, cell_data: DateCellData) -> DateCellDataPB { - let timestamp = cell_data.timestamp.unwrap_or_default(); - if timestamp == 0 { - return DateCellDataPB::default(); - } - - let include_time = cell_data.include_time; - let native = chrono::NaiveDateTime::from_timestamp_opt(timestamp, 0); - if native.is_none() { - return DateCellDataPB::default(); - } - - // Use the local timezone to calculate the formatted date string. We can use the timezone that - // specified by the user in the future. - let offset = Local::now().offset().clone(); - let native = chrono::DateTime::::from_utc(native.unwrap(), offset); - let fmt = self.date_format.format_str(); - let date = format!("{}", native.format_with_items(StrftimeItems::new(fmt))); - - let time = if include_time { - let fmt = self.time_format.format_str(); - format!("{}", native.format_with_items(StrftimeItems::new(fmt))) - } else { - "".to_string() - }; - - DateCellDataPB { - date, - time, - include_time, - timestamp, - } - } - - fn timestamp_from_utc_with_time( - &self, - naive_date: NaiveDateTime, - time_str: &Option, - ) -> FlowyResult { - if let Some(time_str) = time_str.as_ref() { - if !time_str.is_empty() { - let offset = Local::now().offset().clone(); - let naive_time = chrono::NaiveTime::parse_from_str(time_str, self.time_format.format_str()); - - return match naive_time { - Ok(naive_time) => { - let naive = chrono::DateTime::::from_utc(naive_date, offset) - .date_naive() - .and_time(naive_time); - let local = chrono::DateTime::::from_local(naive, offset); - Ok(local.timestamp()) - }, - Err(_e) => { - let msg = format!("Parse {} failed", time_str); - Err(FlowyError::new(ErrorCode::InvalidDateTimeFormat, &msg)) - }, - }; - } - } - - Ok(naive_date.timestamp()) - } -} - -impl TypeOptionTransform for DateTypeOptionPB {} - -impl CellDataDecoder for DateTypeOptionPB { - fn decode_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult<::CellData> { - // Return default data if the type_option_cell_data is not FieldType::DateTime. - // It happens when switching from one field to another. - // For example: - // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen. - if !decoded_field_type.is_date() { - return Ok(Default::default()); - } - - self.decode_type_option_cell_str(cell_str) - } - - fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { - self.today_desc_from_timestamp(cell_data).date - } -} - -impl CellDataChangeset for DateTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - let (timestamp, include_time) = match type_cell_data { - None => (None, false), - Some(type_cell_data) => { - let cell_data = DateCellData::from_cell_str(&type_cell_data.cell_str).unwrap_or_default(); - (cell_data.timestamp, cell_data.include_time) - }, - }; - - let include_time = match changeset.include_time { - None => include_time, - Some(include_time) => include_time, - }; - let timestamp = match changeset.date_timestamp() { - None => timestamp, - Some(date_timestamp) => match (include_time, changeset.time) { - (true, Some(time)) => { - let time = Some(time.trim().to_uppercase()); - let naive = NaiveDateTime::from_timestamp_opt(date_timestamp, 0); - if let Some(naive) = naive { - Some(self.timestamp_from_utc_with_time(naive, &time)?) - } else { - Some(date_timestamp) - } - }, - _ => Some(date_timestamp), - }, - }; - - let date_cell_data = DateCellData { - timestamp, - include_time, - }; - Ok((date_cell_data.to_string(), date_cell_data)) - } -} - -impl TypeOptionCellDataFilter for DateTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_date() { - return true; - } - - filter.is_visible(cell_data.timestamp) - } -} - -impl TypeOptionCellDataCompare for DateTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - match (cell_data.timestamp, other_cell_data.timestamp) { - (Some(left), Some(right)) => left.cmp(&right), - (Some(_), None) => Ordering::Greater, - (None, Some(_)) => Ordering::Less, - (None, None) => default_order(), - } - } -} - -#[derive(Default)] -pub struct DateTypeOptionBuilder(DateTypeOptionPB); -impl_into_box_type_option_builder!(DateTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(DateTypeOptionBuilder, DateTypeOptionPB); - -impl DateTypeOptionBuilder { - pub fn date_format(mut self, date_format: DateFormat) -> Self { - self.0.date_format = date_format; - self - } - - pub fn time_format(mut self, time_format: TimeFormat) -> Self { - self.0.time_format = time_format; - self - } -} -impl TypeOptionBuilder for DateTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::DateTime - } - - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option_entities.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option_entities.rs deleted file mode 100644 index 1561252f87..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/date_type_option_entities.rs +++ /dev/null @@ -1,272 +0,0 @@ -use std::fmt; - -use crate::entities::CellIdPB; -use crate::services::cell::{ - CellProtobufBlobParser, DecodedCellData, FromCellChangesetString, FromCellString, - ToCellChangesetString, -}; -use bytes::Bytes; -use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_error::{internal_error, FlowyResult}; -use serde::de::Visitor; -use serde::{Deserialize, Serialize}; -use strum_macros::EnumIter; - -#[derive(Clone, Debug, Default, ProtoBuf)] -pub struct DateCellDataPB { - #[pb(index = 1)] - pub date: String, - - #[pb(index = 2)] - pub time: String, - - #[pb(index = 3)] - pub timestamp: i64, - - #[pb(index = 4)] - pub include_time: bool, -} - -#[derive(Clone, Debug, Default, ProtoBuf)] -pub struct DateChangesetPB { - #[pb(index = 1)] - pub cell_path: CellIdPB, - - #[pb(index = 2, one_of)] - pub date: Option, - - #[pb(index = 3, one_of)] - pub time: Option, - - #[pb(index = 4, one_of)] - pub include_time: Option, - - #[pb(index = 5)] - pub is_utc: bool, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DateCellChangeset { - pub date: Option, - pub time: Option, - pub include_time: Option, - pub is_utc: bool, -} - -impl DateCellChangeset { - pub fn date_timestamp(&self) -> Option { - if let Some(date) = &self.date { - match date.parse::() { - Ok(date_timestamp) => Some(date_timestamp), - Err(_) => None, - } - } else { - None - } - } -} - -impl FromCellChangesetString for DateCellChangeset { - fn from_changeset(changeset: String) -> FlowyResult - where - Self: Sized, - { - serde_json::from_str::(&changeset).map_err(internal_error) - } -} - -impl ToCellChangesetString for DateCellChangeset { - fn to_cell_changeset_str(&self) -> String { - serde_json::to_string(self).unwrap_or_default() - } -} - -#[derive(Default, Clone, Debug, Serialize)] -pub struct DateCellData { - pub timestamp: Option, - pub include_time: bool, -} - -impl<'de> serde::Deserialize<'de> for DateCellData { - fn deserialize(deserializer: D) -> core::result::Result - where - D: serde::Deserializer<'de>, - { - struct DateCellVisitor(); - - impl<'de> Visitor<'de> for DateCellVisitor { - type Value = DateCellData; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str( - "DateCellData with type: str containing either an integer timestamp or the JSON representation", - ) - } - - fn visit_i64(self, value: i64) -> Result - where - E: serde::de::Error, - { - Ok(DateCellData { - timestamp: Some(value), - include_time: false, - }) - } - - fn visit_u64(self, value: u64) -> Result - where - E: serde::de::Error, - { - self.visit_i64(value as i64) - } - - fn visit_map(self, mut map: M) -> Result - where - M: serde::de::MapAccess<'de>, - { - let mut timestamp: Option = None; - let mut include_time: Option = None; - - while let Some(key) = map.next_key()? { - match key { - "timestamp" => { - timestamp = map.next_value()?; - }, - "include_time" => { - include_time = map.next_value()?; - }, - _ => {}, - } - } - - let include_time = include_time.unwrap_or(false); - - Ok(DateCellData { - timestamp, - include_time, - }) - } - } - - deserializer.deserialize_any(DateCellVisitor()) - } -} - -impl FromCellString for DateCellData { - fn from_cell_str(s: &str) -> FlowyResult - where - Self: Sized, - { - Ok(serde_json::from_str::(s).unwrap_or_default()) - } -} - -impl ToString for DateCellData { - fn to_string(&self) -> String { - serde_json::to_string(self).unwrap() - } -} - -#[derive(Clone, Debug, Copy, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)] -pub enum DateFormat { - Local = 0, - US = 1, - ISO = 2, - Friendly = 3, - DayMonthYear = 4, -} -impl std::default::Default for DateFormat { - fn default() -> Self { - DateFormat::Friendly - } -} - -impl std::convert::From for DateFormat { - fn from(value: i32) -> Self { - match value { - 0 => DateFormat::Local, - 1 => DateFormat::US, - 2 => DateFormat::ISO, - 3 => DateFormat::Friendly, - 4 => DateFormat::DayMonthYear, - _ => { - tracing::error!("Unsupported date format, fallback to friendly"); - DateFormat::Friendly - }, - } - } -} - -impl DateFormat { - pub fn value(&self) -> i32 { - *self as i32 - } - // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html - pub fn format_str(&self) -> &'static str { - match self { - DateFormat::Local => "%m/%d/%Y", - DateFormat::US => "%Y/%m/%d", - DateFormat::ISO => "%Y-%m-%d", - DateFormat::Friendly => "%b %d, %Y", - DateFormat::DayMonthYear => "%d/%m/%Y", - } - } -} - -#[derive( - Clone, Copy, PartialEq, Eq, EnumIter, Debug, Hash, Serialize, Deserialize, ProtoBuf_Enum, -)] -pub enum TimeFormat { - TwelveHour = 0, - TwentyFourHour = 1, -} - -impl std::convert::From for TimeFormat { - fn from(value: i32) -> Self { - match value { - 0 => TimeFormat::TwelveHour, - 1 => TimeFormat::TwentyFourHour, - _ => { - tracing::error!("Unsupported time format, fallback to TwentyFourHour"); - TimeFormat::TwentyFourHour - }, - } - } -} - -impl TimeFormat { - pub fn value(&self) -> i32 { - *self as i32 - } - - // https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html - pub fn format_str(&self) -> &'static str { - match self { - TimeFormat::TwelveHour => "%I:%M %p", - TimeFormat::TwentyFourHour => "%R", - } - } -} - -impl std::default::Default for TimeFormat { - fn default() -> Self { - TimeFormat::TwentyFourHour - } -} - -impl DecodedCellData for DateCellDataPB { - type Object = DateCellDataPB; - - fn is_empty(&self) -> bool { - self.date.is_empty() - } -} - -pub struct DateCellDataParser(); -impl CellProtobufBlobParser for DateCellDataParser { - type Object = DateCellDataPB; - - fn parser(bytes: &Bytes) -> FlowyResult { - DateCellDataPB::try_from(bytes.as_ref()).map_err(internal_error) - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/mod.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/mod.rs deleted file mode 100644 index ff0c344957..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/date_type_option/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![allow(clippy::module_inception)] -mod date_filter; -mod date_tests; -mod date_type_option; -mod date_type_option_entities; - -pub use date_type_option::*; -pub use date_type_option_entities::*; diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/mod.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/mod.rs deleted file mode 100644 index 822ffab987..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub mod checkbox_type_option; -pub mod date_type_option; -pub mod number_type_option; -pub mod selection_type_option; -pub mod text_type_option; -mod type_option; -mod type_option_cell; -pub mod url_type_option; - -pub use checkbox_type_option::*; -pub use date_type_option::*; -pub use number_type_option::*; -pub use selection_type_option::*; -pub use text_type_option::*; -pub use type_option::*; -pub use type_option_cell::*; -pub use url_type_option::*; diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/format.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/format.rs deleted file mode 100644 index 00d0c7ad8e..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/format.rs +++ /dev/null @@ -1,454 +0,0 @@ -use flowy_derive::ProtoBuf_Enum; -use lazy_static::lazy_static; - -use rusty_money::define_currency_set; -use serde::{Deserialize, Serialize}; -use strum::IntoEnumIterator; -use strum_macros::EnumIter; - -lazy_static! { - pub static ref CURRENCY_SYMBOL: Vec = NumberFormat::iter() - .map(|format| format.symbol()) - .collect::>(); - pub static ref STRIP_SYMBOL: Vec = vec![",".to_owned(), ".".to_owned()]; -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)] -pub enum NumberFormat { - Num = 0, - USD = 1, - CanadianDollar = 2, - EUR = 4, - Pound = 5, - Yen = 6, - Ruble = 7, - Rupee = 8, - Won = 9, - Yuan = 10, - Real = 11, - Lira = 12, - Rupiah = 13, - Franc = 14, - HongKongDollar = 15, - NewZealandDollar = 16, - Krona = 17, - NorwegianKrone = 18, - MexicanPeso = 19, - Rand = 20, - NewTaiwanDollar = 21, - DanishKrone = 22, - Baht = 23, - Forint = 24, - Koruna = 25, - Shekel = 26, - ChileanPeso = 27, - PhilippinePeso = 28, - Dirham = 29, - ColombianPeso = 30, - Riyal = 31, - Ringgit = 32, - Leu = 33, - ArgentinePeso = 34, - UruguayanPeso = 35, - Percent = 36, -} - -impl std::default::Default for NumberFormat { - fn default() -> Self { - NumberFormat::Num - } -} - -define_currency_set!( - number_currency { - NUMBER : { - code: "", - exponent: 2, - locale: EnEu, - minor_units: 1, - name: "number", - symbol: "RUB", - symbol_first: false, - }, - PERCENT : { - code: "", - exponent: 2, - locale: EnIn, - minor_units: 1, - name: "percent", - symbol: "%", - symbol_first: false, - }, - USD : { - code: "USD", - exponent: 2, - locale: EnUs, - minor_units: 1, - name: "United States Dollar", - symbol: "$", - symbol_first: true, - }, - CANADIAN_DOLLAR : { - code: "USD", - exponent: 2, - locale: EnUs, - minor_units: 1, - name: "Canadian Dollar", - symbol: "CA$", - symbol_first: true, - }, - NEW_TAIWAN_DOLLAR : { - code: "USD", - exponent: 2, - locale: EnUs, - minor_units: 1, - name: "NewTaiwan Dollar", - symbol: "NT$", - symbol_first: true, - }, - HONG_KONG_DOLLAR : { - code: "USD", - exponent: 2, - locale: EnUs, - minor_units: 1, - name: "HongKong Dollar", - symbol: "HZ$", - symbol_first: true, - }, - NEW_ZEALAND_DOLLAR : { - code: "USD", - exponent: 2, - locale: EnUs, - minor_units: 1, - name: "NewZealand Dollar", - symbol: "NZ$", - symbol_first: true, - }, - EUR : { - code: "EUR", - exponent: 2, - locale: EnEu, - minor_units: 1, - name: "Euro", - symbol: "€", - symbol_first: true, - }, - GIP : { - code: "GIP", - exponent: 2, - locale: EnUs, - minor_units: 1, - name: "Gibraltar Pound", - symbol: "£", - symbol_first: true, - }, - CNY : { - code: "CNY", - exponent: 2, - locale: EnUs, - minor_units: 1, - name: "Chinese Renminbi Yuan", - symbol: "¥", - symbol_first: true, - }, - YUAN : { - code: "CNY", - exponent: 2, - locale: EnUs, - minor_units: 1, - name: "Chinese Renminbi Yuan", - symbol: "CN¥", - symbol_first: true, - }, - RUB : { - code: "RUB", - exponent: 2, - locale: EnEu, - minor_units: 1, - name: "Russian Ruble", - symbol: "RUB", - symbol_first: false, - }, - INR : { - code: "INR", - exponent: 2, - locale: EnIn, - minor_units: 50, - name: "Indian Rupee", - symbol: "₹", - symbol_first: true, - }, - KRW : { - code: "KRW", - exponent: 0, - locale: EnUs, - minor_units: 1, - name: "South Korean Won", - symbol: "₩", - symbol_first: true, - }, - BRL : { - code: "BRL", - exponent: 2, - locale: EnUs, - minor_units: 5, - name: "Brazilian real", - symbol: "R$", - symbol_first: true, - }, - TRY : { - code: "TRY", - exponent: 2, - locale: EnEu, - minor_units: 1, - name: "Turkish Lira", - // symbol: "₺", - symbol: "TRY", - symbol_first: true, - }, - IDR : { - code: "IDR", - exponent: 2, - locale: EnUs, - minor_units: 5000, - name: "Indonesian Rupiah", - // symbol: "Rp", - symbol: "IDR", - symbol_first: true, - }, - CHF : { - code: "CHF", - exponent: 2, - locale: EnUs, - minor_units: 5, - name: "Swiss Franc", - // symbol: "Fr", - symbol: "CHF", - symbol_first: true, - }, - SEK : { - code: "SEK", - exponent: 2, - locale: EnBy, - minor_units: 100, - name: "Swedish Krona", - // symbol: "kr", - symbol: "SEK", - symbol_first: false, - }, - NOK : { - code: "NOK", - exponent: 2, - locale: EnUs, - minor_units: 100, - name: "Norwegian Krone", - // symbol: "kr", - symbol: "NOK", - symbol_first: false, - }, - MEXICAN_PESO : { - code: "USD", - exponent: 2, - locale: EnUs, - minor_units: 1, - name: "Mexican Peso", - symbol: "MX$", - symbol_first: true, - }, - ZAR : { - code: "ZAR", - exponent: 2, - locale: EnUs, - minor_units: 10, - name: "South African Rand", - // symbol: "R", - symbol: "ZAR", - symbol_first: true, - }, - DKK : { - code: "DKK", - exponent: 2, - locale: EnEu, - minor_units: 50, - name: "Danish Krone", - // symbol: "kr.", - symbol: "DKK", - symbol_first: false, - }, - THB : { - code: "THB", - exponent: 2, - locale: EnUs, - minor_units: 1, - name: "Thai Baht", - // symbol: "฿", - symbol: "THB", - symbol_first: true, - }, - HUF : { - code: "HUF", - exponent: 0, - locale: EnBy, - minor_units: 5, - name: "Hungarian Forint", - // symbol: "Ft", - symbol: "HUF", - symbol_first: false, - }, - KORUNA : { - code: "CZK", - exponent: 2, - locale: EnBy, - minor_units: 100, - name: "Czech Koruna", - // symbol: "Kč", - symbol: "CZK", - symbol_first: false, - }, - SHEKEL : { - code: "CZK", - exponent: 2, - locale: EnBy, - minor_units: 100, - name: "Czech Koruna", - symbol: "Kč", - symbol_first: false, - }, - CLP : { - code: "CLP", - exponent: 0, - locale: EnEu, - minor_units: 1, - name: "Chilean Peso", - // symbol: "$", - symbol: "CLP", - symbol_first: true, - }, - PHP : { - code: "PHP", - exponent: 2, - locale: EnUs, - minor_units: 1, - name: "Philippine Peso", - symbol: "₱", - symbol_first: true, - }, - AED : { - code: "AED", - exponent: 2, - locale: EnUs, - minor_units: 25, - name: "United Arab Emirates Dirham", - // symbol: "د.إ", - symbol: "AED", - symbol_first: false, - }, - COP : { - code: "COP", - exponent: 2, - locale: EnEu, - minor_units: 20, - name: "Colombian Peso", - // symbol: "$", - symbol: "COP", - symbol_first: true, - }, - SAR : { - code: "SAR", - exponent: 2, - locale: EnUs, - minor_units: 5, - name: "Saudi Riyal", - // symbol: "ر.س", - symbol: "SAR", - symbol_first: true, - }, - MYR : { - code: "MYR", - exponent: 2, - locale: EnUs, - minor_units: 5, - name: "Malaysian Ringgit", - // symbol: "RM", - symbol: "MYR", - symbol_first: true, - }, - RON : { - code: "RON", - exponent: 2, - locale: EnEu, - minor_units: 1, - name: "Romanian Leu", - // symbol: "ر.ق", - symbol: "RON", - symbol_first: false, - }, - ARS : { - code: "ARS", - exponent: 2, - locale: EnEu, - minor_units: 1, - name: "Argentine Peso", - // symbol: "$", - symbol: "ARS", - symbol_first: true, - }, - UYU : { - code: "UYU", - exponent: 2, - locale: EnEu, - minor_units: 100, - name: "Uruguayan Peso", - // symbol: "$U", - symbol: "UYU", - symbol_first: true, - } - } -); - -impl NumberFormat { - pub fn currency(&self) -> &'static number_currency::Currency { - match self { - NumberFormat::Num => number_currency::NUMBER, - NumberFormat::USD => number_currency::USD, - NumberFormat::CanadianDollar => number_currency::CANADIAN_DOLLAR, - NumberFormat::EUR => number_currency::EUR, - NumberFormat::Pound => number_currency::GIP, - NumberFormat::Yen => number_currency::CNY, - NumberFormat::Ruble => number_currency::RUB, - NumberFormat::Rupee => number_currency::INR, - NumberFormat::Won => number_currency::KRW, - NumberFormat::Yuan => number_currency::YUAN, - NumberFormat::Real => number_currency::BRL, - NumberFormat::Lira => number_currency::TRY, - NumberFormat::Rupiah => number_currency::IDR, - NumberFormat::Franc => number_currency::CHF, - NumberFormat::HongKongDollar => number_currency::HONG_KONG_DOLLAR, - NumberFormat::NewZealandDollar => number_currency::NEW_ZEALAND_DOLLAR, - NumberFormat::Krona => number_currency::SEK, - NumberFormat::NorwegianKrone => number_currency::NOK, - NumberFormat::MexicanPeso => number_currency::MEXICAN_PESO, - NumberFormat::Rand => number_currency::ZAR, - NumberFormat::NewTaiwanDollar => number_currency::NEW_TAIWAN_DOLLAR, - NumberFormat::DanishKrone => number_currency::DKK, - NumberFormat::Baht => number_currency::THB, - NumberFormat::Forint => number_currency::HUF, - NumberFormat::Koruna => number_currency::KORUNA, - NumberFormat::Shekel => number_currency::SHEKEL, - NumberFormat::ChileanPeso => number_currency::CLP, - NumberFormat::PhilippinePeso => number_currency::PHP, - NumberFormat::Dirham => number_currency::AED, - NumberFormat::ColombianPeso => number_currency::COP, - NumberFormat::Riyal => number_currency::SAR, - NumberFormat::Ringgit => number_currency::MYR, - NumberFormat::Leu => number_currency::RON, - NumberFormat::ArgentinePeso => number_currency::ARS, - NumberFormat::UruguayanPeso => number_currency::UYU, - NumberFormat::Percent => number_currency::PERCENT, - } - } - - pub fn symbol(&self) -> String { - self.currency().symbol.to_string() - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/mod.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/mod.rs deleted file mode 100644 index 8136fb57c5..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![allow(clippy::module_inception)] -mod format; -mod number_filter; -mod number_tests; -mod number_type_option; -mod number_type_option_entities; - -pub use format::*; -pub use number_type_option::*; -pub use number_type_option_entities::*; diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_filter.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_filter.rs deleted file mode 100644 index 3e2058be97..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_filter.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::entities::{NumberFilterConditionPB, NumberFilterPB}; - -use crate::services::field::NumberCellData; - -use rust_decimal::prelude::Zero; -use rust_decimal::Decimal; -use std::str::FromStr; - -impl NumberFilterPB { - pub fn is_visible(&self, num_cell_data: &NumberCellData) -> bool { - if self.content.is_empty() { - match self.condition { - NumberFilterConditionPB::NumberIsEmpty => { - return num_cell_data.is_empty(); - }, - NumberFilterConditionPB::NumberIsNotEmpty => { - return !num_cell_data.is_empty(); - }, - _ => {}, - } - } - match num_cell_data.decimal().as_ref() { - None => false, - Some(cell_decimal) => { - let decimal = Decimal::from_str(&self.content).unwrap_or_else(|_| Decimal::zero()); - match self.condition { - NumberFilterConditionPB::Equal => cell_decimal == &decimal, - NumberFilterConditionPB::NotEqual => cell_decimal != &decimal, - NumberFilterConditionPB::GreaterThan => cell_decimal > &decimal, - NumberFilterConditionPB::LessThan => cell_decimal < &decimal, - NumberFilterConditionPB::GreaterThanOrEqualTo => cell_decimal >= &decimal, - NumberFilterConditionPB::LessThanOrEqualTo => cell_decimal <= &decimal, - _ => true, - } - }, - } - } -} - -#[cfg(test)] -mod tests { - use crate::entities::{NumberFilterConditionPB, NumberFilterPB}; - use crate::services::field::{NumberCellData, NumberFormat}; - #[test] - fn number_filter_equal_test() { - let number_filter = NumberFilterPB { - condition: NumberFilterConditionPB::Equal, - content: "123".to_owned(), - }; - - for (num_str, visible) in [("123", true), ("1234", false), ("", false)] { - let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); - assert_eq!(number_filter.is_visible(&data), visible); - } - - let format = NumberFormat::USD; - for (num_str, visible) in [("$123", true), ("1234", false), ("", false)] { - let data = NumberCellData::from_format_str(num_str, true, &format).unwrap(); - assert_eq!(number_filter.is_visible(&data), visible); - } - } - #[test] - fn number_filter_greater_than_test() { - let number_filter = NumberFilterPB { - condition: NumberFilterConditionPB::GreaterThan, - content: "12".to_owned(), - }; - for (num_str, visible) in [("123", true), ("10", false), ("30", true), ("", false)] { - let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); - assert_eq!(number_filter.is_visible(&data), visible); - } - } - - #[test] - fn number_filter_less_than_test() { - let number_filter = NumberFilterPB { - condition: NumberFilterConditionPB::LessThan, - content: "100".to_owned(), - }; - for (num_str, visible) in [("12", true), ("1234", false), ("30", true), ("", false)] { - let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); - assert_eq!(number_filter.is_visible(&data), visible); - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_tests.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_tests.rs deleted file mode 100644 index 6978c37928..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_tests.rs +++ /dev/null @@ -1,678 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::entities::FieldType; - use crate::services::cell::CellDataDecoder; - use crate::services::field::FieldBuilder; - - use crate::services::field::{strip_currency_symbol, NumberFormat, NumberTypeOptionPB}; - use database_model::FieldRevision; - use strum::IntoEnumIterator; - - /// Testing when the input is not a number. - #[test] - fn number_type_option_invalid_input_test() { - let type_option = NumberTypeOptionPB::default(); - let field_type = FieldType::Number; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - - // Input is empty String - assert_number(&type_option, "", "", &field_type, &field_rev); - - // Input is letter - assert_number(&type_option, "abc", "", &field_type, &field_rev); - - assert_number(&type_option, "-123", "-123", &field_type, &field_rev); - - assert_number(&type_option, "abc-123", "-123", &field_type, &field_rev); - - assert_number(&type_option, "+123", "123", &field_type, &field_rev); - - assert_number(&type_option, "0.2", "0.2", &field_type, &field_rev); - - assert_number(&type_option, "-0.2", "-0.2", &field_type, &field_rev); - } - - /// Testing the strip_currency_symbol function. It should return the string without the input symbol. - #[test] - fn number_type_option_strip_symbol_test() { - // Remove the $ symbol - assert_eq!(strip_currency_symbol("$18,443"), "18,443".to_owned()); - // Remove the ¥ symbol - assert_eq!(strip_currency_symbol("¥0.2"), "0.2".to_owned()); - } - - /// Format the input number to the corresponding format string. - #[test] - fn number_type_option_format_number_test() { - let mut type_option = NumberTypeOptionPB::default(); - let field_type = FieldType::Number; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - - for format in NumberFormat::iter() { - type_option.format = format; - match format { - NumberFormat::Num => { - assert_number(&type_option, "18443", "18443", &field_type, &field_rev); - }, - NumberFormat::USD => { - assert_number(&type_option, "18443", "$18,443", &field_type, &field_rev); - }, - NumberFormat::CanadianDollar => { - assert_number(&type_option, "18443", "CA$18,443", &field_type, &field_rev) - }, - NumberFormat::EUR => { - assert_number(&type_option, "18443", "€18.443", &field_type, &field_rev) - }, - NumberFormat::Pound => { - assert_number(&type_option, "18443", "£18,443", &field_type, &field_rev) - }, - - NumberFormat::Yen => { - assert_number(&type_option, "18443", "¥18,443", &field_type, &field_rev); - }, - NumberFormat::Ruble => { - assert_number(&type_option, "18443", "18.443RUB", &field_type, &field_rev) - }, - NumberFormat::Rupee => { - assert_number(&type_option, "18443", "₹18,443", &field_type, &field_rev) - }, - NumberFormat::Won => { - assert_number(&type_option, "18443", "₩18,443", &field_type, &field_rev) - }, - - NumberFormat::Yuan => { - assert_number(&type_option, "18443", "CN¥18,443", &field_type, &field_rev); - }, - NumberFormat::Real => { - assert_number(&type_option, "18443", "R$18,443", &field_type, &field_rev); - }, - NumberFormat::Lira => { - assert_number(&type_option, "18443", "TRY18.443", &field_type, &field_rev) - }, - NumberFormat::Rupiah => { - assert_number(&type_option, "18443", "IDR18,443", &field_type, &field_rev) - }, - NumberFormat::Franc => { - assert_number(&type_option, "18443", "CHF18,443", &field_type, &field_rev) - }, - NumberFormat::HongKongDollar => { - assert_number(&type_option, "18443", "HZ$18,443", &field_type, &field_rev) - }, - NumberFormat::NewZealandDollar => { - assert_number(&type_option, "18443", "NZ$18,443", &field_type, &field_rev) - }, - NumberFormat::Krona => { - assert_number(&type_option, "18443", "18 443SEK", &field_type, &field_rev) - }, - NumberFormat::NorwegianKrone => { - assert_number(&type_option, "18443", "18,443NOK", &field_type, &field_rev) - }, - NumberFormat::MexicanPeso => { - assert_number(&type_option, "18443", "MX$18,443", &field_type, &field_rev) - }, - NumberFormat::Rand => { - assert_number(&type_option, "18443", "ZAR18,443", &field_type, &field_rev) - }, - NumberFormat::NewTaiwanDollar => { - assert_number(&type_option, "18443", "NT$18,443", &field_type, &field_rev) - }, - NumberFormat::DanishKrone => { - assert_number(&type_option, "18443", "18.443DKK", &field_type, &field_rev) - }, - NumberFormat::Baht => { - assert_number(&type_option, "18443", "THB18,443", &field_type, &field_rev) - }, - NumberFormat::Forint => { - assert_number(&type_option, "18443", "18 443HUF", &field_type, &field_rev) - }, - NumberFormat::Koruna => { - assert_number(&type_option, "18443", "18 443CZK", &field_type, &field_rev) - }, - NumberFormat::Shekel => { - assert_number(&type_option, "18443", "18 443Kč", &field_type, &field_rev) - }, - NumberFormat::ChileanPeso => { - assert_number(&type_option, "18443", "CLP18.443", &field_type, &field_rev) - }, - NumberFormat::PhilippinePeso => { - assert_number(&type_option, "18443", "₱18,443", &field_type, &field_rev) - }, - NumberFormat::Dirham => { - assert_number(&type_option, "18443", "18,443AED", &field_type, &field_rev) - }, - NumberFormat::ColombianPeso => { - assert_number(&type_option, "18443", "COP18.443", &field_type, &field_rev) - }, - NumberFormat::Riyal => { - assert_number(&type_option, "18443", "SAR18,443", &field_type, &field_rev) - }, - NumberFormat::Ringgit => { - assert_number(&type_option, "18443", "MYR18,443", &field_type, &field_rev) - }, - NumberFormat::Leu => { - assert_number(&type_option, "18443", "18.443RON", &field_type, &field_rev) - }, - NumberFormat::ArgentinePeso => { - assert_number(&type_option, "18443", "ARS18.443", &field_type, &field_rev) - }, - NumberFormat::UruguayanPeso => { - assert_number(&type_option, "18443", "UYU18.443", &field_type, &field_rev) - }, - NumberFormat::Percent => { - assert_number(&type_option, "18443", "18,443%", &field_type, &field_rev) - }, - } - } - } - - /// Format the input String to the corresponding format string. - #[test] - fn number_type_option_format_str_test() { - let mut type_option = NumberTypeOptionPB::default(); - let field_type = FieldType::Number; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - - for format in NumberFormat::iter() { - type_option.format = format; - match format { - NumberFormat::Num => { - assert_number(&type_option, "18443", "18443", &field_type, &field_rev); - assert_number(&type_option, "0.2", "0.2", &field_type, &field_rev); - assert_number(&type_option, "", "", &field_type, &field_rev); - assert_number(&type_option, "abc", "", &field_type, &field_rev); - }, - NumberFormat::USD => { - assert_number(&type_option, "$18,44", "$1,844", &field_type, &field_rev); - assert_number(&type_option, "$0.2", "$0.2", &field_type, &field_rev); - assert_number(&type_option, "$1844", "$1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "$1,844", &field_type, &field_rev); - }, - NumberFormat::CanadianDollar => { - assert_number( - &type_option, - "CA$18,44", - "CA$1,844", - &field_type, - &field_rev, - ); - assert_number(&type_option, "CA$0.2", "CA$0.2", &field_type, &field_rev); - assert_number(&type_option, "CA$1844", "CA$1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "CA$1,844", &field_type, &field_rev); - }, - NumberFormat::EUR => { - assert_number(&type_option, "€18.44", "€18,44", &field_type, &field_rev); - assert_number(&type_option, "€0.5", "€0,5", &field_type, &field_rev); - assert_number(&type_option, "€1844", "€1.844", &field_type, &field_rev); - assert_number(&type_option, "1844", "€1.844", &field_type, &field_rev); - }, - NumberFormat::Pound => { - assert_number(&type_option, "£18,44", "£1,844", &field_type, &field_rev); - assert_number(&type_option, "£0.2", "£0.2", &field_type, &field_rev); - assert_number(&type_option, "£1844", "£1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "£1,844", &field_type, &field_rev); - }, - NumberFormat::Yen => { - assert_number(&type_option, "¥18,44", "¥1,844", &field_type, &field_rev); - assert_number(&type_option, "¥0.2", "¥0.2", &field_type, &field_rev); - assert_number(&type_option, "¥1844", "¥1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "¥1,844", &field_type, &field_rev); - }, - NumberFormat::Ruble => { - assert_number( - &type_option, - "RUB18.44", - "18,44RUB", - &field_type, - &field_rev, - ); - assert_number(&type_option, "0.5", "0,5RUB", &field_type, &field_rev); - assert_number(&type_option, "RUB1844", "1.844RUB", &field_type, &field_rev); - assert_number(&type_option, "1844", "1.844RUB", &field_type, &field_rev); - }, - NumberFormat::Rupee => { - assert_number(&type_option, "₹18,44", "₹1,844", &field_type, &field_rev); - assert_number(&type_option, "₹0.2", "₹0.2", &field_type, &field_rev); - assert_number(&type_option, "₹1844", "₹1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "₹1,844", &field_type, &field_rev); - }, - NumberFormat::Won => { - assert_number(&type_option, "₩18,44", "₩1,844", &field_type, &field_rev); - assert_number(&type_option, "₩0.3", "₩0", &field_type, &field_rev); - assert_number(&type_option, "₩1844", "₩1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "₩1,844", &field_type, &field_rev); - }, - NumberFormat::Yuan => { - assert_number( - &type_option, - "CN¥18,44", - "CN¥1,844", - &field_type, - &field_rev, - ); - assert_number(&type_option, "CN¥0.2", "CN¥0.2", &field_type, &field_rev); - assert_number(&type_option, "CN¥1844", "CN¥1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "CN¥1,844", &field_type, &field_rev); - }, - NumberFormat::Real => { - assert_number(&type_option, "R$18,44", "R$1,844", &field_type, &field_rev); - assert_number(&type_option, "R$0.2", "R$0.2", &field_type, &field_rev); - assert_number(&type_option, "R$1844", "R$1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "R$1,844", &field_type, &field_rev); - }, - NumberFormat::Lira => { - assert_number( - &type_option, - "TRY18.44", - "TRY18,44", - &field_type, - &field_rev, - ); - assert_number(&type_option, "TRY0.5", "TRY0,5", &field_type, &field_rev); - assert_number(&type_option, "TRY1844", "TRY1.844", &field_type, &field_rev); - assert_number(&type_option, "1844", "TRY1.844", &field_type, &field_rev); - }, - NumberFormat::Rupiah => { - assert_number( - &type_option, - "IDR18,44", - "IDR1,844", - &field_type, - &field_rev, - ); - assert_number(&type_option, "IDR0.2", "IDR0.2", &field_type, &field_rev); - assert_number(&type_option, "IDR1844", "IDR1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "IDR1,844", &field_type, &field_rev); - }, - NumberFormat::Franc => { - assert_number( - &type_option, - "CHF18,44", - "CHF1,844", - &field_type, - &field_rev, - ); - assert_number(&type_option, "CHF0.2", "CHF0.2", &field_type, &field_rev); - assert_number(&type_option, "CHF1844", "CHF1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "CHF1,844", &field_type, &field_rev); - }, - NumberFormat::HongKongDollar => { - assert_number( - &type_option, - "HZ$18,44", - "HZ$1,844", - &field_type, - &field_rev, - ); - assert_number(&type_option, "HZ$0.2", "HZ$0.2", &field_type, &field_rev); - assert_number(&type_option, "HZ$1844", "HZ$1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "HZ$1,844", &field_type, &field_rev); - }, - NumberFormat::NewZealandDollar => { - assert_number( - &type_option, - "NZ$18,44", - "NZ$1,844", - &field_type, - &field_rev, - ); - assert_number(&type_option, "NZ$0.2", "NZ$0.2", &field_type, &field_rev); - assert_number(&type_option, "NZ$1844", "NZ$1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "NZ$1,844", &field_type, &field_rev); - }, - NumberFormat::Krona => { - assert_number( - &type_option, - "SEK18,44", - "18,44SEK", - &field_type, - &field_rev, - ); - assert_number(&type_option, "SEK0.2", "0,2SEK", &field_type, &field_rev); - assert_number(&type_option, "SEK1844", "1 844SEK", &field_type, &field_rev); - assert_number(&type_option, "1844", "1 844SEK", &field_type, &field_rev); - }, - NumberFormat::NorwegianKrone => { - assert_number( - &type_option, - "NOK18,44", - "1,844NOK", - &field_type, - &field_rev, - ); - assert_number(&type_option, "NOK0.2", "0.2NOK", &field_type, &field_rev); - assert_number(&type_option, "NOK1844", "1,844NOK", &field_type, &field_rev); - assert_number(&type_option, "1844", "1,844NOK", &field_type, &field_rev); - }, - NumberFormat::MexicanPeso => { - assert_number( - &type_option, - "MX$18,44", - "MX$1,844", - &field_type, - &field_rev, - ); - assert_number(&type_option, "MX$0.2", "MX$0.2", &field_type, &field_rev); - assert_number(&type_option, "MX$1844", "MX$1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "MX$1,844", &field_type, &field_rev); - }, - NumberFormat::Rand => { - assert_number( - &type_option, - "ZAR18,44", - "ZAR1,844", - &field_type, - &field_rev, - ); - assert_number(&type_option, "ZAR0.2", "ZAR0.2", &field_type, &field_rev); - assert_number(&type_option, "ZAR1844", "ZAR1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "ZAR1,844", &field_type, &field_rev); - }, - NumberFormat::NewTaiwanDollar => { - assert_number( - &type_option, - "NT$18,44", - "NT$1,844", - &field_type, - &field_rev, - ); - assert_number(&type_option, "NT$0.2", "NT$0.2", &field_type, &field_rev); - assert_number(&type_option, "NT$1844", "NT$1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "NT$1,844", &field_type, &field_rev); - }, - NumberFormat::DanishKrone => { - assert_number( - &type_option, - "DKK18.44", - "18,44DKK", - &field_type, - &field_rev, - ); - assert_number(&type_option, "DKK0.5", "0,5DKK", &field_type, &field_rev); - assert_number(&type_option, "DKK1844", "1.844DKK", &field_type, &field_rev); - assert_number(&type_option, "1844", "1.844DKK", &field_type, &field_rev); - }, - NumberFormat::Baht => { - assert_number( - &type_option, - "THB18,44", - "THB1,844", - &field_type, - &field_rev, - ); - assert_number(&type_option, "THB0.2", "THB0.2", &field_type, &field_rev); - assert_number(&type_option, "THB1844", "THB1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "THB1,844", &field_type, &field_rev); - }, - NumberFormat::Forint => { - assert_number(&type_option, "HUF18,44", "18HUF", &field_type, &field_rev); - assert_number(&type_option, "HUF0.3", "0HUF", &field_type, &field_rev); - assert_number(&type_option, "HUF1844", "1 844HUF", &field_type, &field_rev); - assert_number(&type_option, "1844", "1 844HUF", &field_type, &field_rev); - }, - NumberFormat::Koruna => { - assert_number( - &type_option, - "CZK18,44", - "18,44CZK", - &field_type, - &field_rev, - ); - assert_number(&type_option, "CZK0.2", "0,2CZK", &field_type, &field_rev); - assert_number(&type_option, "CZK1844", "1 844CZK", &field_type, &field_rev); - assert_number(&type_option, "1844", "1 844CZK", &field_type, &field_rev); - }, - NumberFormat::Shekel => { - assert_number(&type_option, "Kč18,44", "18,44Kč", &field_type, &field_rev); - assert_number(&type_option, "Kč0.2", "0,2Kč", &field_type, &field_rev); - assert_number(&type_option, "Kč1844", "1 844Kč", &field_type, &field_rev); - assert_number(&type_option, "1844", "1 844Kč", &field_type, &field_rev); - }, - NumberFormat::ChileanPeso => { - assert_number(&type_option, "CLP18.44", "CLP18", &field_type, &field_rev); - assert_number(&type_option, "0.5", "CLP0", &field_type, &field_rev); - assert_number(&type_option, "CLP1844", "CLP1.844", &field_type, &field_rev); - assert_number(&type_option, "1844", "CLP1.844", &field_type, &field_rev); - }, - NumberFormat::PhilippinePeso => { - assert_number(&type_option, "₱18,44", "₱1,844", &field_type, &field_rev); - assert_number(&type_option, "₱0.2", "₱0.2", &field_type, &field_rev); - assert_number(&type_option, "₱1844", "₱1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "₱1,844", &field_type, &field_rev); - }, - NumberFormat::Dirham => { - assert_number( - &type_option, - "AED18,44", - "1,844AED", - &field_type, - &field_rev, - ); - assert_number(&type_option, "AED0.2", "0.2AED", &field_type, &field_rev); - assert_number(&type_option, "AED1844", "1,844AED", &field_type, &field_rev); - assert_number(&type_option, "1844", "1,844AED", &field_type, &field_rev); - }, - NumberFormat::ColombianPeso => { - assert_number( - &type_option, - "COP18.44", - "COP18,44", - &field_type, - &field_rev, - ); - assert_number(&type_option, "0.5", "COP0,5", &field_type, &field_rev); - assert_number(&type_option, "COP1844", "COP1.844", &field_type, &field_rev); - assert_number(&type_option, "1844", "COP1.844", &field_type, &field_rev); - }, - NumberFormat::Riyal => { - assert_number( - &type_option, - "SAR18,44", - "SAR1,844", - &field_type, - &field_rev, - ); - assert_number(&type_option, "SAR0.2", "SAR0.2", &field_type, &field_rev); - assert_number(&type_option, "SAR1844", "SAR1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "SAR1,844", &field_type, &field_rev); - }, - - NumberFormat::Ringgit => { - assert_number( - &type_option, - "MYR18,44", - "MYR1,844", - &field_type, - &field_rev, - ); - assert_number(&type_option, "MYR0.2", "MYR0.2", &field_type, &field_rev); - assert_number(&type_option, "MYR1844", "MYR1,844", &field_type, &field_rev); - assert_number(&type_option, "1844", "MYR1,844", &field_type, &field_rev); - }, - NumberFormat::Leu => { - assert_number( - &type_option, - "RON18.44", - "18,44RON", - &field_type, - &field_rev, - ); - assert_number(&type_option, "0.5", "0,5RON", &field_type, &field_rev); - assert_number(&type_option, "RON1844", "1.844RON", &field_type, &field_rev); - assert_number(&type_option, "1844", "1.844RON", &field_type, &field_rev); - }, - NumberFormat::ArgentinePeso => { - assert_number( - &type_option, - "ARS18.44", - "ARS18,44", - &field_type, - &field_rev, - ); - assert_number(&type_option, "0.5", "ARS0,5", &field_type, &field_rev); - assert_number(&type_option, "ARS1844", "ARS1.844", &field_type, &field_rev); - assert_number(&type_option, "1844", "ARS1.844", &field_type, &field_rev); - }, - NumberFormat::UruguayanPeso => { - assert_number( - &type_option, - "UYU18.44", - "UYU18,44", - &field_type, - &field_rev, - ); - assert_number(&type_option, "0.5", "UYU0,5", &field_type, &field_rev); - assert_number(&type_option, "UYU1844", "UYU1.844", &field_type, &field_rev); - assert_number(&type_option, "1844", "UYU1.844", &field_type, &field_rev); - }, - NumberFormat::Percent => { - assert_number(&type_option, "1", "1%", &field_type, &field_rev); - assert_number(&type_option, "10.1", "10.1%", &field_type, &field_rev); - assert_number(&type_option, "100", "100%", &field_type, &field_rev); - }, - } - } - } - - /// Carry out the sign positive to input number - #[test] - fn number_description_sign_test() { - let mut type_option = NumberTypeOptionPB { - sign_positive: false, - ..Default::default() - }; - let field_type = FieldType::Number; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - - for format in NumberFormat::iter() { - type_option.format = format; - match format { - NumberFormat::Num => { - assert_number(&type_option, "18443", "18443", &field_type, &field_rev); - }, - NumberFormat::USD => { - assert_number(&type_option, "18443", "-$18,443", &field_type, &field_rev); - }, - NumberFormat::CanadianDollar => { - assert_number(&type_option, "18443", "-CA$18,443", &field_type, &field_rev) - }, - NumberFormat::EUR => { - assert_number(&type_option, "18443", "-€18.443", &field_type, &field_rev) - }, - NumberFormat::Pound => { - assert_number(&type_option, "18443", "-£18,443", &field_type, &field_rev) - }, - - NumberFormat::Yen => { - assert_number(&type_option, "18443", "-¥18,443", &field_type, &field_rev); - }, - NumberFormat::Ruble => { - assert_number(&type_option, "18443", "-18.443RUB", &field_type, &field_rev) - }, - NumberFormat::Rupee => { - assert_number(&type_option, "18443", "-₹18,443", &field_type, &field_rev) - }, - NumberFormat::Won => { - assert_number(&type_option, "18443", "-₩18,443", &field_type, &field_rev) - }, - - NumberFormat::Yuan => { - assert_number(&type_option, "18443", "-CN¥18,443", &field_type, &field_rev); - }, - NumberFormat::Real => { - assert_number(&type_option, "18443", "-R$18,443", &field_type, &field_rev); - }, - NumberFormat::Lira => { - assert_number(&type_option, "18443", "-TRY18.443", &field_type, &field_rev) - }, - NumberFormat::Rupiah => { - assert_number(&type_option, "18443", "-IDR18,443", &field_type, &field_rev) - }, - NumberFormat::Franc => { - assert_number(&type_option, "18443", "-CHF18,443", &field_type, &field_rev) - }, - NumberFormat::HongKongDollar => { - assert_number(&type_option, "18443", "-HZ$18,443", &field_type, &field_rev) - }, - NumberFormat::NewZealandDollar => { - assert_number(&type_option, "18443", "-NZ$18,443", &field_type, &field_rev) - }, - NumberFormat::Krona => { - assert_number(&type_option, "18443", "-18 443SEK", &field_type, &field_rev) - }, - NumberFormat::NorwegianKrone => { - assert_number(&type_option, "18443", "-18,443NOK", &field_type, &field_rev) - }, - NumberFormat::MexicanPeso => { - assert_number(&type_option, "18443", "-MX$18,443", &field_type, &field_rev) - }, - NumberFormat::Rand => { - assert_number(&type_option, "18443", "-ZAR18,443", &field_type, &field_rev) - }, - NumberFormat::NewTaiwanDollar => { - assert_number(&type_option, "18443", "-NT$18,443", &field_type, &field_rev) - }, - NumberFormat::DanishKrone => { - assert_number(&type_option, "18443", "-18.443DKK", &field_type, &field_rev) - }, - NumberFormat::Baht => { - assert_number(&type_option, "18443", "-THB18,443", &field_type, &field_rev) - }, - NumberFormat::Forint => { - assert_number(&type_option, "18443", "-18 443HUF", &field_type, &field_rev) - }, - NumberFormat::Koruna => { - assert_number(&type_option, "18443", "-18 443CZK", &field_type, &field_rev) - }, - NumberFormat::Shekel => { - assert_number(&type_option, "18443", "-18 443Kč", &field_type, &field_rev) - }, - NumberFormat::ChileanPeso => { - assert_number(&type_option, "18443", "-CLP18.443", &field_type, &field_rev) - }, - NumberFormat::PhilippinePeso => { - assert_number(&type_option, "18443", "-₱18,443", &field_type, &field_rev) - }, - NumberFormat::Dirham => { - assert_number(&type_option, "18443", "-18,443AED", &field_type, &field_rev) - }, - NumberFormat::ColombianPeso => { - assert_number(&type_option, "18443", "-COP18.443", &field_type, &field_rev) - }, - NumberFormat::Riyal => { - assert_number(&type_option, "18443", "-SAR18,443", &field_type, &field_rev) - }, - NumberFormat::Ringgit => { - assert_number(&type_option, "18443", "-MYR18,443", &field_type, &field_rev) - }, - NumberFormat::Leu => { - assert_number(&type_option, "18443", "-18.443RON", &field_type, &field_rev) - }, - NumberFormat::ArgentinePeso => { - assert_number(&type_option, "18443", "-ARS18.443", &field_type, &field_rev) - }, - NumberFormat::UruguayanPeso => { - assert_number(&type_option, "18443", "-UYU18.443", &field_type, &field_rev) - }, - NumberFormat::Percent => { - assert_number(&type_option, "18443", "-18,443%", &field_type, &field_rev) - }, - } - } - } - - fn assert_number( - type_option: &NumberTypeOptionPB, - input_str: &str, - expected_str: &str, - field_type: &FieldType, - field_rev: &FieldRevision, - ) { - assert_eq!( - type_option - .decode_cell_str(input_str.to_owned(), field_type, field_rev) - .unwrap() - .to_string(), - expected_str.to_owned() - ); - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option.rs deleted file mode 100644 index e6bbf826b3..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option.rs +++ /dev/null @@ -1,249 +0,0 @@ -use crate::entities::{FieldType, NumberFilterPB}; -use crate::impl_type_option; -use crate::services::cell::{CellDataChangeset, CellDataDecoder, TypeCellData}; -use crate::services::field::type_options::number_type_option::format::*; -use crate::services::field::{ - BoxTypeOptionBuilder, NumberCellData, StrCellData, TypeOption, TypeOptionBuilder, - TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, -}; -use bytes::Bytes; -use database_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; -use fancy_regex::Regex; -use flowy_derive::ProtoBuf; -use flowy_error::FlowyResult; -use lazy_static::lazy_static; -use rust_decimal::Decimal; -use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; -use std::default::Default; -use std::str::FromStr; - -#[derive(Default)] -pub struct NumberTypeOptionBuilder(NumberTypeOptionPB); -impl_into_box_type_option_builder!(NumberTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(NumberTypeOptionBuilder, NumberTypeOptionPB); - -impl NumberTypeOptionBuilder { - pub fn name(mut self, name: &str) -> Self { - self.0.name = name.to_string(); - self - } - - pub fn set_format(mut self, format: NumberFormat) -> Self { - self.0.set_format(format); - self - } - - pub fn scale(mut self, scale: u32) -> Self { - self.0.scale = scale; - self - } - - pub fn positive(mut self, positive: bool) -> Self { - self.0.sign_positive = positive; - self - } -} - -impl TypeOptionBuilder for NumberTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::Number - } - - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } -} - -// Number -#[derive(Clone, Debug, Serialize, Deserialize, ProtoBuf)] -pub struct NumberTypeOptionPB { - #[pb(index = 1)] - pub format: NumberFormat, - - #[pb(index = 2)] - pub scale: u32, - - #[pb(index = 3)] - pub symbol: String, - - #[pb(index = 4)] - pub sign_positive: bool, - - #[pb(index = 5)] - pub name: String, -} -impl_type_option!(NumberTypeOptionPB, FieldType::Number); - -impl TypeOption for NumberTypeOptionPB { - type CellData = StrCellData; - type CellChangeset = NumberCellChangeset; - type CellProtobufType = StrCellData; - type CellFilter = NumberFilterPB; -} - -impl TypeOptionCellData for NumberTypeOptionPB { - fn convert_to_protobuf( - &self, - cell_data: ::CellData, - ) -> ::CellProtobufType { - cell_data - } - - fn decode_type_option_cell_str( - &self, - cell_str: String, - ) -> FlowyResult<::CellData> { - Ok(cell_str.into()) - } -} - -impl NumberTypeOptionPB { - pub fn new() -> Self { - Self::default() - } - - pub(crate) fn format_cell_data(&self, s: &str) -> FlowyResult { - match self.format { - NumberFormat::Num => { - if SCIENTIFIC_NOTATION_REGEX.is_match(s).unwrap() { - match Decimal::from_scientific(&s.to_lowercase()) { - Ok(value, ..) => Ok(NumberCellData::from_decimal(value)), - Err(_) => Ok(NumberCellData::new()), - } - } else { - let num = match EXTRACT_NUM_REGEX.captures(s) { - Ok(Some(captures)) => captures - .get(0) - .map(|m| m.as_str().to_string()) - .unwrap_or_default(), - _ => "".to_string(), - }; - match Decimal::from_str(&num) { - Ok(value, ..) => Ok(NumberCellData::from_decimal(value)), - Err(_) => Ok(NumberCellData::new()), - } - } - }, - _ => NumberCellData::from_format_str(s, self.sign_positive, &self.format), - } - } - - pub fn set_format(&mut self, format: NumberFormat) { - self.format = format; - self.symbol = format.symbol(); - } -} - -pub(crate) fn strip_currency_symbol(s: T) -> String { - let mut s = s.to_string(); - for symbol in CURRENCY_SYMBOL.iter() { - if s.starts_with(symbol) { - s = s.strip_prefix(symbol).unwrap_or("").to_string(); - break; - } - } - s -} - -impl TypeOptionTransform for NumberTypeOptionPB {} - -impl CellDataDecoder for NumberTypeOptionPB { - fn decode_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult<::CellData> { - if decoded_field_type.is_date() { - return Ok(Default::default()); - } - - let str_cell_data = self.decode_type_option_cell_str(cell_str)?; - let s = self.format_cell_data(&str_cell_data)?.to_string(); - Ok(s.into()) - } - - fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { - match self.format_cell_data(&cell_data) { - Ok(cell_data) => cell_data.to_string(), - Err(_) => "".to_string(), - } - } -} - -pub type NumberCellChangeset = String; - -impl CellDataChangeset for NumberTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - _type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - let data = changeset.trim().to_string(); - let number_cell_data = self.format_cell_data(&data)?; - - match self.format { - NumberFormat::Num => Ok(( - number_cell_data.to_string(), - number_cell_data.to_string().into(), - )), - _ => Ok((data, number_cell_data.to_string().into())), - } - } -} - -impl TypeOptionCellDataFilter for NumberTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_number() { - return true; - } - match self.format_cell_data(cell_data) { - Ok(cell_data) => filter.is_visible(&cell_data), - Err(_) => true, - } - } -} - -impl TypeOptionCellDataCompare for NumberTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - let left = NumberCellData::from_format_str(&cell_data.0, self.sign_positive, &self.format); - let right = - NumberCellData::from_format_str(&other_cell_data.0, self.sign_positive, &self.format); - match (left, right) { - (Ok(left), Ok(right)) => { - return left.decimal().cmp(&right.decimal()); - }, - (Ok(_), Err(_)) => Ordering::Greater, - (Err(_), Ok(_)) => Ordering::Less, - (Err(_), Err(_)) => Ordering::Equal, - } - } -} -impl std::default::Default for NumberTypeOptionPB { - fn default() -> Self { - let format = NumberFormat::default(); - let symbol = format.symbol(); - NumberTypeOptionPB { - format, - scale: 0, - symbol, - sign_positive: true, - name: "Number".to_string(), - } - } -} - -lazy_static! { - static ref SCIENTIFIC_NOTATION_REGEX: Regex = Regex::new(r"([+-]?\d*\.?\d+)e([+-]?\d+)").unwrap(); - static ref EXTRACT_NUM_REGEX: Regex = Regex::new(r"-?\d+(\.\d+)?").unwrap(); -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option_entities.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option_entities.rs deleted file mode 100644 index ab650f8a7d..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option_entities.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::services::cell::{CellBytesCustomParser, CellProtobufBlobParser, DecodedCellData}; -use crate::services::field::number_currency::Currency; -use crate::services::field::{strip_currency_symbol, NumberFormat, STRIP_SYMBOL}; -use bytes::Bytes; -use flowy_error::FlowyResult; -use rust_decimal::Decimal; -use rusty_money::Money; -use std::str::FromStr; - -#[derive(Default)] -pub struct NumberCellData { - decimal: Option, - money: Option, -} - -impl NumberCellData { - pub fn new() -> Self { - Self { - decimal: Default::default(), - money: None, - } - } - - pub fn from_format_str(s: &str, sign_positive: bool, format: &NumberFormat) -> FlowyResult { - let mut num_str = strip_currency_symbol(s); - let currency = format.currency(); - if num_str.is_empty() { - return Ok(Self::default()); - } - match Decimal::from_str(&num_str) { - Ok(mut decimal) => { - decimal.set_sign_positive(sign_positive); - let money = Money::from_decimal(decimal, currency); - Ok(Self::from_money(money)) - }, - Err(_) => match Money::from_str(&num_str, currency) { - Ok(money) => Ok(NumberCellData::from_money(money)), - Err(_) => { - num_str.retain(|c| !STRIP_SYMBOL.contains(&c.to_string())); - if num_str.chars().all(char::is_numeric) { - Self::from_format_str(&num_str, sign_positive, format) - } else { - // returns empty string if it can be formatted - Ok(Self::default()) - } - }, - }, - } - } - - pub fn from_decimal(decimal: Decimal) -> Self { - Self { - decimal: Some(decimal), - money: None, - } - } - - pub fn from_money(money: Money) -> Self { - Self { - decimal: Some(*money.amount()), - money: Some(money.to_string()), - } - } - - pub fn decimal(&self) -> &Option { - &self.decimal - } - - pub fn is_empty(&self) -> bool { - self.decimal.is_none() - } -} - -// impl FromStr for NumberCellData { -// type Err = FlowyError; -// -// fn from_str(s: &str) -> Result { -// if s.is_empty() { -// return Ok(Self::default()); -// } -// let decimal = Decimal::from_str(s).map_err(internal_error)?; -// Ok(Self::from_decimal(decimal)) -// } -// } - -impl ToString for NumberCellData { - fn to_string(&self) -> String { - match &self.money { - None => match self.decimal { - None => String::default(), - Some(decimal) => decimal.to_string(), - }, - Some(money) => money.to_string(), - } - } -} - -impl DecodedCellData for NumberCellData { - type Object = NumberCellData; - - fn is_empty(&self) -> bool { - self.decimal.is_none() - } -} - -pub struct NumberCellDataParser(); -impl CellProtobufBlobParser for NumberCellDataParser { - type Object = NumberCellData; - fn parser(bytes: &Bytes) -> FlowyResult { - match String::from_utf8(bytes.to_vec()) { - Ok(s) => NumberCellData::from_format_str(&s, true, &NumberFormat::Num), - Err(_) => Ok(NumberCellData::default()), - } - } -} - -pub struct NumberCellCustomDataParser(pub NumberFormat); -impl CellBytesCustomParser for NumberCellCustomDataParser { - type Object = NumberCellData; - fn parse(&self, bytes: &Bytes) -> FlowyResult { - match String::from_utf8(bytes.to_vec()) { - Ok(s) => NumberCellData::from_format_str(&s, true, &self.0), - Err(_) => Ok(NumberCellData::default()), - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/checklist_filter.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/checklist_filter.rs deleted file mode 100644 index c1f3de0a52..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/checklist_filter.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::entities::{ChecklistFilterConditionPB, ChecklistFilterPB}; -use crate::services::field::{SelectOptionPB, SelectedSelectOptions}; - -impl ChecklistFilterPB { - pub fn is_visible( - &self, - all_options: &[SelectOptionPB], - selected_options: &SelectedSelectOptions, - ) -> bool { - let selected_option_ids = selected_options - .options - .iter() - .map(|option| option.id.as_str()) - .collect::>(); - - let mut all_option_ids = all_options - .iter() - .map(|option| option.id.as_str()) - .collect::>(); - - match self.condition { - ChecklistFilterConditionPB::IsComplete => { - if selected_option_ids.is_empty() { - return false; - } - - all_option_ids.retain(|option_id| !selected_option_ids.contains(option_id)); - all_option_ids.is_empty() - }, - ChecklistFilterConditionPB::IsIncomplete => { - if selected_option_ids.is_empty() { - return true; - } - - all_option_ids.retain(|option_id| !selected_option_ids.contains(option_id)); - !all_option_ids.is_empty() - }, - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/checklist_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/checklist_type_option.rs deleted file mode 100644 index cb17f473c4..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/checklist_type_option.rs +++ /dev/null @@ -1,146 +0,0 @@ -use crate::entities::{ChecklistFilterPB, FieldType}; -use crate::impl_type_option; -use crate::services::cell::{CellDataChangeset, FromCellString, TypeCellData}; -use crate::services::field::{ - BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, SelectOptionIds, - SelectOptionPB, SelectTypeOptionSharedAction, SelectedSelectOptions, TypeOption, - TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, -}; -use bytes::Bytes; -use database_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; -use flowy_derive::ProtoBuf; -use flowy_error::FlowyResult; -use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; - -// Multiple select -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] -pub struct ChecklistTypeOptionPB { - #[pb(index = 1)] - pub options: Vec, - - #[pb(index = 2)] - pub disable_color: bool, -} -impl_type_option!(ChecklistTypeOptionPB, FieldType::Checklist); - -impl TypeOption for ChecklistTypeOptionPB { - type CellData = SelectOptionIds; - type CellChangeset = SelectOptionCellChangeset; - type CellProtobufType = SelectOptionCellDataPB; - type CellFilter = ChecklistFilterPB; -} - -impl TypeOptionCellData for ChecklistTypeOptionPB { - fn convert_to_protobuf( - &self, - cell_data: ::CellData, - ) -> ::CellProtobufType { - self.get_selected_options(cell_data) - } - - fn decode_type_option_cell_str( - &self, - cell_str: String, - ) -> FlowyResult<::CellData> { - SelectOptionIds::from_cell_str(&cell_str) - } -} - -impl SelectTypeOptionSharedAction for ChecklistTypeOptionPB { - fn number_of_max_options(&self) -> Option { - None - } - - fn options(&self) -> &Vec { - &self.options - } - - fn mut_options(&mut self) -> &mut Vec { - &mut self.options - } -} - -impl CellDataChangeset for ChecklistTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - let insert_option_ids = changeset - .insert_option_ids - .into_iter() - .filter(|insert_option_id| { - self - .options - .iter() - .any(|option| &option.id == insert_option_id) - }) - .collect::>(); - - let select_option_ids = match type_cell_data { - None => SelectOptionIds::from(insert_option_ids), - Some(type_cell_data) => { - let mut select_ids: SelectOptionIds = type_cell_data.cell_str.into(); - for insert_option_id in insert_option_ids { - if !select_ids.contains(&insert_option_id) { - select_ids.push(insert_option_id); - } - } - - for delete_option_id in changeset.delete_option_ids { - select_ids.retain(|id| id != &delete_option_id); - } - - select_ids - }, - }; - Ok((select_option_ids.to_string(), select_option_ids)) - } -} -impl TypeOptionCellDataFilter for ChecklistTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_check_list() { - return true; - } - let selected_options = - SelectedSelectOptions::from(self.get_selected_options(cell_data.clone())); - filter.is_visible(&self.options, &selected_options) - } -} - -impl TypeOptionCellDataCompare for ChecklistTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - cell_data.len().cmp(&other_cell_data.len()) - } -} - -#[derive(Default)] -pub struct ChecklistTypeOptionBuilder(ChecklistTypeOptionPB); -impl_into_box_type_option_builder!(ChecklistTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(ChecklistTypeOptionBuilder, ChecklistTypeOptionPB); -impl ChecklistTypeOptionBuilder { - pub fn add_option(mut self, opt: SelectOptionPB) -> Self { - self.0.options.push(opt); - self - } -} - -impl TypeOptionBuilder for ChecklistTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::Checklist - } - - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/mod.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/mod.rs deleted file mode 100644 index 15f69ad2c8..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -mod checklist_filter; -mod checklist_type_option; -mod multi_select_type_option; -mod select_filter; -mod select_type_option; -mod single_select_type_option; -mod type_option_transform; - -pub use checklist_type_option::*; -pub use multi_select_type_option::*; -pub use select_type_option::*; -pub use single_select_type_option::*; - -pub use checklist_filter::*; diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/multi_select_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/multi_select_type_option.rs deleted file mode 100644 index 24b887fc06..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/multi_select_type_option.rs +++ /dev/null @@ -1,312 +0,0 @@ -use crate::entities::{FieldType, SelectOptionFilterPB}; -use crate::impl_type_option; -use crate::services::cell::{CellDataChangeset, FromCellString, TypeCellData}; -use std::cmp::{min, Ordering}; - -use crate::services::field::{ - default_order, BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionCellDataPB, - SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction, SelectedSelectOptions, TypeOption, - TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, -}; -use bytes::Bytes; -use database_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; -use flowy_derive::ProtoBuf; -use flowy_error::FlowyResult; -use serde::{Deserialize, Serialize}; - -// Multiple select -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] -pub struct MultiSelectTypeOptionPB { - #[pb(index = 1)] - pub options: Vec, - - #[pb(index = 2)] - pub disable_color: bool, -} -impl_type_option!(MultiSelectTypeOptionPB, FieldType::MultiSelect); - -impl TypeOption for MultiSelectTypeOptionPB { - type CellData = SelectOptionIds; - type CellChangeset = SelectOptionCellChangeset; - type CellProtobufType = SelectOptionCellDataPB; - type CellFilter = SelectOptionFilterPB; -} - -impl TypeOptionCellData for MultiSelectTypeOptionPB { - fn convert_to_protobuf( - &self, - cell_data: ::CellData, - ) -> ::CellProtobufType { - self.get_selected_options(cell_data) - } - - fn decode_type_option_cell_str( - &self, - cell_str: String, - ) -> FlowyResult<::CellData> { - SelectOptionIds::from_cell_str(&cell_str) - } -} - -impl SelectTypeOptionSharedAction for MultiSelectTypeOptionPB { - fn number_of_max_options(&self) -> Option { - None - } - - fn options(&self) -> &Vec { - &self.options - } - - fn mut_options(&mut self) -> &mut Vec { - &mut self.options - } -} - -impl CellDataChangeset for MultiSelectTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - let insert_option_ids = changeset - .insert_option_ids - .into_iter() - .filter(|insert_option_id| { - self - .options - .iter() - .any(|option| &option.id == insert_option_id) - }) - .collect::>(); - - let select_option_ids = match type_cell_data { - None => SelectOptionIds::from(insert_option_ids), - Some(type_cell_data) => { - let mut select_ids: SelectOptionIds = type_cell_data.cell_str.into(); - for insert_option_id in insert_option_ids { - if !select_ids.contains(&insert_option_id) { - select_ids.push(insert_option_id); - } - } - - for delete_option_id in changeset.delete_option_ids { - select_ids.retain(|id| id != &delete_option_id); - } - - tracing::trace!("Multi-select cell data: {}", select_ids.to_string()); - select_ids - }, - }; - Ok((select_option_ids.to_string(), select_option_ids)) - } -} - -impl TypeOptionCellDataFilter for MultiSelectTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_multi_select() { - return true; - } - let selected_options = - SelectedSelectOptions::from(self.get_selected_options(cell_data.clone())); - filter.is_visible(&selected_options, FieldType::MultiSelect) - } -} - -impl TypeOptionCellDataCompare for MultiSelectTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - for i in 0..min(cell_data.len(), other_cell_data.len()) { - let order = match ( - cell_data - .get(i) - .and_then(|id| self.options.iter().find(|option| &option.id == id)), - other_cell_data - .get(i) - .and_then(|id| self.options.iter().find(|option| &option.id == id)), - ) { - (Some(left), Some(right)) => left.name.cmp(&right.name), - (Some(_), None) => Ordering::Greater, - (None, Some(_)) => Ordering::Less, - (None, None) => default_order(), - }; - - if order.is_ne() { - return order; - } - } - default_order() - } -} -#[derive(Default)] -pub struct MultiSelectTypeOptionBuilder(MultiSelectTypeOptionPB); -impl_into_box_type_option_builder!(MultiSelectTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(MultiSelectTypeOptionBuilder, MultiSelectTypeOptionPB); -impl MultiSelectTypeOptionBuilder { - pub fn add_option(mut self, opt: SelectOptionPB) -> Self { - self.0.options.push(opt); - self - } -} - -impl TypeOptionBuilder for MultiSelectTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::MultiSelect - } - - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } -} - -#[cfg(test)] -mod tests { - use crate::entities::FieldType; - use crate::services::cell::CellDataChangeset; - use crate::services::field::type_options::selection_type_option::*; - use crate::services::field::{ - CheckboxTypeOptionBuilder, FieldBuilder, TypeOptionBuilder, TypeOptionTransform, - }; - use crate::services::field::{MultiSelectTypeOptionBuilder, MultiSelectTypeOptionPB}; - - #[test] - fn multi_select_transform_with_checkbox_type_option_test() { - let checkbox_type_option_builder = CheckboxTypeOptionBuilder::default(); - let checkbox_type_option_data = checkbox_type_option_builder.serializer().json_str(); - - let mut multi_select = MultiSelectTypeOptionBuilder::default().0; - multi_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data.clone()); - debug_assert_eq!(multi_select.options.len(), 2); - - // Already contain the yes/no option. It doesn't need to insert new options - multi_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data); - debug_assert_eq!(multi_select.options.len(), 2); - } - - #[test] - fn multi_select_transform_with_single_select_type_option_test() { - let mut singleselect_type_option_builder = SingleSelectTypeOptionBuilder::default(); - - let google = SelectOptionPB::new("Google"); - singleselect_type_option_builder = singleselect_type_option_builder.add_option(google); - - let facebook = SelectOptionPB::new("Facebook"); - singleselect_type_option_builder = singleselect_type_option_builder.add_option(facebook); - - let singleselect_type_option_data = singleselect_type_option_builder.serializer().json_str(); - - let mut multi_select = MultiSelectTypeOptionBuilder::default().0; - multi_select.transform_type_option( - FieldType::MultiSelect, - singleselect_type_option_data.clone(), - ); - debug_assert_eq!(multi_select.options.len(), 2); - - // Already contain the yes/no option. It doesn't need to insert new options - multi_select.transform_type_option(FieldType::MultiSelect, singleselect_type_option_data); - debug_assert_eq!(multi_select.options.len(), 2); - } - - // #[test] - - #[test] - fn multi_select_insert_multi_option_test() { - let google = SelectOptionPB::new("Google"); - let facebook = SelectOptionPB::new("Facebook"); - let multi_select = MultiSelectTypeOptionBuilder::default() - .add_option(google.clone()) - .add_option(facebook.clone()); - - let field_rev = FieldBuilder::new(multi_select).name("Platform").build(); - let type_option = MultiSelectTypeOptionPB::from(&field_rev); - let option_ids = vec![google.id, facebook.id]; - let changeset = SelectOptionCellChangeset::from_insert_options(option_ids.clone()); - let select_option_ids: SelectOptionIds = - type_option.apply_changeset(changeset, None).unwrap().1; - - assert_eq!(&*select_option_ids, &option_ids); - } - - #[test] - fn multi_select_unselect_multi_option_test() { - let google = SelectOptionPB::new("Google"); - let facebook = SelectOptionPB::new("Facebook"); - let multi_select = MultiSelectTypeOptionBuilder::default() - .add_option(google.clone()) - .add_option(facebook.clone()); - - let field_rev = FieldBuilder::new(multi_select).name("Platform").build(); - let type_option = MultiSelectTypeOptionPB::from(&field_rev); - let option_ids = vec![google.id, facebook.id]; - - // insert - let changeset = SelectOptionCellChangeset::from_insert_options(option_ids.clone()); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert_eq!(&*select_option_ids, &option_ids); - - // delete - let changeset = SelectOptionCellChangeset::from_delete_options(option_ids); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert!(select_option_ids.is_empty()); - } - - #[test] - fn multi_select_insert_single_option_test() { - let google = SelectOptionPB::new("Google"); - let multi_select = MultiSelectTypeOptionBuilder::default().add_option(google.clone()); - - let field_rev = FieldBuilder::new(multi_select) - .name("Platform") - .visibility(true) - .build(); - - let type_option = MultiSelectTypeOptionPB::from(&field_rev); - let changeset = SelectOptionCellChangeset::from_insert_option_id(&google.id); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert_eq!(select_option_ids.to_string(), google.id); - } - - #[test] - fn multi_select_insert_non_exist_option_test() { - let google = SelectOptionPB::new("Google"); - let multi_select = MultiSelectTypeOptionBuilder::default(); - let field_rev = FieldBuilder::new(multi_select) - .name("Platform") - .visibility(true) - .build(); - - let type_option = MultiSelectTypeOptionPB::from(&field_rev); - let changeset = SelectOptionCellChangeset::from_insert_option_id(&google.id); - let (_, select_option_ids) = type_option.apply_changeset(changeset, None).unwrap(); - assert!(select_option_ids.is_empty()); - } - - #[test] - fn multi_select_insert_invalid_option_id_test() { - let google = SelectOptionPB::new("Google"); - let multi_select = MultiSelectTypeOptionBuilder::default().add_option(google); - - let field_rev = FieldBuilder::new(multi_select) - .name("Platform") - .visibility(true) - .build(); - - let type_option = MultiSelectTypeOptionPB::from(&field_rev); - - // empty option id string - let changeset = SelectOptionCellChangeset::from_insert_option_id(""); - let (cell_str, _) = type_option.apply_changeset(changeset, None).unwrap(); - assert_eq!(cell_str, ""); - - let changeset = SelectOptionCellChangeset::from_insert_option_id("123,456"); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert!(select_option_ids.is_empty()); - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/select_filter.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/select_filter.rs deleted file mode 100644 index bfade9dbf1..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/select_filter.rs +++ /dev/null @@ -1,315 +0,0 @@ -#![allow(clippy::needless_collect)] - -use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB}; -use crate::services::field::SelectedSelectOptions; - -impl SelectOptionFilterPB { - pub fn is_visible( - &self, - selected_options: &SelectedSelectOptions, - field_type: FieldType, - ) -> bool { - let selected_option_ids: Vec<&String> = selected_options - .options - .iter() - .map(|option| &option.id) - .collect(); - match self.condition { - SelectOptionConditionPB::OptionIs => match field_type { - FieldType::SingleSelect => { - if self.option_ids.is_empty() { - return true; - } - - if selected_options.options.is_empty() { - return false; - } - - let required_options = self - .option_ids - .iter() - .filter(|id| selected_option_ids.contains(id)) - .collect::>(); - - !required_options.is_empty() - }, - FieldType::MultiSelect => { - if self.option_ids.is_empty() { - return true; - } - - let required_options = self - .option_ids - .iter() - .filter(|id| selected_option_ids.contains(id)) - .collect::>(); - - !required_options.is_empty() - }, - _ => false, - }, - SelectOptionConditionPB::OptionIsNot => match field_type { - FieldType::SingleSelect => { - if self.option_ids.is_empty() { - return true; - } - - if selected_options.options.is_empty() { - return false; - } - - let required_options = self - .option_ids - .iter() - .filter(|id| selected_option_ids.contains(id)) - .collect::>(); - - required_options.is_empty() - }, - FieldType::MultiSelect => { - let required_options = self - .option_ids - .iter() - .filter(|id| selected_option_ids.contains(id)) - .collect::>(); - - required_options.is_empty() - }, - _ => false, - }, - SelectOptionConditionPB::OptionIsEmpty => selected_option_ids.is_empty(), - SelectOptionConditionPB::OptionIsNotEmpty => !selected_option_ids.is_empty(), - } - } -} - -#[cfg(test)] -mod tests { - #![allow(clippy::all)] - use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB}; - use crate::services::field::selection_type_option::{SelectOptionPB, SelectedSelectOptions}; - - #[test] - fn select_option_filter_is_empty_test() { - let option = SelectOptionPB::new("A"); - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIsEmpty, - option_ids: vec![], - }; - - assert_eq!( - filter.is_visible( - &SelectedSelectOptions { options: vec![] }, - FieldType::SingleSelect - ), - true - ); - assert_eq!( - filter.is_visible( - &SelectedSelectOptions { - options: vec![option.clone()] - }, - FieldType::SingleSelect - ), - false, - ); - - assert_eq!( - filter.is_visible( - &SelectedSelectOptions { options: vec![] }, - FieldType::MultiSelect - ), - true - ); - assert_eq!( - filter.is_visible( - &SelectedSelectOptions { - options: vec![option] - }, - FieldType::MultiSelect - ), - false, - ); - } - - #[test] - fn select_option_filter_is_not_empty_test() { - let option_1 = SelectOptionPB::new("A"); - let option_2 = SelectOptionPB::new("B"); - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIsNotEmpty, - option_ids: vec![option_1.id.clone(), option_2.id.clone()], - }; - - assert_eq!( - filter.is_visible( - &SelectedSelectOptions { - options: vec![option_1.clone()] - }, - FieldType::SingleSelect - ), - true - ); - assert_eq!( - filter.is_visible( - &SelectedSelectOptions { options: vec![] }, - FieldType::SingleSelect - ), - false, - ); - - assert_eq!( - filter.is_visible( - &SelectedSelectOptions { - options: vec![option_1.clone()] - }, - FieldType::MultiSelect - ), - true - ); - assert_eq!( - filter.is_visible( - &SelectedSelectOptions { options: vec![] }, - FieldType::MultiSelect - ), - false, - ); - } - - #[test] - fn single_select_option_filter_is_not_test() { - let option_1 = SelectOptionPB::new("A"); - let option_2 = SelectOptionPB::new("B"); - let option_3 = SelectOptionPB::new("C"); - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIsNot, - option_ids: vec![option_1.id.clone(), option_2.id.clone()], - }; - - for (options, is_visible) in vec![ - (vec![option_2.clone()], false), - (vec![option_1.clone()], false), - (vec![option_3.clone()], true), - (vec![option_1.clone(), option_2.clone()], false), - ] { - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options }, FieldType::SingleSelect), - is_visible - ); - } - } - - #[test] - fn single_select_option_filter_is_test() { - let option_1 = SelectOptionPB::new("A"); - let option_2 = SelectOptionPB::new("B"); - let option_3 = SelectOptionPB::new("c"); - - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![option_1.id.clone()], - }; - for (options, is_visible) in vec![ - (vec![option_1.clone()], true), - (vec![option_2.clone()], false), - (vec![option_3.clone()], false), - (vec![option_1.clone(), option_2.clone()], true), - ] { - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options }, FieldType::SingleSelect), - is_visible - ); - } - } - - #[test] - fn single_select_option_filter_is_test2() { - let option_1 = SelectOptionPB::new("A"); - let option_2 = SelectOptionPB::new("B"); - - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![], - }; - for (options, is_visible) in vec![ - (vec![option_1.clone()], true), - (vec![option_2.clone()], true), - (vec![option_1.clone(), option_2.clone()], true), - ] { - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options }, FieldType::SingleSelect), - is_visible - ); - } - } - - #[test] - fn multi_select_option_filter_not_contains_test() { - let option_1 = SelectOptionPB::new("A"); - let option_2 = SelectOptionPB::new("B"); - let option_3 = SelectOptionPB::new("C"); - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIsNot, - option_ids: vec![option_1.id.clone(), option_2.id.clone()], - }; - - for (options, is_visible) in vec![ - (vec![option_1.clone(), option_2.clone()], false), - (vec![option_1.clone()], false), - (vec![option_2.clone()], false), - (vec![option_3.clone()], true), - ( - vec![option_1.clone(), option_2.clone(), option_3.clone()], - false, - ), - (vec![], true), - ] { - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options }, FieldType::MultiSelect), - is_visible - ); - } - } - #[test] - fn multi_select_option_filter_contains_test() { - let option_1 = SelectOptionPB::new("A"); - let option_2 = SelectOptionPB::new("B"); - let option_3 = SelectOptionPB::new("C"); - - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![option_1.id.clone(), option_2.id.clone()], - }; - for (options, is_visible) in vec![ - ( - vec![option_1.clone(), option_2.clone(), option_3.clone()], - true, - ), - (vec![option_2.clone(), option_1.clone()], true), - (vec![option_2.clone()], true), - (vec![option_1.clone(), option_3.clone()], true), - (vec![option_3.clone()], false), - ] { - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options }, FieldType::MultiSelect), - is_visible - ); - } - } - - #[test] - fn multi_select_option_filter_contains_test2() { - let option_1 = SelectOptionPB::new("A"); - - let filter = SelectOptionFilterPB { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![], - }; - for (options, is_visible) in vec![(vec![option_1.clone()], true), (vec![], true)] { - assert_eq!( - filter.is_visible(&SelectedSelectOptions { options }, FieldType::MultiSelect), - is_visible - ); - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/select_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/select_type_option.rs deleted file mode 100644 index b3d8c35aaa..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/select_type_option.rs +++ /dev/null @@ -1,553 +0,0 @@ -use crate::entities::parser::NotEmptyStr; -use crate::entities::{CellIdPB, CellIdParams, FieldType}; -use crate::services::cell::{ - CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangesetString, - FromCellString, ToCellChangesetString, -}; - -use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformHelper; -use crate::services::field::{ - CheckboxCellData, ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB, - TypeOption, TypeOptionCellData, TypeOptionTransform, -}; -use bytes::Bytes; -use database_model::{FieldRevision, TypeOptionDataSerializer}; -use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_error::{internal_error, ErrorCode, FlowyResult}; -use nanoid::nanoid; -use serde::{Deserialize, Serialize}; - -pub const SELECTION_IDS_SEPARATOR: &str = ","; - -/// [SelectOptionPB] represents an option for a single select, and multiple select. -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, ProtoBuf)] -pub struct SelectOptionPB { - #[pb(index = 1)] - pub id: String, - - #[pb(index = 2)] - pub name: String, - - #[pb(index = 3)] - pub color: SelectOptionColorPB, -} - -pub fn gen_option_id() -> String { - nanoid!(4) -} - -impl SelectOptionPB { - pub fn new(name: &str) -> Self { - SelectOptionPB { - id: gen_option_id(), - name: name.to_owned(), - color: SelectOptionColorPB::default(), - } - } - - pub fn with_color(name: &str, color: SelectOptionColorPB) -> Self { - SelectOptionPB { - id: nanoid!(4), - name: name.to_owned(), - color, - } - } -} - -#[derive(ProtoBuf_Enum, PartialEq, Eq, Serialize, Deserialize, Debug, Clone)] -#[repr(u8)] -pub enum SelectOptionColorPB { - Purple = 0, - Pink = 1, - LightPink = 2, - Orange = 3, - Yellow = 4, - Lime = 5, - Green = 6, - Aqua = 7, - Blue = 8, -} - -impl std::default::Default for SelectOptionColorPB { - fn default() -> Self { - SelectOptionColorPB::Purple - } -} - -pub fn make_selected_options( - ids: SelectOptionIds, - options: &[SelectOptionPB], -) -> Vec { - ids - .iter() - .flat_map(|option_id| { - options - .iter() - .find(|option| &option.id == option_id) - .cloned() - }) - .collect() -} -/// Defines the shared actions used by SingleSelect or Multi-Select. -pub trait SelectTypeOptionSharedAction: TypeOptionDataSerializer + Send + Sync { - /// Returns `None` means there is no limited - fn number_of_max_options(&self) -> Option; - - /// Insert the `SelectOptionPB` into corresponding type option. - fn insert_option(&mut self, new_option: SelectOptionPB) { - let options = self.mut_options(); - if let Some(index) = options - .iter() - .position(|option| option.id == new_option.id || option.name == new_option.name) - { - options.remove(index); - options.insert(index, new_option); - } else { - options.insert(0, new_option); - } - } - - fn delete_option(&mut self, delete_option: SelectOptionPB) { - let options = self.mut_options(); - if let Some(index) = options - .iter() - .position(|option| option.id == delete_option.id) - { - options.remove(index); - } - } - - fn create_option(&self, name: &str) -> SelectOptionPB { - let color = new_select_option_color(self.options()); - SelectOptionPB::with_color(name, color) - } - - /// Return a list of options that are selected by user - fn get_selected_options(&self, ids: SelectOptionIds) -> SelectOptionCellDataPB { - let mut select_options = make_selected_options(ids, self.options()); - match self.number_of_max_options() { - None => {}, - Some(number_of_max_options) => { - select_options.truncate(number_of_max_options); - }, - } - SelectOptionCellDataPB { - options: self.options().clone(), - select_options, - } - } - - fn options(&self) -> &Vec; - - fn mut_options(&mut self) -> &mut Vec; -} - -impl TypeOptionTransform for T -where - T: SelectTypeOptionSharedAction - + TypeOption - + TypeOptionDataSerializer - + CellDataDecoder, -{ - fn transformable(&self) -> bool { - true - } - - fn transform_type_option( - &mut self, - old_type_option_field_type: FieldType, - old_type_option_data: String, - ) { - SelectOptionTypeOptionTransformHelper::transform_type_option( - self, - &old_type_option_field_type, - old_type_option_data, - ); - } - - fn transform_type_option_cell_str( - &self, - cell_str: &str, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> Option<::CellData> { - match decoded_field_type { - FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => None, - FieldType::Checkbox => match CheckboxCellData::from_cell_str(cell_str) { - Ok(checkbox_cell_data) => { - let cell_content = checkbox_cell_data.to_string(); - let mut transformed_ids = Vec::new(); - let options = self.options(); - if let Some(option) = options.iter().find(|option| option.name == cell_content) { - transformed_ids.push(option.id.clone()); - } - Some(SelectOptionIds::from(transformed_ids)) - }, - Err(_) => None, - }, - FieldType::RichText => SelectOptionIds::from_cell_str(cell_str).ok(), - _ => Some(SelectOptionIds::from(vec![])), - } - } -} - -impl CellDataDecoder for T -where - T: SelectTypeOptionSharedAction + TypeOption + TypeOptionCellData, -{ - fn decode_cell_str( - &self, - cell_str: String, - _decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult<::CellData> { - self.decode_type_option_cell_str(cell_str) - } - - fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { - self - .get_selected_options(cell_data) - .select_options - .into_iter() - .map(|option| option.name) - .collect::>() - .join(SELECTION_IDS_SEPARATOR) - } -} - -pub fn select_type_option_from_field_rev( - field_rev: &FieldRevision, -) -> FlowyResult> { - let field_type: FieldType = field_rev.ty.into(); - match &field_type { - FieldType::SingleSelect => { - let type_option = SingleSelectTypeOptionPB::from(field_rev); - Ok(Box::new(type_option)) - }, - FieldType::MultiSelect => { - let type_option = MultiSelectTypeOptionPB::from(field_rev); - Ok(Box::new(type_option)) - }, - FieldType::Checklist => { - let type_option = ChecklistTypeOptionPB::from(field_rev); - Ok(Box::new(type_option)) - }, - ty => { - tracing::error!("Unsupported field type: {:?} for this handler", ty); - Err(ErrorCode::FieldInvalidOperation.into()) - }, - } -} - -pub fn new_select_option_color(options: &Vec) -> SelectOptionColorPB { - let mut freq: Vec = vec![0; 9]; - - for option in options { - freq[option.color.to_owned() as usize] += 1; - } - - match freq - .into_iter() - .enumerate() - .min_by_key(|(_, v)| *v) - .map(|(idx, _val)| idx) - .unwrap() - { - 0 => SelectOptionColorPB::Purple, - 1 => SelectOptionColorPB::Pink, - 2 => SelectOptionColorPB::LightPink, - 3 => SelectOptionColorPB::Orange, - 4 => SelectOptionColorPB::Yellow, - 5 => SelectOptionColorPB::Lime, - 6 => SelectOptionColorPB::Green, - 7 => SelectOptionColorPB::Aqua, - 8 => SelectOptionColorPB::Blue, - _ => SelectOptionColorPB::Purple, - } -} - -/// List of select option ids -/// -/// Calls [to_string] will return a string consists list of ids, -/// placing a commas separator between each -/// -#[derive(Default, Clone, Debug)] -pub struct SelectOptionIds(Vec); - -impl SelectOptionIds { - pub fn new() -> Self { - Self::default() - } - pub fn into_inner(self) -> Vec { - self.0 - } -} - -impl FromCellString for SelectOptionIds { - fn from_cell_str(s: &str) -> FlowyResult - where - Self: Sized, - { - Ok(Self::from(s.to_owned())) - } -} - -impl std::convert::From for SelectOptionIds { - fn from(s: String) -> Self { - if s.is_empty() { - return Self(vec![]); - } - - let ids = s - .split(SELECTION_IDS_SEPARATOR) - .map(|id| id.to_string()) - .collect::>(); - Self(ids) - } -} - -impl std::convert::From> for SelectOptionIds { - fn from(ids: Vec) -> Self { - let ids = ids - .into_iter() - .filter(|id| !id.is_empty()) - .collect::>(); - Self(ids) - } -} - -impl ToString for SelectOptionIds { - /// Returns a string that consists list of ids, placing a commas - /// separator between each - fn to_string(&self) -> String { - self.0.join(SELECTION_IDS_SEPARATOR) - } -} - -impl std::convert::From> for SelectOptionIds { - fn from(s: Option) -> Self { - match s { - None => Self(vec![]), - Some(s) => Self::from(s), - } - } -} - -impl std::ops::Deref for SelectOptionIds { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl std::ops::DerefMut for SelectOptionIds { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl DecodedCellData for SelectOptionIds { - type Object = SelectOptionIds; - - fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -pub struct SelectOptionIdsParser(); -impl CellProtobufBlobParser for SelectOptionIdsParser { - type Object = SelectOptionIds; - fn parser(bytes: &Bytes) -> FlowyResult { - match String::from_utf8(bytes.to_vec()) { - Ok(s) => Ok(SelectOptionIds::from(s)), - Err(_) => Ok(SelectOptionIds::from("".to_owned())), - } - } -} - -impl DecodedCellData for SelectOptionCellDataPB { - type Object = SelectOptionCellDataPB; - - fn is_empty(&self) -> bool { - self.select_options.is_empty() - } -} - -pub struct SelectOptionCellDataParser(); -impl CellProtobufBlobParser for SelectOptionCellDataParser { - type Object = SelectOptionCellDataPB; - - fn parser(bytes: &Bytes) -> FlowyResult { - SelectOptionCellDataPB::try_from(bytes.as_ref()).map_err(internal_error) - } -} - -#[derive(Clone, Debug, Default, ProtoBuf)] -pub struct SelectOptionCellChangesetPB { - #[pb(index = 1)] - pub cell_identifier: CellIdPB, - - #[pb(index = 2)] - pub insert_option_ids: Vec, - - #[pb(index = 3)] - pub delete_option_ids: Vec, -} - -pub struct SelectOptionCellChangesetParams { - pub cell_identifier: CellIdParams, - pub insert_option_ids: Vec, - pub delete_option_ids: Vec, -} - -impl TryInto for SelectOptionCellChangesetPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let cell_identifier: CellIdParams = self.cell_identifier.try_into()?; - let insert_option_ids = self - .insert_option_ids - .into_iter() - .flat_map(|option_id| match NotEmptyStr::parse(option_id) { - Ok(option_id) => Some(option_id.0), - Err(_) => { - tracing::error!("The insert option id should not be empty"); - None - }, - }) - .collect::>(); - - let delete_option_ids = self - .delete_option_ids - .into_iter() - .flat_map(|option_id| match NotEmptyStr::parse(option_id) { - Ok(option_id) => Some(option_id.0), - Err(_) => { - tracing::error!("The deleted option id should not be empty"); - None - }, - }) - .collect::>(); - - Ok(SelectOptionCellChangesetParams { - cell_identifier, - insert_option_ids, - delete_option_ids, - }) - } -} - -#[derive(Clone, Serialize, Deserialize, Debug)] -pub struct SelectOptionCellChangeset { - pub insert_option_ids: Vec, - pub delete_option_ids: Vec, -} - -impl FromCellChangesetString for SelectOptionCellChangeset { - fn from_changeset(changeset: String) -> FlowyResult - where - Self: Sized, - { - serde_json::from_str::(&changeset).map_err(internal_error) - } -} - -impl ToCellChangesetString for SelectOptionCellChangeset { - fn to_cell_changeset_str(&self) -> String { - serde_json::to_string(self).unwrap_or_default() - } -} - -impl SelectOptionCellChangeset { - pub fn from_insert_option_id(option_id: &str) -> Self { - SelectOptionCellChangeset { - insert_option_ids: vec![option_id.to_string()], - delete_option_ids: vec![], - } - } - - pub fn from_insert_options(option_ids: Vec) -> Self { - SelectOptionCellChangeset { - insert_option_ids: option_ids, - delete_option_ids: vec![], - } - } - - pub fn from_delete_option_id(option_id: &str) -> Self { - SelectOptionCellChangeset { - insert_option_ids: vec![], - delete_option_ids: vec![option_id.to_string()], - } - } - - pub fn from_delete_options(option_ids: Vec) -> Self { - SelectOptionCellChangeset { - insert_option_ids: vec![], - delete_option_ids: option_ids, - } - } -} - -/// [SelectOptionCellDataPB] contains a list of user's selected options and a list of all the options -/// that the cell can use. -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] -pub struct SelectOptionCellDataPB { - /// The available options that the cell can use. - #[pb(index = 1)] - pub options: Vec, - - /// The selected options for the cell. - #[pb(index = 2)] - pub select_options: Vec, -} - -/// [SelectOptionChangesetPB] describes the changes of a FieldTypeOptionData. For the moment, -/// it is used by [MultiSelectTypeOptionPB] and [SingleSelectTypeOptionPB]. -#[derive(Clone, Debug, Default, ProtoBuf)] -pub struct SelectOptionChangesetPB { - #[pb(index = 1)] - pub cell_identifier: CellIdPB, - - #[pb(index = 2)] - pub insert_options: Vec, - - #[pb(index = 3)] - pub update_options: Vec, - - #[pb(index = 4)] - pub delete_options: Vec, -} - -pub struct SelectOptionChangeset { - pub cell_path: CellIdParams, - pub insert_options: Vec, - pub update_options: Vec, - pub delete_options: Vec, -} - -impl TryInto for SelectOptionChangesetPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let cell_identifier = self.cell_identifier.try_into()?; - Ok(SelectOptionChangeset { - cell_path: cell_identifier, - insert_options: self.insert_options, - update_options: self.update_options, - delete_options: self.delete_options, - }) - } -} - -pub struct SelectedSelectOptions { - pub(crate) options: Vec, -} - -impl std::convert::From for SelectedSelectOptions { - fn from(data: SelectOptionCellDataPB) -> Self { - Self { - options: data.select_options, - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/single_select_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/single_select_type_option.rs deleted file mode 100644 index 48ea5de405..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/single_select_type_option.rs +++ /dev/null @@ -1,263 +0,0 @@ -use crate::entities::{FieldType, SelectOptionFilterPB}; -use crate::impl_type_option; -use crate::services::cell::{CellDataChangeset, FromCellString, TypeCellData}; -use std::cmp::Ordering; - -use crate::services::field::{ - default_order, BoxTypeOptionBuilder, SelectOptionCellDataPB, SelectedSelectOptions, TypeOption, - TypeOptionBuilder, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, -}; -use crate::services::field::{ - SelectOptionCellChangeset, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction, -}; -use bytes::Bytes; -use database_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; -use flowy_derive::ProtoBuf; -use flowy_error::FlowyResult; -use serde::{Deserialize, Serialize}; - -// Single select -#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] -pub struct SingleSelectTypeOptionPB { - #[pb(index = 1)] - pub options: Vec, - - #[pb(index = 2)] - pub disable_color: bool, -} -impl_type_option!(SingleSelectTypeOptionPB, FieldType::SingleSelect); - -impl TypeOption for SingleSelectTypeOptionPB { - type CellData = SelectOptionIds; - type CellChangeset = SelectOptionCellChangeset; - type CellProtobufType = SelectOptionCellDataPB; - type CellFilter = SelectOptionFilterPB; -} - -impl TypeOptionCellData for SingleSelectTypeOptionPB { - fn convert_to_protobuf( - &self, - cell_data: ::CellData, - ) -> ::CellProtobufType { - self.get_selected_options(cell_data) - } - - fn decode_type_option_cell_str( - &self, - cell_str: String, - ) -> FlowyResult<::CellData> { - SelectOptionIds::from_cell_str(&cell_str) - } -} - -impl SelectTypeOptionSharedAction for SingleSelectTypeOptionPB { - fn number_of_max_options(&self) -> Option { - Some(1) - } - - fn options(&self) -> &Vec { - &self.options - } - - fn mut_options(&mut self) -> &mut Vec { - &mut self.options - } -} - -impl CellDataChangeset for SingleSelectTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - _type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - let mut insert_option_ids = changeset - .insert_option_ids - .into_iter() - .filter(|insert_option_id| { - self - .options - .iter() - .any(|option| &option.id == insert_option_id) - }) - .collect::>(); - - // In single select, the insert_option_ids should only contain one select option id. - // Sometimes, the insert_option_ids may contain list of option ids. For example, - // copy/paste a ids string. - let select_option_ids = if insert_option_ids.is_empty() { - SelectOptionIds::from(insert_option_ids) - } else { - // Just take the first select option - let _ = insert_option_ids.drain(1..); - SelectOptionIds::from(insert_option_ids) - }; - Ok((select_option_ids.to_string(), select_option_ids)) - } -} - -impl TypeOptionCellDataFilter for SingleSelectTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_single_select() { - return true; - } - let selected_options = - SelectedSelectOptions::from(self.get_selected_options(cell_data.clone())); - filter.is_visible(&selected_options, FieldType::SingleSelect) - } -} - -impl TypeOptionCellDataCompare for SingleSelectTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - match ( - cell_data - .first() - .and_then(|id| self.options.iter().find(|option| &option.id == id)), - other_cell_data - .first() - .and_then(|id| self.options.iter().find(|option| &option.id == id)), - ) { - (Some(left), Some(right)) => left.name.cmp(&right.name), - (Some(_), None) => Ordering::Greater, - (None, Some(_)) => Ordering::Less, - (None, None) => default_order(), - } - } -} -#[derive(Default)] -pub struct SingleSelectTypeOptionBuilder(SingleSelectTypeOptionPB); -impl_into_box_type_option_builder!(SingleSelectTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(SingleSelectTypeOptionBuilder, SingleSelectTypeOptionPB); - -impl SingleSelectTypeOptionBuilder { - pub fn add_option(mut self, opt: SelectOptionPB) -> Self { - self.0.options.push(opt); - self - } -} - -impl TypeOptionBuilder for SingleSelectTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::SingleSelect - } - - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } -} - -#[cfg(test)] -mod tests { - use crate::entities::FieldType; - use crate::services::cell::CellDataChangeset; - use crate::services::field::type_options::*; - use crate::services::field::{FieldBuilder, TypeOptionBuilder}; - - #[test] - fn single_select_transform_with_checkbox_type_option_test() { - let checkbox_type_option_builder = CheckboxTypeOptionBuilder::default(); - let checkbox_type_option_data = checkbox_type_option_builder.serializer().json_str(); - - let mut single_select = SingleSelectTypeOptionBuilder::default().0; - single_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data.clone()); - debug_assert_eq!(single_select.options.len(), 2); - - // Already contain the yes/no option. It doesn't need to insert new options - single_select.transform_type_option(FieldType::Checkbox, checkbox_type_option_data); - debug_assert_eq!(single_select.options.len(), 2); - } - - #[test] - fn single_select_transform_with_multi_select_type_option_test() { - let mut multiselect_type_option_builder = MultiSelectTypeOptionBuilder::default(); - - let google = SelectOptionPB::new("Google"); - multiselect_type_option_builder = multiselect_type_option_builder.add_option(google); - - let facebook = SelectOptionPB::new("Facebook"); - multiselect_type_option_builder = multiselect_type_option_builder.add_option(facebook); - - let multiselect_type_option_data = multiselect_type_option_builder.serializer().json_str(); - - let mut single_select = SingleSelectTypeOptionBuilder::default().0; - single_select - .transform_type_option(FieldType::MultiSelect, multiselect_type_option_data.clone()); - debug_assert_eq!(single_select.options.len(), 2); - - // Already contain the yes/no option. It doesn't need to insert new options - single_select.transform_type_option(FieldType::MultiSelect, multiselect_type_option_data); - debug_assert_eq!(single_select.options.len(), 2); - } - - #[test] - fn single_select_insert_multi_option_test() { - let google = SelectOptionPB::new("Google"); - let facebook = SelectOptionPB::new("Facebook"); - let single_select = SingleSelectTypeOptionBuilder::default() - .add_option(google.clone()) - .add_option(facebook.clone()); - - let field_rev = FieldBuilder::new(single_select).name("Platform").build(); - let type_option = SingleSelectTypeOptionPB::from(&field_rev); - let option_ids = vec![google.id.clone(), facebook.id]; - let changeset = SelectOptionCellChangeset::from_insert_options(option_ids); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert_eq!(&*select_option_ids, &vec![google.id]); - } - - #[test] - fn single_select_unselect_multi_option_test() { - let google = SelectOptionPB::new("Google"); - let facebook = SelectOptionPB::new("Facebook"); - let single_select = SingleSelectTypeOptionBuilder::default() - .add_option(google.clone()) - .add_option(facebook.clone()); - - let field_rev = FieldBuilder::new(single_select).name("Platform").build(); - let type_option = SingleSelectTypeOptionPB::from(&field_rev); - let option_ids = vec![google.id.clone(), facebook.id]; - - // insert - let changeset = SelectOptionCellChangeset::from_insert_options(option_ids.clone()); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert_eq!(&*select_option_ids, &vec![google.id]); - - // delete - let changeset = SelectOptionCellChangeset::from_delete_options(option_ids); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert!(select_option_ids.is_empty()); - } - - #[test] - fn single_select_insert_non_exist_option_test() { - let google = SelectOptionPB::new("Google"); - let single_select = SingleSelectTypeOptionBuilder::default(); - let field_rev = FieldBuilder::new(single_select).name("Platform").build(); - let type_option = SingleSelectTypeOptionPB::from(&field_rev); - - let option_ids = vec![google.id]; - let changeset = SelectOptionCellChangeset::from_insert_options(option_ids); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - - assert!(select_option_ids.is_empty()); - } - - #[test] - fn single_select_insert_invalid_option_id_test() { - let single_select = SingleSelectTypeOptionBuilder::default(); - let field_rev = FieldBuilder::new(single_select).name("Platform").build(); - let type_option = SingleSelectTypeOptionPB::from(&field_rev); - - let changeset = SelectOptionCellChangeset::from_insert_option_id(""); - let select_option_ids = type_option.apply_changeset(changeset, None).unwrap().1; - assert!(select_option_ids.is_empty()); - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/type_option_transform.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/type_option_transform.rs deleted file mode 100644 index 7e9f9a7b58..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/selection_type_option/type_option_transform.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::entities::FieldType; - -use crate::services::field::{ - MultiSelectTypeOptionPB, SelectOptionColorPB, SelectOptionIds, SelectOptionPB, - SelectTypeOptionSharedAction, SingleSelectTypeOptionPB, TypeOption, CHECK, UNCHECK, -}; - -use database_model::TypeOptionDataDeserializer; - -/// Handles how to transform the cell data when switching between different field types -pub(crate) struct SelectOptionTypeOptionTransformHelper(); -impl SelectOptionTypeOptionTransformHelper { - /// Transform the TypeOptionData from 'field_type' to single select option type. - /// - /// # Arguments - /// - /// * `old_field_type`: the FieldType of the passed-in TypeOptionData - /// - pub fn transform_type_option( - shared: &mut T, - old_field_type: &FieldType, - old_type_option_data: String, - ) where - T: SelectTypeOptionSharedAction + TypeOption, - { - match old_field_type { - FieldType::Checkbox => { - //add Yes and No options if it does not exist. - if !shared.options().iter().any(|option| option.name == CHECK) { - let check_option = SelectOptionPB::with_color(CHECK, SelectOptionColorPB::Green); - shared.mut_options().push(check_option); - } - - if !shared.options().iter().any(|option| option.name == UNCHECK) { - let uncheck_option = SelectOptionPB::with_color(UNCHECK, SelectOptionColorPB::Yellow); - shared.mut_options().push(uncheck_option); - } - }, - FieldType::MultiSelect => { - let options = MultiSelectTypeOptionPB::from_json_str(&old_type_option_data).options; - options.iter().for_each(|new_option| { - if !shared - .options() - .iter() - .any(|option| option.name == new_option.name) - { - shared.mut_options().push(new_option.clone()); - } - }) - }, - FieldType::SingleSelect => { - let options = SingleSelectTypeOptionPB::from_json_str(&old_type_option_data).options; - options.iter().for_each(|new_option| { - if !shared - .options() - .iter() - .any(|option| option.name == new_option.name) - { - shared.mut_options().push(new_option.clone()); - } - }) - }, - _ => {}, - } - } - - // pub fn transform_e_option_cell_data( - // // shared: &T, - // // cell_data: String, - // // decoded_field_type: &FieldType, - // // ) -> ::CellData - // // where - // // T: SelectTypeOptionSharedAction + TypeOption + CellDataDecoder, - // // { - // // match decoded_field_type { - // // FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => { - // // self.try_decode_cell_data(cell_data) - // // } - // // FieldType::Checkbox => { - // // // transform the cell data to the option id - // // let mut transformed_ids = Vec::new(); - // // let options = shared.options(); - // // cell_data.iter().for_each(|name| { - // // if let Some(option) = options.iter().find(|option| &option.name == name) { - // // transformed_ids.push(option.id.clone()); - // // } - // // }); - // // SelectOptionIds::from(transformed_ids) - // // } - // // _ => SelectOptionIds::from(vec![]), - // // } - // // }typ -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/mod.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/mod.rs deleted file mode 100644 index 9537ee8f33..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -#![allow(clippy::module_inception)] -mod text_filter; -mod text_tests; -mod text_type_option; - -pub use text_type_option::*; diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_filter.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_filter.rs deleted file mode 100644 index f684dcc56b..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_filter.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::entities::{TextFilterConditionPB, TextFilterPB}; - -impl TextFilterPB { - pub fn is_visible>(&self, cell_data: T) -> bool { - let cell_data = cell_data.as_ref().to_lowercase(); - let content = &self.content.to_lowercase(); - match self.condition { - TextFilterConditionPB::Is => &cell_data == content, - TextFilterConditionPB::IsNot => &cell_data != content, - TextFilterConditionPB::Contains => cell_data.contains(content), - TextFilterConditionPB::DoesNotContain => !cell_data.contains(content), - TextFilterConditionPB::StartsWith => cell_data.starts_with(content), - TextFilterConditionPB::EndsWith => cell_data.ends_with(content), - TextFilterConditionPB::TextIsEmpty => cell_data.is_empty(), - TextFilterConditionPB::TextIsNotEmpty => !cell_data.is_empty(), - } - } -} - -#[cfg(test)] -mod tests { - #![allow(clippy::all)] - use crate::entities::{TextFilterConditionPB, TextFilterPB}; - - #[test] - fn text_filter_equal_test() { - let text_filter = TextFilterPB { - condition: TextFilterConditionPB::Is, - content: "appflowy".to_owned(), - }; - - assert!(text_filter.is_visible("AppFlowy")); - assert_eq!(text_filter.is_visible("appflowy"), true); - assert_eq!(text_filter.is_visible("Appflowy"), true); - assert_eq!(text_filter.is_visible("AppFlowy.io"), false); - } - #[test] - fn text_filter_start_with_test() { - let text_filter = TextFilterPB { - condition: TextFilterConditionPB::StartsWith, - content: "appflowy".to_owned(), - }; - - assert_eq!(text_filter.is_visible("AppFlowy.io"), true); - assert_eq!(text_filter.is_visible(""), false); - assert_eq!(text_filter.is_visible("https"), false); - } - - #[test] - fn text_filter_end_with_test() { - let text_filter = TextFilterPB { - condition: TextFilterConditionPB::EndsWith, - content: "appflowy".to_owned(), - }; - - assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true); - assert_eq!(text_filter.is_visible("App"), false); - assert_eq!(text_filter.is_visible("appflowy.io"), false); - } - #[test] - fn text_filter_empty_test() { - let text_filter = TextFilterPB { - condition: TextFilterConditionPB::TextIsEmpty, - content: "appflowy".to_owned(), - }; - - assert_eq!(text_filter.is_visible(""), true); - assert_eq!(text_filter.is_visible("App"), false); - } - #[test] - fn text_filter_contain_test() { - let text_filter = TextFilterPB { - condition: TextFilterConditionPB::Contains, - content: "appflowy".to_owned(), - }; - - assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true); - assert_eq!(text_filter.is_visible("AppFlowy"), true); - assert_eq!(text_filter.is_visible("App"), false); - assert_eq!(text_filter.is_visible(""), false); - assert_eq!(text_filter.is_visible("github"), false); - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_tests.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_tests.rs deleted file mode 100644 index 8494383e6e..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_tests.rs +++ /dev/null @@ -1,87 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::entities::FieldType; - use crate::services::cell::stringify_cell_data; - - use crate::services::field::FieldBuilder; - use crate::services::field::*; - - // Test parser the cell data which field's type is FieldType::Date to cell data - // which field's type is FieldType::Text - #[test] - fn date_type_to_text_type() { - let field_type = FieldType::DateTime; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - - assert_eq!( - stringify_cell_data( - 1647251762.to_string(), - &FieldType::RichText, - &field_type, - &field_rev - ), - "Mar 14, 2022" - ); - - let data = DateCellData { - timestamp: Some(1647251762), - include_time: true, - }; - - assert_eq!( - stringify_cell_data( - data.to_string(), - &FieldType::RichText, - &field_type, - &field_rev - ), - "Mar 14, 2022" - ); - } - - // Test parser the cell data which field's type is FieldType::SingleSelect to cell data - // which field's type is FieldType::Text - #[test] - fn single_select_to_text_type() { - let field_type = FieldType::SingleSelect; - let done_option = SelectOptionPB::new("Done"); - let option_id = done_option.id.clone(); - let single_select = SingleSelectTypeOptionBuilder::default().add_option(done_option.clone()); - let field_rev = FieldBuilder::new(single_select).build(); - - assert_eq!( - stringify_cell_data(option_id, &FieldType::RichText, &field_type, &field_rev), - done_option.name, - ); - } - /* - - [Unit Test] Testing the switching from Multi-selection type to Text type - - Tracking : https://github.com/AppFlowy-IO/AppFlowy/issues/1183 - */ - #[test] - fn multiselect_to_text_type() { - let field_type = FieldType::MultiSelect; - - let france = SelectOptionPB::new("france"); - let france_option_id = france.id.clone(); - - let argentina = SelectOptionPB::new("argentina"); - let argentina_option_id = argentina.id.clone(); - - let multi_select = MultiSelectTypeOptionBuilder::default() - .add_option(france.clone()) - .add_option(argentina.clone()); - - let field_rev = FieldBuilder::new(multi_select).build(); - - assert_eq!( - stringify_cell_data( - format!("{},{}", france_option_id, argentina_option_id), - &FieldType::RichText, - &field_type, - &field_rev - ), - format!("{},{}", france.name, argentina.name) - ); - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_type_option.rs deleted file mode 100644 index 5f86f1fce3..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/text_type_option/text_type_option.rs +++ /dev/null @@ -1,274 +0,0 @@ -use crate::entities::{FieldType, TextFilterPB}; -use crate::impl_type_option; -use crate::services::cell::{ - stringify_cell_data, CellDataChangeset, CellDataDecoder, CellProtobufBlobParser, DecodedCellData, - FromCellString, TypeCellData, -}; -use crate::services::field::{ - BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, - TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, -}; -use bytes::Bytes; -use database_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; -use flowy_derive::ProtoBuf; -use flowy_error::{FlowyError, FlowyResult}; -use protobuf::ProtobufError; -use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; - -#[derive(Default)] -pub struct RichTextTypeOptionBuilder(RichTextTypeOptionPB); -impl_into_box_type_option_builder!(RichTextTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(RichTextTypeOptionBuilder, RichTextTypeOptionPB); - -impl TypeOptionBuilder for RichTextTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::RichText - } - - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } -} - -/// For the moment, the `RichTextTypeOptionPB` is empty. The `data` property is not -/// used yet. -#[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)] -pub struct RichTextTypeOptionPB { - #[pb(index = 1)] - #[serde(default)] - data: String, -} -impl_type_option!(RichTextTypeOptionPB, FieldType::RichText); - -impl TypeOption for RichTextTypeOptionPB { - type CellData = StrCellData; - type CellChangeset = String; - type CellProtobufType = StrCellData; - type CellFilter = TextFilterPB; -} - -impl TypeOptionTransform for RichTextTypeOptionPB { - fn transformable(&self) -> bool { - true - } - - fn transform_type_option( - &mut self, - _old_type_option_field_type: FieldType, - _old_type_option_data: String, - ) { - } - - fn transform_type_option_cell_str( - &self, - cell_str: &str, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> Option<::CellData> { - if decoded_field_type.is_date() - || decoded_field_type.is_single_select() - || decoded_field_type.is_multi_select() - || decoded_field_type.is_number() - || decoded_field_type.is_url() - { - Some( - stringify_cell_data( - cell_str.to_owned(), - decoded_field_type, - decoded_field_type, - field_rev, - ) - .into(), - ) - } else { - StrCellData::from_cell_str(cell_str).ok() - } - } -} - -impl TypeOptionCellData for RichTextTypeOptionPB { - fn convert_to_protobuf( - &self, - cell_data: ::CellData, - ) -> ::CellProtobufType { - cell_data - } - - fn decode_type_option_cell_str( - &self, - cell_str: String, - ) -> FlowyResult<::CellData> { - StrCellData::from_cell_str(&cell_str) - } -} - -impl CellDataDecoder for RichTextTypeOptionPB { - fn decode_cell_str( - &self, - cell_str: String, - _decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult<::CellData> { - StrCellData::from_cell_str(&cell_str) - } - - fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { - cell_data.to_string() - } -} - -impl CellDataChangeset for RichTextTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - _type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - if changeset.len() > 10000 { - Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000")) - } else { - let text_cell_data = StrCellData(changeset); - Ok((text_cell_data.to_string(), text_cell_data)) - } - } -} - -impl TypeOptionCellDataFilter for RichTextTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_text() { - return false; - } - - filter.is_visible(cell_data) - } -} - -impl TypeOptionCellDataCompare for RichTextTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - cell_data.0.cmp(&other_cell_data.0) - } -} - -#[derive(Clone)] -pub struct TextCellData(pub String); -impl AsRef for TextCellData { - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl std::ops::Deref for TextCellData { - type Target = String; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl FromCellString for TextCellData { - fn from_cell_str(s: &str) -> FlowyResult - where - Self: Sized, - { - Ok(TextCellData(s.to_owned())) - } -} - -impl ToString for TextCellData { - fn to_string(&self) -> String { - self.0.clone() - } -} - -impl DecodedCellData for TextCellData { - type Object = TextCellData; - - fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -pub struct TextCellDataParser(); -impl CellProtobufBlobParser for TextCellDataParser { - type Object = TextCellData; - fn parser(bytes: &Bytes) -> FlowyResult { - match String::from_utf8(bytes.to_vec()) { - Ok(s) => Ok(TextCellData(s)), - Err(_) => Ok(TextCellData("".to_owned())), - } - } -} - -#[derive(Default, Debug, Clone)] -pub struct StrCellData(pub String); -impl std::ops::Deref for StrCellData { - type Target = String; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl std::ops::DerefMut for StrCellData { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl FromCellString for StrCellData { - fn from_cell_str(s: &str) -> FlowyResult { - Ok(Self(s.to_owned())) - } -} - -impl std::convert::From for StrCellData { - fn from(s: String) -> Self { - Self(s) - } -} - -impl ToString for StrCellData { - fn to_string(&self) -> String { - self.0.clone() - } -} - -impl std::convert::From for String { - fn from(value: StrCellData) -> Self { - value.0 - } -} - -impl std::convert::From<&str> for StrCellData { - fn from(s: &str) -> Self { - Self(s.to_owned()) - } -} - -impl std::convert::TryFrom for Bytes { - type Error = ProtobufError; - - fn try_from(value: StrCellData) -> Result { - Ok(Bytes::from(value.0)) - } -} - -impl AsRef<[u8]> for StrCellData { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} -impl AsRef for StrCellData { - fn as_ref(&self) -> &str { - self.0.as_str() - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/type_option.rs deleted file mode 100644 index 95c686b9d4..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/type_option.rs +++ /dev/null @@ -1,129 +0,0 @@ -use crate::entities::FieldType; -use crate::services::cell::{ - CellDataDecoder, FromCellChangesetString, FromCellString, ToCellChangesetString, -}; - -use crate::services::filter::FromFilterString; -use bytes::Bytes; -use database_model::FieldRevision; -use flowy_error::FlowyResult; -use protobuf::ProtobufError; -use std::cmp::Ordering; -use std::fmt::Debug; - -pub trait TypeOption { - /// `CellData` represents as the decoded model for current type option. Each of them impl the - /// `FromCellString` and `Default` trait. If the cell string can not be decoded into the specified - /// cell data type then the default value will be returned. - /// For example: - /// FieldType::Checkbox => CheckboxCellData - /// FieldType::Date => DateCellData - /// FieldType::URL => URLCellData - /// - /// Uses `StrCellData` for any `TypeOption` if their cell data is pure `String`. - /// - type CellData: FromCellString + ToString + Default + Send + Sync + Clone + Debug + 'static; - - /// Represents as the corresponding field type cell changeset. - /// The changeset must implements the `FromCellChangesetString` and the `ToCellChangesetString` trait. - /// These two traits are auto implemented for `String`. - /// - type CellChangeset: FromCellChangesetString + ToCellChangesetString; - - /// For the moment, the protobuf type only be used in the FFI of `Dart`. If the decoded cell - /// struct is just a `String`, then use the `StrCellData` as its `CellProtobufType`. - /// Otherwise, providing a custom protobuf type as its `CellProtobufType`. - /// For example: - /// FieldType::Date => DateCellDataPB - /// FieldType::URL => URLCellDataPB - /// - type CellProtobufType: TryInto + Debug; - - /// Represents as the filter configuration for this type option. - type CellFilter: FromFilterString + Send + Sync + 'static; -} - -pub trait TypeOptionCellData: TypeOption { - /// Convert the decoded cell data into corresponding `Protobuf struct`. - /// For example: - /// FieldType::URL => URLCellDataPB - /// FieldType::Date=> DateCellDataPB - fn convert_to_protobuf( - &self, - cell_data: ::CellData, - ) -> ::CellProtobufType; - - /// Decodes the opaque cell string to corresponding data struct. - // For example, the cell data is timestamp if its field type is `FieldType::Date`. This cell - // data can not directly show to user. So it needs to be encode as the date string with custom - // format setting. Encode `1647251762` to `"Mar 14,2022` - fn decode_type_option_cell_str( - &self, - cell_str: String, - ) -> FlowyResult<::CellData>; -} - -pub trait TypeOptionTransform: TypeOption { - /// Returns true if the current `TypeOption` provides custom type option transformation - fn transformable(&self) -> bool { - false - } - - /// Transform the TypeOption from one field type to another - /// For example, when switching from `checkbox` type-option to `single-select` - /// type-option, adding the `Yes` option if the `single-select` type-option doesn't contain it. - /// But the cell content is a string, `Yes`, it's need to do the cell content transform. - /// The `Yes` string will be transformed to the `Yes` option id. - /// - /// # Arguments - /// - /// * `old_type_option_field_type`: the FieldType of the passed-in TypeOption - /// * `old_type_option_data`: the data that can be parsed into corresponding `TypeOption`. - /// - /// - fn transform_type_option( - &mut self, - _old_type_option_field_type: FieldType, - _old_type_option_data: String, - ) { - } - - /// Transform the cell data from one field type to another - /// - /// # Arguments - /// - /// * `cell_str`: the cell string of the current field type - /// * `decoded_field_type`: the field type of the cell data that's going to be transformed into - /// current `TypeOption` field type. - /// - fn transform_type_option_cell_str( - &self, - _cell_str: &str, - _decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> Option<::CellData> { - None - } -} - -pub trait TypeOptionCellDataFilter: TypeOption + CellDataDecoder { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool; -} - -#[inline(always)] -pub fn default_order() -> Ordering { - Ordering::Equal -} - -pub trait TypeOptionCellDataCompare: TypeOption { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering; -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/type_option_cell.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/type_option_cell.rs deleted file mode 100644 index b3f1e8e8cd..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/type_option_cell.rs +++ /dev/null @@ -1,571 +0,0 @@ -use crate::entities::FieldType; -use crate::services::cell::{ - AtomicCellDataCache, AtomicCellFilterCache, CellDataChangeset, CellDataDecoder, CellProtobufBlob, - FromCellChangesetString, FromCellString, TypeCellData, -}; -use crate::services::field::{ - CheckboxTypeOptionPB, ChecklistTypeOptionPB, DateTypeOptionPB, MultiSelectTypeOptionPB, - NumberTypeOptionPB, RichTextTypeOptionPB, SingleSelectTypeOptionPB, TypeOption, - TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, - URLTypeOptionPB, -}; -use crate::services::filter::FilterType; -use database_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; -use flowy_error::FlowyResult; -use std::any::Any; -use std::cmp::Ordering; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; - -/// A helper trait that used to erase the `Self` of `TypeOption` trait to make it become a Object-safe trait -/// Only object-safe traits can be made into trait objects. -/// > Object-safe traits are traits with methods that follow these two rules: -/// 1.the return type is not Self. -/// 2.there are no generic types parameters. -/// -pub trait TypeOptionCellDataHandler { - fn handle_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> FlowyResult; - - fn handle_cell_changeset( - &self, - cell_changeset: String, - old_type_cell_data: Option, - field_rev: &FieldRevision, - ) -> FlowyResult; - - fn handle_cell_compare( - &self, - left_cell_data: &str, - right_cell_data: &str, - field_rev: &FieldRevision, - ) -> Ordering; - - fn handle_cell_filter( - &self, - filter_type: &FilterType, - field_rev: &FieldRevision, - type_cell_data: TypeCellData, - ) -> bool; - - /// Decode the cell_str to corresponding cell data, and then return the display string of the - /// cell data. - fn stringify_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> String; - - fn get_cell_data( - &self, - cell_str: String, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> FlowyResult; -} - -struct CellDataCacheKey(u64); -impl CellDataCacheKey { - pub fn new(field_rev: &FieldRevision, decoded_field_type: FieldType, cell_str: &str) -> Self { - let mut hasher = DefaultHasher::new(); - if let Some(type_option_str) = field_rev.get_type_option_str(&decoded_field_type) { - type_option_str.hash(&mut hasher); - } - hasher.write(field_rev.id.as_bytes()); - hasher.write_u8(decoded_field_type as u8); - cell_str.hash(&mut hasher); - Self(hasher.finish()) - } -} - -impl AsRef for CellDataCacheKey { - fn as_ref(&self) -> &u64 { - &self.0 - } -} - -struct TypeOptionCellDataHandlerImpl { - inner: T, - cell_data_cache: Option, - cell_filter_cache: Option, -} - -impl TypeOptionCellDataHandlerImpl -where - T: TypeOption - + CellDataDecoder - + CellDataChangeset - + TypeOptionCellData - + TypeOptionTransform - + TypeOptionCellDataFilter - + TypeOptionCellDataCompare - + 'static, -{ - pub fn new_with_boxed( - inner: T, - cell_filter_cache: Option, - cell_data_cache: Option, - ) -> Box { - Box::new(Self { - inner, - cell_data_cache, - cell_filter_cache, - }) as Box - } -} - -impl TypeOptionCellDataHandlerImpl -where - T: TypeOption + CellDataDecoder, -{ - fn get_decoded_cell_data( - &self, - cell_str: String, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> FlowyResult<::CellData> { - let key = CellDataCacheKey::new(field_rev, decoded_field_type.clone(), &cell_str); - if let Some(cell_data_cache) = self.cell_data_cache.as_ref() { - let read_guard = cell_data_cache.read(); - if let Some(cell_data) = read_guard.get(key.as_ref()).cloned() { - tracing::trace!( - "Cell cache hit: field_type:{}, cell_str: {}, cell_data: {:?}", - decoded_field_type, - cell_str, - cell_data - ); - return Ok(cell_data); - } - } - - let cell_data = self.decode_cell_str(cell_str.clone(), decoded_field_type, field_rev)?; - if let Some(cell_data_cache) = self.cell_data_cache.as_ref() { - tracing::trace!( - "Cell cache update: field_type:{}, cell_str: {}, cell_data: {:?}", - decoded_field_type, - cell_str, - cell_data - ); - cell_data_cache - .write() - .insert(key.as_ref(), cell_data.clone()); - } - Ok(cell_data) - } - - fn set_decoded_cell_data( - &self, - cell_str: &str, - cell_data: ::CellData, - field_rev: &FieldRevision, - ) { - if let Some(cell_data_cache) = self.cell_data_cache.as_ref() { - let field_type: FieldType = field_rev.ty.into(); - let key = CellDataCacheKey::new(field_rev, field_type.clone(), cell_str); - tracing::trace!( - "Cell cache update: field_type:{}, cell_str: {}, cell_data: {:?}", - field_type, - cell_str, - cell_data - ); - cell_data_cache.write().insert(key.as_ref(), cell_data); - } - } -} - -impl std::ops::Deref for TypeOptionCellDataHandlerImpl { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl TypeOption for TypeOptionCellDataHandlerImpl -where - T: TypeOption, -{ - type CellData = T::CellData; - type CellChangeset = T::CellChangeset; - type CellProtobufType = T::CellProtobufType; - type CellFilter = T::CellFilter; -} - -impl TypeOptionCellDataHandler for TypeOptionCellDataHandlerImpl -where - T: TypeOption - + CellDataDecoder - + CellDataChangeset - + TypeOptionCellData - + TypeOptionTransform - + TypeOptionCellDataFilter - + TypeOptionCellDataCompare, -{ - fn handle_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> FlowyResult { - let cell_data = self - .get_cell_data(cell_str, decoded_field_type, field_rev)? - .unbox_or_default::<::CellData>(); - - CellProtobufBlob::from(self.convert_to_protobuf(cell_data)) - } - - fn handle_cell_changeset( - &self, - cell_changeset: String, - old_type_cell_data: Option, - field_rev: &FieldRevision, - ) -> FlowyResult { - let changeset = ::CellChangeset::from_changeset(cell_changeset)?; - let (cell_str, cell_data) = self.apply_changeset(changeset, old_type_cell_data)?; - self.set_decoded_cell_data(&cell_str, cell_data, field_rev); - Ok(cell_str) - } - - fn handle_cell_compare( - &self, - left_cell_data: &str, - right_cell_data: &str, - field_rev: &FieldRevision, - ) -> Ordering { - let field_type: FieldType = field_rev.ty.into(); - let left = self - .get_decoded_cell_data(left_cell_data.to_owned(), &field_type, field_rev) - .unwrap_or_default(); - let right = self - .get_decoded_cell_data(right_cell_data.to_owned(), &field_type, field_rev) - .unwrap_or_default(); - self.apply_cmp(&left, &right) - } - - fn handle_cell_filter( - &self, - filter_type: &FilterType, - field_rev: &FieldRevision, - type_cell_data: TypeCellData, - ) -> bool { - let perform_filter = || { - let filter_cache = self.cell_filter_cache.as_ref()?.read(); - let cell_filter = filter_cache.get::<::CellFilter>(filter_type)?; - let cell_data = self - .get_decoded_cell_data(type_cell_data.cell_str, &filter_type.field_type, field_rev) - .ok()?; - Some(self.apply_filter(cell_filter, &filter_type.field_type, &cell_data)) - }; - - perform_filter().unwrap_or(true) - } - - fn stringify_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> String { - if self.transformable() { - let cell_data = self.transform_type_option_cell_str(&cell_str, decoded_field_type, field_rev); - if let Some(cell_data) = cell_data { - return self.decode_cell_data_to_str(cell_data); - } - } - match ::CellData::from_cell_str(&cell_str) { - Ok(cell_data) => self.decode_cell_data_to_str(cell_data), - Err(_) => "".to_string(), - } - } - - fn get_cell_data( - &self, - cell_str: String, - decoded_field_type: &FieldType, - field_rev: &FieldRevision, - ) -> FlowyResult { - // tracing::debug!("get_cell_data: {:?}", std::any::type_name::()); - let cell_data = if self.transformable() { - match self.transform_type_option_cell_str(&cell_str, decoded_field_type, field_rev) { - None => self.get_decoded_cell_data(cell_str, decoded_field_type, field_rev)?, - Some(cell_data) => cell_data, - } - } else { - self.get_decoded_cell_data(cell_str, decoded_field_type, field_rev)? - }; - Ok(BoxCellData::new(cell_data)) - } -} - -pub struct TypeOptionCellExt<'a> { - field_rev: &'a FieldRevision, - cell_data_cache: Option, - cell_filter_cache: Option, -} - -impl<'a> TypeOptionCellExt<'a> { - pub fn new_with_cell_data_cache( - field_rev: &'a FieldRevision, - cell_data_cache: Option, - ) -> Self { - Self { - field_rev, - cell_data_cache, - cell_filter_cache: None, - } - } - - pub fn new( - field_rev: &'a FieldRevision, - cell_data_cache: Option, - cell_filter_cache: Option, - ) -> Self { - let mut this = Self::new_with_cell_data_cache(field_rev, cell_data_cache); - this.cell_filter_cache = cell_filter_cache; - this - } - - pub fn get_cells(&self) -> Vec { - let field_type: FieldType = self.field_rev.ty.into(); - match self.get_type_option_cell_data_handler(&field_type) { - None => vec![], - Some(_handler) => { - todo!() - }, - } - } - - pub fn get_type_option_cell_data_handler( - &self, - field_type: &FieldType, - ) -> Option> { - match field_type { - FieldType::RichText => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - FieldType::Number => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - FieldType::DateTime => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - FieldType::SingleSelect => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - FieldType::MultiSelect => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - FieldType::Checkbox => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - FieldType::URL => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - FieldType::Checklist => self - .field_rev - .get_type_option::(field_type.into()) - .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) - }), - } - } -} - -pub fn transform_type_option( - type_option_data: &str, - new_field_type: &FieldType, - old_type_option_data: Option, - old_field_type: FieldType, -) -> String { - let mut transform_handler = get_type_option_transform_handler(type_option_data, new_field_type); - if let Some(old_type_option_data) = old_type_option_data { - transform_handler.transform(old_field_type, old_type_option_data); - } - transform_handler.json_str() -} - -/// A helper trait that used to erase the `Self` of `TypeOption` trait to make it become a Object-safe trait. -pub trait TypeOptionTransformHandler { - fn transform(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String); - - fn json_str(&self) -> String; -} - -impl TypeOptionTransformHandler for T -where - T: TypeOptionTransform + TypeOptionDataSerializer, -{ - fn transform(&mut self, old_type_option_field_type: FieldType, old_type_option_data: String) { - if self.transformable() { - self.transform_type_option(old_type_option_field_type, old_type_option_data) - } - } - - fn json_str(&self) -> String { - self.json_str() - } -} -fn get_type_option_transform_handler( - type_option_data: &str, - field_type: &FieldType, -) -> Box { - match field_type { - FieldType::RichText => Box::new(RichTextTypeOptionPB::from_json_str(type_option_data)) - as Box, - FieldType::Number => Box::new(NumberTypeOptionPB::from_json_str(type_option_data)) - as Box, - FieldType::DateTime => Box::new(DateTypeOptionPB::from_json_str(type_option_data)) - as Box, - FieldType::SingleSelect => Box::new(SingleSelectTypeOptionPB::from_json_str(type_option_data)) - as Box, - FieldType::MultiSelect => Box::new(MultiSelectTypeOptionPB::from_json_str(type_option_data)) - as Box, - FieldType::Checkbox => Box::new(CheckboxTypeOptionPB::from_json_str(type_option_data)) - as Box, - FieldType::URL => Box::new(URLTypeOptionPB::from_json_str(type_option_data)) - as Box, - FieldType::Checklist => Box::new(ChecklistTypeOptionPB::from_json_str(type_option_data)) - as Box, - } -} - -pub struct BoxCellData(Box); - -impl BoxCellData { - fn new(value: T) -> Self - where - T: Send + Sync + 'static, - { - Self(Box::new(value)) - } - - fn unbox_or_default(self) -> T - where - T: Default + 'static, - { - match self.0.downcast::() { - Ok(value) => *value, - Err(_) => T::default(), - } - } - - pub(crate) fn unbox_or_none(self) -> Option - where - T: Default + 'static, - { - match self.0.downcast::() { - Ok(value) => Some(*value), - Err(_) => None, - } - } - - #[allow(dead_code)] - fn downcast_ref(&self) -> Option<&T> { - self.0.downcast_ref() - } -} - -pub struct RowSingleCellData { - pub row_id: String, - pub field_id: String, - pub field_type: FieldType, - pub cell_data: BoxCellData, -} - -macro_rules! into_cell_data { - ($func_name:ident,$return_ty:ty) => { - #[allow(dead_code)] - pub fn $func_name(self) -> Option<$return_ty> { - self.cell_data.unbox_or_none() - } - }; -} - -impl RowSingleCellData { - into_cell_data!( - into_text_field_cell_data, - ::CellData - ); - into_cell_data!( - into_number_field_cell_data, - ::CellData - ); - into_cell_data!( - into_url_field_cell_data, - ::CellData - ); - into_cell_data!( - into_single_select_field_cell_data, - ::CellData - ); - into_cell_data!( - into_multi_select_field_cell_data, - ::CellData - ); - into_cell_data!( - into_date_field_cell_data, - ::CellData - ); - into_cell_data!( - into_check_list_field_cell_data, - ::CellData - ); -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/mod.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/mod.rs deleted file mode 100644 index 8f6cb884df..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![allow(clippy::module_inception)] -mod url_tests; -mod url_type_option; -mod url_type_option_entities; - -pub use url_type_option::*; -pub use url_type_option_entities::*; diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_tests.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_tests.rs deleted file mode 100644 index aae857c93a..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_tests.rs +++ /dev/null @@ -1,167 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::entities::FieldType; - use crate::services::cell::CellDataChangeset; - - use crate::services::field::FieldBuilder; - use crate::services::field::URLTypeOptionPB; - use database_model::FieldRevision; - - /// The expected_str will equal to the input string, but the expected_url will be empty if there's no - /// http url in the input string. - #[test] - fn url_type_option_does_not_contain_url_test() { - let type_option = URLTypeOptionPB::default(); - let field_type = FieldType::URL; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_url(&type_option, "123", "123", "", &field_rev); - assert_url(&type_option, "", "", "", &field_rev); - } - - /// The expected_str will equal to the input string, but the expected_url will not be empty - /// if there's a http url in the input string. - #[test] - fn url_type_option_contains_url_test() { - let type_option = URLTypeOptionPB::default(); - let field_type = FieldType::URL; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_url( - &type_option, - "AppFlowy website - https://www.appflowy.io", - "AppFlowy website - https://www.appflowy.io", - "https://www.appflowy.io/", - &field_rev, - ); - - assert_url( - &type_option, - "AppFlowy website appflowy.io", - "AppFlowy website appflowy.io", - "https://appflowy.io", - &field_rev, - ); - } - - /// if there's a http url and some words following it in the input string. - #[test] - fn url_type_option_contains_url_with_string_after_test() { - let type_option = URLTypeOptionPB::default(); - let field_type = FieldType::URL; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_url( - &type_option, - "AppFlowy website - https://www.appflowy.io welcome!", - "AppFlowy website - https://www.appflowy.io welcome!", - "https://www.appflowy.io/", - &field_rev, - ); - - assert_url( - &type_option, - "AppFlowy website appflowy.io welcome!", - "AppFlowy website appflowy.io welcome!", - "https://appflowy.io", - &field_rev, - ); - } - - /// if there's a http url and special words following it in the input string. - #[test] - fn url_type_option_contains_url_with_special_string_after_test() { - let type_option = URLTypeOptionPB::default(); - let field_type = FieldType::URL; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_url( - &type_option, - "AppFlowy website - https://www.appflowy.io!", - "AppFlowy website - https://www.appflowy.io!", - "https://www.appflowy.io/", - &field_rev, - ); - - assert_url( - &type_option, - "AppFlowy website appflowy.io!", - "AppFlowy website appflowy.io!", - "https://appflowy.io", - &field_rev, - ); - } - - /// if there's a level4 url in the input string. - #[test] - fn level4_url_type_test() { - let type_option = URLTypeOptionPB::default(); - let field_type = FieldType::URL; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_url( - &type_option, - "test - https://tester.testgroup.appflowy.io", - "test - https://tester.testgroup.appflowy.io", - "https://tester.testgroup.appflowy.io/", - &field_rev, - ); - - assert_url( - &type_option, - "test tester.testgroup.appflowy.io", - "test tester.testgroup.appflowy.io", - "https://tester.testgroup.appflowy.io", - &field_rev, - ); - } - - /// urls with different top level domains. - #[test] - fn different_top_level_domains_test() { - let type_option = URLTypeOptionPB::default(); - let field_type = FieldType::URL; - let field_rev = FieldBuilder::from_field_type(&field_type).build(); - assert_url( - &type_option, - "appflowy - https://appflowy.com", - "appflowy - https://appflowy.com", - "https://appflowy.com/", - &field_rev, - ); - - assert_url( - &type_option, - "appflowy - https://appflowy.top", - "appflowy - https://appflowy.top", - "https://appflowy.top/", - &field_rev, - ); - - assert_url( - &type_option, - "appflowy - https://appflowy.net", - "appflowy - https://appflowy.net", - "https://appflowy.net/", - &field_rev, - ); - - assert_url( - &type_option, - "appflowy - https://appflowy.edu", - "appflowy - https://appflowy.edu", - "https://appflowy.edu/", - &field_rev, - ); - } - - fn assert_url( - type_option: &URLTypeOptionPB, - input_str: &str, - expected_str: &str, - expected_url: &str, - _field_rev: &FieldRevision, - ) { - let decode_cell_data = type_option - .apply_changeset(input_str.to_owned(), None) - .unwrap() - .1; - assert_eq!(expected_str.to_owned(), decode_cell_data.content); - assert_eq!(expected_url.to_owned(), decode_cell_data.url); - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_type_option.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_type_option.rs deleted file mode 100644 index 9a5ec4318d..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_type_option.rs +++ /dev/null @@ -1,152 +0,0 @@ -use crate::entities::{FieldType, TextFilterPB}; -use crate::impl_type_option; -use crate::services::cell::{CellDataChangeset, CellDataDecoder, FromCellString, TypeCellData}; -use crate::services::field::{ - BoxTypeOptionBuilder, TypeOption, TypeOptionBuilder, TypeOptionCellData, - TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionTransform, URLCellData, - URLCellDataPB, -}; -use bytes::Bytes; -use database_model::{FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; -use fancy_regex::Regex; -use flowy_derive::ProtoBuf; -use flowy_error::FlowyResult; -use lazy_static::lazy_static; -use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; - -#[derive(Default)] -pub struct URLTypeOptionBuilder(URLTypeOptionPB); -impl_into_box_type_option_builder!(URLTypeOptionBuilder); -impl_builder_from_json_str_and_from_bytes!(URLTypeOptionBuilder, URLTypeOptionPB); - -impl TypeOptionBuilder for URLTypeOptionBuilder { - fn field_type(&self) -> FieldType { - FieldType::URL - } - - fn serializer(&self) -> &dyn TypeOptionDataSerializer { - &self.0 - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)] -pub struct URLTypeOptionPB { - #[pb(index = 1)] - pub url: String, - - #[pb(index = 2)] - pub content: String, -} -impl_type_option!(URLTypeOptionPB, FieldType::URL); - -impl TypeOption for URLTypeOptionPB { - type CellData = URLCellData; - type CellChangeset = URLCellChangeset; - type CellProtobufType = URLCellDataPB; - type CellFilter = TextFilterPB; -} - -impl TypeOptionTransform for URLTypeOptionPB {} - -impl TypeOptionCellData for URLTypeOptionPB { - fn convert_to_protobuf( - &self, - cell_data: ::CellData, - ) -> ::CellProtobufType { - cell_data.into() - } - - fn decode_type_option_cell_str( - &self, - cell_str: String, - ) -> FlowyResult<::CellData> { - URLCellData::from_cell_str(&cell_str) - } -} - -impl CellDataDecoder for URLTypeOptionPB { - fn decode_cell_str( - &self, - cell_str: String, - decoded_field_type: &FieldType, - _field_rev: &FieldRevision, - ) -> FlowyResult<::CellData> { - if !decoded_field_type.is_url() { - return Ok(Default::default()); - } - - self.decode_type_option_cell_str(cell_str) - } - - fn decode_cell_data_to_str(&self, cell_data: ::CellData) -> String { - cell_data.content - } -} - -pub type URLCellChangeset = String; - -impl CellDataChangeset for URLTypeOptionPB { - fn apply_changeset( - &self, - changeset: ::CellChangeset, - _type_cell_data: Option, - ) -> FlowyResult<(String, ::CellData)> { - let mut url = "".to_string(); - if let Ok(Some(m)) = URL_REGEX.find(&changeset) { - url = auto_append_scheme(m.as_str()); - } - let url_cell_data = URLCellData { - url, - content: changeset, - }; - Ok((url_cell_data.to_string(), url_cell_data)) - } -} - -impl TypeOptionCellDataFilter for URLTypeOptionPB { - fn apply_filter( - &self, - filter: &::CellFilter, - field_type: &FieldType, - cell_data: &::CellData, - ) -> bool { - if !field_type.is_url() { - return true; - } - - filter.is_visible(cell_data) - } -} - -impl TypeOptionCellDataCompare for URLTypeOptionPB { - fn apply_cmp( - &self, - cell_data: &::CellData, - other_cell_data: &::CellData, - ) -> Ordering { - cell_data.content.cmp(&other_cell_data.content) - } -} -fn auto_append_scheme(s: &str) -> String { - // Only support https scheme by now - match url::Url::parse(s) { - Ok(url) => { - if url.scheme() == "https" { - url.into() - } else { - format!("https://{}", s) - } - }, - Err(_) => { - format!("https://{}", s) - }, - } -} - -lazy_static! { - static ref URL_REGEX: Regex = Regex::new( - "[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)" - ) - .unwrap(); -} diff --git a/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_type_option_entities.rs b/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_type_option_entities.rs deleted file mode 100644 index e85ad35d9f..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/field/type_options/url_type_option/url_type_option_entities.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::services::cell::{CellProtobufBlobParser, DecodedCellData, FromCellString}; -use bytes::Bytes; -use flowy_derive::ProtoBuf; -use flowy_error::{internal_error, FlowyResult}; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Default, ProtoBuf)] -pub struct URLCellDataPB { - #[pb(index = 1)] - pub url: String, - - #[pb(index = 2)] - pub content: String, -} - -impl From for URLCellDataPB { - fn from(data: URLCellData) -> Self { - Self { - url: data.url, - content: data.content, - } - } -} - -impl DecodedCellData for URLCellDataPB { - type Object = URLCellDataPB; - - fn is_empty(&self) -> bool { - self.content.is_empty() - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct URLCellData { - pub url: String, - pub content: String, -} - -impl URLCellData { - pub fn new(s: &str) -> Self { - Self { - url: "".to_string(), - content: s.to_string(), - } - } - - pub fn to_json(&self) -> FlowyResult { - serde_json::to_string(self).map_err(internal_error) - } -} - -impl From for URLCellData { - fn from(data: URLCellDataPB) -> Self { - Self { - url: data.url, - content: data.content, - } - } -} - -impl AsRef for URLCellData { - fn as_ref(&self) -> &str { - &self.url - } -} - -impl DecodedCellData for URLCellData { - type Object = URLCellData; - - fn is_empty(&self) -> bool { - self.content.is_empty() - } -} - -pub struct URLCellDataParser(); -impl CellProtobufBlobParser for URLCellDataParser { - type Object = URLCellDataPB; - - fn parser(bytes: &Bytes) -> FlowyResult { - URLCellDataPB::try_from(bytes.as_ref()).map_err(internal_error) - } -} - -impl FromCellString for URLCellData { - fn from_cell_str(s: &str) -> FlowyResult { - serde_json::from_str::(s).map_err(internal_error) - } -} - -impl ToString for URLCellData { - fn to_string(&self) -> String { - self.to_json().unwrap() - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/filter/controller.rs b/frontend/rust-lib/flowy-database/src/services/filter/controller.rs deleted file mode 100644 index 37ec5eb90f..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/filter/controller.rs +++ /dev/null @@ -1,457 +0,0 @@ -use crate::entities::filter_entities::*; -use crate::entities::{FieldType, InsertedRowPB, RowPB}; -use crate::services::cell::{ - AnyTypeCache, AtomicCellDataCache, AtomicCellFilterCache, TypeCellData, -}; -use crate::services::database_view::{DatabaseViewChanged, DatabaseViewChangedNotifier}; -use crate::services::field::*; -use crate::services::filter::{ - FilterChangeset, FilterResult, FilterResultNotification, FilterType, -}; -use crate::services::row::DatabaseBlockRowRevision; -use dashmap::DashMap; -use database_model::{CellRevision, FieldId, FieldRevision, FilterRevision, RowRevision}; -use flowy_error::FlowyResult; -use flowy_task::{QualityOfService, Task, TaskContent, TaskDispatcher}; -use lib_infra::future::Fut; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::str::FromStr; -use std::sync::Arc; -use tokio::sync::RwLock; - -type RowId = String; -pub trait FilterDelegate: Send + Sync + 'static { - fn get_filter_rev(&self, filter_type: FilterType) -> Fut>>; - fn get_field_rev(&self, field_id: &str) -> Fut>>; - fn get_field_revs(&self, field_ids: Option>) -> Fut>>; - fn get_blocks(&self) -> Fut>; - fn get_row_rev(&self, rows_id: &str) -> Fut)>>; -} - -pub trait FromFilterString { - fn from_filter_rev(filter_rev: &FilterRevision) -> Self - where - Self: Sized; -} - -pub struct FilterController { - view_id: String, - handler_id: String, - delegate: Box, - result_by_row_id: DashMap, - cell_data_cache: AtomicCellDataCache, - cell_filter_cache: AtomicCellFilterCache, - task_scheduler: Arc>, - notifier: DatabaseViewChangedNotifier, -} - -impl Drop for FilterController { - fn drop(&mut self) { - tracing::trace!("Drop {}", std::any::type_name::()); - } -} - -impl FilterController { - pub async fn new( - view_id: &str, - handler_id: &str, - delegate: T, - task_scheduler: Arc>, - filter_revs: Vec>, - cell_data_cache: AtomicCellDataCache, - notifier: DatabaseViewChangedNotifier, - ) -> Self - where - T: FilterDelegate + 'static, - { - let this = Self { - view_id: view_id.to_string(), - handler_id: handler_id.to_string(), - delegate: Box::new(delegate), - result_by_row_id: DashMap::default(), - cell_data_cache, - cell_filter_cache: AnyTypeCache::::new(), - task_scheduler, - notifier, - }; - this.refresh_filters(filter_revs).await; - this - } - - pub async fn close(&self) { - if let Ok(mut task_scheduler) = self.task_scheduler.try_write() { - task_scheduler.unregister_handler(&self.handler_id).await; - } else { - tracing::error!("Try to get the lock of task_scheduler failed"); - } - } - - #[tracing::instrument(name = "schedule_filter_task", level = "trace", skip(self))] - async fn gen_task(&self, task_type: FilterEvent, qos: QualityOfService) { - let task_id = self.task_scheduler.read().await.next_task_id(); - let task = Task::new( - &self.handler_id, - task_id, - TaskContent::Text(task_type.to_string()), - qos, - ); - self.task_scheduler.write().await.add_task(task); - } - - pub async fn filter_row_revs(&self, row_revs: &mut Vec>) { - if self.cell_filter_cache.read().is_empty() { - return; - } - let field_rev_by_field_id = self.get_filter_revs_map().await; - row_revs.iter().for_each(|row_rev| { - let _ = filter_row( - row_rev, - &self.result_by_row_id, - &field_rev_by_field_id, - &self.cell_data_cache, - &self.cell_filter_cache, - ); - }); - - row_revs.retain(|row_rev| { - self - .result_by_row_id - .get(&row_rev.id) - .map(|result| result.is_visible()) - .unwrap_or(false) - }); - } - - async fn get_filter_revs_map(&self) -> HashMap> { - self - .delegate - .get_field_revs(None) - .await - .into_iter() - .map(|field_rev| (field_rev.id.clone(), field_rev)) - .collect::>>() - } - - #[tracing::instrument( - name = "process_filter_task", - level = "trace", - skip_all, - fields(filter_result), - err - )] - pub async fn process(&self, predicate: &str) -> FlowyResult<()> { - let event_type = FilterEvent::from_str(predicate).unwrap(); - match event_type { - FilterEvent::FilterDidChanged => self.filter_all_rows().await?, - FilterEvent::RowDidChanged(row_id) => self.filter_row(row_id).await?, - } - Ok(()) - } - - async fn filter_row(&self, row_id: String) -> FlowyResult<()> { - if let Some((_, row_rev)) = self.delegate.get_row_rev(&row_id).await { - let field_rev_by_field_id = self.get_filter_revs_map().await; - let mut notification = - FilterResultNotification::new(self.view_id.clone(), row_rev.block_id.clone()); - if let Some((row_id, is_visible)) = filter_row( - &row_rev, - &self.result_by_row_id, - &field_rev_by_field_id, - &self.cell_data_cache, - &self.cell_filter_cache, - ) { - if is_visible { - if let Some((index, row_rev)) = self.delegate.get_row_rev(&row_id).await { - let row_pb = RowPB::from(row_rev.as_ref()); - notification - .visible_rows - .push(InsertedRowPB::with_index(row_pb, index as i32)) - } - } else { - notification.invisible_rows.push(row_id); - } - } - - let _ = self - .notifier - .send(DatabaseViewChanged::FilterNotification(notification)); - } - Ok(()) - } - - async fn filter_all_rows(&self) -> FlowyResult<()> { - let field_rev_by_field_id = self.get_filter_revs_map().await; - for block in self.delegate.get_blocks().await.into_iter() { - // The row_ids contains the row that its visibility was changed. - let mut visible_rows = vec![]; - let mut invisible_rows = vec![]; - - for (index, row_rev) in block.row_revs.iter().enumerate() { - if let Some((row_id, is_visible)) = filter_row( - row_rev, - &self.result_by_row_id, - &field_rev_by_field_id, - &self.cell_data_cache, - &self.cell_filter_cache, - ) { - if is_visible { - let row_pb = RowPB::from(row_rev.as_ref()); - visible_rows.push(InsertedRowPB::with_index(row_pb, index as i32)) - } else { - invisible_rows.push(row_id); - } - } - } - - let notification = FilterResultNotification { - view_id: self.view_id.clone(), - block_id: block.block_id, - invisible_rows, - visible_rows, - }; - tracing::Span::current().record("filter_result", format!("{:?}", ¬ification).as_str()); - let _ = self - .notifier - .send(DatabaseViewChanged::FilterNotification(notification)); - } - Ok(()) - } - - pub async fn did_receive_row_changed(&self, row_id: &str) { - self - .gen_task( - FilterEvent::RowDidChanged(row_id.to_string()), - QualityOfService::UserInteractive, - ) - .await - } - - #[tracing::instrument(level = "trace", skip(self))] - pub async fn did_receive_changes( - &self, - changeset: FilterChangeset, - ) -> Option { - let mut notification: Option = None; - if let Some(filter_type) = &changeset.insert_filter { - if let Some(filter) = self.filter_from_filter_type(filter_type).await { - notification = Some(FilterChangesetNotificationPB::from_insert( - &self.view_id, - vec![filter], - )); - } - if let Some(filter_rev) = self.delegate.get_filter_rev(filter_type.clone()).await { - self.refresh_filters(vec![filter_rev]).await; - } - } - - if let Some(updated_filter_type) = changeset.update_filter { - if let Some(old_filter_type) = updated_filter_type.old { - let new_filter = self.filter_from_filter_type(&updated_filter_type.new).await; - let old_filter = self.filter_from_filter_type(&old_filter_type).await; - - // Get the filter id - let mut filter_id = old_filter.map(|filter| filter.id); - if filter_id.is_none() { - filter_id = new_filter.as_ref().map(|filter| filter.id.clone()); - } - - // Update the corresponding filter in the cache - if let Some(filter_rev) = self - .delegate - .get_filter_rev(updated_filter_type.new.clone()) - .await - { - self.refresh_filters(vec![filter_rev]).await; - } - - if let Some(filter_id) = filter_id { - notification = Some(FilterChangesetNotificationPB::from_update( - &self.view_id, - vec![UpdatedFilter { - filter_id, - filter: new_filter, - }], - )); - } - } - } - - if let Some(filter_type) = &changeset.delete_filter { - if let Some(filter) = self.filter_from_filter_type(filter_type).await { - notification = Some(FilterChangesetNotificationPB::from_delete( - &self.view_id, - vec![filter], - )); - } - self.cell_filter_cache.write().remove(filter_type); - } - - self - .gen_task(FilterEvent::FilterDidChanged, QualityOfService::Background) - .await; - tracing::trace!("{:?}", notification); - notification - } - - async fn filter_from_filter_type(&self, filter_type: &FilterType) -> Option { - self - .delegate - .get_filter_rev(filter_type.clone()) - .await - .map(|filter| FilterPB::from(filter.as_ref())) - } - - #[tracing::instrument(level = "trace", skip_all)] - async fn refresh_filters(&self, filter_revs: Vec>) { - for filter_rev in filter_revs { - if let Some(field_rev) = self.delegate.get_field_rev(&filter_rev.field_id).await { - let filter_type = FilterType::from(&field_rev); - tracing::trace!("Create filter with type: {:?}", filter_type); - match &filter_type.field_type { - FieldType::RichText => { - self.cell_filter_cache.write().insert( - &filter_type, - TextFilterPB::from_filter_rev(filter_rev.as_ref()), - ); - }, - FieldType::Number => { - self.cell_filter_cache.write().insert( - &filter_type, - NumberFilterPB::from_filter_rev(filter_rev.as_ref()), - ); - }, - FieldType::DateTime => { - self.cell_filter_cache.write().insert( - &filter_type, - DateFilterPB::from_filter_rev(filter_rev.as_ref()), - ); - }, - FieldType::SingleSelect | FieldType::MultiSelect => { - self.cell_filter_cache.write().insert( - &filter_type, - SelectOptionFilterPB::from_filter_rev(filter_rev.as_ref()), - ); - }, - FieldType::Checkbox => { - self.cell_filter_cache.write().insert( - &filter_type, - CheckboxFilterPB::from_filter_rev(filter_rev.as_ref()), - ); - }, - FieldType::URL => { - self.cell_filter_cache.write().insert( - &filter_type, - TextFilterPB::from_filter_rev(filter_rev.as_ref()), - ); - }, - FieldType::Checklist => { - self.cell_filter_cache.write().insert( - &filter_type, - ChecklistFilterPB::from_filter_rev(filter_rev.as_ref()), - ); - }, - } - } - } - } -} - -/// Returns None if there is no change in this row after applying the filter -#[tracing::instrument(level = "trace", skip_all)] -fn filter_row( - row_rev: &Arc, - result_by_row_id: &DashMap, - field_rev_by_field_id: &HashMap>, - cell_data_cache: &AtomicCellDataCache, - cell_filter_cache: &AtomicCellFilterCache, -) -> Option<(String, bool)> { - // Create a filter result cache if it's not exist - let mut filter_result = result_by_row_id - .entry(row_rev.id.clone()) - .or_insert_with(FilterResult::default); - let old_is_visible = filter_result.is_visible(); - - // Iterate each cell of the row to check its visibility - for (field_id, field_rev) in field_rev_by_field_id { - let filter_type = FilterType::from(field_rev); - if !cell_filter_cache.read().contains(&filter_type) { - filter_result.visible_by_filter_id.remove(&filter_type); - continue; - } - - let cell_rev = row_rev.cells.get(field_id); - // if the visibility of the cell_rew is changed, which means the visibility of the - // row is changed too. - if let Some(is_visible) = filter_cell( - &filter_type, - field_rev, - cell_rev, - cell_data_cache, - cell_filter_cache, - ) { - filter_result - .visible_by_filter_id - .insert(filter_type, is_visible); - } - } - - let is_visible = filter_result.is_visible(); - if old_is_visible != is_visible { - Some((row_rev.id.clone(), is_visible)) - } else { - None - } -} - -// Returns None if there is no change in this cell after applying the filter -// Returns Some if the visibility of the cell is changed - -#[tracing::instrument(level = "trace", skip_all, fields(cell_content))] -fn filter_cell( - filter_type: &FilterType, - field_rev: &Arc, - cell_rev: Option<&CellRevision>, - cell_data_cache: &AtomicCellDataCache, - cell_filter_cache: &AtomicCellFilterCache, -) -> Option { - let type_cell_data = match cell_rev { - None => TypeCellData::from_field_type(&filter_type.field_type), - Some(cell_rev) => match TypeCellData::try_from(cell_rev) { - Ok(cell_data) => cell_data, - Err(err) => { - tracing::error!("Deserialize TypeCellData failed: {}", err); - TypeCellData::from_field_type(&filter_type.field_type) - }, - }, - }; - - let handler = TypeOptionCellExt::new( - field_rev.as_ref(), - Some(cell_data_cache.clone()), - Some(cell_filter_cache.clone()), - ) - .get_type_option_cell_data_handler(&filter_type.field_type)?; - - let is_visible = handler.handle_cell_filter(filter_type, field_rev.as_ref(), type_cell_data); - Some(is_visible) -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -enum FilterEvent { - FilterDidChanged, - RowDidChanged(String), -} - -impl ToString for FilterEvent { - fn to_string(&self) -> String { - serde_json::to_string(self).unwrap() - } -} - -impl FromStr for FilterEvent { - type Err = serde_json::Error; - fn from_str(s: &str) -> Result { - serde_json::from_str(s) - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/filter/entities.rs b/frontend/rust-lib/flowy-database/src/services/filter/entities.rs deleted file mode 100644 index 846edef9d7..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/filter/entities.rs +++ /dev/null @@ -1,130 +0,0 @@ -use crate::entities::{ - AlterFilterParams, DatabaseSettingChangesetParams, DeleteFilterParams, FieldType, InsertedRowPB, -}; -use database_model::{FieldRevision, FieldTypeRevision}; -use std::sync::Arc; - -#[derive(Debug)] -pub struct FilterChangeset { - pub(crate) insert_filter: Option, - pub(crate) update_filter: Option, - pub(crate) delete_filter: Option, -} - -#[derive(Debug)] -pub struct UpdatedFilterType { - pub old: Option, - pub new: FilterType, -} - -impl UpdatedFilterType { - pub fn new(old: Option, new: FilterType) -> UpdatedFilterType { - Self { old, new } - } -} - -impl FilterChangeset { - pub fn from_insert(filter_type: FilterType) -> Self { - Self { - insert_filter: Some(filter_type), - update_filter: None, - delete_filter: None, - } - } - - pub fn from_update(filter_type: UpdatedFilterType) -> Self { - Self { - insert_filter: None, - update_filter: Some(filter_type), - delete_filter: None, - } - } - pub fn from_delete(filter_type: FilterType) -> Self { - Self { - insert_filter: None, - update_filter: None, - delete_filter: Some(filter_type), - } - } -} - -impl std::convert::From<&DatabaseSettingChangesetParams> for FilterChangeset { - fn from(params: &DatabaseSettingChangesetParams) -> Self { - let insert_filter = params - .insert_filter - .as_ref() - .map(|insert_filter_params| FilterType { - field_id: insert_filter_params.field_id.clone(), - field_type: insert_filter_params.field_type.into(), - }); - - let delete_filter = params - .delete_filter - .as_ref() - .map(|delete_filter_params| delete_filter_params.filter_type.clone()); - FilterChangeset { - insert_filter, - update_filter: None, - delete_filter, - } - } -} - -#[derive(Hash, Eq, PartialEq, Debug, Clone)] -pub struct FilterType { - pub field_id: String, - pub field_type: FieldType, -} - -impl From for FieldTypeRevision { - fn from(filter_type: FilterType) -> Self { - filter_type.field_type.into() - } -} -impl std::convert::From<&Arc> for FilterType { - fn from(rev: &Arc) -> Self { - Self { - field_id: rev.id.clone(), - field_type: rev.ty.into(), - } - } -} - -impl std::convert::From<&AlterFilterParams> for FilterType { - fn from(params: &AlterFilterParams) -> Self { - let field_type: FieldType = params.field_type.into(); - Self { - field_id: params.field_id.clone(), - field_type, - } - } -} - -impl std::convert::From<&DeleteFilterParams> for FilterType { - fn from(params: &DeleteFilterParams) -> Self { - params.filter_type.clone() - } -} - -#[derive(Clone, Debug)] -pub struct FilterResultNotification { - pub view_id: String, - pub block_id: String, - - // Indicates there will be some new rows being visible from invisible state. - pub visible_rows: Vec, - - // Indicates there will be some new rows being invisible from visible state. - pub invisible_rows: Vec, -} - -impl FilterResultNotification { - pub fn new(view_id: String, block_id: String) -> Self { - Self { - view_id, - block_id, - visible_rows: vec![], - invisible_rows: vec![], - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/filter/mod.rs b/frontend/rust-lib/flowy-database/src/services/filter/mod.rs deleted file mode 100644 index 72bfa3a925..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/filter/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod controller; -mod entities; -mod task; - -pub use controller::*; -pub use entities::*; -pub(crate) use task::*; diff --git a/frontend/rust-lib/flowy-database/src/services/filter/task.rs b/frontend/rust-lib/flowy-database/src/services/filter/task.rs deleted file mode 100644 index c3d99ecbfd..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/filter/task.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::services::filter::{FilterController, FilterType}; -use flowy_task::{TaskContent, TaskHandler}; -use lib_infra::future::BoxResultFuture; -use std::collections::HashMap; -use std::sync::Arc; - -pub struct FilterTaskHandler { - handler_id: String, - filter_controller: Arc, -} - -impl FilterTaskHandler { - pub fn new(handler_id: String, filter_controller: Arc) -> Self { - Self { - handler_id, - filter_controller, - } - } -} - -impl TaskHandler for FilterTaskHandler { - fn handler_id(&self) -> &str { - &self.handler_id - } - - fn handler_name(&self) -> &str { - "FilterTaskHandler" - } - - fn run(&self, content: TaskContent) -> BoxResultFuture<(), anyhow::Error> { - let filter_controller = self.filter_controller.clone(); - Box::pin(async move { - if let TaskContent::Text(predicate) = content { - filter_controller - .process(&predicate) - .await - .map_err(anyhow::Error::from)?; - } - Ok(()) - }) - } -} -/// Refresh the filter according to the field id. -#[derive(Default)] -pub(crate) struct FilterResult { - pub(crate) visible_by_filter_id: HashMap, -} - -impl FilterResult { - pub(crate) fn is_visible(&self) -> bool { - let mut is_visible = true; - for visible in self.visible_by_filter_id.values() { - if !is_visible { - break; - } - is_visible = *visible; - } - is_visible - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/group/action.rs b/frontend/rust-lib/flowy-database/src/services/group/action.rs deleted file mode 100644 index c44566a29e..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/group/action.rs +++ /dev/null @@ -1,127 +0,0 @@ -use crate::entities::{GroupChangesetPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB}; -use crate::services::cell::DecodedCellData; -use crate::services::group::controller::MoveGroupRowContext; -use crate::services::group::Group; -use database_model::{CellRevision, FieldRevision, RowRevision}; -use flowy_error::FlowyResult; -use std::sync::Arc; - -/// Using polymorphism to provides the customs action for different group controller. -/// -/// For example, the `CheckboxGroupController` implements this trait to provide custom behavior. -/// -pub trait GroupCustomize: Send + Sync { - type CellData: DecodedCellData; - /// Returns the a value of the cell if the cell data is not exist. - /// The default value is `None` - /// - /// Determine which group the row is placed in based on the data of the cell. If the cell data - /// is None. The row will be put in to the `No status` group - /// - fn placeholder_cell(&self) -> Option { - None - } - - /// Returns a bool value to determine whether the group should contain this cell or not. - fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool; - - fn create_or_delete_group_when_cell_changed( - &mut self, - _row_rev: &RowRevision, - _old_cell_data: Option<&Self::CellData>, - _cell_data: &Self::CellData, - ) -> FlowyResult<(Option, Option)> { - Ok((None, None)) - } - - /// Adds or removes a row if the cell data match the group filter. - /// It gets called after editing the cell or row - /// - fn add_or_remove_row_when_cell_changed( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellData, - ) -> Vec; - - /// Deletes the row from the group - fn delete_row( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellData, - ) -> Vec; - - /// Move row from one group to another - fn move_row( - &mut self, - cell_data: &Self::CellData, - context: MoveGroupRowContext, - ) -> Vec; - - /// Returns None if there is no need to delete the group when corresponding row get removed - fn delete_group_when_move_row( - &mut self, - _row_rev: &RowRevision, - _cell_data: &Self::CellData, - ) -> Option { - None - } -} - -/// Defines the shared actions any group controller can perform. -pub trait GroupControllerActions: Send + Sync { - /// The field that is used for grouping the rows - fn field_id(&self) -> &str; - - /// Returns number of groups the current field has - fn groups(&self) -> Vec<&Group>; - - /// Returns the index and the group data with group_id - fn get_group(&self, group_id: &str) -> Option<(usize, Group)>; - - /// Separates the rows into different groups - fn fill_groups( - &mut self, - row_revs: &[Arc], - field_rev: &FieldRevision, - ) -> FlowyResult<()>; - - /// Remove the group with from_group_id and insert it to the index with to_group_id - fn move_group(&mut self, from_group_id: &str, to_group_id: &str) -> FlowyResult<()>; - - /// Insert/Remove the row to the group if the corresponding cell data is changed - fn did_update_group_row( - &mut self, - old_row_rev: &Option>, - row_rev: &RowRevision, - field_rev: &FieldRevision, - ) -> FlowyResult; - - /// Remove the row from the group if the row gets deleted - fn did_delete_delete_row( - &mut self, - row_rev: &RowRevision, - field_rev: &FieldRevision, - ) -> FlowyResult; - - /// Move the row from one group to another group - fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult; - - /// Update the group if the corresponding field is changed - fn did_update_group_field( - &mut self, - field_rev: &FieldRevision, - ) -> FlowyResult>; -} - -#[derive(Debug)] -pub struct DidUpdateGroupRowResult { - pub(crate) inserted_group: Option, - pub(crate) deleted_group: Option, - pub(crate) row_changesets: Vec, -} - -#[derive(Debug)] -pub struct DidMoveGroupRowResult { - pub(crate) deleted_group: Option, - pub(crate) row_changesets: Vec, -} diff --git a/frontend/rust-lib/flowy-database/src/services/group/configuration.rs b/frontend/rust-lib/flowy-database/src/services/group/configuration.rs deleted file mode 100644 index 765694f21e..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/group/configuration.rs +++ /dev/null @@ -1,488 +0,0 @@ -use crate::entities::{GroupChangesetPB, GroupPB, InsertedGroupPB}; -use crate::services::field::RowSingleCellData; -use crate::services::group::{default_group_configuration, GeneratedGroupContext, Group}; -use database_model::{ - FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, - GroupRevision, -}; -use flowy_error::{FlowyError, FlowyResult}; -use indexmap::IndexMap; -use lib_infra::future::Fut; -use std::collections::HashMap; -use std::fmt::Formatter; -use std::marker::PhantomData; -use std::sync::Arc; - -pub trait GroupConfigurationReader: Send + Sync + 'static { - fn get_configuration(&self) -> Fut>>; - fn get_configuration_cells(&self, field_id: &str) -> Fut>>; -} - -pub trait GroupConfigurationWriter: Send + Sync + 'static { - fn save_configuration( - &self, - field_id: &str, - field_type: FieldTypeRevision, - group_configuration: GroupConfigurationRevision, - ) -> Fut>; -} - -impl std::fmt::Display for GroupContext { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - self.groups_map.iter().for_each(|(_, group)| { - let _ = f.write_fmt(format_args!( - "Group:{} has {} rows \n", - group.id, - group.rows.len() - )); - }); - - Ok(()) - } -} - -/// A [GroupContext] represents as the groups memory cache -/// Each [GenericGroupController] has its own [GroupContext], the `context` has its own configuration -/// that is restored from the disk. -/// -/// The `context` contains a list of [Group]s and the grouping [FieldRevision] -pub struct GroupContext { - pub view_id: String, - /// The group configuration restored from the disk. - /// - /// Uses the [GroupConfigurationReader] to read the configuration data from disk - configuration: Arc, - configuration_phantom: PhantomData, - - /// The grouping field - field_rev: Arc, - - /// Cache all the groups - groups_map: IndexMap, - - /// A reader that implement the [GroupConfigurationReader] trait - /// - #[allow(dead_code)] - reader: Arc, - - /// A writer that implement the [GroupConfigurationWriter] trait is used to save the - /// configuration to disk - /// - writer: Arc, -} - -impl GroupContext -where - C: GroupConfigurationContentSerde, -{ - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn new( - view_id: String, - field_rev: Arc, - reader: Arc, - writer: Arc, - ) -> FlowyResult { - let configuration = match reader.get_configuration().await { - None => { - let default_configuration = default_group_configuration(&field_rev); - writer - .save_configuration(&field_rev.id, field_rev.ty, default_configuration.clone()) - .await?; - Arc::new(default_configuration) - }, - Some(configuration) => configuration, - }; - - Ok(Self { - view_id, - field_rev, - groups_map: IndexMap::new(), - reader, - writer, - configuration, - configuration_phantom: PhantomData, - }) - } - - /// Returns the no `status` group - /// - /// We take the `id` of the `field` as the no status group id - pub(crate) fn get_no_status_group(&self) -> Option<&Group> { - self.groups_map.get(&self.field_rev.id) - } - - pub(crate) fn get_mut_no_status_group(&mut self) -> Option<&mut Group> { - self.groups_map.get_mut(&self.field_rev.id) - } - - pub(crate) fn groups(&self) -> Vec<&Group> { - self.groups_map.values().collect() - } - - pub(crate) fn get_mut_group(&mut self, group_id: &str) -> Option<&mut Group> { - self.groups_map.get_mut(group_id) - } - - // Returns the index and group specified by the group_id - pub(crate) fn get_group(&self, group_id: &str) -> Option<(usize, &Group)> { - match ( - self.groups_map.get_index_of(group_id), - self.groups_map.get(group_id), - ) { - (Some(index), Some(group)) => Some((index, group)), - _ => None, - } - } - - /// Iterate mut the groups without `No status` group - pub(crate) fn iter_mut_status_groups(&mut self, mut each: impl FnMut(&mut Group)) { - self.groups_map.iter_mut().for_each(|(_, group)| { - if group.id != self.field_rev.id { - each(group); - } - }); - } - - pub(crate) fn iter_mut_groups(&mut self, mut each: impl FnMut(&mut Group)) { - self.groups_map.iter_mut().for_each(|(_, group)| { - each(group); - }); - } - #[tracing::instrument(level = "trace", skip(self), err)] - pub(crate) fn add_new_group(&mut self, group_rev: GroupRevision) -> FlowyResult { - let group = Group::new( - group_rev.id.clone(), - self.field_rev.id.clone(), - group_rev.name.clone(), - group_rev.id.clone(), - ); - self.groups_map.insert(group_rev.id.clone(), group); - let (index, group) = self.get_group(&group_rev.id).unwrap(); - let insert_group = InsertedGroupPB { - group: GroupPB::from(group.clone()), - index: index as i32, - }; - self.mut_configuration(|configuration| { - configuration.groups.push(group_rev); - true - })?; - - Ok(insert_group) - } - - #[tracing::instrument(level = "trace", skip(self))] - pub(crate) fn delete_group(&mut self, deleted_group_id: &str) -> FlowyResult<()> { - self.groups_map.remove(deleted_group_id); - self.mut_configuration(|configuration| { - configuration - .groups - .retain(|group| group.id != deleted_group_id); - true - })?; - Ok(()) - } - - pub(crate) fn move_group(&mut self, from_id: &str, to_id: &str) -> FlowyResult<()> { - let from_index = self.groups_map.get_index_of(from_id); - let to_index = self.groups_map.get_index_of(to_id); - match (from_index, to_index) { - (Some(from_index), Some(to_index)) => { - self.groups_map.move_index(from_index, to_index); - - self.mut_configuration(|configuration| { - let from_index = configuration - .groups - .iter() - .position(|group| group.id == from_id); - let to_index = configuration - .groups - .iter() - .position(|group| group.id == to_id); - if let (Some(from), Some(to)) = &(from_index, to_index) { - tracing::trace!( - "Move group from index:{:?} to index:{:?}", - from_index, - to_index - ); - let group = configuration.groups.remove(*from); - configuration.groups.insert(*to, group); - } - tracing::debug!( - "Group order: {:?} ", - configuration - .groups - .iter() - .map(|group| group.name.clone()) - .collect::>() - .join(",") - ); - - from_index.is_some() && to_index.is_some() - })?; - Ok(()) - }, - _ => Err(FlowyError::record_not_found().context("Moving group failed. Groups are not exist")), - } - } - - /// Reset the memory cache of the groups and update the group configuration - /// - /// # Arguments - /// - /// * `generated_group_configs`: the generated groups contains a list of [GeneratedGroupConfig]. - /// - /// Each [FieldType] can implement the [GroupGenerator] trait in order to generate different - /// groups. For example, the FieldType::Checkbox has the [CheckboxGroupGenerator] that implements - /// the [GroupGenerator] trait. - /// - /// Consider the passed-in generated_group_configs as new groups, the groups in the current - /// [GroupConfigurationRevision] as old groups. The old groups and the new groups will be merged - /// while keeping the order of the old groups. - /// - #[tracing::instrument(level = "trace", skip(self, generated_group_context), err)] - pub(crate) fn init_groups( - &mut self, - generated_group_context: GeneratedGroupContext, - ) -> FlowyResult> { - let GeneratedGroupContext { - no_status_group, - group_configs, - } = generated_group_context; - - let mut new_groups = vec![]; - let mut filter_content_map = HashMap::new(); - group_configs.into_iter().for_each(|generate_group| { - filter_content_map.insert( - generate_group.group_rev.id.clone(), - generate_group.filter_content, - ); - new_groups.push(generate_group.group_rev); - }); - - let mut old_groups = self.configuration.groups.clone(); - // clear all the groups if grouping by a new field - if self.configuration.field_id != self.field_rev.id { - old_groups.clear(); - } - - // The `all_group_revs` is the combination of the new groups and old groups - let MergeGroupResult { - mut all_group_revs, - new_group_revs, - deleted_group_revs, - } = merge_groups(no_status_group, old_groups, new_groups); - - let deleted_group_ids = deleted_group_revs - .into_iter() - .map(|group_rev| group_rev.id) - .collect::>(); - - self.mut_configuration(|configuration| { - let mut is_changed = !deleted_group_ids.is_empty(); - // Remove the groups - configuration - .groups - .retain(|group| !deleted_group_ids.contains(&group.id)); - - // Update/Insert new groups - for group_rev in &mut all_group_revs { - match configuration - .groups - .iter() - .position(|old_group_rev| old_group_rev.id == group_rev.id) - { - None => { - // Push the group to the end of the list if it doesn't exist in the group - configuration.groups.push(group_rev.clone()); - is_changed = true; - }, - Some(pos) => { - let mut old_group = configuration.groups.get_mut(pos).unwrap(); - // Take the old group setting - group_rev.update_with_other(old_group); - if !is_changed { - is_changed = is_group_changed(group_rev, old_group); - } - // Consider the the name of the `group_rev` as the newest. - old_group.name = group_rev.name.clone(); - }, - } - } - is_changed - })?; - - // Update the memory cache of the groups - all_group_revs.into_iter().for_each(|group_rev| { - let filter_content = filter_content_map - .get(&group_rev.id) - .cloned() - .unwrap_or_else(|| "".to_owned()); - let group = Group::new( - group_rev.id, - self.field_rev.id.clone(), - group_rev.name, - filter_content, - ); - self.groups_map.insert(group.id.clone(), group); - }); - - let initial_groups = new_group_revs - .into_iter() - .flat_map(|group_rev| { - let filter_content = filter_content_map.get(&group_rev.id)?; - let group = Group::new( - group_rev.id, - self.field_rev.id.clone(), - group_rev.name, - filter_content.clone(), - ); - Some(GroupPB::from(group)) - }) - .collect(); - - let changeset = GroupChangesetPB { - view_id: self.view_id.clone(), - initial_groups, - deleted_groups: deleted_group_ids, - update_groups: vec![], - inserted_groups: vec![], - }; - tracing::trace!("Group changeset: {:?}", changeset); - if changeset.is_empty() { - Ok(None) - } else { - Ok(Some(changeset)) - } - } - - #[allow(dead_code)] - pub(crate) async fn hide_group(&mut self, group_id: &str) -> FlowyResult<()> { - self.mut_group_rev(group_id, |group_rev| { - group_rev.visible = false; - })?; - Ok(()) - } - - #[allow(dead_code)] - pub(crate) async fn show_group(&mut self, group_id: &str) -> FlowyResult<()> { - self.mut_group_rev(group_id, |group_rev| { - group_rev.visible = true; - })?; - Ok(()) - } - - pub(crate) async fn get_all_cells(&self) -> Vec { - self - .reader - .get_configuration_cells(&self.field_rev.id) - .await - .unwrap_or_default() - } - - fn mut_configuration( - &mut self, - mut_configuration_fn: impl FnOnce(&mut GroupConfigurationRevision) -> bool, - ) -> FlowyResult<()> { - let configuration = Arc::make_mut(&mut self.configuration); - let is_changed = mut_configuration_fn(configuration); - if is_changed { - let configuration = (*self.configuration).clone(); - let writer = self.writer.clone(); - let field_id = self.field_rev.id.clone(); - let field_type = self.field_rev.ty; - tokio::spawn(async move { - match writer - .save_configuration(&field_id, field_type, configuration) - .await - { - Ok(_) => {}, - Err(e) => { - tracing::error!("Save group configuration failed: {}", e); - }, - } - }); - } - Ok(()) - } - - fn mut_group_rev( - &mut self, - group_id: &str, - mut_groups_fn: impl Fn(&mut GroupRevision), - ) -> FlowyResult<()> { - self.mut_configuration(|configuration| { - match configuration - .groups - .iter_mut() - .find(|group| group.id == group_id) - { - None => false, - Some(group_rev) => { - mut_groups_fn(group_rev); - true - }, - } - }) - } -} - -/// Merge the new groups into old groups while keeping the order in the old groups -/// -fn merge_groups( - no_status_group: Option, - old_groups: Vec, - new_groups: Vec, -) -> MergeGroupResult { - let mut merge_result = MergeGroupResult::new(); - // group_map is a helper map is used to filter out the new groups. - let mut new_group_map: IndexMap = IndexMap::new(); - new_groups.into_iter().for_each(|group_rev| { - new_group_map.insert(group_rev.id.clone(), group_rev); - }); - - // The group is ordered in old groups. Add them before adding the new groups - for old in old_groups { - if let Some(new) = new_group_map.remove(&old.id) { - merge_result.all_group_revs.push(new.clone()); - } else { - merge_result.deleted_group_revs.push(old); - } - } - - // Find out the new groups - let new_groups = new_group_map.into_values(); - for (_, group) in new_groups.into_iter().enumerate() { - merge_result.all_group_revs.push(group.clone()); - merge_result.new_group_revs.push(group); - } - - // The `No status` group index is initialized to 0 - if let Some(no_status_group) = no_status_group { - merge_result.all_group_revs.insert(0, no_status_group); - } - merge_result -} - -fn is_group_changed(new: &GroupRevision, old: &GroupRevision) -> bool { - if new.name != old.name { - return true; - } - false -} - -struct MergeGroupResult { - // Contains the new groups and the updated groups - all_group_revs: Vec, - new_group_revs: Vec, - deleted_group_revs: Vec, -} - -impl MergeGroupResult { - fn new() -> Self { - Self { - all_group_revs: vec![], - new_group_revs: vec![], - deleted_group_revs: vec![], - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller.rs b/frontend/rust-lib/flowy-database/src/services/group/controller.rs deleted file mode 100644 index 3432530396..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/group/controller.rs +++ /dev/null @@ -1,361 +0,0 @@ -use crate::entities::{GroupChangesetPB, GroupRowsNotificationPB, InsertedRowPB, RowPB}; -use crate::services::cell::{get_type_cell_protobuf, CellProtobufBlobParser, DecodedCellData}; - -use crate::services::group::action::{ - DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerActions, GroupCustomize, -}; -use crate::services::group::configuration::GroupContext; -use crate::services::group::entities::Group; -use database_model::{ - CellRevision, FieldRevision, GroupConfigurationContentSerde, GroupRevision, RowChangeset, - RowRevision, TypeOptionDataDeserializer, -}; -use flowy_error::FlowyResult; -use std::marker::PhantomData; -use std::sync::Arc; - -/// The [GroupController] trait defines the group actions, including create/delete/move items -/// For example, the group will insert a item if the one of the new [RowRevision]'s [CellRevision]s -/// content match the group filter. -/// -/// Different [FieldType] has a different controller that implements the [GroupController] trait. -/// If the [FieldType] doesn't implement its group controller, then the [DefaultGroupController] will -/// be used. -/// -pub trait GroupController: GroupControllerActions + Send + Sync { - fn will_create_row( - &mut self, - row_rev: &mut RowRevision, - field_rev: &FieldRevision, - group_id: &str, - ); - fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str); -} - -/// The [GroupGenerator] trait is used to generate the groups for different [FieldType] -pub trait GroupGenerator { - type Context; - type TypeOptionType; - - fn generate_groups( - field_rev: &FieldRevision, - group_ctx: &Self::Context, - type_option: &Option, - ) -> GeneratedGroupContext; -} - -pub struct GeneratedGroupContext { - pub no_status_group: Option, - pub group_configs: Vec, -} - -pub struct GeneratedGroupConfig { - pub group_rev: GroupRevision, - pub filter_content: String, -} - -pub struct MoveGroupRowContext<'a> { - pub row_rev: &'a RowRevision, - pub row_changeset: &'a mut RowChangeset, - pub field_rev: &'a FieldRevision, - pub to_group_id: &'a str, - pub to_row_id: Option, -} -/// C: represents the group configuration that impl [GroupConfigurationSerde] -/// T: the type-option data deserializer that impl [TypeOptionDataDeserializer] -/// G: the group generator, [GroupGenerator] -/// P: the parser that impl [CellProtobufBlobParser] for the CellBytes -pub struct GenericGroupController { - pub grouping_field_id: String, - pub type_option: Option, - pub group_ctx: GroupContext, - group_action_phantom: PhantomData, - cell_parser_phantom: PhantomData

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

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

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

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

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

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

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

().ok() -} diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/checkbox_controller.rs b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/checkbox_controller.rs deleted file mode 100644 index 277d7ae608..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/checkbox_controller.rs +++ /dev/null @@ -1,174 +0,0 @@ -use crate::entities::{GroupRowsNotificationPB, InsertedRowPB, RowPB}; -use crate::services::field::{ - CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK, -}; -use crate::services::group::action::GroupCustomize; -use crate::services::group::configuration::GroupContext; -use crate::services::group::controller::{ - GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, -}; - -use crate::services::cell::insert_checkbox_cell; -use crate::services::group::{move_group_row, GeneratedGroupConfig, GeneratedGroupContext}; -use database_model::{ - CellRevision, CheckboxGroupConfigurationRevision, FieldRevision, GroupRevision, RowRevision, -}; - -pub type CheckboxGroupController = GenericGroupController< - CheckboxGroupConfigurationRevision, - CheckboxTypeOptionPB, - CheckboxGroupGenerator, - CheckboxCellDataParser, ->; - -pub type CheckboxGroupContext = GroupContext; - -impl GroupCustomize for CheckboxGroupController { - type CellData = CheckboxCellData; - fn placeholder_cell(&self) -> Option { - Some(CellRevision::new(UNCHECK.to_string())) - } - - fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool { - if cell_data.is_check() { - content == CHECK - } else { - content == UNCHECK - } - } - - fn add_or_remove_row_when_cell_changed( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellData, - ) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_status_groups(|group| { - let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); - let is_not_contained = !group.contains_row(&row_rev.id); - if group.id == CHECK { - if cell_data.is_uncheck() { - // Remove the row if the group.id is CHECK but the cell_data is UNCHECK - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); - } else { - // Add the row to the group if the group didn't contain the row - if is_not_contained { - let row_pb = RowPB::from(row_rev); - changeset - .inserted_rows - .push(InsertedRowPB::new(row_pb.clone())); - group.add_row(row_pb); - } - } - } - - if group.id == UNCHECK { - if cell_data.is_check() { - // Remove the row if the group.id is UNCHECK but the cell_data is CHECK - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); - } else { - // Add the row to the group if the group didn't contain the row - if is_not_contained { - let row_pb = RowPB::from(row_rev); - changeset - .inserted_rows - .push(InsertedRowPB::new(row_pb.clone())); - group.add_row(row_pb); - } - } - } - - if !changeset.is_empty() { - changesets.push(changeset); - } - }); - changesets - } - - fn delete_row( - &mut self, - row_rev: &RowRevision, - _cell_data: &Self::CellData, - ) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_groups(|group| { - let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); - if group.contains_row(&row_rev.id) { - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); - } - - if !changeset.is_empty() { - changesets.push(changeset); - } - }); - changesets - } - - fn move_row( - &mut self, - _cell_data: &Self::CellData, - mut context: MoveGroupRowContext, - ) -> Vec { - let mut group_changeset = vec![]; - self.group_ctx.iter_mut_groups(|group| { - if let Some(changeset) = move_group_row(group, &mut context) { - group_changeset.push(changeset); - } - }); - group_changeset - } -} - -impl GroupController for CheckboxGroupController { - fn will_create_row( - &mut self, - row_rev: &mut RowRevision, - field_rev: &FieldRevision, - group_id: &str, - ) { - match self.group_ctx.get_group(group_id) { - None => tracing::warn!("Can not find the group: {}", group_id), - Some((_, group)) => { - let is_check = group.id == CHECK; - let cell_rev = insert_checkbox_cell(is_check, field_rev); - row_rev.cells.insert(field_rev.id.clone(), cell_rev); - }, - } - } - - fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str) { - if let Some(group) = self.group_ctx.get_mut_group(group_id) { - group.add_row(row_pb.clone()) - } - } -} - -pub struct CheckboxGroupGenerator(); -impl GroupGenerator for CheckboxGroupGenerator { - type Context = CheckboxGroupContext; - type TypeOptionType = CheckboxTypeOptionPB; - - fn generate_groups( - _field_rev: &FieldRevision, - _group_ctx: &Self::Context, - _type_option: &Option, - ) -> GeneratedGroupContext { - let check_group = GeneratedGroupConfig { - group_rev: GroupRevision::new(CHECK.to_string(), "".to_string()), - filter_content: CHECK.to_string(), - }; - - let uncheck_group = GeneratedGroupConfig { - group_rev: GroupRevision::new(UNCHECK.to_string(), "".to_string()), - filter_content: UNCHECK.to_string(), - }; - - GeneratedGroupContext { - no_status_group: None, - group_configs: vec![check_group, uncheck_group], - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/default_controller.rs b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/default_controller.rs deleted file mode 100644 index 21e0446c95..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/default_controller.rs +++ /dev/null @@ -1,116 +0,0 @@ -use crate::entities::{GroupChangesetPB, RowPB}; -use crate::services::group::action::{ - DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerActions, -}; -use crate::services::group::{Group, GroupController, MoveGroupRowContext}; -use database_model::{FieldRevision, RowRevision}; -use flowy_error::FlowyResult; -use std::sync::Arc; - -/// A [DefaultGroupController] is used to handle the group actions for the [FieldType] that doesn't -/// implement its own group controller. The default group controller only contains one group, which -/// means all rows will be grouped in the same group. -/// -pub struct DefaultGroupController { - pub field_id: String, - pub group: Group, -} - -const DEFAULT_GROUP_CONTROLLER: &str = "DefaultGroupController"; - -impl DefaultGroupController { - pub fn new(field_rev: &Arc) -> Self { - let group = Group::new( - DEFAULT_GROUP_CONTROLLER.to_owned(), - field_rev.id.clone(), - "".to_owned(), - "".to_owned(), - ); - Self { - field_id: field_rev.id.clone(), - group, - } - } -} - -impl GroupControllerActions for DefaultGroupController { - fn field_id(&self) -> &str { - &self.field_id - } - - fn groups(&self) -> Vec<&Group> { - vec![&self.group] - } - - fn get_group(&self, _group_id: &str) -> Option<(usize, Group)> { - Some((0, self.group.clone())) - } - - fn fill_groups( - &mut self, - row_revs: &[Arc], - _field_rev: &FieldRevision, - ) -> FlowyResult<()> { - row_revs.iter().for_each(|row_rev| { - self.group.add_row(RowPB::from(row_rev)); - }); - Ok(()) - } - - fn move_group(&mut self, _from_group_id: &str, _to_group_id: &str) -> FlowyResult<()> { - Ok(()) - } - - fn did_update_group_row( - &mut self, - _old_row_rev: &Option>, - _row_rev: &RowRevision, - _field_rev: &FieldRevision, - ) -> FlowyResult { - Ok(DidUpdateGroupRowResult { - inserted_group: None, - deleted_group: None, - row_changesets: vec![], - }) - } - - fn did_delete_delete_row( - &mut self, - _row_rev: &RowRevision, - _field_rev: &FieldRevision, - ) -> FlowyResult { - Ok(DidMoveGroupRowResult { - deleted_group: None, - row_changesets: vec![], - }) - } - - fn move_group_row( - &mut self, - _context: MoveGroupRowContext, - ) -> FlowyResult { - Ok(DidMoveGroupRowResult { - deleted_group: None, - row_changesets: vec![], - }) - } - - fn did_update_group_field( - &mut self, - _field_rev: &FieldRevision, - ) -> FlowyResult> { - Ok(None) - } -} - -impl GroupController for DefaultGroupController { - fn will_create_row( - &mut self, - _row_rev: &mut RowRevision, - _field_rev: &FieldRevision, - _group_id: &str, - ) { - } - - fn did_create_row(&mut self, _row_rev: &RowPB, _group_id: &str) {} -} diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/mod.rs b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/mod.rs deleted file mode 100644 index fb890e6d29..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod checkbox_controller; -mod default_controller; -mod select_option_controller; -mod url_controller; - -pub use checkbox_controller::*; -pub use default_controller::*; -pub use select_option_controller::*; -pub use url_controller::*; diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/mod.rs b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/mod.rs deleted file mode 100644 index 0d7b8fa03e..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod multi_select_controller; -mod single_select_controller; -mod util; - -pub use multi_select_controller::*; -pub use single_select_controller::*; -pub use util::*; diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs deleted file mode 100644 index 0bd8ca47db..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::entities::{GroupRowsNotificationPB, RowPB}; -use crate::services::cell::insert_select_option_cell; -use crate::services::field::{ - MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser, -}; -use crate::services::group::action::GroupCustomize; - -use crate::services::group::controller::{ - GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, -}; -use crate::services::group::controller_impls::select_option_controller::util::*; - -use crate::services::group::{make_no_status_group, GeneratedGroupContext}; -use database_model::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision}; - -// MultiSelect -pub type MultiSelectGroupController = GenericGroupController< - SelectOptionGroupConfigurationRevision, - MultiSelectTypeOptionPB, - MultiSelectGroupGenerator, - SelectOptionCellDataParser, ->; - -impl GroupCustomize for MultiSelectGroupController { - type CellData = SelectOptionCellDataPB; - - fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool { - cell_data - .select_options - .iter() - .any(|option| option.id == content) - } - - fn add_or_remove_row_when_cell_changed( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellData, - ) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_status_groups(|group| { - if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) { - changesets.push(changeset); - } - }); - changesets - } - - fn delete_row( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellData, - ) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_status_groups(|group| { - if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) { - changesets.push(changeset); - } - }); - changesets - } - - fn move_row( - &mut self, - _cell_data: &Self::CellData, - mut context: MoveGroupRowContext, - ) -> Vec { - let mut group_changeset = vec![]; - self.group_ctx.iter_mut_groups(|group| { - if let Some(changeset) = move_group_row(group, &mut context) { - group_changeset.push(changeset); - } - }); - group_changeset - } -} - -impl GroupController for MultiSelectGroupController { - fn will_create_row( - &mut self, - row_rev: &mut RowRevision, - field_rev: &FieldRevision, - group_id: &str, - ) { - match self.group_ctx.get_group(group_id) { - None => tracing::warn!("Can not find the group: {}", group_id), - Some((_, group)) => { - let cell_rev = insert_select_option_cell(vec![group.id.clone()], field_rev); - row_rev.cells.insert(field_rev.id.clone(), cell_rev); - }, - } - } - - fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str) { - if let Some(group) = self.group_ctx.get_mut_group(group_id) { - group.add_row(row_pb.clone()) - } - } -} - -pub struct MultiSelectGroupGenerator(); -impl GroupGenerator for MultiSelectGroupGenerator { - type Context = SelectOptionGroupContext; - type TypeOptionType = MultiSelectTypeOptionPB; - - fn generate_groups( - field_rev: &FieldRevision, - group_ctx: &Self::Context, - type_option: &Option, - ) -> GeneratedGroupContext { - let group_configs = match type_option { - None => vec![], - Some(type_option) => { - generate_select_option_groups(&field_rev.id, group_ctx, &type_option.options) - }, - }; - - GeneratedGroupContext { - no_status_group: Some(make_no_status_group(field_rev)), - group_configs, - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/single_select_controller.rs b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/single_select_controller.rs deleted file mode 100644 index aa9d3d1f9d..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/single_select_controller.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crate::entities::{GroupRowsNotificationPB, RowPB}; -use crate::services::cell::insert_select_option_cell; -use crate::services::field::{ - SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB, -}; -use crate::services::group::action::GroupCustomize; - -use crate::services::group::controller::{ - GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, -}; -use crate::services::group::controller_impls::select_option_controller::util::*; -use crate::services::group::entities::Group; - -use crate::services::group::{make_no_status_group, GeneratedGroupContext}; -use database_model::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision}; - -// SingleSelect -pub type SingleSelectGroupController = GenericGroupController< - SelectOptionGroupConfigurationRevision, - SingleSelectTypeOptionPB, - SingleSelectGroupGenerator, - SelectOptionCellDataParser, ->; - -impl GroupCustomize for SingleSelectGroupController { - type CellData = SelectOptionCellDataPB; - fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool { - cell_data - .select_options - .iter() - .any(|option| option.id == content) - } - - fn add_or_remove_row_when_cell_changed( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellData, - ) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_status_groups(|group| { - if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) { - changesets.push(changeset); - } - }); - changesets - } - - fn delete_row( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellData, - ) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_status_groups(|group| { - if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) { - changesets.push(changeset); - } - }); - changesets - } - - fn move_row( - &mut self, - _cell_data: &Self::CellData, - mut context: MoveGroupRowContext, - ) -> Vec { - let mut group_changeset = vec![]; - self.group_ctx.iter_mut_groups(|group| { - if let Some(changeset) = move_group_row(group, &mut context) { - group_changeset.push(changeset); - } - }); - group_changeset - } -} - -impl GroupController for SingleSelectGroupController { - fn will_create_row( - &mut self, - row_rev: &mut RowRevision, - field_rev: &FieldRevision, - group_id: &str, - ) { - let group: Option<&mut Group> = self.group_ctx.get_mut_group(group_id); - match group { - None => {}, - Some(group) => { - let cell_rev = insert_select_option_cell(vec![group.id.clone()], field_rev); - row_rev.cells.insert(field_rev.id.clone(), cell_rev); - }, - } - } - fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str) { - if let Some(group) = self.group_ctx.get_mut_group(group_id) { - group.add_row(row_pb.clone()) - } - } -} - -pub struct SingleSelectGroupGenerator(); -impl GroupGenerator for SingleSelectGroupGenerator { - type Context = SelectOptionGroupContext; - type TypeOptionType = SingleSelectTypeOptionPB; - fn generate_groups( - field_rev: &FieldRevision, - group_ctx: &Self::Context, - type_option: &Option, - ) -> GeneratedGroupContext { - let group_configs = match type_option { - None => vec![], - Some(type_option) => { - generate_select_option_groups(&field_rev.id, group_ctx, &type_option.options) - }, - }; - - GeneratedGroupContext { - no_status_group: Some(make_no_status_group(field_rev)), - group_configs, - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/util.rs b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/util.rs deleted file mode 100644 index b800cf988f..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/select_option_controller/util.rs +++ /dev/null @@ -1,180 +0,0 @@ -use crate::entities::{FieldType, GroupRowsNotificationPB, InsertedRowPB, RowPB}; -use crate::services::cell::{insert_checkbox_cell, insert_select_option_cell, insert_url_cell}; -use crate::services::field::{SelectOptionCellDataPB, SelectOptionPB, CHECK}; -use crate::services::group::configuration::GroupContext; -use crate::services::group::controller::MoveGroupRowContext; -use crate::services::group::{GeneratedGroupConfig, Group}; -use database_model::{ - CellRevision, FieldRevision, GroupRevision, RowRevision, SelectOptionGroupConfigurationRevision, -}; - -pub type SelectOptionGroupContext = GroupContext; - -pub fn add_or_remove_select_option_row( - group: &mut Group, - cell_data: &SelectOptionCellDataPB, - row_rev: &RowRevision, -) -> Option { - let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); - if cell_data.select_options.is_empty() { - if group.contains_row(&row_rev.id) { - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); - } - } else { - cell_data.select_options.iter().for_each(|option| { - if option.id == group.id { - if !group.contains_row(&row_rev.id) { - let row_pb = RowPB::from(row_rev); - changeset - .inserted_rows - .push(InsertedRowPB::new(row_pb.clone())); - group.add_row(row_pb); - } - } else if group.contains_row(&row_rev.id) { - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); - } - }); - } - - if changeset.is_empty() { - None - } else { - Some(changeset) - } -} - -pub fn remove_select_option_row( - group: &mut Group, - cell_data: &SelectOptionCellDataPB, - row_rev: &RowRevision, -) -> Option { - let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); - cell_data.select_options.iter().for_each(|option| { - if option.id == group.id && group.contains_row(&row_rev.id) { - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); - } - }); - - if changeset.is_empty() { - None - } else { - Some(changeset) - } -} - -pub fn move_group_row( - group: &mut Group, - context: &mut MoveGroupRowContext, -) -> Option { - let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); - let MoveGroupRowContext { - row_rev, - row_changeset, - field_rev, - to_group_id, - to_row_id, - } = context; - - let from_index = group.index_of_row(&row_rev.id); - let to_index = match to_row_id { - None => None, - Some(to_row_id) => group.index_of_row(to_row_id), - }; - - // Remove the row in which group contains it - if let Some(from_index) = &from_index { - changeset.deleted_rows.push(row_rev.id.clone()); - tracing::debug!("Group:{} remove {} at {}", group.id, row_rev.id, from_index); - group.remove_row(&row_rev.id); - } - - if group.id == *to_group_id { - let row_pb = RowPB::from(*row_rev); - let mut inserted_row = InsertedRowPB::new(row_pb.clone()); - match to_index { - None => { - changeset.inserted_rows.push(inserted_row); - tracing::debug!("Group:{} append row:{}", group.id, row_rev.id); - group.add_row(row_pb); - }, - Some(to_index) => { - if to_index < group.number_of_row() { - tracing::debug!("Group:{} insert {} at {} ", group.id, row_rev.id, to_index); - inserted_row.index = Some(to_index as i32); - group.insert_row(to_index, row_pb); - } else { - tracing::warn!("Move to index: {} is out of bounds", to_index); - tracing::debug!("Group:{} append row:{}", group.id, row_rev.id); - group.add_row(row_pb); - } - changeset.inserted_rows.push(inserted_row); - }, - } - - // Update the corresponding row's cell content. - // If the from_index is none which means the row is not belong to this group before and - // it is moved from other groups. - if from_index.is_none() { - let cell_rev = make_inserted_cell_rev(&group.id, field_rev); - if let Some(cell_rev) = cell_rev { - tracing::debug!( - "Update content of the cell in the row:{} to group:{}", - row_rev.id, - group.id - ); - row_changeset - .cell_by_field_id - .insert(field_rev.id.clone(), cell_rev); - } - } - } - if changeset.is_empty() { - None - } else { - Some(changeset) - } -} - -pub fn make_inserted_cell_rev(group_id: &str, field_rev: &FieldRevision) -> Option { - let field_type: FieldType = field_rev.ty.into(); - match field_type { - FieldType::SingleSelect => { - let cell_rev = insert_select_option_cell(vec![group_id.to_owned()], field_rev); - Some(cell_rev) - }, - FieldType::MultiSelect => { - let cell_rev = insert_select_option_cell(vec![group_id.to_owned()], field_rev); - Some(cell_rev) - }, - FieldType::Checkbox => { - let cell_rev = insert_checkbox_cell(group_id == CHECK, field_rev); - Some(cell_rev) - }, - FieldType::URL => { - let cell_rev = insert_url_cell(group_id.to_owned(), field_rev); - Some(cell_rev) - }, - _ => { - tracing::warn!("Unknown field type: {:?}", field_type); - None - }, - } -} -pub fn generate_select_option_groups( - _field_id: &str, - _group_ctx: &SelectOptionGroupContext, - options: &[SelectOptionPB], -) -> Vec { - let groups = options - .iter() - .map(|option| GeneratedGroupConfig { - group_rev: GroupRevision::new(option.id.clone(), option.name.clone()), - filter_content: option.id.clone(), - }) - .collect(); - - groups -} diff --git a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/url_controller.rs b/frontend/rust-lib/flowy-database/src/services/group/controller_impls/url_controller.rs deleted file mode 100644 index 89304ee98c..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/group/controller_impls/url_controller.rs +++ /dev/null @@ -1,219 +0,0 @@ -use crate::entities::{GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB, RowPB}; -use crate::services::cell::insert_url_cell; -use crate::services::field::{URLCellData, URLCellDataPB, URLCellDataParser, URLTypeOptionPB}; -use crate::services::group::action::GroupCustomize; -use crate::services::group::configuration::GroupContext; -use crate::services::group::controller::{ - GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext, -}; -use crate::services::group::{ - make_no_status_group, move_group_row, GeneratedGroupConfig, GeneratedGroupContext, -}; -use database_model::{ - CellRevision, FieldRevision, GroupRevision, RowRevision, URLGroupConfigurationRevision, -}; -use flowy_error::FlowyResult; - -pub type URLGroupController = GenericGroupController< - URLGroupConfigurationRevision, - URLTypeOptionPB, - URLGroupGenerator, - URLCellDataParser, ->; - -pub type URLGroupContext = GroupContext; - -impl GroupCustomize for URLGroupController { - type CellData = URLCellDataPB; - - fn placeholder_cell(&self) -> Option { - Some(CellRevision::new("".to_string())) - } - - fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool { - cell_data.content == content - } - - fn create_or_delete_group_when_cell_changed( - &mut self, - row_rev: &RowRevision, - old_cell_data: Option<&Self::CellData>, - cell_data: &Self::CellData, - ) -> FlowyResult<(Option, Option)> { - // Just return if the group with this url already exists - let mut inserted_group = None; - if self.group_ctx.get_group(&cell_data.url).is_none() { - let cell_data: URLCellData = cell_data.clone().into(); - let group_revision = make_group_from_url_cell(&cell_data); - let mut new_group = self.group_ctx.add_new_group(group_revision)?; - new_group.group.rows.push(RowPB::from(row_rev)); - inserted_group = Some(new_group); - } - - // Delete the old url group if there are no rows in that group - let deleted_group = match old_cell_data - .and_then(|old_cell_data| self.group_ctx.get_group(&old_cell_data.content)) - { - None => None, - Some((_, group)) => { - if group.rows.len() == 1 { - Some(group.clone()) - } else { - None - } - }, - }; - - let deleted_group = match deleted_group { - None => None, - Some(group) => { - self.group_ctx.delete_group(&group.id)?; - Some(GroupPB::from(group.clone())) - }, - }; - - Ok((inserted_group, deleted_group)) - } - - fn add_or_remove_row_when_cell_changed( - &mut self, - row_rev: &RowRevision, - cell_data: &Self::CellData, - ) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_status_groups(|group| { - let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); - if group.id == cell_data.content { - if !group.contains_row(&row_rev.id) { - let row_pb = RowPB::from(row_rev); - changeset - .inserted_rows - .push(InsertedRowPB::new(row_pb.clone())); - group.add_row(row_pb); - } - } else if group.contains_row(&row_rev.id) { - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); - } - - if !changeset.is_empty() { - changesets.push(changeset); - } - }); - changesets - } - - fn delete_row( - &mut self, - row_rev: &RowRevision, - _cell_data: &Self::CellData, - ) -> Vec { - let mut changesets = vec![]; - self.group_ctx.iter_mut_groups(|group| { - let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); - if group.contains_row(&row_rev.id) { - changeset.deleted_rows.push(row_rev.id.clone()); - group.remove_row(&row_rev.id); - } - - if !changeset.is_empty() { - changesets.push(changeset); - } - }); - changesets - } - - fn move_row( - &mut self, - _cell_data: &Self::CellData, - mut context: MoveGroupRowContext, - ) -> Vec { - let mut group_changeset = vec![]; - self.group_ctx.iter_mut_groups(|group| { - if let Some(changeset) = move_group_row(group, &mut context) { - group_changeset.push(changeset); - } - }); - group_changeset - } - - fn delete_group_when_move_row( - &mut self, - _row_rev: &RowRevision, - cell_data: &Self::CellData, - ) -> Option { - let mut deleted_group = None; - if let Some((_, group)) = self.group_ctx.get_group(&cell_data.content) { - if group.rows.len() == 1 { - deleted_group = Some(GroupPB::from(group.clone())); - } - } - if deleted_group.is_some() { - let _ = self - .group_ctx - .delete_group(&deleted_group.as_ref().unwrap().group_id); - } - deleted_group - } -} - -impl GroupController for URLGroupController { - fn will_create_row( - &mut self, - row_rev: &mut RowRevision, - field_rev: &FieldRevision, - group_id: &str, - ) { - match self.group_ctx.get_group(group_id) { - None => tracing::warn!("Can not find the group: {}", group_id), - Some((_, group)) => { - let cell_rev = insert_url_cell(group.id.clone(), field_rev); - row_rev.cells.insert(field_rev.id.clone(), cell_rev); - }, - } - } - - fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str) { - if let Some(group) = self.group_ctx.get_mut_group(group_id) { - group.add_row(row_pb.clone()) - } - } -} - -pub struct URLGroupGenerator(); -impl GroupGenerator for URLGroupGenerator { - type Context = URLGroupContext; - type TypeOptionType = URLTypeOptionPB; - - fn generate_groups( - field_rev: &FieldRevision, - group_ctx: &Self::Context, - _type_option: &Option, - ) -> GeneratedGroupContext { - // Read all the cells for the grouping field - let cells = futures::executor::block_on(group_ctx.get_all_cells()); - - // Generate the groups - let group_configs = cells - .into_iter() - .flat_map(|value| value.into_url_field_cell_data()) - .filter(|cell| !cell.content.is_empty()) - .map(|cell| GeneratedGroupConfig { - group_rev: make_group_from_url_cell(&cell), - filter_content: cell.content, - }) - .collect(); - - let no_status_group = Some(make_no_status_group(field_rev)); - GeneratedGroupContext { - no_status_group, - group_configs, - } - } -} - -fn make_group_from_url_cell(cell: &URLCellData) -> GroupRevision { - let group_id = cell.content.clone(); - let group_name = cell.content.clone(); - GroupRevision::new(group_id, group_name) -} diff --git a/frontend/rust-lib/flowy-database/src/services/group/entities.rs b/frontend/rust-lib/flowy-database/src/services/group/entities.rs deleted file mode 100644 index a664e22162..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/group/entities.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::entities::RowPB; - -#[derive(Clone, PartialEq, Debug, Eq)] -pub struct Group { - pub id: String, - pub field_id: String, - pub name: String, - pub is_default: bool, - pub is_visible: bool, - pub(crate) rows: Vec, - - /// [filter_content] is used to determine which group the cell belongs to. - pub filter_content: String, -} - -impl Group { - pub fn new(id: String, field_id: String, name: String, filter_content: String) -> Self { - let is_default = id == field_id; - Self { - id, - field_id, - is_default, - is_visible: true, - name, - rows: vec![], - filter_content, - } - } - - pub fn contains_row(&self, row_id: &str) -> bool { - self.rows.iter().any(|row| row.id == row_id) - } - - pub fn remove_row(&mut self, row_id: &str) { - match self.rows.iter().position(|row| row.id == row_id) { - None => {}, - Some(pos) => { - self.rows.remove(pos); - }, - } - } - - pub fn add_row(&mut self, row_pb: RowPB) { - match self.rows.iter().find(|row| row.id == row_pb.id) { - None => { - self.rows.push(row_pb); - }, - Some(_) => {}, - } - } - - pub fn insert_row(&mut self, index: usize, row_pb: RowPB) { - if index < self.rows.len() { - self.rows.insert(index, row_pb); - } else { - tracing::error!( - "Insert row index:{} beyond the bounds:{},", - index, - self.rows.len() - ); - } - } - - pub fn index_of_row(&self, row_id: &str) -> Option { - self.rows.iter().position(|row| row.id == row_id) - } - - pub fn number_of_row(&self) -> usize { - self.rows.len() - } - - pub fn is_empty(&self) -> bool { - self.rows.is_empty() - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/group/group_util.rs b/frontend/rust-lib/flowy-database/src/services/group/group_util.rs deleted file mode 100644 index 98197ad394..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/group/group_util.rs +++ /dev/null @@ -1,209 +0,0 @@ -use crate::entities::FieldType; -use crate::services::group::configuration::GroupConfigurationReader; -use crate::services::group::controller::GroupController; -use crate::services::group::{ - CheckboxGroupContext, CheckboxGroupController, DefaultGroupController, GroupConfigurationWriter, - MultiSelectGroupController, SelectOptionGroupContext, SingleSelectGroupController, - URLGroupContext, URLGroupController, -}; -use database_model::{ - CheckboxGroupConfigurationRevision, DateGroupConfigurationRevision, FieldRevision, - GroupConfigurationRevision, GroupRevision, LayoutRevision, NumberGroupConfigurationRevision, - RowRevision, SelectOptionGroupConfigurationRevision, TextGroupConfigurationRevision, - URLGroupConfigurationRevision, -}; -use flowy_error::FlowyResult; -use std::sync::Arc; - -/// Returns a group controller. -/// -/// Each view can be grouped by one field, each field has its own group controller. -/// # Arguments -/// -/// * `view_id`: the id of the view -/// * `grouping_field_rev`: the grouping field -/// * `row_revs`: the rows will be separated into different groups -/// * `configuration_reader`: a reader used to read the group configuration from disk -/// * `configuration_writer`: as writer used to write the group configuration to disk -/// -#[tracing::instrument( - level = "debug", - skip( - row_revs, - configuration_reader, - configuration_writer, - grouping_field_rev - ), - fields(grouping_field_id=%grouping_field_rev.id, grouping_field_type) - err -)] -pub async fn make_group_controller( - view_id: String, - grouping_field_rev: Arc, - row_revs: Vec>, - configuration_reader: R, - configuration_writer: W, -) -> FlowyResult> -where - R: GroupConfigurationReader, - W: GroupConfigurationWriter, -{ - let grouping_field_type: FieldType = grouping_field_rev.ty.into(); - tracing::Span::current().record("grouping_field_type", &format!("{}", grouping_field_type)); - - let mut group_controller: Box; - let configuration_reader = Arc::new(configuration_reader); - let configuration_writer = Arc::new(configuration_writer); - - match grouping_field_type { - FieldType::SingleSelect => { - let configuration = SelectOptionGroupContext::new( - view_id, - grouping_field_rev.clone(), - configuration_reader, - configuration_writer, - ) - .await?; - let controller = SingleSelectGroupController::new(&grouping_field_rev, configuration).await?; - group_controller = Box::new(controller); - }, - FieldType::MultiSelect => { - let configuration = SelectOptionGroupContext::new( - view_id, - grouping_field_rev.clone(), - configuration_reader, - configuration_writer, - ) - .await?; - let controller = MultiSelectGroupController::new(&grouping_field_rev, configuration).await?; - group_controller = Box::new(controller); - }, - FieldType::Checkbox => { - let configuration = CheckboxGroupContext::new( - view_id, - grouping_field_rev.clone(), - configuration_reader, - configuration_writer, - ) - .await?; - let controller = CheckboxGroupController::new(&grouping_field_rev, configuration).await?; - group_controller = Box::new(controller); - }, - FieldType::URL => { - let configuration = URLGroupContext::new( - view_id, - grouping_field_rev.clone(), - configuration_reader, - configuration_writer, - ) - .await?; - let controller = URLGroupController::new(&grouping_field_rev, configuration).await?; - group_controller = Box::new(controller); - }, - _ => { - group_controller = Box::new(DefaultGroupController::new(&grouping_field_rev)); - }, - } - - // Separates the rows into different groups - group_controller.fill_groups(&row_revs, &grouping_field_rev)?; - Ok(group_controller) -} - -#[tracing::instrument(level = "debug", skip_all)] -pub fn find_grouping_field( - field_revs: &[Arc], - _layout: &LayoutRevision, -) -> Option> { - let mut groupable_field_revs = field_revs - .iter() - .flat_map(|field_rev| { - let field_type: FieldType = field_rev.ty.into(); - match field_type.can_be_group() { - true => Some(field_rev.clone()), - false => None, - } - }) - .collect::>>(); - - if groupable_field_revs.is_empty() { - // If there is not groupable fields then we use the primary field. - field_revs - .iter() - .find(|field_rev| field_rev.is_primary) - .cloned() - } else { - Some(groupable_field_revs.remove(0)) - } -} - -/// Returns a `default` group configuration for the [FieldRevision] -/// -/// # Arguments -/// -/// * `field_rev`: making the group configuration for the field -/// -pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurationRevision { - let field_id = field_rev.id.clone(); - let field_type_rev = field_rev.ty; - let field_type: FieldType = field_rev.ty.into(); - match field_type { - FieldType::RichText => GroupConfigurationRevision::new( - field_id, - field_type_rev, - TextGroupConfigurationRevision::default(), - ) - .unwrap(), - FieldType::Number => GroupConfigurationRevision::new( - field_id, - field_type_rev, - NumberGroupConfigurationRevision::default(), - ) - .unwrap(), - FieldType::DateTime => GroupConfigurationRevision::new( - field_id, - field_type_rev, - DateGroupConfigurationRevision::default(), - ) - .unwrap(), - - FieldType::SingleSelect => GroupConfigurationRevision::new( - field_id, - field_type_rev, - SelectOptionGroupConfigurationRevision::default(), - ) - .unwrap(), - FieldType::MultiSelect => GroupConfigurationRevision::new( - field_id, - field_type_rev, - SelectOptionGroupConfigurationRevision::default(), - ) - .unwrap(), - FieldType::Checklist => GroupConfigurationRevision::new( - field_id, - field_type_rev, - SelectOptionGroupConfigurationRevision::default(), - ) - .unwrap(), - FieldType::Checkbox => GroupConfigurationRevision::new( - field_id, - field_type_rev, - CheckboxGroupConfigurationRevision::default(), - ) - .unwrap(), - FieldType::URL => GroupConfigurationRevision::new( - field_id, - field_type_rev, - URLGroupConfigurationRevision::default(), - ) - .unwrap(), - } -} - -pub fn make_no_status_group(field_rev: &FieldRevision) -> GroupRevision { - GroupRevision { - id: field_rev.id.clone(), - name: format!("No {}", field_rev.name), - visible: true, - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/group/mod.rs b/frontend/rust-lib/flowy-database/src/services/group/mod.rs deleted file mode 100644 index b73ac511b6..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/group/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -mod action; -mod configuration; -mod controller; -mod controller_impls; -mod entities; -mod group_util; - -pub(crate) use configuration::*; -pub(crate) use controller::*; -pub(crate) use controller_impls::*; -pub(crate) use entities::*; -pub(crate) use group_util::*; diff --git a/frontend/rust-lib/flowy-database/src/services/mod.rs b/frontend/rust-lib/flowy-database/src/services/mod.rs deleted file mode 100644 index a53e20fc06..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -mod util; - -pub mod cell; -pub mod database; -pub mod database_view; -pub mod field; -pub mod filter; -pub mod group; -pub mod persistence; -pub mod row; -pub mod setting; -pub mod sort; diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/block_index.rs b/frontend/rust-lib/flowy-database/src/services/persistence/block_index.rs deleted file mode 100644 index 03676eeff7..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/persistence/block_index.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::services::persistence::DatabaseDBConnection; -use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; -use flowy_error::FlowyResult; -use flowy_sqlite::{ - prelude::*, - schema::{grid_block_index_table, grid_block_index_table::dsl}, -}; -use std::sync::Arc; - -/// Allow getting the block id from row id. -pub struct BlockRowIndexer { - database: Arc, -} - -impl BlockRowIndexer { - pub fn new(database: Arc) -> Self { - Self { database } - } - - pub fn get_block_id(&self, row_id: &str) -> FlowyResult { - let conn = self.database.get_db_connection()?; - let block_id = dsl::grid_block_index_table - .filter(grid_block_index_table::row_id.eq(row_id)) - .select(grid_block_index_table::block_id) - .first::(&*conn)?; - - Ok(block_id) - } - - pub fn insert(&self, block_id: &str, row_id: &str) -> FlowyResult<()> { - let conn = self.database.get_db_connection()?; - let item = IndexItem { - row_id: row_id.to_string(), - block_id: block_id.to_string(), - }; - let _ = diesel::replace_into(grid_block_index_table::table) - .values(item) - .execute(&*conn)?; - Ok(()) - } -} - -#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[table_name = "grid_block_index_table"] -#[primary_key(row_id)] -struct IndexItem { - row_id: String, - block_id: String, -} diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/database_ref.rs b/frontend/rust-lib/flowy-database/src/services/persistence/database_ref.rs deleted file mode 100644 index 607946c600..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/persistence/database_ref.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::services::persistence::DatabaseDBConnection; -use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; -use flowy_error::FlowyResult; -use flowy_sqlite::{ - prelude::*, - schema::{database_refs, database_refs::dsl}, -}; -use std::sync::Arc; - -pub struct DatabaseRefs { - database: Arc, -} - -impl DatabaseRefs { - pub fn new(database: Arc) -> Self { - Self { database } - } - - pub fn bind( - &self, - database_id: &str, - view_id: &str, - is_base: bool, - name: &str, - ) -> FlowyResult<()> { - let conn = self.database.get_db_connection()?; - let ref_id = make_ref_id(database_id, view_id); - let record = DatabaseRefRecord { - ref_id, - name: name.to_string(), - is_base, - view_id: view_id.to_string(), - database_id: database_id.to_string(), - }; - let _ = diesel::replace_into(database_refs::table) - .values(record) - .execute(&*conn)?; - Ok(()) - } - - pub fn unbind(&self, view_id: &str) -> FlowyResult<()> { - let conn = self.database.get_db_connection()?; - diesel::delete(dsl::database_refs.filter(database_refs::view_id.eq(view_id))) - .execute(&*conn)?; - Ok(()) - } - - pub fn get_ref_views_with_database( - &self, - database_id: &str, - ) -> FlowyResult> { - let conn = self.database.get_db_connection()?; - let views = dsl::database_refs - .filter(database_refs::database_id.like(database_id)) - .load::(&*conn)? - .into_iter() - .map(|record| record.into()) - .collect::>(); - tracing::trace!("database:{} has {} ref views", database_id, views.len()); - Ok(views) - } - - pub fn get_database_with_view(&self, view_id: &str) -> FlowyResult { - let conn = self.database.get_db_connection()?; - let record = dsl::database_refs - .filter(database_refs::view_id.eq(view_id)) - .first::(&*conn)?; - Ok(record.into()) - } - - pub fn get_all_databases(&self) -> FlowyResult> { - let conn = self.database.get_db_connection()?; - let database_infos = dsl::database_refs - .filter(database_refs::is_base.eq(true)) - .load::(&*conn)? - .into_iter() - .map(|record| record.into()) - .collect::>(); - - Ok(database_infos) - } -} - -fn make_ref_id(database_id: &str, view_id: &str) -> String { - format!("{}:{}", database_id, view_id) -} - -#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[table_name = "database_refs"] -#[primary_key(ref_id)] -struct DatabaseRefRecord { - ref_id: String, - name: String, - is_base: bool, - view_id: String, - database_id: String, -} - -pub struct DatabaseViewRef { - pub view_id: String, - pub name: String, - pub database_id: String, -} -impl std::convert::From for DatabaseViewRef { - fn from(record: DatabaseRefRecord) -> Self { - Self { - view_id: record.view_id, - name: record.name, - database_id: record.database_id, - } - } -} - -pub struct DatabaseInfo { - pub name: String, - pub database_id: String, -} - -impl std::convert::From for DatabaseInfo { - fn from(record: DatabaseRefRecord) -> Self { - Self { - name: record.name, - database_id: record.database_id, - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/kv.rs b/frontend/rust-lib/flowy-database/src/services/persistence/kv.rs deleted file mode 100644 index f3da8c8911..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/persistence/kv.rs +++ /dev/null @@ -1,150 +0,0 @@ -use crate::services::persistence::DatabaseDBConnection; -use ::diesel::{query_dsl::*, ExpressionMethods}; -use bytes::Bytes; -use diesel::SqliteConnection; -use flowy_error::{FlowyError, FlowyResult}; -use flowy_sqlite::{ - prelude::*, - schema::{kv_table, kv_table::dsl}, -}; -use std::sync::Arc; - -#[derive(PartialEq, Eq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[table_name = "kv_table"] -#[primary_key(key)] -pub struct KeyValue { - key: String, - value: Vec, -} - -pub trait KVTransaction { - fn get>( - &self, - key: &str, - ) -> FlowyResult>; - fn set>(&self, value: T) -> FlowyResult<()>; - fn remove(&self, key: &str) -> FlowyResult<()>; - - fn batch_get>( - &self, - keys: Vec, - ) -> FlowyResult>; - fn batch_set>(&self, values: Vec) -> FlowyResult<()>; - fn batch_remove(&self, keys: Vec) -> FlowyResult<()>; -} - -pub struct DatabaseKVPersistence { - database: Arc, -} - -impl DatabaseKVPersistence { - pub fn new(database: Arc) -> Self { - Self { database } - } - - pub fn begin_transaction(&self, f: F) -> FlowyResult - where - F: for<'a> FnOnce(SqliteTransaction<'a>) -> FlowyResult, - { - let conn = self.database.get_db_connection()?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - let sql_transaction = SqliteTransaction { conn: &conn }; - f(sql_transaction) - }) - } -} - -impl KVTransaction for DatabaseKVPersistence { - fn get>( - &self, - key: &str, - ) -> FlowyResult> { - self.begin_transaction(|transaction| transaction.get(key)) - } - - fn set>(&self, value: T) -> FlowyResult<()> { - self.begin_transaction(|transaction| transaction.set(value)) - } - - fn remove(&self, key: &str) -> FlowyResult<()> { - self.begin_transaction(|transaction| transaction.remove(key)) - } - - fn batch_get>( - &self, - keys: Vec, - ) -> FlowyResult> { - self.begin_transaction(|transaction| transaction.batch_get(keys)) - } - - fn batch_set>(&self, values: Vec) -> FlowyResult<()> { - self.begin_transaction(|transaction| transaction.batch_set(values)) - } - - fn batch_remove(&self, keys: Vec) -> FlowyResult<()> { - self.begin_transaction(|transaction| transaction.batch_remove(keys)) - } -} - -pub struct SqliteTransaction<'a> { - conn: &'a SqliteConnection, -} - -impl<'a> KVTransaction for SqliteTransaction<'a> { - fn get>( - &self, - key: &str, - ) -> FlowyResult> { - let item = dsl::kv_table - .filter(kv_table::key.eq(key)) - .first::(self.conn)?; - let value = T::try_from(Bytes::from(item.value)).unwrap(); - Ok(Some(value)) - } - - fn set>(&self, value: T) -> FlowyResult<()> { - let item: KeyValue = value.into(); - let _ = diesel::replace_into(kv_table::table) - .values(&item) - .execute(self.conn)?; - Ok(()) - } - - fn remove(&self, key: &str) -> FlowyResult<()> { - let sql = dsl::kv_table.filter(kv_table::key.eq(key)); - let _ = diesel::delete(sql).execute(self.conn)?; - Ok(()) - } - - fn batch_get>( - &self, - keys: Vec, - ) -> FlowyResult> { - let items = dsl::kv_table - .filter(kv_table::key.eq_any(&keys)) - .load::(self.conn)?; - let mut values = vec![]; - for item in items { - let value = T::try_from(Bytes::from(item.value)).unwrap(); - values.push(value); - } - Ok(values) - } - - fn batch_set>(&self, values: Vec) -> FlowyResult<()> { - let items = values - .into_iter() - .map(|value| value.into()) - .collect::>(); - let _ = diesel::replace_into(kv_table::table) - .values(&items) - .execute(self.conn)?; - Ok(()) - } - - fn batch_remove(&self, keys: Vec) -> FlowyResult<()> { - let sql = dsl::kv_table.filter(kv_table::key.eq_any(keys)); - let _ = diesel::delete(sql).execute(self.conn)?; - Ok(()) - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/migration/database_migration.rs b/frontend/rust-lib/flowy-database/src/services/persistence/migration/database_migration.rs deleted file mode 100644 index 754d397bcb..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/persistence/migration/database_migration.rs +++ /dev/null @@ -1,83 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_variables)] -use crate::services::persistence::migration::MigratedDatabase; -use crate::services::persistence::rev_sqlite::SQLiteDatabaseRevisionPersistence; -use bytes::Bytes; -use database_model::DatabaseRevision; -use flowy_client_sync::client_database::{ - make_database_rev_json_str, DatabaseOperationsBuilder, DatabaseRevisionPad, -}; -use flowy_error::FlowyResult; -use flowy_revision::reset::{RevisionResettable, RevisionStructReset}; -use flowy_sqlite::kv::KV; -use flowy_sqlite::ConnectionPool; -use lib_infra::util::md5; -use revision_model::Revision; -use std::sync::Arc; - -const V1_MIGRATION: &str = "DATABASE_V1_MIGRATION"; -pub fn is_database_rev_migrated(user_id: i64) -> bool { - let key = migration_flag_key(user_id, V1_MIGRATION); - KV::get_bool(&key) -} - -pub(crate) async fn migration_database_rev_struct( - user_id: i64, - databases: &Vec, - pool: Arc, -) -> FlowyResult<()> { - if is_database_rev_migrated(user_id) || databases.is_empty() { - return Ok(()); - } - tracing::debug!("Migrate databases"); - for database in databases { - let object = DatabaseRevisionResettable { - database_id: database.view_id.clone(), - }; - let disk_cache = SQLiteDatabaseRevisionPersistence::new(pool.clone()); - let reset = RevisionStructReset::new(object, Arc::new(disk_cache)); - reset.run().await?; - } - let key = migration_flag_key(user_id, V1_MIGRATION); - KV::set_bool(&key, true); - Ok(()) -} - -fn migration_flag_key(user_id: i64, version: &str) -> String { - md5(format!("{}{}", user_id, version,)) -} - -struct DatabaseRevisionResettable { - database_id: String, -} - -impl RevisionResettable for DatabaseRevisionResettable { - fn target_id(&self) -> &str { - &self.database_id - } - - fn reset_data(&self, revisions: Vec) -> FlowyResult { - let pad = DatabaseRevisionPad::from_revisions(revisions)?; - let json = pad.json_str()?; - let bytes = DatabaseOperationsBuilder::new() - .insert(&json) - .build() - .json_bytes(); - Ok(bytes) - } - - fn default_target_rev_str(&self) -> FlowyResult { - let grid_rev = DatabaseRevision::default(); - let json = make_database_rev_json_str(&grid_rev)?; - Ok(json) - } - - fn read_record(&self) -> Option { - KV::get_str(self.target_id()) - } - - fn set_record(&self, record: String) { - KV::set_str(self.target_id(), record); - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/migration/database_view_migration.rs b/frontend/rust-lib/flowy-database/src/services/persistence/migration/database_view_migration.rs deleted file mode 100644 index 5394f250b5..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/persistence/migration/database_view_migration.rs +++ /dev/null @@ -1,146 +0,0 @@ -use crate::services::database_view::make_database_view_rev_manager; -use crate::services::persistence::database_ref::DatabaseRefs; -use flowy_error::FlowyResult; -use flowy_sqlite::kv::KV; - -use crate::services::persistence::migration::MigratedDatabase; -use crate::services::persistence::rev_sqlite::SQLiteDatabaseViewRevisionPersistence; -use bytes::Bytes; -use database_model::DatabaseViewRevision; -use flowy_client_sync::client_database::{ - make_database_view_operations, make_database_view_rev_json_str, DatabaseViewOperationsBuilder, - DatabaseViewRevisionPad, -}; -use flowy_revision::reset::{RevisionResettable, RevisionStructReset}; -use flowy_sqlite::{ - prelude::*, - schema::{grid_view_rev_table, grid_view_rev_table::dsl}, -}; -use lib_infra::util::md5; -use revision_model::Revision; -use std::sync::Arc; - -const DATABASE_VIEW_MIGRATE_1: &str = "database_view_migrate_v1"; - -pub fn is_database_view_migrated(user_id: &str) -> bool { - let key = md5(format!("{}{}", user_id, DATABASE_VIEW_MIGRATE_1)); - KV::get_bool(&key) -} - -pub(crate) async fn migrate_database_view( - user_id: i64, - database_refs: Arc, - migrated_databases: &Vec, - pool: Arc, -) -> FlowyResult<()> { - if is_database_view_migrated(&user_id.to_string()) || migrated_databases.is_empty() { - return Ok(()); - } - - let mut database_with_view = vec![]; - - let database_without_view = { - let conn = pool.get()?; - let databases = migrated_databases - .iter() - .filter(|database| { - let predicate = grid_view_rev_table::object_id.eq(&database.view_id); - let exist = diesel::dsl::exists(dsl::grid_view_rev_table.filter(predicate)); - match select(exist).get_result::(&*conn) { - Ok(is_exist) => { - if is_exist { - database_with_view.push((**database).clone()) - } - !is_exist - }, - Err(_) => true, - } - }) - .collect::>(); - drop(conn); - databases - }; - - // Create database view if it's not exist. - for database in database_without_view { - tracing::debug!("[Migration]: create database view: {}", database.view_id); - let database_id = database.view_id.clone(); - let database_view_id = database.view_id.clone(); - // - let database_view_rev = DatabaseViewRevision::new( - database_id, - database_view_id.clone(), - true, - database.name.clone(), - database.layout.clone(), - ); - 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(&database_view_id, database_view_bytes); - let rev_manager = make_database_view_rev_manager(pool.clone(), &database_view_id).await?; - rev_manager.reset_object(vec![revision]).await?; - } - - // Reset existing database view - for database in database_with_view { - let object = DatabaseViewRevisionResettable { - database_view_id: database.view_id.clone(), - }; - let disk_cache = SQLiteDatabaseViewRevisionPersistence::new(pool.clone()); - let reset = RevisionStructReset::new(object, Arc::new(disk_cache)); - reset.run().await?; - } - - tracing::debug!("[Migration]: Add database view refs"); - for database in migrated_databases { - // Bind the database with database view id. For historical reasons, - // the default database_id is empty, so the view_id will be used - // as the database_id. - let database_id = database.view_id.clone(); - let database_view_id = database.view_id.clone(); - tracing::debug!( - "Bind database:{} with view:{}", - database_id, - database_view_id - ); - let _ = database_refs.bind(&database_id, &database_view_id, true, &database.name); - } - - let key = md5(format!("{}{}", user_id, DATABASE_VIEW_MIGRATE_1)); - KV::set_bool(&key, true); - Ok(()) -} - -struct DatabaseViewRevisionResettable { - database_view_id: String, -} - -impl RevisionResettable for DatabaseViewRevisionResettable { - fn target_id(&self) -> &str { - &self.database_view_id - } - - fn reset_data(&self, revisions: Vec) -> FlowyResult { - let pad = DatabaseViewRevisionPad::from_revisions(revisions)?; - let json = pad.json_str()?; - let bytes = DatabaseViewOperationsBuilder::new() - .insert(&json) - .build() - .json_bytes(); - Ok(bytes) - } - - fn default_target_rev_str(&self) -> FlowyResult { - let database_view_rev = DatabaseViewRevision::default(); - let json = make_database_view_rev_json_str(&database_view_rev)?; - Ok(json) - } - - fn read_record(&self) -> Option { - KV::get_str(self.target_id()) - } - - fn set_record(&self, record: String) { - KV::set_str(self.target_id(), record); - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/migration/mod.rs b/frontend/rust-lib/flowy-database/src/services/persistence/migration/mod.rs deleted file mode 100644 index b109dc1604..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/persistence/migration/mod.rs +++ /dev/null @@ -1,69 +0,0 @@ -mod database_migration; -mod database_view_migration; -use crate::entities::DatabaseLayoutPB; -use crate::manager::DatabaseUser; -use crate::services::persistence::database_ref::DatabaseRefs; -use crate::services::persistence::migration::database_migration::{ - is_database_rev_migrated, migration_database_rev_struct, -}; -use crate::services::persistence::migration::database_view_migration::{ - is_database_view_migrated, migrate_database_view, -}; -use database_model::LayoutRevision; -use flowy_error::FlowyResult; -use lib_infra::future::Fut; -use std::sync::Arc; - -pub(crate) struct DatabaseMigration { - #[allow(dead_code)] - user: Arc, - database_refs: Arc, -} - -impl DatabaseMigration { - pub fn new(user: Arc, database_refs: Arc) -> Self { - Self { - user, - database_refs, - } - } - - pub async fn run( - &self, - user_id: i64, - get_views_fn: Fut>, - ) -> FlowyResult<()> { - let pool = self.user.db_pool()?; - - if !is_database_view_migrated(&user_id.to_string()) || !is_database_rev_migrated(user_id) { - let migrated_databases = get_views_fn - .await - .into_iter() - .map(|(view_id, name, layout)| MigratedDatabase { - view_id, - name, - layout: layout.into(), - }) - .collect::>(); - - migration_database_rev_struct(user_id, &migrated_databases, pool.clone()).await?; - - let _ = migrate_database_view( - user_id, - self.database_refs.clone(), - &migrated_databases, - pool.clone(), - ) - .await; - } - - Ok(()) - } -} - -#[derive(Debug, Clone)] -pub(crate) struct MigratedDatabase { - pub(crate) view_id: String, - pub(crate) name: String, - pub(crate) layout: LayoutRevision, -} diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-database/src/services/persistence/mod.rs deleted file mode 100644 index 70bc472840..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/persistence/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -use flowy_error::FlowyError; -use flowy_sqlite::{ConnectionPool, DBConnection}; -use std::sync::Arc; - -pub mod block_index; -pub mod database_ref; -pub mod kv; -pub mod migration; -pub mod rev_sqlite; - -pub trait DatabaseDBConnection: Send + Sync { - fn get_db_pool(&self) -> Result, FlowyError>; - - fn get_db_connection(&self) -> Result { - let pool = self.get_db_pool()?; - let conn = pool.get().map_err(|e| FlowyError::internal().context(e))?; - Ok(conn) - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/block_impl.rs b/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/block_impl.rs deleted file mode 100644 index 12b11d459f..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/block_impl.rs +++ /dev/null @@ -1,249 +0,0 @@ -use bytes::Bytes; -use diesel::{sql_types::Integer, update, SqliteConnection}; -use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_revision_persistence::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; -use flowy_sqlite::{ - impl_sql_integer_expression, insert_or_ignore_into, - prelude::*, - schema::{grid_meta_rev_table, grid_meta_rev_table::dsl}, - ConnectionPool, -}; -use lib_infra::util::md5; -use revision_model::{Revision, RevisionRange}; -use std::sync::Arc; - -pub struct SQLiteDatabaseBlockRevisionPersistence { - pub(crate) pool: Arc, -} - -impl RevisionDiskCache> for SQLiteDatabaseBlockRevisionPersistence { - type Error = FlowyError; - - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - DatabaseBlockMetaRevisionSql::create(revision_records, &conn)?; - Ok(()) - } - - fn get_connection(&self) -> Result, Self::Error> { - Ok(self.pool.clone()) - } - - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - let records = DatabaseBlockMetaRevisionSql::read(object_id, rev_ids, &conn)?; - Ok(records) - } - - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - let revisions = DatabaseBlockMetaRevisionSql::read_with_range(object_id, range.clone(), conn)?; - Ok(revisions) - } - - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - let conn = &*self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - for changeset in changesets { - DatabaseBlockMetaRevisionSql::update(changeset, conn)?; - } - Ok(()) - })?; - Ok(()) - } - - fn delete_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result<(), Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - DatabaseBlockMetaRevisionSql::delete(object_id, rev_ids, conn)?; - Ok(()) - } - - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - DatabaseBlockMetaRevisionSql::delete(object_id, deleted_rev_ids, &conn)?; - DatabaseBlockMetaRevisionSql::create(inserted_records, &conn)?; - Ok(()) - }) - } -} - -impl SQLiteDatabaseBlockRevisionPersistence { - pub fn new(pool: Arc) -> Self { - Self { pool } - } -} - -struct DatabaseBlockMetaRevisionSql(); -impl DatabaseBlockMetaRevisionSql { - fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { - // Batch insert: https://diesel.rs/guides/all-about-inserts.html - - let records = revision_records - .into_iter() - .map(|record| { - tracing::trace!( - "[{}] create revision: {}:{:?}", - std::any::type_name::(), - record.revision.object_id, - record.revision.rev_id - ); - let rev_state: GridBlockRevisionState = record.state.into(); - ( - dsl::object_id.eq(record.revision.object_id), - dsl::base_rev_id.eq(record.revision.base_rev_id), - dsl::rev_id.eq(record.revision.rev_id), - dsl::data.eq(record.revision.bytes), - dsl::state.eq(rev_state), - ) - }) - .collect::>(); - - let _ = insert_or_ignore_into(dsl::grid_meta_rev_table) - .values(&records) - .execute(conn)?; - Ok(()) - } - - fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { - let state: GridBlockRevisionState = changeset.state.clone().into(); - let filter = dsl::grid_meta_rev_table - .filter(dsl::rev_id.eq(changeset.rev_id)) - .filter(dsl::object_id.eq(changeset.object_id)); - let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?; - tracing::debug!( - "[{}] update revision:{} state:to {:?}", - std::any::type_name::(), - changeset.rev_id, - changeset.state - ); - Ok(()) - } - - fn read( - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let mut sql = dsl::grid_meta_rev_table - .filter(dsl::object_id.eq(object_id)) - .into_boxed(); - if let Some(rev_ids) = rev_ids { - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - let rows = sql - .order(dsl::rev_id.asc()) - .load::(conn)?; - let records = rows - .into_iter() - .map(mk_revision_record_from_table) - .collect::>(); - - Ok(records) - } - - fn read_with_range( - object_id: &str, - range: RevisionRange, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let rev_tables = dsl::grid_meta_rev_table - .filter(dsl::rev_id.ge(range.start)) - .filter(dsl::rev_id.le(range.end)) - .filter(dsl::object_id.eq(object_id)) - .order(dsl::rev_id.asc()) - .load::(conn)?; - - let revisions = rev_tables - .into_iter() - .map(mk_revision_record_from_table) - .collect::>(); - Ok(revisions) - } - - fn delete( - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result<(), FlowyError> { - let mut sql = diesel::delete(dsl::grid_meta_rev_table).into_boxed(); - sql = sql.filter(dsl::object_id.eq(object_id)); - - if let Some(rev_ids) = rev_ids { - tracing::trace!( - "[{}] Delete revision: {}:{:?}", - std::any::type_name::(), - object_id, - rev_ids - ); - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - - let affected_row = sql.execute(conn)?; - tracing::trace!( - "[{}] Delete {} rows", - std::any::type_name::(), - affected_row - ); - Ok(()) - } -} - -#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[table_name = "grid_meta_rev_table"] -struct GridBlockRevisionTable { - id: i32, - object_id: String, - base_rev_id: i64, - rev_id: i64, - data: Vec, - state: GridBlockRevisionState, -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] -#[repr(i32)] -#[sql_type = "Integer"] -pub enum GridBlockRevisionState { - Sync = 0, - Ack = 1, -} -impl_sql_integer_expression!(GridBlockRevisionState); -impl_rev_state_map!(GridBlockRevisionState); -impl std::default::Default for GridBlockRevisionState { - fn default() -> Self { - GridBlockRevisionState::Sync - } -} - -fn mk_revision_record_from_table(table: GridBlockRevisionTable) -> SyncRecord { - let md5 = md5(&table.data); - let revision = Revision::new( - &table.object_id, - table.base_rev_id, - table.rev_id, - Bytes::from(table.data), - md5, - ); - SyncRecord { - revision, - state: table.state.into(), - write_to_disk: false, - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/database_impl.rs b/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/database_impl.rs deleted file mode 100644 index e555327396..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/database_impl.rs +++ /dev/null @@ -1,249 +0,0 @@ -use bytes::Bytes; -use diesel::{sql_types::Integer, update, SqliteConnection}; -use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_revision_persistence::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; -use flowy_sqlite::{ - impl_sql_integer_expression, insert_or_ignore_into, - prelude::*, - schema::{grid_rev_table, grid_rev_table::dsl}, - ConnectionPool, -}; -use lib_infra::util::md5; -use revision_model::{Revision, RevisionRange}; -use std::sync::Arc; - -pub struct SQLiteDatabaseRevisionPersistence { - pub(crate) pool: Arc, -} - -impl RevisionDiskCache> for SQLiteDatabaseRevisionPersistence { - type Error = FlowyError; - - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - DatabaseRevisionSql::create(revision_records, &conn)?; - Ok(()) - } - - fn get_connection(&self) -> Result, Self::Error> { - Ok(self.pool.clone()) - } - - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - let records = DatabaseRevisionSql::read(object_id, rev_ids, &conn)?; - Ok(records) - } - - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - let revisions = DatabaseRevisionSql::read_with_range(object_id, range.clone(), conn)?; - Ok(revisions) - } - - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - let conn = &*self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - for changeset in changesets { - DatabaseRevisionSql::update(changeset, conn)?; - } - Ok(()) - })?; - Ok(()) - } - - fn delete_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result<(), Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - DatabaseRevisionSql::delete(object_id, rev_ids, conn)?; - Ok(()) - } - - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - DatabaseRevisionSql::delete(object_id, deleted_rev_ids, &conn)?; - DatabaseRevisionSql::create(inserted_records, &conn)?; - Ok(()) - }) - } -} - -impl SQLiteDatabaseRevisionPersistence { - pub fn new(pool: Arc) -> Self { - Self { pool } - } -} - -struct DatabaseRevisionSql(); -impl DatabaseRevisionSql { - fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { - // Batch insert: https://diesel.rs/guides/all-about-inserts.html - let records = revision_records - .into_iter() - .map(|record| { - tracing::trace!( - "[{}] create revision: {}:{:?}", - std::any::type_name::(), - record.revision.object_id, - record.revision.rev_id - ); - let rev_state: DatabaseRevisionState = record.state.into(); - ( - dsl::object_id.eq(record.revision.object_id), - dsl::base_rev_id.eq(record.revision.base_rev_id), - dsl::rev_id.eq(record.revision.rev_id), - dsl::data.eq(record.revision.bytes), - dsl::state.eq(rev_state), - ) - }) - .collect::>(); - - let _ = insert_or_ignore_into(dsl::grid_rev_table) - .values(&records) - .execute(conn)?; - Ok(()) - } - - fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { - let state: DatabaseRevisionState = changeset.state.clone().into(); - let filter = dsl::grid_rev_table - .filter(dsl::rev_id.eq(changeset.rev_id)) - .filter(dsl::object_id.eq(changeset.object_id)); - let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?; - tracing::debug!( - "[{}] update revision:{} state:to {:?}", - std::any::type_name::(), - changeset.rev_id, - changeset.state - ); - Ok(()) - } - - fn read( - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let mut sql = dsl::grid_rev_table - .filter(dsl::object_id.eq(object_id)) - .into_boxed(); - if let Some(rev_ids) = rev_ids { - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - let rows = sql - .order(dsl::rev_id.asc()) - .load::(conn)?; - let records = rows - .into_iter() - .map(mk_revision_record_from_table) - .collect::>(); - - Ok(records) - } - - fn read_with_range( - object_id: &str, - range: RevisionRange, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let rev_tables = dsl::grid_rev_table - .filter(dsl::rev_id.ge(range.start)) - .filter(dsl::rev_id.le(range.end)) - .filter(dsl::object_id.eq(object_id)) - .order(dsl::rev_id.asc()) - .load::(conn)?; - - let revisions = rev_tables - .into_iter() - .map(mk_revision_record_from_table) - .collect::>(); - Ok(revisions) - } - - fn delete( - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result<(), FlowyError> { - let mut sql = diesel::delete(dsl::grid_rev_table).into_boxed(); - sql = sql.filter(dsl::object_id.eq(object_id)); - - if let Some(rev_ids) = rev_ids { - tracing::trace!( - "[{}] Delete revision: {}:{:?}", - std::any::type_name::(), - object_id, - rev_ids - ); - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - - let affected_row = sql.execute(conn)?; - tracing::trace!( - "[{}] Delete {} rows", - std::any::type_name::(), - affected_row - ); - Ok(()) - } -} - -#[derive(PartialEq, Eq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[table_name = "grid_rev_table"] -pub struct DatabaseRevisionTable { - id: i32, - pub object_id: String, - pub base_rev_id: i64, - pub rev_id: i64, - pub data: Vec, - state: DatabaseRevisionState, -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] -#[repr(i32)] -#[sql_type = "Integer"] -pub enum DatabaseRevisionState { - Sync = 0, - Ack = 1, -} -impl_sql_integer_expression!(DatabaseRevisionState); -impl_rev_state_map!(DatabaseRevisionState); - -impl std::default::Default for DatabaseRevisionState { - fn default() -> Self { - DatabaseRevisionState::Sync - } -} - -fn mk_revision_record_from_table(table: DatabaseRevisionTable) -> SyncRecord { - let md5 = md5(&table.data); - let revision = Revision::new( - &table.object_id, - table.base_rev_id, - table.rev_id, - Bytes::from(table.data), - md5, - ); - SyncRecord { - revision, - state: table.state.into(), - write_to_disk: false, - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/mod.rs b/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/mod.rs deleted file mode 100644 index a71d59776f..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod block_impl; -mod database_impl; -mod snapshot_impl; -mod view_impl; - -pub use block_impl::*; -pub use database_impl::*; -pub use snapshot_impl::*; -pub use view_impl::*; diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/snapshot_impl.rs b/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/snapshot_impl.rs deleted file mode 100644 index c87d15c71d..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/snapshot_impl.rs +++ /dev/null @@ -1,118 +0,0 @@ -#![allow(clippy::unused_unit)] -use bytes::Bytes; -use flowy_error::{internal_error, FlowyResult}; -use flowy_revision::{RevisionSnapshotData, RevisionSnapshotPersistence}; -use flowy_sqlite::{ - prelude::*, - schema::{grid_rev_snapshot, grid_rev_snapshot::dsl}, - ConnectionPool, -}; -use lib_infra::util::timestamp; -use std::sync::Arc; - -pub struct SQLiteDatabaseRevisionSnapshotPersistence { - object_id: String, - pool: Arc, -} - -impl SQLiteDatabaseRevisionSnapshotPersistence { - pub fn new(object_id: &str, pool: Arc) -> Self { - Self { - object_id: object_id.to_string(), - pool, - } - } - - fn gen_snapshot_id(&self, rev_id: i64) -> String { - format!("{}:{}", self.object_id, rev_id) - } -} - -impl RevisionSnapshotPersistence for SQLiteDatabaseRevisionSnapshotPersistence { - fn write_snapshot(&self, rev_id: i64, data: Vec) -> FlowyResult<()> { - let conn = self.pool.get().map_err(internal_error)?; - let snapshot_id = self.gen_snapshot_id(rev_id); - let timestamp = timestamp(); - let record = ( - dsl::snapshot_id.eq(&snapshot_id), - dsl::object_id.eq(&self.object_id), - dsl::rev_id.eq(rev_id), - dsl::base_rev_id.eq(rev_id), - dsl::timestamp.eq(timestamp), - dsl::data.eq(data), - ); - let _ = insert_or_ignore_into(dsl::grid_rev_snapshot) - .values(record) - .execute(&*conn)?; - Ok(()) - - // conn.immediate_transaction::<_, FlowyError, _>(|| { - // let filter = dsl::grid_rev_snapshot - // .filter(dsl::object_id.eq(&self.object_id)) - // .filter(dsl::rev_id.eq(rev_id)); - // - // let is_exist: bool = select(exists(filter)).get_result(&*conn)?; - // match is_exist { - // false => { - // let record = ( - // dsl::object_id.eq(&self.object_id), - // dsl::rev_id.eq(rev_id), - // dsl::data.eq(data), - // ); - // insert_or_ignore_into(dsl::grid_rev_snapshot) - // .values(record) - // .execute(&*conn)?; - // } - // true => { - // let affected_row = update(filter).set(dsl::data.eq(data)).execute(&*conn)?; - // debug_assert_eq!(affected_row, 1); - // } - // } - // Ok(()) - // }) - } - - fn read_snapshot(&self, rev_id: i64) -> FlowyResult> { - let conn = self.pool.get().map_err(internal_error)?; - let snapshot_id = self.gen_snapshot_id(rev_id); - let record = dsl::grid_rev_snapshot - .filter(dsl::snapshot_id.eq(&snapshot_id)) - .first::(&*conn)?; - - Ok(Some(record.into())) - } - - fn read_last_snapshot(&self) -> FlowyResult> { - let conn = self.pool.get().map_err(internal_error)?; - let latest_record = dsl::grid_rev_snapshot - .filter(dsl::object_id.eq(&self.object_id)) - .order(dsl::timestamp.desc()) - // .select(max(dsl::rev_id)) - // .select((dsl::id, dsl::object_id, dsl::rev_id, dsl::data)) - .first::(&*conn)?; - Ok(Some(latest_record.into())) - } -} - -#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[table_name = "grid_rev_snapshot"] -#[primary_key("snapshot_id")] -struct GridSnapshotRecord { - snapshot_id: String, - object_id: String, - rev_id: i64, - base_rev_id: i64, - timestamp: i64, - data: Vec, -} - -impl std::convert::From for RevisionSnapshotData { - fn from(record: GridSnapshotRecord) -> Self { - RevisionSnapshotData { - rev_id: record.rev_id, - base_rev_id: record.base_rev_id, - timestamp: record.timestamp, - data: Bytes::from(record.data), - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/view_impl.rs b/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/view_impl.rs deleted file mode 100644 index 6e19d1cbd4..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/persistence/rev_sqlite/view_impl.rs +++ /dev/null @@ -1,249 +0,0 @@ -use bytes::Bytes; -use diesel::{sql_types::Integer, update, SqliteConnection}; -use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_revision_persistence::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; -use flowy_sqlite::{ - impl_sql_integer_expression, insert_or_ignore_into, - prelude::*, - schema::{grid_view_rev_table, grid_view_rev_table::dsl}, - ConnectionPool, -}; -use lib_infra::util::md5; -use revision_model::{Revision, RevisionRange}; -use std::sync::Arc; - -pub struct SQLiteDatabaseViewRevisionPersistence { - pub(crate) pool: Arc, -} - -impl SQLiteDatabaseViewRevisionPersistence { - pub fn new(pool: Arc) -> Self { - Self { pool } - } -} - -impl RevisionDiskCache> for SQLiteDatabaseViewRevisionPersistence { - type Error = FlowyError; - - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - DatabaseViewRevisionSql::create(revision_records, &conn)?; - Ok(()) - } - - fn get_connection(&self) -> Result, Self::Error> { - Ok(self.pool.clone()) - } - - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - let records = DatabaseViewRevisionSql::read(object_id, rev_ids, &conn)?; - Ok(records) - } - - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - let revisions = DatabaseViewRevisionSql::read_with_range(object_id, range.clone(), conn)?; - Ok(revisions) - } - - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - let conn = &*self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - for changeset in changesets { - DatabaseViewRevisionSql::update(changeset, conn)?; - } - Ok(()) - })?; - Ok(()) - } - - fn delete_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result<(), Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - DatabaseViewRevisionSql::delete(object_id, rev_ids, conn)?; - Ok(()) - } - - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - DatabaseViewRevisionSql::delete(object_id, deleted_rev_ids, &conn)?; - DatabaseViewRevisionSql::create(inserted_records, &conn)?; - Ok(()) - }) - } -} - -struct DatabaseViewRevisionSql(); -impl DatabaseViewRevisionSql { - fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { - // Batch insert: https://diesel.rs/guides/all-about-inserts.html - let records = revision_records - .into_iter() - .map(|record| { - tracing::trace!( - "[{}] create revision: {}:{:?}", - std::any::type_name::(), - record.revision.object_id, - record.revision.rev_id - ); - let rev_state: DatabaseViewRevisionState = record.state.into(); - ( - dsl::object_id.eq(record.revision.object_id), - dsl::base_rev_id.eq(record.revision.base_rev_id), - dsl::rev_id.eq(record.revision.rev_id), - dsl::data.eq(record.revision.bytes), - dsl::state.eq(rev_state), - ) - }) - .collect::>(); - - let _ = insert_or_ignore_into(dsl::grid_view_rev_table) - .values(&records) - .execute(conn)?; - Ok(()) - } - - fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { - let state: DatabaseViewRevisionState = changeset.state.clone().into(); - let filter = dsl::grid_view_rev_table - .filter(dsl::rev_id.eq(changeset.rev_id)) - .filter(dsl::object_id.eq(changeset.object_id)); - let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?; - tracing::debug!( - "[{}] update revision:{} state:to {:?}", - std::any::type_name::(), - changeset.rev_id, - changeset.state - ); - Ok(()) - } - - fn read( - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let mut sql = dsl::grid_view_rev_table - .filter(dsl::object_id.eq(object_id)) - .into_boxed(); - if let Some(rev_ids) = rev_ids { - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - let rows = sql - .order(dsl::rev_id.asc()) - .load::(conn)?; - let records = rows - .into_iter() - .map(mk_revision_record_from_table) - .collect::>(); - - Ok(records) - } - - fn read_with_range( - object_id: &str, - range: RevisionRange, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let rev_tables = dsl::grid_view_rev_table - .filter(dsl::rev_id.ge(range.start)) - .filter(dsl::rev_id.le(range.end)) - .filter(dsl::object_id.eq(object_id)) - .order(dsl::rev_id.asc()) - .load::(conn)?; - - let revisions = rev_tables - .into_iter() - .map(mk_revision_record_from_table) - .collect::>(); - Ok(revisions) - } - - fn delete( - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result<(), FlowyError> { - let mut sql = diesel::delete(dsl::grid_view_rev_table).into_boxed(); - sql = sql.filter(dsl::object_id.eq(object_id)); - - if let Some(rev_ids) = rev_ids { - tracing::trace!( - "[{}] Delete revision: {}:{:?}", - std::any::type_name::(), - object_id, - rev_ids - ); - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - - let affected_row = sql.execute(conn)?; - tracing::trace!( - "[{}] Delete {} rows", - std::any::type_name::(), - affected_row - ); - Ok(()) - } -} - -#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[table_name = "grid_view_rev_table"] -struct DatabaseViewRevisionTable { - id: i32, - object_id: String, - base_rev_id: i64, - rev_id: i64, - data: Vec, - state: DatabaseViewRevisionState, -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] -#[repr(i32)] -#[sql_type = "Integer"] -pub enum DatabaseViewRevisionState { - Sync = 0, - Ack = 1, -} -impl_sql_integer_expression!(DatabaseViewRevisionState); -impl_rev_state_map!(DatabaseViewRevisionState); - -impl std::default::Default for DatabaseViewRevisionState { - fn default() -> Self { - DatabaseViewRevisionState::Sync - } -} - -fn mk_revision_record_from_table(table: DatabaseViewRevisionTable) -> SyncRecord { - let md5 = md5(&table.data); - let revision = Revision::new( - &table.object_id, - table.base_rev_id, - table.rev_id, - Bytes::from(table.data), - md5, - ); - SyncRecord { - revision, - state: table.state.into(), - write_to_disk: false, - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/row/mod.rs b/frontend/rust-lib/flowy-database/src/services/row/mod.rs deleted file mode 100644 index 8af63cc54a..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/row/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod row_builder; -mod row_loader; - -pub use row_builder::*; -pub use row_loader::*; diff --git a/frontend/rust-lib/flowy-database/src/services/row/row_builder.rs b/frontend/rust-lib/flowy-database/src/services/row/row_builder.rs deleted file mode 100644 index 8d4a4f95f2..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/row/row_builder.rs +++ /dev/null @@ -1,187 +0,0 @@ -use crate::services::cell::{ - insert_checkbox_cell, insert_date_cell, insert_number_cell, insert_select_option_cell, - insert_text_cell, insert_url_cell, FromCellString, -}; - -use crate::entities::FieldType; -use crate::services::field::{CheckboxCellData, DateCellData, SelectOptionIds}; -use database_model::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT}; -use indexmap::IndexMap; -use std::collections::HashMap; -use std::sync::Arc; - -pub struct RowRevisionBuilder { - block_id: String, - field_rev_map: HashMap>, - payload: CreateRowRevisionPayload, -} - -impl RowRevisionBuilder { - pub fn new(block_id: &str, fields: Vec>) -> Self { - Self::new_with_data(block_id, fields, Default::default()) - } - - pub fn new_with_data( - block_id: &str, - field_revs: Vec>, - cell_data_by_field_id: HashMap, - ) -> Self { - let field_rev_map = field_revs - .iter() - .map(|field| (field.id.clone(), field.clone())) - .collect::>>(); - - let payload = CreateRowRevisionPayload { - row_id: gen_row_id(), - cell_by_field_id: Default::default(), - height: DEFAULT_ROW_HEIGHT, - visibility: true, - }; - - let block_id = block_id.to_string(); - let mut builder = Self { - block_id, - field_rev_map, - payload, - }; - - for (field_id, cell_data) in cell_data_by_field_id { - if let Some(field_rev) = builder.field_rev_map.get(&field_id) { - let field_type: FieldType = field_rev.ty.into(); - match field_type { - FieldType::RichText => builder.insert_text_cell(&field_id, cell_data), - FieldType::Number => { - if let Ok(num) = cell_data.parse::() { - builder.insert_number_cell(&field_id, num) - } - }, - FieldType::DateTime => { - if let Ok(date_cell_data) = DateCellData::from_cell_str(&cell_data) { - builder.insert_date_cell(&field_id, date_cell_data) - } - }, - FieldType::MultiSelect | FieldType::SingleSelect => { - if let Ok(ids) = SelectOptionIds::from_cell_str(&cell_data) { - builder.insert_select_option_cell(&field_id, ids.into_inner()); - } - }, - FieldType::Checkbox => { - if let Ok(value) = CheckboxCellData::from_cell_str(&cell_data) { - builder.insert_checkbox_cell(&field_id, value.into_inner()); - } - }, - FieldType::URL => { - builder.insert_url_cell(&field_id, cell_data); - }, - FieldType::Checklist => { - if let Ok(ids) = SelectOptionIds::from_cell_str(&cell_data) { - builder.insert_select_option_cell(&field_id, ids.into_inner()); - } - }, - } - } - } - builder - } - - pub fn insert_text_cell(&mut self, field_id: &str, data: String) { - match self.field_rev_map.get(&field_id.to_owned()) { - None => tracing::warn!("Can't find the text field with id: {}", field_id), - Some(field_rev) => { - self - .payload - .cell_by_field_id - .insert(field_id.to_owned(), insert_text_cell(data, field_rev)); - }, - } - } - - pub fn insert_url_cell(&mut self, field_id: &str, data: String) { - match self.field_rev_map.get(&field_id.to_owned()) { - None => tracing::warn!("Can't find the url field with id: {}", field_id), - Some(field_rev) => { - self - .payload - .cell_by_field_id - .insert(field_id.to_owned(), insert_url_cell(data, field_rev)); - }, - } - } - - pub fn insert_number_cell(&mut self, field_id: &str, num: i64) { - match self.field_rev_map.get(&field_id.to_owned()) { - None => tracing::warn!("Can't find the number field with id: {}", field_id), - Some(field_rev) => { - self - .payload - .cell_by_field_id - .insert(field_id.to_owned(), insert_number_cell(num, field_rev)); - }, - } - } - - pub fn insert_checkbox_cell(&mut self, field_id: &str, is_check: bool) { - match self.field_rev_map.get(&field_id.to_owned()) { - None => tracing::warn!("Can't find the checkbox field with id: {}", field_id), - Some(field_rev) => { - self.payload.cell_by_field_id.insert( - field_id.to_owned(), - insert_checkbox_cell(is_check, field_rev), - ); - }, - } - } - - pub fn insert_date_cell(&mut self, field_id: &str, date_cell_data: DateCellData) { - match self.field_rev_map.get(&field_id.to_owned()) { - None => tracing::warn!("Can't find the date field with id: {}", field_id), - Some(field_rev) => { - self.payload.cell_by_field_id.insert( - field_id.to_owned(), - insert_date_cell(date_cell_data, field_rev), - ); - }, - } - } - - pub fn insert_select_option_cell(&mut self, field_id: &str, option_ids: Vec) { - match self.field_rev_map.get(&field_id.to_owned()) { - None => tracing::warn!("Can't find the select option field with id: {}", field_id), - Some(field_rev) => { - self.payload.cell_by_field_id.insert( - field_id.to_owned(), - insert_select_option_cell(option_ids, field_rev), - ); - }, - } - } - - #[allow(dead_code)] - pub fn height(mut self, height: i32) -> Self { - self.payload.height = height; - self - } - - #[allow(dead_code)] - pub fn visibility(mut self, visibility: bool) -> Self { - self.payload.visibility = visibility; - self - } - - pub fn build(self) -> RowRevision { - RowRevision { - id: self.payload.row_id, - block_id: self.block_id, - cells: self.payload.cell_by_field_id, - height: self.payload.height, - visibility: self.payload.visibility, - } - } -} - -pub struct CreateRowRevisionPayload { - pub row_id: String, - pub cell_by_field_id: IndexMap, - pub height: i32, - pub visibility: bool, -} diff --git a/frontend/rust-lib/flowy-database/src/services/row/row_loader.rs b/frontend/rust-lib/flowy-database/src/services/row/row_loader.rs deleted file mode 100644 index 3808c1f1ab..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/row/row_loader.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::entities::RowPB; -use database_model::RowRevision; - -use std::sync::Arc; - -pub struct DatabaseBlockRowRevision { - pub(crate) block_id: String, - pub row_revs: Vec>, -} - -pub struct DatabaseBlockRow { - pub block_id: String, - pub row_ids: Vec, -} - -impl DatabaseBlockRow { - pub fn new(block_id: String, row_ids: Vec) -> Self { - Self { block_id, row_ids } - } -} - -pub(crate) fn make_row_from_row_rev(row_rev: Arc) -> RowPB { - make_rows_from_row_revs(&[row_rev]).pop().unwrap() -} - -pub(crate) fn make_rows_from_row_revs(row_revs: &[Arc]) -> Vec { - let make_row = |row_rev: &Arc| RowPB { - block_id: row_rev.block_id.clone(), - id: row_rev.id.clone(), - height: row_rev.height, - }; - - row_revs.iter().map(make_row).collect::>() -} diff --git a/frontend/rust-lib/flowy-database/src/services/setting/mod.rs b/frontend/rust-lib/flowy-database/src/services/setting/mod.rs deleted file mode 100644 index 229b814e25..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/setting/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod setting_builder; - -pub use setting_builder::*; diff --git a/frontend/rust-lib/flowy-database/src/services/setting/setting_builder.rs b/frontend/rust-lib/flowy-database/src/services/setting/setting_builder.rs deleted file mode 100644 index 1b5ba1d4d1..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/setting/setting_builder.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::entities::{ - AlterFilterParams, DatabaseLayoutPB, DatabaseSettingChangesetParams, DeleteFilterParams, -}; - -pub struct GridSettingChangesetBuilder { - params: DatabaseSettingChangesetParams, -} - -impl GridSettingChangesetBuilder { - pub fn new(grid_id: &str, layout_type: &DatabaseLayoutPB) -> Self { - let params = DatabaseSettingChangesetParams { - view_id: grid_id.to_string(), - layout_type: layout_type.clone().into(), - insert_filter: None, - delete_filter: None, - insert_group: None, - delete_group: None, - alert_sort: None, - delete_sort: None, - }; - Self { params } - } - - pub fn insert_filter(mut self, params: AlterFilterParams) -> Self { - self.params.insert_filter = Some(params); - self - } - - pub fn delete_filter(mut self, params: DeleteFilterParams) -> Self { - self.params.delete_filter = Some(params); - self - } - - pub fn build(self) -> DatabaseSettingChangesetParams { - self.params - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/sort/controller.rs b/frontend/rust-lib/flowy-database/src/services/sort/controller.rs deleted file mode 100644 index dba5d7916b..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/sort/controller.rs +++ /dev/null @@ -1,303 +0,0 @@ -use crate::entities::FieldType; -use crate::entities::SortChangesetNotificationPB; -use crate::services::cell::{AtomicCellDataCache, TypeCellData}; -use crate::services::database_view::{DatabaseViewChanged, DatabaseViewChangedNotifier}; -use crate::services::field::{default_order, TypeOptionCellExt}; -use crate::services::sort::{ - ReorderAllRowsResult, ReorderSingleRowResult, SortChangeset, SortType, -}; -use database_model::{CellRevision, FieldRevision, RowRevision, SortCondition, SortRevision}; -use flowy_error::FlowyResult; -use flowy_task::{QualityOfService, Task, TaskContent, TaskDispatcher}; -use lib_infra::future::Fut; -use rayon::prelude::ParallelSliceMut; -use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; -use std::collections::HashMap; -use std::str::FromStr; -use std::sync::Arc; -use tokio::sync::RwLock; - -pub trait SortDelegate: Send + Sync { - fn get_sort_rev(&self, sort_type: SortType) -> Fut>>; - /// Returns all the rows after applying grid's filter - fn get_row_revs(&self) -> Fut>>; - fn get_field_rev(&self, field_id: &str) -> Fut>>; - fn get_field_revs(&self, field_ids: Option>) -> Fut>>; -} - -pub struct SortController { - view_id: String, - handler_id: String, - delegate: Box, - task_scheduler: Arc>, - sorts: Vec>, - cell_data_cache: AtomicCellDataCache, - row_index_cache: HashMap, - notifier: DatabaseViewChangedNotifier, -} - -impl Drop for SortController { - fn drop(&mut self) { - tracing::trace!("Drop {}", std::any::type_name::()); - } -} - -impl SortController { - pub fn new( - view_id: &str, - handler_id: &str, - sorts: Vec>, - delegate: T, - task_scheduler: Arc>, - cell_data_cache: AtomicCellDataCache, - notifier: DatabaseViewChangedNotifier, - ) -> Self - where - T: SortDelegate + 'static, - { - Self { - view_id: view_id.to_string(), - handler_id: handler_id.to_string(), - delegate: Box::new(delegate), - task_scheduler, - sorts, - cell_data_cache, - row_index_cache: Default::default(), - notifier, - } - } - - pub async fn close(&self) { - if let Ok(mut task_scheduler) = self.task_scheduler.try_write() { - task_scheduler.unregister_handler(&self.handler_id).await; - } else { - tracing::error!("Try to get the lock of task_scheduler failed"); - } - } - - pub async fn did_receive_row_changed(&self, row_id: &str) { - let task_type = SortEvent::RowDidChanged(row_id.to_string()); - self.gen_task(task_type, QualityOfService::Background).await; - } - - #[tracing::instrument(name = "process_sort_task", level = "trace", skip_all, err)] - pub async fn process(&mut self, predicate: &str) -> FlowyResult<()> { - let event_type = SortEvent::from_str(predicate).unwrap(); - let mut row_revs = self.delegate.get_row_revs().await; - match event_type { - SortEvent::SortDidChanged => { - self.sort_rows(&mut row_revs).await; - let row_orders = row_revs - .iter() - .map(|row_rev| row_rev.id.clone()) - .collect::>(); - - let notification = ReorderAllRowsResult { - view_id: self.view_id.clone(), - row_orders, - }; - - let _ = self - .notifier - .send(DatabaseViewChanged::ReorderAllRowsNotification( - notification, - )); - }, - SortEvent::RowDidChanged(row_id) => { - let old_row_index = self.row_index_cache.get(&row_id).cloned(); - self.sort_rows(&mut row_revs).await; - let new_row_index = self.row_index_cache.get(&row_id).cloned(); - match (old_row_index, new_row_index) { - (Some(old_row_index), Some(new_row_index)) => { - if old_row_index == new_row_index { - return Ok(()); - } - let notification = ReorderSingleRowResult { - row_id, - view_id: self.view_id.clone(), - old_index: old_row_index, - new_index: new_row_index, - }; - let _ = self - .notifier - .send(DatabaseViewChanged::ReorderSingleRowNotification( - notification, - )); - }, - _ => tracing::trace!("The row index cache is outdated"), - } - }, - } - Ok(()) - } - - #[tracing::instrument(name = "schedule_sort_task", level = "trace", skip(self))] - async fn gen_task(&self, task_type: SortEvent, qos: QualityOfService) { - let task_id = self.task_scheduler.read().await.next_task_id(); - let task = Task::new( - &self.handler_id, - task_id, - TaskContent::Text(task_type.to_string()), - qos, - ); - self.task_scheduler.write().await.add_task(task); - } - - pub async fn sort_rows(&mut self, rows: &mut Vec>) { - if self.sorts.is_empty() { - return; - } - - let field_revs = self.delegate.get_field_revs(None).await; - for sort in self.sorts.iter() { - rows - .par_sort_by(|left, right| cmp_row(left, right, sort, &field_revs, &self.cell_data_cache)); - } - rows.iter().enumerate().for_each(|(index, row)| { - self.row_index_cache.insert(row.id.to_string(), index); - }); - } - - pub async fn delete_all_sorts(&mut self) { - self.sorts.clear(); - self - .gen_task(SortEvent::SortDidChanged, QualityOfService::Background) - .await; - } - - pub async fn did_update_view_field_type_option(&self, _field_rev: &FieldRevision) { - // - } - - #[tracing::instrument(level = "trace", skip(self))] - pub async fn did_receive_changes( - &mut self, - changeset: SortChangeset, - ) -> SortChangesetNotificationPB { - let mut notification = SortChangesetNotificationPB::new(self.view_id.clone()); - if let Some(insert_sort) = changeset.insert_sort { - if let Some(sort) = self.delegate.get_sort_rev(insert_sort).await { - notification.insert_sorts.push(sort.as_ref().into()); - self.sorts.push(sort); - } - } - - if let Some(delete_sort_type) = changeset.delete_sort { - if let Some(index) = self - .sorts - .iter() - .position(|sort| sort.id == delete_sort_type.sort_id) - { - let sort = self.sorts.remove(index); - notification.delete_sorts.push(sort.as_ref().into()); - } - } - - if let Some(update_sort) = changeset.update_sort { - if let Some(updated_sort) = self.delegate.get_sort_rev(update_sort).await { - notification.update_sorts.push(updated_sort.as_ref().into()); - if let Some(index) = self - .sorts - .iter() - .position(|sort| sort.id == updated_sort.id) - { - self.sorts[index] = updated_sort; - } - } - } - - if !notification.is_empty() { - self - .gen_task(SortEvent::SortDidChanged, QualityOfService::UserInteractive) - .await; - } - tracing::trace!("sort notification: {:?}", notification); - notification - } -} - -fn cmp_row( - left: &Arc, - right: &Arc, - sort: &Arc, - field_revs: &[Arc], - cell_data_cache: &AtomicCellDataCache, -) -> Ordering { - let order = match ( - left.cells.get(&sort.field_id), - right.cells.get(&sort.field_id), - ) { - (Some(left_cell), Some(right_cell)) => { - let field_type: FieldType = sort.field_type.into(); - match field_revs - .iter() - .find(|field_rev| field_rev.id == sort.field_id) - { - None => default_order(), - Some(field_rev) => cmp_cell( - left_cell, - right_cell, - field_rev, - field_type, - cell_data_cache, - ), - } - }, - (Some(_), None) => Ordering::Greater, - (None, Some(_)) => Ordering::Less, - _ => default_order(), - }; - - // The order is calculated by Ascending. So reverse the order if the SortCondition is descending. - match sort.condition { - SortCondition::Ascending => order, - SortCondition::Descending => order.reverse(), - } -} - -fn cmp_cell( - left_cell: &CellRevision, - right_cell: &CellRevision, - field_rev: &Arc, - field_type: FieldType, - cell_data_cache: &AtomicCellDataCache, -) -> Ordering { - match TypeOptionCellExt::new_with_cell_data_cache( - field_rev.as_ref(), - Some(cell_data_cache.clone()), - ) - .get_type_option_cell_data_handler(&field_type) - { - None => default_order(), - Some(handler) => { - let cal_order = || { - let left_cell_str = TypeCellData::try_from(left_cell).ok()?.into_inner(); - let right_cell_str = TypeCellData::try_from(right_cell).ok()?.into_inner(); - let order = - handler.handle_cell_compare(&left_cell_str, &right_cell_str, field_rev.as_ref()); - Option::::Some(order) - }; - - cal_order().unwrap_or_else(default_order) - }, - } -} -#[derive(Serialize, Deserialize, Clone, Debug)] -enum SortEvent { - SortDidChanged, - RowDidChanged(String), -} - -impl ToString for SortEvent { - fn to_string(&self) -> String { - serde_json::to_string(self).unwrap() - } -} - -impl FromStr for SortEvent { - type Err = serde_json::Error; - fn from_str(s: &str) -> Result { - serde_json::from_str(s) - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/sort/entities.rs b/frontend/rust-lib/flowy-database/src/services/sort/entities.rs deleted file mode 100644 index 911b4794bc..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/sort/entities.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::entities::{AlterSortParams, DeleteSortParams, FieldType}; -use database_model::{FieldRevision, FieldTypeRevision}; -use std::sync::Arc; - -#[derive(Hash, Eq, PartialEq, Debug, Clone)] -pub struct SortType { - pub field_id: String, - pub field_type: FieldType, -} - -impl From for FieldTypeRevision { - fn from(sort_type: SortType) -> Self { - sort_type.field_type.into() - } -} - -impl std::convert::From<&AlterSortParams> for SortType { - fn from(params: &AlterSortParams) -> Self { - Self { - field_id: params.field_id.clone(), - field_type: params.field_type.into(), - } - } -} - -impl std::convert::From<&Arc> for SortType { - fn from(rev: &Arc) -> Self { - Self { - field_id: rev.id.clone(), - field_type: rev.ty.into(), - } - } -} - -#[derive(Clone)] -pub struct ReorderAllRowsResult { - pub view_id: String, - pub row_orders: Vec, -} - -impl ReorderAllRowsResult { - pub fn new(view_id: String, row_orders: Vec) -> Self { - Self { - view_id, - row_orders, - } - } -} - -#[derive(Clone)] -pub struct ReorderSingleRowResult { - pub view_id: String, - pub row_id: String, - pub old_index: usize, - pub new_index: usize, -} - -#[derive(Debug)] -pub struct SortChangeset { - pub(crate) insert_sort: Option, - pub(crate) update_sort: Option, - pub(crate) delete_sort: Option, -} - -impl SortChangeset { - pub fn from_insert(sort: SortType) -> Self { - Self { - insert_sort: Some(sort), - update_sort: None, - delete_sort: None, - } - } - - pub fn from_update(sort: SortType) -> Self { - Self { - insert_sort: None, - update_sort: Some(sort), - delete_sort: None, - } - } - - pub fn from_delete(deleted_sort: DeletedSortType) -> Self { - Self { - insert_sort: None, - update_sort: None, - delete_sort: Some(deleted_sort), - } - } -} - -#[derive(Debug)] -pub struct DeletedSortType { - pub sort_type: SortType, - pub sort_id: String, -} - -impl std::convert::From for DeletedSortType { - fn from(params: DeleteSortParams) -> Self { - Self { - sort_type: params.sort_type, - sort_id: params.sort_id, - } - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/sort/mod.rs b/frontend/rust-lib/flowy-database/src/services/sort/mod.rs deleted file mode 100644 index 89f10043b0..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/sort/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod controller; -mod entities; -mod task; - -pub use controller::*; -pub use entities::*; -pub use task::*; diff --git a/frontend/rust-lib/flowy-database/src/services/sort/task.rs b/frontend/rust-lib/flowy-database/src/services/sort/task.rs deleted file mode 100644 index 9bac020e8d..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/sort/task.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::services::sort::SortController; -use flowy_task::{TaskContent, TaskHandler}; -use lib_infra::future::BoxResultFuture; -use std::sync::Arc; -use tokio::sync::RwLock; - -pub struct SortTaskHandler { - handler_id: String, - #[allow(dead_code)] - sort_controller: Arc>, -} - -impl SortTaskHandler { - pub fn new(handler_id: String, sort_controller: Arc>) -> Self { - Self { - handler_id, - sort_controller, - } - } -} - -impl TaskHandler for SortTaskHandler { - fn handler_id(&self) -> &str { - &self.handler_id - } - - fn handler_name(&self) -> &str { - "SortTaskHandler" - } - - fn run(&self, content: TaskContent) -> BoxResultFuture<(), anyhow::Error> { - let sort_controller = self.sort_controller.clone(); - Box::pin(async move { - if let TaskContent::Text(predicate) = content { - sort_controller - .write() - .await - .process(&predicate) - .await - .map_err(anyhow::Error::from)?; - } - Ok(()) - }) - } -} diff --git a/frontend/rust-lib/flowy-database/src/services/util.rs b/frontend/rust-lib/flowy-database/src/services/util.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/frontend/rust-lib/flowy-database/src/services/util.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/rust-lib/flowy-database/src/util.rs b/frontend/rust-lib/flowy-database/src/util.rs deleted file mode 100644 index 269105549a..0000000000 --- a/frontend/rust-lib/flowy-database/src/util.rs +++ /dev/null @@ -1,243 +0,0 @@ -use crate::entities::FieldType; -use crate::services::field::*; -use crate::services::row::RowRevisionBuilder; -use database_model::{BuildDatabaseContext, CalendarLayoutSetting, LayoutRevision, LayoutSetting}; -use flowy_client_sync::client_database::DatabaseBuilder; - -pub fn make_default_grid() -> BuildDatabaseContext { - let mut database_builder = DatabaseBuilder::new(); - // text - let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Name") - .visibility(true) - .primary(true) - .build(); - database_builder.add_field(text_field); - - // single select - let single_select = SingleSelectTypeOptionBuilder::default(); - let single_select_field = FieldBuilder::new(single_select) - .name("Type") - .visibility(true) - .build(); - database_builder.add_field(single_select_field); - - // checkbox - let checkbox_field = FieldBuilder::from_field_type(&FieldType::Checkbox) - .name("Done") - .visibility(true) - .build(); - database_builder.add_field(checkbox_field); - - database_builder.add_empty_row(); - database_builder.add_empty_row(); - database_builder.add_empty_row(); - database_builder.build() -} - -pub fn make_default_board() -> BuildDatabaseContext { - let mut database_builder = DatabaseBuilder::new(); - // text - let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Description") - .visibility(true) - .primary(true) - .build(); - let text_field_id = text_field.id.clone(); - database_builder.add_field(text_field); - - // single select - let to_do_option = SelectOptionPB::with_color("To Do", SelectOptionColorPB::Purple); - let doing_option = SelectOptionPB::with_color("Doing", SelectOptionColorPB::Orange); - let done_option = SelectOptionPB::with_color("Done", SelectOptionColorPB::Yellow); - let single_select_type_option = SingleSelectTypeOptionBuilder::default() - .add_option(to_do_option.clone()) - .add_option(doing_option) - .add_option(done_option); - let single_select_field = FieldBuilder::new(single_select_type_option) - .name("Status") - .visibility(true) - .build(); - let single_select_field_id = single_select_field.id.clone(); - database_builder.add_field(single_select_field); - - for i in 0..3 { - let mut row_builder = RowRevisionBuilder::new( - database_builder.block_id(), - database_builder.field_revs().clone(), - ); - row_builder.insert_select_option_cell(&single_select_field_id, vec![to_do_option.id.clone()]); - let data = format!("Card {}", i + 1); - row_builder.insert_text_cell(&text_field_id, data); - let row = row_builder.build(); - database_builder.add_row(row); - } - - database_builder.build() -} - -pub fn make_default_calendar() -> BuildDatabaseContext { - let mut database_builder = DatabaseBuilder::new(); - // text - let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Title") - .visibility(true) - .primary(true) - .build(); - database_builder.add_field(text_field); - - // date - let date_type_option = DateTypeOptionBuilder::default(); - let date_field = FieldBuilder::new(date_type_option) - .name("Date") - .visibility(true) - .build(); - let date_field_id = date_field.id.clone(); - database_builder.add_field(date_field); - - // single select - let multi_select_type_option = MultiSelectTypeOptionBuilder::default(); - let multi_select_field = FieldBuilder::new(multi_select_type_option) - .name("Tags") - .visibility(true) - .build(); - database_builder.add_field(multi_select_field); - - let calendar_layout_setting = CalendarLayoutSetting::new(date_field_id); - let mut layout_setting = LayoutSetting::new(); - let calendar_setting = serde_json::to_string(&calendar_layout_setting).unwrap(); - layout_setting.insert(LayoutRevision::Calendar, calendar_setting); - database_builder.set_layout_setting(layout_setting); - database_builder.build() -} - -#[allow(dead_code)] -pub fn make_default_board_2() -> BuildDatabaseContext { - let mut database_builder = DatabaseBuilder::new(); - // text - let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Description") - .visibility(true) - .primary(true) - .build(); - let text_field_id = text_field.id.clone(); - database_builder.add_field(text_field); - - // single select - let to_do_option = SelectOptionPB::with_color("To Do", SelectOptionColorPB::Purple); - let doing_option = SelectOptionPB::with_color("Doing", SelectOptionColorPB::Orange); - let done_option = SelectOptionPB::with_color("Done", SelectOptionColorPB::Yellow); - let single_select_type_option = SingleSelectTypeOptionBuilder::default() - .add_option(to_do_option.clone()) - .add_option(doing_option.clone()) - .add_option(done_option.clone()); - let single_select_field = FieldBuilder::new(single_select_type_option) - .name("Status") - .visibility(true) - .build(); - let single_select_field_id = single_select_field.id.clone(); - database_builder.add_field(single_select_field); - - // MultiSelect - let work_option = SelectOptionPB::with_color("Work", SelectOptionColorPB::Aqua); - let travel_option = SelectOptionPB::with_color("Travel", SelectOptionColorPB::Green); - let fun_option = SelectOptionPB::with_color("Fun", SelectOptionColorPB::Lime); - let health_option = SelectOptionPB::with_color("Health", SelectOptionColorPB::Pink); - let multi_select_type_option = MultiSelectTypeOptionBuilder::default() - .add_option(travel_option.clone()) - .add_option(work_option.clone()) - .add_option(fun_option.clone()) - .add_option(health_option.clone()); - let multi_select_field = FieldBuilder::new(multi_select_type_option) - .name("Tags") - .visibility(true) - .build(); - let multi_select_field_id = multi_select_field.id.clone(); - database_builder.add_field(multi_select_field); - - for i in 0..3 { - let mut row_builder = RowRevisionBuilder::new( - database_builder.block_id(), - database_builder.field_revs().clone(), - ); - row_builder.insert_select_option_cell(&single_select_field_id, vec![to_do_option.id.clone()]); - match i { - 0 => { - row_builder.insert_text_cell(&text_field_id, "Update AppFlowy Website".to_string()); - row_builder.insert_select_option_cell(&multi_select_field_id, vec![work_option.id.clone()]); - }, - 1 => { - row_builder.insert_text_cell(&text_field_id, "Learn French".to_string()); - let mut options = SelectOptionIds::new(); - options.push(fun_option.id.clone()); - options.push(travel_option.id.clone()); - row_builder.insert_select_option_cell(&multi_select_field_id, vec![options.to_string()]); - }, - - 2 => { - row_builder.insert_text_cell(&text_field_id, "Exercise 4x/week".to_string()); - row_builder.insert_select_option_cell(&multi_select_field_id, vec![fun_option.id.clone()]); - }, - _ => {}, - } - let row = row_builder.build(); - database_builder.add_row(row); - } - - for i in 0..3 { - let mut row_builder = RowRevisionBuilder::new( - database_builder.block_id(), - database_builder.field_revs().clone(), - ); - row_builder.insert_select_option_cell(&single_select_field_id, vec![doing_option.id.clone()]); - match i { - 0 => { - row_builder.insert_text_cell(&text_field_id, "Learn how to swim".to_string()); - row_builder.insert_select_option_cell(&multi_select_field_id, vec![fun_option.id.clone()]); - }, - 1 => { - row_builder.insert_text_cell(&text_field_id, "Meditate 10 mins each day".to_string()); - row_builder - .insert_select_option_cell(&multi_select_field_id, vec![health_option.id.clone()]); - }, - - 2 => { - row_builder.insert_text_cell(&text_field_id, "Write atomic essays ".to_string()); - let mut options = SelectOptionIds::new(); - options.push(fun_option.id.clone()); - options.push(work_option.id.clone()); - row_builder.insert_select_option_cell(&multi_select_field_id, vec![options.to_string()]); - }, - _ => {}, - } - let row = row_builder.build(); - database_builder.add_row(row); - } - - for i in 0..2 { - let mut row_builder = RowRevisionBuilder::new( - database_builder.block_id(), - database_builder.field_revs().clone(), - ); - row_builder.insert_select_option_cell(&single_select_field_id, vec![done_option.id.clone()]); - match i { - 0 => { - row_builder.insert_text_cell(&text_field_id, "Publish an article".to_string()); - row_builder.insert_select_option_cell(&multi_select_field_id, vec![work_option.id.clone()]); - }, - 1 => { - row_builder.insert_text_cell(&text_field_id, "Visit Chicago".to_string()); - let mut options = SelectOptionIds::new(); - options.push(travel_option.id.clone()); - options.push(fun_option.id.clone()); - row_builder.insert_select_option_cell(&multi_select_field_id, vec![options.to_string()]); - }, - - _ => {}, - } - let row = row_builder.build(); - database_builder.add_row(row); - } - - database_builder.build() -} diff --git a/frontend/rust-lib/flowy-database/tests/database/block_test/block_test.rs b/frontend/rust-lib/flowy-database/tests/database/block_test/block_test.rs deleted file mode 100644 index 6828e8bace..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/block_test/block_test.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::database::block_test::script::DatabaseRowTest; -use crate::database::block_test::script::RowScript::*; - -use database_model::{DatabaseBlockMetaRevision, DatabaseBlockMetaRevisionChangeset}; - -#[tokio::test] -async fn grid_create_block() { - let block_meta_rev = DatabaseBlockMetaRevision::new(); - let scripts = vec![ - AssertBlockCount(1), - CreateBlock { - block: block_meta_rev, - }, - AssertBlockCount(2), - ]; - DatabaseRowTest::new().await.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_update_block() { - let block_meta_rev = DatabaseBlockMetaRevision::new(); - let mut cloned_grid_block = block_meta_rev.clone(); - let changeset = DatabaseBlockMetaRevisionChangeset { - block_id: block_meta_rev.block_id.clone(), - start_row_index: Some(2), - row_count: Some(10), - }; - - cloned_grid_block.start_row_index = 2; - cloned_grid_block.row_count = 10; - - let scripts = vec![ - AssertBlockCount(1), - CreateBlock { - block: block_meta_rev, - }, - UpdateBlock { changeset }, - AssertBlockCount(2), - AssertBlockEqual { - block_index: 1, - block: cloned_grid_block, - }, - ]; - DatabaseRowTest::new().await.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/block_test/mod.rs b/frontend/rust-lib/flowy-database/tests/database/block_test/mod.rs deleted file mode 100644 index c982869655..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/block_test/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#![allow(clippy::module_inception)] -mod block_test; -mod row_test; -mod script; -pub mod util; diff --git a/frontend/rust-lib/flowy-database/tests/database/block_test/row_test.rs b/frontend/rust-lib/flowy-database/tests/database/block_test/row_test.rs deleted file mode 100644 index 3e31f985dd..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/block_test/row_test.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::database::block_test::script::RowScript::*; -use crate::database::block_test::script::{CreateRowScriptBuilder, DatabaseRowTest}; -use crate::database::mock_data::{COMPLETED, FACEBOOK, GOOGLE, PAUSED, TWITTER}; -use database_model::RowChangeset; -use flowy_database::entities::FieldType; -use flowy_database::services::field::{SELECTION_IDS_SEPARATOR, UNCHECK}; - -#[tokio::test] -async fn grid_create_row_count_test() { - let mut test = DatabaseRowTest::new().await; - let scripts = vec![ - AssertRowCount(6), - CreateEmptyRow, - CreateEmptyRow, - CreateRow { - row_rev: test.row_builder().build(), - }, - AssertRowCount(9), - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_update_row() { - let mut test = DatabaseRowTest::new().await; - let row_rev = test.row_builder().build(); - let changeset = RowChangeset { - row_id: row_rev.id.clone(), - height: None, - visibility: None, - cell_by_field_id: Default::default(), - }; - let row_count = test.row_revs.len(); - let scripts = vec![CreateRow { row_rev }, UpdateRow { changeset }]; - test.run_scripts(scripts).await; - - let expected_row = test.last_row().unwrap(); - let scripts = vec![AssertRow { expected_row }, AssertRowCount(row_count + 1)]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_delete_row() { - let mut test = DatabaseRowTest::new().await; - let row_1 = test.row_builder().build(); - let row_2 = test.row_builder().build(); - let row_ids = vec![row_1.id.clone(), row_2.id.clone()]; - let row_count = test.row_revs.len() as i32; - let scripts = vec![ - CreateRow { row_rev: row_1 }, - CreateRow { row_rev: row_2 }, - AssertBlockCount(1), - AssertBlock { - block_index: 0, - row_count: row_count + 2, - start_row_index: 0, - }, - DeleteRows { row_ids }, - AssertBlock { - block_index: 0, - row_count, - start_row_index: 0, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_row_add_cells_test() { - let mut test = DatabaseRowTest::new().await; - let mut builder = CreateRowScriptBuilder::new(&test); - builder.insert(FieldType::RichText, "hello world", "hello world"); - builder.insert(FieldType::DateTime, "1647251762", "2022/03/14"); - builder.insert(FieldType::Number, "18,443", "$18,443.00"); - builder.insert(FieldType::Checkbox, "false", UNCHECK); - builder.insert(FieldType::URL, "https://appflowy.io", "https://appflowy.io"); - builder.insert_single_select_cell(|mut options| options.remove(0), COMPLETED); - builder.insert_multi_select_cell( - |options| options, - &vec![GOOGLE, FACEBOOK, TWITTER].join(SELECTION_IDS_SEPARATOR), - ); - let scripts = builder.build(); - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_row_insert_number_test() { - let mut test = DatabaseRowTest::new().await; - for (val, expected) in &[("1647251762", "2022/03/14"), ("2022/03/14", ""), ("", "")] { - let mut builder = CreateRowScriptBuilder::new(&test); - builder.insert(FieldType::DateTime, val, expected); - let scripts = builder.build(); - test.run_scripts(scripts).await; - } -} - -#[tokio::test] -async fn grid_row_insert_date_test() { - let mut test = DatabaseRowTest::new().await; - for (val, expected) in &[ - ("18,443", "$18,443.00"), - ("0", "$0.00"), - ("100000", "$100,000.00"), - ("$100,000.00", "$100,000.00"), - ("", ""), - ] { - let mut builder = CreateRowScriptBuilder::new(&test); - builder.insert(FieldType::Number, val, expected); - let scripts = builder.build(); - test.run_scripts(scripts).await; - } -} -#[tokio::test] -async fn grid_row_insert_single_select_test() { - let mut test = DatabaseRowTest::new().await; - let mut builder = CreateRowScriptBuilder::new(&test); - builder.insert_single_select_cell(|mut options| options.pop().unwrap(), PAUSED); - let scripts = builder.build(); - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_row_insert_multi_select_test() { - let mut test = DatabaseRowTest::new().await; - let mut builder = CreateRowScriptBuilder::new(&test); - builder.insert_multi_select_cell( - |mut options| { - options.remove(0); - options - }, - &vec![FACEBOOK, TWITTER].join(SELECTION_IDS_SEPARATOR), - ); - let scripts = builder.build(); - test.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/block_test/script.rs b/frontend/rust-lib/flowy-database/tests/database/block_test/script.rs deleted file mode 100644 index da13700b4d..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/block_test/script.rs +++ /dev/null @@ -1,425 +0,0 @@ -use crate::database::block_test::script::RowScript::{AssertCell, CreateRow}; -use crate::database::block_test::util::DatabaseRowTestBuilder; -use crate::database::database_editor::DatabaseEditorTest; -use database_model::{ - DatabaseBlockMetaRevision, DatabaseBlockMetaRevisionChangeset, RowChangeset, RowRevision, -}; -use flowy_database::entities::{CellIdParams, CreateRowParams, FieldType, RowPB}; -use flowy_database::services::field::*; -use flowy_database::services::row::DatabaseBlockRow; -use std::collections::HashMap; -use std::sync::Arc; -use strum::IntoEnumIterator; - -pub enum RowScript { - CreateEmptyRow, - CreateRow { - row_rev: RowRevision, - }, - UpdateRow { - changeset: RowChangeset, - }, - AssertRow { - expected_row: RowRevision, - }, - DeleteRows { - row_ids: Vec, - }, - AssertCell { - row_id: String, - field_id: String, - field_type: FieldType, - expected: String, - }, - AssertRowCount(usize), - CreateBlock { - block: DatabaseBlockMetaRevision, - }, - UpdateBlock { - changeset: DatabaseBlockMetaRevisionChangeset, - }, - AssertBlockCount(usize), - AssertBlock { - block_index: usize, - row_count: i32, - start_row_index: i32, - }, - AssertBlockEqual { - block_index: usize, - block: DatabaseBlockMetaRevision, - }, -} - -pub struct DatabaseRowTest { - inner: DatabaseEditorTest, -} - -impl DatabaseRowTest { - pub async fn new() -> Self { - let editor_test = DatabaseEditorTest::new_grid().await; - Self { inner: editor_test } - } - - pub fn last_row(&self) -> Option { - self.row_revs.last().map(|a| a.clone().as_ref().clone()) - } - - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - pub fn row_builder(&self) -> DatabaseRowTestBuilder { - DatabaseRowTestBuilder::new(self.block_id().to_string(), self.field_revs.clone()) - } - - pub async fn run_script(&mut self, script: RowScript) { - match script { - RowScript::CreateEmptyRow => { - let params = CreateRowParams { - view_id: self.editor.database_id.clone(), - start_row_id: None, - group_id: None, - cell_data_by_field_id: None, - }; - let row_order = self.editor.create_row(params).await.unwrap(); - self - .row_by_row_id - .insert(row_order.row_id().to_owned(), row_order); - self.row_revs = self.get_row_revs().await; - self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); - }, - RowScript::CreateRow { row_rev } => { - let row_orders = self.editor.insert_rows(vec![row_rev]).await.unwrap(); - for row_order in row_orders { - self - .row_by_row_id - .insert(row_order.row_id().to_owned(), row_order); - } - self.row_revs = self.get_row_revs().await; - self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); - }, - RowScript::UpdateRow { changeset: change } => self.editor.update_row(change).await.unwrap(), - RowScript::DeleteRows { row_ids } => { - let row_pbs = row_ids - .into_iter() - .map(|row_id| self.row_by_row_id.get(&row_id).unwrap().clone()) - .collect::>(); - - let block_rows = block_from_row_pbs(row_pbs); - self.editor.delete_rows(block_rows).await.unwrap(); - self.row_revs = self.get_row_revs().await; - self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); - }, - RowScript::AssertCell { - row_id, - field_id, - field_type, - expected, - } => { - let id = CellIdParams { - view_id: self.view_id.clone(), - field_id, - row_id, - }; - self.compare_cell_content(id, field_type, expected).await; - }, - RowScript::AssertRow { expected_row } => { - let row = &*self - .row_revs - .iter() - .find(|row| row.id == expected_row.id) - .cloned() - .unwrap(); - assert_eq!(&expected_row, row); - }, - RowScript::AssertRowCount(expected_row_count) => { - assert_eq!(expected_row_count, self.row_revs.len()); - }, - RowScript::CreateBlock { block } => { - self.editor.create_block(block).await.unwrap(); - self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); - }, - RowScript::UpdateBlock { changeset: change } => { - self.editor.update_block(change).await.unwrap(); - }, - RowScript::AssertBlockCount(count) => { - assert_eq!( - self.editor.get_block_meta_revs().await.unwrap().len(), - count - ); - }, - RowScript::AssertBlock { - block_index, - row_count, - start_row_index, - } => { - assert_eq!(self.block_meta_revs[block_index].row_count, row_count); - assert_eq!( - self.block_meta_revs[block_index].start_row_index, - start_row_index - ); - }, - RowScript::AssertBlockEqual { block_index, block } => { - let blocks = self.editor.get_block_meta_revs().await.unwrap(); - let compared_block = blocks[block_index].clone(); - assert_eq!(compared_block, Arc::new(block)); - }, - } - } - - async fn compare_cell_content( - &self, - cell_id: CellIdParams, - field_type: FieldType, - expected: String, - ) { - match field_type { - FieldType::RichText => { - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .parser::() - .unwrap(); - - assert_eq!(cell_data.as_ref(), &expected); - }, - FieldType::Number => { - let field_rev = self.editor.get_field_rev(&cell_id.field_id).await.unwrap(); - let number_type_option = field_rev - .get_type_option::(FieldType::Number.into()) - .unwrap(); - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .custom_parser(NumberCellCustomDataParser(number_type_option.format)) - .unwrap(); - assert_eq!(cell_data.to_string(), expected); - }, - FieldType::DateTime => { - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .parser::() - .unwrap(); - - assert_eq!(cell_data.date, expected); - }, - FieldType::SingleSelect => { - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .parser::() - .unwrap(); - let select_option = cell_data.select_options.first().unwrap(); - assert_eq!(select_option.name, expected); - }, - FieldType::MultiSelect => { - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .parser::() - .unwrap(); - - let s = cell_data - .select_options - .into_iter() - .map(|option| option.name) - .collect::>() - .join(SELECTION_IDS_SEPARATOR); - - assert_eq!(s, expected); - }, - - FieldType::Checklist => { - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .parser::() - .unwrap(); - - let s = cell_data - .select_options - .into_iter() - .map(|option| option.name) - .collect::>() - .join(SELECTION_IDS_SEPARATOR); - - assert_eq!(s, expected); - }, - FieldType::Checkbox => { - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .parser::() - .unwrap(); - assert_eq!(cell_data.to_string(), expected); - }, - FieldType::URL => { - let cell_data = self - .editor - .get_cell_protobuf(&cell_id) - .await - .unwrap() - .parser::() - .unwrap(); - - assert_eq!(cell_data.content, expected); - // assert_eq!(cell_data.url, expected); - }, - } - } -} - -fn block_from_row_pbs(row_orders: Vec) -> Vec { - let mut map: HashMap = HashMap::new(); - row_orders.into_iter().for_each(|row_pb| { - let block_id = row_pb.block_id().to_owned(); - let cloned_block_id = block_id.clone(); - map - .entry(block_id) - .or_insert_with(|| DatabaseBlockRow::new(cloned_block_id, vec![])) - .row_ids - .push(row_pb.id); - }); - map.into_values().collect::>() -} - -impl std::ops::Deref for DatabaseRowTest { - type Target = DatabaseEditorTest; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl std::ops::DerefMut for DatabaseRowTest { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} - -pub struct CreateRowScriptBuilder { - builder: DatabaseRowTestBuilder, - data_by_field_type: HashMap, - output_by_field_type: HashMap, -} - -impl CreateRowScriptBuilder { - pub fn new(test: &DatabaseRowTest) -> Self { - Self { - builder: test.row_builder(), - data_by_field_type: HashMap::new(), - output_by_field_type: HashMap::new(), - } - } - - pub fn insert(&mut self, field_type: FieldType, input: &str, expected: &str) { - self.data_by_field_type.insert( - field_type, - CellTestData { - input: input.to_string(), - expected: expected.to_owned(), - }, - ); - } - - pub fn insert_single_select_cell(&mut self, f: F, expected: &str) - where - F: Fn(Vec) -> SelectOptionPB, - { - let field_id = self.builder.insert_single_select_cell(f); - self.output_by_field_type.insert( - FieldType::SingleSelect, - CellTestOutput { - field_id, - expected: expected.to_owned(), - }, - ); - } - - pub fn insert_multi_select_cell(&mut self, f: F, expected: &str) - where - F: Fn(Vec) -> Vec, - { - let field_id = self.builder.insert_multi_select_cell(f); - self.output_by_field_type.insert( - FieldType::MultiSelect, - CellTestOutput { - field_id, - expected: expected.to_owned(), - }, - ); - } - - pub fn build(mut self) -> Vec { - let mut scripts = vec![]; - let output_by_field_type = &mut self.output_by_field_type; - - for field_type in FieldType::iter() { - let field_type: FieldType = field_type; - if let Some(data) = self.data_by_field_type.get(&field_type) { - let field_id = match field_type { - FieldType::RichText => self.builder.insert_text_cell(&data.input), - FieldType::Number => self.builder.insert_number_cell(&data.input), - FieldType::DateTime => self.builder.insert_date_cell(&data.input), - FieldType::Checkbox => self.builder.insert_checkbox_cell(&data.input), - FieldType::URL => self.builder.insert_url_cell(&data.input), - _ => "".to_owned(), - }; - - if !field_id.is_empty() { - output_by_field_type.insert( - field_type, - CellTestOutput { - field_id, - expected: data.expected.clone(), - }, - ); - } - } - } - - let row_rev = self.builder.build(); - let row_id = row_rev.id.clone(); - scripts.push(CreateRow { row_rev }); - - for field_type in FieldType::iter() { - if let Some(data) = output_by_field_type.get(&field_type) { - let script = AssertCell { - row_id: row_id.clone(), - field_id: data.field_id.clone(), - field_type, - expected: data.expected.clone(), - }; - scripts.push(script); - } - } - scripts - } -} - -pub struct CellTestData { - pub input: String, - pub expected: String, -} - -struct CellTestOutput { - field_id: String, - expected: String, -} diff --git a/frontend/rust-lib/flowy-database/tests/database/block_test/util.rs b/frontend/rust-lib/flowy-database/tests/database/block_test/util.rs deleted file mode 100644 index f2d1fc9a89..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/block_test/util.rs +++ /dev/null @@ -1,150 +0,0 @@ -use database_model::{FieldRevision, RowRevision}; -use flowy_database::entities::FieldType; -use flowy_database::services::field::{ - ChecklistTypeOptionPB, DateCellChangeset, MultiSelectTypeOptionPB, SelectOptionPB, - SingleSelectTypeOptionPB, -}; -use flowy_database::services::row::RowRevisionBuilder; -use std::sync::Arc; - -pub struct DatabaseRowTestBuilder { - field_revs: Vec>, - inner_builder: RowRevisionBuilder, -} - -impl DatabaseRowTestBuilder { - pub fn new(block_id: String, field_revs: Vec>) -> Self { - let inner_builder = RowRevisionBuilder::new(&block_id, field_revs.clone()); - Self { - field_revs, - inner_builder, - } - } - - pub fn insert_text_cell(&mut self, data: &str) -> String { - let text_field = self.field_rev_with_type(&FieldType::RichText); - self - .inner_builder - .insert_text_cell(&text_field.id, data.to_string()); - - text_field.id.clone() - } - - pub fn insert_number_cell(&mut self, data: &str) -> String { - let number_field = self.field_rev_with_type(&FieldType::Number); - self - .inner_builder - .insert_text_cell(&number_field.id, data.to_string()); - number_field.id.clone() - } - - pub fn insert_date_cell(&mut self, data: &str) -> String { - let value = serde_json::to_string(&DateCellChangeset { - date: Some(data.to_string()), - time: None, - is_utc: true, - include_time: Some(false), - }) - .unwrap(); - let date_field = self.field_rev_with_type(&FieldType::DateTime); - self.inner_builder.insert_text_cell(&date_field.id, value); - date_field.id.clone() - } - - pub fn insert_checkbox_cell(&mut self, data: &str) -> String { - let checkbox_field = self.field_rev_with_type(&FieldType::Checkbox); - self - .inner_builder - .insert_text_cell(&checkbox_field.id, data.to_string()); - - checkbox_field.id.clone() - } - - pub fn insert_url_cell(&mut self, content: &str) -> String { - let url_field = self.field_rev_with_type(&FieldType::URL); - self - .inner_builder - .insert_url_cell(&url_field.id, content.to_string()); - url_field.id.clone() - } - - pub fn insert_single_select_cell(&mut self, f: F) -> String - where - F: Fn(Vec) -> SelectOptionPB, - { - let single_select_field = self.field_rev_with_type(&FieldType::SingleSelect); - let type_option = SingleSelectTypeOptionPB::from(&single_select_field); - let option = f(type_option.options); - self - .inner_builder - .insert_select_option_cell(&single_select_field.id, vec![option.id]); - - single_select_field.id.clone() - } - - pub fn insert_multi_select_cell(&mut self, f: F) -> String - where - F: Fn(Vec) -> Vec, - { - let multi_select_field = self.field_rev_with_type(&FieldType::MultiSelect); - let type_option = MultiSelectTypeOptionPB::from(&multi_select_field); - let options = f(type_option.options); - let ops_ids = options - .iter() - .map(|option| option.id.clone()) - .collect::>(); - self - .inner_builder - .insert_select_option_cell(&multi_select_field.id, ops_ids); - - multi_select_field.id.clone() - } - - pub fn insert_checklist_cell(&mut self, f: F) -> String - where - F: Fn(Vec) -> Vec, - { - let checklist_field = self.field_rev_with_type(&FieldType::Checklist); - let type_option = ChecklistTypeOptionPB::from(&checklist_field); - let options = f(type_option.options); - let ops_ids = options - .iter() - .map(|option| option.id.clone()) - .collect::>(); - self - .inner_builder - .insert_select_option_cell(&checklist_field.id, ops_ids); - - checklist_field.id.clone() - } - pub fn field_rev_with_type(&self, field_type: &FieldType) -> FieldRevision { - self - .field_revs - .iter() - .find(|field_rev| { - let t_field_type: FieldType = field_rev.ty.into(); - &t_field_type == field_type - }) - .unwrap() - .as_ref() - .clone() - } - - pub fn build(self) -> RowRevision { - self.inner_builder.build() - } -} - -impl std::ops::Deref for DatabaseRowTestBuilder { - type Target = RowRevisionBuilder; - - fn deref(&self) -> &Self::Target { - &self.inner_builder - } -} - -impl std::ops::DerefMut for DatabaseRowTestBuilder { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner_builder - } -} diff --git a/frontend/rust-lib/flowy-database/tests/database/cell_test/mod.rs b/frontend/rust-lib/flowy-database/tests/database/cell_test/mod.rs deleted file mode 100644 index 63d424afaf..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/cell_test/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod script; -mod test; diff --git a/frontend/rust-lib/flowy-database/tests/database/cell_test/script.rs b/frontend/rust-lib/flowy-database/tests/database/cell_test/script.rs deleted file mode 100644 index 0dc72cd6ea..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/cell_test/script.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::database::database_editor::DatabaseEditorTest; -use flowy_database::entities::CellChangesetPB; - -pub enum CellScript { - UpdateCell { - changeset: CellChangesetPB, - is_err: bool, - }, -} - -pub struct DatabaseCellTest { - inner: DatabaseEditorTest, -} - -impl DatabaseCellTest { - pub async fn new() -> Self { - let inner = DatabaseEditorTest::new_grid().await; - Self { inner } - } - - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - pub async fn run_script(&mut self, script: CellScript) { - // let grid_manager = self.sdk.grid_manager.clone(); - // let pool = self.sdk.user_session.db_pool().unwrap(); - // let rev_manager = self.editor.rev_manager(); - // let _cache = rev_manager.revision_cache().await; - - match script { - CellScript::UpdateCell { changeset, is_err } => { - let result = self - .editor - .update_cell_with_changeset( - &changeset.row_id, - &changeset.field_id, - changeset.type_cell_data, - ) - .await; - if is_err { - assert!(result.is_err()) - } else { - result.unwrap(); - } - }, // CellScript::AssertGridRevisionPad => { - // sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await; - // let mut grid_rev_manager = grid_manager.make_grid_rev_manager(&self.grid_id, pool.clone()).unwrap(); - // let grid_pad = grid_rev_manager.load::(None).await.unwrap(); - // println!("{}", grid_pad.delta_str()); - // } - } - } -} - -impl std::ops::Deref for DatabaseCellTest { - type Target = DatabaseEditorTest; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl std::ops::DerefMut for DatabaseCellTest { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} diff --git a/frontend/rust-lib/flowy-database/tests/database/cell_test/test.rs b/frontend/rust-lib/flowy-database/tests/database/cell_test/test.rs deleted file mode 100644 index 4657c641be..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/cell_test/test.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::database::cell_test::script::CellScript::*; -use crate::database::cell_test::script::DatabaseCellTest; -use crate::database::field_test::util::make_date_cell_string; -use flowy_database::entities::{CellChangesetPB, FieldType}; -use flowy_database::services::cell::ToCellChangesetString; -use flowy_database::services::field::selection_type_option::SelectOptionCellChangeset; -use flowy_database::services::field::{ - ChecklistTypeOptionPB, MultiSelectTypeOptionPB, SingleSelectTypeOptionPB, -}; - -#[tokio::test] -async fn grid_cell_update() { - let mut test = DatabaseCellTest::new().await; - let field_revs = &test.field_revs; - let row_revs = &test.row_revs; - let grid_blocks = &test.block_meta_revs; - - // For the moment, We only have one block to store rows - let block_id = &grid_blocks.first().unwrap().block_id; - - let mut scripts = vec![]; - for (_, row_rev) in row_revs.iter().enumerate() { - for field_rev in field_revs { - let field_type: FieldType = field_rev.ty.into(); - let data = match field_type { - FieldType::RichText => "".to_string(), - FieldType::Number => "123".to_string(), - FieldType::DateTime => make_date_cell_string("123"), - FieldType::SingleSelect => { - let type_option = SingleSelectTypeOptionPB::from(field_rev); - SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id) - .to_cell_changeset_str() - }, - FieldType::MultiSelect => { - let type_option = MultiSelectTypeOptionPB::from(field_rev); - SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id) - .to_cell_changeset_str() - }, - FieldType::Checklist => { - let type_option = ChecklistTypeOptionPB::from(field_rev); - SelectOptionCellChangeset::from_insert_option_id(&type_option.options.first().unwrap().id) - .to_cell_changeset_str() - }, - FieldType::Checkbox => "1".to_string(), - FieldType::URL => "1".to_string(), - }; - - scripts.push(UpdateCell { - changeset: CellChangesetPB { - view_id: block_id.to_string(), - row_id: row_rev.id.clone(), - field_id: field_rev.id.clone(), - type_cell_data: data, - }, - is_err: false, - }); - } - } - - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn text_cell_date_test() { - let test = DatabaseCellTest::new().await; - let text_field = test.get_first_field_rev(FieldType::RichText); - let cells = test - .editor - .get_cells_for_field(&test.view_id, &text_field.id) - .await - .unwrap(); - - for (i, cell) in cells.into_iter().enumerate() { - let text = cell.into_text_field_cell_data().unwrap(); - match i { - 0 => assert_eq!(text.as_str(), "A"), - 1 => assert_eq!(text.as_str(), ""), - 2 => assert_eq!(text.as_str(), "C"), - 3 => assert_eq!(text.as_str(), "DA"), - 4 => assert_eq!(text.as_str(), "AE"), - 5 => assert_eq!(text.as_str(), "AE"), - _ => {}, - } - } -} - -#[tokio::test] -async fn url_cell_date_test() { - let test = DatabaseCellTest::new().await; - let url_field = test.get_first_field_rev(FieldType::URL); - let cells = test - .editor - .get_cells_for_field(&test.view_id, &url_field.id) - .await - .unwrap(); - - for (i, cell) in cells.into_iter().enumerate() { - let url_cell_data = cell.into_url_field_cell_data().unwrap(); - if i == 0 { - assert_eq!(url_cell_data.url.as_str(), "https://www.appflowy.io/"); - } - } -} diff --git a/frontend/rust-lib/flowy-database/tests/database/database_editor.rs b/frontend/rust-lib/flowy-database/tests/database/database_editor.rs deleted file mode 100644 index 90b0dcbf4d..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/database_editor.rs +++ /dev/null @@ -1,212 +0,0 @@ -use crate::database::mock_data::*; -use bytes::Bytes; -use database_model::*; -use flowy_database::entities::*; -use flowy_database::services::cell::ToCellChangesetString; -use flowy_database::services::database::DatabaseEditor; -use flowy_database::services::field::SelectOptionPB; -use flowy_database::services::field::*; -use flowy_test::helper::ViewTest; -use flowy_test::FlowySDKTest; -use std::collections::HashMap; -use std::sync::Arc; -use strum::EnumCount; - -pub struct DatabaseEditorTest { - pub sdk: FlowySDKTest, - pub app_id: String, - pub view_id: String, - pub editor: Arc, - pub field_revs: Vec>, - pub block_meta_revs: Vec>, - pub row_revs: Vec>, - pub field_count: usize, - pub row_by_row_id: HashMap, -} - -impl DatabaseEditorTest { - pub async fn new_grid() -> Self { - Self::new(DatabaseLayoutPB::Grid).await - } - - pub async fn new_board() -> Self { - Self::new(DatabaseLayoutPB::Board).await - } - - pub async fn new_calendar() -> Self { - Self::new(DatabaseLayoutPB::Calendar).await - } - - pub async fn new(layout: DatabaseLayoutPB) -> Self { - let sdk = FlowySDKTest::default(); - let _ = sdk.init_user().await; - let test = match layout { - DatabaseLayoutPB::Grid => { - let build_context = make_test_grid(); - let view_data: Bytes = build_context.into(); - ViewTest::new_grid_view(&sdk, view_data.to_vec()).await - }, - DatabaseLayoutPB::Board => { - let build_context = make_test_board(); - let view_data: Bytes = build_context.into(); - ViewTest::new_board_view(&sdk, view_data.to_vec()).await - }, - DatabaseLayoutPB::Calendar => { - let build_context = make_test_calendar(); - let view_data: Bytes = build_context.into(); - ViewTest::new_calendar_view(&sdk, view_data.to_vec()).await - }, - }; - - let editor = sdk - .database_manager - .open_database_view(&test.child_view.id) - .await - .unwrap(); - let field_revs = editor.get_field_revs(None).await.unwrap(); - let block_meta_revs = editor.get_block_meta_revs().await.unwrap(); - let row_pbs = editor.get_all_row_revs(&test.child_view.id).await.unwrap(); - assert_eq!(block_meta_revs.len(), 1); - - let view_id = test.child_view.id; - let app_id = test.parent_view.id; - Self { - sdk, - app_id, - view_id, - editor, - field_revs, - block_meta_revs, - row_revs: row_pbs, - field_count: FieldType::COUNT, - row_by_row_id: HashMap::default(), - } - } - - pub async fn get_row_revs(&self) -> Vec> { - self.editor.get_all_row_revs(&self.view_id).await.unwrap() - } - - pub async fn database_filters(&self) -> Vec { - self.editor.get_all_filters(&self.view_id).await.unwrap() - } - - pub fn get_field_rev(&self, field_id: &str, field_type: FieldType) -> &Arc { - self - .field_revs - .iter() - .filter(|field_rev| { - let t_field_type: FieldType = field_rev.ty.into(); - field_rev.id == field_id && t_field_type == field_type - }) - .collect::>() - .pop() - .unwrap() - } - - /// returns the first `FieldRevision` in the build-in test grid. - /// Not support duplicate `FieldType` in test grid yet. - pub fn get_first_field_rev(&self, field_type: FieldType) -> &Arc { - self - .field_revs - .iter() - .filter(|field_rev| { - let t_field_type: FieldType = field_rev.ty.into(); - t_field_type == field_type - }) - .collect::>() - .pop() - .unwrap() - } - - pub fn get_multi_select_type_option(&self, field_id: &str) -> Vec { - let field_type = FieldType::MultiSelect; - let field_rev = self.get_field_rev(field_id, field_type.clone()); - let type_option = field_rev - .get_type_option::(field_type.into()) - .unwrap(); - type_option.options - } - - pub fn get_single_select_type_option(&self, field_id: &str) -> SingleSelectTypeOptionPB { - let field_type = FieldType::SingleSelect; - let field_rev = self.get_field_rev(field_id, field_type.clone()); - field_rev - .get_type_option::(field_type.into()) - .unwrap() - } - - #[allow(dead_code)] - pub fn get_checklist_type_option(&self, field_id: &str) -> ChecklistTypeOptionPB { - let field_type = FieldType::Checklist; - let field_rev = self.get_field_rev(field_id, field_type.clone()); - field_rev - .get_type_option::(field_type.into()) - .unwrap() - } - - #[allow(dead_code)] - pub fn get_checkbox_type_option(&self, field_id: &str) -> CheckboxTypeOptionPB { - let field_type = FieldType::Checkbox; - let field_rev = self.get_field_rev(field_id, field_type.clone()); - field_rev - .get_type_option::(field_type.into()) - .unwrap() - } - - pub fn block_id(&self) -> &str { - &self.block_meta_revs.last().unwrap().block_id - } - - pub async fn update_cell( - &mut self, - field_id: &str, - row_id: String, - cell_changeset: T, - ) { - let field_rev = self - .field_revs - .iter() - .find(|field_rev| field_rev.id == field_id) - .unwrap(); - - self - .editor - .update_cell_with_changeset(&row_id, &field_rev.id, cell_changeset) - .await - .unwrap(); - } - - pub(crate) async fn update_text_cell(&mut self, row_id: String, content: &str) { - let field_rev = self - .field_revs - .iter() - .find(|field_rev| { - let field_type: FieldType = field_rev.ty.into(); - field_type == FieldType::RichText - }) - .unwrap() - .clone(); - - self - .update_cell(&field_rev.id, row_id, content.to_string()) - .await; - } - - pub(crate) async fn update_single_select_cell(&mut self, row_id: String, option_id: &str) { - let field_rev = self - .field_revs - .iter() - .find(|field_rev| { - let field_type: FieldType = field_rev.ty.into(); - field_type == FieldType::SingleSelect - }) - .unwrap() - .clone(); - - let cell_changeset = SelectOptionCellChangeset::from_insert_option_id(option_id); - self - .update_cell(&field_rev.id, row_id, cell_changeset) - .await; - } -} diff --git a/frontend/rust-lib/flowy-database/tests/database/database_ref_test/mod.rs b/frontend/rust-lib/flowy-database/tests/database/database_ref_test/mod.rs deleted file mode 100644 index 63d424afaf..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/database_ref_test/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod script; -mod test; diff --git a/frontend/rust-lib/flowy-database/tests/database/database_ref_test/script.rs b/frontend/rust-lib/flowy-database/tests/database/database_ref_test/script.rs deleted file mode 100644 index 9e01ced934..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/database_ref_test/script.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::database::block_test::util::DatabaseRowTestBuilder; -use crate::database::database_editor::DatabaseEditorTest; -use database_model::RowRevision; -use flowy_database::services::database::DatabaseEditor; -use flowy_database::services::persistence::database_ref::{DatabaseInfo, DatabaseViewRef}; -use std::collections::HashMap; -use std::sync::Arc; - -pub enum LinkDatabaseTestScript { - CreateGridViewAndLinkToDatabase { - database_id: String, - }, - #[allow(dead_code)] - LinkBoardToDatabase { - database_id: String, - }, - CreateNewGrid, - CreateRow { - view_id: String, - row_rev: RowRevision, - }, - AssertNumberOfRows { - view_id: String, - expected: usize, - }, - AssertNumberOfDatabase { - expected: usize, - }, -} - -pub struct LinkDatabaseTest { - inner: DatabaseEditorTest, -} - -impl LinkDatabaseTest { - pub async fn new() -> Self { - let inner = DatabaseEditorTest::new_grid().await; - Self { inner } - } - - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - pub async fn block_id(&self, view_id: &str) -> String { - let editor = self.get_database_editor(view_id).await; - let mut block_meta_revs = editor.get_block_meta_revs().await.unwrap(); - let block = block_meta_revs.pop().unwrap(); - block.block_id.clone() - } - - pub async fn all_databases(&self) -> Vec { - self - .inner - .sdk - .database_manager - .get_databases() - .await - .unwrap() - } - - pub async fn all_database_ref_views(&self, database_id: &str) -> Vec { - self - .inner - .sdk - .database_manager - .get_database_ref_views(database_id) - .await - .unwrap() - } - - async fn get_database_editor(&self, view_id: &str) -> Arc { - self - .inner - .sdk - .database_manager - .open_database_view(&view_id) - .await - .unwrap() - } - - pub async fn row_builder(&self, view_id: &str) -> DatabaseRowTestBuilder { - let editor = self.get_database_editor(view_id).await; - let field_revs = editor.get_field_revs(None).await.unwrap(); - DatabaseRowTestBuilder::new(self.block_id(view_id).await, field_revs) - } - - pub async fn run_script(&mut self, script: LinkDatabaseTestScript) { - match script { - LinkDatabaseTestScript::CreateGridViewAndLinkToDatabase { database_id } => { - let mut ext = HashMap::new(); - ext.insert("database_id".to_owned(), database_id); - self - .inner - .sdk - .folder_manager - .create_test_grid_view(&self.inner.app_id, "test link grid", ext) - .await; - }, - LinkDatabaseTestScript::LinkBoardToDatabase { database_id } => { - let mut ext = HashMap::new(); - ext.insert("database_id".to_owned(), database_id); - self - .inner - .sdk - .folder_manager - .create_test_board_view(&self.inner.app_id, "test link board", ext) - .await; - }, - LinkDatabaseTestScript::CreateNewGrid => { - self - .inner - .sdk - .folder_manager - .create_test_grid_view(&self.inner.app_id, "Create test grid", HashMap::new()) - .await; - }, - LinkDatabaseTestScript::AssertNumberOfDatabase { expected } => { - let databases = self.all_databases().await; - assert_eq!(databases.len(), expected); - }, - LinkDatabaseTestScript::CreateRow { view_id, row_rev } => { - let editor = self.get_database_editor(&view_id).await; - let _ = editor.insert_rows(vec![row_rev]).await.unwrap(); - }, - LinkDatabaseTestScript::AssertNumberOfRows { view_id, expected } => { - let editor = self.get_database_editor(&view_id).await; - let rows = editor.get_all_row_revs(&view_id).await.unwrap(); - assert_eq!(rows.len(), expected); - }, - } - } -} diff --git a/frontend/rust-lib/flowy-database/tests/database/database_ref_test/test.rs b/frontend/rust-lib/flowy-database/tests/database/database_ref_test/test.rs deleted file mode 100644 index 3c07017cb3..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/database_ref_test/test.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::database::database_ref_test::script::LinkDatabaseTest; -use crate::database::database_ref_test::script::LinkDatabaseTestScript::*; - -#[tokio::test] -async fn number_of_database_test() { - let mut test = LinkDatabaseTest::new().await; - test - .run_scripts(vec![ - // After the LinkDatabaseTest initialize, it will create a grid. - AssertNumberOfDatabase { expected: 1 }, - CreateNewGrid, - AssertNumberOfDatabase { expected: 2 }, - ]) - .await; -} - -#[tokio::test] -async fn database_view_link_with_existing_database_test() { - let mut test = LinkDatabaseTest::new().await; - let database = test.all_databases().await.pop().unwrap(); - test - .run_scripts(vec![ - CreateGridViewAndLinkToDatabase { - database_id: database.database_id, - }, - AssertNumberOfDatabase { expected: 1 }, - ]) - .await; -} - -#[tokio::test] -async fn check_number_of_rows_in_linked_database_view() { - let mut test = LinkDatabaseTest::new().await; - let database = test.all_databases().await.pop().unwrap(); - let view = test - .all_database_ref_views(&database.database_id) - .await - .remove(0); - - test - .run_scripts(vec![ - CreateGridViewAndLinkToDatabase { - database_id: database.database_id, - }, - // The initial number of rows is 6 - AssertNumberOfRows { - view_id: view.view_id.clone(), - expected: 6, - }, - ]) - .await; -} - -#[tokio::test] -async fn multiple_views_share_database_rows() { - let mut test = LinkDatabaseTest::new().await; - - // After the LinkDatabaseTest initialize, it will create a default database - // with Grid layout. - let database = test.all_databases().await.pop().unwrap(); - let mut database_views = test.all_database_ref_views(&database.database_id).await; - assert_eq!(database_views.len(), 1); - let view = database_views.remove(0); - - test - .run_scripts(vec![ - AssertNumberOfRows { - view_id: view.view_id.clone(), - expected: 6, - }, - CreateGridViewAndLinkToDatabase { - database_id: database.database_id.clone(), - }, - ]) - .await; - - let database_views = test.all_database_ref_views(&database.database_id).await; - assert_eq!(database_views.len(), 2); - let view_id_1 = database_views.get(0).unwrap().view_id.clone(); - let view_id_2 = database_views.get(1).unwrap().view_id.clone(); - - // Create a new row - let mut builder = test.row_builder(&view_id_1).await; - builder.insert_text_cell("hello world"); - - test - .run_scripts(vec![ - CreateRow { - view_id: view_id_1.clone(), - row_rev: builder.build(), - }, - AssertNumberOfRows { - view_id: view_id_1, - expected: 7, - }, - AssertNumberOfRows { - view_id: view_id_2, - expected: 7, - }, - AssertNumberOfDatabase { expected: 1 }, - ]) - .await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/field_test/mod.rs b/frontend/rust-lib/flowy-database/tests/database/field_test/mod.rs deleted file mode 100644 index 5ac4da9f24..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/field_test/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod script; -mod test; -pub mod util; diff --git a/frontend/rust-lib/flowy-database/tests/database/field_test/script.rs b/frontend/rust-lib/flowy-database/tests/database/field_test/script.rs deleted file mode 100644 index 0dfcb5df6c..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/field_test/script.rs +++ /dev/null @@ -1,178 +0,0 @@ -use crate::database::database_editor::DatabaseEditorTest; -use database_model::FieldRevision; -use flowy_database::entities::{CreateFieldParams, FieldChangesetParams, FieldType}; -use flowy_database::services::cell::{stringify_cell_data, TypeCellData}; - -pub enum FieldScript { - CreateField { - params: CreateFieldParams, - }, - UpdateField { - changeset: FieldChangesetParams, - }, - DeleteField { - field_rev: FieldRevision, - }, - SwitchToField { - field_id: String, - new_field_type: FieldType, - }, - UpdateTypeOption { - field_id: String, - type_option: Vec, - }, - AssertFieldCount(usize), - AssertFieldFrozen { - field_index: usize, - frozen: bool, - }, - AssertFieldTypeOptionEqual { - field_index: usize, - expected_type_option_data: String, - }, - AssertCellContent { - field_id: String, - row_index: usize, - from_field_type: FieldType, - expected_content: String, - }, -} - -pub struct DatabaseFieldTest { - inner: DatabaseEditorTest, -} - -impl DatabaseFieldTest { - pub async fn new() -> Self { - let editor_test = DatabaseEditorTest::new_grid().await; - Self { inner: editor_test } - } - - pub fn view_id(&self) -> String { - self.view_id.clone() - } - - pub fn field_count(&self) -> usize { - self.field_count - } - - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - pub async fn run_script(&mut self, script: FieldScript) { - match script { - FieldScript::CreateField { params } => { - self.field_count += 1; - self - .editor - .create_new_field_rev_with_type_option(¶ms.field_type, params.type_option_data) - .await - .unwrap(); - self.field_revs = self.editor.get_field_revs(None).await.unwrap(); - assert_eq!(self.field_count, self.field_revs.len()); - }, - FieldScript::UpdateField { changeset: change } => { - self.editor.update_field(change).await.unwrap(); - self.field_revs = self.editor.get_field_revs(None).await.unwrap(); - }, - FieldScript::DeleteField { field_rev } => { - if self.editor.contain_field(&field_rev.id).await { - self.field_count -= 1; - } - - self.editor.delete_field(&field_rev.id).await.unwrap(); - self.field_revs = self.editor.get_field_revs(None).await.unwrap(); - assert_eq!(self.field_count, self.field_revs.len()); - }, - FieldScript::SwitchToField { - field_id, - new_field_type, - } => { - // - self - .editor - .switch_to_field_type(&field_id, &new_field_type) - .await - .unwrap(); - self.field_revs = self.editor.get_field_revs(None).await.unwrap(); - }, - FieldScript::UpdateTypeOption { - field_id, - type_option, - } => { - // - self - .editor - .update_field_type_option(&self.view_id, &field_id, type_option, None) - .await - .unwrap(); - self.field_revs = self.editor.get_field_revs(None).await.unwrap(); - }, - FieldScript::AssertFieldCount(count) => { - assert_eq!(self.editor.get_field_revs(None).await.unwrap().len(), count); - }, - FieldScript::AssertFieldFrozen { - field_index, - frozen, - } => { - let field_revs = self.editor.get_field_revs(None).await.unwrap(); - let field_rev = field_revs[field_index].as_ref(); - assert_eq!(field_rev.frozen, frozen); - }, - FieldScript::AssertFieldTypeOptionEqual { - field_index, - expected_type_option_data, - } => { - let field_revs = self.editor.get_field_revs(None).await.unwrap(); - let field_rev = field_revs[field_index].as_ref(); - let type_option_data = field_rev.get_type_option_str(field_rev.ty).unwrap(); - assert_eq!(type_option_data, expected_type_option_data); - }, - FieldScript::AssertCellContent { - field_id, - row_index, - from_field_type, - expected_content, - } => { - let field_rev = self.editor.get_field_rev(&field_id).await.unwrap(); - let field_type: FieldType = field_rev.ty.into(); - - let rows = self - .editor - .get_database(&self.view_id()) - .await - .unwrap() - .rows; - let row = rows.get(row_index).unwrap(); - let row_rev = self.editor.get_row_rev(&row.id).await.unwrap().unwrap(); - - let cell_rev = row_rev.cells.get(&field_id).unwrap().clone(); - let type_cell_data: TypeCellData = cell_rev.try_into().unwrap(); - let content = stringify_cell_data( - type_cell_data.cell_str, - &from_field_type, - &field_type, - &field_rev, - ); - assert_eq!(content, expected_content); - }, - } - } -} - -impl std::ops::Deref for DatabaseFieldTest { - type Target = DatabaseEditorTest; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl std::ops::DerefMut for DatabaseFieldTest { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} diff --git a/frontend/rust-lib/flowy-database/tests/database/field_test/test.rs b/frontend/rust-lib/flowy-database/tests/database/field_test/test.rs deleted file mode 100644 index a33a04b8e0..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/field_test/test.rs +++ /dev/null @@ -1,355 +0,0 @@ -use crate::database::field_test::script::DatabaseFieldTest; -use crate::database::field_test::script::FieldScript::*; -use crate::database::field_test::util::*; -use bytes::Bytes; -use flowy_database::entities::{FieldChangesetParams, FieldType}; -use flowy_database::services::field::selection_type_option::SelectOptionPB; -use flowy_database::services::field::{gen_option_id, SingleSelectTypeOptionPB, CHECK, UNCHECK}; - -#[tokio::test] -async fn grid_create_field() { - let mut test = DatabaseFieldTest::new().await; - let (params, field_rev) = create_text_field(&test.view_id()); - - let scripts = vec![ - CreateField { params }, - AssertFieldTypeOptionEqual { - field_index: test.field_count(), - expected_type_option_data: field_rev - .get_type_option_str(field_rev.ty) - .unwrap() - .to_owned(), - }, - ]; - test.run_scripts(scripts).await; - - let (params, field_rev) = create_single_select_field(&test.view_id()); - let scripts = vec![ - CreateField { params }, - AssertFieldTypeOptionEqual { - field_index: test.field_count(), - expected_type_option_data: field_rev - .get_type_option_str(field_rev.ty) - .unwrap() - .to_owned(), - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_create_duplicate_field() { - let mut test = DatabaseFieldTest::new().await; - let (params, _) = create_text_field(&test.view_id()); - let field_count = test.field_count(); - let expected_field_count = field_count + 1; - let scripts = vec![ - CreateField { - params: params.clone(), - }, - AssertFieldCount(expected_field_count), - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_update_field_with_empty_change() { - let mut test = DatabaseFieldTest::new().await; - let (params, _) = create_single_select_field(&test.view_id()); - let create_field_index = test.field_count(); - let scripts = vec![CreateField { params }]; - test.run_scripts(scripts).await; - - let field_rev = (*test.field_revs.clone().pop().unwrap()).clone(); - let changeset = FieldChangesetParams { - field_id: field_rev.id.clone(), - view_id: test.view_id(), - ..Default::default() - }; - - let scripts = vec![ - UpdateField { changeset }, - AssertFieldTypeOptionEqual { - field_index: create_field_index, - expected_type_option_data: field_rev - .get_type_option_str(field_rev.ty) - .unwrap() - .to_owned(), - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_update_field() { - let mut test = DatabaseFieldTest::new().await; - let (params, _) = create_single_select_field(&test.view_id()); - let scripts = vec![CreateField { params }]; - let create_field_index = test.field_count(); - test.run_scripts(scripts).await; - // - let single_select_field = (*test.field_revs.clone().pop().unwrap()).clone(); - let mut single_select_type_option = SingleSelectTypeOptionPB::from(&single_select_field); - single_select_type_option - .options - .push(SelectOptionPB::new("Unknown")); - - let changeset = FieldChangesetParams { - field_id: single_select_field.id.clone(), - view_id: test.view_id(), - frozen: Some(true), - width: Some(1000), - ..Default::default() - }; - - // The expected_field must be equal to the field that applied the changeset - let mut expected_field_rev = single_select_field.clone(); - expected_field_rev.frozen = true; - expected_field_rev.width = 1000; - expected_field_rev.insert_type_option(&single_select_type_option); - - let scripts = vec![ - UpdateField { changeset }, - AssertFieldFrozen { - field_index: create_field_index, - frozen: true, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_delete_field() { - let mut test = DatabaseFieldTest::new().await; - let original_field_count = test.field_count(); - let (params, _) = create_text_field(&test.view_id()); - let scripts = vec![CreateField { params }]; - test.run_scripts(scripts).await; - - let text_field_rev = (*test.field_revs.clone().pop().unwrap()).clone(); - let scripts = vec![ - DeleteField { - field_rev: text_field_rev, - }, - AssertFieldCount(original_field_count), - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_switch_from_select_option_to_checkbox_test() { - let mut test = DatabaseFieldTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::SingleSelect); - - // Update the type option data of single select option - let mut single_select_type_option = test.get_single_select_type_option(&field_rev.id); - single_select_type_option.options.clear(); - // Add a new option with name CHECK - single_select_type_option.options.push(SelectOptionPB { - id: gen_option_id(), - name: CHECK.to_string(), - color: Default::default(), - }); - // Add a new option with name UNCHECK - single_select_type_option.options.push(SelectOptionPB { - id: gen_option_id(), - name: UNCHECK.to_string(), - color: Default::default(), - }); - - let bytes: Bytes = single_select_type_option.try_into().unwrap(); - let scripts = vec![ - UpdateTypeOption { - field_id: field_rev.id.clone(), - type_option: bytes.to_vec(), - }, - SwitchToField { - field_id: field_rev.id.clone(), - new_field_type: FieldType::Checkbox, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_switch_from_checkbox_to_select_option_test() { - let mut test = DatabaseFieldTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::Checkbox).clone(); - let scripts = vec![ - // switch to single-select field type - SwitchToField { - field_id: field_rev.id.clone(), - new_field_type: FieldType::SingleSelect, - }, - // Assert the cell content after switch the field type. The cell content will be changed if - // the FieldType::SingleSelect implement the cell data TypeOptionTransform. Check out the - // TypeOptionTransform trait for more information. - // - // Make sure which cell of the row you want to check. - AssertCellContent { - field_id: field_rev.id.clone(), - // the mock data of the checkbox with row_index one is "true" - row_index: 1, - // the from_field_type represents as the current field type - from_field_type: FieldType::Checkbox, - // The content of the checkbox should transform to the corresponding option name. - expected_content: CHECK.to_string(), - }, - ]; - test.run_scripts(scripts).await; - - let single_select_type_option = test.get_single_select_type_option(&field_rev.id); - assert_eq!(single_select_type_option.options.len(), 2); - assert!(single_select_type_option - .options - .iter() - .any(|option| option.name == UNCHECK)); - assert!(single_select_type_option - .options - .iter() - .any(|option| option.name == CHECK)); -} - -// Test when switching the current field from Multi-select to Text test -// The build-in test data is located in `make_test_grid` method(flowy-database/tests/grid_editor.rs). -// input: -// option1, option2 -> "option1.name, option2.name" -#[tokio::test] -async fn grid_switch_from_multi_select_to_text_test() { - let mut test = DatabaseFieldTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::MultiSelect).clone(); - - let multi_select_type_option = test.get_multi_select_type_option(&field_rev.id); - - let script_switch_field = vec![SwitchToField { - field_id: field_rev.id.clone(), - new_field_type: FieldType::RichText, - }]; - - test.run_scripts(script_switch_field).await; - - let script_assert_field = vec![AssertCellContent { - field_id: field_rev.id.clone(), - row_index: 0, - from_field_type: FieldType::MultiSelect, - expected_content: format!( - "{},{}", - multi_select_type_option.get(0).unwrap().name, - multi_select_type_option.get(1).unwrap().name - ), - }]; - - test.run_scripts(script_assert_field).await; -} - -// Test when switching the current field from Checkbox to Text test -// input: -// check -> "Yes" -// unchecked -> "" -#[tokio::test] -async fn grid_switch_from_checkbox_to_text_test() { - let mut test = DatabaseFieldTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::Checkbox); - - let scripts = vec![ - SwitchToField { - field_id: field_rev.id.clone(), - new_field_type: FieldType::RichText, - }, - AssertCellContent { - field_id: field_rev.id.clone(), - row_index: 1, - from_field_type: FieldType::Checkbox, - expected_content: "Yes".to_string(), - }, - AssertCellContent { - field_id: field_rev.id.clone(), - row_index: 2, - from_field_type: FieldType::Checkbox, - expected_content: "No".to_string(), - }, - ]; - test.run_scripts(scripts).await; -} - -// Test when switching the current field from Checkbox to Text test -// input: -// "Yes" -> check -// "" -> unchecked -#[tokio::test] -async fn grid_switch_from_text_to_checkbox_test() { - let mut test = DatabaseFieldTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::RichText).clone(); - - let scripts = vec![ - SwitchToField { - field_id: field_rev.id.clone(), - new_field_type: FieldType::Checkbox, - }, - AssertCellContent { - field_id: field_rev.id.clone(), - row_index: 0, - from_field_type: FieldType::RichText, - expected_content: "".to_string(), - }, - ]; - test.run_scripts(scripts).await; -} - -// Test when switching the current field from Date to Text test -// input: -// 1647251762 -> Mar 14,2022 (This string will be different base on current data setting) -// #[tokio::test] -// async fn grid_switch_from_date_to_text_test() { -// let mut test = DatabaseFieldTest::new().await; -// let field_rev = test.get_first_field_rev(FieldType::DateTime).clone(); -// let scripts = vec![ -// SwitchToField { -// field_id: field_rev.id.clone(), -// new_field_type: FieldType::RichText, -// }, -// AssertCellContent { -// field_id: field_rev.id.clone(), -// row_index: 2, -// from_field_type: FieldType::DateTime, -// expected_content: "2022/03/14".to_string(), -// }, -// AssertCellContent { -// field_id: field_rev.id.clone(), -// row_index: 3, -// from_field_type: FieldType::DateTime, -// expected_content: "2022/11/17".to_string(), -// }, -// ]; -// test.run_scripts(scripts).await; -// } - -// Test when switching the current field from Number to Text test -// input: -// $1 -> "$1"(This string will be different base on current data setting) -#[tokio::test] -async fn grid_switch_from_number_to_text_test() { - let mut test = DatabaseFieldTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::Number).clone(); - - let scripts = vec![ - SwitchToField { - field_id: field_rev.id.clone(), - new_field_type: FieldType::RichText, - }, - AssertCellContent { - field_id: field_rev.id.clone(), - row_index: 0, - from_field_type: FieldType::Number, - expected_content: "$1".to_string(), - }, - AssertCellContent { - field_id: field_rev.id.clone(), - row_index: 4, - from_field_type: FieldType::Number, - expected_content: "".to_string(), - }, - ]; - - test.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/field_test/util.rs b/frontend/rust-lib/flowy-database/tests/database/field_test/util.rs deleted file mode 100644 index d13145a14e..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/field_test/util.rs +++ /dev/null @@ -1,70 +0,0 @@ -use database_model::*; -use flowy_database::entities::*; -use flowy_database::services::field::selection_type_option::SelectOptionPB; -use flowy_database::services::field::*; - -pub fn create_text_field(grid_id: &str) -> (CreateFieldParams, FieldRevision) { - let mut field_rev = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Name") - .visibility(true) - .build(); - - let cloned_field_rev = field_rev.clone(); - - let type_option_data = field_rev - .get_type_option::(field_rev.ty) - .unwrap() - .protobuf_bytes() - .to_vec(); - - let type_option_builder = - type_option_builder_from_bytes(type_option_data.clone(), &field_rev.ty.into()); - field_rev.insert_type_option(type_option_builder.serializer()); - - let params = CreateFieldParams { - view_id: grid_id.to_owned(), - field_type: field_rev.ty.into(), - type_option_data: Some(type_option_data), - }; - (params, cloned_field_rev) -} - -pub fn create_single_select_field(grid_id: &str) -> (CreateFieldParams, FieldRevision) { - let single_select = SingleSelectTypeOptionBuilder::default() - .add_option(SelectOptionPB::new("Done")) - .add_option(SelectOptionPB::new("Progress")); - - let mut field_rev = FieldBuilder::new(single_select) - .name("Name") - .visibility(true) - .build(); - let cloned_field_rev = field_rev.clone(); - let type_option_data = field_rev - .get_type_option::(field_rev.ty) - .unwrap() - .protobuf_bytes() - .to_vec(); - - let type_option_builder = - type_option_builder_from_bytes(type_option_data.clone(), &field_rev.ty.into()); - field_rev.insert_type_option(type_option_builder.serializer()); - - let params = CreateFieldParams { - view_id: grid_id.to_owned(), - field_type: field_rev.ty.into(), - type_option_data: Some(type_option_data), - }; - (params, cloned_field_rev) -} - -// The grid will contains all existing field types and there are three empty rows in this grid. - -pub fn make_date_cell_string(s: &str) -> String { - serde_json::to_string(&DateCellChangeset { - date: Some(s.to_string()), - time: None, - is_utc: true, - include_time: Some(false), - }) - .unwrap() -} diff --git a/frontend/rust-lib/flowy-database/tests/database/filter_test/checkbox_filter_test.rs b/frontend/rust-lib/flowy-database/tests/database/filter_test/checkbox_filter_test.rs deleted file mode 100644 index 2971b864a4..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/filter_test/checkbox_filter_test.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::database::filter_test::script::FilterScript::*; -use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; -use flowy_database::entities::CheckboxFilterConditionPB; - -#[tokio::test] -async fn grid_filter_checkbox_is_check_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - // The initial number of unchecked is 3 - // The initial number of checked is 2 - let scripts = vec![CreateCheckboxFilter { - condition: CheckboxFilterConditionPB::IsChecked, - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - 3, - }), - }]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_checkbox_is_uncheck_test() { - let mut test = DatabaseFilterTest::new().await; - let expected = 3; - let row_count = test.row_revs.len(); - let scripts = vec![ - CreateCheckboxFilter { - condition: CheckboxFilterConditionPB::IsUnChecked, - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/filter_test/checklist_filter_test.rs b/frontend/rust-lib/flowy-database/tests/database/filter_test/checklist_filter_test.rs deleted file mode 100644 index 6c93afd59e..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/filter_test/checklist_filter_test.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::database::filter_test::script::FilterScript::*; -use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; -use flowy_database::entities::ChecklistFilterConditionPB; - -#[tokio::test] -async fn grid_filter_checklist_is_incomplete_test() { - let mut test = DatabaseFilterTest::new().await; - let expected = 5; - let row_count = test.row_revs.len(); - let scripts = vec![ - CreateChecklistFilter { - condition: ChecklistFilterConditionPB::IsIncomplete, - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_checklist_is_complete_test() { - let mut test = DatabaseFilterTest::new().await; - let expected = 1; - let row_count = test.row_revs.len(); - let scripts = vec![ - CreateChecklistFilter { - condition: ChecklistFilterConditionPB::IsComplete, - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/filter_test/date_filter_test.rs b/frontend/rust-lib/flowy-database/tests/database/filter_test/date_filter_test.rs deleted file mode 100644 index aa2d396c09..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/filter_test/date_filter_test.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::database::filter_test::script::FilterScript::*; -use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; -use flowy_database::entities::DateFilterConditionPB; - -#[tokio::test] -async fn grid_filter_date_is_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 3; - let scripts = vec![ - CreateDateFilter { - condition: DateFilterConditionPB::DateIs, - start: None, - end: None, - timestamp: Some(1647251762), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_date_after_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 3; - let scripts = vec![ - CreateDateFilter { - condition: DateFilterConditionPB::DateAfter, - start: None, - end: None, - timestamp: Some(1647251762), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_date_on_or_after_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 3; - let scripts = vec![ - CreateDateFilter { - condition: DateFilterConditionPB::DateOnOrAfter, - start: None, - end: None, - timestamp: Some(1668359085), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_date_on_or_before_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 4; - let scripts = vec![ - CreateDateFilter { - condition: DateFilterConditionPB::DateOnOrBefore, - start: None, - end: None, - timestamp: Some(1668359085), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_date_within_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 5; - let scripts = vec![ - CreateDateFilter { - condition: DateFilterConditionPB::DateWithIn, - start: Some(1647251762), - end: Some(1668704685), - timestamp: None, - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/filter_test/mod.rs b/frontend/rust-lib/flowy-database/tests/database/filter_test/mod.rs deleted file mode 100644 index 160bf3427f..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/filter_test/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod checkbox_filter_test; -mod checklist_filter_test; -mod date_filter_test; -mod number_filter_test; -mod script; -mod select_option_filter_test; -mod text_filter_test; diff --git a/frontend/rust-lib/flowy-database/tests/database/filter_test/number_filter_test.rs b/frontend/rust-lib/flowy-database/tests/database/filter_test/number_filter_test.rs deleted file mode 100644 index 142335bd1b..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/filter_test/number_filter_test.rs +++ /dev/null @@ -1,118 +0,0 @@ -use crate::database::filter_test::script::FilterScript::*; -use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; -use flowy_database::entities::NumberFilterConditionPB; - -#[tokio::test] -async fn grid_filter_number_is_equal_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 1; - let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::Equal, - content: "1".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_number_is_less_than_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 2; - let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::LessThan, - content: "3".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -#[should_panic] -async fn grid_filter_number_is_less_than_test2() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 2; - let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::LessThan, - content: "$3".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_number_is_less_than_or_equal_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 3; - let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::LessThanOrEqualTo, - content: "3".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_number_is_empty_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 1; - let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::NumberIsEmpty, - content: "".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_number_is_not_empty_test() { - let mut test = DatabaseFilterTest::new().await; - let row_count = test.row_revs.len(); - let expected = 5; - let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::NumberIsNotEmpty, - content: "".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/filter_test/script.rs b/frontend/rust-lib/flowy-database/tests/database/filter_test/script.rs deleted file mode 100644 index a66751c29f..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/filter_test/script.rs +++ /dev/null @@ -1,313 +0,0 @@ -#![cfg_attr(rustfmt, rustfmt::skip)] -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_imports)] - -use std::time::Duration; -use bytes::Bytes; -use futures::TryFutureExt; -use tokio::sync::broadcast::Receiver; -use flowy_database::entities::{AlterFilterParams, AlterFilterPayloadPB, DeleteFilterParams, DatabaseLayoutPB, DatabaseSettingChangesetParams, DatabaseViewSettingPB, RowPB, TextFilterConditionPB, FieldType, NumberFilterConditionPB, CheckboxFilterConditionPB, DateFilterConditionPB, DateFilterContentPB, SelectOptionConditionPB, TextFilterPB, NumberFilterPB, CheckboxFilterPB, DateFilterPB, SelectOptionFilterPB, CellChangesetPB, FilterPB, ChecklistFilterConditionPB, ChecklistFilterPB}; -use flowy_database::services::field::{SelectOptionCellChangeset, SelectOptionIds}; -use flowy_database::services::setting::GridSettingChangesetBuilder; -use database_model::{FieldRevision, FieldTypeRevision}; -use flowy_sqlite::schema::view_table::dsl::view_table; -use flowy_database::services::cell::insert_select_option_cell; -use flowy_database::services::filter::FilterType; -use flowy_database::services::database_view::DatabaseViewChanged; -use crate::database::database_editor::DatabaseEditorTest; - -pub struct FilterRowChanged { - pub(crate) showing_num_of_rows: usize, - pub(crate) hiding_num_of_rows: usize, -} - -pub enum FilterScript { - UpdateTextCell { - row_id: String, - text: String, - changed: Option, - }, - UpdateSingleSelectCell { - row_id: String, - option_id: String, - changed: Option, - }, - InsertFilter { - payload: AlterFilterPayloadPB, - }, - CreateTextFilter { - condition: TextFilterConditionPB, - content: String, - changed: Option, - }, - UpdateTextFilter { - filter: FilterPB, - condition: TextFilterConditionPB, - content: String, - changed: Option, - }, - CreateNumberFilter { - condition: NumberFilterConditionPB, - content: String, - changed: Option, - }, - CreateCheckboxFilter { - condition: CheckboxFilterConditionPB, - changed: Option, - }, - CreateDateFilter{ - condition: DateFilterConditionPB, - start: Option, - end: Option, - timestamp: Option, - changed: Option, - }, - CreateMultiSelectFilter { - condition: SelectOptionConditionPB, - option_ids: Vec, - }, - CreateSingleSelectFilter { - condition: SelectOptionConditionPB, - option_ids: Vec, - changed: Option, - }, - CreateChecklistFilter { - condition: ChecklistFilterConditionPB, - changed: Option, - }, - AssertFilterCount { - count: i32, - }, - DeleteFilter { - filter_id: String, - filter_type: FilterType, - changed: Option, - }, - AssertFilterContent { - filter_type: FilterType, - condition: u32, - content: String - }, - AssertNumberOfVisibleRows { - expected: usize, - }, - #[allow(dead_code)] - AssertGridSetting { - expected_setting: DatabaseViewSettingPB, - }, - Wait { millisecond: u64 } -} - -pub struct DatabaseFilterTest { - inner: DatabaseEditorTest, - recv: Option>, -} - -impl DatabaseFilterTest { - pub async fn new() -> Self { - let editor_test = DatabaseEditorTest::new_grid().await; - Self { - inner: editor_test, - recv: None, - } - } - - pub fn view_id(&self) -> String { - self.view_id.clone() - } - - pub async fn get_all_filters(&self) -> Vec { - self.editor.get_all_filters(&self.view_id).await.unwrap() - } - - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - pub async fn run_script(&mut self, script: FilterScript) { - match script { - FilterScript::UpdateTextCell { row_id, text, changed} => { - self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap()); - self.assert_future_changed(changed).await; - self.update_text_cell(row_id, &text).await; - } - FilterScript::UpdateSingleSelectCell { row_id, option_id, changed} => { - self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap()); - self.assert_future_changed(changed).await; - self.update_single_select_cell(row_id, &option_id).await; - } - FilterScript::InsertFilter { payload } => { - self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap()); - self.insert_filter(payload).await; - } - FilterScript::CreateTextFilter { condition, content, changed} => { - self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap()); - self.assert_future_changed(changed).await; - let field_rev = self.get_first_field_rev(FieldType::RichText); - let text_filter= TextFilterPB { - condition, - content - }; - let payload = - AlterFilterPayloadPB::new( - & self.view_id(), - field_rev, text_filter); - self.insert_filter(payload).await; - } - FilterScript::UpdateTextFilter { filter, condition, content, changed} => { - self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap()); - self.assert_future_changed(changed).await; - let params = AlterFilterParams { - view_id: self.view_id(), - field_id: filter.field_id, - filter_id: Some(filter.id), - field_type: filter.field_type.into(), - condition: condition as u8, - content - }; - self.editor.create_or_update_filter(params).await.unwrap(); - } - FilterScript::CreateNumberFilter {condition, content, changed} => { - self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap()); - self.assert_future_changed(changed).await; - let field_rev = self.get_first_field_rev(FieldType::Number); - let number_filter = NumberFilterPB { - condition, - content - }; - let payload = - AlterFilterPayloadPB::new( - &self.view_id(), - field_rev, number_filter); - self.insert_filter(payload).await; - } - FilterScript::CreateCheckboxFilter {condition, changed} => { - self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap()); - self.assert_future_changed(changed).await; - let field_rev = self.get_first_field_rev(FieldType::Checkbox); - let checkbox_filter = CheckboxFilterPB { - condition - }; - let payload = - AlterFilterPayloadPB::new(& self.view_id(), field_rev, checkbox_filter); - self.insert_filter(payload).await; - } - FilterScript::CreateDateFilter { condition, start, end, timestamp, changed} => { - self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap()); - self.assert_future_changed(changed).await; - let field_rev = self.get_first_field_rev(FieldType::DateTime); - let date_filter = DateFilterPB { - condition, - start, - end, - timestamp - }; - - let payload = - AlterFilterPayloadPB::new( &self.view_id(), field_rev, date_filter); - self.insert_filter(payload).await; - } - FilterScript::CreateMultiSelectFilter { condition, option_ids} => { - self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap()); - let field_rev = self.get_first_field_rev(FieldType::MultiSelect); - let filter = SelectOptionFilterPB { condition, option_ids }; - let payload = - AlterFilterPayloadPB::new( &self.view_id(),field_rev, filter); - self.insert_filter(payload).await; - } - FilterScript::CreateSingleSelectFilter { condition, option_ids, changed} => { - self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap()); - self.assert_future_changed(changed).await; - let field_rev = self.get_first_field_rev(FieldType::SingleSelect); - let filter = SelectOptionFilterPB { condition, option_ids }; - let payload = - AlterFilterPayloadPB::new(& self.view_id(),field_rev, filter); - self.insert_filter(payload).await; - } - FilterScript::CreateChecklistFilter { condition,changed} => { - self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap()); - self.assert_future_changed(changed).await; - let field_rev = self.get_first_field_rev(FieldType::Checklist); - // let type_option = self.get_checklist_type_option(&field_rev.id); - let filter = ChecklistFilterPB { condition }; - let payload = - AlterFilterPayloadPB::new(& self.view_id(),field_rev, filter); - self.insert_filter(payload).await; - } - FilterScript::AssertFilterCount { count } => { - let filters = self.editor.get_all_filters(&self.view_id).await.unwrap(); - assert_eq!(count as usize, filters.len()); - } - FilterScript::AssertFilterContent { filter_type: filter_id, condition, content} => { - let filter = self.editor.get_filters(&self.view_id, filter_id).await.unwrap().pop().unwrap(); - assert_eq!(&filter.content, &content); - assert_eq!(filter.condition as u32, condition); - - } - FilterScript::DeleteFilter { filter_id, filter_type ,changed} => { - self.recv = Some(self.editor.subscribe_view_changed(&self.view_id()).await.unwrap()); - self.assert_future_changed(changed).await; - let params = DeleteFilterParams { view_id: self.view_id(),filter_type, filter_id }; - let _ = self.editor.delete_filter(params).await.unwrap(); - } - FilterScript::AssertGridSetting { expected_setting } => { - let setting = self.editor.get_setting(&self.view_id).await.unwrap(); - assert_eq!(expected_setting, setting); - } - FilterScript::AssertNumberOfVisibleRows { expected } => { - let grid = self.editor.get_database(&self.view_id()).await.unwrap(); - assert_eq!(grid.rows.len(), expected); - } - FilterScript::Wait { millisecond } => { - tokio::time::sleep(Duration::from_millis(millisecond)).await; - } - } - } - - async fn assert_future_changed(&mut self, change: Option) { - if change.is_none() {return;} - let change = change.unwrap(); - let mut receiver = self.recv.take().unwrap(); - tokio::spawn(async move { - match tokio::time::timeout(Duration::from_secs(2), receiver.recv()).await { - Ok(changed) => { - match changed.unwrap() { DatabaseViewChanged::FilterNotification(notification) => { - assert_eq!(notification.visible_rows.len(), change.showing_num_of_rows, "visible rows not match"); - assert_eq!(notification.invisible_rows.len(), change.hiding_num_of_rows, "invisible rows not match"); - } - _ => {} - } - }, - Err(e) => { - panic!("Process filter task timeout: {:?}", e); - } - } - }); - - - } - - async fn insert_filter(&self, payload: AlterFilterPayloadPB) { - let params: AlterFilterParams = payload.try_into().unwrap(); - let _ = self.editor.create_or_update_filter(params).await.unwrap(); - } - -} - - -impl std::ops::Deref for DatabaseFilterTest { - type Target = DatabaseEditorTest; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl std::ops::DerefMut for DatabaseFilterTest { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} diff --git a/frontend/rust-lib/flowy-database/tests/database/filter_test/select_option_filter_test.rs b/frontend/rust-lib/flowy-database/tests/database/filter_test/select_option_filter_test.rs deleted file mode 100644 index 8afad84cce..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/filter_test/select_option_filter_test.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::database::filter_test::script::FilterScript::*; -use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; -use flowy_database::entities::{FieldType, SelectOptionConditionPB}; - -#[tokio::test] -async fn grid_filter_multi_select_is_empty_test() { - let mut test = DatabaseFilterTest::new().await; - let scripts = vec![ - CreateMultiSelectFilter { - condition: SelectOptionConditionPB::OptionIsEmpty, - option_ids: vec![], - }, - AssertNumberOfVisibleRows { expected: 3 }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_multi_select_is_not_empty_test() { - let mut test = DatabaseFilterTest::new().await; - let scripts = vec![ - CreateMultiSelectFilter { - condition: SelectOptionConditionPB::OptionIsNotEmpty, - option_ids: vec![], - }, - AssertNumberOfVisibleRows { expected: 3 }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_multi_select_is_test() { - let mut test = DatabaseFilterTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::MultiSelect); - let mut options = test.get_multi_select_type_option(&field_rev.id); - let scripts = vec![ - CreateMultiSelectFilter { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![options.remove(0).id, options.remove(0).id], - }, - AssertNumberOfVisibleRows { expected: 3 }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_multi_select_is_test2() { - let mut test = DatabaseFilterTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::MultiSelect); - let mut options = test.get_multi_select_type_option(&field_rev.id); - let scripts = vec![ - CreateMultiSelectFilter { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![options.remove(1).id], - }, - AssertNumberOfVisibleRows { expected: 2 }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_single_select_is_empty_test() { - let mut test = DatabaseFilterTest::new().await; - let expected = 2; - let row_count = test.row_revs.len(); - let scripts = vec![ - CreateSingleSelectFilter { - condition: SelectOptionConditionPB::OptionIsEmpty, - option_ids: vec![], - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_single_select_is_test() { - let mut test = DatabaseFilterTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::SingleSelect); - let mut options = test.get_single_select_type_option(&field_rev.id).options; - let expected = 2; - let row_count = test.row_revs.len(); - let scripts = vec![ - CreateSingleSelectFilter { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![options.remove(0).id], - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - expected, - }), - }, - AssertNumberOfVisibleRows { expected: 2 }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_single_select_is_test2() { - let mut test = DatabaseFilterTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::SingleSelect); - let row_revs = test.get_row_revs().await; - let mut options = test.get_single_select_type_option(&field_rev.id).options; - let option = options.remove(0); - let row_count = test.row_revs.len(); - - let scripts = vec![ - CreateSingleSelectFilter { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![option.id.clone()], - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - 2, - }), - }, - AssertNumberOfVisibleRows { expected: 2 }, - UpdateSingleSelectCell { - row_id: row_revs[1].id.clone(), - option_id: option.id.clone(), - changed: None, - }, - AssertNumberOfVisibleRows { expected: 3 }, - UpdateSingleSelectCell { - row_id: row_revs[1].id.clone(), - option_id: "".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 1, - }), - }, - AssertNumberOfVisibleRows { expected: 2 }, - ]; - test.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/filter_test/text_filter_test.rs b/frontend/rust-lib/flowy-database/tests/database/filter_test/text_filter_test.rs deleted file mode 100644 index 91df7bd58b..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/filter_test/text_filter_test.rs +++ /dev/null @@ -1,245 +0,0 @@ -use crate::database::filter_test::script::FilterScript::*; -use crate::database::filter_test::script::*; -use flowy_database::entities::{ - AlterFilterPayloadPB, FieldType, TextFilterConditionPB, TextFilterPB, -}; -use flowy_database::services::filter::FilterType; - -#[tokio::test] -async fn grid_filter_text_is_empty_test() { - let mut test = DatabaseFilterTest::new().await; - let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::TextIsEmpty, - content: "".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 5, - }), - }, - AssertFilterCount { count: 1 }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_text_is_not_empty_test() { - let mut test = DatabaseFilterTest::new().await; - // Only one row's text of the initial rows is "" - let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::TextIsNotEmpty, - content: "".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 1, - }), - }, - AssertFilterCount { count: 1 }, - ]; - test.run_scripts(scripts).await; - - let filter = test.database_filters().await.pop().unwrap(); - let field_rev = test.get_first_field_rev(FieldType::RichText).clone(); - test - .run_scripts(vec![ - DeleteFilter { - filter_id: filter.id, - filter_type: FilterType::from(&field_rev), - changed: Some(FilterRowChanged { - showing_num_of_rows: 1, - hiding_num_of_rows: 0, - }), - }, - AssertFilterCount { count: 0 }, - ]) - .await; -} - -#[tokio::test] -async fn grid_filter_is_text_test() { - let mut test = DatabaseFilterTest::new().await; - // Only one row's text of the initial rows is "A" - let scripts = vec![CreateTextFilter { - condition: TextFilterConditionPB::Is, - content: "A".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 5, - }), - }]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_contain_text_test() { - let mut test = DatabaseFilterTest::new().await; - let scripts = vec![CreateTextFilter { - condition: TextFilterConditionPB::Contains, - content: "A".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 2, - }), - }]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_contain_text_test2() { - let mut test = DatabaseFilterTest::new().await; - let row_revs = test.row_revs.clone(); - - let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::Contains, - content: "A".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 2, - }), - }, - UpdateTextCell { - row_id: row_revs[1].id.clone(), - text: "ABC".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 1, - hiding_num_of_rows: 0, - }), - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_does_not_contain_text_test() { - let mut test = DatabaseFilterTest::new().await; - // None of the initial rows contains the text "AB" - let scripts = vec![CreateTextFilter { - condition: TextFilterConditionPB::DoesNotContain, - content: "AB".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 0, - }), - }]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_start_with_text_test() { - let mut test = DatabaseFilterTest::new().await; - let scripts = vec![CreateTextFilter { - condition: TextFilterConditionPB::StartsWith, - content: "A".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 3, - }), - }]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_ends_with_text_test() { - let mut test = DatabaseFilterTest::new().await; - let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::EndsWith, - content: "A".to_string(), - changed: None, - }, - AssertNumberOfVisibleRows { expected: 2 }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_update_text_filter_test() { - let mut test = DatabaseFilterTest::new().await; - let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::EndsWith, - content: "A".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 4, - }), - }, - AssertNumberOfVisibleRows { expected: 2 }, - AssertFilterCount { count: 1 }, - ]; - test.run_scripts(scripts).await; - - // Update the filter - let filter = test.get_all_filters().await.pop().unwrap(); - let scripts = vec![ - UpdateTextFilter { - filter, - condition: TextFilterConditionPB::Is, - content: "A".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 1, - }), - }, - AssertNumberOfVisibleRows { expected: 1 }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_delete_test() { - let mut test = DatabaseFilterTest::new().await; - let field_rev = test.get_first_field_rev(FieldType::RichText).clone(); - let text_filter = TextFilterPB { - condition: TextFilterConditionPB::TextIsEmpty, - content: "".to_string(), - }; - let payload = AlterFilterPayloadPB::new(&test.view_id(), &field_rev, text_filter); - let scripts = vec![ - InsertFilter { payload }, - AssertFilterCount { count: 1 }, - AssertNumberOfVisibleRows { expected: 1 }, - ]; - test.run_scripts(scripts).await; - - let filter = test.database_filters().await.pop().unwrap(); - test - .run_scripts(vec![ - DeleteFilter { - filter_id: filter.id, - filter_type: FilterType::from(&field_rev), - changed: None, - }, - AssertFilterCount { count: 0 }, - AssertNumberOfVisibleRows { expected: 6 }, - ]) - .await; -} - -#[tokio::test] -async fn grid_filter_update_empty_text_cell_test() { - let mut test = DatabaseFilterTest::new().await; - let row_revs = test.row_revs.clone(); - let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::TextIsEmpty, - content: "".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: 5, - }), - }, - AssertFilterCount { count: 1 }, - UpdateTextCell { - row_id: row_revs[0].id.clone(), - text: "".to_string(), - changed: Some(FilterRowChanged { - showing_num_of_rows: 1, - hiding_num_of_rows: 0, - }), - }, - ]; - test.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/group_test/mod.rs b/frontend/rust-lib/flowy-database/tests/database/group_test/mod.rs deleted file mode 100644 index 67671ae7f5..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/group_test/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod script; -mod test; -mod url_group_test; diff --git a/frontend/rust-lib/flowy-database/tests/database/group_test/script.rs b/frontend/rust-lib/flowy-database/tests/database/group_test/script.rs deleted file mode 100644 index a7496c0c6b..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/group_test/script.rs +++ /dev/null @@ -1,328 +0,0 @@ -use crate::database::database_editor::DatabaseEditorTest; -use database_model::{FieldRevision, RowChangeset}; -use flowy_database::entities::{ - CreateRowParams, FieldType, GroupPB, MoveGroupParams, MoveGroupRowParams, RowPB, -}; -use flowy_database::services::cell::{ - delete_select_option_cell, insert_select_option_cell, insert_url_cell, -}; -use flowy_database::services::field::{ - edit_single_select_type_option, SelectOptionPB, SelectTypeOptionSharedAction, - SingleSelectTypeOptionPB, -}; -use std::sync::Arc; - -pub enum GroupScript { - AssertGroupRowCount { - group_index: usize, - row_count: usize, - }, - AssertGroupCount(usize), - AssertGroup { - group_index: usize, - expected_group: GroupPB, - }, - AssertRow { - group_index: usize, - row_index: usize, - row: RowPB, - }, - MoveRow { - from_group_index: usize, - from_row_index: usize, - to_group_index: usize, - to_row_index: usize, - }, - CreateRow { - group_index: usize, - }, - DeleteRow { - group_index: usize, - row_index: usize, - }, - UpdateGroupedCell { - from_group_index: usize, - row_index: usize, - to_group_index: usize, - }, - UpdateGroupedCellWithData { - from_group_index: usize, - row_index: usize, - cell_data: String, - }, - MoveGroup { - from_group_index: usize, - to_group_index: usize, - }, - UpdateSingleSelectSelectOption { - inserted_options: Vec, - }, - GroupByField { - field_id: String, - }, -} - -pub struct DatabaseGroupTest { - inner: DatabaseEditorTest, -} - -impl DatabaseGroupTest { - pub async fn new() -> Self { - let editor_test = DatabaseEditorTest::new_board().await; - Self { inner: editor_test } - } - - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - pub async fn run_script(&mut self, script: GroupScript) { - match script { - GroupScript::AssertGroupRowCount { - group_index, - row_count, - } => { - assert_eq!(row_count, self.group_at_index(group_index).await.rows.len()); - }, - GroupScript::AssertGroupCount(count) => { - let groups = self.editor.load_groups(&self.view_id).await.unwrap(); - assert_eq!(count, groups.len()); - }, - GroupScript::MoveRow { - from_group_index, - from_row_index, - to_group_index, - to_row_index, - } => { - let groups: Vec = self.editor.load_groups(&self.view_id).await.unwrap().items; - let from_row = groups - .get(from_group_index) - .unwrap() - .rows - .get(from_row_index) - .unwrap(); - let to_group = groups.get(to_group_index).unwrap(); - let to_row = to_group.rows.get(to_row_index).unwrap(); - let params = MoveGroupRowParams { - view_id: self.view_id.clone(), - from_row_id: from_row.id.clone(), - to_group_id: to_group.group_id.clone(), - to_row_id: Some(to_row.id.clone()), - }; - - self.editor.move_group_row(params).await.unwrap(); - }, - GroupScript::AssertRow { - group_index, - row_index, - row, - } => { - // - let group = self.group_at_index(group_index).await; - let compare_row = group.rows.get(row_index).unwrap().clone(); - assert_eq!(row.id, compare_row.id); - }, - GroupScript::CreateRow { group_index } => { - let group = self.group_at_index(group_index).await; - let params = CreateRowParams { - view_id: self.view_id.clone(), - start_row_id: None, - group_id: Some(group.group_id.clone()), - cell_data_by_field_id: None, - }; - let _ = self.editor.create_row(params).await.unwrap(); - }, - GroupScript::DeleteRow { - group_index, - row_index, - } => { - let row = self.row_at_index(group_index, row_index).await; - self.editor.delete_row(&row.id).await.unwrap(); - }, - GroupScript::UpdateGroupedCell { - from_group_index, - row_index, - to_group_index, - } => { - let from_group = self.group_at_index(from_group_index).await; - let to_group = self.group_at_index(to_group_index).await; - let field_id = from_group.field_id; - let field_rev = self.editor.get_field_rev(&field_id).await.unwrap(); - let field_type: FieldType = field_rev.ty.into(); - - let cell_rev = if to_group.is_default { - match field_type { - FieldType::SingleSelect => { - delete_select_option_cell(vec![to_group.group_id.clone()], &field_rev) - }, - FieldType::MultiSelect => { - delete_select_option_cell(vec![to_group.group_id.clone()], &field_rev) - }, - _ => { - panic!("Unsupported group field type"); - }, - } - } else { - match field_type { - FieldType::SingleSelect => { - insert_select_option_cell(vec![to_group.group_id.clone()], &field_rev) - }, - FieldType::MultiSelect => { - insert_select_option_cell(vec![to_group.group_id.clone()], &field_rev) - }, - FieldType::URL => insert_url_cell(to_group.group_id.clone(), &field_rev), - _ => { - panic!("Unsupported group field type"); - }, - } - }; - - let row_id = self.row_at_index(from_group_index, row_index).await.id; - let mut row_changeset = RowChangeset::new(row_id); - row_changeset.cell_by_field_id.insert(field_id, cell_rev); - self.editor.update_row(row_changeset).await.unwrap(); - }, - GroupScript::UpdateGroupedCellWithData { - from_group_index, - row_index, - cell_data, - } => { - let from_group = self.group_at_index(from_group_index).await; - let field_id = from_group.field_id; - let field_rev = self.editor.get_field_rev(&field_id).await.unwrap(); - let field_type: FieldType = field_rev.ty.into(); - let cell_rev = match field_type { - FieldType::URL => insert_url_cell(cell_data, &field_rev), - _ => { - panic!("Unsupported group field type"); - }, - }; - - let row_id = self.row_at_index(from_group_index, row_index).await.id; - let mut row_changeset = RowChangeset::new(row_id); - row_changeset.cell_by_field_id.insert(field_id, cell_rev); - self.editor.update_row(row_changeset).await.unwrap(); - }, - GroupScript::MoveGroup { - from_group_index, - to_group_index, - } => { - let from_group = self.group_at_index(from_group_index).await; - let to_group = self.group_at_index(to_group_index).await; - let params = MoveGroupParams { - view_id: self.view_id.clone(), - from_group_id: from_group.group_id, - to_group_id: to_group.group_id, - }; - self.editor.move_group(params).await.unwrap(); - // - }, - GroupScript::AssertGroup { - group_index, - expected_group: group_pb, - } => { - let group = self.group_at_index(group_index).await; - assert_eq!(group.group_id, group_pb.group_id); - assert_eq!(group.desc, group_pb.desc); - }, - GroupScript::UpdateSingleSelectSelectOption { inserted_options } => { - self - .edit_single_select_type_option(|type_option| { - for inserted_option in inserted_options { - type_option.insert_option(inserted_option); - } - }) - .await; - }, - GroupScript::GroupByField { field_id } => { - self - .editor - .group_by_field(&self.view_id, &field_id) - .await - .unwrap(); - }, - } - } - - pub async fn group_at_index(&self, index: usize) -> GroupPB { - let groups = self.editor.load_groups(&self.view_id).await.unwrap().items; - groups.get(index).unwrap().clone() - } - - pub async fn row_at_index(&self, group_index: usize, row_index: usize) -> RowPB { - let groups = self.group_at_index(group_index).await; - groups.rows.get(row_index).unwrap().clone() - } - - #[allow(dead_code)] - pub async fn get_multi_select_field(&self) -> Arc { - let field = self - .inner - .field_revs - .iter() - .find(|field_rev| { - let field_type: FieldType = field_rev.ty.into(); - field_type.is_multi_select() - }) - .unwrap() - .clone(); - field - } - - pub async fn get_single_select_field(&self) -> Arc { - self - .inner - .field_revs - .iter() - .find(|field_rev| { - let field_type: FieldType = field_rev.ty.into(); - field_type.is_single_select() - }) - .unwrap() - .clone() - } - - pub async fn edit_single_select_type_option( - &self, - action: impl FnOnce(&mut SingleSelectTypeOptionPB), - ) { - let single_select = self.get_single_select_field().await; - edit_single_select_type_option( - &self.view_id, - &single_select.id, - self.editor.clone(), - action, - ) - .await - .unwrap(); - } - - pub async fn get_url_field(&self) -> Arc { - self - .inner - .field_revs - .iter() - .find(|field_rev| { - let field_type: FieldType = field_rev.ty.into(); - field_type.is_url() - }) - .unwrap() - .clone() - } -} - -impl std::ops::Deref for DatabaseGroupTest { - type Target = DatabaseEditorTest; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl std::ops::DerefMut for DatabaseGroupTest { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} diff --git a/frontend/rust-lib/flowy-database/tests/database/group_test/test.rs b/frontend/rust-lib/flowy-database/tests/database/group_test/test.rs deleted file mode 100644 index 87504ba61f..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/group_test/test.rs +++ /dev/null @@ -1,488 +0,0 @@ -use crate::database::group_test::script::DatabaseGroupTest; -use crate::database::group_test::script::GroupScript::*; - -use flowy_database::services::field::SelectOptionPB; - -#[tokio::test] -async fn group_init_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![ - AssertGroupCount(4), - AssertGroupRowCount { - group_index: 1, - row_count: 2, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 2, - }, - AssertGroupRowCount { - group_index: 3, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 0, - row_count: 0, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_move_row_test() { - let mut test = DatabaseGroupTest::new().await; - let group = test.group_at_index(1).await; - let scripts = vec![ - // Move the row at 0 in group0 to group1 at 1 - MoveRow { - from_group_index: 1, - from_row_index: 0, - to_group_index: 1, - to_row_index: 1, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 2, - }, - AssertRow { - group_index: 1, - row_index: 1, - row: group.rows.get(0).unwrap().clone(), - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_move_row_to_other_group_test() { - let mut test = DatabaseGroupTest::new().await; - let group = test.group_at_index(1).await; - let scripts = vec![ - MoveRow { - from_group_index: 1, - from_row_index: 0, - to_group_index: 2, - to_row_index: 1, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 3, - }, - AssertRow { - group_index: 2, - row_index: 1, - row: group.rows.get(0).unwrap().clone(), - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_move_two_row_to_other_group_test() { - let mut test = DatabaseGroupTest::new().await; - let group_1 = test.group_at_index(1).await; - let scripts = vec![ - // Move row at index 0 from group 1 to group 2 at index 1 - MoveRow { - from_group_index: 1, - from_row_index: 0, - to_group_index: 2, - to_row_index: 1, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 3, - }, - AssertRow { - group_index: 2, - row_index: 1, - row: group_1.rows.get(0).unwrap().clone(), - }, - ]; - test.run_scripts(scripts).await; - - let group_1 = test.group_at_index(1).await; - // Move row at index 0 from group 1 to group 2 at index 1 - let scripts = vec![ - MoveRow { - from_group_index: 1, - from_row_index: 0, - to_group_index: 2, - to_row_index: 1, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 0, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 4, - }, - AssertRow { - group_index: 2, - row_index: 1, - row: group_1.rows.get(0).unwrap().clone(), - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_move_row_to_other_group_and_reorder_from_up_to_down_test() { - let mut test = DatabaseGroupTest::new().await; - let group_1 = test.group_at_index(1).await; - let group_2 = test.group_at_index(2).await; - let scripts = vec![ - MoveRow { - from_group_index: 1, - from_row_index: 0, - to_group_index: 2, - to_row_index: 1, - }, - AssertRow { - group_index: 2, - row_index: 1, - row: group_1.rows.get(0).unwrap().clone(), - }, - ]; - test.run_scripts(scripts).await; - - let scripts = vec![ - MoveRow { - from_group_index: 2, - from_row_index: 0, - to_group_index: 2, - to_row_index: 2, - }, - AssertRow { - group_index: 2, - row_index: 2, - row: group_2.rows.get(0).unwrap().clone(), - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_move_row_to_other_group_and_reorder_from_bottom_to_up_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![MoveRow { - from_group_index: 1, - from_row_index: 0, - to_group_index: 2, - to_row_index: 1, - }]; - test.run_scripts(scripts).await; - - let group = test.group_at_index(2).await; - let scripts = vec![ - AssertGroupRowCount { - group_index: 2, - row_count: 3, - }, - MoveRow { - from_group_index: 2, - from_row_index: 2, - to_group_index: 2, - to_row_index: 0, - }, - AssertRow { - group_index: 2, - row_index: 0, - row: group.rows.get(2).unwrap().clone(), - }, - ]; - test.run_scripts(scripts).await; -} -#[tokio::test] -async fn group_create_row_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![ - CreateRow { group_index: 1 }, - AssertGroupRowCount { - group_index: 1, - row_count: 3, - }, - CreateRow { group_index: 2 }, - CreateRow { group_index: 2 }, - AssertGroupRowCount { - group_index: 2, - row_count: 4, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_delete_row_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![ - DeleteRow { - group_index: 1, - row_index: 0, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_delete_all_row_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![ - DeleteRow { - group_index: 1, - row_index: 0, - }, - DeleteRow { - group_index: 1, - row_index: 0, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 0, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_update_row_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![ - // Update the row at 0 in group0 by setting the row's group field data - UpdateGroupedCell { - from_group_index: 1, - row_index: 0, - to_group_index: 2, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 3, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_reorder_group_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![ - // Update the row at 0 in group0 by setting the row's group field data - UpdateGroupedCell { - from_group_index: 1, - row_index: 0, - to_group_index: 2, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 3, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_move_to_default_group_test() { - let mut test = DatabaseGroupTest::new().await; - let scripts = vec![ - UpdateGroupedCell { - from_group_index: 1, - row_index: 0, - to_group_index: 0, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 0, - row_count: 1, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_move_from_default_group_test() { - let mut test = DatabaseGroupTest::new().await; - // Move one row from group 1 to group 0 - let scripts = vec![ - UpdateGroupedCell { - from_group_index: 1, - row_index: 0, - to_group_index: 0, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 0, - row_count: 1, - }, - ]; - test.run_scripts(scripts).await; - - // Move one row from group 0 to group 1 - let scripts = vec![ - UpdateGroupedCell { - from_group_index: 0, - row_index: 0, - to_group_index: 1, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 2, - }, - AssertGroupRowCount { - group_index: 0, - row_count: 0, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_move_group_test() { - let mut test = DatabaseGroupTest::new().await; - let group_0 = test.group_at_index(0).await; - let group_1 = test.group_at_index(1).await; - let scripts = vec![ - MoveGroup { - from_group_index: 0, - to_group_index: 1, - }, - AssertGroupRowCount { - group_index: 0, - row_count: 2, - }, - AssertGroup { - group_index: 0, - expected_group: group_1, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 0, - }, - AssertGroup { - group_index: 1, - expected_group: group_0, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_move_group_row_after_move_group_test() { - let mut test = DatabaseGroupTest::new().await; - let group_1 = test.group_at_index(1).await; - let group_2 = test.group_at_index(2).await; - let scripts = vec![ - MoveGroup { - from_group_index: 1, - to_group_index: 2, - }, - AssertGroup { - group_index: 1, - expected_group: group_2, - }, - AssertGroup { - group_index: 2, - expected_group: group_1, - }, - MoveRow { - from_group_index: 1, - from_row_index: 0, - to_group_index: 2, - to_row_index: 0, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 3, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_move_group_to_default_group_pos_test() { - let mut test = DatabaseGroupTest::new().await; - let group_0 = test.group_at_index(0).await; - let group_3 = test.group_at_index(3).await; - let scripts = vec![ - MoveGroup { - from_group_index: 3, - to_group_index: 0, - }, - AssertGroup { - group_index: 0, - expected_group: group_3, - }, - AssertGroup { - group_index: 1, - expected_group: group_0, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_insert_single_select_option_test() { - let mut test = DatabaseGroupTest::new().await; - let new_option_name = "New option"; - let scripts = vec![ - AssertGroupCount(4), - UpdateSingleSelectSelectOption { - inserted_options: vec![SelectOptionPB::new(new_option_name)], - }, - AssertGroupCount(5), - ]; - test.run_scripts(scripts).await; - let new_group = test.group_at_index(4).await; - assert_eq!(new_group.desc, new_option_name); -} - -#[tokio::test] -async fn group_group_by_other_field() { - let mut test = DatabaseGroupTest::new().await; - let multi_select_field = test.get_multi_select_field().await; - let scripts = vec![ - GroupByField { - field_id: multi_select_field.id.clone(), - }, - AssertGroupRowCount { - group_index: 1, - row_count: 3, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 2, - }, - AssertGroupCount(4), - ]; - test.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/group_test/url_group_test.rs b/frontend/rust-lib/flowy-database/tests/database/group_test/url_group_test.rs deleted file mode 100644 index 83a38b07d3..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/group_test/url_group_test.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::database::group_test::script::DatabaseGroupTest; -use crate::database::group_test::script::GroupScript::*; - -#[tokio::test] -async fn group_group_by_url() { - let mut test = DatabaseGroupTest::new().await; - let url_field = test.get_url_field().await; - let scripts = vec![ - GroupByField { - field_id: url_field.id.clone(), - }, - // no status group - AssertGroupRowCount { - group_index: 0, - row_count: 2, - }, - // https://appflowy.io - AssertGroupRowCount { - group_index: 1, - row_count: 2, - }, - // https://github.com/AppFlowy-IO/AppFlowy - AssertGroupRowCount { - group_index: 2, - row_count: 1, - }, - AssertGroupCount(3), - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_alter_url_to_another_group_url_test() { - let mut test = DatabaseGroupTest::new().await; - let url_field = test.get_url_field().await; - let scripts = vec![ - GroupByField { - field_id: url_field.id.clone(), - }, - // no status group - AssertGroupRowCount { - group_index: 0, - row_count: 2, - }, - // https://appflowy.io - AssertGroupRowCount { - group_index: 1, - row_count: 2, - }, - // https://github.com/AppFlowy-IO/AppFlowy - AssertGroupRowCount { - group_index: 2, - row_count: 1, - }, - // When moving the last row from 2nd group to 1nd group, the 2nd group will be removed - UpdateGroupedCell { - from_group_index: 2, - row_index: 0, - to_group_index: 1, - }, - AssertGroupCount(2), - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_alter_url_to_new_url_test() { - let mut test = DatabaseGroupTest::new().await; - let url_field = test.get_url_field().await; - let scripts = vec![ - GroupByField { - field_id: url_field.id.clone(), - }, - // When moving the last row from 2nd group to 1nd group, the 2nd group will be removed - UpdateGroupedCellWithData { - from_group_index: 0, - row_index: 0, - cell_data: "https://github.com/AppFlowy-IO".to_string(), - }, - // no status group - AssertGroupRowCount { - group_index: 0, - row_count: 1, - }, - // https://appflowy.io - AssertGroupRowCount { - group_index: 1, - row_count: 2, - }, - // https://github.com/AppFlowy-IO/AppFlowy - AssertGroupRowCount { - group_index: 2, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 3, - row_count: 1, - }, - AssertGroupCount(4), - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn group_move_url_group_row_test() { - let mut test = DatabaseGroupTest::new().await; - let url_field = test.get_url_field().await; - let scripts = vec![ - GroupByField { - field_id: url_field.id.clone(), - }, - // no status group - AssertGroupRowCount { - group_index: 0, - row_count: 2, - }, - // https://appflowy.io - AssertGroupRowCount { - group_index: 1, - row_count: 2, - }, - // https://github.com/AppFlowy-IO/AppFlowy - AssertGroupRowCount { - group_index: 2, - row_count: 1, - }, - AssertGroupCount(3), - MoveRow { - from_group_index: 0, - from_row_index: 0, - to_group_index: 1, - to_row_index: 0, - }, - AssertGroupRowCount { - group_index: 0, - row_count: 1, - }, - AssertGroupRowCount { - group_index: 1, - row_count: 3, - }, - AssertGroupRowCount { - group_index: 2, - row_count: 1, - }, - ]; - test.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/layout_test/mod.rs b/frontend/rust-lib/flowy-database/tests/database/layout_test/mod.rs deleted file mode 100644 index 63d424afaf..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/layout_test/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod script; -mod test; diff --git a/frontend/rust-lib/flowy-database/tests/database/layout_test/script.rs b/frontend/rust-lib/flowy-database/tests/database/layout_test/script.rs deleted file mode 100644 index 1172899d3d..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/layout_test/script.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::database::database_editor::DatabaseEditorTest; -use database_model::{CalendarLayoutSetting, FieldRevision, LayoutRevision}; -use flowy_database::entities::FieldType; -use std::sync::Arc; - -pub enum LayoutScript { - AssertCalendarLayoutSetting { expected: CalendarLayoutSetting }, - GetCalendarEvents, -} - -pub struct DatabaseLayoutTest { - database_test: DatabaseEditorTest, -} - -impl DatabaseLayoutTest { - pub async fn new_calendar() -> Self { - let database_test = DatabaseEditorTest::new_calendar().await; - Self { database_test } - } - - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - pub async fn get_first_date_field(&self) -> Arc { - self - .database_test - .get_first_field_rev(FieldType::DateTime) - .clone() - } - - pub async fn run_script(&mut self, script: LayoutScript) { - match script { - LayoutScript::AssertCalendarLayoutSetting { expected } => { - let view_id = self.database_test.view_id.clone(); - let layout_ty = LayoutRevision::Calendar; - - let calendar_setting = self - .database_test - .editor - .get_layout_setting(&view_id, layout_ty) - .await - .unwrap() - .calendar - .unwrap(); - - assert_eq!(calendar_setting.layout_ty, expected.layout_ty); - assert_eq!( - calendar_setting.first_day_of_week, - expected.first_day_of_week - ); - assert_eq!(calendar_setting.show_weekends, expected.show_weekends); - }, - LayoutScript::GetCalendarEvents => { - let events = self - .database_test - .editor - .get_all_calendar_events(&self.database_test.view_id) - .await; - assert_eq!(events.len(), 5); - - for (index, event) in events.into_iter().enumerate() { - if index == 0 { - assert_eq!(event.title, "A"); - assert_eq!(event.timestamp, 1678090778); - } - - if index == 1 { - assert_eq!(event.title, "B"); - assert_eq!(event.timestamp, 1677917978); - } - if index == 2 { - assert_eq!(event.title, "C"); - assert_eq!(event.timestamp, 1679213978); - } - if index == 4 { - assert_eq!(event.title, "E"); - assert_eq!(event.timestamp, 1678695578); - } - } - }, - } - } -} diff --git a/frontend/rust-lib/flowy-database/tests/database/layout_test/test.rs b/frontend/rust-lib/flowy-database/tests/database/layout_test/test.rs deleted file mode 100644 index 6e55134721..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/layout_test/test.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::database::layout_test::script::DatabaseLayoutTest; -use crate::database::layout_test::script::LayoutScript::*; -use database_model::CalendarLayoutSetting; - -#[tokio::test] -async fn calendar_initial_layout_setting_test() { - let mut test = DatabaseLayoutTest::new_calendar().await; - let date_field = test.get_first_date_field().await; - let default_calendar_setting = CalendarLayoutSetting::new(date_field.id.clone()); - let scripts = vec![AssertCalendarLayoutSetting { - expected: default_calendar_setting, - }]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn calendar_get_events_test() { - let mut test = DatabaseLayoutTest::new_calendar().await; - let scripts = vec![GetCalendarEvents]; - test.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/mock_data/board_mock_data.rs b/frontend/rust-lib/flowy-database/tests/database/mock_data/board_mock_data.rs deleted file mode 100644 index 19407c8369..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/mock_data/board_mock_data.rs +++ /dev/null @@ -1,207 +0,0 @@ -// #![allow(clippy::all)] -// #![allow(dead_code)] -// #![allow(unused_imports)] -use crate::database::block_test::util::DatabaseRowTestBuilder; -use crate::database::mock_data::{ - COMPLETED, FACEBOOK, FIRST_THING, GOOGLE, PAUSED, PLANNED, SECOND_THING, THIRD_THING, TWITTER, -}; - -use flowy_client_sync::client_database::DatabaseBuilder; -use flowy_database::entities::*; - -use flowy_database::services::field::SelectOptionPB; -use flowy_database::services::field::*; - -use database_model::*; - -use strum::IntoEnumIterator; - -// Kanban board unit test mock data -pub fn make_test_board() -> BuildDatabaseContext { - let mut database_builder = DatabaseBuilder::new(); - // Iterate through the FieldType to create the corresponding Field. - for field_type in FieldType::iter() { - let field_type: FieldType = field_type; - - match field_type { - FieldType::RichText => { - let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Name") - .visibility(true) - .primary(true) - .build(); - database_builder.add_field(text_field); - }, - FieldType::Number => { - // Number - let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); - let number_field = FieldBuilder::new(number) - .name("Price") - .visibility(true) - .build(); - database_builder.add_field(number_field); - }, - FieldType::DateTime => { - // Date - let date = DateTypeOptionBuilder::default() - .date_format(DateFormat::US) - .time_format(TimeFormat::TwentyFourHour); - let date_field = FieldBuilder::new(date) - .name("Time") - .visibility(true) - .build(); - database_builder.add_field(date_field); - }, - FieldType::SingleSelect => { - // Single Select - let single_select = SingleSelectTypeOptionBuilder::default() - .add_option(SelectOptionPB::new(COMPLETED)) - .add_option(SelectOptionPB::new(PLANNED)) - .add_option(SelectOptionPB::new(PAUSED)); - let single_select_field = FieldBuilder::new(single_select) - .name("Status") - .visibility(true) - .build(); - database_builder.add_field(single_select_field); - }, - FieldType::MultiSelect => { - // MultiSelect - let multi_select = MultiSelectTypeOptionBuilder::default() - .add_option(SelectOptionPB::new(GOOGLE)) - .add_option(SelectOptionPB::new(FACEBOOK)) - .add_option(SelectOptionPB::new(TWITTER)); - let multi_select_field = FieldBuilder::new(multi_select) - .name("Platform") - .visibility(true) - .build(); - database_builder.add_field(multi_select_field); - }, - FieldType::Checkbox => { - // Checkbox - let checkbox = CheckboxTypeOptionBuilder::default(); - let checkbox_field = FieldBuilder::new(checkbox) - .name("is urgent") - .visibility(true) - .build(); - database_builder.add_field(checkbox_field); - }, - FieldType::URL => { - // URL - let url = URLTypeOptionBuilder::default(); - let url_field = FieldBuilder::new(url).name("link").visibility(true).build(); - database_builder.add_field(url_field); - }, - FieldType::Checklist => { - let checklist = ChecklistTypeOptionBuilder::default() - .add_option(SelectOptionPB::new(FIRST_THING)) - .add_option(SelectOptionPB::new(SECOND_THING)) - .add_option(SelectOptionPB::new(THIRD_THING)); - let checklist_field = FieldBuilder::new(checklist) - .name("TODO") - .visibility(true) - .build(); - database_builder.add_field(checklist_field); - }, - } - } - - // We have many assumptions base on the number of the rows, so do not change the number of the loop. - for i in 0..5 { - let block_id = database_builder.block_id().to_owned(); - let field_revs = database_builder.field_revs().clone(); - let mut row_builder = DatabaseRowTestBuilder::new(block_id.clone(), field_revs); - match i { - 0 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("A"), - FieldType::Number => row_builder.insert_number_cell("1"), - // 1647251762 => Mar 14,2022 - FieldType::DateTime => row_builder.insert_date_cell("1647251762"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(0)) - }, - FieldType::MultiSelect => row_builder - .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]), - FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), - FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"), - _ => "".to_owned(), - }; - } - }, - 1 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("B"), - FieldType::Number => row_builder.insert_number_cell("2"), - // 1647251762 => Mar 14,2022 - FieldType::DateTime => row_builder.insert_date_cell("1647251762"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(0)) - }, - FieldType::MultiSelect => row_builder - .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]), - FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), - _ => "".to_owned(), - }; - } - }, - 2 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("C"), - FieldType::Number => row_builder.insert_number_cell("3"), - // 1647251762 => Mar 14,2022 - FieldType::DateTime => row_builder.insert_date_cell("1647251762"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(1)) - }, - FieldType::MultiSelect => { - row_builder.insert_multi_select_cell(|mut options| vec![options.remove(0)]) - }, - FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), - FieldType::URL => { - row_builder.insert_url_cell("https://github.com/AppFlowy-IO/AppFlowy") - }, - _ => "".to_owned(), - }; - } - }, - 3 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("DA"), - FieldType::Number => row_builder.insert_number_cell("4"), - FieldType::DateTime => row_builder.insert_date_cell("1668704685"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(1)) - }, - FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), - FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"), - _ => "".to_owned(), - }; - } - }, - 4 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("AE"), - FieldType::Number => row_builder.insert_number_cell(""), - FieldType::DateTime => row_builder.insert_date_cell("1668359085"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(2)) - }, - - FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), - _ => "".to_owned(), - }; - } - }, - _ => {}, - } - - let row_rev = row_builder.build(); - database_builder.add_row(row_rev); - } - database_builder.build() -} diff --git a/frontend/rust-lib/flowy-database/tests/database/mock_data/calendar_mock_data.rs b/frontend/rust-lib/flowy-database/tests/database/mock_data/calendar_mock_data.rs deleted file mode 100644 index b418649f72..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/mock_data/calendar_mock_data.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::database::block_test::util::DatabaseRowTestBuilder; -use database_model::{BuildDatabaseContext, CalendarLayoutSetting, LayoutRevision, LayoutSetting}; -use flowy_client_sync::client_database::DatabaseBuilder; -use flowy_database::entities::FieldType; -use flowy_database::services::field::{ - DateTypeOptionBuilder, FieldBuilder, MultiSelectTypeOptionBuilder, RichTextTypeOptionBuilder, -}; - -use strum::IntoEnumIterator; - -// Calendar unit test mock data -pub fn make_test_calendar() -> BuildDatabaseContext { - let mut database_builder = DatabaseBuilder::new(); - // text - let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Title") - .visibility(true) - .primary(true) - .build(); - let _text_field_id = text_field.id.clone(); - database_builder.add_field(text_field); - - // date - let date_type_option = DateTypeOptionBuilder::default(); - let date_field = FieldBuilder::new(date_type_option) - .name("Date") - .visibility(true) - .build(); - let date_field_id = date_field.id.clone(); - database_builder.add_field(date_field); - - // single select - let multi_select_type_option = MultiSelectTypeOptionBuilder::default(); - let multi_select_field = FieldBuilder::new(multi_select_type_option) - .name("Tags") - .visibility(true) - .build(); - database_builder.add_field(multi_select_field); - - let calendar_layout_setting = CalendarLayoutSetting::new(date_field_id); - let mut layout_setting = LayoutSetting::new(); - let calendar_setting = serde_json::to_string(&calendar_layout_setting).unwrap(); - layout_setting.insert(LayoutRevision::Calendar, calendar_setting); - database_builder.set_layout_setting(layout_setting); - - for i in 0..5 { - let block_id = database_builder.block_id().to_owned(); - let field_revs = database_builder.field_revs().clone(); - let mut row_builder = DatabaseRowTestBuilder::new(block_id.clone(), field_revs); - match i { - 0 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("A"), - FieldType::DateTime => row_builder.insert_date_cell("1678090778"), - _ => "".to_owned(), - }; - } - }, - 1 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("B"), - FieldType::DateTime => row_builder.insert_date_cell("1677917978"), - _ => "".to_owned(), - }; - } - }, - 2 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("C"), - FieldType::DateTime => row_builder.insert_date_cell("1679213978"), - _ => "".to_owned(), - }; - } - }, - 3 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("D"), - FieldType::DateTime => row_builder.insert_date_cell("1678695578"), - _ => "".to_owned(), - }; - } - }, - 4 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("E"), - FieldType::DateTime => row_builder.insert_date_cell("1678695578"), - _ => "".to_owned(), - }; - } - }, - _ => {}, - } - - let row_rev = row_builder.build(); - database_builder.add_row(row_rev); - } - - database_builder.build() -} diff --git a/frontend/rust-lib/flowy-database/tests/database/mock_data/grid_mock_data.rs b/frontend/rust-lib/flowy-database/tests/database/mock_data/grid_mock_data.rs deleted file mode 100644 index 391ce84cb5..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/mock_data/grid_mock_data.rs +++ /dev/null @@ -1,210 +0,0 @@ -// #![allow(clippy::all)] -// #![allow(dead_code)] -// #![allow(unused_imports)] -use crate::database::block_test::util::DatabaseRowTestBuilder; -use crate::database::mock_data::{ - COMPLETED, FACEBOOK, FIRST_THING, GOOGLE, PAUSED, PLANNED, SECOND_THING, THIRD_THING, TWITTER, -}; - -use flowy_client_sync::client_database::DatabaseBuilder; -use flowy_database::entities::*; - -use flowy_database::services::field::SelectOptionPB; -use flowy_database::services::field::*; - -use database_model::*; - -use strum::IntoEnumIterator; - -pub fn make_test_grid() -> BuildDatabaseContext { - let mut database_builder = DatabaseBuilder::new(); - // Iterate through the FieldType to create the corresponding Field. - for field_type in FieldType::iter() { - let field_type: FieldType = field_type; - - // The - match field_type { - FieldType::RichText => { - let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Name") - .visibility(true) - .primary(true) - .build(); - database_builder.add_field(text_field); - }, - FieldType::Number => { - // Number - let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); - let number_field = FieldBuilder::new(number) - .name("Price") - .visibility(true) - .build(); - database_builder.add_field(number_field); - }, - FieldType::DateTime => { - // Date - let date = DateTypeOptionBuilder::default() - .date_format(DateFormat::US) - .time_format(TimeFormat::TwentyFourHour); - let date_field = FieldBuilder::new(date) - .name("Time") - .visibility(true) - .build(); - database_builder.add_field(date_field); - }, - FieldType::SingleSelect => { - // Single Select - let single_select = SingleSelectTypeOptionBuilder::default() - .add_option(SelectOptionPB::new(COMPLETED)) - .add_option(SelectOptionPB::new(PLANNED)) - .add_option(SelectOptionPB::new(PAUSED)); - let single_select_field = FieldBuilder::new(single_select) - .name("Status") - .visibility(true) - .build(); - database_builder.add_field(single_select_field); - }, - FieldType::MultiSelect => { - // MultiSelect - let multi_select = MultiSelectTypeOptionBuilder::default() - .add_option(SelectOptionPB::new(GOOGLE)) - .add_option(SelectOptionPB::new(FACEBOOK)) - .add_option(SelectOptionPB::new(TWITTER)); - let multi_select_field = FieldBuilder::new(multi_select) - .name("Platform") - .visibility(true) - .build(); - database_builder.add_field(multi_select_field); - }, - FieldType::Checkbox => { - // Checkbox - let checkbox = CheckboxTypeOptionBuilder::default(); - let checkbox_field = FieldBuilder::new(checkbox) - .name("is urgent") - .visibility(true) - .build(); - database_builder.add_field(checkbox_field); - }, - FieldType::URL => { - // URL - let url = URLTypeOptionBuilder::default(); - let url_field = FieldBuilder::new(url).name("link").visibility(true).build(); - database_builder.add_field(url_field); - }, - FieldType::Checklist => { - let checklist = ChecklistTypeOptionBuilder::default() - .add_option(SelectOptionPB::new(FIRST_THING)) - .add_option(SelectOptionPB::new(SECOND_THING)) - .add_option(SelectOptionPB::new(THIRD_THING)); - let checklist_field = FieldBuilder::new(checklist) - .name("TODO") - .visibility(true) - .build(); - database_builder.add_field(checklist_field); - }, - } - } - - for i in 0..6 { - let block_id = database_builder.block_id().to_owned(); - let field_revs = database_builder.field_revs().clone(); - let mut row_builder = DatabaseRowTestBuilder::new(block_id, field_revs); - match i { - 0 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("A"), - FieldType::Number => row_builder.insert_number_cell("1"), - FieldType::DateTime => row_builder.insert_date_cell("1647251762"), - FieldType::MultiSelect => row_builder - .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]), - FieldType::Checklist => row_builder.insert_checklist_cell(|options| options), - FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), - FieldType::URL => { - row_builder.insert_url_cell("AppFlowy website - https://www.appflowy.io") - }, - _ => "".to_owned(), - }; - } - }, - 1 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell(""), - FieldType::Number => row_builder.insert_number_cell("2"), - FieldType::DateTime => row_builder.insert_date_cell("1647251762"), - FieldType::MultiSelect => row_builder - .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(1)]), - FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), - _ => "".to_owned(), - }; - } - }, - 2 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("C"), - FieldType::Number => row_builder.insert_number_cell("3"), - FieldType::DateTime => row_builder.insert_date_cell("1647251762"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(0)) - }, - FieldType::MultiSelect => { - row_builder.insert_multi_select_cell(|mut options| vec![options.remove(1)]) - }, - FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), - _ => "".to_owned(), - }; - } - }, - 3 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("DA"), - FieldType::Number => row_builder.insert_number_cell("14"), - FieldType::DateTime => row_builder.insert_date_cell("1668704685"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(0)) - }, - FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), - _ => "".to_owned(), - }; - } - }, - 4 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("AE"), - FieldType::Number => row_builder.insert_number_cell(""), - FieldType::DateTime => row_builder.insert_date_cell("1668359085"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(1)) - }, - - FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), - _ => "".to_owned(), - }; - } - }, - 5 => { - for field_type in FieldType::iter() { - match field_type { - FieldType::RichText => row_builder.insert_text_cell("AE"), - FieldType::Number => row_builder.insert_number_cell("5"), - FieldType::DateTime => row_builder.insert_date_cell("1671938394"), - FieldType::SingleSelect => { - row_builder.insert_single_select_cell(|mut options| options.remove(1)) - }, - FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), - _ => "".to_owned(), - }; - } - }, - _ => {}, - } - - let row_rev = row_builder.build(); - database_builder.add_row(row_rev); - } - database_builder.build() -} diff --git a/frontend/rust-lib/flowy-database/tests/database/mock_data/mod.rs b/frontend/rust-lib/flowy-database/tests/database/mock_data/mod.rs deleted file mode 100644 index 6b47039952..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/mock_data/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -mod board_mock_data; -mod calendar_mock_data; -mod grid_mock_data; - -pub use board_mock_data::*; -pub use calendar_mock_data::*; -pub use grid_mock_data::*; - -pub const GOOGLE: &str = "Google"; -pub const FACEBOOK: &str = "Facebook"; -pub const TWITTER: &str = "Twitter"; - -pub const COMPLETED: &str = "Completed"; -pub const PLANNED: &str = "Planned"; -pub const PAUSED: &str = "Paused"; - -pub const FIRST_THING: &str = "Wake up at 6:00 am"; -pub const SECOND_THING: &str = "Get some coffee"; -pub const THIRD_THING: &str = "Start working"; diff --git a/frontend/rust-lib/flowy-database/tests/database/mod.rs b/frontend/rust-lib/flowy-database/tests/database/mod.rs deleted file mode 100644 index 0cb845d78c..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -mod block_test; -mod cell_test; -mod database_editor; -mod database_ref_test; -mod field_test; -mod filter_test; -mod group_test; -mod layout_test; -mod snapshot_test; -mod sort_test; - -mod mock_data; diff --git a/frontend/rust-lib/flowy-database/tests/database/script.rs b/frontend/rust-lib/flowy-database/tests/database/script.rs deleted file mode 100644 index 69511759c0..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/script.rs +++ /dev/null @@ -1,425 +0,0 @@ -use bytes::Bytes; -use database_model::entities::{ - BuildGridContext, CellChangeset, Field, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, - GridBlockInfoChangeset, GridBlockMetaSnapshot, InsertFieldParams, RowMeta, RowMetaChangeset, - RowOrder, TypeOptionDataFormat, -}; -use flowy_client_sync::client_grid::GridBuilder; -use flowy_database::services::field::*; -use flowy_database::services::grid_meta_editor::{GridMetaEditor, GridPadBuilder}; -use flowy_database::services::row::CreateRowMetaPayload; -use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS; -use flowy_test::helper::ViewTest; -use flowy_test::FlowySDKTest; -use std::collections::HashMap; -use std::sync::Arc; -use std::time::Duration; -use strum::EnumCount; -use tokio::time::sleep; - -pub enum EditorScript { - CreateField { - params: InsertFieldParams, - }, - UpdateField { - changeset: FieldChangesetParams, - }, - DeleteField { - field_meta: FieldMeta, - }, - AssertFieldCount(usize), - AssertFieldEqual { - field_index: usize, - field_meta: FieldMeta, - }, - CreateBlock { - block: GridBlockMetaSnapshot, - }, - UpdateBlock { - changeset: GridBlockInfoChangeset, - }, - AssertBlockCount(usize), - AssertBlock { - block_index: usize, - row_count: i32, - start_row_index: i32, - }, - AssertBlockEqual { - block_index: usize, - block: GridBlockMetaSnapshot, - }, - CreateEmptyRow, - CreateRow { - context: CreateRowMetaPayload, - }, - UpdateRow { - changeset: RowMetaChangeset, - }, - AssertRow { - changeset: RowMetaChangeset, - }, - DeleteRow { - row_ids: Vec, - }, - UpdateCell { - changeset: CellChangeset, - is_err: bool, - }, - AssertRowCount(usize), - // AssertRowEqual{ row_index: usize, row: RowMeta}, - AssertGridMetaPad, -} - -pub struct GridEditorTest { - pub sdk: FlowySDKTest, - pub grid_id: String, - pub editor: Arc, - pub field_metas: Vec, - pub grid_blocks: Vec, - pub row_metas: Vec>, - pub field_count: usize, - - pub row_order_by_row_id: HashMap, -} - -impl GridEditorTest { - pub async fn new() -> Self { - let sdk = FlowySDKTest::default(); - let _ = sdk.init_user().await; - let build_context = make_template_1_grid(); - let view_data: Bytes = build_context.into(); - let test = ViewTest::new_grid_view(&sdk, view_data.to_vec()).await; - let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap(); - let field_metas = editor.get_field_metas::(None).await.unwrap(); - let grid_blocks = editor.get_block_metas().await.unwrap(); - let row_metas = get_row_metas(&editor).await; - - let grid_id = test.view.id; - Self { - sdk, - grid_id, - editor, - field_metas, - grid_blocks, - row_metas, - field_count: FieldType::COUNT, - row_order_by_row_id: HashMap::default(), - } - } - - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - pub async fn run_script(&mut self, script: EditorScript) { - let grid_manager = self.sdk.grid_manager.clone(); - let pool = self.sdk.user_session.db_pool().unwrap(); - let rev_manager = self.editor.rev_manager(); - let _cache = rev_manager.revision_cache().await; - - match script { - EditorScript::CreateField { params } => { - if !self.editor.contain_field(¶ms.field.id).await { - self.field_count += 1; - } - - self.editor.insert_field(params).await.unwrap(); - self.field_metas = self - .editor - .get_field_metas::(None) - .await - .unwrap(); - assert_eq!(self.field_count, self.field_metas.len()); - }, - EditorScript::UpdateField { changeset: change } => { - self.editor.update_field(change).await.unwrap(); - self.field_metas = self - .editor - .get_field_metas::(None) - .await - .unwrap(); - }, - EditorScript::DeleteField { field_meta } => { - if self.editor.contain_field(&field_meta.id).await { - self.field_count -= 1; - } - - self.editor.delete_field(&field_meta.id).await.unwrap(); - self.field_metas = self - .editor - .get_field_metas::(None) - .await - .unwrap(); - assert_eq!(self.field_count, self.field_metas.len()); - }, - EditorScript::AssertFieldCount(count) => { - assert_eq!( - self - .editor - .get_field_metas::(None) - .await - .unwrap() - .len(), - count - ); - }, - EditorScript::AssertFieldEqual { - field_index, - field_meta, - } => { - let field_metas = self - .editor - .get_field_metas::(None) - .await - .unwrap(); - assert_eq!(field_metas[field_index].clone(), field_meta); - }, - EditorScript::CreateBlock { block } => { - self.editor.create_block(block).await.unwrap(); - self.grid_blocks = self.editor.get_block_metas().await.unwrap(); - }, - EditorScript::UpdateBlock { changeset: change } => { - self.editor.update_block(change).await.unwrap(); - }, - EditorScript::AssertBlockCount(count) => { - assert_eq!(self.editor.get_block_metas().await.unwrap().len(), count); - }, - EditorScript::AssertBlock { - block_index, - row_count, - start_row_index, - } => { - assert_eq!(self.grid_blocks[block_index].row_count, row_count); - assert_eq!( - self.grid_blocks[block_index].start_row_index, - start_row_index - ); - }, - EditorScript::AssertBlockEqual { block_index, block } => { - let blocks = self.editor.get_block_metas().await.unwrap(); - let compared_block = blocks[block_index].clone(); - assert_eq!(compared_block, block); - }, - EditorScript::CreateEmptyRow => { - let row_order = self.editor.create_row(None).await.unwrap(); - self - .row_order_by_row_id - .insert(row_order.row_id.clone(), row_order); - self.row_metas = self.get_row_metas().await; - self.grid_blocks = self.editor.get_block_metas().await.unwrap(); - }, - EditorScript::CreateRow { context } => { - let row_orders = self.editor.insert_rows(vec![context]).await.unwrap(); - for row_order in row_orders { - self - .row_order_by_row_id - .insert(row_order.row_id.clone(), row_order); - } - self.row_metas = self.get_row_metas().await; - self.grid_blocks = self.editor.get_block_metas().await.unwrap(); - }, - EditorScript::UpdateRow { changeset: change } => { - self.editor.update_row(change).await.unwrap() - }, - EditorScript::DeleteRow { row_ids } => { - let row_orders = row_ids - .into_iter() - .map(|row_id| self.row_order_by_row_id.get(&row_id).unwrap().clone()) - .collect::>(); - - self.editor.delete_rows(row_orders).await.unwrap(); - self.row_metas = self.get_row_metas().await; - self.grid_blocks = self.editor.get_block_metas().await.unwrap(); - }, - EditorScript::AssertRow { changeset } => { - let row = self - .row_metas - .iter() - .find(|row| row.id == changeset.row_id) - .unwrap(); - - if let Some(visibility) = changeset.visibility { - assert_eq!(row.visibility, visibility); - } - - if let Some(height) = changeset.height { - assert_eq!(row.height, height); - } - }, - EditorScript::UpdateCell { changeset, is_err } => { - let result = self.editor.update_cell(changeset).await; - if is_err { - assert!(result.is_err()) - } else { - let _ = result.unwrap(); - self.row_metas = self.get_row_metas().await; - } - }, - EditorScript::AssertRowCount(count) => { - assert_eq!(self.row_metas.len(), count); - }, - EditorScript::AssertGridMetaPad => { - sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await; - let mut grid_rev_manager = grid_manager - .make_grid_rev_manager(&self.grid_id, pool.clone()) - .unwrap(); - let grid_pad = grid_rev_manager.load::(None).await.unwrap(); - println!("{}", grid_pad.delta_str()); - }, - } - } - - async fn get_row_metas(&self) -> Vec> { - get_row_metas(&self.editor).await - } -} - -async fn get_row_metas(editor: &Arc) -> Vec> { - editor - .grid_block_snapshots(None) - .await - .unwrap() - .pop() - .unwrap() - .row_metas -} - -pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldMeta) { - let field_meta = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Name") - .visibility(true) - .build(); - - let cloned_field_meta = field_meta.clone(); - - let type_option_data = field_meta - .get_type_option_entry::(&field_meta.field_type) - .unwrap() - .protobuf_bytes() - .to_vec(); - - let field = Field { - id: field_meta.id, - name: field_meta.name, - desc: field_meta.desc, - field_type: field_meta.field_type, - frozen: field_meta.frozen, - visibility: field_meta.visibility, - width: field_meta.width, - is_primary: false, - }; - - let params = InsertFieldParams { - grid_id: grid_id.to_owned(), - field, - type_option_data, - start_field_id: None, - }; - (params, cloned_field_meta) -} - -pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldMeta) { - let single_select = SingleSelectTypeOptionBuilder::default() - .option(SelectOption::new("Done")) - .option(SelectOption::new("Progress")); - - let field_meta = FieldBuilder::new(single_select) - .name("Name") - .visibility(true) - .build(); - let cloned_field_meta = field_meta.clone(); - let type_option_data = field_meta - .get_type_option_entry::(&field_meta.field_type) - .unwrap() - .protobuf_bytes() - .to_vec(); - - let field = Field { - id: field_meta.id, - name: field_meta.name, - desc: field_meta.desc, - field_type: field_meta.field_type, - frozen: field_meta.frozen, - visibility: field_meta.visibility, - width: field_meta.width, - is_primary: false, - }; - - let params = InsertFieldParams { - grid_id: grid_id.to_owned(), - field, - type_option_data, - start_field_id: None, - }; - (params, cloned_field_meta) -} - -fn make_template_1_grid() -> BuildGridContext { - let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Name") - .visibility(true) - .build(); - - // Single Select - let single_select = SingleSelectTypeOptionBuilder::default() - .option(SelectOption::new("Live")) - .option(SelectOption::new("Completed")) - .option(SelectOption::new("Planned")) - .option(SelectOption::new("Paused")); - let single_select_field = FieldBuilder::new(single_select) - .name("Status") - .visibility(true) - .build(); - - // MultiSelect - let multi_select = MultiSelectTypeOptionBuilder::default() - .option(SelectOption::new("Google")) - .option(SelectOption::new("Facebook")) - .option(SelectOption::new("Twitter")); - let multi_select_field = FieldBuilder::new(multi_select) - .name("Platform") - .visibility(true) - .build(); - - // Number - let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); - let number_field = FieldBuilder::new(number) - .name("Price") - .visibility(true) - .build(); - - // Date - let date = DateTypeOptionBuilder::default() - .date_format(DateFormat::US) - .time_format(TimeFormat::TwentyFourHour); - let date_field = FieldBuilder::new(date) - .name("Time") - .visibility(true) - .build(); - - // Checkbox - let checkbox = CheckboxTypeOptionBuilder::default(); - let checkbox_field = FieldBuilder::new(checkbox) - .name("is done") - .visibility(true) - .build(); - - // URL - let url = URLTypeOptionBuilder::default(); - let url_field = FieldBuilder::new(url).name("link").visibility(true).build(); - - GridBuilder::default() - .add_field(text_field) - .add_field(single_select_field) - .add_field(multi_select_field) - .add_field(number_field) - .add_field(date_field) - .add_field(checkbox_field) - .add_field(url_field) - .add_empty_row() - .add_empty_row() - .add_empty_row() - .build() -} diff --git a/frontend/rust-lib/flowy-database/tests/database/snapshot_test/mod.rs b/frontend/rust-lib/flowy-database/tests/database/snapshot_test/mod.rs deleted file mode 100644 index 63d424afaf..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/snapshot_test/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod script; -mod test; diff --git a/frontend/rust-lib/flowy-database/tests/database/snapshot_test/script.rs b/frontend/rust-lib/flowy-database/tests/database/snapshot_test/script.rs deleted file mode 100644 index 096d968ab5..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/snapshot_test/script.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::database::database_editor::DatabaseEditorTest; - -use database_model::FieldRevision; -use flowy_client_sync::client_database::{DatabaseOperations, DatabaseRevisionPad}; -use flowy_revision::{RevisionSnapshotData, REVISION_WRITE_INTERVAL_IN_MILLIS}; -use revision_model::Revision; -use std::time::Duration; -use tokio::time::sleep; - -pub enum SnapshotScript { - WriteSnapshot, - #[allow(dead_code)] - AssertSnapshot { - rev_id: i64, - expected: Option, - }, - AssertSnapshotContent { - snapshot: RevisionSnapshotData, - expected: String, - }, - CreateField { - field_rev: FieldRevision, - }, - DeleteField { - field_rev: FieldRevision, - }, -} - -pub struct DatabaseSnapshotTest { - inner: DatabaseEditorTest, - pub current_snapshot: Option, - pub current_revision: Option, -} - -impl DatabaseSnapshotTest { - pub async fn new() -> Self { - let editor_test = DatabaseEditorTest::new_grid().await; - Self { - inner: editor_test, - current_snapshot: None, - current_revision: None, - } - } - - pub fn grid_id(&self) -> String { - self.view_id.clone() - } - - pub async fn grid_pad(&self) -> DatabaseRevisionPad { - self.editor.database_pad().read().await.clone() - } - - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - pub async fn get_latest_snapshot(&self) -> Option { - self.editor.rev_manager().read_snapshot(None).await.unwrap() - } - - pub async fn run_script(&mut self, script: SnapshotScript) { - let rev_manager = self.editor.rev_manager(); - match script { - SnapshotScript::WriteSnapshot => { - sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await; - rev_manager.generate_snapshot().await; - self.current_snapshot = rev_manager.read_snapshot(None).await.unwrap(); - }, - SnapshotScript::AssertSnapshot { rev_id, expected } => { - let snapshot = rev_manager.read_snapshot(Some(rev_id)).await.unwrap(); - assert_eq!(snapshot, expected); - }, - SnapshotScript::AssertSnapshotContent { snapshot, expected } => { - let operations = DatabaseOperations::from_bytes(snapshot.data).unwrap(); - let pad = DatabaseRevisionPad::from_operations(operations).unwrap(); - assert_eq!(pad.json_str().unwrap(), expected); - }, - SnapshotScript::CreateField { field_rev } => { - self.editor.create_new_field_rev(field_rev).await.unwrap(); - let current_rev_id = rev_manager.rev_id(); - self.current_revision = rev_manager.get_revision(current_rev_id).await; - }, - SnapshotScript::DeleteField { field_rev } => { - self.editor.delete_field(&field_rev.id).await.unwrap(); - }, - } - } -} -impl std::ops::Deref for DatabaseSnapshotTest { - type Target = DatabaseEditorTest; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl std::ops::DerefMut for DatabaseSnapshotTest { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} diff --git a/frontend/rust-lib/flowy-database/tests/database/snapshot_test/test.rs b/frontend/rust-lib/flowy-database/tests/database/snapshot_test/test.rs deleted file mode 100644 index cf8bb512ec..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/snapshot_test/test.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::database::field_test::util::create_text_field; -use crate::database::snapshot_test::script::{DatabaseSnapshotTest, SnapshotScript::*}; - -#[tokio::test] -async fn snapshot_create_test() { - let mut test = DatabaseSnapshotTest::new().await; - let (_, field_rev) = create_text_field(&test.grid_id()); - let scripts = vec![CreateField { field_rev }, WriteSnapshot]; - test.run_scripts(scripts).await; - - let snapshot = test.current_snapshot.clone().unwrap(); - let content = test.grid_pad().await.json_str().unwrap(); - test - .run_scripts(vec![AssertSnapshotContent { - snapshot, - expected: content, - }]) - .await; -} - -#[tokio::test] -async fn snapshot_multi_version_test() { - let mut test = DatabaseSnapshotTest::new().await; - let original_content = test.grid_pad().await.json_str().unwrap(); - - // Create a field - let (_, field_rev) = create_text_field(&test.grid_id()); - let scripts = vec![ - CreateField { - field_rev: field_rev.clone(), - }, - WriteSnapshot, - ]; - test.run_scripts(scripts).await; - - // Delete a field - let scripts = vec![DeleteField { field_rev }, WriteSnapshot]; - test.run_scripts(scripts).await; - - // The latest snapshot will be the same as the original content. - test - .run_scripts(vec![AssertSnapshotContent { - snapshot: test.get_latest_snapshot().await.unwrap(), - expected: original_content, - }]) - .await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/sort_test/checkbox_and_text_test.rs b/frontend/rust-lib/flowy-database/tests/database/sort_test/checkbox_and_text_test.rs deleted file mode 100644 index 896c7aa5c8..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/sort_test/checkbox_and_text_test.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::database::sort_test::script::{DatabaseSortTest, SortScript::*}; -use database_model::SortCondition; -use flowy_database::entities::FieldType; - -#[tokio::test] -async fn sort_checkbox_and_then_text_by_descending_test() { - let mut test = DatabaseSortTest::new().await; - let checkbox_field = test.get_first_field_rev(FieldType::Checkbox); - let text_field = test.get_first_field_rev(FieldType::RichText); - let scripts = vec![ - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"], - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["A", "", "C", "DA", "AE", "AE"], - }, - // Insert checkbox sort - InsertSort { - field_rev: checkbox_field.clone(), - condition: SortCondition::Descending, - }, - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"], - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["A", "", "AE", "C", "DA", "AE"], - }, - // Insert text sort. After inserting the text sort, the order of the rows - // will be changed. - // before: ["A", "", "AE", "C", "DA", "AE"] - // after: ["", "A", "AE", "AE", "C", "DA"] - InsertSort { - field_rev: text_field.clone(), - condition: SortCondition::Ascending, - }, - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"], - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["", "A", "AE", "AE", "C", "DA"], - }, - ]; - test.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/sort_test/mod.rs b/frontend/rust-lib/flowy-database/tests/database/sort_test/mod.rs deleted file mode 100644 index 69e0a622f8..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/sort_test/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod checkbox_and_text_test; -mod multi_sort_test; -mod script; -mod single_sort_test; diff --git a/frontend/rust-lib/flowy-database/tests/database/sort_test/multi_sort_test.rs b/frontend/rust-lib/flowy-database/tests/database/sort_test/multi_sort_test.rs deleted file mode 100644 index b2677d4dac..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/sort_test/multi_sort_test.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::database::sort_test::script::DatabaseSortTest; -use crate::database::sort_test::script::SortScript::*; -use database_model::SortCondition; -use flowy_database::entities::FieldType; - -#[tokio::test] -async fn sort_text_with_checkbox_by_ascending_test() { - let mut test = DatabaseSortTest::new().await; - let text_field = test.get_first_field_rev(FieldType::RichText).clone(); - let checkbox_field = test.get_first_field_rev(FieldType::Checkbox).clone(); - let scripts = vec![ - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["A", "", "C", "DA", "AE", "AE"], - }, - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "No", "No", "No"], - }, - InsertSort { - field_rev: text_field.clone(), - condition: SortCondition::Ascending, - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["", "A", "AE", "AE", "C", "DA"], - }, - ]; - test.run_scripts(scripts).await; - - let scripts = vec![ - InsertSort { - field_rev: checkbox_field.clone(), - condition: SortCondition::Descending, - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["", "A", "AE", "AE", "C", "DA"], - }, - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "Yes", "No", "No"], - }, - ]; - test.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-database/tests/database/sort_test/script.rs b/frontend/rust-lib/flowy-database/tests/database/sort_test/script.rs deleted file mode 100644 index 93d4e77bd9..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/sort_test/script.rs +++ /dev/null @@ -1,196 +0,0 @@ -use crate::database::database_editor::DatabaseEditorTest; -use async_stream::stream; -use database_model::{FieldRevision, SortCondition, SortRevision}; -use flowy_database::entities::{AlterSortParams, CellIdParams, DeleteSortParams}; -use flowy_database::services::database_view::DatabaseViewChanged; -use flowy_database::services::sort::SortType; -use futures::stream::StreamExt; -use std::cmp::min; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::broadcast::Receiver; - -pub enum SortScript { - InsertSort { - field_rev: Arc, - condition: SortCondition, - }, - DeleteSort { - field_rev: Arc, - sort_id: String, - }, - AssertCellContentOrder { - field_id: String, - orders: Vec<&'static str>, - }, - UpdateTextCell { - row_id: String, - text: String, - }, - AssertSortChanged { - old_row_orders: Vec<&'static str>, - new_row_orders: Vec<&'static str>, - }, - Wait { - millis: u64, - }, -} - -pub struct DatabaseSortTest { - inner: DatabaseEditorTest, - pub current_sort_rev: Option, - recv: Option>, -} - -impl DatabaseSortTest { - pub async fn new() -> Self { - let editor_test = DatabaseEditorTest::new_grid().await; - Self { - inner: editor_test, - current_sort_rev: None, - recv: None, - } - } - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - pub async fn run_script(&mut self, script: SortScript) { - match script { - SortScript::InsertSort { - condition, - field_rev, - } => { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id) - .await - .unwrap(), - ); - let params = AlterSortParams { - view_id: self.view_id.clone(), - field_id: field_rev.id.clone(), - sort_id: None, - field_type: field_rev.ty, - condition: condition.into(), - }; - let sort_rev = self.editor.create_or_update_sort(params).await.unwrap(); - self.current_sort_rev = Some(sort_rev); - }, - SortScript::DeleteSort { field_rev, sort_id } => { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id) - .await - .unwrap(), - ); - let params = DeleteSortParams { - view_id: self.view_id.clone(), - sort_type: SortType::from(&field_rev), - sort_id, - }; - self.editor.delete_sort(params).await.unwrap(); - self.current_sort_rev = None; - }, - SortScript::AssertCellContentOrder { field_id, orders } => { - let mut cells = vec![]; - let rows = self.editor.get_database(&self.view_id).await.unwrap().rows; - for row in rows { - let params = CellIdParams { - view_id: self.view_id.clone(), - field_id: field_id.clone(), - row_id: row.id, - }; - let cell = self.editor.get_cell_display_str(¶ms).await; - cells.push(cell); - } - if orders.is_empty() { - assert_eq!(cells, orders); - } else { - let len = min(cells.len(), orders.len()); - assert_eq!(cells.split_at(len).0, orders); - } - }, - SortScript::UpdateTextCell { row_id, text } => { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id) - .await - .unwrap(), - ); - self.update_text_cell(row_id, &text).await; - }, - SortScript::AssertSortChanged { - new_row_orders, - old_row_orders, - } => { - if let Some(receiver) = self.recv.take() { - assert_sort_changed( - receiver, - new_row_orders - .into_iter() - .map(|order| order.to_owned()) - .collect(), - old_row_orders - .into_iter() - .map(|order| order.to_owned()) - .collect(), - ) - .await; - } - }, - SortScript::Wait { millis } => { - tokio::time::sleep(Duration::from_millis(millis)).await; - }, - } - } -} - -async fn assert_sort_changed( - mut receiver: Receiver, - new_row_orders: Vec, - old_row_orders: Vec, -) { - let stream = stream! { - loop { - tokio::select! { - changed = receiver.recv() => yield changed.unwrap(), - _ = tokio::time::sleep(Duration::from_secs(2)) => break, - }; - } - }; - - stream - .for_each(|changed| async { - match changed { - DatabaseViewChanged::ReorderAllRowsNotification(_changed) => {}, - DatabaseViewChanged::ReorderSingleRowNotification(changed) => { - let mut old_row_orders = old_row_orders.clone(); - let old = old_row_orders.remove(changed.old_index); - old_row_orders.insert(changed.new_index, old); - assert_eq!(old_row_orders, new_row_orders); - }, - _ => {}, - } - }) - .await; -} - -impl std::ops::Deref for DatabaseSortTest { - type Target = DatabaseEditorTest; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl std::ops::DerefMut for DatabaseSortTest { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} diff --git a/frontend/rust-lib/flowy-database/tests/database/sort_test/single_sort_test.rs b/frontend/rust-lib/flowy-database/tests/database/sort_test/single_sort_test.rs deleted file mode 100644 index f7179b8e7a..0000000000 --- a/frontend/rust-lib/flowy-database/tests/database/sort_test/single_sort_test.rs +++ /dev/null @@ -1,269 +0,0 @@ -use crate::database::sort_test::script::{DatabaseSortTest, SortScript::*}; -use database_model::SortCondition; -use flowy_database::entities::FieldType; - -#[tokio::test] -async fn sort_text_by_ascending_test() { - let mut test = DatabaseSortTest::new().await; - let text_field = test.get_first_field_rev(FieldType::RichText); - let scripts = vec![ - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["A", "", "C", "DA", "AE", "AE"], - }, - InsertSort { - field_rev: text_field.clone(), - condition: SortCondition::Ascending, - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["", "A", "AE", "AE", "C", "DA"], - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn sort_change_notification_by_update_text_test() { - let mut test = DatabaseSortTest::new().await; - let text_field = test.get_first_field_rev(FieldType::RichText).clone(); - let scripts = vec![ - InsertSort { - field_rev: text_field.clone(), - condition: SortCondition::Ascending, - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["", "A", "AE", "AE", "C", "DA"], - }, - // Wait the insert task to finish. The cost of time should be less than 200 milliseconds. - Wait { millis: 200 }, - ]; - test.run_scripts(scripts).await; - - let row_revs = test.get_row_revs().await; - let scripts = vec![ - UpdateTextCell { - row_id: row_revs[2].id.clone(), - text: "E".to_string(), - }, - AssertSortChanged { - old_row_orders: vec!["", "A", "E", "AE", "C", "DA"], - new_row_orders: vec!["", "A", "AE", "C", "DA", "E"], - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn sort_text_by_ascending_and_delete_sort_test() { - let mut test = DatabaseSortTest::new().await; - let text_field = test.get_first_field_rev(FieldType::RichText).clone(); - let scripts = vec![InsertSort { - field_rev: text_field.clone(), - condition: SortCondition::Ascending, - }]; - test.run_scripts(scripts).await; - let sort_rev = test.current_sort_rev.as_ref().unwrap(); - let scripts = vec![ - DeleteSort { - field_rev: text_field.clone(), - sort_id: sort_rev.id.clone(), - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["A", "", "C", "DA", "AE"], - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn sort_text_by_descending_test() { - let mut test = DatabaseSortTest::new().await; - let text_field = test.get_first_field_rev(FieldType::RichText); - let scripts = vec![ - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["A", "", "C", "DA", "AE", "AE"], - }, - InsertSort { - field_rev: text_field.clone(), - condition: SortCondition::Descending, - }, - AssertCellContentOrder { - field_id: text_field.id.clone(), - orders: vec!["DA", "C", "AE", "AE", "A", ""], - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn sort_checkbox_by_ascending_test() { - let mut test = DatabaseSortTest::new().await; - let checkbox_field = test.get_first_field_rev(FieldType::Checkbox); - let scripts = vec![ - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "No", "No", "No"], - }, - InsertSort { - field_rev: checkbox_field.clone(), - condition: SortCondition::Ascending, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn sort_checkbox_by_descending_test() { - let mut test = DatabaseSortTest::new().await; - let checkbox_field = test.get_first_field_rev(FieldType::Checkbox); - let scripts = vec![ - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"], - }, - InsertSort { - field_rev: checkbox_field.clone(), - condition: SortCondition::Descending, - }, - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"], - }, - ]; - test.run_scripts(scripts).await; -} - -// #[tokio::test] -// async fn sort_date_by_ascending_test() { -// let mut test = DatabaseSortTest::new().await; -// let date_field = test.get_first_field_rev(FieldType::DateTime); -// let scripts = vec![ -// AssertCellContentOrder { -// field_id: date_field.id.clone(), -// orders: vec![ -// "2022/03/14", -// "2022/03/14", -// "2022/03/14", -// "2022/11/17", -// "2022/11/13", -// ], -// }, -// InsertSort { -// field_rev: date_field.clone(), -// condition: SortCondition::Ascending, -// }, -// AssertCellContentOrder { -// field_id: date_field.id.clone(), -// orders: vec![ -// "2022/03/14", -// "2022/03/14", -// "2022/03/14", -// "2022/11/13", -// "2022/11/17", -// ], -// }, -// ]; -// test.run_scripts(scripts).await; -// } - -// #[tokio::test] -// async fn sort_date_by_descending_test() { -// let mut test = DatabaseSortTest::new().await; -// let date_field = test.get_first_field_rev(FieldType::DateTime); -// let scripts = vec![ -// AssertCellContentOrder { -// field_id: date_field.id.clone(), -// orders: vec![ -// "2022/03/14", -// "2022/03/14", -// "2022/03/14", -// "2022/11/17", -// "2022/11/13", -// "2022/12/25", -// ], -// }, -// InsertSort { -// field_rev: date_field.clone(), -// condition: SortCondition::Descending, -// }, -// AssertCellContentOrder { -// field_id: date_field.id.clone(), -// orders: vec![ -// "2022/12/25", -// "2022/11/17", -// "2022/11/13", -// "2022/03/14", -// "2022/03/14", -// "2022/03/14", -// ], -// }, -// ]; -// test.run_scripts(scripts).await; -// } - -#[tokio::test] -async fn sort_number_by_descending_test() { - let mut test = DatabaseSortTest::new().await; - let number_field = test.get_first_field_rev(FieldType::Number); - let scripts = vec![ - AssertCellContentOrder { - field_id: number_field.id.clone(), - orders: vec!["$1", "$2", "$3", "$14", "", "$5"], - }, - InsertSort { - field_rev: number_field.clone(), - condition: SortCondition::Descending, - }, - AssertCellContentOrder { - field_id: number_field.id.clone(), - orders: vec!["$14", "$5", "$3", "$2", "$1", ""], - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn sort_single_select_by_descending_test() { - let mut test = DatabaseSortTest::new().await; - let single_select = test.get_first_field_rev(FieldType::SingleSelect); - let scripts = vec![ - AssertCellContentOrder { - field_id: single_select.id.clone(), - orders: vec!["", "", "Completed", "Completed", "Planned", "Planned"], - }, - InsertSort { - field_rev: single_select.clone(), - condition: SortCondition::Descending, - }, - AssertCellContentOrder { - field_id: single_select.id.clone(), - orders: vec!["Planned", "Planned", "Completed", "Completed", "", ""], - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn sort_multi_select_by_ascending_test() { - let mut test = DatabaseSortTest::new().await; - let multi_select = test.get_first_field_rev(FieldType::MultiSelect); - let scripts = vec![ - AssertCellContentOrder { - field_id: multi_select.id.clone(), - orders: vec!["Google,Facebook", "Google,Twitter", "Facebook", "", "", ""], - }, - InsertSort { - field_rev: multi_select.clone(), - condition: SortCondition::Ascending, - }, - AssertCellContentOrder { - field_id: multi_select.id.clone(), - orders: vec!["", "", "", "Facebook", "Google,Facebook", "Google,Twitter"], - }, - ]; - test.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-database/tests/main.rs b/frontend/rust-lib/flowy-database/tests/main.rs deleted file mode 100644 index d9afd095bf..0000000000 --- a/frontend/rust-lib/flowy-database/tests/main.rs +++ /dev/null @@ -1 +0,0 @@ -// mod database; diff --git a/frontend/rust-lib/flowy-database2/Cargo.toml b/frontend/rust-lib/flowy-database2/Cargo.toml index 987b8769f6..9e2395f95f 100644 --- a/frontend/rust-lib/flowy-database2/Cargo.toml +++ b/frontend/rust-lib/flowy-database2/Cargo.toml @@ -10,7 +10,7 @@ collab = { version = "0.1.0" } collab-database = { version = "0.1.0" } appflowy-integrate = {version = "0.1.0" } -flowy-derive = { path = "../flowy-derive" } +flowy-derive = { path = "../../../shared-lib/flowy-derive" } flowy-notification = { path = "../flowy-notification" } parking_lot = "0.12.1" protobuf = {version = "2.28.0"} @@ -20,7 +20,6 @@ tokio = { version = "1.26", features = ["sync"] } flowy-task= { path = "../flowy-task" } bytes = { version = "1.4" } tracing = { version = "0.1", features = ["log"] } -database-model = { path = "../../../shared-lib/database-model" } serde = { version = "1.0", features = ["derive"] } serde_json = {version = "1.0"} serde_repr = "0.1" @@ -48,7 +47,7 @@ strum_macros = "0.21" flowy-test = { path = "../flowy-test" } [build-dependencies] -flowy-codegen = { path = "../flowy-codegen"} +flowy-codegen = { path = "../../../shared-lib/flowy-codegen"} [features] diff --git a/frontend/rust-lib/flowy-database2/src/services/cell/type_cell_data.rs b/frontend/rust-lib/flowy-database2/src/services/cell/type_cell_data.rs index 45a7bede17..630c134cc5 100644 --- a/frontend/rust-lib/flowy-database2/src/services/cell/type_cell_data.rs +++ b/frontend/rust-lib/flowy-database2/src/services/cell/type_cell_data.rs @@ -1,9 +1,10 @@ -use crate::entities::FieldType; use bytes::Bytes; -use database_model::CellRevision; -use flowy_error::{internal_error, FlowyError, FlowyResult}; use serde::{Deserialize, Serialize}; +use flowy_error::{internal_error, FlowyError, FlowyResult}; + +use crate::entities::FieldType; + /// 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. @@ -55,22 +56,6 @@ impl ToString for TypeCellData { } } -impl std::convert::TryFrom<&CellRevision> for TypeCellData { - type Error = FlowyError; - - fn try_from(value: &CellRevision) -> Result { - Self::from_json_str(&value.type_cell_data) - } -} - -impl std::convert::TryFrom for TypeCellData { - type Error = FlowyError; - - fn try_from(value: CellRevision) -> Result { - Self::try_from(&value) - } -} - impl TypeCellData { pub fn new(cell_str: String, field_type: FieldType) -> Self { TypeCellData { diff --git a/frontend/rust-lib/flowy-document/Cargo.toml b/frontend/rust-lib/flowy-document/Cargo.toml deleted file mode 100644 index 2ddf6cb1fb..0000000000 --- a/frontend/rust-lib/flowy-document/Cargo.toml +++ /dev/null @@ -1,67 +0,0 @@ - -[package] -name = "flowy-document" -version = "0.1.0" -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -flowy-client-sync = { path = "../flowy-client-sync"} -revision-model = { path = "../../../shared-lib/revision-model"} -document-model = { path = "../../../shared-lib/document-model"} -ws-model = { path = "../../../shared-lib/ws-model"} -flowy-derive = { path = "../flowy-derive" } -lib-ot = { path = "../../../shared-lib/lib-ot" } -lib-ws = { path = "../../../shared-lib/lib-ws" } -lib-infra = { path = "../../../shared-lib/lib-infra" } - -lib-dispatch = { path = "../lib-dispatch" } -flowy-sqlite = { path = "../flowy-sqlite", optional = true } -flowy-revision = { path = "../flowy-revision" } -flowy-revision-persistence = { path = "../flowy-revision-persistence" } -flowy-error = { path = "../flowy-error", features = ["adaptor_sync", "adaptor_ot", "adaptor_serde", "adaptor_database", "adaptor_dispatch"] } -flowy-notification = { path = "../flowy-notification" } - -diesel = {version = "1.4.8", features = ["sqlite"]} -diesel_derives = {version = "1.4.1", features = ["sqlite"]} -protobuf = {version = "2.28.0"} -tokio = { version = "1.26", features = ["sync"]} -tracing = { version = "0.1", features = ["log"] } - -bytes = { version = "1.4" } -md5 = "0.7.0" -strum = "0.21" -strum_macros = "0.21" -dashmap = "5" -url = "2.3" -serde = { version = "1.0", features = ["derive"] } -serde_json = {version = "1.0"} -chrono = "0.4.23" -futures-util = "0.3.26" -async-stream = "0.3.4" -futures = "0.3.26" - -[dev-dependencies] -flowy-test = { path = "../flowy-test" } -flowy-document = { path = "../flowy-document", features = ["flowy_unit_test"]} -derive_more = {version = "0.99", features = ["display"]} -tracing-subscriber = "0.2.25" -unicode-segmentation = "1.10" - -color-eyre = { version = "0.5", default-features = false } -criterion = "0.3" -rand = "0.8.5" - -[build-dependencies] -flowy-codegen = { path = "../flowy-codegen"} - - -[features] -default = ["rev-sqlite"] -sync = [] -cloud_sync = ["sync"] -rev-sqlite = ["flowy-sqlite"] -flowy_unit_test = ["lib-ot/flowy_unit_test", "flowy-revision/flowy_unit_test"] -dart = ["flowy-codegen/dart", "flowy-notification/dart"] -ts = ["flowy-codegen/ts", "flowy-notification/ts"] diff --git a/frontend/rust-lib/flowy-document/Flowy.toml b/frontend/rust-lib/flowy-document/Flowy.toml deleted file mode 100644 index 0dbe74b3e3..0000000000 --- a/frontend/rust-lib/flowy-document/Flowy.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Check out the FlowyConfig (located in flowy_toml.rs) for more details. -proto_input = ["src/event_map.rs", "src/entities.rs"] -event_files = ["src/event_map.rs"] \ No newline at end of file diff --git a/frontend/rust-lib/flowy-document/build.rs b/frontend/rust-lib/flowy-document/build.rs deleted file mode 100644 index 06388d2a02..0000000000 --- a/frontend/rust-lib/flowy-document/build.rs +++ /dev/null @@ -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); -} diff --git a/frontend/rust-lib/flowy-document/src/editor/document.rs b/frontend/rust-lib/flowy-document/src/editor/document.rs deleted file mode 100644 index 8d6974fa37..0000000000 --- a/frontend/rust-lib/flowy-document/src/editor/document.rs +++ /dev/null @@ -1,123 +0,0 @@ -use bytes::Bytes; -use flowy_error::{FlowyError, FlowyResult}; -use flowy_revision::{RevisionMergeable, RevisionObjectDeserializer, RevisionObjectSerializer}; -use lib_ot::core::{ - Extension, NodeDataBuilder, NodeOperation, NodeTree, NodeTreeContext, Selection, Transaction, -}; -use lib_ot::text_delta::DeltaTextOperationBuilder; -use revision_model::Revision; - -#[derive(Debug)] -pub struct Document { - tree: NodeTree, -} - -impl Document { - pub fn new(tree: NodeTree) -> Self { - Self { tree } - } - - pub fn from_transaction(transaction: Transaction) -> FlowyResult { - let tree = NodeTree::from_operations(transaction.operations, make_tree_context())?; - Ok(Self { tree }) - } - - pub fn get_content(&self, pretty: bool) -> FlowyResult { - if pretty { - serde_json::to_string_pretty(self).map_err(|err| FlowyError::serde().context(err)) - } else { - serde_json::to_string(self).map_err(|err| FlowyError::serde().context(err)) - } - } - - pub fn document_md5(&self) -> String { - let bytes = self.tree.to_bytes(); - format!("{:x}", md5::compute(&bytes)) - } - - pub fn get_tree(&self) -> &NodeTree { - &self.tree - } -} - -pub(crate) fn make_tree_context() -> NodeTreeContext { - NodeTreeContext {} -} - -pub fn initial_document_content() -> String { - let delta = DeltaTextOperationBuilder::new().insert("").build(); - let node_data = NodeDataBuilder::new("text").insert_delta(delta).build(); - let editor_node = NodeDataBuilder::new("editor") - .add_node_data(node_data) - .build(); - let node_operation = NodeOperation::Insert { - path: vec![0].into(), - nodes: vec![editor_node], - }; - let extension = Extension::TextSelection { - before_selection: Selection::default(), - after_selection: Selection::default(), - }; - let transaction = Transaction { - operations: vec![node_operation].into(), - extension, - }; - transaction.to_json().unwrap() -} - -impl std::ops::Deref for Document { - type Target = NodeTree; - - fn deref(&self) -> &Self::Target { - &self.tree - } -} - -impl std::ops::DerefMut for Document { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.tree - } -} - -pub struct DocumentRevisionSerde(); -impl RevisionObjectDeserializer for DocumentRevisionSerde { - type Output = Document; - - fn deserialize_revisions( - _object_id: &str, - revisions: Vec, - ) -> FlowyResult { - let mut tree = NodeTree::new(make_tree_context()); - let transaction = make_transaction_from_revisions(&revisions)?; - tree.apply_transaction(transaction)?; - let document = Document::new(tree); - Result::::Ok(document) - } - - fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { - None - } -} - -impl RevisionObjectSerializer for DocumentRevisionSerde { - fn combine_revisions(revisions: Vec) -> FlowyResult { - let transaction = make_transaction_from_revisions(&revisions)?; - Ok(Bytes::from(transaction.to_bytes()?)) - } -} - -pub(crate) struct DocumentRevisionMergeable(); -impl RevisionMergeable for DocumentRevisionMergeable { - fn combine_revisions(&self, revisions: Vec) -> FlowyResult { - DocumentRevisionSerde::combine_revisions(revisions) - } -} - -#[tracing::instrument(level = "trace", skip_all, err)] -pub fn make_transaction_from_revisions(revisions: &[Revision]) -> FlowyResult { - let mut transaction = Transaction::new(); - for revision in revisions { - transaction.compose(Transaction::from_bytes(&revision.bytes)?)?; - } - Ok(transaction) -} diff --git a/frontend/rust-lib/flowy-document/src/editor/document_serde.rs b/frontend/rust-lib/flowy-document/src/editor/document_serde.rs deleted file mode 100644 index 76452deeea..0000000000 --- a/frontend/rust-lib/flowy-document/src/editor/document_serde.rs +++ /dev/null @@ -1,514 +0,0 @@ -use crate::editor::document::Document; -use bytes::Bytes; -use flowy_error::FlowyResult; -use lib_ot::core::{ - AttributeHashMap, Body, Changeset, Extension, NodeData, NodeId, NodeOperation, NodeTree, - NodeTreeContext, Path, Selection, Transaction, -}; -use lib_ot::text_delta::DeltaTextOperations; -use serde::de::{self, MapAccess, Unexpected, Visitor}; -use serde::ser::{SerializeMap, SerializeSeq}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::fmt; - -impl Serialize for Document { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut map = serializer.serialize_map(Some(1))?; - map.serialize_key("document")?; - map.serialize_value(&DocumentContentSerializer(self))?; - map.end() - } -} - -const FIELDS: &[&str] = &["Document"]; - -impl<'de> Deserialize<'de> for Document { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct DocumentVisitor(); - - impl<'de> Visitor<'de> for DocumentVisitor { - type Value = Document; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("Expect document tree") - } - - fn visit_map(self, mut map: M) -> Result - where - M: MapAccess<'de>, - { - let mut document_node = None; - while let Some(key) = map.next_key()? { - match key { - "document" => { - if document_node.is_some() { - return Err(de::Error::duplicate_field("document")); - } - document_node = Some(map.next_value::()?) - }, - s => { - return Err(de::Error::unknown_field(s, FIELDS)); - }, - } - } - - match document_node { - Some(document_node) => { - match NodeTree::from_node_data(document_node.into(), NodeTreeContext::default()) { - Ok(tree) => Ok(Document::new(tree)), - Err(err) => Err(de::Error::invalid_value( - Unexpected::Other(&format!("{}", err)), - &"", - )), - } - }, - None => Err(de::Error::missing_field("document")), - } - } - } - deserializer.deserialize_any(DocumentVisitor()) - } -} - -pub fn make_transaction_from_document_content(content: &str) -> FlowyResult { - let document_node: DocumentNode = - serde_json::from_str::(content)?.document; - let document_operation = DocumentOperation::Insert { - path: 0_usize.into(), - nodes: vec![document_node], - }; - let mut document_transaction = DocumentTransaction::default(); - document_transaction.operations.push(document_operation); - Ok(document_transaction.into()) -} - -pub struct DocumentContentSerde {} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct DocumentTransaction { - #[serde(default)] - operations: Vec, - - #[serde(default)] - before_selection: Selection, - - #[serde(default)] - after_selection: Selection, -} - -impl DocumentTransaction { - pub fn to_json(&self) -> FlowyResult { - let json = serde_json::to_string(self)?; - Ok(json) - } - - pub fn to_bytes(&self) -> FlowyResult { - let data = serde_json::to_vec(&self)?; - Ok(Bytes::from(data)) - } - - pub fn from_bytes(bytes: Bytes) -> FlowyResult { - let transaction = serde_json::from_slice(&bytes)?; - Ok(transaction) - } -} - -impl std::convert::From for DocumentTransaction { - fn from(transaction: Transaction) -> Self { - let (operations, extension) = transaction.split(); - let (before_selection, after_selection) = match extension { - Extension::Empty => (Selection::default(), Selection::default()), - Extension::TextSelection { - before_selection, - after_selection, - } => (before_selection, after_selection), - }; - - DocumentTransaction { - operations: operations - .into_iter() - .map(|operation| operation.as_ref().into()) - .collect(), - before_selection, - after_selection, - } - } -} - -impl std::convert::From for Transaction { - fn from(document_transaction: DocumentTransaction) -> Self { - let mut transaction = Transaction::new(); - for document_operation in document_transaction.operations { - transaction.push_operation(document_operation); - } - transaction.extension = Extension::TextSelection { - before_selection: document_transaction.before_selection, - after_selection: document_transaction.after_selection, - }; - transaction - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "op")] -pub enum DocumentOperation { - #[serde(rename = "insert")] - Insert { - path: Path, - nodes: Vec, - }, - - #[serde(rename = "delete")] - Delete { - path: Path, - nodes: Vec, - }, - - #[serde(rename = "update")] - Update { - path: Path, - attributes: AttributeHashMap, - #[serde(rename = "oldAttributes")] - old_attributes: AttributeHashMap, - }, - - #[serde(rename = "update_text")] - UpdateText { - path: Path, - delta: DeltaTextOperations, - inverted: DeltaTextOperations, - }, -} - -impl std::convert::From for NodeOperation { - fn from(document_operation: DocumentOperation) -> Self { - match document_operation { - DocumentOperation::Insert { path, nodes } => NodeOperation::Insert { - path, - nodes: nodes.into_iter().map(|node| node.into()).collect(), - }, - DocumentOperation::Delete { path, nodes } => NodeOperation::Delete { - path, - - nodes: nodes.into_iter().map(|node| node.into()).collect(), - }, - DocumentOperation::Update { - path, - attributes, - old_attributes, - } => NodeOperation::Update { - path, - changeset: Changeset::Attributes { - new: attributes, - old: old_attributes, - }, - }, - DocumentOperation::UpdateText { - path, - delta, - inverted, - } => NodeOperation::Update { - path, - changeset: Changeset::Delta { delta, inverted }, - }, - } - } -} - -impl std::convert::From<&NodeOperation> for DocumentOperation { - fn from(node_operation: &NodeOperation) -> Self { - let node_operation = node_operation.clone(); - match node_operation { - NodeOperation::Insert { path, nodes } => DocumentOperation::Insert { - path, - nodes: nodes.into_iter().map(|node| node.into()).collect(), - }, - NodeOperation::Update { path, changeset } => match changeset { - Changeset::Delta { delta, inverted } => DocumentOperation::UpdateText { - path, - delta, - inverted, - }, - Changeset::Attributes { new, old } => DocumentOperation::Update { - path, - attributes: new, - old_attributes: old, - }, - }, - NodeOperation::Delete { path, nodes } => DocumentOperation::Delete { - path, - nodes: nodes.into_iter().map(|node| node.into()).collect(), - }, - } - } -} - -#[derive(Default, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] -pub struct DocumentNode { - #[serde(rename = "type")] - pub node_type: String, - - #[serde(skip_serializing_if = "AttributeHashMap::is_empty")] - #[serde(default)] - pub attributes: AttributeHashMap, - - #[serde(skip_serializing_if = "DeltaTextOperations::is_empty")] - #[serde(default)] - pub delta: DeltaTextOperations, - - #[serde(skip_serializing_if = "Vec::is_empty")] - #[serde(default)] - pub children: Vec, -} - -impl DocumentNode { - pub fn new() -> Self { - Self::default() - } -} - -impl std::convert::From for DocumentNode { - fn from(node_data: NodeData) -> Self { - let delta = if let Body::Delta(operations) = node_data.body { - operations - } else { - DeltaTextOperations::default() - }; - DocumentNode { - node_type: node_data.node_type, - attributes: node_data.attributes, - delta, - children: node_data - .children - .into_iter() - .map(DocumentNode::from) - .collect(), - } - } -} - -impl std::convert::From for NodeData { - fn from(document_node: DocumentNode) -> Self { - NodeData { - node_type: document_node.node_type, - attributes: document_node.attributes, - body: Body::Delta(document_node.delta), - children: document_node - .children - .into_iter() - .map(|child| child.into()) - .collect(), - } - } -} - -#[derive(Debug, Deserialize)] -struct DocumentContentDeserializer { - document: DocumentNode, -} - -#[derive(Debug)] -struct DocumentContentSerializer<'a>(pub &'a Document); - -impl<'a> Serialize for DocumentContentSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let tree = self.0.get_tree(); - let root_node_id = tree.root_node_id(); - - // transform the NodeData to DocumentNodeData - let get_document_node_data = - |node_id: NodeId| tree.get_node_data(node_id).map(DocumentNode::from); - - let mut children = tree.get_children_ids(root_node_id); - if children.len() == 1 { - let node_id = children.pop().unwrap(); - match get_document_node_data(node_id) { - None => serializer.serialize_str(""), - Some(node_data) => node_data.serialize(serializer), - } - } else { - let mut seq = serializer.serialize_seq(Some(children.len()))?; - for child in children { - if let Some(node_data) = get_document_node_data(child) { - seq.serialize_element(&node_data)?; - } - } - seq.end() - } - } -} - -#[cfg(test)] -mod tests { - use crate::editor::document::Document; - use crate::editor::document_serde::DocumentTransaction; - use crate::editor::initial_read_me; - - #[test] - fn load_read_me() { - let _ = initial_read_me(); - } - - #[test] - fn transaction_deserialize_update_text_operation_test() { - // bold - let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"retain":3,"attributes":{"bold":true}}],"inverted":[{"retain":3,"attributes":{"bold":null}}]}],"after_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":3}},"before_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":3}}}"#; - let _ = serde_json::from_str::(json).unwrap(); - - // delete character - let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"retain":2},{"delete":1}],"inverted":[{"retain":2},{"insert":"C","attributes":{"bold":true}}]}],"after_selection":{"start":{"path":[0],"offset":2},"end":{"path":[0],"offset":2}},"before_selection":{"start":{"path":[0],"offset":3},"end":{"path":[0],"offset":3}}}"#; - let _ = serde_json::from_str::(json).unwrap(); - } - - #[test] - fn transaction_deserialize_insert_operation_test() { - let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"insert":"a"}],"inverted":[{"delete":1}]}],"after_selection":{"start":{"path":[0],"offset":1},"end":{"path":[0],"offset":1}},"before_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":0}}}"#; - let _ = serde_json::from_str::(json).unwrap(); - } - - #[test] - fn transaction_deserialize_delete_operation_test() { - let json = r#"{"operations": [{"op":"delete","path":[1],"nodes":[{"type":"text","delta":[]}]}],"after_selection":{"start":{"path":[0],"offset":2},"end":{"path":[0],"offset":2}},"before_selection":{"start":{"path":[1],"offset":0},"end":{"path":[1],"offset":0}}}"#; - let _transaction = serde_json::from_str::(json).unwrap(); - } - - #[test] - fn transaction_deserialize_update_attribute_operation_test() { - // let json = r#"{"operations":[{"op":"update","path":[0],"attributes":{"retain":3,"attributes":{"bold":true}},"oldAttributes":{"retain":3,"attributes":{"bold":null}}}]}"#; - // let transaction = serde_json::from_str::(&json).unwrap(); - - let json = r#"{"operations":[{"op":"update","path":[0],"attributes":{"retain":3},"oldAttributes":{"retain":3}}]}"#; - let _ = serde_json::from_str::(json).unwrap(); - } - - #[test] - fn document_serde_test() { - let document: Document = serde_json::from_str(EXAMPLE_DOCUMENT).unwrap(); - let _ = serde_json::to_string_pretty(&document).unwrap(); - } - - // #[test] - // fn document_operation_compose_test() { - // let json = include_str!("./test.json"); - // let transaction: Transaction = Transaction::from_json(json).unwrap(); - // let json = transaction.to_json().unwrap(); - // // let transaction: Transaction = Transaction::from_json(&json).unwrap(); - // let document = Document::from_transaction(transaction).unwrap(); - // let content = document.get_content(false).unwrap(); - // println!("{}", json); - // } - - const EXAMPLE_DOCUMENT: &str = r#"{ - "document": { - "type": "editor", - "children": [ - { - "type": "image", - "attributes": { - "image_src": "https://s1.ax1x.com/2022/08/26/v2sSbR.jpg", - "align": "center" - } - }, - { - "type": "text", - "attributes": { "subtype": "heading", "heading": "h1" }, - "delta": [ - { "insert": "👋 " }, - { "insert": "Welcome to ", "attributes": { "bold": true } }, - { - "insert": "AppFlowy Editor", - "attributes": { - "href": "appflowy.io", - "italic": true, - "bold": true - } - } - ] - }, - { "type": "text", "delta": [] }, - { - "type": "text", - "delta": [ - { "insert": "AppFlowy Editor is a " }, - { "insert": "highly customizable", "attributes": { "bold": true } }, - { "insert": " " }, - { "insert": "rich-text editor", "attributes": { "italic": true } }, - { "insert": " for " }, - { "insert": "Flutter", "attributes": { "underline": true } } - ] - }, - { - "type": "text", - "attributes": { "checkbox": true, "subtype": "checkbox" }, - "delta": [{ "insert": "Customizable" }] - }, - { - "type": "text", - "attributes": { "checkbox": true, "subtype": "checkbox" }, - "delta": [{ "insert": "Test-covered" }] - }, - { - "type": "text", - "attributes": { "checkbox": false, "subtype": "checkbox" }, - "delta": [{ "insert": "more to come!" }] - }, - { "type": "text", "delta": [] }, - { - "type": "text", - "attributes": { "subtype": "quote" }, - "delta": [{ "insert": "Here is an example you can give a try" }] - }, - { "type": "text", "delta": [] }, - { - "type": "text", - "delta": [ - { "insert": "You can also use " }, - { - "insert": "AppFlowy Editor", - "attributes": { - "italic": true, - "bold": true, - "backgroundColor": "0x6000BCF0" - } - }, - { "insert": " as a component to build your own app." } - ] - }, - { "type": "text", "delta": [] }, - { - "type": "text", - "attributes": { "subtype": "bulleted-list" }, - "delta": [{ "insert": "Use / to insert blocks" }] - }, - { - "type": "text", - "attributes": { "subtype": "bulleted-list" }, - "delta": [ - { - "insert": "Select text to trigger to the toolbar to format your notes." - } - ] - }, - { "type": "text", "delta": [] }, - { - "type": "text", - "delta": [ - { - "insert": "If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!" - } - ] - } - ] - } -} -"#; -} diff --git a/frontend/rust-lib/flowy-document/src/editor/editor.rs b/frontend/rust-lib/flowy-document/src/editor/editor.rs deleted file mode 100644 index f0bb08785a..0000000000 --- a/frontend/rust-lib/flowy-document/src/editor/editor.rs +++ /dev/null @@ -1,127 +0,0 @@ -use crate::editor::document::{Document, DocumentRevisionSerde}; -use crate::editor::document_serde::DocumentTransaction; -use crate::editor::make_transaction_from_revisions; -use crate::editor::queue::{Command, CommandSender, DocumentQueue}; -use crate::{DocumentEditor, DocumentUser}; -use bytes::Bytes; -use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_revision::{RevisionCloudService, RevisionManager}; -use flowy_sqlite::ConnectionPool; -use lib_infra::async_trait::async_trait; -use lib_infra::future::FutureResult; -use lib_ot::core::Transaction; -use lib_ws::WSConnectState; -use std::any::Any; -use std::sync::Arc; -use tokio::sync::{mpsc, oneshot}; -use ws_model::ws_revision::ServerRevisionWSData; - -pub struct AppFlowyDocumentEditor { - #[allow(dead_code)] - doc_id: String, - command_sender: CommandSender, - rev_manager: Arc>>, -} - -impl AppFlowyDocumentEditor { - pub async fn new( - doc_id: &str, - user: Arc, - mut rev_manager: RevisionManager>, - cloud_service: Arc, - ) -> FlowyResult> { - let document = rev_manager - .initialize::(Some(cloud_service)) - .await?; - let rev_manager = Arc::new(rev_manager); - let command_sender = spawn_edit_queue(user, rev_manager.clone(), document); - let doc_id = doc_id.to_string(); - let editor = Arc::new(Self { - doc_id, - command_sender, - rev_manager, - }); - Ok(editor) - } - - pub async fn apply_transaction(&self, transaction: Transaction) -> FlowyResult<()> { - let (ret, rx) = oneshot::channel::>(); - let _ = self - .command_sender - .send(Command::ComposeTransaction { transaction, ret }) - .await; - rx.await.map_err(internal_error)??; - Ok(()) - } - - pub async fn get_content(&self, pretty: bool) -> FlowyResult { - let (ret, rx) = oneshot::channel::>(); - let _ = self - .command_sender - .send(Command::GetDocumentContent { pretty, ret }) - .await; - let content = rx.await.map_err(internal_error)??; - Ok(content) - } - - pub async fn duplicate_document(&self) -> FlowyResult { - let transaction = self.document_transaction().await?; - let document = Document::from_transaction(transaction)?; - let json = serde_json::to_string(&document)?; - Ok(json) - } - - pub async fn document_transaction(&self) -> FlowyResult { - let revisions = self.rev_manager.load_revisions().await?; - make_transaction_from_revisions(&revisions) - } -} - -fn spawn_edit_queue( - user: Arc, - rev_manager: Arc>>, - document: Document, -) -> CommandSender { - let (sender, receiver) = mpsc::channel(1000); - let queue = DocumentQueue::new(user, rev_manager, document, receiver); - tokio::spawn(queue.run()); - sender -} - -#[async_trait] -impl DocumentEditor for Arc { - #[tracing::instrument(name = "close document editor", level = "trace", skip_all)] - async fn close(&self) { - self.rev_manager.generate_snapshot().await; - self.rev_manager.close().await; - } - - fn export(&self) -> FutureResult { - let this = self.clone(); - FutureResult::new(async move { this.get_content(false).await }) - } - - fn duplicate(&self) -> FutureResult { - let this = self.clone(); - FutureResult::new(async move { this.duplicate_document().await }) - } - - fn receive_ws_data(&self, _data: ServerRevisionWSData) -> FutureResult<(), FlowyError> { - FutureResult::new(async move { Ok(()) }) - } - - fn receive_ws_state(&self, _state: &WSConnectState) {} - - fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError> { - let this = self.clone(); - FutureResult::new(async move { - let transaction = DocumentTransaction::from_bytes(data)?; - this.apply_transaction(transaction.into()).await?; - Ok(()) - }) - } - - fn as_any(&self) -> &dyn Any { - self - } -} diff --git a/frontend/rust-lib/flowy-document/src/editor/mod.rs b/frontend/rust-lib/flowy-document/src/editor/mod.rs deleted file mode 100644 index 3966c89e84..0000000000 --- a/frontend/rust-lib/flowy-document/src/editor/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![allow(clippy::module_inception)] -mod document; -mod document_serde; -mod editor; -mod queue; - -pub use document::*; -pub use document_serde::*; -pub use editor::*; - -#[inline] -// Return the read me document content -pub fn initial_read_me() -> String { - let document_content = include_str!("READ_ME.json"); - document_content.to_string() -} diff --git a/frontend/rust-lib/flowy-document/src/editor/queue.rs b/frontend/rust-lib/flowy-document/src/editor/queue.rs deleted file mode 100644 index 588f510d6d..0000000000 --- a/frontend/rust-lib/flowy-document/src/editor/queue.rs +++ /dev/null @@ -1,107 +0,0 @@ -#![allow(clippy::while_let_loop)] -use crate::editor::document::Document; -use crate::DocumentUser; -use async_stream::stream; -use bytes::Bytes; -use flowy_error::FlowyError; -use flowy_revision::RevisionManager; -use futures::stream::StreamExt; -use lib_ot::core::Transaction; - -use flowy_sqlite::ConnectionPool; -use std::sync::Arc; -use tokio::sync::mpsc::{Receiver, Sender}; -use tokio::sync::{oneshot, RwLock}; - -pub struct DocumentQueue { - #[allow(dead_code)] - user: Arc, - document: Arc>, - #[allow(dead_code)] - rev_manager: Arc>>, - receiver: Option, -} - -impl DocumentQueue { - pub fn new( - user: Arc, - rev_manager: Arc>>, - document: Document, - receiver: CommandReceiver, - ) -> Self { - let document = Arc::new(RwLock::new(document)); - Self { - user, - document, - rev_manager, - receiver: Some(receiver), - } - } - - pub async fn run(mut self) { - let mut receiver = self.receiver.take().expect("Only take once"); - let stream = stream! { - loop { - match receiver.recv().await { - Some(msg) => yield msg, - None => break, - } - } - }; - stream - .for_each(|command| async { - match self.handle_command(command).await { - Ok(_) => {}, - Err(e) => tracing::debug!("[DocumentQueue]: {}", e), - } - }) - .await; - } - - async fn handle_command(&self, command: Command) -> Result<(), FlowyError> { - match command { - Command::ComposeTransaction { transaction, ret } => { - self - .document - .write() - .await - .apply_transaction(transaction.clone())?; - let _ = self - .save_local_operations(transaction, self.document.read().await.document_md5()) - .await?; - let _ = ret.send(Ok(())); - }, - Command::GetDocumentContent { pretty, ret } => { - let content = self.document.read().await.get_content(pretty)?; - let _ = ret.send(Ok(content)); - }, - } - Ok(()) - } - - #[tracing::instrument(level = "trace", skip(self, transaction, md5), err)] - async fn save_local_operations( - &self, - transaction: Transaction, - md5: String, - ) -> Result { - let bytes = Bytes::from(transaction.to_bytes()?); - let rev_id = self.rev_manager.add_local_revision(bytes, md5).await?; - Ok(rev_id) - } -} - -pub(crate) type CommandSender = Sender; -pub(crate) type CommandReceiver = Receiver; -pub(crate) type Ret = oneshot::Sender>; - -pub enum Command { - ComposeTransaction { - transaction: Transaction, - ret: Ret<()>, - }, - GetDocumentContent { - pretty: bool, - ret: Ret, - }, -} diff --git a/frontend/rust-lib/flowy-document/src/entities.rs b/frontend/rust-lib/flowy-document/src/entities.rs deleted file mode 100644 index 3a8ee308f4..0000000000 --- a/frontend/rust-lib/flowy-document/src/entities.rs +++ /dev/null @@ -1,132 +0,0 @@ -use crate::errors::ErrorCode; -use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use std::convert::TryInto; - -#[derive(PartialEq, Eq, Debug, ProtoBuf_Enum, Clone)] -pub enum ExportType { - Text = 0, - Markdown = 1, - Link = 2, -} - -impl Default for ExportType { - fn default() -> Self { - ExportType::Text - } -} - -impl From for ExportType { - fn from(val: i32) -> Self { - match val { - 0 => ExportType::Text, - 1 => ExportType::Markdown, - 2 => ExportType::Link, - _ => { - tracing::error!("Invalid export type: {}", val); - ExportType::Text - }, - } - } -} - -#[derive(Default, ProtoBuf)] -pub struct EditPayloadPB { - #[pb(index = 1)] - pub doc_id: String, - - // Encode in JSON format - #[pb(index = 2)] - pub operations: String, -} - -#[derive(Default)] -pub struct EditParams { - pub doc_id: String, - - // Encode in JSON format - pub operations: String, -} - -impl TryInto for EditPayloadPB { - type Error = ErrorCode; - fn try_into(self) -> Result { - Ok(EditParams { - doc_id: self.doc_id, - operations: self.operations, - }) - } -} - -#[derive(Default, ProtoBuf)] -pub struct DocumentDataPB { - #[pb(index = 1)] - pub doc_id: String, - - /// Encode in JSON format - #[pb(index = 2)] - pub content: String, -} - -#[derive(Default, ProtoBuf)] -pub struct ExportPayloadPB { - #[pb(index = 1)] - pub view_id: String, - - #[pb(index = 2)] - pub export_type: ExportType, - - #[pb(index = 3)] - pub document_version: DocumentVersionPB, -} - -#[derive(PartialEq, Eq, Debug, ProtoBuf_Enum, Clone)] -pub enum DocumentVersionPB { - /// this version's content of the document is build from `Delta`. It uses - /// `DeltaDocumentEditor`. - V0 = 0, - /// this version's content of the document is build from `NodeTree`. It uses - /// `AppFlowyDocumentEditor` - V1 = 1, -} - -impl std::default::Default for DocumentVersionPB { - fn default() -> Self { - Self::V0 - } -} - -#[derive(Default, ProtoBuf)] -pub struct OpenDocumentPayloadPB { - #[pb(index = 1)] - pub document_id: String, - - #[pb(index = 2)] - pub version: DocumentVersionPB, -} - -#[derive(Default, Debug)] -pub struct ExportParams { - pub view_id: String, - pub export_type: ExportType, - pub document_version: DocumentVersionPB, -} - -impl TryInto for ExportPayloadPB { - type Error = ErrorCode; - fn try_into(self) -> Result { - Ok(ExportParams { - view_id: self.view_id, - export_type: self.export_type, - document_version: self.document_version, - }) - } -} - -#[derive(Default, ProtoBuf)] -pub struct ExportDataPB { - #[pb(index = 1)] - pub data: String, - - #[pb(index = 2)] - pub export_type: ExportType, -} diff --git a/frontend/rust-lib/flowy-document/src/event_handler.rs b/frontend/rust-lib/flowy-document/src/event_handler.rs deleted file mode 100644 index 5011ebd9fd..0000000000 --- a/frontend/rust-lib/flowy-document/src/event_handler.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::entities::{ - DocumentDataPB, EditParams, EditPayloadPB, ExportDataPB, ExportParams, ExportPayloadPB, - OpenDocumentPayloadPB, -}; -use crate::DocumentManager; -use flowy_error::FlowyError; - -use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult}; -use std::convert::TryInto; -use std::sync::Arc; - -pub(crate) async fn get_document_handler( - data: AFPluginData, - manager: AFPluginState>, -) -> DataResult { - let context: OpenDocumentPayloadPB = data.into_inner(); - let editor = manager.open_document_editor(&context.document_id).await?; - let document_data = editor.export().await?; - data_result_ok(DocumentDataPB { - doc_id: context.document_id, - content: document_data, - }) -} - -pub(crate) async fn apply_edit_handler( - data: AFPluginData, - manager: AFPluginState>, -) -> Result<(), FlowyError> { - let params: EditParams = data.into_inner().try_into()?; - manager.apply_edit(params).await?; - Ok(()) -} - -#[tracing::instrument(level = "debug", skip(data, manager), err)] -pub(crate) async fn export_handler( - data: AFPluginData, - manager: AFPluginState>, -) -> DataResult { - let params: ExportParams = data.into_inner().try_into()?; - let editor = manager.open_document_editor(¶ms.view_id).await?; - let document_data = editor.export().await?; - data_result_ok(ExportDataPB { - data: document_data, - export_type: params.export_type, - }) -} diff --git a/frontend/rust-lib/flowy-document/src/event_map.rs b/frontend/rust-lib/flowy-document/src/event_map.rs deleted file mode 100644 index 71905d5adb..0000000000 --- a/frontend/rust-lib/flowy-document/src/event_map.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::event_handler::*; -use crate::DocumentManager; -use flowy_derive::{Flowy_Event, ProtoBuf_Enum}; -use lib_dispatch::prelude::AFPlugin; -use std::sync::Arc; -use strum_macros::Display; - -pub fn init(document_manager: Arc) -> AFPlugin { - let mut plugin = AFPlugin::new() - .name(env!("CARGO_PKG_NAME")) - .state(document_manager); - - plugin = plugin - .event(DocumentEvent::GetDocument, get_document_handler) - .event(DocumentEvent::ApplyEdit, apply_edit_handler) - .event(DocumentEvent::ExportDocument, export_handler); - - plugin -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)] -#[event_err = "FlowyError"] -pub enum DocumentEvent { - #[event(input = "OpenDocumentPayloadPB", output = "DocumentDataPB")] - GetDocument = 0, - - #[event(input = "EditPayloadPB")] - ApplyEdit = 1, - - #[event(input = "ExportPayloadPB", output = "ExportDataPB")] - ExportDocument = 2, -} diff --git a/frontend/rust-lib/flowy-document/src/lib.rs b/frontend/rust-lib/flowy-document/src/lib.rs deleted file mode 100644 index 5106885014..0000000000 --- a/frontend/rust-lib/flowy-document/src/lib.rs +++ /dev/null @@ -1,42 +0,0 @@ -pub mod entities; -mod event_handler; -pub mod event_map; -pub mod manager; - -pub mod editor; -pub mod old_editor; -pub mod protobuf; -mod services; - -pub use manager::*; -pub mod errors { - pub use flowy_error::{internal_error, ErrorCode, FlowyError}; -} - -pub const TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS: u64 = 1000; - -use crate::errors::FlowyError; -use document_model::document::{ - CreateDocumentParams, DocumentId, DocumentInfo, ResetDocumentParams, -}; -use lib_infra::future::FutureResult; - -pub trait DocumentCloudService: Send + Sync { - fn create_document( - &self, - token: &str, - params: CreateDocumentParams, - ) -> FutureResult<(), FlowyError>; - - fn fetch_document( - &self, - token: &str, - params: DocumentId, - ) -> FutureResult, FlowyError>; - - fn update_document_content( - &self, - token: &str, - params: ResetDocumentParams, - ) -> FutureResult<(), FlowyError>; -} diff --git a/frontend/rust-lib/flowy-document/src/manager.rs b/frontend/rust-lib/flowy-document/src/manager.rs deleted file mode 100644 index 060819f120..0000000000 --- a/frontend/rust-lib/flowy-document/src/manager.rs +++ /dev/null @@ -1,382 +0,0 @@ -use crate::editor::{initial_document_content, AppFlowyDocumentEditor, DocumentRevisionMergeable}; -use crate::entities::{DocumentVersionPB, EditParams}; -use crate::old_editor::editor::{DeltaDocumentEditor, DeltaDocumentRevisionMergeable}; -use crate::old_editor::snapshot::DeltaDocumentSnapshotPersistence; -use crate::services::rev_sqlite::{ - SQLiteDeltaDocumentRevisionPersistence, SQLiteDocumentRevisionPersistence, - SQLiteDocumentRevisionSnapshotPersistence, -}; -use crate::services::DocumentPersistence; -use crate::{errors::FlowyError, DocumentCloudService}; -use bytes::Bytes; -use document_model::document::DocumentId; -use flowy_client_sync::client_document::initial_delta_document_content; -use flowy_error::FlowyResult; -use flowy_revision::{ - RevisionCloudService, RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, - RevisionWebSocket, -}; -use flowy_sqlite::ConnectionPool; -use lib_infra::async_trait::async_trait; -use lib_infra::future::FutureResult; -use lib_infra::ref_map::{RefCountHashMap, RefCountValue}; -use lib_infra::util::md5; -use lib_ws::WSConnectState; -use revision_model::Revision; -use std::any::Any; -use std::convert::TryFrom; -use std::sync::Arc; -use tokio::sync::RwLock; -use ws_model::ws_revision::ServerRevisionWSData; - -pub trait DocumentUser: Send + Sync { - fn user_dir(&self) -> Result; - fn user_id(&self) -> Result; - fn token(&self) -> Result; -} - -pub trait DocumentDatabase: Send + Sync { - fn db_pool(&self) -> Result, FlowyError>; -} - -#[async_trait] -pub trait DocumentEditor: Send + Sync { - /// Called when the document get closed - async fn close(&self); - - /// Exports the document content. The content is encoded in the corresponding - /// editor data format. - fn export(&self) -> FutureResult; - - /// Duplicate the document inner data into String - fn duplicate(&self) -> FutureResult; - - fn receive_ws_data(&self, data: ServerRevisionWSData) -> FutureResult<(), FlowyError>; - - fn receive_ws_state(&self, state: &WSConnectState); - - /// Receives the local operations made by the user input. The operations are encoded - /// in binary format. - fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError>; - - /// Returns the `Any` reference that can be used to downcast back to the original, - /// concrete type. - /// - /// The indirection through `as_any` is because using `downcast_ref` - /// on `Box` *directly* only lets us downcast back to `&A` again. You can take a look at [this](https://stackoverflow.com/questions/33687447/how-to-get-a-reference-to-a-concrete-type-from-a-trait-object) - /// for more information. - /// - /// - fn as_any(&self) -> &dyn Any; -} - -#[derive(Clone, Debug)] -pub struct DocumentConfig { - pub version: DocumentVersionPB, -} - -impl std::default::Default for DocumentConfig { - fn default() -> Self { - Self { - version: DocumentVersionPB::V1, - } - } -} - -pub struct DocumentManager { - cloud_service: Arc, - rev_web_socket: Arc, - editor_map: Arc>>, - user: Arc, - persistence: Arc, - #[allow(dead_code)] - config: DocumentConfig, -} - -impl DocumentManager { - pub fn new( - cloud_service: Arc, - document_user: Arc, - database: Arc, - rev_web_socket: Arc, - config: DocumentConfig, - ) -> Self { - Self { - cloud_service, - rev_web_socket, - editor_map: Arc::new(RwLock::new(RefCountHashMap::new())), - user: document_user, - persistence: Arc::new(DocumentPersistence::new(database)), - config, - } - } - - /// Called immediately after the application launched with the user sign in/sign up. - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn initialize(&self, user_id: i64) -> FlowyResult<()> { - self.persistence.initialize(user_id)?; - listen_ws_state_changed(self.rev_web_socket.clone(), self.editor_map.clone()); - Ok(()) - } - - pub async fn initialize_with_new_user(&self, _user_id: i64, _token: &str) -> FlowyResult<()> { - Ok(()) - } - - #[tracing::instrument(level = "trace", skip_all, fields(document_id), err)] - pub async fn open_document_editor>( - &self, - document_id: T, - ) -> Result, FlowyError> { - let document_id = document_id.as_ref(); - tracing::Span::current().record("document_id", document_id); - self.init_document_editor(document_id).await - } - - #[tracing::instrument(level = "trace", skip(self, editor_id), fields(editor_id), err)] - pub async fn close_document_editor>(&self, editor_id: T) -> Result<(), FlowyError> { - let editor_id = editor_id.as_ref(); - tracing::Span::current().record("editor_id", editor_id); - self.editor_map.write().await.remove(editor_id).await; - Ok(()) - } - - pub async fn apply_edit(&self, params: EditParams) -> FlowyResult<()> { - let editor = self.get_document_editor(¶ms.doc_id).await?; - editor - .compose_local_operations(Bytes::from(params.operations)) - .await?; - Ok(()) - } - - pub async fn create_document>( - &self, - doc_id: T, - revisions: Vec, - ) -> FlowyResult<()> { - let doc_id = doc_id.as_ref().to_owned(); - let db_pool = self.persistence.database.db_pool()?; - // Maybe we could save the document to disk without creating the RevisionManager - let rev_manager = self.make_rev_manager(&doc_id, db_pool)?; - rev_manager.reset_object(revisions).await?; - Ok(()) - } - - pub async fn receive_ws_data(&self, data: Bytes) { - let result: Result = - ServerRevisionWSData::try_from(data); - match result { - Ok(data) => match self.editor_map.read().await.get(&data.object_id) { - None => tracing::error!( - "Can't find any source handler for {:?}-{:?}", - data.object_id, - data.payload - ), - Some(handler) => match handler.0.receive_ws_data(data).await { - Ok(_) => {}, - Err(e) => tracing::error!("{}", e), - }, - }, - Err(e) => { - tracing::error!("Document ws data parser failed: {:?}", e); - }, - } - } - - pub fn initial_document_content(&self) -> String { - match self.config.version { - DocumentVersionPB::V0 => initial_delta_document_content(), - DocumentVersionPB::V1 => initial_document_content(), - } - } -} - -impl DocumentManager { - /// Returns the `DocumentEditor` - /// - /// # Arguments - /// - /// * `doc_id`: the id of the document - /// - /// returns: Result, FlowyError> - /// - async fn get_document_editor(&self, doc_id: &str) -> FlowyResult> { - match self.editor_map.read().await.get(doc_id) { - None => { - // - tracing::warn!("Should call init_document_editor first"); - self.init_document_editor(doc_id).await - }, - Some(handler) => Ok(handler.0.clone()), - } - } - - /// Initializes a document editor with the doc_id - /// - /// # Arguments - /// - /// * `doc_id`: the id of the document - /// * `pool`: sqlite connection pool - /// - /// returns: Result, FlowyError> - /// - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn init_document_editor( - &self, - doc_id: &str, - ) -> Result, FlowyError> { - let pool = self.persistence.database.db_pool()?; - let user = self.user.clone(); - let token = self.user.token()?; - let cloud_service = Arc::new(DocumentRevisionCloudService { - token, - server: self.cloud_service.clone(), - }); - - match self.config.version { - DocumentVersionPB::V0 => { - let rev_manager = self.make_delta_document_rev_manager(doc_id, pool.clone())?; - let editor: Arc = Arc::new( - DeltaDocumentEditor::new( - doc_id, - user, - rev_manager, - self.rev_web_socket.clone(), - cloud_service, - ) - .await?, - ); - self - .editor_map - .write() - .await - .insert(doc_id.to_string(), RefCountDocumentHandler(editor.clone())); - Ok(editor) - }, - DocumentVersionPB::V1 => { - let rev_manager = self.make_document_rev_manager(doc_id, pool.clone())?; - let editor: Arc = - Arc::new(AppFlowyDocumentEditor::new(doc_id, user, rev_manager, cloud_service).await?); - self - .editor_map - .write() - .await - .insert(doc_id.to_string(), RefCountDocumentHandler(editor.clone())); - Ok(editor) - }, - } - } - - fn make_rev_manager( - &self, - doc_id: &str, - pool: Arc, - ) -> Result>, FlowyError> { - match self.config.version { - DocumentVersionPB::V0 => self.make_delta_document_rev_manager(doc_id, pool), - DocumentVersionPB::V1 => self.make_document_rev_manager(doc_id, pool), - } - } - - fn make_document_rev_manager( - &self, - doc_id: &str, - pool: Arc, - ) -> Result>, FlowyError> { - let disk_cache = SQLiteDocumentRevisionPersistence::new(pool.clone()); - let configuration = RevisionPersistenceConfiguration::new(200, true); - let rev_persistence = RevisionPersistence::new(doc_id, disk_cache, configuration); - let snapshot_persistence = SQLiteDocumentRevisionSnapshotPersistence::new(doc_id, pool); - Ok(RevisionManager::new( - doc_id, - rev_persistence, - DocumentRevisionMergeable(), - snapshot_persistence, - )) - } - - fn make_delta_document_rev_manager( - &self, - doc_id: &str, - pool: Arc, - ) -> Result>, FlowyError> { - let disk_cache = SQLiteDeltaDocumentRevisionPersistence::new(pool); - let configuration = RevisionPersistenceConfiguration::new(100, true); - let rev_persistence = RevisionPersistence::new(doc_id, disk_cache, configuration); - Ok(RevisionManager::new( - doc_id, - rev_persistence, - DeltaDocumentRevisionMergeable(), - DeltaDocumentSnapshotPersistence(), - )) - } -} - -struct DocumentRevisionCloudService { - token: String, - server: Arc, -} - -impl RevisionCloudService for DocumentRevisionCloudService { - #[tracing::instrument(level = "trace", skip(self))] - fn fetch_object( - &self, - user_id: &str, - object_id: &str, - ) -> FutureResult, FlowyError> { - let params: DocumentId = object_id.to_string().into(); - let server = self.server.clone(); - let token = self.token.clone(); - - FutureResult::new(async move { - match server.fetch_document(&token, params).await? { - None => Err(FlowyError::record_not_found().context("Remote doesn't have this document")), - Some(payload) => { - let bytes = Bytes::from(payload.data.clone()); - let doc_md5 = md5(&bytes); - let revision = Revision::new( - &payload.doc_id, - payload.base_rev_id, - payload.rev_id, - bytes, - doc_md5, - ); - Ok(vec![revision]) - }, - } - }) - } -} - -#[derive(Clone)] -struct RefCountDocumentHandler(Arc); - -#[async_trait] -impl RefCountValue for RefCountDocumentHandler { - async fn did_remove(&self) { - self.0.close().await; - } -} - -impl std::ops::Deref for RefCountDocumentHandler { - type Target = Arc; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[tracing::instrument(level = "trace", skip(web_socket, handlers))] -fn listen_ws_state_changed( - web_socket: Arc, - handlers: Arc>>, -) { - tokio::spawn(async move { - let mut notify = web_socket.subscribe_state_changed().await; - while let Ok(state) = notify.recv().await { - handlers.read().await.values().iter().for_each(|handler| { - handler.receive_ws_state(&state); - }) - } - }); -} diff --git a/frontend/rust-lib/flowy-document/src/old_editor/conflict.rs b/frontend/rust-lib/flowy-document/src/old_editor/conflict.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/frontend/rust-lib/flowy-document/src/old_editor/conflict.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/rust-lib/flowy-document/src/old_editor/editor.rs b/frontend/rust-lib/flowy-document/src/old_editor/editor.rs deleted file mode 100644 index 59082cbca3..0000000000 --- a/frontend/rust-lib/flowy-document/src/old_editor/editor.rs +++ /dev/null @@ -1,306 +0,0 @@ -#![allow(unused_attributes)] -#![allow(unused_attributes)] - -use crate::old_editor::queue::{EditDocumentQueue, EditorCommand, EditorCommandSender}; -use crate::{errors::FlowyError, DocumentEditor, DocumentUser}; -use bytes::Bytes; -use document_model::document::DocumentInfo; -use flowy_client_sync::errors::SyncResult; -use flowy_client_sync::make_operations_from_revisions; -use flowy_error::{internal_error, FlowyResult}; -use flowy_revision::{ - RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, - RevisionObjectSerializer, RevisionWebSocket, -}; -use flowy_sqlite::ConnectionPool; -use lib_infra::async_trait::async_trait; -use lib_infra::future::FutureResult; -use lib_ot::core::{AttributeEntry, AttributeHashMap}; -use lib_ot::{ - core::{DeltaOperation, Interval}, - text_delta::DeltaTextOperations, -}; -use lib_ws::WSConnectState; -use revision_model::Revision; -use std::any::Any; -use std::sync::Arc; -use tokio::sync::{mpsc, oneshot}; -use ws_model::ws_revision::ServerRevisionWSData; - -pub struct DeltaDocumentEditor { - pub doc_id: String, - #[allow(dead_code)] - rev_manager: Arc>>, - #[cfg(feature = "sync")] - ws_manager: Arc, - edit_cmd_tx: EditorCommandSender, -} - -impl DeltaDocumentEditor { - #[allow(unused_variables)] - pub(crate) async fn new( - doc_id: &str, - user: Arc, - mut rev_manager: RevisionManager>, - rev_web_socket: Arc, - cloud_service: Arc, - ) -> FlowyResult> { - let document = rev_manager - .initialize::(Some(cloud_service)) - .await?; - let operations = DeltaTextOperations::from_bytes(&document.data)?; - let rev_manager = Arc::new(rev_manager); - let doc_id = doc_id.to_string(); - let user_id = user.user_id()?; - - let edit_cmd_tx = spawn_edit_queue(user, rev_manager.clone(), operations); - #[cfg(feature = "sync")] - let ws_manager = crate::old_editor::web_socket::make_document_ws_manager( - doc_id.clone(), - edit_cmd_tx.clone(), - rev_manager.clone(), - rev_web_socket, - ) - .await; - let editor = Arc::new(Self { - doc_id, - rev_manager, - #[cfg(feature = "sync")] - ws_manager, - edit_cmd_tx, - }); - Ok(editor) - } - - pub async fn insert(&self, index: usize, data: T) -> Result<(), FlowyError> { - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::Insert { - index, - data: data.to_string(), - ret, - }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.map_err(internal_error)??; - Ok(()) - } - - pub async fn delete(&self, interval: Interval) -> Result<(), FlowyError> { - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::Delete { interval, ret }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.map_err(internal_error)??; - Ok(()) - } - - pub async fn format( - &self, - interval: Interval, - attribute: AttributeEntry, - ) -> Result<(), FlowyError> { - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::Format { - interval, - attribute, - ret, - }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.map_err(internal_error)??; - Ok(()) - } - - pub async fn replace(&self, interval: Interval, data: T) -> Result<(), FlowyError> { - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::Replace { - interval, - data: data.to_string(), - ret, - }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.map_err(internal_error)??; - Ok(()) - } - - pub async fn can_undo(&self) -> bool { - let (ret, rx) = oneshot::channel::(); - let msg = EditorCommand::CanUndo { ret }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.unwrap_or(false) - } - - pub async fn can_redo(&self) -> bool { - let (ret, rx) = oneshot::channel::(); - let msg = EditorCommand::CanRedo { ret }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.unwrap_or(false) - } - - pub async fn undo(&self) -> Result<(), FlowyError> { - let (ret, rx) = oneshot::channel(); - let msg = EditorCommand::Undo { ret }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.map_err(internal_error)??; - Ok(()) - } - - pub async fn redo(&self) -> Result<(), FlowyError> { - let (ret, rx) = oneshot::channel(); - let msg = EditorCommand::Redo { ret }; - let _ = self.edit_cmd_tx.send(msg).await; - rx.await.map_err(internal_error)??; - Ok(()) - } -} - -#[async_trait] -impl DocumentEditor for Arc { - async fn close(&self) { - #[cfg(feature = "sync")] - self.ws_manager.stop(); - } - - fn export(&self) -> FutureResult { - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::GetOperationsString { ret }; - let edit_cmd_tx = self.edit_cmd_tx.clone(); - FutureResult::new(async move { - let _ = edit_cmd_tx.send(msg).await; - let json = rx.await.map_err(internal_error)??; - Ok(json) - }) - } - - fn duplicate(&self) -> FutureResult { - self.export() - } - - #[allow(unused_variables)] - fn receive_ws_data(&self, data: ServerRevisionWSData) -> FutureResult<(), FlowyError> { - let cloned_self = self.clone(); - FutureResult::new(async move { - #[cfg(feature = "sync")] - let _ = cloned_self.ws_manager.receive_ws_data(data).await?; - - Ok(()) - }) - } - - #[allow(unused_variables)] - fn receive_ws_state(&self, state: &WSConnectState) { - #[cfg(feature = "sync")] - self.ws_manager.connect_state_changed(state.clone()); - } - - fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError> { - let edit_cmd_tx = self.edit_cmd_tx.clone(); - FutureResult::new(async move { - let operations = DeltaTextOperations::from_bytes(&data)?; - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::ComposeLocalOperations { operations, ret }; - - let _ = edit_cmd_tx.send(msg).await; - rx.await.map_err(internal_error)??; - Ok(()) - }) - } - - fn as_any(&self) -> &dyn Any { - self - } -} -impl std::ops::Drop for DeltaDocumentEditor { - fn drop(&mut self) { - tracing::trace!("{} DocumentEditor was dropped", self.doc_id) - } -} - -// The edit queue will exit after the EditorCommandSender was dropped. -fn spawn_edit_queue( - user: Arc, - rev_manager: Arc>>, - delta: DeltaTextOperations, -) -> EditorCommandSender { - let (sender, receiver) = mpsc::channel(1000); - let edit_queue = EditDocumentQueue::new(user, rev_manager, delta, receiver); - // We can use tokio::task::spawn_local here by using tokio::spawn_blocking. - // https://github.com/tokio-rs/tokio/issues/2095 - // tokio::task::spawn_blocking(move || { - // let rt = tokio::runtime::Handle::current(); - // rt.block_on(async { - // let local = tokio::task::LocalSet::new(); - // local.run_until(edit_queue.run()).await; - // }); - // }); - tokio::spawn(edit_queue.run()); - sender -} - -#[cfg(feature = "flowy_unit_test")] -impl DeltaDocumentEditor { - pub async fn document_operations(&self) -> FlowyResult { - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::GetOperations { ret }; - let _ = self.edit_cmd_tx.send(msg).await; - let delta = rx.await.map_err(internal_error)??; - Ok(delta) - } - - pub fn rev_manager(&self) -> Arc>> { - self.rev_manager.clone() - } -} - -pub struct DeltaDocumentRevisionSerde(); -impl RevisionObjectDeserializer for DeltaDocumentRevisionSerde { - type Output = DocumentInfo; - - fn deserialize_revisions(object_id: &str, revisions: Vec) -> FlowyResult { - let (base_rev_id, rev_id) = revisions.last().unwrap().pair_rev_id(); - let mut delta = make_operations_from_revisions(revisions)?; - correct_delta(&mut delta); - - Result::::Ok(DocumentInfo { - doc_id: object_id.to_owned(), - data: delta.json_bytes().to_vec(), - rev_id, - base_rev_id, - }) - } - - fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { - None - } -} - -impl RevisionObjectSerializer for DeltaDocumentRevisionSerde { - fn combine_revisions(revisions: Vec) -> FlowyResult { - let operations = make_operations_from_revisions::(revisions)?; - Ok(operations.json_bytes()) - } -} - -pub(crate) struct DeltaDocumentRevisionMergeable(); -impl RevisionMergeable for DeltaDocumentRevisionMergeable { - fn combine_revisions(&self, revisions: Vec) -> FlowyResult { - DeltaDocumentRevisionSerde::combine_revisions(revisions) - } -} - -// quill-editor requires the delta should end with '\n' and only contains the -// insert operation. The function, correct_delta maybe be removed in the future. -fn correct_delta(delta: &mut DeltaTextOperations) { - if let Some(op) = delta.ops.last() { - let op_data = op.get_data(); - if !op_data.ends_with('\n') { - tracing::warn!("The document must end with newline. Correcting it by inserting newline op"); - delta.ops.push(DeltaOperation::Insert("\n".into())); - } - } - - if let Some(op) = delta.ops.iter().find(|op| !op.is_insert()) { - tracing::warn!( - "The document can only contains insert operations, but found {:?}", - op - ); - delta.ops.retain(|op| op.is_insert()); - } -} diff --git a/frontend/rust-lib/flowy-document/src/old_editor/mod.rs b/frontend/rust-lib/flowy-document/src/old_editor/mod.rs deleted file mode 100644 index 10faf2e2cf..0000000000 --- a/frontend/rust-lib/flowy-document/src/old_editor/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod conflict; -pub mod editor; -pub mod queue; -pub mod snapshot; -mod web_socket; diff --git a/frontend/rust-lib/flowy-document/src/old_editor/queue.rs b/frontend/rust-lib/flowy-document/src/old_editor/queue.rs deleted file mode 100644 index e1b06674f3..0000000000 --- a/frontend/rust-lib/flowy-document/src/old_editor/queue.rs +++ /dev/null @@ -1,279 +0,0 @@ -#![allow(clippy::while_let_loop)] -use crate::old_editor::web_socket::DeltaDocumentResolveOperations; -use crate::DocumentUser; -use async_stream::stream; -use flowy_client_sync::{ - client_document::{history::UndoResult, ClientDocument}, - errors::SyncError, -}; -use flowy_error::FlowyError; -use flowy_revision::{RevisionMD5, RevisionManager, TransformOperations}; -use flowy_sqlite::ConnectionPool; -use futures::stream::StreamExt; -use lib_ot::core::AttributeEntry; -use lib_ot::{ - core::{Interval, OperationTransform}, - text_delta::DeltaTextOperations, -}; -use std::sync::Arc; -use tokio::sync::mpsc::{Receiver, Sender}; -use tokio::sync::{oneshot, RwLock}; - -// The EditorCommandQueue executes each command that will alter the document in -// serial. -pub(crate) struct EditDocumentQueue { - document: Arc>, - #[allow(dead_code)] - user: Arc, - rev_manager: Arc>>, - receiver: Option, -} - -impl EditDocumentQueue { - pub(crate) fn new( - user: Arc, - rev_manager: Arc>>, - operations: DeltaTextOperations, - receiver: EditorCommandReceiver, - ) -> Self { - let document = Arc::new(RwLock::new(ClientDocument::from_operations(operations))); - Self { - document, - user, - rev_manager, - receiver: Some(receiver), - } - } - - pub(crate) async fn run(mut self) { - let mut receiver = self.receiver.take().expect("Should only call once"); - let stream = stream! { - loop { - match receiver.recv().await { - Some(msg) => yield msg, - None => break, - } - } - }; - stream - .for_each(|command| async { - match self.handle_command(command).await { - Ok(_) => {}, - Err(e) => tracing::debug!("[EditCommandQueue]: {}", e), - } - }) - .await; - } - - #[tracing::instrument(level = "trace", skip(self), err)] - async fn handle_command(&self, command: EditorCommand) -> Result<(), FlowyError> { - match command { - EditorCommand::ComposeLocalOperations { operations, ret } => { - let mut document = self.document.write().await; - document.compose_operations(operations.clone())?; - let md5 = document.document_md5(); - drop(document); - let _ = self.save_local_operations(operations, md5).await?; - let _ = ret.send(Ok(())); - }, - EditorCommand::ComposeRemoteOperation { - client_operations, - ret, - } => { - let mut document = self.document.write().await; - document.compose_operations(client_operations.clone())?; - let md5 = document.document_md5(); - drop(document); - let _ = ret.send(Ok(md5.into())); - }, - EditorCommand::ResetOperations { operations, ret } => { - let mut document = self.document.write().await; - document.set_operations(operations); - let md5 = document.document_md5(); - drop(document); - let _ = ret.send(Ok(md5.into())); - }, - EditorCommand::TransformOperations { operations, ret } => { - let f = || async { - let read_guard = self.document.read().await; - let mut server_operations: Option = None; - let client_operations: DeltaTextOperations; - - if read_guard.is_empty() { - // Do nothing - client_operations = operations; - } else { - let (s_prime, c_prime) = read_guard.get_operations().transform(&operations)?; - client_operations = c_prime; - server_operations = Some(DeltaDocumentResolveOperations(s_prime)); - } - drop(read_guard); - Ok::(TransformOperations { - client_operations: DeltaDocumentResolveOperations(client_operations), - server_operations, - }) - }; - let _ = ret.send(f().await); - }, - EditorCommand::Insert { index, data, ret } => { - let mut write_guard = self.document.write().await; - let operations = write_guard.insert(index, data)?; - let md5 = write_guard.document_md5(); - let _ = self.save_local_operations(operations, md5).await?; - let _ = ret.send(Ok(())); - }, - EditorCommand::Delete { interval, ret } => { - let mut write_guard = self.document.write().await; - let operations = write_guard.delete(interval)?; - let md5 = write_guard.document_md5(); - let _ = self.save_local_operations(operations, md5).await?; - let _ = ret.send(Ok(())); - }, - EditorCommand::Format { - interval, - attribute, - ret, - } => { - let mut write_guard = self.document.write().await; - let operations = write_guard.format(interval, attribute)?; - let md5 = write_guard.document_md5(); - let _ = self.save_local_operations(operations, md5).await?; - let _ = ret.send(Ok(())); - }, - EditorCommand::Replace { - interval, - data, - ret, - } => { - let mut write_guard = self.document.write().await; - let operations = write_guard.replace(interval, data)?; - let md5 = write_guard.document_md5(); - let _ = self.save_local_operations(operations, md5).await?; - let _ = ret.send(Ok(())); - }, - EditorCommand::CanUndo { ret } => { - let _ = ret.send(self.document.read().await.can_undo()); - }, - EditorCommand::CanRedo { ret } => { - let _ = ret.send(self.document.read().await.can_redo()); - }, - EditorCommand::Undo { ret } => { - let mut write_guard = self.document.write().await; - let UndoResult { operations } = write_guard.undo()?; - let md5 = write_guard.document_md5(); - let _ = self.save_local_operations(operations, md5).await?; - let _ = ret.send(Ok(())); - }, - EditorCommand::Redo { ret } => { - let mut write_guard = self.document.write().await; - let UndoResult { operations } = write_guard.redo()?; - let md5 = write_guard.document_md5(); - let _ = self.save_local_operations(operations, md5).await?; - let _ = ret.send(Ok(())); - }, - EditorCommand::GetOperationsString { ret } => { - let data = self.document.read().await.get_operations_json(); - let _ = ret.send(Ok(data)); - }, - EditorCommand::GetOperations { ret } => { - let operations = self.document.read().await.get_operations().clone(); - let _ = ret.send(Ok(operations)); - }, - } - Ok(()) - } - - async fn save_local_operations( - &self, - operations: DeltaTextOperations, - md5: String, - ) -> Result { - let bytes = operations.json_bytes(); - let rev_id = self.rev_manager.add_local_revision(bytes, md5).await?; - Ok(rev_id) - } -} - -pub type TextTransformOperations = TransformOperations; -pub(crate) type EditorCommandSender = Sender; -pub(crate) type EditorCommandReceiver = Receiver; -pub(crate) type Ret = oneshot::Sender>; - -pub(crate) enum EditorCommand { - ComposeLocalOperations { - operations: DeltaTextOperations, - ret: Ret<()>, - }, - ComposeRemoteOperation { - client_operations: DeltaTextOperations, - ret: Ret, - }, - ResetOperations { - operations: DeltaTextOperations, - ret: Ret, - }, - TransformOperations { - operations: DeltaTextOperations, - ret: Ret, - }, - Insert { - index: usize, - data: String, - ret: Ret<()>, - }, - Delete { - interval: Interval, - ret: Ret<()>, - }, - Format { - interval: Interval, - attribute: AttributeEntry, - ret: Ret<()>, - }, - Replace { - interval: Interval, - data: String, - ret: Ret<()>, - }, - CanUndo { - ret: oneshot::Sender, - }, - CanRedo { - ret: oneshot::Sender, - }, - Undo { - ret: Ret<()>, - }, - Redo { - ret: Ret<()>, - }, - GetOperationsString { - ret: Ret, - }, - #[allow(dead_code)] - GetOperations { - ret: Ret, - }, -} - -impl std::fmt::Debug for EditorCommand { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let s = match self { - EditorCommand::ComposeLocalOperations { .. } => "ComposeLocalOperations", - EditorCommand::ComposeRemoteOperation { .. } => "ComposeRemoteOperation", - EditorCommand::ResetOperations { .. } => "ResetOperations", - EditorCommand::TransformOperations { .. } => "TransformOperations", - EditorCommand::Insert { .. } => "Insert", - EditorCommand::Delete { .. } => "Delete", - EditorCommand::Format { .. } => "Format", - EditorCommand::Replace { .. } => "Replace", - EditorCommand::CanUndo { .. } => "CanUndo", - EditorCommand::CanRedo { .. } => "CanRedo", - EditorCommand::Undo { .. } => "Undo", - EditorCommand::Redo { .. } => "Redo", - EditorCommand::GetOperationsString { .. } => "StringifyOperations", - EditorCommand::GetOperations { .. } => "ReadOperations", - }; - f.write_str(s) - } -} diff --git a/frontend/rust-lib/flowy-document/src/old_editor/snapshot.rs b/frontend/rust-lib/flowy-document/src/old_editor/snapshot.rs deleted file mode 100644 index 9269df24a5..0000000000 --- a/frontend/rust-lib/flowy-document/src/old_editor/snapshot.rs +++ /dev/null @@ -1,18 +0,0 @@ -use flowy_error::FlowyResult; -use flowy_revision::{RevisionSnapshotData, RevisionSnapshotPersistence}; - -pub struct DeltaDocumentSnapshotPersistence(); - -impl RevisionSnapshotPersistence for DeltaDocumentSnapshotPersistence { - fn write_snapshot(&self, _rev_id: i64, _data: Vec) -> FlowyResult<()> { - Ok(()) - } - - fn read_snapshot(&self, _rev_id: i64) -> FlowyResult> { - Ok(None) - } - - fn read_last_snapshot(&self) -> FlowyResult> { - Ok(None) - } -} diff --git a/frontend/rust-lib/flowy-document/src/old_editor/util.rs b/frontend/rust-lib/flowy-document/src/old_editor/util.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frontend/rust-lib/flowy-document/src/old_editor/web_socket.rs b/frontend/rust-lib/flowy-document/src/old_editor/web_socket.rs deleted file mode 100644 index bd1c3fa7c6..0000000000 --- a/frontend/rust-lib/flowy-document/src/old_editor/web_socket.rs +++ /dev/null @@ -1,192 +0,0 @@ -use crate::old_editor::queue::{EditorCommand, EditorCommandSender, TextTransformOperations}; -use crate::TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS; -use bytes::Bytes; -use flowy_client_sync::errors::SyncResult; -use flowy_client_sync::make_operations_from_revisions; -use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_revision::*; -use flowy_sqlite::ConnectionPool; -use lib_infra::future::{BoxResultFuture, FutureResult}; -use lib_ot::text_delta::DeltaTextOperations; -use lib_ws::WSConnectState; -use revision_model::{Revision, RevisionRange}; -use std::{sync::Arc, time::Duration}; -use tokio::sync::{broadcast, oneshot}; -use ws_model::ws_revision::{ClientRevisionWSData, NewDocumentUser}; - -#[derive(Clone)] -pub struct DeltaDocumentResolveOperations(pub DeltaTextOperations); - -impl OperationsDeserializer for DeltaDocumentResolveOperations { - fn deserialize_revisions( - revisions: Vec, - ) -> FlowyResult { - Ok(DeltaDocumentResolveOperations( - make_operations_from_revisions(revisions)?, - )) - } -} - -impl OperationsSerializer for DeltaDocumentResolveOperations { - fn serialize_operations(&self) -> Bytes { - self.0.json_bytes() - } -} - -impl DeltaDocumentResolveOperations { - pub fn into_inner(self) -> DeltaTextOperations { - self.0 - } -} - -pub type DocumentConflictController = - ConflictController>; - -#[allow(dead_code)] -pub(crate) async fn make_document_ws_manager( - doc_id: String, - edit_cmd_tx: EditorCommandSender, - rev_manager: Arc>>, - rev_web_socket: Arc, -) -> Arc { - let ws_data_provider = Arc::new(WSDataProvider::new(&doc_id, Arc::new(rev_manager.clone()))); - let resolver = Arc::new(DocumentConflictResolver { edit_cmd_tx }); - let conflict_controller = - DocumentConflictController::new(resolver, Arc::new(ws_data_provider.clone()), rev_manager); - let ws_data_stream = Arc::new(DocumentRevisionWSDataStream::new(conflict_controller)); - let ws_data_sink = Arc::new(DocumentWSDataSink(ws_data_provider)); - let ping_duration = Duration::from_millis(TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS); - let ws_manager = Arc::new(RevisionWebSocketManager::new( - "Block", - &doc_id, - rev_web_socket, - ws_data_sink, - ws_data_stream, - ping_duration, - )); - listen_document_ws_state(&doc_id, ws_manager.scribe_state()); - ws_manager -} - -#[allow(dead_code)] -fn listen_document_ws_state(_doc_id: &str, mut subscriber: broadcast::Receiver) { - tokio::spawn(async move { - while let Ok(state) = subscriber.recv().await { - match state { - WSConnectState::Init => {}, - WSConnectState::Connecting => {}, - WSConnectState::Connected => {}, - WSConnectState::Disconnected => {}, - } - } - }); -} - -pub(crate) struct DocumentRevisionWSDataStream { - conflict_controller: Arc, -} - -impl DocumentRevisionWSDataStream { - #[allow(dead_code)] - pub fn new(conflict_controller: DocumentConflictController) -> Self { - Self { - conflict_controller: Arc::new(conflict_controller), - } - } -} - -impl RevisionWSDataStream for DocumentRevisionWSDataStream { - fn receive_push_revision(&self, revisions: Vec) -> BoxResultFuture<(), FlowyError> { - let resolver = self.conflict_controller.clone(); - Box::pin(async move { resolver.receive_revisions(revisions).await }) - } - - fn receive_ack(&self, rev_id: i64) -> BoxResultFuture<(), FlowyError> { - let resolver = self.conflict_controller.clone(); - Box::pin(async move { resolver.ack_revision(rev_id).await }) - } - - fn receive_new_user_connect( - &self, - _new_user: NewDocumentUser, - ) -> BoxResultFuture<(), FlowyError> { - // Do nothing by now, just a placeholder for future extension. - Box::pin(async move { Ok(()) }) - } - - fn pull_revisions_in_range(&self, range: RevisionRange) -> BoxResultFuture<(), FlowyError> { - let resolver = self.conflict_controller.clone(); - Box::pin(async move { resolver.send_revisions(range).await }) - } -} - -pub(crate) struct DocumentWSDataSink(pub(crate) Arc); -impl RevisionWebSocketSink for DocumentWSDataSink { - fn next(&self) -> FutureResult, FlowyError> { - let sink_provider = self.0.clone(); - FutureResult::new(async move { sink_provider.next().await }) - } -} - -struct DocumentConflictResolver { - edit_cmd_tx: EditorCommandSender, -} - -impl ConflictResolver for DocumentConflictResolver { - fn compose_operations( - &self, - operations: DeltaDocumentResolveOperations, - ) -> BoxResultFuture { - let tx = self.edit_cmd_tx.clone(); - let operations = operations.into_inner(); - Box::pin(async move { - let (ret, rx) = oneshot::channel(); - tx.send(EditorCommand::ComposeRemoteOperation { - client_operations: operations, - ret, - }) - .await - .map_err(internal_error)?; - let md5 = rx.await.map_err(|e| { - FlowyError::internal().context(format!("Compose operations failed: {}", e)) - })??; - Ok(md5) - }) - } - - fn transform_operations( - &self, - operations: DeltaDocumentResolveOperations, - ) -> BoxResultFuture, FlowyError> { - let tx = self.edit_cmd_tx.clone(); - let operations = operations.into_inner(); - Box::pin(async move { - let (ret, rx) = oneshot::channel::>(); - tx.send(EditorCommand::TransformOperations { operations, ret }) - .await - .map_err(internal_error)?; - let transformed_operations = rx.await.map_err(|e| { - FlowyError::internal().context(format!("Transform operations failed: {}", e)) - })??; - Ok(transformed_operations) - }) - } - - fn reset_operations( - &self, - operations: DeltaDocumentResolveOperations, - ) -> BoxResultFuture { - let tx = self.edit_cmd_tx.clone(); - let operations = operations.into_inner(); - Box::pin(async move { - let (ret, rx) = oneshot::channel(); - tx.send(EditorCommand::ResetOperations { operations, ret }) - .await - .map_err(internal_error)?; - let md5 = rx - .await - .map_err(|e| FlowyError::internal().context(format!("Reset operations failed: {}", e)))??; - Ok(md5) - }) - } -} diff --git a/frontend/rust-lib/flowy-document/src/services/migration.rs b/frontend/rust-lib/flowy-document/src/services/migration.rs deleted file mode 100644 index abc49fea82..0000000000 --- a/frontend/rust-lib/flowy-document/src/services/migration.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::services::delta_migration::DeltaRevisionMigration; -use crate::services::rev_sqlite::{DeltaRevisionSql, SQLiteDocumentRevisionPersistence}; -use crate::DocumentDatabase; -use bytes::Bytes; -use flowy_client_sync::make_operations_from_revisions; -use flowy_error::FlowyResult; -use flowy_revision_persistence::{RevisionDiskCache, SyncRecord}; -use flowy_sqlite::kv::KV; -use lib_infra::util::md5; -use revision_model::Revision; -use std::sync::Arc; - -const V1_MIGRATION: &str = "DOCUMENT_V1_MIGRATION"; -pub(crate) struct DocumentMigration { - user_id: i64, - database: Arc, -} - -impl DocumentMigration { - pub fn new(user_id: i64, database: Arc) -> Self { - let user_id = user_id.to_owned(); - Self { user_id, database } - } - - pub fn run_v1_migration(&self) -> FlowyResult<()> { - let key = migration_flag_key(self.user_id, V1_MIGRATION); - if KV::get_bool(&key) { - return Ok(()); - } - - let pool = self.database.db_pool()?; - let conn = &*pool.get()?; - let disk_cache = SQLiteDocumentRevisionPersistence::new(pool); - let documents = DeltaRevisionSql::read_all_documents(conn)?; - tracing::debug!("[Migration]: try migrate {} documents", documents.len()); - for revisions in documents { - if revisions.is_empty() { - continue; - } - - let document_id = revisions.first().unwrap().object_id.clone(); - if let Ok(delta) = make_operations_from_revisions(revisions) { - match DeltaRevisionMigration::run(delta) { - Ok(transaction) => { - let bytes = Bytes::from(transaction.to_bytes()?); - let md5 = format!("{:x}", md5::compute(&bytes)); - let revision = Revision::new(&document_id, 0, 1, bytes, md5); - let record = SyncRecord::new(revision); - match disk_cache.create_revision_records(vec![record]) { - Ok(_) => {}, - Err(err) => { - tracing::error!( - "[Document Migration]: Save revisions to disk failed {:?}", - err - ); - }, - } - }, - Err(err) => { - tracing::error!( - "[Document Migration]: Migrate revisions to transaction failed {:?}", - err - ); - }, - } - } - } - // - - KV::set_bool(&key, true); - Ok(()) - } -} -fn migration_flag_key(user_id: i64, version: &str) -> String { - md5(format!("{}{}", user_id, version,)) -} diff --git a/frontend/rust-lib/flowy-document/src/services/mod.rs b/frontend/rust-lib/flowy-document/src/services/mod.rs deleted file mode 100644 index d92b4a1a53..0000000000 --- a/frontend/rust-lib/flowy-document/src/services/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod migration; -mod persistence; - -pub use persistence::*; diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/delta_migration.rs b/frontend/rust-lib/flowy-document/src/services/persistence/delta_migration.rs deleted file mode 100644 index ddc932df23..0000000000 --- a/frontend/rust-lib/flowy-document/src/services/persistence/delta_migration.rs +++ /dev/null @@ -1,431 +0,0 @@ -use crate::editor::{DocumentNode, DocumentOperation}; -use flowy_error::FlowyResult; - -use lib_ot::core::{AttributeHashMap, DeltaOperation, Insert, Transaction}; -use lib_ot::text_delta::{DeltaTextOperation, DeltaTextOperations}; - -pub struct DeltaRevisionMigration(); - -impl DeltaRevisionMigration { - pub fn run(delta: DeltaTextOperations) -> FlowyResult { - let migrate_background_attribute = |insert: &mut Insert| { - if let Some(Some(color)) = insert - .attributes - .get("background") - .map(|value| value.str_value()) - { - insert.attributes.remove_key("background"); - insert.attributes.insert("backgroundColor", color); - } - }; - let migrate_strike_attribute = |insert: &mut Insert| { - if let Some(Some(_)) = insert - .attributes - .get("strike") - .map(|value| value.str_value()) - { - insert.attributes.remove_key("strike"); - insert.attributes.insert("strikethrough", true); - } - }; - - let migrate_link_attribute = |insert: &mut Insert| { - if let Some(Some(link)) = insert.attributes.get("link").map(|value| value.str_value()) { - insert.attributes.remove_key("link"); - insert.attributes.insert("href", link); - } - }; - - let migrate_list_attribute = - |attribute_node: &mut DocumentNode, value: &str, number_list_number: &mut usize| { - if value == "unchecked" { - *number_list_number = 0; - attribute_node.attributes.insert("subtype", "checkbox"); - attribute_node.attributes.insert("checkbox", false); - } - if value == "checked" { - *number_list_number = 0; - attribute_node.attributes.insert("subtype", "checkbox"); - attribute_node.attributes.insert("checkbox", true); - } - - if value == "bullet" { - *number_list_number = 0; - attribute_node.attributes.insert("subtype", "bulleted-list"); - } - - if value == "ordered" { - *number_list_number += 1; - attribute_node.attributes.insert("subtype", "number-list"); - attribute_node - .attributes - .insert("number", *number_list_number); - } - }; - - let generate_new_op_with_double_new_lines = |insert: &mut Insert| { - let pattern = "\n\n"; - let mut new_ops = vec![]; - if insert.s.as_str().contains(pattern) { - let insert_str = insert.s.clone(); - let insert_strings = insert_str.split(pattern).map(|s| s.to_owned()); - for (index, new_s) in insert_strings.enumerate() { - if index == 0 { - insert.s = new_s.into(); - } else { - new_ops.push(DeltaOperation::Insert(Insert { - s: new_s.into(), - attributes: AttributeHashMap::default(), - })); - } - } - } - new_ops - }; - - let create_text_node = |ops: Vec| { - let mut document_node = DocumentNode::new(); - document_node.node_type = "text".to_owned(); - ops.into_iter().for_each(|op| document_node.delta.add(op)); - document_node - }; - - let transform_op = |mut insert: Insert| { - // Rename the attribute name from background to backgroundColor - migrate_background_attribute(&mut insert); - migrate_strike_attribute(&mut insert); - migrate_link_attribute(&mut insert); - - let new_ops = generate_new_op_with_double_new_lines(&mut insert); - (DeltaOperation::Insert(insert), new_ops) - }; - let mut index: usize = 0; - let mut number_list_number = 0; - let mut editor_node = DocumentNode::new(); - editor_node.node_type = "editor".to_owned(); - - let mut transaction = Transaction::new(); - transaction.push_operation(DocumentOperation::Insert { - path: 0.into(), - nodes: vec![editor_node], - }); - - let mut iter = delta.ops.into_iter().enumerate(); - while let Some((_, op)) = iter.next() { - let mut document_node = create_text_node(vec![]); - let mut split_document_nodes = vec![]; - match op { - DeltaOperation::Delete(_) => tracing::warn!("Should not contain delete operation"), - DeltaOperation::Retain(_) => tracing::warn!("Should not contain retain operation"), - DeltaOperation::Insert(insert) => { - if insert.s.as_str() != "\n" { - let (op, new_ops) = transform_op(insert); - document_node.delta.add(op); - if !new_ops.is_empty() { - split_document_nodes.push(create_text_node(new_ops)); - } - } - - while let Some((_, DeltaOperation::Insert(insert))) = iter.next() { - if insert.s.as_str() != "\n" { - let (op, new_ops) = transform_op(insert); - document_node.delta.add(op); - - if !new_ops.is_empty() { - split_document_nodes.push(create_text_node(new_ops)); - } - } else { - let attribute_node = match split_document_nodes.last_mut() { - None => &mut document_node, - Some(split_document_node) => split_document_node, - }; - - if let Some(value) = insert.attributes.get("header") { - attribute_node.attributes.insert("subtype", "heading"); - if let Some(v) = value.int_value() { - number_list_number = 0; - attribute_node - .attributes - .insert("heading", format!("h{}", v)); - } - } - - if insert.attributes.get("blockquote").is_some() { - attribute_node.attributes.insert("subtype", "quote"); - } - - if let Some(value) = insert.attributes.get("list") { - if let Some(s) = value.str_value() { - migrate_list_attribute(attribute_node, &s, &mut number_list_number); - } - } - break; - } - } - }, - } - let mut operations = vec![document_node]; - operations.extend(split_document_nodes); - operations.into_iter().for_each(|node| { - // println!("{}", serde_json::to_string(&node).unwrap()); - let operation = DocumentOperation::Insert { - path: vec![0, index].into(), - nodes: vec![node], - }; - transaction.push_operation(operation); - index += 1; - }); - } - Ok(transaction) - } -} - -#[cfg(test)] -mod tests { - use crate::editor::Document; - use crate::services::delta_migration::DeltaRevisionMigration; - use lib_ot::text_delta::DeltaTextOperations; - - #[test] - fn transform_delta_to_transaction_test() { - let delta = DeltaTextOperations::from_json(DELTA_STR).unwrap(); - let transaction = DeltaRevisionMigration::run(delta).unwrap(); - let document = Document::from_transaction(transaction).unwrap(); - let s = document.get_content(true).unwrap(); - assert!(!s.is_empty()); - } - - const DELTA_STR: &str = r#"[ - { - "insert": "\n👋 Welcome to AppFlowy!" - }, - { - "insert": "\n", - "attributes": { - "header": 1 - } - }, - { - "insert": "\nHere are the basics" - }, - { - "insert": "\n", - "attributes": { - "header": 2 - } - }, - { - "insert": "Click anywhere and just start typing" - }, - { - "insert": "\n", - "attributes": { - "list": "unchecked" - } - }, - { - "insert": "Highlight", - "attributes": { - "background": "$fff2cd" - } - }, - { - "insert": " any text, and use the menu at the bottom to " - }, - { - "insert": "style", - "attributes": { - "italic": true - } - }, - { - "insert": " " - }, - { - "insert": "your", - "attributes": { - "bold": true - } - }, - { - "insert": " " - }, - { - "insert": "writing", - "attributes": { - "underline": true - } - }, - { - "insert": " " - }, - { - "insert": "however", - "attributes": { - "code": true - } - }, - { - "insert": " " - }, - { - "insert": "you", - "attributes": { - "strike": true - } - }, - { - "insert": " " - }, - { - "insert": "like", - "attributes": { - "background": "$e8e0ff" - } - }, - { - "insert": "\n", - "attributes": { - "list": "unchecked" - } - }, - { - "insert": "Click " - }, - { - "insert": "+ New Page", - "attributes": { - "background": "$defff1", - "bold": true - } - }, - { - "insert": " button at the bottom of your sidebar to add a new page" - }, - { - "insert": "\n", - "attributes": { - "list": "unchecked" - } - }, - { - "insert": "Click the " - }, - { - "insert": "'", - "attributes": { - "background": "$defff1" - } - }, - { - "insert": "+", - "attributes": { - "background": "$defff1", - "bold": true - } - }, - { - "insert": "'", - "attributes": { - "background": "$defff1" - } - }, - { - "insert": " next to any page title in the sidebar to quickly add a new subpage" - }, - { - "insert": "\n", - "attributes": { - "list": "unchecked" - } - }, - { - "insert": "\nHave a question? " - }, - { - "insert": "\n", - "attributes": { - "header": 2 - } - }, - { - "insert": "Click the " - }, - { - "insert": "'?'", - "attributes": { - "background": "$defff1", - "bold": true - } - }, - { - "insert": " at the bottom right for help and support.\n\nLike AppFlowy? Follow us:" - }, - { - "insert": "\n", - "attributes": { - "header": 2 - } - }, - { - "insert": "GitHub: https://github.com/AppFlowy-IO/appflowy" - }, - { - "insert": "\n", - "attributes": { - "blockquote": true - } - }, - { - "insert": "Twitter: https://twitter.com/appflowy" - }, - { - "insert": "\n", - "attributes": { - "blockquote": true - } - }, - { - "insert": "Newsletter: https://www.appflowy.io/blog" - }, - { - "insert": "\n", - "attributes": { - "blockquote": true - } - }, - { - "insert": "item 1" - }, - { - "insert": "\n", - "attributes": { - "list": "ordered" - } - }, - { - "insert": "item 2" - }, - { - "insert": "\n", - "attributes": { - "list": "ordered" - } - }, - { - "insert": "item3" - }, - { - "insert": "\n", - "attributes": { - "list": "ordered" - } - }, - { - "insert": "appflowy", - "attributes": { - "link": "https://www.appflowy.io/" - } - } -]"#; -} diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-document/src/services/persistence/mod.rs deleted file mode 100644 index e985c47480..0000000000 --- a/frontend/rust-lib/flowy-document/src/services/persistence/mod.rs +++ /dev/null @@ -1,26 +0,0 @@ -pub mod delta_migration; -pub mod rev_sqlite; - -use crate::services::migration::DocumentMigration; -use crate::DocumentDatabase; -use flowy_error::FlowyResult; -use std::sync::Arc; - -pub struct DocumentPersistence { - pub database: Arc, -} - -impl DocumentPersistence { - pub fn new(database: Arc) -> Self { - Self { database } - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub fn initialize(&self, user_id: i64) -> FlowyResult<()> { - let migration = DocumentMigration::new(user_id, self.database.clone()); - if let Err(e) = migration.run_v1_migration() { - tracing::error!("[Document Migration]: run v1 migration failed: {:?}", e); - } - Ok(()) - } -} diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v0.rs b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v0.rs deleted file mode 100644 index ab59cf10c0..0000000000 --- a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v0.rs +++ /dev/null @@ -1,301 +0,0 @@ -use bytes::Bytes; -use diesel::{sql_types::Integer, update, SqliteConnection}; -use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_revision_persistence::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; -use flowy_sqlite::{ - impl_sql_integer_expression, insert_or_ignore_into, - prelude::*, - schema::{rev_table, rev_table::dsl}, - ConnectionPool, -}; -use lib_infra::util::md5; -use revision_model::{Revision, RevisionRange}; -use std::collections::HashMap; -use std::sync::Arc; - -pub struct SQLiteDeltaDocumentRevisionPersistence { - pub(crate) pool: Arc, -} - -impl RevisionDiskCache> for SQLiteDeltaDocumentRevisionPersistence { - type Error = FlowyError; - - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - DeltaRevisionSql::create(revision_records, &conn)?; - Ok(()) - } - - fn get_connection(&self) -> Result, Self::Error> { - Ok(self.pool.clone()) - } - - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - let records = DeltaRevisionSql::read(object_id, rev_ids, &conn)?; - Ok(records) - } - - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - let revisions = DeltaRevisionSql::read_with_range(object_id, range.clone(), conn)?; - Ok(revisions) - } - - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - let conn = &*self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - for changeset in changesets { - DeltaRevisionSql::update(changeset, conn)?; - } - Ok(()) - })?; - Ok(()) - } - - fn delete_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result<(), Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - DeltaRevisionSql::delete(object_id, rev_ids, conn)?; - Ok(()) - } - - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - DeltaRevisionSql::delete(object_id, deleted_rev_ids, &conn)?; - DeltaRevisionSql::create(inserted_records, &conn)?; - Ok(()) - }) - } -} - -impl SQLiteDeltaDocumentRevisionPersistence { - pub fn new(pool: Arc) -> Self { - Self { pool } - } -} - -pub struct DeltaRevisionSql {} - -impl DeltaRevisionSql { - fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { - // Batch insert: https://diesel.rs/guides/all-about-inserts.html - let records = revision_records - .into_iter() - .map(|record| { - tracing::trace!( - "[TextRevisionSql] create revision: {}:{:?}", - record.revision.object_id, - record.revision.rev_id - ); - let rev_state: TextRevisionState = record.state.into(); - ( - dsl::doc_id.eq(record.revision.object_id), - dsl::base_rev_id.eq(record.revision.base_rev_id), - dsl::rev_id.eq(record.revision.rev_id), - dsl::data.eq(record.revision.bytes), - dsl::state.eq(rev_state), - dsl::ty.eq(RevTableType::Local), - ) - }) - .collect::>(); - - let _ = insert_or_ignore_into(dsl::rev_table) - .values(&records) - .execute(conn)?; - Ok(()) - } - - fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { - let state: TextRevisionState = changeset.state.clone().into(); - let filter = dsl::rev_table - .filter(dsl::rev_id.eq(changeset.rev_id)) - .filter(dsl::doc_id.eq(changeset.object_id)); - let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?; - tracing::debug!( - "[TextRevisionSql] update revision:{} state:to {:?}", - changeset.rev_id, - changeset.state - ); - Ok(()) - } - - fn read( - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let mut sql = dsl::rev_table - .filter(dsl::doc_id.eq(object_id)) - .into_boxed(); - if let Some(rev_ids) = rev_ids { - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - let rows = sql.order(dsl::rev_id.asc()).load::(conn)?; - let records = rows - .into_iter() - .map(mk_revision_record_from_table) - .collect::>(); - - Ok(records) - } - - fn read_with_range( - object_id: &str, - range: RevisionRange, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let rev_tables = dsl::rev_table - .filter(dsl::rev_id.ge(range.start)) - .filter(dsl::rev_id.le(range.end)) - .filter(dsl::doc_id.eq(object_id)) - .order(dsl::rev_id.asc()) - .load::(conn)?; - - let revisions = rev_tables - .into_iter() - .map(mk_revision_record_from_table) - .collect::>(); - Ok(revisions) - } - - fn delete( - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result<(), FlowyError> { - let mut sql = diesel::delete(dsl::rev_table).into_boxed(); - sql = sql.filter(dsl::doc_id.eq(object_id)); - - if let Some(rev_ids) = rev_ids { - tracing::trace!( - "[TextRevisionSql] Delete revision: {}:{:?}", - object_id, - rev_ids - ); - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - - let affected_row = sql.execute(conn)?; - tracing::trace!("[TextRevisionSql] Delete {} rows", affected_row); - Ok(()) - } - - pub fn read_all_documents(conn: &SqliteConnection) -> Result>, FlowyError> { - let rev_tables = dsl::rev_table - .order(dsl::rev_id.asc()) - .load::(conn)?; - let mut document_map = HashMap::new(); - for rev_table in rev_tables { - document_map - .entry(rev_table.doc_id.clone()) - .or_insert_with(Vec::new) - .push(rev_table); - } - let mut documents = vec![]; - for rev_tables in document_map.into_values() { - let revisions = rev_tables - .into_iter() - .map(|table| { - let record = mk_revision_record_from_table(table); - record.revision - }) - .collect::>(); - documents.push(revisions); - } - - Ok(documents) - } -} - -#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[table_name = "rev_table"] -struct RevisionTable { - id: i32, - doc_id: String, - base_rev_id: i64, - rev_id: i64, - data: Vec, - state: TextRevisionState, - ty: RevTableType, // Deprecated -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] -#[repr(i32)] -#[sql_type = "Integer"] -enum TextRevisionState { - Sync = 0, - Ack = 1, -} -impl_sql_integer_expression!(TextRevisionState); -impl_rev_state_map!(TextRevisionState); - -impl std::default::Default for TextRevisionState { - fn default() -> Self { - TextRevisionState::Sync - } -} - -fn mk_revision_record_from_table(table: RevisionTable) -> SyncRecord { - let md5 = md5(&table.data); - let revision = Revision::new( - &table.doc_id, - table.base_rev_id, - table.rev_id, - Bytes::from(table.data), - md5, - ); - SyncRecord { - revision, - state: table.state.into(), - write_to_disk: false, - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] -#[repr(i32)] -#[sql_type = "Integer"] -pub enum RevTableType { - Local = 0, - Remote = 1, -} -impl_sql_integer_expression!(RevTableType); - -impl std::default::Default for RevTableType { - fn default() -> Self { - RevTableType::Local - } -} - -impl std::convert::From for RevTableType { - fn from(value: i32) -> Self { - match value { - 0 => RevTableType::Local, - 1 => RevTableType::Remote, - o => { - tracing::error!( - "Unsupported rev type {}, fallback to RevTableType::Local", - o - ); - RevTableType::Local - }, - } - } -} diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v1.rs b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v1.rs deleted file mode 100644 index bab2ca95b7..0000000000 --- a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v1.rs +++ /dev/null @@ -1,243 +0,0 @@ -use bytes::Bytes; -use diesel::{sql_types::Integer, update, SqliteConnection}; -use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_revision_persistence::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; -use flowy_sqlite::{ - impl_sql_integer_expression, insert_or_ignore_into, - prelude::*, - schema::{document_rev_table, document_rev_table::dsl}, - ConnectionPool, -}; -use lib_infra::util::md5; -use revision_model::{Revision, RevisionRange}; -use std::sync::Arc; - -pub struct SQLiteDocumentRevisionPersistence { - pub(crate) pool: Arc, -} - -impl RevisionDiskCache> for SQLiteDocumentRevisionPersistence { - type Error = FlowyError; - - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - DocumentRevisionSql::create(revision_records, &conn)?; - Ok(()) - } - - fn get_connection(&self) -> Result, Self::Error> { - Ok(self.pool.clone()) - } - - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - let records = DocumentRevisionSql::read(object_id, rev_ids, &conn)?; - Ok(records) - } - - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - let revisions = DocumentRevisionSql::read_with_range(object_id, range.clone(), conn)?; - Ok(revisions) - } - - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - let conn = &*self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - for changeset in changesets { - DocumentRevisionSql::update(changeset, conn)?; - } - Ok(()) - })?; - Ok(()) - } - - fn delete_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result<(), Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - DocumentRevisionSql::delete(object_id, rev_ids, conn)?; - Ok(()) - } - - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - DocumentRevisionSql::delete(object_id, deleted_rev_ids, &conn)?; - DocumentRevisionSql::create(inserted_records, &conn)?; - Ok(()) - }) - } -} - -impl SQLiteDocumentRevisionPersistence { - pub fn new(pool: Arc) -> Self { - Self { pool } - } -} - -struct DocumentRevisionSql {} - -impl DocumentRevisionSql { - fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { - // Batch insert: https://diesel.rs/guides/all-about-inserts.html - let records = revision_records - .into_iter() - .map(|record| { - tracing::trace!( - "[DocumentRevisionSql] create revision: {}:{:?}", - record.revision.object_id, - record.revision.rev_id - ); - let rev_state: DocumentRevisionState = record.state.into(); - ( - dsl::document_id.eq(record.revision.object_id), - dsl::base_rev_id.eq(record.revision.base_rev_id), - dsl::rev_id.eq(record.revision.rev_id), - dsl::data.eq(record.revision.bytes), - dsl::state.eq(rev_state), - ) - }) - .collect::>(); - - let _ = insert_or_ignore_into(dsl::document_rev_table) - .values(&records) - .execute(conn)?; - Ok(()) - } - - fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { - let state: DocumentRevisionState = changeset.state.clone().into(); - let filter = dsl::document_rev_table - .filter(dsl::rev_id.eq(changeset.rev_id)) - .filter(dsl::document_id.eq(changeset.object_id)); - let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?; - tracing::debug!( - "[DocumentRevisionSql] update revision:{} state:to {:?}", - changeset.rev_id, - changeset.state - ); - Ok(()) - } - - fn read( - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let mut sql = dsl::document_rev_table - .filter(dsl::document_id.eq(object_id)) - .into_boxed(); - if let Some(rev_ids) = rev_ids { - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - let rows = sql - .order(dsl::rev_id.asc()) - .load::(conn)?; - let records = rows - .into_iter() - .map(mk_revision_record_from_table) - .collect::>(); - - Ok(records) - } - - fn read_with_range( - object_id: &str, - range: RevisionRange, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let rev_tables = dsl::document_rev_table - .filter(dsl::rev_id.ge(range.start)) - .filter(dsl::rev_id.le(range.end)) - .filter(dsl::document_id.eq(object_id)) - .order(dsl::rev_id.asc()) - .load::(conn)?; - - let revisions = rev_tables - .into_iter() - .map(mk_revision_record_from_table) - .collect::>(); - Ok(revisions) - } - - fn delete( - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result<(), FlowyError> { - let mut sql = diesel::delete(dsl::document_rev_table).into_boxed(); - sql = sql.filter(dsl::document_id.eq(object_id)); - - if let Some(rev_ids) = rev_ids { - tracing::trace!( - "[DocumentRevisionSql] Delete revision: {}:{:?}", - object_id, - rev_ids - ); - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - - let affected_row = sql.execute(conn)?; - tracing::trace!("[DocumentRevisionSql] Delete {} rows", affected_row); - Ok(()) - } -} - -#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[table_name = "document_rev_table"] -struct DocumentRevisionTable { - id: i32, - document_id: String, - base_rev_id: i64, - rev_id: i64, - data: Vec, - state: DocumentRevisionState, -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] -#[repr(i32)] -#[sql_type = "Integer"] -enum DocumentRevisionState { - Sync = 0, - Ack = 1, -} -impl_sql_integer_expression!(DocumentRevisionState); -impl_rev_state_map!(DocumentRevisionState); - -impl std::default::Default for DocumentRevisionState { - fn default() -> Self { - DocumentRevisionState::Sync - } -} - -fn mk_revision_record_from_table(table: DocumentRevisionTable) -> SyncRecord { - let md5 = md5(&table.data); - let revision = Revision::new( - &table.document_id, - table.base_rev_id, - table.rev_id, - Bytes::from(table.data), - md5, - ); - SyncRecord { - revision, - state: table.state.into(), - write_to_disk: false, - } -} diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_snapshot.rs b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_snapshot.rs deleted file mode 100644 index 719a47a15b..0000000000 --- a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_snapshot.rs +++ /dev/null @@ -1,97 +0,0 @@ -#![allow(clippy::unused_unit)] -use bytes::Bytes; -use flowy_error::{internal_error, FlowyResult}; -use flowy_revision::{RevisionSnapshotData, RevisionSnapshotPersistence}; -use flowy_sqlite::{ - prelude::*, - schema::{document_rev_snapshot, document_rev_snapshot::dsl}, - ConnectionPool, -}; -use lib_infra::util::timestamp; -use std::sync::Arc; - -pub struct SQLiteDocumentRevisionSnapshotPersistence { - object_id: String, - pool: Arc, -} - -impl SQLiteDocumentRevisionSnapshotPersistence { - pub fn new(object_id: &str, pool: Arc) -> Self { - Self { - object_id: object_id.to_string(), - pool, - } - } - - fn gen_snapshot_id(&self, rev_id: i64) -> String { - format!("{}:{}", self.object_id, rev_id) - } -} - -impl RevisionSnapshotPersistence for SQLiteDocumentRevisionSnapshotPersistence { - fn should_generate_snapshot_from_range(&self, start_rev_id: i64, current_rev_id: i64) -> bool { - (current_rev_id - start_rev_id) >= 150 - } - - fn write_snapshot(&self, rev_id: i64, data: Vec) -> FlowyResult<()> { - let conn = self.pool.get().map_err(internal_error)?; - let snapshot_id = self.gen_snapshot_id(rev_id); - let timestamp = timestamp(); - let record = ( - dsl::snapshot_id.eq(&snapshot_id), - dsl::object_id.eq(&self.object_id), - dsl::rev_id.eq(rev_id), - dsl::base_rev_id.eq(rev_id), - dsl::timestamp.eq(timestamp), - dsl::data.eq(data), - ); - let _ = insert_or_ignore_into(dsl::document_rev_snapshot) - .values(record) - .execute(&*conn)?; - Ok(()) - } - - fn read_snapshot(&self, rev_id: i64) -> FlowyResult> { - let conn = self.pool.get().map_err(internal_error)?; - let snapshot_id = self.gen_snapshot_id(rev_id); - let record = dsl::document_rev_snapshot - .filter(dsl::snapshot_id.eq(&snapshot_id)) - .first::(&*conn)?; - - Ok(Some(record.into())) - } - - fn read_last_snapshot(&self) -> FlowyResult> { - let conn = self.pool.get().map_err(internal_error)?; - let latest_record = dsl::document_rev_snapshot - .filter(dsl::object_id.eq(&self.object_id)) - .order(dsl::timestamp.desc()) - // .select(max(dsl::rev_id)) - // .select((dsl::id, dsl::object_id, dsl::rev_id, dsl::data)) - .first::(&*conn)?; - Ok(Some(latest_record.into())) - } -} - -#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[table_name = "document_rev_snapshot"] -#[primary_key("snapshot_id")] -struct DocumentSnapshotRecord { - snapshot_id: String, - object_id: String, - rev_id: i64, - base_rev_id: i64, - timestamp: i64, - data: Vec, -} - -impl std::convert::From for RevisionSnapshotData { - fn from(record: DocumentSnapshotRecord) -> Self { - RevisionSnapshotData { - rev_id: record.rev_id, - base_rev_id: record.base_rev_id, - timestamp: record.timestamp, - data: Bytes::from(record.data), - } - } -} diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/mod.rs b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/mod.rs deleted file mode 100644 index 8102291344..0000000000 --- a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod document_rev_sqlite_v0; -mod document_rev_sqlite_v1; -mod document_snapshot; - -pub use document_rev_sqlite_v0::*; -pub use document_rev_sqlite_v1::*; -pub use document_snapshot::*; diff --git a/frontend/rust-lib/flowy-document/tests/editor/attribute_test.rs b/frontend/rust-lib/flowy-document/tests/editor/attribute_test.rs deleted file mode 100644 index ecf5aeb238..0000000000 --- a/frontend/rust-lib/flowy-document/tests/editor/attribute_test.rs +++ /dev/null @@ -1,800 +0,0 @@ -#![cfg_attr(rustfmt, rustfmt::skip)] -use crate::editor::{TestBuilder, TestOp::*}; -use flowy_client_sync::client_document::{NewlineDocument, EmptyDocument}; -use lib_ot::core::{Interval, OperationTransform, NEW_LINE, WHITESPACE, OTString}; -use unicode_segmentation::UnicodeSegmentation; -use lib_ot::text_delta::DeltaTextOperations; - -#[test] -fn attributes_bold_added() { - let ops = vec![ - Insert(0, "123456", 0), - Bold(0, Interval::new(3, 5), true), - AssertDocJson( - 0, - r#"[ - {"insert":"123"}, - {"insert":"45","attributes":{"bold":true}}, - {"insert":"6"} - ]"#, - ), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_bold_added_and_invert_all() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":true}}]"#), - Bold(0, Interval::new(0, 3), false), - AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":false}}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_bold_added_and_invert_partial_suffix() { - let ops = vec![ - Insert(0, "1234", 0), - Bold(0, Interval::new(0, 4), true), - AssertDocJson(0, r#"[{"insert":"1234","attributes":{"bold":true}}]"#), - Bold(0, Interval::new(2, 4), false), - AssertDocJson(0, r#"[{"insert":"12","attributes":{"bold":true}},{"insert":"34","attributes":{"bold":false}}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_bold_added_and_invert_partial_suffix2() { - let ops = vec![ - Insert(0, "1234", 0), - Bold(0, Interval::new(0, 4), true), - AssertDocJson(0, r#"[{"insert":"1234","attributes":{"bold":true}}]"#), - Bold(0, Interval::new(2, 4), false), - AssertDocJson(0, r#"[{"insert":"12","attributes":{"bold":true}},{"insert":"34","attributes":{"bold":false}}]"#), - Bold(0, Interval::new(2, 4), true), - AssertDocJson(0, r#"[{"insert":"1234","attributes":{"bold":true}}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_bold_added_with_new_line() { - let ops = vec![ - Insert(0, "123456", 0), - Bold(0, Interval::new(0, 6), true), - AssertDocJson( - 0, - r#"[{"insert":"123456","attributes":{"bold":true}},{"insert":"\n"}]"#, - ), - Insert(0, "\n", 3), - AssertDocJson( - 0, - r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"\n"},{"insert":"456","attributes":{"bold":true}},{"insert":"\n"}]"#, - ), - Insert(0, "\n", 4), - AssertDocJson( - 0, - r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"\n\n"},{"insert":"456","attributes":{"bold":true}},{"insert":"\n"}]"#, - ), - Insert(0, "a", 4), - AssertDocJson( - 0, - r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"\na\n"},{"insert":"456","attributes":{"bold":true}},{"insert":"\n"}]"#, - ), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_bold_added_and_invert_partial_prefix() { - let ops = vec![ - Insert(0, "1234", 0), - Bold(0, Interval::new(0, 4), true), - AssertDocJson(0, r#"[{"insert":"1234","attributes":{"bold":true}}]"#), - Bold(0, Interval::new(0, 2), false), - AssertDocJson(0, r#"[{"insert":"12","attributes":{"bold":false}},{"insert":"34","attributes":{"bold":true}}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_bold_added_consecutive() { - let ops = vec![ - Insert(0, "1234", 0), - Bold(0, Interval::new(0, 1), true), - AssertDocJson(0, r#"[{"insert":"1","attributes":{"bold":true}},{"insert":"234"}]"#), - Bold(0, Interval::new(1, 2), true), - AssertDocJson(0, r#"[{"insert":"12","attributes":{"bold":true}},{"insert":"34"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_bold_added_italic() { - let ops = vec![ - Insert(0, "1234", 0), - Bold(0, Interval::new(0, 4), true), - Italic(0, Interval::new(0, 4), true), - AssertDocJson( - 0, - r#"[{"insert":"1234","attributes":{"italic":true,"bold":true}},{"insert":"\n"}]"#, - ), - Insert(0, "5678", 4), - AssertDocJson( - 0, - r#"[{"insert":"12345678","attributes":{"bold":true,"italic":true}},{"insert":"\n"}]"#, - ), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_bold_added_italic2() { - let ops = vec![ - Insert(0, "123456", 0), - Bold(0, Interval::new(0, 6), true), - AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":true}}]"#), - Italic(0, Interval::new(0, 2), true), - AssertDocJson( - 0, - r#"[ - {"insert":"12","attributes":{"italic":true,"bold":true}}, - {"insert":"3456","attributes":{"bold":true}}] - "#, - ), - Italic(0, Interval::new(4, 6), true), - AssertDocJson( - 0, - r#"[ - {"insert":"12","attributes":{"italic":true,"bold":true}}, - {"insert":"34","attributes":{"bold":true}}, - {"insert":"56","attributes":{"italic":true,"bold":true}}] - "#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_bold_added_italic3() { - let ops = vec![ - Insert(0, "123456789", 0), - Bold(0, Interval::new(0, 5), true), - Italic(0, Interval::new(0, 2), true), - AssertDocJson( - 0, - r#"[ - {"insert":"12","attributes":{"bold":true,"italic":true}}, - {"insert":"345","attributes":{"bold":true}},{"insert":"6789"}] - "#, - ), - Italic(0, Interval::new(2, 4), true), - AssertDocJson( - 0, - r#"[ - {"insert":"1234","attributes":{"bold":true,"italic":true}}, - {"insert":"5","attributes":{"bold":true}}, - {"insert":"6789"}] - "#, - ), - Bold(0, Interval::new(7, 9), true), - AssertDocJson( - 0, - r#"[ - {"insert":"1234","attributes":{"bold":true,"italic":true}}, - {"insert":"5","attributes":{"bold":true}}, - {"insert":"67"}, - {"insert":"89","attributes":{"bold":true}}] - "#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_bold_added_italic_delete() { - let ops = vec![ - Insert(0, "123456789", 0), - Bold(0, Interval::new(0, 5), true), - Italic(0, Interval::new(0, 2), true), - AssertDocJson( - 0, - r#"[ - {"insert":"12","attributes":{"italic":true,"bold":true}}, - {"insert":"345","attributes":{"bold":true}},{"insert":"6789"}] - "#, - ), - Italic(0, Interval::new(2, 4), true), - AssertDocJson( - 0, - r#"[ - {"insert":"1234","attributes":{"bold":true,"italic":true}} - ,{"insert":"5","attributes":{"bold":true}},{"insert":"6789"}]"#, - ), - Bold(0, Interval::new(7, 9), true), - AssertDocJson( - 0, - r#"[ - {"insert":"1234","attributes":{"bold":true,"italic":true}}, - {"insert":"5","attributes":{"bold":true}},{"insert":"67"}, - {"insert":"89","attributes":{"bold":true}}] - "#, - ), - Delete(0, Interval::new(0, 5)), - AssertDocJson(0, r#"[{"insert":"67"},{"insert":"89","attributes":{"bold":true}}]"#), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_merge_inserted_text_with_same_attribute() { - let ops = vec![ - InsertBold(0, "123", Interval::new(0, 3)), - AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":true}}]"#), - InsertBold(0, "456", Interval::new(3, 6)), - AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":true}}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_compose_attr_attributes_with_attr_attributes_test() { - let ops = vec![ - InsertBold(0, "123456", Interval::new(0, 6)), - AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":true}}]"#), - InsertBold(1, "7", Interval::new(0, 1)), - AssertDocJson(1, r#"[{"insert":"7","attributes":{"bold":true}}]"#), - Transform(0, 1), - AssertDocJson(0, r#"[{"insert":"1234567","attributes":{"bold":true}}]"#), - AssertDocJson(1, r#"[{"insert":"1234567","attributes":{"bold":true}}]"#), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_compose_attr_attributes_with_attr_attributes_test2() { - let ops = vec![ - Insert(0, "123456", 0), - Bold(0, Interval::new(0, 6), true), - Italic(0, Interval::new(0, 2), true), - Italic(0, Interval::new(4, 6), true), - AssertDocJson( - 0, - r#"[ - {"insert":"12","attributes":{"bold":true,"italic":true}}, - {"insert":"34","attributes":{"bold":true}}, - {"insert":"56","attributes":{"italic":true,"bold":true}}] - "#, - ), - InsertBold(1, "7", Interval::new(0, 1)), - AssertDocJson(1, r#"[{"insert":"7","attributes":{"bold":true}}]"#), - Transform(0, 1), - AssertDocJson( - 0, - r#"[ - {"insert":"12","attributes":{"italic":true,"bold":true}}, - {"insert":"34","attributes":{"bold":true}}, - {"insert":"56","attributes":{"italic":true,"bold":true}}, - {"insert":"7","attributes":{"bold":true}}] - "#, - ), - AssertDocJson( - 1, - r#"[ - {"insert":"12","attributes":{"italic":true,"bold":true}}, - {"insert":"34","attributes":{"bold":true}}, - {"insert":"56","attributes":{"italic":true,"bold":true}}, - {"insert":"7","attributes":{"bold":true}}] - "#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_compose_attr_attributes_with_no_attr_attributes_test() { - let expected = r#"[{"insert":"123456","attributes":{"bold":true}},{"insert":"7"}]"#; - - let ops = vec![ - InsertBold(0, "123456", Interval::new(0, 6)), - AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":true}}]"#), - Insert(1, "7", 0), - AssertDocJson(1, r#"[{"insert":"7"}]"#), - Transform(0, 1), - AssertDocJson(0, expected), - AssertDocJson(1, expected), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_replace_heading() { - let ops = vec![ - InsertBold(0, "123456", Interval::new(0, 6)), - AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":true}}]"#), - Delete(0, Interval::new(0, 2)), - AssertDocJson(0, r#"[{"insert":"3456","attributes":{"bold":true}}]"#), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_replace_trailing() { - let ops = vec![ - InsertBold(0, "123456", Interval::new(0, 6)), - AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":true}}]"#), - Delete(0, Interval::new(5, 6)), - AssertDocJson(0, r#"[{"insert":"12345","attributes":{"bold":true}}]"#), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_replace_middle() { - let ops = vec![ - InsertBold(0, "123456", Interval::new(0, 6)), - AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":true}}]"#), - Delete(0, Interval::new(0, 2)), - AssertDocJson(0, r#"[{"insert":"3456","attributes":{"bold":true}}]"#), - Delete(0, Interval::new(2, 4)), - AssertDocJson(0, r#"[{"insert":"34","attributes":{"bold":true}}]"#), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_replace_all() { - let ops = vec![ - InsertBold(0, "123456", Interval::new(0, 6)), - AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":true}}]"#), - Delete(0, Interval::new(0, 6)), - AssertDocJson(0, r#"[]"#), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_replace_with_text() { - let ops = vec![ - InsertBold(0, "123456", Interval::new(0, 6)), - AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":true}}]"#), - Replace(0, Interval::new(0, 3), "ab"), - AssertDocJson(0, r#"[{"insert":"ab"},{"insert":"456","attributes":{"bold":true}}]"#), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_header_insert_newline_at_middle() { - let ops = vec![ - Insert(0, "123456", 0), - Header(0, Interval::new(0, 6), 1), - AssertDocJson(0, r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":1}}]"#), - Insert(0, "\n", 3), - AssertDocJson( - 0, - r#"[{"insert":"123"},{"insert":"\n","attributes":{"header":1}},{"insert":"456"},{"insert":"\n","attributes":{"header":1}}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_header_insert_double_newline_at_middle() { - let ops = vec![ - Insert(0, "123456", 0), - Header(0, Interval::new(0, 6), 1), - Insert(0, "\n", 3), - AssertDocJson( - 0, - r#"[{"insert":"123"},{"insert":"\n","attributes":{"header":1}},{"insert":"456"},{"insert":"\n","attributes":{"header":1}}]"#, - ), - Insert(0, "\n", 4), - AssertDocJson( - 0, - r#"[{"insert":"123"},{"insert":"\n\n","attributes":{"header":1}},{"insert":"456"},{"insert":"\n","attributes":{"header":1}}]"#, - ), - Insert(0, "\n", 4), - AssertDocJson( - 0, - r#"[{"insert":"123"},{"insert":"\n\n","attributes":{"header":1}},{"insert":"\n456"},{"insert":"\n","attributes":{"header":1}}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_header_insert_newline_at_trailing() { - let ops = vec![ - Insert(0, "123456", 0), - Header(0, Interval::new(0, 6), 1), - Insert(0, "\n", 6), - AssertDocJson( - 0, - r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":1}},{"insert":"\n"}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_header_insert_double_newline_at_trailing() { - let ops = vec![ - Insert(0, "123456", 0), - Header(0, Interval::new(0, 6), 1), - Insert(0, "\n", 6), - Insert(0, "\n", 7), - AssertDocJson( - 0, - r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":1}},{"insert":"\n\n"}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_link_added() { - let ops = vec![ - Insert(0, "123456", 0), - Link(0, Interval::new(0, 6), "https://appflowy.io"), - AssertDocJson( - 0, - r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_link_format_with_bold() { - let ops = vec![ - Insert(0, "123456", 0), - Link(0, Interval::new(0, 6), "https://appflowy.io"), - Bold(0, Interval::new(0, 3), true), - AssertDocJson( - 0, - r#"[ - {"insert":"123","attributes":{"bold":true,"link":"https://appflowy.io"}}, - {"insert":"456","attributes":{"link":"https://appflowy.io"}}, - {"insert":"\n"}] - "#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_link_insert_char_at_head() { - let ops = vec![ - Insert(0, "123456", 0), - Link(0, Interval::new(0, 6), "https://appflowy.io"), - AssertDocJson( - 0, - r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#, - ), - Insert(0, "a", 0), - AssertDocJson( - 0, - r#"[{"insert":"a"},{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_link_insert_char_at_middle() { - let ops = vec![ - Insert(0, "1256", 0), - Link(0, Interval::new(0, 4), "https://appflowy.io"), - Insert(0, "34", 2), - AssertDocJson( - 0, - r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_link_insert_char_at_trailing() { - let ops = vec![ - Insert(0, "123456", 0), - Link(0, Interval::new(0, 6), "https://appflowy.io"), - AssertDocJson( - 0, - r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#, - ), - Insert(0, "a", 6), - AssertDocJson( - 0, - r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"a\n"}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_link_insert_newline_at_middle() { - let ops = vec![ - Insert(0, "123456", 0), - Link(0, Interval::new(0, 6), "https://appflowy.io"), - Insert(0, NEW_LINE, 3), - AssertDocJson( - 0, - r#"[{"insert":"123","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"},{"insert":"456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_link_auto_format() { - let site = "https://appflowy.io"; - let ops = vec![ - Insert(0, site, 0), - AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#), - Insert(0, WHITESPACE, site.len()), - AssertDocJson( - 0, - r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io/"}},{"insert":" \n"}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_link_auto_format_exist() { - let site = "https://appflowy.io"; - let ops = vec![ - Insert(0, site, 0), - Link(0, Interval::new(0, site.len()), site), - Insert(0, WHITESPACE, site.len()), - AssertDocJson( - 0, - r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io/"}},{"insert":" \n"}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_link_auto_format_exist2() { - let site = "https://appflowy.io"; - let ops = vec![ - Insert(0, site, 0), - Link(0, Interval::new(0, site.len() / 2), site), - Insert(0, WHITESPACE, site.len()), - AssertDocJson( - 0, - r#"[{"insert":"https://a","attributes":{"link":"https://appflowy.io"}},{"insert":"ppflowy.io \n"}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_bullet_added() { - let ops = vec![ - Insert(0, "12", 0), - Bullet(0, Interval::new(0, 1), true), - AssertDocJson(0, r#"[{"insert":"12"},{"insert":"\n","attributes":{"list":"bullet"}}]"#), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_bullet_added_2() { - let ops = vec![ - Insert(0, "1", 0), - Bullet(0, Interval::new(0, 1), true), - AssertDocJson(0, r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}}]"#), - Insert(0, NEW_LINE, 1), - AssertDocJson( - 0, - r#"[{"insert":"1"},{"insert":"\n\n","attributes":{"list":"bullet"}}]"#, - ), - Insert(0, "2", 2), - AssertDocJson( - 0, - r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_bullet_remove_partial() { - let ops = vec![ - Insert(0, "1", 0), - Bullet(0, Interval::new(0, 1), true), - Insert(0, NEW_LINE, 1), - Insert(0, "2", 2), - Bullet(0, Interval::new(2, 3), false), - AssertDocJson( - 0, - r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2\n"}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_bullet_auto_exit() { - let ops = vec![ - Insert(0, "1", 0), - Bullet(0, Interval::new(0, 1), true), - Insert(0, NEW_LINE, 1), - Insert(0, NEW_LINE, 2), - AssertDocJson( - 0, - r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"\n"}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_preserve_block_when_insert_newline_inside() { - let ops = vec![ - Insert(0, "12", 0), - Bullet(0, Interval::new(0, 2), true), - Insert(0, NEW_LINE, 2), - AssertDocJson( - 0, - r#"[{"insert":"12"},{"insert":"\n\n","attributes":{"list":"bullet"}}]"#, - ), - Insert(0, "34", 3), - AssertDocJson( - 0, - r#"[ - {"insert":"12"},{"insert":"\n","attributes":{"list":"bullet"}}, - {"insert":"34"},{"insert":"\n","attributes":{"list":"bullet"}} - ]"#, - ), - Insert(0, NEW_LINE, 3), - AssertDocJson( - 0, - r#"[ - {"insert":"12"},{"insert":"\n\n","attributes":{"list":"bullet"}}, - {"insert":"34"},{"insert":"\n","attributes":{"list":"bullet"}} - ]"#, - ), - Insert(0, "ab", 3), - AssertDocJson( - 0, - r#"[ - {"insert":"12"},{"insert":"\n","attributes":{"list":"bullet"}}, - {"insert":"ab"},{"insert":"\n","attributes":{"list":"bullet"}}, - {"insert":"34"},{"insert":"\n","attributes":{"list":"bullet"}} - ]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_preserve_header_format_on_merge() { - let ops = vec![ - Insert(0, "123456", 0), - Header(0, Interval::new(0, 6), 1), - Insert(0, NEW_LINE, 3), - AssertDocJson( - 0, - r#"[{"insert":"123"},{"insert":"\n","attributes":{"header":1}},{"insert":"456"},{"insert":"\n","attributes":{"header":1}}]"#, - ), - Delete(0, Interval::new(3, 4)), - AssertDocJson(0, r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":1}}]"#), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_format_emoji() { - let emoji_s = "👋 "; - let s: OTString = emoji_s.into(); - let len = s.utf16_len(); - assert_eq!(3, len); - assert_eq!(2, s.graphemes(true).count()); - - let ops = vec![ - Insert(0, emoji_s, 0), - AssertDocJson(0, r#"[{"insert":"👋 \n"}]"#), - Header(0, Interval::new(0, len), 1), - AssertDocJson( - 0, - r#"[{"insert":"👋 "},{"insert":"\n","attributes":{"header":1}}]"#, - ), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_preserve_list_format_on_merge() { - let ops = vec![ - Insert(0, "123456", 0), - Bullet(0, Interval::new(0, 6), true), - Insert(0, NEW_LINE, 3), - AssertDocJson( - 0, - r#"[{"insert":"123"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - Delete(0, Interval::new(3, 4)), - AssertDocJson( - 0, - r#"[{"insert":"123456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn delta_compose() { - let mut delta = DeltaTextOperations::from_json(r#"[{"insert":"\n"}]"#).unwrap(); - let deltas = vec![ - DeltaTextOperations::from_json(r#"[{"retain":1,"attributes":{"list":"unchecked"}}]"#).unwrap(), - DeltaTextOperations::from_json(r#"[{"insert":"a"}]"#).unwrap(), - DeltaTextOperations::from_json(r#"[{"retain":1},{"insert":"\n","attributes":{"list":"unchecked"}}]"#).unwrap(), - DeltaTextOperations::from_json(r#"[{"retain":2},{"retain":1,"attributes":{"list":""}}]"#).unwrap(), - ]; - - for d in deltas { - delta = delta.compose(&d).unwrap(); - } - assert_eq!( - delta.json_str(), - r#"[{"insert":"a"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"\n","attributes":{"list":""}}]"# - ); - - let ops = vec![ - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - Insert(0, "a", 0), - AssertDocJson(0, r#"[{"insert":"a\n"}]"#), - Bullet(0, Interval::new(0, 1), true), - AssertDocJson(0, r#"[{"insert":"a"},{"insert":"\n","attributes":{"list":"bullet"}}]"#), - Insert(0, NEW_LINE, 1), - AssertDocJson( - 0, - r#"[{"insert":"a"},{"insert":"\n\n","attributes":{"list":"bullet"}}]"#, - ), - Insert(0, NEW_LINE, 2), - AssertDocJson( - 0, - r#"[{"insert":"a"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"\n"}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} diff --git a/frontend/rust-lib/flowy-document/tests/editor/mod.rs b/frontend/rust-lib/flowy-document/tests/editor/mod.rs deleted file mode 100644 index a6eee1be5e..0000000000 --- a/frontend/rust-lib/flowy-document/tests/editor/mod.rs +++ /dev/null @@ -1,346 +0,0 @@ -#![allow(clippy::module_inception)] -mod attribute_test; -mod op_test; -mod serde_test; -mod undo_redo_test; - -use derive_more::Display; -use flowy_client_sync::client_document::{ClientDocument, InitialDocument}; -use lib_ot::{ - core::*, - text_delta::{BuildInTextAttribute, DeltaTextOperations}, -}; -use rand::{prelude::*, Rng as WrappedRng}; -use std::{sync::Once, time::Duration}; - -#[derive(Clone, Debug, Display)] -pub enum TestOp { - #[display(fmt = "Insert")] - Insert(usize, &'static str, usize), - - // delta_i, s, start, length, - #[display(fmt = "InsertBold")] - InsertBold(usize, &'static str, Interval), - - // delta_i, start, length, enable - #[display(fmt = "Bold")] - Bold(usize, Interval, bool), - - #[display(fmt = "Delete")] - Delete(usize, Interval), - - #[display(fmt = "Replace")] - Replace(usize, Interval, &'static str), - - #[display(fmt = "Italic")] - Italic(usize, Interval, bool), - - #[display(fmt = "Header")] - Header(usize, Interval, usize), - - #[display(fmt = "Link")] - Link(usize, Interval, &'static str), - - #[display(fmt = "Bullet")] - Bullet(usize, Interval, bool), - - #[display(fmt = "Transform")] - Transform(usize, usize), - - #[display(fmt = "TransformPrime")] - TransformPrime(usize, usize), - - // invert the delta_a base on the delta_b - #[display(fmt = "Invert")] - Invert(usize, usize), - - #[display(fmt = "Undo")] - Undo(usize), - - #[display(fmt = "Redo")] - Redo(usize), - - #[display(fmt = "Wait")] - Wait(usize), - - #[display(fmt = "AssertStr")] - AssertStr(usize, &'static str), - - #[display(fmt = "AssertDocJson")] - AssertDocJson(usize, &'static str), - - #[display(fmt = "AssertPrimeJson")] - AssertPrimeJson(usize, &'static str), - - #[display(fmt = "DocComposeDelta")] - DocComposeDelta(usize, usize), - - #[display(fmt = "ApplyPrimeDelta")] - DocComposePrime(usize, usize), -} - -pub struct TestBuilder { - documents: Vec, - deltas: Vec>, - primes: Vec>, -} - -impl TestBuilder { - pub fn new() -> Self { - static INIT: Once = Once::new(); - INIT.call_once(|| { - let _ = color_eyre::install(); - // let subscriber = FmtSubscriber::builder().with_max_level(Level::INFO).finish(); - // tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); - }); - - Self { - documents: vec![], - deltas: vec![], - primes: vec![], - } - } - - fn run_op(&mut self, op: &TestOp) { - tracing::trace!("***************** 😈{} *******************", &op); - match op { - TestOp::Insert(delta_i, s, index) => { - let document = &mut self.documents[*delta_i]; - let delta = document.insert(*index, s).unwrap(); - tracing::debug!("Insert delta: {}", delta.json_str()); - - self.deltas.insert(*delta_i, Some(delta)); - }, - TestOp::Delete(delta_i, iv) => { - let document = &mut self.documents[*delta_i]; - let delta = document.replace(*iv, "").unwrap(); - tracing::trace!("Delete delta: {}", delta.json_str()); - self.deltas.insert(*delta_i, Some(delta)); - }, - TestOp::Replace(delta_i, iv, s) => { - let document = &mut self.documents[*delta_i]; - let delta = document.replace(*iv, s).unwrap(); - tracing::trace!("Replace delta: {}", delta.json_str()); - self.deltas.insert(*delta_i, Some(delta)); - }, - TestOp::InsertBold(delta_i, s, iv) => { - let document = &mut self.documents[*delta_i]; - document.insert(iv.start, s).unwrap(); - document - .format(*iv, BuildInTextAttribute::Bold(true)) - .unwrap(); - }, - TestOp::Bold(delta_i, iv, enable) => { - let document = &mut self.documents[*delta_i]; - let attribute = BuildInTextAttribute::Bold(*enable); - let delta = document.format(*iv, attribute).unwrap(); - tracing::trace!("Bold delta: {}", delta.json_str()); - self.deltas.insert(*delta_i, Some(delta)); - }, - TestOp::Italic(delta_i, iv, enable) => { - let document = &mut self.documents[*delta_i]; - let attribute = match *enable { - true => BuildInTextAttribute::Italic(true), - false => BuildInTextAttribute::Italic(false), - }; - let delta = document.format(*iv, attribute).unwrap(); - tracing::trace!("Italic delta: {}", delta.json_str()); - self.deltas.insert(*delta_i, Some(delta)); - }, - TestOp::Header(delta_i, iv, level) => { - let document = &mut self.documents[*delta_i]; - let attribute = BuildInTextAttribute::Header(*level); - let delta = document.format(*iv, attribute).unwrap(); - tracing::trace!("Header delta: {}", delta.json_str()); - self.deltas.insert(*delta_i, Some(delta)); - }, - TestOp::Link(delta_i, iv, link) => { - let document = &mut self.documents[*delta_i]; - let attribute = BuildInTextAttribute::Link(link.to_owned()); - let delta = document.format(*iv, attribute).unwrap(); - tracing::trace!("Link delta: {}", delta.json_str()); - self.deltas.insert(*delta_i, Some(delta)); - }, - TestOp::Bullet(delta_i, iv, enable) => { - let document = &mut self.documents[*delta_i]; - let attribute = BuildInTextAttribute::Bullet(*enable); - let delta = document.format(*iv, attribute).unwrap(); - tracing::debug!("Bullet delta: {}", delta.json_str()); - - self.deltas.insert(*delta_i, Some(delta)); - }, - TestOp::Transform(delta_a_i, delta_b_i) => { - let (a_prime, b_prime) = self.documents[*delta_a_i] - .get_operations() - .transform(self.documents[*delta_b_i].get_operations()) - .unwrap(); - tracing::trace!("a:{:?},b:{:?}", a_prime, b_prime); - - let data_left = self.documents[*delta_a_i] - .get_operations() - .compose(&b_prime) - .unwrap(); - let data_right = self.documents[*delta_b_i] - .get_operations() - .compose(&a_prime) - .unwrap(); - - self.documents[*delta_a_i].set_operations(data_left); - self.documents[*delta_b_i].set_operations(data_right); - }, - TestOp::TransformPrime(a_doc_index, b_doc_index) => { - let (prime_left, prime_right) = self.documents[*a_doc_index] - .get_operations() - .transform(self.documents[*b_doc_index].get_operations()) - .unwrap(); - - self.primes.insert(*a_doc_index, Some(prime_left)); - self.primes.insert(*b_doc_index, Some(prime_right)); - }, - TestOp::Invert(delta_a_i, delta_b_i) => { - let delta_a = &self.documents[*delta_a_i].get_operations(); - let delta_b = &self.documents[*delta_b_i].get_operations(); - tracing::debug!("Invert: "); - tracing::debug!("a: {}", delta_a.json_str()); - tracing::debug!("b: {}", delta_b.json_str()); - - let (_, b_prime) = delta_a.transform(delta_b).unwrap(); - let undo = b_prime.invert(delta_a); - - let new_delta = delta_a.compose(&b_prime).unwrap(); - tracing::debug!("new delta: {}", new_delta.json_str()); - tracing::debug!("undo delta: {}", undo.json_str()); - - let new_delta_after_undo = new_delta.compose(&undo).unwrap(); - - tracing::debug!("inverted delta a: {}", new_delta_after_undo.to_string()); - - assert_eq!(delta_a, &&new_delta_after_undo); - - self.documents[*delta_a_i].set_operations(new_delta_after_undo); - }, - TestOp::Undo(delta_i) => { - self.documents[*delta_i].undo().unwrap(); - }, - TestOp::Redo(delta_i) => { - self.documents[*delta_i].redo().unwrap(); - }, - TestOp::Wait(mills_sec) => { - std::thread::sleep(Duration::from_millis(*mills_sec as u64)); - }, - TestOp::AssertStr(delta_i, expected) => { - assert_eq!(&self.documents[*delta_i].to_content(), expected); - }, - - TestOp::AssertDocJson(delta_i, expected) => { - let delta_json = self.documents[*delta_i].get_operations_json(); - let expected_delta: DeltaTextOperations = serde_json::from_str(expected).unwrap(); - let target_delta: DeltaTextOperations = serde_json::from_str(&delta_json).unwrap(); - - if expected_delta != target_delta { - println!("✅ expect: {}", expected,); - println!("❌ receive: {}", delta_json); - } - assert_eq!(target_delta, expected_delta); - }, - - TestOp::AssertPrimeJson(doc_i, expected) => { - let prime_json = self.primes[*doc_i].as_ref().unwrap().json_str(); - let expected_prime: DeltaTextOperations = serde_json::from_str(expected).unwrap(); - let target_prime: DeltaTextOperations = serde_json::from_str(&prime_json).unwrap(); - - if expected_prime != target_prime { - tracing::error!("✅ expect prime: {}", expected,); - tracing::error!("❌ receive prime: {}", prime_json); - } - assert_eq!(target_prime, expected_prime); - }, - TestOp::DocComposeDelta(doc_index, delta_i) => { - let delta = self.deltas.get(*delta_i).unwrap().as_ref().unwrap(); - self.documents[*doc_index] - .compose_operations(delta.clone()) - .unwrap(); - }, - TestOp::DocComposePrime(doc_index, prime_i) => { - let delta = self - .primes - .get(*prime_i) - .expect("Must call TransformPrime first") - .as_ref() - .unwrap(); - let new_delta = self.documents[*doc_index] - .get_operations() - .compose(delta) - .unwrap(); - self.documents[*doc_index].set_operations(new_delta); - }, - } - } - - pub fn run_scripts(mut self, scripts: Vec) { - self.documents = vec![ClientDocument::new::(), ClientDocument::new::()]; - self.primes = vec![None, None]; - self.deltas = vec![None, None]; - for (_i, op) in scripts.iter().enumerate() { - self.run_op(op); - } - } -} - -pub struct Rng(StdRng); - -impl Default for Rng { - fn default() -> Self { - Rng(StdRng::from_rng(thread_rng()).unwrap()) - } -} - -impl Rng { - #[allow(dead_code)] - pub fn from_seed(seed: [u8; 32]) -> Self { - Rng(StdRng::from_seed(seed)) - } - - pub fn gen_string(&mut self, len: usize) -> String { - (0..len) - .map(|_| { - let c = self.0.gen::(); - format!("{:x}", c as u32) - }) - .collect() - } - - pub fn gen_delta(&mut self, s: &str) -> DeltaTextOperations { - let mut delta = DeltaTextOperations::default(); - let s = OTString::from(s); - loop { - let left = s.utf16_len() - delta.utf16_base_len; - if left == 0 { - break; - } - let i = if left == 1 { - 1 - } else { - 1 + self.0.gen_range(0..std::cmp::min(left - 1, 20)) - }; - match self.0.gen_range(0.0..1.0) { - f if f < 0.2 => { - delta.insert(&self.gen_string(i), AttributeHashMap::default()); - }, - f if f < 0.4 => { - delta.delete(i); - }, - _ => { - delta.retain(i, AttributeHashMap::default()); - }, - } - } - if self.0.gen_range(0.0..1.0) < 0.3 { - delta.insert( - &("1".to_owned() + &self.gen_string(10)), - AttributeHashMap::default(), - ); - } - delta - } -} diff --git a/frontend/rust-lib/flowy-document/tests/editor/op_test.rs b/frontend/rust-lib/flowy-document/tests/editor/op_test.rs deleted file mode 100644 index 5f2f6f42ba..0000000000 --- a/frontend/rust-lib/flowy-document/tests/editor/op_test.rs +++ /dev/null @@ -1,788 +0,0 @@ -#![allow(clippy::all)] -use crate::editor::{Rng, TestBuilder, TestOp::*}; -use flowy_client_sync::client_document::{EmptyDocument, NewlineDocument}; -use lib_ot::text_delta::DeltaTextOperationBuilder; -use lib_ot::{core::Interval, core::*, text_delta::DeltaTextOperations}; - -#[test] -fn attributes_insert_text() { - let ops = vec![ - Insert(0, "123", 0), - Insert(0, "456", 3), - AssertDocJson(0, r#"[{"insert":"123456"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_insert_text_at_head() { - let ops = vec![ - Insert(0, "123", 0), - Insert(0, "456", 0), - AssertDocJson(0, r#"[{"insert":"456123"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn attributes_insert_text_at_middle() { - let ops = vec![ - Insert(0, "123", 0), - Insert(0, "456", 1), - AssertDocJson(0, r#"[{"insert":"145623"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn delta_get_ops_in_interval_1() { - let delta = DeltaTextOperationBuilder::new() - .insert("123") - .insert("4") - .build(); - - let mut iterator = OperationIterator::from_interval(&delta, Interval::new(0, 4)); - assert_eq!(iterator.ops(), delta.ops); -} - -#[test] -fn delta_get_ops_in_interval_2() { - let mut delta = DeltaTextOperations::default(); - let insert_a = DeltaOperation::insert("123"); - let insert_b = DeltaOperation::insert("4"); - let insert_c = DeltaOperation::insert("5"); - let retain_a = DeltaOperation::retain(3); - - delta.add(insert_a.clone()); - delta.add(retain_a.clone()); - delta.add(insert_b.clone()); - delta.add(insert_c.clone()); - - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(0, 2)).ops(), - vec![DeltaOperation::insert("12")] - ); - - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(1, 3)).ops(), - vec![DeltaOperation::insert("23")] - ); - - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(0, 3)).ops(), - vec![insert_a.clone()] - ); - - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(0, 4)).ops(), - vec![insert_a.clone(), DeltaOperation::retain(1)] - ); - - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(0, 6)).ops(), - vec![insert_a.clone(), retain_a.clone()] - ); - - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(0, 7)).ops(), - vec![insert_a.clone(), retain_a.clone(), insert_b.clone()] - ); -} - -#[test] -fn delta_get_ops_in_interval_3() { - let mut delta = DeltaTextOperations::default(); - let insert_a = DeltaOperation::insert("123456"); - delta.add(insert_a.clone()); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(3, 5)).ops(), - vec![DeltaOperation::insert("45")] - ); -} - -#[test] -fn delta_get_ops_in_interval_4() { - let mut delta = DeltaTextOperations::default(); - let insert_a = DeltaOperation::insert("12"); - let insert_b = DeltaOperation::insert("34"); - let insert_c = DeltaOperation::insert("56"); - - delta.ops.push(insert_a.clone()); - delta.ops.push(insert_b.clone()); - delta.ops.push(insert_c.clone()); - - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(0, 2)).ops(), - vec![insert_a] - ); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(2, 4)).ops(), - vec![insert_b] - ); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(4, 6)).ops(), - vec![insert_c] - ); - - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(2, 5)).ops(), - vec![DeltaOperation::insert("34"), DeltaOperation::insert("5")] - ); -} - -#[test] -fn delta_get_ops_in_interval_5() { - let mut delta = DeltaTextOperations::default(); - let insert_a = DeltaOperation::insert("123456"); - let insert_b = DeltaOperation::insert("789"); - delta.ops.push(insert_a.clone()); - delta.ops.push(insert_b.clone()); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(4, 8)).ops(), - vec![DeltaOperation::insert("56"), DeltaOperation::insert("78")] - ); - - // assert_eq!( - // DeltaIter::from_interval(&delta, Interval::new(8, 9)).ops(), - // vec![Builder::insert("9")] - // ); -} - -#[test] -fn delta_get_ops_in_interval_6() { - let mut delta = DeltaTextOperations::default(); - let insert_a = DeltaOperation::insert("12345678"); - delta.add(insert_a.clone()); - assert_eq!( - OperationIterator::from_interval(&delta, Interval::new(4, 6)).ops(), - vec![DeltaOperation::insert("56")] - ); -} - -#[test] -fn delta_get_ops_in_interval_7() { - let mut delta = DeltaTextOperations::default(); - let insert_a = DeltaOperation::insert("12345"); - let retain_a = DeltaOperation::retain(3); - - delta.add(insert_a.clone()); - delta.add(retain_a.clone()); - - let mut iter_1 = OperationIterator::from_offset(&delta, 2); - assert_eq!(iter_1.next_op().unwrap(), DeltaOperation::insert("345")); - assert_eq!(iter_1.next_op().unwrap(), DeltaOperation::retain(3)); - - let mut iter_2 = OperationIterator::new(&delta); - assert_eq!( - iter_2.next_op_with_len(2).unwrap(), - DeltaOperation::insert("12") - ); - assert_eq!(iter_2.next_op().unwrap(), DeltaOperation::insert("345")); - - assert_eq!(iter_2.next_op().unwrap(), DeltaOperation::retain(3)); -} - -#[test] -fn delta_op_seek() { - let mut delta = DeltaTextOperations::default(); - let insert_a = DeltaOperation::insert("12345"); - let retain_a = DeltaOperation::retain(3); - delta.add(insert_a.clone()); - delta.add(retain_a.clone()); - let mut iter = OperationIterator::new(&delta); - iter.seek::(1); - assert_eq!(iter.next_op().unwrap(), retain_a); -} - -#[test] -fn delta_utf16_code_unit_seek() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("12345")); - - let mut iter = OperationIterator::new(&delta); - iter.seek::(3); - assert_eq!( - iter.next_op_with_len(2).unwrap(), - DeltaOperation::insert("45") - ); -} - -#[test] -fn delta_utf16_code_unit_seek_with_attributes() { - let mut delta = DeltaTextOperations::default(); - let attributes = AttributeBuilder::new() - .insert("bold", true) - .insert("italic", true) - .build(); - - delta.add(DeltaOperation::insert_with_attributes( - "1234", - attributes.clone(), - )); - delta.add(DeltaOperation::insert("\n")); - - let mut iter = OperationIterator::new(&delta); - iter.seek::(0); - - assert_eq!( - iter.next_op_with_len(4).unwrap(), - DeltaOperation::insert_with_attributes("1234", attributes), - ); -} - -#[test] -fn delta_next_op_len() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("12345")); - let mut iter = OperationIterator::new(&delta); - assert_eq!( - iter.next_op_with_len(2).unwrap(), - DeltaOperation::insert("12") - ); - assert_eq!( - iter.next_op_with_len(2).unwrap(), - DeltaOperation::insert("34") - ); - assert_eq!( - iter.next_op_with_len(2).unwrap(), - DeltaOperation::insert("5") - ); - assert_eq!(iter.next_op_with_len(1), None); -} - -#[test] -fn delta_next_op_len_with_chinese() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("你好")); - - let mut iter = OperationIterator::new(&delta); - assert_eq!(iter.next_op_len().unwrap(), 2); - assert_eq!( - iter.next_op_with_len(2).unwrap(), - DeltaOperation::insert("你好") - ); -} - -#[test] -fn delta_next_op_len_with_english() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("ab")); - let mut iter = OperationIterator::new(&delta); - assert_eq!(iter.next_op_len().unwrap(), 2); - assert_eq!( - iter.next_op_with_len(2).unwrap(), - DeltaOperation::insert("ab") - ); -} - -#[test] -fn delta_next_op_len_after_seek() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("12345")); - let mut iter = OperationIterator::new(&delta); - assert_eq!(iter.next_op_len().unwrap(), 5); - iter.seek::(3); - assert_eq!(iter.next_op_len().unwrap(), 2); - assert_eq!( - iter.next_op_with_len(1).unwrap(), - DeltaOperation::insert("4") - ); - assert_eq!(iter.next_op_len().unwrap(), 1); - assert_eq!(iter.next_op().unwrap(), DeltaOperation::insert("5")); -} - -#[test] -fn delta_next_op_len_none() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("12345")); - let mut iter = OperationIterator::new(&delta); - - assert_eq!(iter.next_op_len().unwrap(), 5); - assert_eq!( - iter.next_op_with_len(5).unwrap(), - DeltaOperation::insert("12345") - ); - assert_eq!(iter.next_op_len(), None); -} - -#[test] -fn delta_next_op_with_len_zero() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("12345")); - let mut iter = OperationIterator::new(&delta); - assert_eq!(iter.next_op_with_len(0), None,); - assert_eq!(iter.next_op_len().unwrap(), 5); -} - -#[test] -fn delta_next_op_with_len_cross_op_return_last() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("12345")); - delta.add(DeltaOperation::retain(1)); - delta.add(DeltaOperation::insert("678")); - - let mut iter = OperationIterator::new(&delta); - iter.seek::(4); - assert_eq!(iter.next_op_len().unwrap(), 1); - assert_eq!(iter.next_op_with_len(2).unwrap(), DeltaOperation::retain(1)); -} - -#[test] -fn lengths() { - let mut delta = DeltaTextOperations::default(); - assert_eq!(delta.utf16_base_len, 0); - assert_eq!(delta.utf16_target_len, 0); - delta.retain(5, AttributeHashMap::default()); - assert_eq!(delta.utf16_base_len, 5); - assert_eq!(delta.utf16_target_len, 5); - delta.insert("abc", AttributeHashMap::default()); - assert_eq!(delta.utf16_base_len, 5); - assert_eq!(delta.utf16_target_len, 8); - delta.retain(2, AttributeHashMap::default()); - assert_eq!(delta.utf16_base_len, 7); - assert_eq!(delta.utf16_target_len, 10); - delta.delete(2); - assert_eq!(delta.utf16_base_len, 9); - assert_eq!(delta.utf16_target_len, 10); -} -#[test] -fn sequence() { - let mut delta = DeltaTextOperations::default(); - delta.retain(5, AttributeHashMap::default()); - delta.retain(0, AttributeHashMap::default()); - delta.insert("appflowy", AttributeHashMap::default()); - delta.insert("", AttributeHashMap::default()); - delta.delete(3); - delta.delete(0); - assert_eq!(delta.ops.len(), 3); -} - -#[test] -fn apply_1000() { - for _ in 0..1 { - let mut rng = Rng::default(); - let s: OTString = rng.gen_string(50).into(); - let delta = rng.gen_delta(&s); - assert_eq!(s.utf16_len(), delta.utf16_base_len); - } -} - -#[test] -fn apply_test() { - let s = "hello"; - let delta_a = DeltaBuilder::new().insert(s).build(); - let delta_b = DeltaBuilder::new() - .retain(s.len()) - .insert(", AppFlowy") - .build(); - - let after_a = delta_a.content().unwrap(); - let after_b = delta_b.apply(&after_a).unwrap(); - assert_eq!("hello, AppFlowy", &after_b); -} - -#[test] -fn base_len_test() { - let mut delta_a = DeltaTextOperations::default(); - delta_a.insert("a", AttributeHashMap::default()); - delta_a.insert("b", AttributeHashMap::default()); - delta_a.insert("c", AttributeHashMap::default()); - - let s = "hello world,".to_owned(); - delta_a.delete(s.len()); - let after_a = delta_a.apply(&s).unwrap(); - - delta_a.insert("d", AttributeHashMap::default()); - assert_eq!("abc", &after_a); -} - -#[test] -fn invert() { - for _ in 0..1000 { - let mut rng = Rng::default(); - let s = rng.gen_string(50); - let delta_a = rng.gen_delta(&s); - let delta_b = delta_a.invert_str(&s); - assert_eq!(delta_a.utf16_base_len, delta_b.utf16_target_len); - assert_eq!(delta_a.utf16_target_len, delta_b.utf16_base_len); - assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s); - } -} - -#[test] -fn invert_test() { - let s = "hello world"; - let delta = DeltaBuilder::new().insert(s).build(); - let invert_delta = delta.invert_str(""); - assert_eq!(delta.utf16_base_len, invert_delta.utf16_target_len); - assert_eq!(delta.utf16_target_len, invert_delta.utf16_base_len); - - assert_eq!(invert_delta.apply(s).unwrap(), "") -} - -#[test] -fn empty_ops() { - let mut delta = DeltaTextOperations::default(); - delta.retain(0, AttributeHashMap::default()); - delta.insert("", AttributeHashMap::default()); - delta.delete(0); - assert_eq!(delta.ops.len(), 0); -} -#[test] -fn eq() { - let mut delta_a = DeltaTextOperations::default(); - delta_a.delete(1); - delta_a.insert("lo", AttributeHashMap::default()); - delta_a.retain(2, AttributeHashMap::default()); - delta_a.retain(3, AttributeHashMap::default()); - let mut delta_b = DeltaTextOperations::default(); - delta_b.delete(1); - delta_b.insert("l", AttributeHashMap::default()); - delta_b.insert("o", AttributeHashMap::default()); - delta_b.retain(5, AttributeHashMap::default()); - assert_eq!(delta_a, delta_b); - delta_a.delete(1); - delta_b.retain(1, AttributeHashMap::default()); - assert_ne!(delta_a, delta_b); -} -#[test] -fn ops_merging() { - let mut delta = DeltaTextOperations::default(); - assert_eq!(delta.ops.len(), 0); - delta.retain(2, AttributeHashMap::default()); - assert_eq!(delta.ops.len(), 1); - assert_eq!(delta.ops.last(), Some(&DeltaOperation::retain(2))); - delta.retain(3, AttributeHashMap::default()); - assert_eq!(delta.ops.len(), 1); - assert_eq!(delta.ops.last(), Some(&DeltaOperation::retain(5))); - delta.insert("abc", AttributeHashMap::default()); - assert_eq!(delta.ops.len(), 2); - assert_eq!(delta.ops.last(), Some(&DeltaOperation::insert("abc"))); - delta.insert("xyz", AttributeHashMap::default()); - assert_eq!(delta.ops.len(), 2); - assert_eq!(delta.ops.last(), Some(&DeltaOperation::insert("abcxyz"))); - delta.delete(1); - assert_eq!(delta.ops.len(), 3); - assert_eq!(delta.ops.last(), Some(&DeltaOperation::delete(1))); - delta.delete(1); - assert_eq!(delta.ops.len(), 3); - assert_eq!(delta.ops.last(), Some(&DeltaOperation::delete(2))); -} - -#[test] -fn is_noop() { - let mut delta = DeltaTextOperations::default(); - assert!(delta.is_noop()); - delta.retain(5, AttributeHashMap::default()); - assert!(delta.is_noop()); - delta.retain(3, AttributeHashMap::default()); - assert!(delta.is_noop()); - delta.insert("lorem", AttributeHashMap::default()); - assert!(!delta.is_noop()); -} -#[test] -fn compose() { - for _ in 0..1000 { - let mut rng = Rng::default(); - let s = rng.gen_string(20); - let a = rng.gen_delta(&s); - let after_a: OTString = a.apply(&s).unwrap().into(); - assert_eq!(a.utf16_target_len, after_a.utf16_len()); - - let b = rng.gen_delta(&after_a); - let after_b: OTString = b.apply(&after_a).unwrap().into(); - assert_eq!(b.utf16_target_len, after_b.utf16_len()); - - let ab = a.compose(&b).unwrap(); - assert_eq!(ab.utf16_target_len, b.utf16_target_len); - let after_ab: OTString = ab.apply(&s).unwrap().into(); - assert_eq!(after_b, after_ab); - } -} -#[test] -fn transform_random_delta() { - for _ in 0..1000 { - let mut rng = Rng::default(); - let s = rng.gen_string(20); - let a = rng.gen_delta(&s); - let b = rng.gen_delta(&s); - let (a_prime, b_prime) = a.transform(&b).unwrap(); - let ab_prime = a.compose(&b_prime).unwrap(); - let ba_prime = b.compose(&a_prime).unwrap(); - assert_eq!(ab_prime, ba_prime); - - let after_ab_prime = ab_prime.apply(&s).unwrap(); - let after_ba_prime = ba_prime.apply(&s).unwrap(); - assert_eq!(after_ab_prime, after_ba_prime); - } -} - -#[test] -fn transform_with_two_delta() { - let mut a = DeltaTextOperations::default(); - let mut a_s = String::new(); - a.insert("123", AttributeBuilder::new().insert("bold", true).build()); - a_s = a.apply(&a_s).unwrap(); - assert_eq!(&a_s, "123"); - - let mut b = DeltaTextOperations::default(); - let mut b_s = String::new(); - b.insert("456", AttributeHashMap::default()); - b_s = b.apply(&b_s).unwrap(); - assert_eq!(&b_s, "456"); - - let (a_prime, b_prime) = a.transform(&b).unwrap(); - assert_eq!( - r#"[{"insert":"123","attributes":{"bold":true}},{"retain":3}]"#, - serde_json::to_string(&a_prime).unwrap() - ); - assert_eq!( - r#"[{"retain":3,"attributes":{"bold":true}},{"insert":"456"}]"#, - serde_json::to_string(&b_prime).unwrap() - ); - - let new_a = a.compose(&b_prime).unwrap(); - let new_b = b.compose(&a_prime).unwrap(); - assert_eq!( - r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"456"}]"#, - serde_json::to_string(&new_a).unwrap() - ); - - assert_eq!( - r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"456"}]"#, - serde_json::to_string(&new_b).unwrap() - ); -} - -#[test] -fn transform_two_plain_delta() { - let ops = vec![ - Insert(0, "123", 0), - Insert(1, "456", 0), - Transform(0, 1), - AssertDocJson(0, r#"[{"insert":"123456"}]"#), - AssertDocJson(1, r#"[{"insert":"123456"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn transform_two_plain_delta2() { - let ops = vec![ - Insert(0, "123", 0), - Insert(1, "456", 0), - TransformPrime(0, 1), - DocComposePrime(0, 1), - DocComposePrime(1, 0), - AssertDocJson(0, r#"[{"insert":"123456"}]"#), - AssertDocJson(1, r#"[{"insert":"123456"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn transform_two_non_seq_delta() { - let ops = vec![ - Insert(0, "123", 0), - Insert(1, "456", 0), - TransformPrime(0, 1), - AssertPrimeJson(0, r#"[{"insert":"123"},{"retain":3}]"#), - AssertPrimeJson(1, r#"[{"retain":3},{"insert":"456"}]"#), - DocComposePrime(0, 1), - Insert(1, "78", 3), - Insert(1, "9", 5), - DocComposePrime(1, 0), - AssertDocJson(0, r#"[{"insert":"123456"}]"#), - AssertDocJson(1, r#"[{"insert":"123456789"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn transform_two_conflict_non_seq_delta() { - let ops = vec![ - Insert(0, "123", 0), - Insert(1, "456", 0), - TransformPrime(0, 1), - DocComposePrime(0, 1), - Insert(1, "78", 0), - DocComposePrime(1, 0), - AssertDocJson(0, r#"[{"insert":"123456"}]"#), - AssertDocJson(1, r#"[{"insert":"12378456"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn delta_invert_no_attribute_delta() { - let mut delta = DeltaTextOperations::default(); - delta.add(DeltaOperation::insert("123")); - - let mut change = DeltaTextOperations::default(); - change.add(DeltaOperation::retain(3)); - change.add(DeltaOperation::insert("456")); - let undo = change.invert(&delta); - - let new_delta = delta.compose(&change).unwrap(); - let delta_after_undo = new_delta.compose(&undo).unwrap(); - - assert_eq!(delta_after_undo, delta); -} - -#[test] -fn delta_invert_no_attribute_delta2() { - let ops = vec![ - Insert(0, "123", 0), - Insert(1, "4567", 0), - Invert(0, 1), - AssertDocJson(0, r#"[{"insert":"123"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn delta_invert_attribute_delta_with_no_attribute_delta() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":true}}]"#), - Insert(1, "4567", 0), - Invert(0, 1), - AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":true}}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn delta_invert_attribute_delta_with_no_attribute_delta2() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - Insert(0, "456", 3), - AssertDocJson( - 0, - r#"[ - {"insert":"123456","attributes":{"bold":true}}] - "#, - ), - Italic(0, Interval::new(2, 4), true), - AssertDocJson( - 0, - r#"[ - {"insert":"12","attributes":{"bold":true}}, - {"insert":"34","attributes":{"bold":true,"italic":true}}, - {"insert":"56","attributes":{"bold":true}} - ]"#, - ), - Insert(1, "abc", 0), - Invert(0, 1), - AssertDocJson( - 0, - r#"[ - {"insert":"12","attributes":{"bold":true}}, - {"insert":"34","attributes":{"bold":true,"italic":true}}, - {"insert":"56","attributes":{"bold":true}} - ]"#, - ), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn delta_invert_no_attribute_delta_with_attribute_delta() { - let ops = vec![ - Insert(0, "123", 0), - Insert(1, "4567", 0), - Bold(1, Interval::new(0, 3), true), - AssertDocJson( - 1, - r#"[{"insert":"456","attributes":{"bold":true}},{"insert":"7"}]"#, - ), - Invert(0, 1), - AssertDocJson(0, r#"[{"insert":"123"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn delta_invert_no_attribute_delta_with_attribute_delta2() { - let ops = vec![ - Insert(0, "123", 0), - AssertDocJson(0, r#"[{"insert":"123"}]"#), - Insert(1, "abc", 0), - Bold(1, Interval::new(0, 3), true), - Insert(1, "d", 3), - Italic(1, Interval::new(1, 3), true), - AssertDocJson( - 1, - r#"[{"insert":"a","attributes":{"bold":true}},{"insert":"bc","attributes":{"bold":true,"italic":true}},{"insert":"d","attributes":{"bold":true}}]"#, - ), - Invert(0, 1), - AssertDocJson(0, r#"[{"insert":"123"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn delta_invert_attribute_delta_with_attribute_delta() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - Insert(0, "456", 3), - AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":true}}]"#), - Italic(0, Interval::new(2, 4), true), - AssertDocJson( - 0, - r#"[ - {"insert":"12","attributes":{"bold":true}}, - {"insert":"34","attributes":{"bold":true,"italic":true}}, - {"insert":"56","attributes":{"bold":true}} - ]"#, - ), - Insert(1, "abc", 0), - Bold(1, Interval::new(0, 3), true), - Insert(1, "d", 3), - Italic(1, Interval::new(1, 3), true), - AssertDocJson( - 1, - r#"[ - {"insert":"a","attributes":{"bold":true}}, - {"insert":"bc","attributes":{"bold":true,"italic":true}}, - {"insert":"d","attributes":{"bold":true}} - ]"#, - ), - Invert(0, 1), - AssertDocJson( - 0, - r#"[ - {"insert":"12","attributes":{"bold":true}}, - {"insert":"34","attributes":{"bold":true,"italic":true}}, - {"insert":"56","attributes":{"bold":true}} - ]"#, - ), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn delta_compose_str() { - let ops = vec![ - Insert(0, "1", 0), - Insert(0, "2", 1), - AssertDocJson(0, r#"[{"insert":"12\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -#[should_panic] -fn delta_compose_with_missing_delta() { - let ops = vec![ - Insert(0, "123", 0), - Insert(0, "4", 3), - DocComposeDelta(1, 0), - AssertDocJson(0, r#"[{"insert":"1234\n"}]"#), - AssertStr(1, r#"4\n"#), - ]; - TestBuilder::new().run_scripts::(ops); -} diff --git a/frontend/rust-lib/flowy-document/tests/editor/serde_test.rs b/frontend/rust-lib/flowy-document/tests/editor/serde_test.rs deleted file mode 100644 index ce38ac7058..0000000000 --- a/frontend/rust-lib/flowy-document/tests/editor/serde_test.rs +++ /dev/null @@ -1,118 +0,0 @@ -use flowy_client_sync::client_document::{ClientDocument, EmptyDocument}; -use lib_ot::text_delta::DeltaTextOperation; -use lib_ot::{ - core::*, - text_delta::{BuildInTextAttribute, DeltaTextOperations}, -}; - -#[test] -fn operation_insert_serialize_test() { - let attributes = AttributeBuilder::new() - .insert("bold", true) - .insert("italic", true) - .build(); - let operation = DeltaOperation::insert_with_attributes("123", attributes); - let json = serde_json::to_string(&operation).unwrap(); - eprintln!("{}", json); - - let insert_op: DeltaTextOperation = serde_json::from_str(&json).unwrap(); - assert_eq!(insert_op, operation); -} - -#[test] -fn operation_retain_serialize_test() { - let operation = DeltaOperation::Retain(12.into()); - let json = serde_json::to_string(&operation).unwrap(); - eprintln!("{}", json); - let insert_op: DeltaTextOperation = serde_json::from_str(&json).unwrap(); - assert_eq!(insert_op, operation); -} - -#[test] -fn operation_delete_serialize_test() { - let operation = DeltaTextOperation::Delete(2); - let json = serde_json::to_string(&operation).unwrap(); - let insert_op: DeltaTextOperation = serde_json::from_str(&json).unwrap(); - assert_eq!(insert_op, operation); -} - -#[test] -fn attributes_serialize_test() { - let attributes = AttributeBuilder::new() - .insert_entry(BuildInTextAttribute::Bold(true)) - .insert_entry(BuildInTextAttribute::Italic(true)) - .build(); - let retain = DeltaOperation::insert_with_attributes("123", attributes); - - let json = serde_json::to_string(&retain).unwrap(); - eprintln!("{}", json); -} - -#[test] -fn delta_serialize_multi_attribute_test() { - let mut delta = DeltaOperations::default(); - - let attributes = AttributeBuilder::new() - .insert_entry(BuildInTextAttribute::Bold(true)) - .insert_entry(BuildInTextAttribute::Italic(true)) - .build(); - let retain = DeltaOperation::insert_with_attributes("123", attributes); - - delta.add(retain); - delta.add(DeltaOperation::Retain(5.into())); - delta.add(DeltaOperation::Delete(3)); - - let json = serde_json::to_string(&delta).unwrap(); - eprintln!("{}", json); - - let delta_from_json = DeltaOperations::from_json(&json).unwrap(); - assert_eq!(delta_from_json, delta); -} - -#[test] -fn delta_deserialize_test() { - let json = r#"[ - {"retain":2,"attributes":{"italic":true}}, - {"retain":2,"attributes":{"italic":123}}, - {"retain":2,"attributes":{"italic":true,"bold":true}}, - {"retain":2,"attributes":{"italic":true,"bold":true}} - ]"#; - let delta = DeltaTextOperations::from_json(json).unwrap(); - eprintln!("{}", delta); -} - -#[test] -fn delta_deserialize_null_test() { - let json = r#"[ - {"retain":7,"attributes":{"bold":null}} - ]"#; - let delta1 = DeltaTextOperations::from_json(json).unwrap(); - - let mut attribute = BuildInTextAttribute::Bold(true); - attribute.clear(); - - let delta2 = DeltaOperationBuilder::new() - .retain_with_attributes(7, attribute.into()) - .build(); - - assert_eq!( - delta2.json_str(), - r#"[{"retain":7,"attributes":{"bold":null}}]"# - ); - assert_eq!(delta1, delta2); -} - -#[test] -fn document_insert_serde_test() { - let mut document = ClientDocument::new::(); - document.insert(0, "\n").unwrap(); - document.insert(0, "123").unwrap(); - let json = document.get_operations_json(); - assert_eq!(r#"[{"insert":"123\n"}]"#, json); - assert_eq!( - r#"[{"insert":"123\n"}]"#, - ClientDocument::from_json(&json) - .unwrap() - .get_operations_json() - ); -} diff --git a/frontend/rust-lib/flowy-document/tests/editor/undo_redo_test.rs b/frontend/rust-lib/flowy-document/tests/editor/undo_redo_test.rs deleted file mode 100644 index 53d957527d..0000000000 --- a/frontend/rust-lib/flowy-document/tests/editor/undo_redo_test.rs +++ /dev/null @@ -1,389 +0,0 @@ -use crate::editor::{TestBuilder, TestOp::*}; -use flowy_client_sync::client_document::{EmptyDocument, NewlineDocument, RECORD_THRESHOLD}; -use lib_ot::core::{Interval, NEW_LINE, WHITESPACE}; - -#[test] -fn history_insert_undo() { - let ops = vec![ - Insert(0, "123", 0), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_insert_undo_with_lagging() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - Insert(0, "456", 0), - Undo(0), - AssertDocJson(0, r#"[{"insert":"123\n"}]"#), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_insert_redo() { - let ops = vec![ - Insert(0, "123", 0), - AssertDocJson(0, r#"[{"insert":"123\n"}]"#), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - Redo(0), - AssertDocJson(0, r#"[{"insert":"123\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_insert_redo_with_lagging() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - Insert(0, "456", 3), - Wait(RECORD_THRESHOLD), - AssertStr(0, "123456\n"), - AssertDocJson(0, r#"[{"insert":"123456\n"}]"#), - Undo(0), - AssertDocJson(0, r#"[{"insert":"123\n"}]"#), - Redo(0), - AssertDocJson(0, r#"[{"insert":"123456\n"}]"#), - Undo(0), - AssertDocJson(0, r#"[{"insert":"123\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_bold_undo() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_bold_undo_with_lagging() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - Bold(0, Interval::new(0, 3), true), - Undo(0), - AssertDocJson(0, r#"[{"insert":"123\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_bold_redo() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - Redo(0), - AssertDocJson( - 0, - r#" [{"insert":"123","attributes":{"bold":true}},{"insert":"\n"}]"#, - ), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_bold_redo_with_lagging() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - Bold(0, Interval::new(0, 3), true), - Undo(0), - AssertDocJson(0, r#"[{"insert":"123\n"}]"#), - Redo(0), - AssertDocJson( - 0, - r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"\n"}]"#, - ), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_delete_undo() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - AssertDocJson(0, r#"[{"insert":"123"}]"#), - Delete(0, Interval::new(0, 3)), - AssertDocJson(0, r#"[]"#), - Undo(0), - AssertDocJson(0, r#"[{"insert":"123"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_delete_undo_2() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - Delete(0, Interval::new(0, 1)), - AssertDocJson( - 0, - r#"[ - {"insert":"23","attributes":{"bold":true}}, - {"insert":"\n"}] - "#, - ), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_delete_undo_with_lagging() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - Bold(0, Interval::new(0, 3), true), - Wait(RECORD_THRESHOLD), - Delete(0, Interval::new(0, 1)), - AssertDocJson( - 0, - r#"[ - {"insert":"23","attributes":{"bold":true}}, - {"insert":"\n"}] - "#, - ), - Undo(0), - AssertDocJson( - 0, - r#"[ - {"insert":"123","attributes":{"bold":true}}, - {"insert":"\n"}] - "#, - ), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_delete_redo() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - Delete(0, Interval::new(0, 3)), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - Undo(0), - Redo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_replace_undo() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - Replace(0, Interval::new(0, 2), "ab"), - AssertDocJson( - 0, - r#"[ - {"insert":"ab"}, - {"insert":"3","attributes":{"bold":true}},{"insert":"\n"}] - "#, - ), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_replace_undo_with_lagging() { - let ops = vec![ - Insert(0, "123", 0), - Wait(RECORD_THRESHOLD), - Bold(0, Interval::new(0, 3), true), - Wait(RECORD_THRESHOLD), - Replace(0, Interval::new(0, 2), "ab"), - AssertDocJson( - 0, - r#"[ - {"insert":"ab"}, - {"insert":"3","attributes":{"bold":true}},{"insert":"\n"}] - "#, - ), - Undo(0), - AssertDocJson( - 0, - r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"\n"}]"#, - ), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_replace_redo() { - let ops = vec![ - Insert(0, "123", 0), - Bold(0, Interval::new(0, 3), true), - Replace(0, Interval::new(0, 2), "ab"), - Undo(0), - Redo(0), - AssertDocJson( - 0, - r#"[ - {"insert":"ab"}, - {"insert":"3","attributes":{"bold":true}},{"insert":"\n"}] - "#, - ), - ]; - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_header_added_undo() { - let ops = vec![ - Insert(0, "123456", 0), - Header(0, Interval::new(0, 6), 1), - Insert(0, "\n", 3), - Insert(0, "\n", 4), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - Redo(0), - AssertDocJson( - 0, - r#"[{"insert":"123"},{"insert":"\n\n","attributes":{"header":1}},{"insert":"456"},{"insert":"\n","attributes":{"header":1}}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_link_added_undo() { - let site = "https://appflowy.io"; - let ops = vec![ - Insert(0, site, 0), - Wait(RECORD_THRESHOLD), - Link(0, Interval::new(0, site.len()), site), - Undo(0), - AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#), - Redo(0), - AssertDocJson( - 0, - r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_link_auto_format_undo_with_lagging() { - let site = "https://appflowy.io"; - let ops = vec![ - Insert(0, site, 0), - AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#), - Wait(RECORD_THRESHOLD), - Insert(0, WHITESPACE, site.len()), - AssertDocJson( - 0, - r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io/"}},{"insert":" \n"}]"#, - ), - Undo(0), - AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_bullet_undo() { - let ops = vec![ - Insert(0, "1", 0), - Bullet(0, Interval::new(0, 1), true), - Insert(0, NEW_LINE, 1), - Insert(0, "2", 2), - AssertDocJson( - 0, - r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - Redo(0), - AssertDocJson( - 0, - r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_bullet_undo_with_lagging() { - let ops = vec![ - Insert(0, "1", 0), - Bullet(0, Interval::new(0, 1), true), - Wait(RECORD_THRESHOLD), - Insert(0, NEW_LINE, 1), - Insert(0, "2", 2), - Wait(RECORD_THRESHOLD), - AssertDocJson( - 0, - r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - Undo(0), - AssertDocJson( - 0, - r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - Undo(0), - AssertDocJson(0, r#"[{"insert":"\n"}]"#), - Redo(0), - Redo(0), - AssertDocJson( - 0, - r#"[{"insert":"1"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"2"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} - -#[test] -fn history_undo_attribute_on_merge_between_line() { - let ops = vec![ - Insert(0, "123456", 0), - Bullet(0, Interval::new(0, 6), true), - Wait(RECORD_THRESHOLD), - Insert(0, NEW_LINE, 3), - Wait(RECORD_THRESHOLD), - AssertDocJson( - 0, - r#"[{"insert":"123"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - Delete(0, Interval::new(3, 4)), // delete the newline - AssertDocJson( - 0, - r#"[{"insert":"123456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - Undo(0), - AssertDocJson( - 0, - r#"[{"insert":"123"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"456"},{"insert":"\n","attributes":{"list":"bullet"}}]"#, - ), - ]; - - TestBuilder::new().run_scripts::(ops); -} diff --git a/frontend/rust-lib/flowy-document/tests/main.rs b/frontend/rust-lib/flowy-document/tests/main.rs deleted file mode 100644 index 02e5746a91..0000000000 --- a/frontend/rust-lib/flowy-document/tests/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -// mod editor; -// mod new_document; -// mod old_document; diff --git a/frontend/rust-lib/flowy-document/tests/new_document/document_compose_test.rs b/frontend/rust-lib/flowy-document/tests/new_document/document_compose_test.rs deleted file mode 100644 index f6070751dd..0000000000 --- a/frontend/rust-lib/flowy-document/tests/new_document/document_compose_test.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::new_document::script::DocumentEditorTest; -use crate::new_document::script::EditScript::*; - -#[tokio::test] -async fn document_insert_h1_style_test() { - let scripts = vec![ - ComposeTransactionStr { - transaction: r#"{"operations":[{"op":"update_text","path":[0,0],"delta":[{"insert":"/"}],"inverted":[{"delete":1}]}],"after_selection":{"start":{"path":[0,0],"offset":1},"end":{"path":[0,0],"offset":1}},"before_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}}}"#, - }, - AssertContent { - expected: r#"{"document":{"type":"editor","children":[{"type":"text","delta":[{"insert":"/"}]}]}}"#, - }, - ComposeTransactionStr { - transaction: r#"{"operations":[{"op":"update_text","path":[0,0],"delta":[{"delete":1}],"inverted":[{"insert":"/"}]}],"after_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}},"before_selection":{"start":{"path":[0,0],"offset":1},"end":{"path":[0,0],"offset":1}}}"#, - }, - ComposeTransactionStr { - transaction: r#"{"operations":[{"op":"update","path":[0,0],"attributes":{"subtype":"heading","heading":"h1"},"oldAttributes":{"subtype":null,"heading":null}}],"after_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}},"before_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}}}"#, - }, - AssertContent { - expected: r#"{"document":{"type":"editor","children":[{"type":"text","attributes":{"subtype":"heading","heading":"h1"}}]}}"#, - }, - ]; - DocumentEditorTest::new().await.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-document/tests/new_document/mod.rs b/frontend/rust-lib/flowy-document/tests/new_document/mod.rs deleted file mode 100644 index f4464c9147..0000000000 --- a/frontend/rust-lib/flowy-document/tests/new_document/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod document_compose_test; -mod script; -mod test; diff --git a/frontend/rust-lib/flowy-document/tests/new_document/script.rs b/frontend/rust-lib/flowy-document/tests/new_document/script.rs deleted file mode 100644 index 26736f5b23..0000000000 --- a/frontend/rust-lib/flowy-document/tests/new_document/script.rs +++ /dev/null @@ -1,130 +0,0 @@ -use flowy_document::editor::{AppFlowyDocumentEditor, Document, DocumentTransaction}; - -use flowy_document::entities::DocumentVersionPB; -use flowy_test::helper::ViewTest; -use flowy_test::FlowySDKTest; -use lib_ot::core::{Changeset, NodeDataBuilder, NodeOperation, Path, Transaction}; -use lib_ot::text_delta::DeltaTextOperations; -use std::sync::Arc; - -pub enum EditScript { - InsertText { - path: Path, - delta: DeltaTextOperations, - }, - UpdateText { - path: Path, - delta: DeltaTextOperations, - }, - #[allow(dead_code)] - ComposeTransaction { - transaction: Transaction, - }, - ComposeTransactionStr { - transaction: &'static str, - }, - Delete { - path: Path, - }, - AssertContent { - expected: &'static str, - }, - AssertPrettyContent { - expected: &'static str, - }, -} - -pub struct DocumentEditorTest { - pub sdk: FlowySDKTest, - pub editor: Arc, -} - -impl DocumentEditorTest { - pub async fn new() -> Self { - let version = DocumentVersionPB::V1; - let sdk = FlowySDKTest::new(version.clone()); - let _ = sdk.init_user().await; - - let test = ViewTest::new_document_view(&sdk).await; - let document_editor = sdk - .document_manager - .open_document_editor(&test.child_view.id) - .await - .unwrap(); - let editor = match document_editor - .as_any() - .downcast_ref::>() - { - None => panic!(), - Some(editor) => editor.clone(), - }; - - Self { sdk, editor } - } - - pub async fn run_scripts(&self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - async fn run_script(&self, script: EditScript) { - match script { - EditScript::InsertText { path, delta } => { - let node_data = NodeDataBuilder::new("text").insert_delta(delta).build(); - let operation = NodeOperation::Insert { - path, - nodes: vec![node_data], - }; - self - .editor - .apply_transaction(Transaction::from_operations(vec![operation])) - .await - .unwrap(); - }, - EditScript::UpdateText { path, delta } => { - let inverted = delta.invert_str(""); - let changeset = Changeset::Delta { delta, inverted }; - let operation = NodeOperation::Update { path, changeset }; - self - .editor - .apply_transaction(Transaction::from_operations(vec![operation])) - .await - .unwrap(); - }, - EditScript::ComposeTransaction { transaction } => { - self.editor.apply_transaction(transaction).await.unwrap(); - }, - EditScript::ComposeTransactionStr { transaction } => { - let document_transaction = - serde_json::from_str::(transaction).unwrap(); - let transaction: Transaction = document_transaction.into(); - self.editor.apply_transaction(transaction).await.unwrap(); - }, - EditScript::Delete { path } => { - let operation = NodeOperation::Delete { - path, - nodes: vec![], - }; - self - .editor - .apply_transaction(Transaction::from_operations(vec![operation])) - .await - .unwrap(); - }, - EditScript::AssertContent { expected } => { - // - let content = self.editor.get_content(false).await.unwrap(); - let expected_document: Document = serde_json::from_str(expected).unwrap(); - let expected = serde_json::to_string(&expected_document).unwrap(); - - assert_eq!(content, expected); - }, - EditScript::AssertPrettyContent { expected } => { - // - let content = self.editor.get_content(true).await.unwrap(); - assert_eq!(content, expected); - }, - } - } -} diff --git a/frontend/rust-lib/flowy-document/tests/new_document/test.rs b/frontend/rust-lib/flowy-document/tests/new_document/test.rs deleted file mode 100644 index 593c7a461e..0000000000 --- a/frontend/rust-lib/flowy-document/tests/new_document/test.rs +++ /dev/null @@ -1,164 +0,0 @@ -use crate::new_document::script::DocumentEditorTest; -use crate::new_document::script::EditScript::*; - -use lib_ot::text_delta::DeltaTextOperationBuilder; - -#[tokio::test] -async fn document_initialize_test() { - let scripts = vec![AssertContent { - expected: r#"{"document":{"type":"editor","children":[{"type":"text"}]}}"#, - }]; - DocumentEditorTest::new().await.run_scripts(scripts).await; -} - -#[tokio::test] -async fn document_insert_text_test() { - let delta = DeltaTextOperationBuilder::new() - .insert("Hello world") - .build(); - let expected = r#"{ - "document": { - "type": "editor", - "children": [ - { - "type": "text", - "delta": [ - { - "insert": "Hello world" - } - ] - }, - { - "type": "text" - } - ] - } -}"#; - let scripts = vec![ - InsertText { - path: vec![0, 0].into(), - delta, - }, - AssertPrettyContent { expected }, - ]; - DocumentEditorTest::new().await.run_scripts(scripts).await; -} - -#[tokio::test] -async fn document_update_text_test() { - let test = DocumentEditorTest::new().await; - let hello_world = "Hello world".to_string(); - let scripts = vec![ - UpdateText { - path: vec![0, 0].into(), - delta: DeltaTextOperationBuilder::new() - .insert(&hello_world) - .build(), - }, - AssertPrettyContent { - expected: r#"{ - "document": { - "type": "editor", - "children": [ - { - "type": "text", - "delta": [ - { - "insert": "Hello world" - } - ] - } - ] - } -}"#, - }, - ]; - - test.run_scripts(scripts).await; - - let scripts = vec![ - UpdateText { - path: vec![0, 0].into(), - delta: DeltaTextOperationBuilder::new() - .retain(hello_world.len()) - .insert(", AppFlowy") - .build(), - }, - AssertPrettyContent { - expected: r#"{ - "document": { - "type": "editor", - "children": [ - { - "type": "text", - "delta": [ - { - "insert": "Hello world, AppFlowy" - } - ] - } - ] - } -}"#, - }, - ]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn document_delete_text_test() { - let expected = r#"{ - "document": { - "type": "editor", - "children": [ - { - "type": "text", - "delta": [ - { - "insert": "Hello" - } - ] - } - ] - } -}"#; - let hello_world = "Hello world".to_string(); - let scripts = vec![ - UpdateText { - path: vec![0, 0].into(), - delta: DeltaTextOperationBuilder::new() - .insert(&hello_world) - .build(), - }, - UpdateText { - path: vec![0, 0].into(), - delta: DeltaTextOperationBuilder::new().retain(5).delete(6).build(), - }, - AssertPrettyContent { expected }, - ]; - - DocumentEditorTest::new().await.run_scripts(scripts).await; -} - -#[tokio::test] -async fn document_delete_node_test() { - let scripts = vec![ - UpdateText { - path: vec![0, 0].into(), - delta: DeltaTextOperationBuilder::new() - .insert("Hello world") - .build(), - }, - AssertContent { - expected: r#"{"document":{"type":"editor","children":[{"type":"text","delta":[{"insert":"Hello world"}]}]}}"#, - }, - Delete { - path: vec![0, 0].into(), - }, - AssertContent { - expected: r#"{"document":{"type":"editor"}}"#, - }, - ]; - - DocumentEditorTest::new().await.run_scripts(scripts).await; -} diff --git a/frontend/rust-lib/flowy-document/tests/old_document/mod.rs b/frontend/rust-lib/flowy-document/tests/old_document/mod.rs deleted file mode 100644 index 395d1bfe2f..0000000000 --- a/frontend/rust-lib/flowy-document/tests/old_document/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod old_document_test; -mod script; diff --git a/frontend/rust-lib/flowy-document/tests/old_document/old_document_test.rs b/frontend/rust-lib/flowy-document/tests/old_document/old_document_test.rs deleted file mode 100644 index 6daedc2f8d..0000000000 --- a/frontend/rust-lib/flowy-document/tests/old_document/old_document_test.rs +++ /dev/null @@ -1,129 +0,0 @@ -use crate::old_document::script::{EditorScript::*, *}; -use flowy_revision_persistence::RevisionState; -use lib_ot::core::{count_utf16_code_units, Interval}; - -#[tokio::test] -async fn text_block_sync_current_rev_id_check() { - let scripts = vec![ - InsertText("1", 0), - AssertCurrentRevId(1), - InsertText("2", 1), - AssertCurrentRevId(2), - InsertText("3", 2), - AssertCurrentRevId(3), - AssertNextSyncRevId(None), - AssertJson(r#"[{"insert":"123\n"}]"#), - ]; - DeltaDocumentEditorTest::new() - .await - .run_scripts(scripts) - .await; -} - -#[tokio::test] -async fn text_block_sync_state_check() { - let scripts = vec![ - InsertText("1", 0), - InsertText("2", 1), - InsertText("3", 2), - AssertRevisionState(1, RevisionState::Ack), - AssertRevisionState(2, RevisionState::Ack), - AssertRevisionState(3, RevisionState::Ack), - AssertJson(r#"[{"insert":"123\n"}]"#), - ]; - DeltaDocumentEditorTest::new() - .await - .run_scripts(scripts) - .await; -} - -#[tokio::test] -async fn text_block_sync_insert_test() { - let scripts = vec![ - InsertText("1", 0), - InsertText("2", 1), - InsertText("3", 2), - AssertJson(r#"[{"insert":"123\n"}]"#), - AssertNextSyncRevId(None), - ]; - DeltaDocumentEditorTest::new() - .await - .run_scripts(scripts) - .await; -} - -#[tokio::test] -async fn text_block_sync_insert_in_chinese() { - let s = "好".to_owned(); - let offset = count_utf16_code_units(&s); - let scripts = vec![ - InsertText("你", 0), - InsertText("好", offset), - AssertJson(r#"[{"insert":"你好\n"}]"#), - ]; - DeltaDocumentEditorTest::new() - .await - .run_scripts(scripts) - .await; -} - -#[tokio::test] -async fn text_block_sync_insert_with_emoji() { - let s = "😁".to_owned(); - let offset = count_utf16_code_units(&s); - let scripts = vec![ - InsertText("😁", 0), - InsertText("☺️", offset), - AssertJson(r#"[{"insert":"😁☺️\n"}]"#), - ]; - DeltaDocumentEditorTest::new() - .await - .run_scripts(scripts) - .await; -} - -#[tokio::test] -async fn text_block_sync_delete_in_english() { - let scripts = vec![ - InsertText("1", 0), - InsertText("2", 1), - InsertText("3", 2), - Delete(Interval::new(0, 2)), - AssertJson(r#"[{"insert":"3\n"}]"#), - ]; - DeltaDocumentEditorTest::new() - .await - .run_scripts(scripts) - .await; -} - -#[tokio::test] -async fn text_block_sync_delete_in_chinese() { - let s = "好".to_owned(); - let offset = count_utf16_code_units(&s); - let scripts = vec![ - InsertText("你", 0), - InsertText("好", offset), - Delete(Interval::new(0, offset)), - AssertJson(r#"[{"insert":"好\n"}]"#), - ]; - DeltaDocumentEditorTest::new() - .await - .run_scripts(scripts) - .await; -} - -#[tokio::test] -async fn text_block_sync_replace_test() { - let scripts = vec![ - InsertText("1", 0), - InsertText("2", 1), - InsertText("3", 2), - Replace(Interval::new(0, 3), "abc"), - AssertJson(r#"[{"insert":"abc\n"}]"#), - ]; - DeltaDocumentEditorTest::new() - .await - .run_scripts(scripts) - .await; -} diff --git a/frontend/rust-lib/flowy-document/tests/old_document/script.rs b/frontend/rust-lib/flowy-document/tests/old_document/script.rs deleted file mode 100644 index 0fd94aa8b8..0000000000 --- a/frontend/rust-lib/flowy-document/tests/old_document/script.rs +++ /dev/null @@ -1,95 +0,0 @@ -use flowy_document::old_editor::editor::DeltaDocumentEditor; -use flowy_document::TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS; -use flowy_revision_persistence::RevisionState; -use flowy_test::{helper::ViewTest, FlowySDKTest}; -use lib_ot::{core::Interval, text_delta::DeltaTextOperations}; -use std::sync::Arc; -use tokio::time::{sleep, Duration}; - -pub enum EditorScript { - InsertText(&'static str, usize), - Delete(Interval), - Replace(Interval, &'static str), - - AssertRevisionState(i64, RevisionState), - AssertNextSyncRevId(Option), - AssertCurrentRevId(i64), - AssertJson(&'static str), -} - -pub struct DeltaDocumentEditorTest { - pub sdk: FlowySDKTest, - pub editor: Arc, -} - -impl DeltaDocumentEditorTest { - pub async fn new() -> Self { - let sdk = FlowySDKTest::default(); - let _ = sdk.init_user().await; - let test = ViewTest::new_document_view(&sdk).await; - let document_editor = sdk - .document_manager - .open_document_editor(&test.view.id) - .await - .unwrap(); - let editor = match document_editor - .as_any() - .downcast_ref::>() - { - None => panic!(), - Some(editor) => editor.clone(), - }; - Self { sdk, editor } - } - - pub async fn run_scripts(mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - async fn run_script(&mut self, script: EditorScript) { - let rev_manager = self.editor.rev_manager(); - let cache = rev_manager.revision_cache().await; - - match script { - EditorScript::InsertText(s, offset) => { - self.editor.insert(offset, s).await.unwrap(); - }, - EditorScript::Delete(interval) => { - self.editor.delete(interval).await.unwrap(); - }, - EditorScript::Replace(interval, s) => { - self.editor.replace(interval, s).await.unwrap(); - }, - EditorScript::AssertRevisionState(rev_id, state) => { - let record = cache.get(rev_id).await.unwrap(); - assert_eq!(record.state, state); - }, - EditorScript::AssertCurrentRevId(rev_id) => { - assert_eq!(self.editor.rev_manager().rev_id(), rev_id); - }, - EditorScript::AssertNextSyncRevId(rev_id) => { - let next_revision = rev_manager.next_sync_revision().await.unwrap(); - if rev_id.is_none() { - assert!(next_revision.is_none(), "Next revision should be None"); - return; - } - let next_revision = next_revision.unwrap(); - let mut notify = rev_manager.ack_notify(); - let _ = notify.recv().await; - assert_eq!(next_revision.rev_id, rev_id.unwrap()); - }, - EditorScript::AssertJson(expected) => { - let expected_delta: DeltaTextOperations = serde_json::from_str(expected).unwrap(); - let delta = self.editor.document_operations().await.unwrap(); - if expected_delta != delta { - eprintln!("✅ expect: {}", expected,); - eprintln!("❌ receive: {}", delta.json_str()); - } - assert_eq!(expected_delta, delta); - }, - } - sleep(Duration::from_millis(TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS)).await; - } -} diff --git a/frontend/rust-lib/flowy-document2/Cargo.toml b/frontend/rust-lib/flowy-document2/Cargo.toml index 43cc542498..42551adb4c 100644 --- a/frontend/rust-lib/flowy-document2/Cargo.toml +++ b/frontend/rust-lib/flowy-document2/Cargo.toml @@ -10,9 +10,9 @@ collab = { version = "0.1.0" } collab-document = { version = "0.1.0" } appflowy-integrate = {version = "0.1.0" } -flowy-derive = { path = "../flowy-derive" } +flowy-derive = { path = "../../../shared-lib/flowy-derive" } flowy-notification = { path = "../flowy-notification" } -flowy-error = { path = "../flowy-error", features = ["adaptor_sync", "adaptor_ot", "adaptor_serde", "adaptor_database", "adaptor_dispatch", "collab"] } +flowy-error = { path = "../flowy-error", features = ["adaptor_serde", "adaptor_database", "adaptor_dispatch", "collab"] } lib-dispatch = { path = "../lib-dispatch" } @@ -32,7 +32,7 @@ tempfile = "3.4.0" tracing-subscriber = { version = "0.3.3", features = ["env-filter"] } [build-dependencies] -flowy-codegen = { path = "../flowy-codegen"} +flowy-codegen = { path = "../../../shared-lib/flowy-codegen"} [features] dart = ["flowy-codegen/dart", "flowy-notification/dart"] diff --git a/frontend/rust-lib/flowy-document2/src/entities.rs b/frontend/rust-lib/flowy-document2/src/entities.rs index 11a5e090a6..9963f3456b 100644 --- a/frontend/rust-lib/flowy-document2/src/entities.rs +++ b/frontend/rust-lib/flowy-document2/src/entities.rs @@ -157,3 +157,49 @@ pub struct BlockEventPayloadPB { #[pb(index = 4)] pub value: String, } + +#[derive(PartialEq, Eq, Debug, ProtoBuf_Enum, Clone)] +pub enum ExportType { + Text = 0, + Markdown = 1, + Link = 2, +} + +impl Default for ExportType { + fn default() -> Self { + ExportType::Text + } +} + +impl From for ExportType { + fn from(val: i32) -> Self { + match val { + 0 => ExportType::Text, + 1 => ExportType::Markdown, + 2 => ExportType::Link, + _ => { + tracing::error!("Invalid export type: {}", val); + ExportType::Text + }, + } + } +} + +#[derive(Default, ProtoBuf)] +pub struct EditPayloadPB { + #[pb(index = 1)] + pub doc_id: String, + + // Encode in JSON format + #[pb(index = 2)] + pub operations: String, +} + +#[derive(Default, ProtoBuf)] +pub struct ExportDataPB { + #[pb(index = 1)] + pub data: String, + + #[pb(index = 2)] + pub export_type: ExportType, +} diff --git a/frontend/rust-lib/flowy-document2/tests/document/document_insert_test.rs b/frontend/rust-lib/flowy-document2/tests/document/document_insert_test.rs index f0b1ed0cf6..5f4bd0bec5 100644 --- a/frontend/rust-lib/flowy-document2/tests/document/document_insert_test.rs +++ b/frontend/rust-lib/flowy-document2/tests/document/document_insert_test.rs @@ -51,7 +51,7 @@ fn create_and_open_empty_document() -> (DocumentManager, Arc, String) .create_document(doc_id.clone(), data.clone()) .unwrap(); - let document = manager.open_document(doc_id.clone()).unwrap(); + let document = manager.open_document(doc_id).unwrap(); (manager, document, data.0.page_id) } diff --git a/frontend/rust-lib/flowy-error/Cargo.toml b/frontend/rust-lib/flowy-error/Cargo.toml index a55c6ff5f7..8a2fa14cce 100644 --- a/frontend/rust-lib/flowy-error/Cargo.toml +++ b/frontend/rust-lib/flowy-error/Cargo.toml @@ -6,17 +6,13 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -flowy-derive = { path = "../flowy-derive" } +flowy-derive = { path = "../../../shared-lib/flowy-derive" } protobuf = {version = "2.28.0"} bytes = "1.4" anyhow = "1.0" thiserror = "1.0" -flowy-client-sync = { path = "../flowy-client-sync", optional = true} lib-dispatch = { path = "../lib-dispatch", optional = true } -lib-ot = { path = "../../../shared-lib/lib-ot", optional = true} -user-model = { path = "../../../shared-lib/user-model", optional = true} -flowy-client-ws = { path = "../../../shared-lib/flowy-client-ws", optional = true } serde_json = {version = "1.0", optional = true} serde_repr = { version = "0.1" } serde = "1.0" @@ -28,18 +24,14 @@ collab-database = { version = "0.1.0", optional = true } collab-document = { version = "0.1.0", optional = true } [features] -adaptor_sync = ["flowy-client-sync"] -adaptor_ot = ["lib-ot"] adaptor_dispatch = ["lib-dispatch"] adaptor_serde = ["serde_json"] adaptor_reqwest = ["reqwest"] adaptor_database = ["flowy-sqlite", "r2d2"] -adaptor_ws= ["flowy-client-ws"] -adaptor_user= ["user-model"] adaptor_server_error = ["http-error-code"] dart = ["flowy-codegen/dart"] ts = ["flowy-codegen/ts"] collab = ["collab-database", "collab-document"] [build-dependencies] -flowy-codegen = { path = "../flowy-codegen", features = ["proto_gen"]} +flowy-codegen = { path = "../../../shared-lib/flowy-codegen", features = ["proto_gen"]} diff --git a/frontend/rust-lib/flowy-error/src/code.rs b/frontend/rust-lib/flowy-error/src/code.rs index 0827b690c5..4b7abde17a 100644 --- a/frontend/rust-lib/flowy-error/src/code.rs +++ b/frontend/rust-lib/flowy-error/src/code.rs @@ -1,7 +1,8 @@ -use flowy_derive::ProtoBuf_Enum; use serde_repr::*; use thiserror::Error; +use flowy_derive::ProtoBuf_Enum; + #[derive(Debug, Clone, PartialEq, Eq, Error, Serialize_repr, Deserialize_repr, ProtoBuf_Enum)] #[repr(u8)] pub enum ErrorCode { diff --git a/frontend/rust-lib/flowy-error/src/ext/mod.rs b/frontend/rust-lib/flowy-error/src/ext/mod.rs index 901bd707e4..0789b6fb8c 100644 --- a/frontend/rust-lib/flowy-error/src/ext/mod.rs +++ b/frontend/rust-lib/flowy-error/src/ext/mod.rs @@ -1,8 +1,5 @@ -#[cfg(feature = "adaptor_sync")] -pub mod sync; - -#[cfg(feature = "adaptor_ot")] -pub mod ot; +// #[cfg(feature = "adaptor_ot")] +// pub mod ot; #[cfg(feature = "adaptor_serde")] pub mod serde; @@ -16,12 +13,6 @@ pub mod reqwest; #[cfg(feature = "adaptor_database")] pub mod database; -#[cfg(feature = "adaptor_ws")] -pub mod ws; - -#[cfg(feature = "adaptor_user")] -pub mod user; - #[cfg(feature = "adaptor_server_error")] pub mod http_server; diff --git a/frontend/rust-lib/flowy-error/src/ext/ot.rs b/frontend/rust-lib/flowy-error/src/ext/ot.rs deleted file mode 100644 index 65e0354c15..0000000000 --- a/frontend/rust-lib/flowy-error/src/ext/ot.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::FlowyError; - -impl std::convert::From for FlowyError { - fn from(error: lib_ot::errors::OTError) -> Self { - FlowyError::internal().context(error.msg) - } -} diff --git a/frontend/rust-lib/flowy-error/src/ext/sync.rs b/frontend/rust-lib/flowy-error/src/ext/sync.rs deleted file mode 100644 index 3cae9098df..0000000000 --- a/frontend/rust-lib/flowy-error/src/ext/sync.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::FlowyError; - -use flowy_client_sync::errors::ErrorCode; - -impl std::convert::From for FlowyError { - fn from(error: flowy_client_sync::errors::SyncError) -> Self { - match error.code { - ErrorCode::RecordNotFound => FlowyError::record_not_found().context(error.msg), - _ => FlowyError::internal().context(error.msg), - } - } -} diff --git a/frontend/rust-lib/flowy-error/src/ext/user.rs b/frontend/rust-lib/flowy-error/src/ext/user.rs deleted file mode 100644 index 0862c6e4fd..0000000000 --- a/frontend/rust-lib/flowy-error/src/ext/user.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::code::ErrorCode; -use user_model::errors::UserErrorCode; - -impl std::convert::From for ErrorCode { - fn from(code: UserErrorCode) -> Self { - match code { - UserErrorCode::Internal => ErrorCode::Internal, - UserErrorCode::WorkspaceIdInvalid => ErrorCode::WorkspaceIdInvalid, - UserErrorCode::EmailIsEmpty => ErrorCode::EmailIsEmpty, - UserErrorCode::EmailFormatInvalid => ErrorCode::EmailFormatInvalid, - UserErrorCode::UserIdInvalid => ErrorCode::UserIdInvalid, - UserErrorCode::UserNameContainForbiddenCharacters => { - ErrorCode::UserNameContainForbiddenCharacters - }, - UserErrorCode::UserNameIsEmpty => ErrorCode::UserNameIsEmpty, - UserErrorCode::UserNotExist => ErrorCode::UserNotExist, - UserErrorCode::PasswordIsEmpty => ErrorCode::PasswordIsEmpty, - UserErrorCode::PasswordTooLong => ErrorCode::PasswordTooLong, - UserErrorCode::PasswordContainsForbidCharacters => { - ErrorCode::PasswordContainsForbidCharacters - }, - UserErrorCode::PasswordFormatInvalid => ErrorCode::PasswordFormatInvalid, - UserErrorCode::PasswordNotMatch => ErrorCode::PasswordNotMatch, - UserErrorCode::UserNameTooLong => ErrorCode::UserNameTooLong, - } - } -} diff --git a/frontend/rust-lib/flowy-error/src/ext/ws.rs b/frontend/rust-lib/flowy-error/src/ext/ws.rs deleted file mode 100644 index b790ed2f0e..0000000000 --- a/frontend/rust-lib/flowy-error/src/ext/ws.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::FlowyError; -use flowy_client_ws::WSErrorCode; - -impl std::convert::From for FlowyError { - fn from(code: WSErrorCode) -> Self { - match code { - WSErrorCode::Internal => FlowyError::internal().context(code), - } - } -} diff --git a/frontend/rust-lib/flowy-folder/Cargo.toml b/frontend/rust-lib/flowy-folder/Cargo.toml deleted file mode 100644 index dc091b76c6..0000000000 --- a/frontend/rust-lib/flowy-folder/Cargo.toml +++ /dev/null @@ -1,57 +0,0 @@ -[package] -name = "flowy-folder" -version = "0.1.0" -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -folder-model = { path = "../../../shared-lib/folder-model" } -flowy-client-sync = { path = "../flowy-client-sync"} -revision-model = { path = "../../../shared-lib/revision-model" } -ws-model = { path = "../../../shared-lib/ws-model" } -flowy-derive = { path = "../flowy-derive" } -lib-ot = { path = "../../../shared-lib/lib-ot" } -lib-infra = { path = "../../../shared-lib/lib-infra" } - -flowy-document = { path = "../flowy-document" } -flowy-sqlite = { path = "../flowy-sqlite", optional = true } -flowy-error = { path = "../flowy-error", features = ["adaptor_database", "adaptor_dispatch"]} -flowy-notification = { path = "../flowy-notification" } -lib-dispatch = { path = "../lib-dispatch" } -flowy-revision = { path = "../flowy-revision" } -flowy-revision-persistence = { path = "../flowy-revision-persistence" } - -parking_lot = "0.12.1" -protobuf = {version = "2.28.0"} -log = "0.4.17" -diesel = {version = "1.4.8", features = ["sqlite"]} -diesel_derives = {version = "1.4.1", features = ["sqlite"]} -futures = "0.3.26" -pin-project = "1.0" -strum = "0.21" -strum_macros = "0.21" -tokio = { version = "1.26", features = ["rt"] } -lazy_static = "1.4.0" -serde = { version = "1.0", features = ["derive"] } -tracing = { version = "0.1", features = ["log"] } -bytes = { version = "1.4" } -unicode-segmentation = "1.10" -serde_json = "1.0" - -[dev-dependencies] -flowy-folder = { path = "../flowy-folder", features = ["flowy_unit_test"]} -flowy-test = { path = "../flowy-test" } - -[build-dependencies] -flowy-codegen = { path = "../flowy-codegen"} - - -[features] -default = ["rev-sqlite"] -sync = [] -cloud_sync = ["sync"] -rev-sqlite = ["flowy-sqlite", "flowy-folder/rev-sqlite"] -flowy_unit_test = ["lib-ot/flowy_unit_test", "flowy-revision/flowy_unit_test"] -dart = ["flowy-codegen/dart", "flowy-notification/dart"] -ts = ["flowy-codegen/ts", "flowy-notification/ts"] diff --git a/frontend/rust-lib/flowy-folder/Flowy.toml b/frontend/rust-lib/flowy-folder/Flowy.toml deleted file mode 100644 index 0ae861cf30..0000000000 --- a/frontend/rust-lib/flowy-folder/Flowy.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Check out the FlowyConfig (located in flowy_toml.rs) for more details. -proto_input = ["src/entities", "src/event_map.rs", "src/notification.rs"] -event_files = ["src/event_map.rs"] \ No newline at end of file diff --git a/frontend/rust-lib/flowy-folder/build.rs b/frontend/rust-lib/flowy-folder/build.rs deleted file mode 100644 index d17034a7e8..0000000000 --- a/frontend/rust-lib/flowy-folder/build.rs +++ /dev/null @@ -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); -} diff --git a/frontend/rust-lib/flowy-folder/src/entities/app.rs b/frontend/rust-lib/flowy-folder/src/entities/app.rs deleted file mode 100644 index 6b1e3f1f6d..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/app.rs +++ /dev/null @@ -1,222 +0,0 @@ -use crate::{ - entities::parser::{ - app::{AppColorStyle, AppIdentify, AppName}, - workspace::WorkspaceIdentify, - }, - entities::view::RepeatedViewPB, - errors::ErrorCode, - impl_def_and_def_mut, -}; -use flowy_derive::ProtoBuf; -use folder_model::AppRevision; -use std::convert::TryInto; - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct AppPB { - #[pb(index = 1)] - pub id: String, - - #[pb(index = 2)] - pub workspace_id: String, - - #[pb(index = 3)] - pub name: String, - - #[pb(index = 4)] - pub desc: String, - - #[pb(index = 5)] - pub belongings: RepeatedViewPB, - - #[pb(index = 6)] - pub version: i64, - - #[pb(index = 7)] - pub modified_time: i64, - - #[pb(index = 8)] - pub create_time: i64, -} - -impl std::convert::From for AppPB { - fn from(app_serde: AppRevision) -> Self { - AppPB { - id: app_serde.id, - workspace_id: app_serde.workspace_id, - name: app_serde.name, - desc: app_serde.desc, - belongings: app_serde.belongings.into(), - version: app_serde.version, - modified_time: app_serde.modified_time, - create_time: app_serde.create_time, - } - } -} -#[derive(Eq, PartialEq, Debug, Default, ProtoBuf, Clone)] -pub struct RepeatedAppPB { - #[pb(index = 1)] - pub items: Vec, -} - -impl_def_and_def_mut!(RepeatedAppPB, AppPB); - -impl std::convert::From> for RepeatedAppPB { - fn from(values: Vec) -> Self { - let items = values - .into_iter() - .map(|value| value.into()) - .collect::>(); - RepeatedAppPB { items } - } -} -#[derive(ProtoBuf, Default)] -pub struct CreateAppPayloadPB { - #[pb(index = 1)] - pub workspace_id: String, - - #[pb(index = 2)] - pub name: String, - - #[pb(index = 3)] - pub desc: String, - - #[pb(index = 4)] - pub color_style: ColorStylePB, -} - -#[derive(ProtoBuf, Default, Debug, Clone)] -pub struct ColorStylePB { - #[pb(index = 1)] - pub theme_color: String, -} - -#[derive(Debug)] -pub struct CreateAppParams { - pub workspace_id: String, - pub name: String, - pub desc: String, - pub color_style: ColorStylePB, -} - -impl TryInto for CreateAppPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let name = AppName::parse(self.name)?; - let id = WorkspaceIdentify::parse(self.workspace_id)?; - let color_style = AppColorStyle::parse(self.color_style.theme_color.clone())?; - - Ok(CreateAppParams { - workspace_id: id.0, - name: name.0, - desc: self.desc, - color_style: color_style.into(), - }) - } -} - -impl std::convert::From for ColorStylePB { - fn from(data: AppColorStyle) -> Self { - ColorStylePB { - theme_color: data.theme_color, - } - } -} - -#[derive(ProtoBuf, Default, Clone, Debug)] -pub struct AppIdPB { - #[pb(index = 1)] - pub value: String, -} - -impl AppIdPB { - pub fn new(app_id: &str) -> Self { - Self { - value: app_id.to_string(), - } - } -} - -#[derive(ProtoBuf, Default)] -pub struct UpdateAppPayloadPB { - #[pb(index = 1)] - pub app_id: String, - - #[pb(index = 2, one_of)] - pub name: Option, - - #[pb(index = 3, one_of)] - pub desc: Option, - - #[pb(index = 4, one_of)] - pub color_style: Option, - - #[pb(index = 5, one_of)] - pub is_trash: Option, -} - -#[derive(Debug, Clone)] -pub struct UpdateAppParams { - pub app_id: String, - - pub name: Option, - - pub desc: Option, - - pub color_style: Option, - - pub is_trash: Option, -} - -impl UpdateAppParams { - pub fn new(app_id: &str) -> Self { - Self { - app_id: app_id.to_string(), - name: None, - desc: None, - color_style: None, - is_trash: None, - } - } - - pub fn name(mut self, name: &str) -> Self { - self.name = Some(name.to_string()); - self - } - - pub fn desc(mut self, desc: &str) -> Self { - self.desc = Some(desc.to_string()); - self - } - - pub fn trash(mut self) -> Self { - self.is_trash = Some(true); - self - } -} - -impl TryInto for UpdateAppPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let app_id = AppIdentify::parse(self.app_id)?.0; - - let name = match self.name { - None => None, - Some(name) => Some(AppName::parse(name)?.0), - }; - - let color_style = match self.color_style { - None => None, - Some(color_style) => Some(AppColorStyle::parse(color_style.theme_color)?.into()), - }; - - Ok(UpdateAppParams { - app_id, - name, - desc: self.desc, - color_style, - is_trash: self.is_trash, - }) - } -} diff --git a/frontend/rust-lib/flowy-folder/src/entities/mod.rs b/frontend/rust-lib/flowy-folder/src/entities/mod.rs deleted file mode 100644 index d5370f0786..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod app; -mod parser; -pub mod trash; -pub mod view; -pub mod workspace; - -pub use app::*; -pub use trash::*; -pub use view::*; -pub use workspace::*; diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_color_style.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_color_style.rs deleted file mode 100644 index cad7851b3f..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_color_style.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::errors::ErrorCode; - -#[derive(Debug)] -pub struct AppColorStyle { - pub theme_color: String, -} - -impl AppColorStyle { - pub fn parse(theme_color: String) -> Result { - // TODO: verify the color style format - Ok(AppColorStyle { theme_color }) - } -} diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_desc.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_desc.rs deleted file mode 100644 index 24cc2dcab8..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_desc.rs +++ /dev/null @@ -1,20 +0,0 @@ -use unicode_segmentation::UnicodeSegmentation; -#[derive(Debug)] -pub struct AppDesc(pub String); - -impl AppDesc { - #[allow(dead_code)] - pub fn parse(s: String) -> Result { - if s.graphemes(true).count() > 1024 { - return Err("Workspace description too long".to_string()); - } - - Ok(Self(s)) - } -} - -impl AsRef for AppDesc { - fn as_ref(&self) -> &str { - &self.0 - } -} diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_id.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_id.rs deleted file mode 100644 index 889c219b3a..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_id.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::errors::ErrorCode; - -#[derive(Debug)] -pub struct AppIdentify(pub String); - -impl AppIdentify { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err(ErrorCode::AppIdInvalid); - } - - Ok(Self(s)) - } -} - -impl AsRef for AppIdentify { - fn as_ref(&self) -> &str { - &self.0 - } -} diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_name.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_name.rs deleted file mode 100644 index c36a43b758..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/app/app_name.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::errors::ErrorCode; - -#[derive(Debug)] -pub struct AppName(pub String); - -impl AppName { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err(ErrorCode::AppNameInvalid); - } - - Ok(Self(s)) - } -} - -impl AsRef for AppName { - fn as_ref(&self) -> &str { - &self.0 - } -} diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/app/mod.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/app/mod.rs deleted file mode 100644 index fcf6f4bd3f..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/app/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod app_color_style; -mod app_desc; -mod app_id; -mod app_name; - -pub use app_color_style::*; -pub use app_desc::*; -pub use app_id::*; -pub use app_name::*; diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/mod.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/mod.rs deleted file mode 100644 index 3ee0f4b591..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod app; -pub mod trash; -pub mod view; -pub mod workspace; diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/trash/mod.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/trash/mod.rs deleted file mode 100644 index ddb6a3b91e..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/trash/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod trash_id; - -pub use trash_id::*; diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/trash/trash_id.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/trash/trash_id.rs deleted file mode 100644 index 3b6a4c4f6d..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/trash/trash_id.rs +++ /dev/null @@ -1,34 +0,0 @@ -#[derive(Debug)] -pub struct TrashIdentify(pub String); - -impl TrashIdentify { - #[allow(dead_code)] - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err("Trash id can not be empty or whitespace".to_string()); - } - - Ok(Self(s)) - } -} - -impl AsRef for TrashIdentify { - fn as_ref(&self) -> &str { - &self.0 - } -} - -#[derive(Debug)] -pub struct TrashIds(pub Vec); - -impl TrashIds { - #[allow(dead_code)] - pub fn parse(ids: Vec) -> Result { - let mut trash_ids = vec![]; - for id in ids { - let id = TrashIdentify::parse(id)?; - trash_ids.push(id.0); - } - Ok(Self(trash_ids)) - } -} diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/view/mod.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/view/mod.rs deleted file mode 100644 index 399cc44c45..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/view/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod view_desc; -mod view_id; -mod view_name; -mod view_thumbnail; - -pub use view_desc::*; -pub use view_id::*; -pub use view_name::*; -pub use view_thumbnail::*; diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_desc.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_desc.rs deleted file mode 100644 index 7427573c16..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_desc.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::errors::ErrorCode; -use unicode_segmentation::UnicodeSegmentation; - -#[derive(Debug)] -pub struct ViewDesc(pub String); - -impl ViewDesc { - pub fn parse(s: String) -> Result { - if s.graphemes(true).count() > 1000 { - return Err(ErrorCode::ViewDescTooLong); - } - - Ok(Self(s)) - } -} - -impl AsRef for ViewDesc { - fn as_ref(&self) -> &str { - &self.0 - } -} diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_id.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_id.rs deleted file mode 100644 index 83ead5b00b..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_id.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::errors::ErrorCode; - -#[derive(Debug)] -pub struct ViewIdentify(pub String); - -impl ViewIdentify { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err(ErrorCode::ViewIdIsInvalid); - } - - Ok(Self(s)) - } -} - -impl AsRef for ViewIdentify { - fn as_ref(&self) -> &str { - &self.0 - } -} diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_name.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_name.rs deleted file mode 100644 index 08f35a47fb..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_name.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::errors::ErrorCode; -use unicode_segmentation::UnicodeSegmentation; - -#[derive(Debug)] -pub struct ViewName(pub String); - -impl ViewName { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err(ErrorCode::ViewNameInvalid); - } - - if s.graphemes(true).count() > 256 { - return Err(ErrorCode::ViewNameTooLong); - } - - Ok(Self(s)) - } -} - -impl AsRef for ViewName { - fn as_ref(&self) -> &str { - &self.0 - } -} diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_thumbnail.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_thumbnail.rs deleted file mode 100644 index 936a7ae7a1..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/view/view_thumbnail.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::errors::ErrorCode; - -#[derive(Debug)] -pub struct ViewThumbnail(pub String); - -impl ViewThumbnail { - pub fn parse(s: String) -> Result { - // if s.trim().is_empty() { - // return Err(format!("View thumbnail can not be empty or whitespace")); - // } - // TODO: verify the thumbnail url is valid or not - - Ok(Self(s)) - } -} - -impl AsRef for ViewThumbnail { - fn as_ref(&self) -> &str { - &self.0 - } -} diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/mod.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/mod.rs deleted file mode 100644 index 876ba10858..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod workspace_desc; -mod workspace_id; -mod workspace_name; - -pub use workspace_desc::*; -pub use workspace_id::*; -pub use workspace_name::*; diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_desc.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_desc.rs deleted file mode 100644 index f90a27fe34..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_desc.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::errors::ErrorCode; -use unicode_segmentation::UnicodeSegmentation; - -#[derive(Debug)] -pub struct WorkspaceDesc(pub String); - -impl WorkspaceDesc { - pub fn parse(s: String) -> Result { - if s.graphemes(true).count() > 1024 { - return Err(ErrorCode::WorkspaceNameTooLong); - } - - Ok(Self(s)) - } -} - -impl AsRef for WorkspaceDesc { - fn as_ref(&self) -> &str { - &self.0 - } -} diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_id.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_id.rs deleted file mode 100644 index f8f5a8ebee..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_id.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::errors::ErrorCode; - -#[derive(Debug)] -pub struct WorkspaceIdentify(pub String); - -impl WorkspaceIdentify { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err(ErrorCode::WorkspaceIdInvalid); - } - - Ok(Self(s)) - } -} - -impl AsRef for WorkspaceIdentify { - fn as_ref(&self) -> &str { - &self.0 - } -} diff --git a/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_name.rs b/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_name.rs deleted file mode 100644 index 3028546be0..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/parser/workspace/workspace_name.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::errors::ErrorCode; -use unicode_segmentation::UnicodeSegmentation; - -#[derive(Debug)] -pub struct WorkspaceName(pub String); - -impl WorkspaceName { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err(ErrorCode::WorkspaceNameInvalid); - } - - if s.graphemes(true).count() > 256 { - return Err(ErrorCode::WorkspaceNameTooLong); - } - - Ok(Self(s)) - } -} - -impl AsRef for WorkspaceName { - fn as_ref(&self) -> &str { - &self.0 - } -} diff --git a/frontend/rust-lib/flowy-folder/src/entities/trash.rs b/frontend/rust-lib/flowy-folder/src/entities/trash.rs deleted file mode 100644 index b5e866e0ed..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/trash.rs +++ /dev/null @@ -1,190 +0,0 @@ -use crate::impl_def_and_def_mut; -use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use folder_model::{TrashRevision, TrashTypeRevision}; -use serde::{Deserialize, Serialize}; -use std::fmt::Formatter; - -#[derive(Eq, PartialEq, ProtoBuf, Default, Debug, Clone)] -pub struct TrashPB { - #[pb(index = 1)] - pub id: String, - - #[pb(index = 2)] - pub name: String, - - #[pb(index = 3)] - pub modified_time: i64, - - #[pb(index = 4)] - pub create_time: i64, - - #[pb(index = 5)] - pub ty: TrashType, -} - -impl std::convert::From for TrashPB { - fn from(trash_rev: TrashRevision) -> Self { - TrashPB { - id: trash_rev.id, - name: trash_rev.name, - modified_time: trash_rev.modified_time, - create_time: trash_rev.create_time, - ty: trash_rev.ty.into(), - } - } -} - -impl std::convert::From for TrashRevision { - fn from(trash: TrashPB) -> Self { - TrashRevision { - id: trash.id, - name: trash.name, - modified_time: trash.modified_time, - create_time: trash.create_time, - ty: trash.ty.into(), - } - } -} -#[derive(PartialEq, Eq, Debug, Default, ProtoBuf, Clone)] -pub struct RepeatedTrashPB { - #[pb(index = 1)] - pub items: Vec, -} - -impl_def_and_def_mut!(RepeatedTrashPB, TrashPB); -impl std::convert::From> for RepeatedTrashPB { - fn from(trash_revs: Vec) -> Self { - let items: Vec = trash_revs - .into_iter() - .map(|trash_rev| trash_rev.into()) - .collect(); - RepeatedTrashPB { items } - } -} - -#[derive(Eq, PartialEq, Debug, ProtoBuf_Enum, Clone, Serialize, Deserialize)] -pub enum TrashType { - TrashUnknown = 0, - TrashView = 1, - TrashApp = 2, -} - -impl std::convert::TryFrom for TrashType { - type Error = String; - - fn try_from(value: i32) -> Result { - match value { - 0 => Ok(TrashType::TrashUnknown), - 1 => Ok(TrashType::TrashView), - 2 => Ok(TrashType::TrashApp), - _ => Err(format!("Invalid trash type: {}", value)), - } - } -} - -impl std::convert::From for TrashType { - fn from(rev: TrashTypeRevision) -> Self { - match rev { - TrashTypeRevision::Unknown => TrashType::TrashUnknown, - TrashTypeRevision::TrashView => TrashType::TrashView, - TrashTypeRevision::TrashApp => TrashType::TrashApp, - } - } -} - -impl std::convert::From for TrashTypeRevision { - fn from(rev: TrashType) -> Self { - match rev { - TrashType::TrashUnknown => TrashTypeRevision::Unknown, - TrashType::TrashView => TrashTypeRevision::TrashView, - TrashType::TrashApp => TrashTypeRevision::TrashApp, - } - } -} - -impl std::default::Default for TrashType { - fn default() -> Self { - TrashType::TrashUnknown - } -} - -#[derive(PartialEq, Eq, ProtoBuf, Default, Debug, Clone)] -pub struct RepeatedTrashIdPB { - #[pb(index = 1)] - pub items: Vec, - - #[pb(index = 2)] - pub delete_all: bool, -} - -impl std::fmt::Display for RepeatedTrashIdPB { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&format!( - "{:?}", - &self - .items - .iter() - .map(|item| format!("{}", item)) - .collect::>() - )) - } -} - -impl RepeatedTrashIdPB { - pub fn all() -> RepeatedTrashIdPB { - RepeatedTrashIdPB { - items: vec![], - delete_all: true, - } - } -} - -impl std::convert::From> for RepeatedTrashIdPB { - fn from(items: Vec) -> Self { - RepeatedTrashIdPB { - items, - delete_all: false, - } - } -} - -impl std::convert::From> for RepeatedTrashIdPB { - fn from(trash: Vec) -> Self { - let items = trash - .into_iter() - .map(|t| TrashIdPB { - id: t.id, - ty: t.ty.into(), - }) - .collect::>(); - - RepeatedTrashIdPB { - items, - delete_all: false, - } - } -} - -#[derive(PartialEq, Eq, ProtoBuf, Default, Debug, Clone)] -pub struct TrashIdPB { - #[pb(index = 1)] - pub id: String, - - #[pb(index = 2)] - pub ty: TrashType, -} - -impl std::fmt::Display for TrashIdPB { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&format!("{:?}:{}", self.ty, self.id)) - } -} - -impl std::convert::From<&TrashRevision> for TrashIdPB { - fn from(trash: &TrashRevision) -> Self { - TrashIdPB { - id: trash.id.clone(), - ty: trash.ty.clone().into(), - } - } -} diff --git a/frontend/rust-lib/flowy-folder/src/entities/view.rs b/frontend/rust-lib/flowy-folder/src/entities/view.rs deleted file mode 100644 index 57560ddbd2..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/view.rs +++ /dev/null @@ -1,407 +0,0 @@ -use crate::{ - entities::parser::{ - app::AppIdentify, - view::{ViewDesc, ViewIdentify, ViewName, ViewThumbnail}, - }, - errors::ErrorCode, - impl_def_and_def_mut, -}; -use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use folder_model::{gen_view_id, ViewDataFormatRevision, ViewLayoutTypeRevision, ViewRevision}; -use std::collections::HashMap; -use std::convert::TryInto; - -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct ViewPB { - #[pb(index = 1)] - pub id: String, - - #[pb(index = 2)] - pub app_id: String, - - #[pb(index = 3)] - pub name: String, - - #[pb(index = 4)] - pub data_format: ViewDataFormatPB, - - #[pb(index = 5)] - pub modified_time: i64, - - #[pb(index = 6)] - pub create_time: i64, - - #[pb(index = 7)] - pub layout: ViewLayoutTypePB, -} - -impl std::convert::From for ViewPB { - fn from(rev: ViewRevision) -> Self { - ViewPB { - id: rev.id, - app_id: rev.app_id, - name: rev.name, - data_format: rev.data_format.into(), - modified_time: rev.modified_time, - create_time: rev.create_time, - layout: rev.layout.into(), - } - } -} - -#[derive(Eq, PartialEq, Hash, Debug, ProtoBuf_Enum, Clone)] -pub enum ViewDataFormatPB { - /// Indicate this view is using `Delta` for the persistence data format, it's deprecated. - DeltaFormat = 0, - /// Indicate this view is using `Database` for the persistence data format. It is used in AppFlowy database - /// views including Grid,Board, and Calendar. - DatabaseFormat = 1, - /// Indicate this view is using `Node` for the persistence data format. It is used in AppFlowy document - NodeFormat = 2, -} - -impl std::default::Default for ViewDataFormatPB { - fn default() -> Self { - ViewDataFormatRevision::default().into() - } -} - -impl std::convert::From for ViewDataFormatPB { - fn from(rev: ViewDataFormatRevision) -> Self { - match rev { - ViewDataFormatRevision::DeltaFormat => ViewDataFormatPB::DeltaFormat, - ViewDataFormatRevision::DatabaseFormat => ViewDataFormatPB::DatabaseFormat, - ViewDataFormatRevision::NodeFormat => ViewDataFormatPB::NodeFormat, - } - } -} - -impl std::convert::From for ViewDataFormatRevision { - fn from(ty: ViewDataFormatPB) -> Self { - match ty { - ViewDataFormatPB::DeltaFormat => ViewDataFormatRevision::DeltaFormat, - ViewDataFormatPB::DatabaseFormat => ViewDataFormatRevision::DatabaseFormat, - ViewDataFormatPB::NodeFormat => ViewDataFormatRevision::NodeFormat, - } - } -} - -#[derive(Eq, PartialEq, Hash, Debug, ProtoBuf_Enum, Clone)] -pub enum ViewLayoutTypePB { - Document = 0, - Grid = 3, - Board = 4, - Calendar = 5, -} - -impl std::default::Default for ViewLayoutTypePB { - fn default() -> Self { - ViewLayoutTypePB::Grid - } -} - -impl std::convert::From for ViewLayoutTypePB { - fn from(rev: ViewLayoutTypeRevision) -> Self { - match rev { - ViewLayoutTypeRevision::Grid => ViewLayoutTypePB::Grid, - ViewLayoutTypeRevision::Board => ViewLayoutTypePB::Board, - ViewLayoutTypeRevision::Document => ViewLayoutTypePB::Document, - ViewLayoutTypeRevision::Calendar => ViewLayoutTypePB::Calendar, - } - } -} - -impl std::convert::From for ViewLayoutTypeRevision { - fn from(rev: ViewLayoutTypePB) -> Self { - match rev { - ViewLayoutTypePB::Grid => ViewLayoutTypeRevision::Grid, - ViewLayoutTypePB::Board => ViewLayoutTypeRevision::Board, - ViewLayoutTypePB::Document => ViewLayoutTypeRevision::Document, - ViewLayoutTypePB::Calendar => ViewLayoutTypeRevision::Calendar, - } - } -} - -#[derive(Eq, PartialEq, Debug, Default, ProtoBuf, Clone)] -pub struct RepeatedViewPB { - #[pb(index = 1)] - pub items: Vec, -} - -impl_def_and_def_mut!(RepeatedViewPB, ViewPB); - -impl std::convert::From> for RepeatedViewPB { - fn from(values: Vec) -> Self { - let items = values - .into_iter() - .map(|value| value.into()) - .collect::>(); - RepeatedViewPB { items } - } -} -#[derive(Default, ProtoBuf)] -pub struct RepeatedViewIdPB { - #[pb(index = 1)] - pub items: Vec, -} - -#[derive(Default, ProtoBuf)] -pub struct CreateViewPayloadPB { - #[pb(index = 1)] - pub belong_to_id: String, - - #[pb(index = 2)] - pub name: String, - - #[pb(index = 3)] - pub desc: String, - - #[pb(index = 4, one_of)] - pub thumbnail: Option, - - #[pb(index = 5)] - pub layout: ViewLayoutTypePB, - - #[pb(index = 6)] - pub initial_data: Vec, - - #[pb(index = 7)] - pub ext: HashMap, -} - -#[derive(Debug, Clone)] -pub struct CreateViewParams { - pub belong_to_id: String, - pub name: String, - pub desc: String, - pub thumbnail: String, - pub data_format: ViewDataFormatPB, - pub layout: ViewLayoutTypePB, - pub view_id: String, - pub initial_data: Vec, - pub ext: HashMap, -} - -impl TryInto for CreateViewPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let name = ViewName::parse(self.name)?.0; - let belong_to_id = AppIdentify::parse(self.belong_to_id)?.0; - let view_id = gen_view_id(); - let thumbnail = match self.thumbnail { - None => "".to_string(), - Some(thumbnail) => ViewThumbnail::parse(thumbnail)?.0, - }; - let data_format = data_format_from_layout(&self.layout); - - Ok(CreateViewParams { - belong_to_id, - name, - desc: self.desc, - data_format, - layout: self.layout, - thumbnail, - view_id, - initial_data: self.initial_data, - ext: self.ext, - }) - } -} - -pub fn data_format_from_layout(layout: &ViewLayoutTypePB) -> ViewDataFormatPB { - match layout { - ViewLayoutTypePB::Document => ViewDataFormatPB::NodeFormat, - ViewLayoutTypePB::Grid => ViewDataFormatPB::DatabaseFormat, - ViewLayoutTypePB::Board => ViewDataFormatPB::DatabaseFormat, - ViewLayoutTypePB::Calendar => ViewDataFormatPB::DatabaseFormat, - } -} - -#[derive(Default, ProtoBuf, Clone, Debug)] -pub struct ViewIdPB { - #[pb(index = 1)] - pub value: String, -} - -impl std::convert::From<&str> for ViewIdPB { - fn from(value: &str) -> Self { - ViewIdPB { - value: value.to_string(), - } - } -} - -#[derive(Default, ProtoBuf, Clone, Debug)] -pub struct DeletedViewPB { - #[pb(index = 1)] - pub view_id: String, - - #[pb(index = 2, one_of)] - pub index: Option, -} - -impl std::ops::Deref for ViewIdPB { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.value - } -} - -#[derive(Default, ProtoBuf)] -pub struct UpdateViewPayloadPB { - #[pb(index = 1)] - pub view_id: String, - - #[pb(index = 2, one_of)] - pub name: Option, - - #[pb(index = 3, one_of)] - pub desc: Option, - - #[pb(index = 4, one_of)] - pub thumbnail: Option, -} - -#[derive(Clone, Debug)] -pub struct UpdateViewParams { - pub view_id: String, - pub name: Option, - pub desc: Option, - pub thumbnail: Option, -} - -impl TryInto for UpdateViewPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let view_id = ViewIdentify::parse(self.view_id)?.0; - - let name = match self.name { - None => None, - Some(name) => Some(ViewName::parse(name)?.0), - }; - - let desc = match self.desc { - None => None, - Some(desc) => Some(ViewDesc::parse(desc)?.0), - }; - - let thumbnail = match self.thumbnail { - None => None, - Some(thumbnail) => Some(ViewThumbnail::parse(thumbnail)?.0), - }; - - Ok(UpdateViewParams { - view_id, - name, - desc, - thumbnail, - }) - } -} - -#[derive(ProtoBuf_Enum)] -pub enum MoveFolderItemType { - MoveApp = 0, - MoveView = 1, -} - -impl std::default::Default for MoveFolderItemType { - fn default() -> Self { - MoveFolderItemType::MoveApp - } -} - -#[derive(Default, ProtoBuf)] -pub struct MoveFolderItemPayloadPB { - #[pb(index = 1)] - pub item_id: String, - - #[pb(index = 2)] - pub from: i32, - - #[pb(index = 3)] - pub to: i32, - - #[pb(index = 4)] - pub ty: MoveFolderItemType, -} - -pub struct MoveFolderItemParams { - pub item_id: String, - pub from: usize, - pub to: usize, - pub ty: MoveFolderItemType, -} - -impl TryInto for MoveFolderItemPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let view_id = ViewIdentify::parse(self.item_id)?.0; - Ok(MoveFolderItemParams { - item_id: view_id, - from: self.from as usize, - to: self.to as usize, - ty: self.ty, - }) - } -} - -// impl<'de> Deserialize<'de> for ViewDataType { -// fn deserialize(deserializer: D) -> Result>::Error> -// where -// D: Deserializer<'de>, -// { -// struct ViewTypeVisitor(); -// -// impl<'de> Visitor<'de> for ViewTypeVisitor { -// type Value = ViewDataType; -// fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { -// formatter.write_str("RichText, PlainText") -// } -// -// fn visit_u8(self, v: u8) -> Result -// where -// E: de::Error, -// { -// let data_type; -// match v { -// 0 => { -// data_type = ViewDataType::RichText; -// } -// 1 => { -// data_type = ViewDataType::PlainText; -// } -// _ => { -// return Err(de::Error::invalid_value(Unexpected::Unsigned(v as u64), &self)); -// } -// } -// Ok(data_type) -// } -// -// fn visit_str(self, s: &str) -> Result -// where -// E: de::Error, -// { -// let data_type; -// match s { -// "Doc" | "RichText" => { -// // Rename ViewDataType::Doc to ViewDataType::RichText, So we need to migrate the ViewType manually. -// data_type = ViewDataType::RichText; -// } -// "PlainText" => { -// data_type = ViewDataType::PlainText; -// } -// unknown => { -// return Err(de::Error::invalid_value(Unexpected::Str(unknown), &self)); -// } -// } -// Ok(data_type) -// } -// } -// deserializer.deserialize_any(ViewTypeVisitor()) -// } -// } diff --git a/frontend/rust-lib/flowy-folder/src/entities/workspace.rs b/frontend/rust-lib/flowy-folder/src/entities/workspace.rs deleted file mode 100644 index 12c0ceeae1..0000000000 --- a/frontend/rust-lib/flowy-folder/src/entities/workspace.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::{ - entities::parser::workspace::{WorkspaceDesc, WorkspaceIdentify, WorkspaceName}, - entities::{app::RepeatedAppPB, view::ViewPB}, - errors::*, - impl_def_and_def_mut, -}; -use flowy_derive::ProtoBuf; -use folder_model::WorkspaceRevision; -use std::convert::TryInto; - -#[derive(Eq, PartialEq, ProtoBuf, Default, Debug, Clone)] -pub struct WorkspacePB { - #[pb(index = 1)] - pub id: String, - - #[pb(index = 2)] - pub name: String, - - #[pb(index = 3)] - pub desc: String, - - #[pb(index = 4)] - pub apps: RepeatedAppPB, - - #[pb(index = 5)] - pub modified_time: i64, - - #[pb(index = 6)] - pub create_time: i64, -} - -impl std::convert::From for WorkspacePB { - fn from(workspace_serde: WorkspaceRevision) -> Self { - WorkspacePB { - id: workspace_serde.id, - name: workspace_serde.name, - desc: workspace_serde.desc, - apps: workspace_serde.apps.into(), - modified_time: workspace_serde.modified_time, - create_time: workspace_serde.create_time, - } - } -} -#[derive(PartialEq, Eq, Debug, Default, ProtoBuf)] -pub struct RepeatedWorkspacePB { - #[pb(index = 1)] - pub items: Vec, -} - -impl_def_and_def_mut!(RepeatedWorkspacePB, WorkspacePB); - -#[derive(ProtoBuf, Default)] -pub struct CreateWorkspacePayloadPB { - #[pb(index = 1)] - pub name: String, - - #[pb(index = 2)] - pub desc: String, -} - -#[derive(Clone, Debug)] -pub struct CreateWorkspaceParams { - pub name: String, - pub desc: String, -} - -impl TryInto for CreateWorkspacePayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let name = WorkspaceName::parse(self.name)?; - let desc = WorkspaceDesc::parse(self.desc)?; - - Ok(CreateWorkspaceParams { - name: name.0, - desc: desc.0, - }) - } -} - -// Read all workspaces if the workspace_id is None -#[derive(Clone, ProtoBuf, Default, Debug)] -pub struct WorkspaceIdPB { - #[pb(index = 1, one_of)] - pub value: Option, -} - -impl WorkspaceIdPB { - pub fn new(workspace_id: Option) -> Self { - Self { - value: workspace_id, - } - } -} - -#[derive(Default, ProtoBuf, Clone)] -pub struct WorkspaceSettingPB { - #[pb(index = 1)] - pub workspace: WorkspacePB, - - #[pb(index = 2, one_of)] - pub latest_view: Option, -} - -#[derive(ProtoBuf, Default)] -pub struct UpdateWorkspacePayloadPB { - #[pb(index = 1)] - pub id: String, - - #[pb(index = 2, one_of)] - pub name: Option, - - #[pb(index = 3, one_of)] - pub desc: Option, -} - -#[derive(Clone, Debug)] -pub struct UpdateWorkspaceParams { - pub id: String, - pub name: Option, - pub desc: Option, -} - -impl TryInto for UpdateWorkspacePayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let name = match self.name { - None => None, - Some(name) => Some(WorkspaceName::parse(name)?.0), - }; - let id = WorkspaceIdentify::parse(self.id)?; - - Ok(UpdateWorkspaceParams { - id: id.0, - name, - desc: self.desc, - }) - } -} diff --git a/frontend/rust-lib/flowy-folder/src/event_map.rs b/frontend/rust-lib/flowy-folder/src/event_map.rs deleted file mode 100644 index 9790b26f14..0000000000 --- a/frontend/rust-lib/flowy-folder/src/event_map.rs +++ /dev/null @@ -1,250 +0,0 @@ -use crate::{ - entities::{ - app::{AppIdPB, CreateAppParams, UpdateAppParams}, - trash::RepeatedTrashIdPB, - view::{CreateViewParams, RepeatedViewIdPB, UpdateViewParams, ViewIdPB}, - workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceIdPB}, - }, - errors::FlowyError, - manager::FolderManager, - services::{ - app::event_handler::*, trash::event_handler::*, view::event_handler::*, - workspace::event_handler::*, - }, -}; -use flowy_derive::{Flowy_Event, ProtoBuf_Enum}; -use flowy_sqlite::{ConnectionPool, DBConnection}; -use folder_model::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; -use lib_dispatch::prelude::*; -use lib_infra::future::FutureResult; -use std::sync::Arc; -use strum_macros::Display; - -pub trait WorkspaceDeps: WorkspaceUser + WorkspaceDatabase {} - -pub trait WorkspaceUser: Send + Sync { - fn user_id(&self) -> Result; - fn token(&self) -> Result; -} - -pub trait WorkspaceDatabase: Send + Sync { - fn db_pool(&self) -> Result, FlowyError>; - - fn db_connection(&self) -> Result { - let pool = self.db_pool()?; - let conn = pool.get().map_err(|e| FlowyError::internal().context(e))?; - Ok(conn) - } -} - -pub fn init(folder: Arc) -> AFPlugin { - let mut plugin = AFPlugin::new() - .name("Flowy-Workspace") - .state(folder.workspace_controller.clone()) - .state(folder.app_controller.clone()) - .state(folder.view_controller.clone()) - .state(folder.trash_controller.clone()) - .state(folder.clone()); - - // Workspace - plugin = plugin - .event(FolderEvent::CreateWorkspace, create_workspace_handler) - .event( - FolderEvent::ReadCurrentWorkspace, - read_cur_workspace_handler, - ) - .event(FolderEvent::ReadWorkspaces, read_workspaces_handler) - .event(FolderEvent::OpenWorkspace, open_workspace_handler) - .event(FolderEvent::ReadWorkspaceApps, read_workspace_apps_handler); - - // App - plugin = plugin - .event(FolderEvent::CreateApp, create_app_handler) - .event(FolderEvent::ReadApp, read_app_handler) - .event(FolderEvent::UpdateApp, update_app_handler) - .event(FolderEvent::DeleteApp, delete_app_handler); - - // View - plugin = plugin - .event(FolderEvent::CreateView, create_view_handler) - .event(FolderEvent::ReadView, read_view_handler) - .event(FolderEvent::UpdateView, update_view_handler) - .event(FolderEvent::DeleteView, delete_view_handler) - .event(FolderEvent::DuplicateView, duplicate_view_handler) - .event(FolderEvent::SetLatestView, set_latest_view_handler) - .event(FolderEvent::CloseView, close_view_handler) - .event(FolderEvent::MoveItem, move_item_handler); - - // Trash - plugin = plugin - .event(FolderEvent::ReadTrash, read_trash_handler) - .event(FolderEvent::PutbackTrash, putback_trash_handler) - .event(FolderEvent::DeleteTrash, delete_trash_handler) - .event(FolderEvent::RestoreAllTrash, restore_all_trash_handler) - .event(FolderEvent::DeleteAllTrash, delete_all_trash_handler); - - plugin -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)] -#[event_err = "FlowyError"] -pub enum FolderEvent { - /// Create a new workspace - #[event(input = "CreateWorkspacePayloadPB", output = "WorkspacePB")] - CreateWorkspace = 0, - - /// Read the current opening workspace - #[event(output = "WorkspaceSettingPB")] - ReadCurrentWorkspace = 1, - - /// Open the workspace and mark it as the current workspace - #[event(input = "WorkspaceIdPB", output = "RepeatedWorkspacePB")] - ReadWorkspaces = 2, - - /// Delete the workspace - #[event(input = "WorkspaceIdPB")] - DeleteWorkspace = 3, - - /// Open the workspace and mark it as the current workspace - #[event(input = "WorkspaceIdPB", output = "WorkspacePB")] - OpenWorkspace = 4, - - /// Return a list of apps that belong to this workspace - #[event(input = "WorkspaceIdPB", output = "RepeatedAppPB")] - ReadWorkspaceApps = 5, - - /// Create a new app - #[event(input = "CreateAppPayloadPB", output = "AppPB")] - CreateApp = 101, - - /// Delete the app - #[event(input = "AppIdPB")] - DeleteApp = 102, - - /// Read the app - #[event(input = "AppIdPB", output = "AppPB")] - ReadApp = 103, - - /// Update the app's properties including the name,description, etc. - #[event(input = "UpdateAppPayloadPB")] - UpdateApp = 104, - - /// Create a new view in the corresponding app - #[event(input = "CreateViewPayloadPB", output = "ViewPB")] - CreateView = 201, - - /// Return the view info - #[event(input = "ViewIdPB", output = "ViewPB")] - ReadView = 202, - - /// Update the view's properties including the name,description, etc. - #[event(input = "UpdateViewPayloadPB", output = "ViewPB")] - UpdateView = 203, - - /// Move the view to the trash folder - #[event(input = "RepeatedViewIdPB")] - DeleteView = 204, - - /// Duplicate the view - #[event(input = "ViewPB")] - DuplicateView = 205, - - /// Close and release the resources that are used by this view. - /// It should get called when the 'View' page get destroy - #[event(input = "ViewIdPB")] - CloseView = 206, - - #[event()] - CopyLink = 220, - - /// Set the current visiting view - #[event(input = "ViewIdPB")] - SetLatestView = 221, - - /// Move the view or app to another place - #[event(input = "MoveFolderItemPayloadPB")] - MoveItem = 230, - - /// Read the trash that was deleted by the user - #[event(output = "RepeatedTrashPB")] - ReadTrash = 300, - - /// Put back the trash to the origin folder - #[event(input = "TrashIdPB")] - PutbackTrash = 301, - - /// Delete the trash from the disk - #[event(input = "RepeatedTrashIdPB")] - DeleteTrash = 302, - - /// Put back all the trash to its original folder - #[event()] - RestoreAllTrash = 303, - - /// Delete all the trash from the disk - #[event()] - DeleteAllTrash = 304, -} - -pub trait FolderCouldServiceV1: Send + Sync { - fn init(&self); - - // Workspace - fn create_workspace( - &self, - token: &str, - params: CreateWorkspaceParams, - ) -> FutureResult; - - fn read_workspace( - &self, - token: &str, - params: WorkspaceIdPB, - ) -> FutureResult, FlowyError>; - - fn update_workspace( - &self, - token: &str, - params: UpdateWorkspaceParams, - ) -> FutureResult<(), FlowyError>; - - fn delete_workspace(&self, token: &str, params: WorkspaceIdPB) -> FutureResult<(), FlowyError>; - - // View - fn create_view( - &self, - token: &str, - params: CreateViewParams, - ) -> FutureResult; - - fn read_view( - &self, - token: &str, - params: ViewIdPB, - ) -> FutureResult, FlowyError>; - - fn delete_view(&self, token: &str, params: RepeatedViewIdPB) -> FutureResult<(), FlowyError>; - - fn update_view(&self, token: &str, params: UpdateViewParams) -> FutureResult<(), FlowyError>; - - // App - fn create_app( - &self, - token: &str, - params: CreateAppParams, - ) -> FutureResult; - - fn read_app(&self, token: &str, params: AppIdPB) - -> FutureResult, FlowyError>; - - fn update_app(&self, token: &str, params: UpdateAppParams) -> FutureResult<(), FlowyError>; - - fn delete_app(&self, token: &str, params: AppIdPB) -> FutureResult<(), FlowyError>; - - // Trash - fn create_trash(&self, token: &str, params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError>; - - fn delete_trash(&self, token: &str, params: RepeatedTrashIdPB) -> FutureResult<(), FlowyError>; - - fn read_trash(&self, token: &str) -> FutureResult, FlowyError>; -} diff --git a/frontend/rust-lib/flowy-folder/src/lib.rs b/frontend/rust-lib/flowy-folder/src/lib.rs deleted file mode 100644 index 105c0583ee..0000000000 --- a/frontend/rust-lib/flowy-folder/src/lib.rs +++ /dev/null @@ -1,25 +0,0 @@ -pub mod entities; -pub mod event_map; -pub mod services; - -#[macro_use] -mod macros; - -#[macro_use] -extern crate flowy_sqlite; - -pub mod manager; -mod notification; -pub mod protobuf; -mod util; - -#[cfg(feature = "flowy_unit_test")] -pub mod test_helper; - -pub mod prelude { - pub use crate::{errors::*, event_map::*}; -} - -pub mod errors { - pub use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult}; -} diff --git a/frontend/rust-lib/flowy-folder/src/macros.rs b/frontend/rust-lib/flowy-folder/src/macros.rs deleted file mode 100644 index 217aafe47f..0000000000 --- a/frontend/rust-lib/flowy-folder/src/macros.rs +++ /dev/null @@ -1,49 +0,0 @@ -// #[macro_export] -// macro_rules! impl_save_func { -// ($func_name:ident, $target:ident, $table_name:expr, $conn:ident) => { -// fn $func_name(object: $target) -> Result<(), FlowyError> { -// let _ = diesel::insert_into($table_name) -// .values($target) -// .execute(&*($conn))?; -// } -// }; -// } - -#[macro_export] -macro_rules! impl_def_and_def_mut { - ($target:ident, $item: ident) => { - impl std::ops::Deref for $target { - type Target = Vec<$item>; - - fn deref(&self) -> &Self::Target { - &self.items - } - } - impl std::ops::DerefMut for $target { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.items - } - } - - impl $target { - #[allow(dead_code)] - pub fn into_inner(&mut self) -> Vec<$item> { - ::std::mem::take(&mut self.items) - } - - #[allow(dead_code)] - pub fn push(&mut self, item: $item) { - if self.items.contains(&item) { - log::error!("add duplicate item: {:?}", item); - return; - } - - self.items.push(item); - } - - pub fn first_or_crash(&self) -> &$item { - self.items.first().unwrap() - } - } - }; -} diff --git a/frontend/rust-lib/flowy-folder/src/manager.rs b/frontend/rust-lib/flowy-folder/src/manager.rs deleted file mode 100644 index 3786928c8c..0000000000 --- a/frontend/rust-lib/flowy-folder/src/manager.rs +++ /dev/null @@ -1,319 +0,0 @@ -use crate::entities::view::ViewDataFormatPB; -use crate::entities::{ViewLayoutTypePB, ViewPB, WorkspacePB}; -use crate::services::folder_editor::FolderRevisionMergeable; -use crate::services::persistence::rev_sqlite::{ - SQLiteFolderRevisionPersistence, SQLiteFolderRevisionSnapshotPersistence, -}; -use crate::services::{clear_current_workspace, get_current_workspace}; -use crate::{ - entities::workspace::RepeatedWorkspacePB, - errors::FlowyResult, - event_map::{FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser}, - notification::{send_notification, FolderNotification}, - services::{ - folder_editor::FolderEditor, persistence::FolderPersistence, set_current_workspace, - AppController, TrashController, ViewController, WorkspaceController, - }, -}; -use bytes::Bytes; -use flowy_client_sync::client_folder::FolderPad; -use flowy_document::editor::initial_read_me; -use flowy_error::FlowyError; -use flowy_revision::{ - RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, RevisionWebSocket, -}; -use folder_model::user_default; -use lazy_static::lazy_static; -use lib_infra::future::FutureResult; -use std::convert::TryFrom; -use std::{collections::HashMap, fmt::Formatter, sync::Arc}; -use tokio::sync::RwLock as TokioRwLock; -use ws_model::ws_revision::ServerRevisionWSData; -lazy_static! { - static ref INIT_FOLDER_FLAG: TokioRwLock> = - TokioRwLock::new(HashMap::new()); -} -const FOLDER_ID: &str = "folder"; -const FOLDER_ID_SPLIT: &str = ":"; -#[derive(Clone)] -pub struct FolderId(String); -impl FolderId { - pub fn new(user_id: &str) -> Self { - Self(format!("{}{}{}", user_id, FOLDER_ID_SPLIT, FOLDER_ID)) - } -} - -impl std::fmt::Display for FolderId { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(FOLDER_ID) - } -} - -impl std::fmt::Debug for FolderId { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(FOLDER_ID) - } -} - -impl AsRef for FolderId { - fn as_ref(&self) -> &str { - &self.0 - } -} - -pub struct FolderManager { - pub user: Arc, - pub(crate) persistence: Arc, - pub(crate) workspace_controller: Arc, - pub(crate) app_controller: Arc, - pub(crate) view_controller: Arc, - pub(crate) trash_controller: Arc, - web_socket: Arc, - pub(crate) folder_editor: Arc>>>, -} - -impl FolderManager { - pub async fn new( - user: Arc, - cloud_service: Arc, - database: Arc, - data_processors: ViewDataProcessorMap, - web_socket: Arc, - ) -> Self { - if let Ok(user_id) = user.user_id() { - // Reset the flag if the folder manager gets initialized, otherwise, - // the folder_editor will not be initialized after flutter hot reload. - INIT_FOLDER_FLAG.write().await.insert(user_id, false); - } - - let folder_editor = Arc::new(TokioRwLock::new(None)); - let persistence = Arc::new(FolderPersistence::new( - database.clone(), - folder_editor.clone(), - )); - - let trash_controller = Arc::new(TrashController::new( - persistence.clone(), - cloud_service.clone(), - user.clone(), - )); - - let view_controller = Arc::new(ViewController::new( - user.clone(), - persistence.clone(), - cloud_service.clone(), - trash_controller.clone(), - data_processors, - )); - - let app_controller = Arc::new(AppController::new( - user.clone(), - persistence.clone(), - trash_controller.clone(), - cloud_service.clone(), - )); - - let workspace_controller = Arc::new(WorkspaceController::new( - user.clone(), - persistence.clone(), - trash_controller.clone(), - cloud_service.clone(), - )); - - Self { - user, - persistence, - workspace_controller, - app_controller, - view_controller, - trash_controller, - web_socket, - folder_editor, - } - } - - // pub fn network_state_changed(&self, new_type: NetworkType) { - // match new_type { - // NetworkType::UnknownNetworkType => {}, - // NetworkType::Wifi => {}, - // NetworkType::Cell => {}, - // NetworkType::Ethernet => {}, - // } - // } - - pub async fn did_receive_ws_data(&self, data: Bytes) { - let result = ServerRevisionWSData::try_from(data); - match result { - Ok(data) => match self.folder_editor.read().await.clone() { - None => {}, - Some(editor) => match editor.receive_ws_data(data).await { - Ok(_) => {}, - Err(e) => tracing::error!("Folder receive data error: {:?}", e), - }, - }, - Err(e) => { - tracing::error!("Folder ws data parser failed: {:?}", e); - }, - } - } - - /// Called immediately after the application launched with the user sign in/sign up. - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn initialize(&self, user_id: &str, token: &str) -> FlowyResult<()> { - let mut write_guard = INIT_FOLDER_FLAG.write().await; - if let Some(is_init) = write_guard.get(user_id) { - if *is_init { - return Ok(()); - } - } - tracing::debug!("Initialize folder editor"); - let folder_id = FolderId::new(user_id); - self.persistence.initialize(user_id, &folder_id).await?; - - let pool = self.persistence.db_pool()?; - let object_id = folder_id.as_ref(); - let disk_cache = SQLiteFolderRevisionPersistence::new(pool.clone()); - let configuration = RevisionPersistenceConfiguration::new(200, false); - let rev_persistence = RevisionPersistence::new(object_id, disk_cache, configuration); - let rev_compactor = FolderRevisionMergeable(); - - const FOLDER_SP_PREFIX: &str = "folder"; - let snapshot_object_id = format!("{}:{}", FOLDER_SP_PREFIX, object_id); - let snapshot_persistence = - SQLiteFolderRevisionSnapshotPersistence::new(&snapshot_object_id, pool); - let rev_manager = RevisionManager::new( - folder_id.as_ref(), - rev_persistence, - rev_compactor, - snapshot_persistence, - ); - - let folder_editor = - FolderEditor::new(&folder_id, token, rev_manager, self.web_socket.clone()).await?; - *self.folder_editor.write().await = Some(Arc::new(folder_editor)); - - self.app_controller.initialize()?; - self.view_controller.initialize()?; - write_guard.insert(user_id.to_owned(), true); - Ok(()) - } - - pub async fn get_current_workspace(&self) -> FlowyResult { - let user_id = self.user.user_id()?; - let workspace_id = get_current_workspace(&user_id)?; - let workspace = self - .persistence - .begin_transaction(|transaction| { - self - .workspace_controller - .read_workspace(workspace_id, &user_id, &transaction) - }) - .await?; - Ok(workspace) - } - - pub async fn initialize_with_new_user( - &self, - user_id: &str, - token: &str, - view_data_format: ViewDataFormatPB, - ) -> FlowyResult<()> { - DefaultFolderBuilder::build( - token, - user_id, - self.persistence.clone(), - self.view_controller.clone(), - || (view_data_format.clone(), Bytes::from(initial_read_me())), - ) - .await?; - self.initialize(user_id, token).await - } - - /// Called when the current user logout - /// - pub async fn clear(&self, user_id: &str) { - self.view_controller.clear_latest_view(); - clear_current_workspace(user_id); - *self.folder_editor.write().await = None; - } -} - -struct DefaultFolderBuilder(); -impl DefaultFolderBuilder { - async fn build (ViewDataFormatPB, Bytes)>( - token: &str, - user_id: &str, - persistence: Arc, - view_controller: Arc, - create_view_fn: F, - ) -> FlowyResult<()> { - let workspace_rev = user_default::create_default_workspace(); - tracing::debug!( - "Create user:{} default workspace:{}", - user_id, - workspace_rev.id - ); - set_current_workspace(user_id, &workspace_rev.id); - for app in workspace_rev.apps.iter() { - for (index, view) in app.belongings.iter().enumerate() { - let (view_data_type, view_data) = create_view_fn(); - if index == 0 { - let _ = view_controller.set_latest_view(&view.id); - let layout_type = ViewLayoutTypePB::from(view.layout.clone()); - view_controller - .create_view(&view.id, &view.name, view_data_type, layout_type, view_data) - .await?; - } - } - } - let folder = FolderPad::new(vec![workspace_rev.clone()], vec![])?; - let folder_id = FolderId::new(user_id); - persistence.save_folder(user_id, &folder_id, folder).await?; - let repeated_workspace = RepeatedWorkspacePB { - items: vec![workspace_rev.into()], - }; - send_notification(token, FolderNotification::DidCreateWorkspace) - .payload(repeated_workspace) - .send(); - Ok(()) - } -} - -pub trait ViewDataProcessor { - /// Closes the view and releases the resources that this view has in - /// the backend - fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError>; - - /// Gets the data of the this view. - /// For example, the data can be used to duplicate the view. - fn get_view_data(&self, view: &ViewPB) -> FutureResult; - - /// Create a view with the pre-defined data. - /// For example, the initial data of the grid/calendar/kanban board when - /// you create a new view. - fn create_view_with_built_in_data( - &self, - user_id: &str, - view_id: &str, - name: &str, - layout: ViewLayoutTypePB, - data_format: ViewDataFormatPB, - ext: HashMap, - ) -> FutureResult<(), FlowyError>; - - /// Create a view with custom data - fn create_view_with_custom_data( - &self, - user_id: &str, - view_id: &str, - name: &str, - data: Vec, - layout: ViewLayoutTypePB, - ext: HashMap, - ) -> FutureResult<(), FlowyError>; - - fn data_types(&self) -> Vec; -} - -pub type ViewDataProcessorMap = - Arc>>; diff --git a/frontend/rust-lib/flowy-folder/src/notification.rs b/frontend/rust-lib/flowy-folder/src/notification.rs deleted file mode 100644 index 4c9796354f..0000000000 --- a/frontend/rust-lib/flowy-folder/src/notification.rs +++ /dev/null @@ -1,52 +0,0 @@ -use flowy_derive::ProtoBuf_Enum; -use flowy_notification::NotificationBuilder; -const OBSERVABLE_CATEGORY: &str = "Workspace"; - -#[derive(ProtoBuf_Enum, Debug)] -pub(crate) enum FolderNotification { - Unknown = 0, - /// Trigger after creating a workspace - DidCreateWorkspace = 1, - /// Trigger after deleting a workspace - DidDeleteWorkspace = 2, - /// Trigger after updating a workspace - DidUpdateWorkspace = 3, - /// Trigger when the number of apps of the workspace is changed - DidUpdateWorkspaceApps = 4, - /// Trigger when the settings of the workspace are changed. The changes including the latest visiting view, etc - DidUpdateWorkspaceSetting = 5, - /// Trigger when the properties including rename,update description of the app are changed - DidUpdateApp = 20, - /// Trigger when the properties including rename,update description of the view are changed - DidUpdateView = 30, - /// Trigger after deleting the view - DidDeleteView = 31, - /// Trigger when restore the view from trash - DidRestoreView = 32, - /// Trigger after moving the view to trash - DidMoveViewToTrash = 33, - /// Trigger when the number of trash is changed - DidUpdateTrash = 34, -} - -impl std::default::Default for FolderNotification { - fn default() -> Self { - FolderNotification::Unknown - } -} - -impl std::convert::From for i32 { - fn from(notification: FolderNotification) -> Self { - notification as i32 - } -} - -#[tracing::instrument(level = "trace")] -pub(crate) fn send_notification(id: &str, ty: FolderNotification) -> NotificationBuilder { - NotificationBuilder::new(id, ty, OBSERVABLE_CATEGORY) -} - -#[tracing::instrument(level = "trace")] -pub(crate) fn send_anonymous_notification(ty: FolderNotification) -> NotificationBuilder { - NotificationBuilder::new("", ty, OBSERVABLE_CATEGORY) -} diff --git a/frontend/rust-lib/flowy-folder/src/services/app/controller.rs b/frontend/rust-lib/flowy-folder/src/services/app/controller.rs deleted file mode 100644 index af2f0d442b..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/app/controller.rs +++ /dev/null @@ -1,255 +0,0 @@ -use crate::{ - entities::{ - app::{AppPB, CreateAppParams, *}, - trash::TrashType, - }, - errors::*, - event_map::{FolderCouldServiceV1, WorkspaceUser}, - notification::*, - services::{ - persistence::{AppChangeset, FolderPersistence, FolderPersistenceTransaction}, - TrashController, TrashEvent, - }, -}; - -use folder_model::AppRevision; -use futures::{FutureExt, StreamExt}; -use std::{collections::HashSet, sync::Arc}; - -pub(crate) struct AppController { - user: Arc, - persistence: Arc, - trash_controller: Arc, - cloud_service: Arc, -} - -impl AppController { - pub(crate) fn new( - user: Arc, - persistence: Arc, - trash_can: Arc, - cloud_service: Arc, - ) -> Self { - Self { - user, - persistence, - trash_controller: trash_can, - cloud_service, - } - } - - pub fn initialize(&self) -> Result<(), FlowyError> { - self.listen_trash_controller_event(); - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self, params), fields(name = %params.name) err)] - pub(crate) async fn create_app_from_params( - &self, - params: CreateAppParams, - ) -> Result { - let app = self.create_app_on_server(params).await?; - self.create_app_on_local(app).await - } - - pub(crate) async fn create_app_on_local(&self, app: AppRevision) -> Result { - self - .persistence - .begin_transaction(|transaction| { - transaction.create_app(app.clone())?; - notify_apps_changed( - &app.workspace_id, - self.trash_controller.clone(), - &transaction, - )?; - Ok(()) - }) - .await?; - Ok(app.into()) - } - - pub(crate) async fn read_app(&self, params: AppIdPB) -> Result, FlowyError> { - let app = self - .persistence - .begin_transaction(|transaction| { - let app = transaction.read_app(¶ms.value)?; - let trash_ids = self.trash_controller.read_trash_ids(&transaction)?; - if trash_ids.contains(&app.id) { - return Ok(None); - } - Ok(Some(app)) - }) - .await?; - Ok(app) - } - - pub(crate) async fn update_app(&self, params: UpdateAppParams) -> Result<(), FlowyError> { - let changeset = AppChangeset::new(params.clone()); - let app_id = changeset.id.clone(); - - let app: AppPB = self - .persistence - .begin_transaction(|transaction| { - transaction.update_app(changeset)?; - let app = transaction.read_app(&app_id)?; - Ok(app) - }) - .await? - .into(); - send_notification(&app_id, FolderNotification::DidUpdateApp) - .payload(app) - .send(); - self.update_app_on_server(params)?; - Ok(()) - } - - pub(crate) async fn move_app(&self, app_id: &str, from: usize, to: usize) -> FlowyResult<()> { - self - .persistence - .begin_transaction(|transaction| { - transaction.move_app(app_id, from, to)?; - let app = transaction.read_app(app_id)?; - notify_apps_changed( - &app.workspace_id, - self.trash_controller.clone(), - &transaction, - )?; - Ok(()) - }) - .await?; - Ok(()) - } - - pub(crate) async fn read_local_apps( - &self, - ids: Vec, - ) -> Result, FlowyError> { - let app_revs = self - .persistence - .begin_transaction(|transaction| { - let mut apps = vec![]; - for id in ids { - apps.push(transaction.read_app(&id)?); - } - Ok(apps) - }) - .await?; - Ok(app_revs) - } -} - -impl AppController { - #[tracing::instrument(level = "trace", skip(self), err)] - async fn create_app_on_server(&self, params: CreateAppParams) -> Result { - let token = self.user.token()?; - let app = self.cloud_service.create_app(&token, params).await?; - Ok(app) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - fn update_app_on_server(&self, params: UpdateAppParams) -> Result<(), FlowyError> { - let token = self.user.token()?; - let server = self.cloud_service.clone(); - tokio::spawn(async move { - match server.update_app(&token, params).await { - Ok(_) => {}, - Err(e) => { - // TODO: retry? - log::error!("Update app failed: {:?}", e); - }, - } - }); - Ok(()) - } - - fn listen_trash_controller_event(&self) { - let mut rx = self.trash_controller.subscribe(); - let persistence = self.persistence.clone(); - let trash_controller = self.trash_controller.clone(); - let _ = tokio::spawn(async move { - loop { - let mut stream = Box::pin(rx.recv().into_stream().filter_map(|result| async move { - match result { - Ok(event) => event.select(TrashType::TrashApp), - Err(_e) => None, - } - })); - if let Some(event) = stream.next().await { - handle_trash_event(persistence.clone(), trash_controller.clone(), event).await - } - } - }); - } -} - -#[tracing::instrument(level = "trace", skip(persistence, trash_controller))] -async fn handle_trash_event( - persistence: Arc, - trash_controller: Arc, - event: TrashEvent, -) { - match event { - TrashEvent::NewTrash(identifiers, ret) | TrashEvent::Putback(identifiers, ret) => { - let result = persistence - .begin_transaction(|transaction| { - for identifier in identifiers.items { - let app = transaction.read_app(&identifier.id)?; - notify_apps_changed(&app.workspace_id, trash_controller.clone(), &transaction)?; - } - Ok(()) - }) - .await; - let _ = ret.send(result).await; - }, - TrashEvent::Delete(identifiers, ret) => { - let result = persistence - .begin_transaction(|transaction| { - let mut notify_ids = HashSet::new(); - for identifier in identifiers.items { - let app = transaction.read_app(&identifier.id)?; - let _ = transaction.delete_app(&identifier.id)?; - notify_ids.insert(app.workspace_id); - } - - for notify_id in notify_ids { - notify_apps_changed(¬ify_id, trash_controller.clone(), &transaction)?; - } - Ok(()) - }) - .await; - let _ = ret.send(result).await; - }, - } -} - -#[tracing::instrument( - level = "debug", - skip(workspace_id, trash_controller, transaction), - err -)] -fn notify_apps_changed<'a>( - workspace_id: &str, - trash_controller: Arc, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), -) -> FlowyResult<()> { - let items = read_workspace_apps(workspace_id, trash_controller, transaction)? - .into_iter() - .map(|app_rev| app_rev.into()) - .collect(); - let repeated_app = RepeatedAppPB { items }; - send_notification(workspace_id, FolderNotification::DidUpdateWorkspaceApps) - .payload(repeated_app) - .send(); - Ok(()) -} - -pub fn read_workspace_apps<'a>( - workspace_id: &str, - trash_controller: Arc, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), -) -> Result, FlowyError> { - let mut app_revs = transaction.read_workspace_apps(workspace_id)?; - let trash_ids = trash_controller.read_trash_ids(transaction)?; - app_revs.retain(|app| !trash_ids.contains(&app.id)); - Ok(app_revs) -} diff --git a/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs deleted file mode 100644 index 02bb796998..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::{ - entities::app::{ - AppIdPB, AppPB, CreateAppParams, CreateAppPayloadPB, UpdateAppParams, UpdateAppPayloadPB, - }, - errors::FlowyError, - services::{AppController, TrashController, ViewController}, -}; -use folder_model::TrashRevision; -use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult}; -use std::{convert::TryInto, sync::Arc}; - -pub(crate) async fn create_app_handler( - data: AFPluginData, - controller: AFPluginState>, -) -> DataResult { - let params: CreateAppParams = data.into_inner().try_into()?; - let detail = controller.create_app_from_params(params).await?; - - data_result_ok(detail) -} - -pub(crate) async fn delete_app_handler( - data: AFPluginData, - app_controller: AFPluginState>, - trash_controller: AFPluginState>, -) -> Result<(), FlowyError> { - let params: AppIdPB = data.into_inner(); - let trash = app_controller - .read_local_apps(vec![params.value]) - .await? - .into_iter() - .map(|app_rev| app_rev.into()) - .collect::>(); - - trash_controller.add(trash).await?; - Ok(()) -} - -#[tracing::instrument(level = "trace", skip(data, controller))] -pub(crate) async fn update_app_handler( - data: AFPluginData, - controller: AFPluginState>, -) -> Result<(), FlowyError> { - let params: UpdateAppParams = data.into_inner().try_into()?; - controller.update_app(params).await?; - Ok(()) -} - -#[tracing::instrument(level = "trace", skip(data, app_controller, view_controller), err)] -pub(crate) async fn read_app_handler( - data: AFPluginData, - app_controller: AFPluginState>, - view_controller: AFPluginState>, -) -> DataResult { - let params: AppIdPB = data.into_inner(); - if let Some(mut app_rev) = app_controller.read_app(params.clone()).await? { - app_rev.belongings = view_controller.read_views_belong_to(¶ms.value).await?; - data_result_ok(app_rev.into()) - } else { - Err(FlowyError::record_not_found()) - } -} diff --git a/frontend/rust-lib/flowy-folder/src/services/app/mod.rs b/frontend/rust-lib/flowy-folder/src/services/app/mod.rs deleted file mode 100644 index e854517f17..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/app/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod controller; -pub mod event_handler; diff --git a/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs b/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs deleted file mode 100644 index e52742c665..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs +++ /dev/null @@ -1,164 +0,0 @@ -use crate::manager::FolderId; -use bytes::Bytes; -use flowy_client_sync::client_folder::{FolderChangeset, FolderOperations, FolderPad}; -use flowy_client_sync::make_operations_from_revisions; -use flowy_client_sync::util::recover_operation_from_revisions; -use flowy_error::{FlowyError, FlowyResult}; -use flowy_revision::{ - RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, - RevisionObjectSerializer, RevisionWebSocket, -}; -use flowy_sqlite::ConnectionPool; -use lib_infra::future::FutureResult; -use lib_ot::core::EmptyAttributes; -use parking_lot::RwLock; -use revision_model::Revision; -use std::sync::Arc; -use ws_model::ws_revision::ServerRevisionWSData; - -pub struct FolderEditor { - #[allow(dead_code)] - folder_id: FolderId, - pub(crate) folder: Arc>, - rev_manager: Arc>>, - #[cfg(feature = "sync")] - ws_manager: Arc, -} - -impl FolderEditor { - #[allow(unused_variables)] - pub async fn new( - folder_id: &FolderId, - token: &str, - mut rev_manager: RevisionManager>, - web_socket: Arc, - ) -> FlowyResult { - let cloud = Arc::new(FolderRevisionCloudService { - token: token.to_string(), - }); - let folder = Arc::new(RwLock::new( - rev_manager - .initialize::(Some(cloud)) - .await?, - )); - let rev_manager = Arc::new(rev_manager); - - #[cfg(feature = "sync")] - let ws_manager = crate::services::web_socket::make_folder_ws_manager( - folder_id.as_ref(), - rev_manager.clone(), - web_socket, - folder.clone(), - ) - .await; - - let folder_id = folder_id.to_owned(); - Ok(Self { - folder_id, - folder, - rev_manager, - #[cfg(feature = "sync")] - ws_manager, - }) - } - - #[cfg(feature = "sync")] - pub async fn receive_ws_data(&self, data: ServerRevisionWSData) -> FlowyResult<()> { - let _ = self - .ws_manager - .ws_passthrough_tx - .send(data) - .await - .map_err(|e| { - let err_msg = format!("{} passthrough error: {}", self.folder_id, e); - FlowyError::internal().context(err_msg) - })?; - - Ok(()) - } - - #[cfg(not(feature = "sync"))] - pub async fn receive_ws_data(&self, _data: ServerRevisionWSData) -> FlowyResult<()> { - Ok(()) - } - - pub(crate) fn apply_change(&self, change: FolderChangeset) -> FlowyResult<()> { - let FolderChangeset { - operations: delta, - md5, - } = change; - let delta_data = delta.json_bytes(); - let rev_manager = self.rev_manager.clone(); - tokio::spawn(async move { - let _ = rev_manager.add_local_revision(delta_data, md5).await; - }); - Ok(()) - } - - #[allow(dead_code)] - pub fn folder_json(&self) -> FlowyResult { - let json = self.folder.read().to_json()?; - Ok(json) - } -} - -pub struct FolderRevisionSerde(); -impl RevisionObjectDeserializer for FolderRevisionSerde { - type Output = FolderPad; - - fn deserialize_revisions( - _object_id: &str, - revisions: Vec, - ) -> FlowyResult { - let operations: FolderOperations = make_operations_from_revisions(revisions)?; - Ok(FolderPad::from_operations(operations)?) - } - - fn recover_from_revisions(revisions: Vec) -> Option<(Self::Output, i64)> { - if let Some((operations, rev_id)) = recover_operation_from_revisions(revisions, |operations| { - FolderPad::from_operations(operations.clone()).is_ok() - }) { - if let Ok(pad) = FolderPad::from_operations(operations) { - return Some((pad, rev_id)); - } - } - None - } -} - -impl RevisionObjectSerializer for FolderRevisionSerde { - fn combine_revisions(revisions: Vec) -> FlowyResult { - let operations = make_operations_from_revisions::(revisions)?; - Ok(operations.json_bytes()) - } -} - -pub struct FolderRevisionMergeable(); -impl RevisionMergeable for FolderRevisionMergeable { - fn combine_revisions(&self, revisions: Vec) -> FlowyResult { - FolderRevisionSerde::combine_revisions(revisions) - } -} - -struct FolderRevisionCloudService { - #[allow(dead_code)] - token: String, -} - -impl RevisionCloudService for FolderRevisionCloudService { - #[tracing::instrument(level = "trace", skip(self))] - fn fetch_object( - &self, - _user_id: &str, - _object_id: &str, - ) -> FutureResult, FlowyError> { - FutureResult::new(async move { Ok(vec![]) }) - } -} - -#[cfg(feature = "flowy_unit_test")] -impl FolderEditor { - pub fn rev_manager(&self) -> Arc>> { - self.rev_manager.clone() - } -} diff --git a/frontend/rust-lib/flowy-folder/src/services/mod.rs b/frontend/rust-lib/flowy-folder/src/services/mod.rs deleted file mode 100644 index 8594ee510a..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub(crate) use app::controller::*; -pub(crate) use trash::controller::*; -pub(crate) use view::controller::*; -pub(crate) use workspace::controller::*; - -pub(crate) mod app; -pub mod folder_editor; -pub(crate) mod persistence; -pub(crate) mod trash; -pub(crate) mod view; -mod web_socket; -pub(crate) mod workspace; - -pub const FOLDER_SYNC_INTERVAL_IN_MILLIS: u64 = 5000; diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs deleted file mode 100644 index a56495532b..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs +++ /dev/null @@ -1,157 +0,0 @@ -use crate::manager::FolderId; -use crate::{ - event_map::WorkspaceDatabase, - services::persistence::{AppTableSql, TrashTableSql, ViewTableSql, WorkspaceTableSql}, -}; -use bytes::Bytes; -use flowy_client_sync::client_folder::FolderPad; -use flowy_client_sync::client_folder::{make_folder_rev_json_str, FolderOperationsBuilder}; -use flowy_error::{FlowyError, FlowyResult}; -use flowy_revision::reset::{RevisionResettable, RevisionStructReset}; -use flowy_sqlite::kv::KV; -use folder_model::{AppRevision, FolderRevision, ViewRevision, WorkspaceRevision}; -use revision_model::Revision; - -use crate::services::persistence::rev_sqlite::SQLiteFolderRevisionPersistence; -use lib_infra::util::md5; -use std::sync::Arc; - -const V1_MIGRATION: &str = "FOLDER_V1_MIGRATION"; -const V2_MIGRATION: &str = "FOLDER_V2_MIGRATION"; -const V3_MIGRATION: &str = "FOLDER_V3_MIGRATION"; - -pub(crate) struct FolderMigration { - user_id: String, - database: Arc, -} - -impl FolderMigration { - pub fn new(user_id: &str, database: Arc) -> Self { - Self { - user_id: user_id.to_owned(), - database, - } - } - - pub fn run_v1_migration(&self) -> FlowyResult> { - let key = migration_flag_key(&self.user_id, V1_MIGRATION); - if KV::get_bool(&key) { - return Ok(None); - } - - let pool = self.database.db_pool()?; - let conn = &*pool.get()?; - let workspaces = conn.immediate_transaction::<_, FlowyError, _>(|| { - let mut workspaces = WorkspaceTableSql::read_workspaces(&self.user_id, None, conn)? - .into_iter() - .map(WorkspaceRevision::from) - .collect::>(); - - for workspace in workspaces.iter_mut() { - let mut apps = AppTableSql::read_workspace_apps(&workspace.id, conn)? - .into_iter() - .map(AppRevision::from) - .collect::>(); - - for app in apps.iter_mut() { - let views = ViewTableSql::read_views(&app.id, conn)? - .into_iter() - .map(ViewRevision::from) - .collect::>(); - - app.belongings = views; - } - - workspace.apps = apps; - } - Ok(workspaces) - })?; - - if workspaces.is_empty() { - tracing::trace!("Run folder v1 migration, but workspace is empty"); - KV::set_bool(&key, true); - return Ok(None); - } - - let trash = conn.immediate_transaction::<_, FlowyError, _>(|| { - let trash = TrashTableSql::read_all(conn)?; - Ok(trash) - })?; - - let folder = FolderPad::new(workspaces, trash)?; - KV::set_bool(&key, true); - tracing::info!("Run folder v1 migration"); - Ok(Some(folder)) - } - - pub async fn run_v2_migration(&self, folder_id: &FolderId) -> FlowyResult<()> { - let key = migration_flag_key(&self.user_id, V2_MIGRATION); - if KV::get_bool(&key) { - return Ok(()); - } - self.migration_folder_rev_struct(folder_id).await?; - KV::set_bool(&key, true); - // tracing::info!("Run folder v2 migration"); - Ok(()) - } - - pub async fn run_v3_migration(&self, folder_id: &FolderId) -> FlowyResult<()> { - let key = migration_flag_key(&self.user_id, V3_MIGRATION); - if KV::get_bool(&key) { - return Ok(()); - } - self.migration_folder_rev_struct(folder_id).await?; - KV::set_bool(&key, true); - tracing::trace!("Run folder v3 migration"); - Ok(()) - } - - pub async fn migration_folder_rev_struct(&self, folder_id: &FolderId) -> FlowyResult<()> { - let object = FolderRevisionResettable { - folder_id: folder_id.as_ref().to_owned(), - }; - - let pool = self.database.db_pool()?; - let disk_cache = SQLiteFolderRevisionPersistence::new(pool); - let reset = RevisionStructReset::new(object, Arc::new(disk_cache)); - reset.run().await - } -} - -fn migration_flag_key(user_id: &str, version: &str) -> String { - md5(format!("{}{}", user_id, version,)) -} - -struct FolderRevisionResettable { - folder_id: String, -} - -impl RevisionResettable for FolderRevisionResettable { - fn target_id(&self) -> &str { - &self.folder_id - } - - fn reset_data(&self, revisions: Vec) -> FlowyResult { - let pad = FolderPad::from_revisions(revisions)?; - let json = pad.to_json()?; - let bytes = FolderOperationsBuilder::new() - .insert(&json) - .build() - .json_bytes(); - Ok(bytes) - } - - fn default_target_rev_str(&self) -> FlowyResult { - let folder = FolderRevision::default(); - let json = make_folder_rev_json_str(&folder)?; - Ok(json) - } - - fn read_record(&self) -> Option { - KV::get_str(self.target_id()) - } - - fn set_record(&self, record: String) { - KV::set_str(self.target_id(), record); - } -} diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs deleted file mode 100644 index 9b03d00e01..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs +++ /dev/null @@ -1,151 +0,0 @@ -mod migration; -pub mod rev_sqlite; -pub mod version_1; -mod version_2; - -use crate::services::persistence::rev_sqlite::SQLiteFolderRevisionPersistence; -use crate::{ - event_map::WorkspaceDatabase, - manager::FolderId, - services::{folder_editor::FolderEditor, persistence::migration::FolderMigration}, -}; -use flowy_client_sync::client_folder::{FolderOperationsBuilder, FolderPad}; -use flowy_error::{FlowyError, FlowyResult}; -use flowy_revision_persistence::{RevisionDiskCache, RevisionState, SyncRecord}; -use flowy_sqlite::ConnectionPool; -use folder_model::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; -use revision_model::Revision; -use std::sync::Arc; -use tokio::sync::RwLock; -pub use version_1::{ - app_sql::*, trash_sql::*, v1_impl::V1Transaction, view_sql::*, workspace_sql::*, -}; - -pub trait FolderPersistenceTransaction { - fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()>; - fn read_workspaces( - &self, - user_id: &str, - workspace_id: Option, - ) -> FlowyResult>; - fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()>; - fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()>; - - fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()>; - fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()>; - fn read_app(&self, app_id: &str) -> FlowyResult; - fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult>; - fn delete_app(&self, app_id: &str) -> FlowyResult; - fn move_app(&self, app_id: &str, from: usize, to: usize) -> FlowyResult<()>; - - fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()>; - fn read_view(&self, view_id: &str) -> FlowyResult; - fn read_views(&self, belong_to_id: &str) -> FlowyResult>; - fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()>; - fn delete_view(&self, view_id: &str) -> FlowyResult; - fn move_view(&self, view_id: &str, from: usize, to: usize) -> FlowyResult<()>; - - fn create_trash(&self, trashes: Vec) -> FlowyResult<()>; - fn read_trash(&self, trash_id: Option) -> FlowyResult>; - fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()>; -} - -pub struct FolderPersistence { - database: Arc, - folder_editor: Arc>>>, -} - -impl FolderPersistence { - pub fn new( - database: Arc, - folder_editor: Arc>>>, - ) -> Self { - Self { - database, - folder_editor, - } - } - - #[deprecated( - since = "0.0.3", - note = "please use `begin_transaction` instead, this interface will be removed in the future" - )] - #[allow(dead_code)] - pub fn begin_transaction_v_1(&self, f: F) -> FlowyResult - where - F: for<'a> FnOnce(Box) -> FlowyResult, - { - //[[immediate_transaction]] - // https://sqlite.org/lang_transaction.html - // IMMEDIATE cause the database connection to start a new write immediately, - // without waiting for a write statement. The BEGIN IMMEDIATE might fail - // with SQLITE_BUSY if another write transaction is already active on another - // database connection. - // - // EXCLUSIVE is similar to IMMEDIATE in that a write transaction is started - // immediately. EXCLUSIVE and IMMEDIATE are the same in WAL mode, but in - // other journaling modes, EXCLUSIVE prevents other database connections from - // reading the database while the transaction is underway. - let conn = self.database.db_connection()?; - conn.immediate_transaction::<_, FlowyError, _>(|| f(Box::new(V1Transaction(&conn)))) - } - - pub async fn begin_transaction(&self, f: F) -> FlowyResult - where - F: FnOnce(Arc) -> FlowyResult, - { - match self.folder_editor.read().await.clone() { - None => Err( - FlowyError::internal().context("FolderEditor should be initialized after user login in."), - ), - Some(editor) => f(editor), - } - } - - pub fn db_pool(&self) -> FlowyResult> { - self.database.db_pool() - } - - pub async fn initialize(&self, user_id: &str, folder_id: &FolderId) -> FlowyResult<()> { - let migrations = FolderMigration::new(user_id, self.database.clone()); - if let Some(migrated_folder) = migrations.run_v1_migration()? { - self - .save_folder(user_id, folder_id, migrated_folder) - .await?; - } - - migrations.run_v2_migration(folder_id).await?; - migrations.run_v3_migration(folder_id).await?; - Ok(()) - } - - pub async fn save_folder( - &self, - user_id: &str, - folder_id: &FolderId, - folder: FolderPad, - ) -> FlowyResult<()> { - let pool = self.database.db_pool()?; - let json = folder.to_json()?; - let delta_data = FolderOperationsBuilder::new() - .insert(&json) - .build() - .json_bytes(); - let revision = Revision::initial_revision(folder_id.as_ref(), delta_data); - let record = SyncRecord { - revision, - state: RevisionState::Sync, - write_to_disk: true, - }; - - let disk_cache = make_folder_revision_disk_cache(user_id, pool); - disk_cache.delete_and_insert_records(folder_id.as_ref(), None, vec![record]) - } -} - -pub fn make_folder_revision_disk_cache( - _user_id: &str, - pool: Arc, -) -> Arc, Error = FlowyError>> { - Arc::new(SQLiteFolderRevisionPersistence::new(pool)) -} diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_rev_sqlite.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_rev_sqlite.rs deleted file mode 100644 index 79a693eb1d..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_rev_sqlite.rs +++ /dev/null @@ -1,282 +0,0 @@ -use bytes::Bytes; -use diesel::{sql_types::Integer, update, SqliteConnection}; -use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_revision_persistence::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; -use flowy_sqlite::{ - impl_sql_integer_expression, insert_or_ignore_into, - prelude::*, - schema::{rev_table, rev_table::dsl}, - ConnectionPool, -}; -use lib_infra::util::md5; -use revision_model::{Revision, RevisionRange}; -use std::sync::Arc; - -pub struct SQLiteFolderRevisionPersistence { - pub(crate) pool: Arc, -} - -impl RevisionDiskCache> for SQLiteFolderRevisionPersistence { - type Error = FlowyError; - - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - FolderRevisionSql::create(revision_records, &conn)?; - Ok(()) - } - - fn get_connection(&self) -> Result, Self::Error> { - Ok(self.pool.clone()) - } - - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - let records = FolderRevisionSql::read(object_id, rev_ids, &conn)?; - Ok(records) - } - - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - let revisions = FolderRevisionSql::read_with_range(object_id, range.clone(), conn)?; - Ok(revisions) - } - - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - let conn = &*self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - for changeset in changesets { - FolderRevisionSql::update(changeset, conn)?; - } - Ok(()) - })?; - Ok(()) - } - - fn delete_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result<(), Self::Error> { - let conn = &*self.pool.get().map_err(internal_error)?; - FolderRevisionSql::delete(object_id, rev_ids, conn)?; - Ok(()) - } - - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error> { - let conn = self.pool.get().map_err(internal_error)?; - conn.immediate_transaction::<_, FlowyError, _>(|| { - FolderRevisionSql::delete(object_id, deleted_rev_ids, &conn)?; - FolderRevisionSql::create(inserted_records, &conn)?; - Ok(()) - }) - } -} - -impl SQLiteFolderRevisionPersistence { - pub fn new(pool: Arc) -> Self { - Self { pool } - } -} - -struct FolderRevisionSql {} - -impl FolderRevisionSql { - fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { - // Batch insert: https://diesel.rs/guides/all-about-inserts.html - - let records = revision_records - .into_iter() - .map(|record| { - tracing::trace!( - "[{}] create revision: {}:{:?}", - std::any::type_name::(), - record.revision.object_id, - record.revision.rev_id - ); - let rev_state: TextRevisionState = record.state.into(); - ( - dsl::doc_id.eq(record.revision.object_id), - dsl::base_rev_id.eq(record.revision.base_rev_id), - dsl::rev_id.eq(record.revision.rev_id), - dsl::data.eq(record.revision.bytes), - dsl::state.eq(rev_state), - dsl::ty.eq(RevTableType::Local), - ) - }) - .collect::>(); - - let _ = insert_or_ignore_into(dsl::rev_table) - .values(&records) - .execute(conn)?; - Ok(()) - } - - fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { - let state: TextRevisionState = changeset.state.clone().into(); - let filter = dsl::rev_table - .filter(dsl::rev_id.eq(changeset.rev_id)) - .filter(dsl::doc_id.eq(changeset.object_id)); - let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?; - tracing::debug!( - "[{}] update revision:{} state:to {:?}", - std::any::type_name::(), - changeset.rev_id, - changeset.state - ); - Ok(()) - } - - fn read( - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let mut sql = dsl::rev_table - .filter(dsl::doc_id.eq(object_id)) - .into_boxed(); - if let Some(rev_ids) = rev_ids { - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - let rows = sql.order(dsl::rev_id.asc()).load::(conn)?; - let records = rows - .into_iter() - .map(mk_revision_record_from_table) - .collect::>(); - - Ok(records) - } - - fn read_with_range( - object_id: &str, - range: RevisionRange, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let rev_tables = dsl::rev_table - .filter(dsl::rev_id.ge(range.start)) - .filter(dsl::rev_id.le(range.end)) - .filter(dsl::doc_id.eq(object_id)) - .order(dsl::rev_id.asc()) - .load::(conn)?; - - let revisions = rev_tables - .into_iter() - .map(mk_revision_record_from_table) - .collect::>(); - Ok(revisions) - } - - fn delete( - object_id: &str, - rev_ids: Option>, - conn: &SqliteConnection, - ) -> Result<(), FlowyError> { - let mut sql = diesel::delete(dsl::rev_table).into_boxed(); - sql = sql.filter(dsl::doc_id.eq(object_id)); - - if let Some(rev_ids) = rev_ids { - tracing::trace!( - "[{}] Delete revision: {}:{:?}", - std::any::type_name::(), - object_id, - rev_ids - ); - sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); - } - - let affected_row = sql.execute(conn)?; - tracing::trace!( - "[{}] Delete {} rows", - std::any::type_name::(), - affected_row - ); - Ok(()) - } -} - -#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[table_name = "rev_table"] -struct RevisionTable { - id: i32, - doc_id: String, - base_rev_id: i64, - rev_id: i64, - data: Vec, - state: TextRevisionState, - ty: RevTableType, // Deprecated -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] -#[repr(i32)] -#[sql_type = "Integer"] -enum TextRevisionState { - Sync = 0, - Ack = 1, -} -impl_sql_integer_expression!(TextRevisionState); -impl_rev_state_map!(TextRevisionState); - -impl std::default::Default for TextRevisionState { - fn default() -> Self { - TextRevisionState::Sync - } -} - -fn mk_revision_record_from_table(table: RevisionTable) -> SyncRecord { - let md5 = md5(&table.data); - let revision = Revision::new( - &table.doc_id, - table.base_rev_id, - table.rev_id, - Bytes::from(table.data), - md5, - ); - SyncRecord { - revision, - state: table.state.into(), - write_to_disk: false, - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] -#[repr(i32)] -#[sql_type = "Integer"] -pub enum RevTableType { - Local = 0, - Remote = 1, -} -impl_sql_integer_expression!(RevTableType); - -impl std::default::Default for RevTableType { - fn default() -> Self { - RevTableType::Local - } -} - -impl std::convert::From for RevTableType { - fn from(value: i32) -> Self { - match value { - 0 => RevTableType::Local, - 1 => RevTableType::Remote, - o => { - tracing::error!( - "Unsupported rev type {}, fallback to RevTableType::Local", - o - ); - RevTableType::Local - }, - } - } -} diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_snapshot.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_snapshot.rs deleted file mode 100644 index 664818167d..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_snapshot.rs +++ /dev/null @@ -1,97 +0,0 @@ -#![allow(clippy::unused_unit)] -use bytes::Bytes; -use flowy_error::{internal_error, FlowyResult}; -use flowy_revision::{RevisionSnapshotData, RevisionSnapshotPersistence}; -use flowy_sqlite::{ - prelude::*, - schema::{folder_rev_snapshot, folder_rev_snapshot::dsl}, - ConnectionPool, -}; -use lib_infra::util::timestamp; -use std::sync::Arc; - -pub struct SQLiteFolderRevisionSnapshotPersistence { - object_id: String, - pool: Arc, -} - -impl SQLiteFolderRevisionSnapshotPersistence { - pub fn new(object_id: &str, pool: Arc) -> Self { - Self { - object_id: object_id.to_string(), - pool, - } - } - - fn gen_snapshot_id(&self, rev_id: i64) -> String { - format!("{}:{}", self.object_id, rev_id) - } -} - -impl RevisionSnapshotPersistence for SQLiteFolderRevisionSnapshotPersistence { - fn should_generate_snapshot_from_range(&self, start_rev_id: i64, current_rev_id: i64) -> bool { - (current_rev_id - start_rev_id) >= 2 - } - - fn write_snapshot(&self, rev_id: i64, data: Vec) -> FlowyResult<()> { - let conn = self.pool.get().map_err(internal_error)?; - let snapshot_id = self.gen_snapshot_id(rev_id); - let timestamp = timestamp(); - let record = ( - dsl::snapshot_id.eq(&snapshot_id), - dsl::object_id.eq(&self.object_id), - dsl::rev_id.eq(rev_id), - dsl::base_rev_id.eq(rev_id), - dsl::timestamp.eq(timestamp), - dsl::data.eq(data), - ); - let _ = insert_or_ignore_into(dsl::folder_rev_snapshot) - .values(record) - .execute(&*conn)?; - Ok(()) - } - - fn read_snapshot(&self, rev_id: i64) -> FlowyResult> { - let conn = self.pool.get().map_err(internal_error)?; - let snapshot_id = self.gen_snapshot_id(rev_id); - let record = dsl::folder_rev_snapshot - .filter(dsl::snapshot_id.eq(&snapshot_id)) - .first::(&*conn)?; - - Ok(Some(record.into())) - } - - fn read_last_snapshot(&self) -> FlowyResult> { - let conn = self.pool.get().map_err(internal_error)?; - let latest_record = dsl::folder_rev_snapshot - .filter(dsl::object_id.eq(&self.object_id)) - .order(dsl::timestamp.desc()) - // .select(max(dsl::rev_id)) - // .select((dsl::id, dsl::object_id, dsl::rev_id, dsl::data)) - .first::(&*conn)?; - Ok(Some(latest_record.into())) - } -} - -#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[table_name = "folder_rev_snapshot"] -#[primary_key("snapshot_id")] -struct FolderSnapshotRecord { - snapshot_id: String, - object_id: String, - rev_id: i64, - base_rev_id: i64, - timestamp: i64, - data: Vec, -} - -impl std::convert::From for RevisionSnapshotData { - fn from(record: FolderSnapshotRecord) -> Self { - RevisionSnapshotData { - rev_id: record.rev_id, - base_rev_id: record.base_rev_id, - timestamp: record.timestamp, - data: Bytes::from(record.data), - } - } -} diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/mod.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/mod.rs deleted file mode 100644 index 6b185637ba..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod folder_rev_sqlite; -mod folder_snapshot; - -pub use folder_rev_sqlite::*; -pub use folder_snapshot::*; diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs deleted file mode 100644 index 43096ec1b9..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs +++ /dev/null @@ -1,169 +0,0 @@ -use crate::entities::{ - app::UpdateAppParams, - trash::{TrashPB, TrashType}, -}; -use crate::{errors::FlowyError, services::persistence::version_1::workspace_sql::WorkspaceTable}; -use flowy_sqlite::{ - prelude::*, - schema::{app_table, app_table::dsl}, - SqliteConnection, -}; -use folder_model::AppRevision; - -pub struct AppTableSql(); -impl AppTableSql { - pub(crate) fn create_app( - app_rev: AppRevision, - conn: &SqliteConnection, - ) -> Result<(), FlowyError> { - let app_table = AppTable::new(app_rev); - match diesel_record_count!(app_table, &app_table.id, conn) { - 0 => diesel_insert_table!(app_table, app_table.clone(), conn), - _ => { - let changeset = AppChangeset::from_table(app_table); - diesel_update_table!(app_table, changeset, conn) - }, - } - Ok(()) - } - - pub(crate) fn update_app( - changeset: AppChangeset, - conn: &SqliteConnection, - ) -> Result<(), FlowyError> { - diesel_update_table!(app_table, changeset, conn); - Ok(()) - } - - pub(crate) fn read_app(app_id: &str, conn: &SqliteConnection) -> Result { - let filter = dsl::app_table.filter(app_table::id.eq(app_id)).into_boxed(); - let app_table = filter.first::(conn)?; - Ok(app_table) - } - - pub(crate) fn read_workspace_apps( - workspace_id: &str, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let app_table = dsl::app_table - .filter(app_table::workspace_id.eq(workspace_id)) - .order(app_table::create_time.asc()) - .load::(conn)?; - - Ok(app_table) - } - - pub(crate) fn delete_app(app_id: &str, conn: &SqliteConnection) -> Result { - let app_table = dsl::app_table - .filter(app_table::id.eq(app_id)) - .first::(conn)?; - diesel_delete_table!(app_table, app_id, conn); - Ok(app_table) - } - - // pub(crate) fn read_views_belong_to_app( - // &self, - // app_id: &str, - // ) -> Result, FlowyError> { - // let conn = self.database.db_connection()?; - // - // let views = conn.immediate_transaction::<_, FlowyError, _>(|| { - // let app_table: AppTable = dsl::app_table - // .filter(app_table::id.eq(app_id)) - // .first::(&*(conn))?; - // let views = - // ViewTable::belonging_to(&app_table).load::(&*conn)?; - // Ok(views) - // })?; - // - // Ok(views) - // } -} - -#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[belongs_to(WorkspaceTable, foreign_key = "workspace_id")] -#[table_name = "app_table"] -pub(crate) struct AppTable { - pub id: String, - pub workspace_id: String, // equal to #[belongs_to(Workspace, foreign_key = "workspace_id")]. - pub name: String, - pub desc: String, - pub color_style: Vec, - pub last_view_id: Option, - pub modified_time: i64, - pub create_time: i64, - pub version: i64, - pub is_trash: bool, -} - -impl AppTable { - pub fn new(app_rev: AppRevision) -> Self { - Self { - id: app_rev.id, - workspace_id: app_rev.workspace_id, - name: app_rev.name, - desc: app_rev.desc, - color_style: Default::default(), - last_view_id: None, - modified_time: app_rev.modified_time, - create_time: app_rev.create_time, - version: 0, - is_trash: false, - } - } -} - -impl std::convert::From for TrashPB { - fn from(table: AppTable) -> Self { - TrashPB { - id: table.id, - name: table.name, - modified_time: table.modified_time, - create_time: table.create_time, - ty: TrashType::TrashApp, - } - } -} - -#[derive(AsChangeset, Identifiable, Default, Debug)] -#[table_name = "app_table"] -pub struct AppChangeset { - pub id: String, - pub name: Option, - pub desc: Option, - pub is_trash: Option, -} - -impl AppChangeset { - pub(crate) fn new(params: UpdateAppParams) -> Self { - AppChangeset { - id: params.app_id, - name: params.name, - desc: params.desc, - is_trash: params.is_trash, - } - } - - pub(crate) fn from_table(table: AppTable) -> Self { - AppChangeset { - id: table.id, - name: Some(table.name), - desc: Some(table.desc), - is_trash: Some(table.is_trash), - } - } -} -impl std::convert::From for AppRevision { - fn from(table: AppTable) -> Self { - AppRevision { - id: table.id, - workspace_id: table.workspace_id, - name: table.name, - desc: table.desc, - belongings: vec![], - version: table.version, - modified_time: table.modified_time, - create_time: table.create_time, - } - } -} diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/mod.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/mod.rs deleted file mode 100644 index e6992a4a04..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod app_sql; -pub mod trash_sql; -pub mod v1_impl; -pub mod view_sql; -pub mod workspace_sql; diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/trash_sql.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/trash_sql.rs deleted file mode 100644 index def1ae74a5..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/trash_sql.rs +++ /dev/null @@ -1,162 +0,0 @@ -use crate::errors::FlowyError; -use diesel::sql_types::Integer; -use flowy_sqlite::{ - prelude::*, - schema::{trash_table, trash_table::dsl}, - SqliteConnection, -}; -use folder_model::{TrashRevision, TrashTypeRevision}; - -pub struct TrashTableSql(); -impl TrashTableSql { - pub(crate) fn create_trash( - trashes: Vec, - conn: &SqliteConnection, - ) -> Result<(), FlowyError> { - for trash_rev in trashes { - let trash_table: TrashTable = trash_rev.into(); - match diesel_record_count!(trash_table, &trash_table.id, conn) { - 0 => diesel_insert_table!(trash_table, trash_table.clone(), conn), - _ => { - let changeset = TrashChangeset::from(trash_table); - diesel_update_table!(trash_table, changeset, conn) - }, - } - } - - Ok(()) - } - - pub(crate) fn read_all(conn: &SqliteConnection) -> Result, FlowyError> { - let trash_tables = dsl::trash_table.load::(conn)?; - let items = trash_tables - .into_iter() - .map(TrashRevision::from) - .collect::>(); - Ok(items) - } - - pub(crate) fn delete_all(conn: &SqliteConnection) -> Result<(), FlowyError> { - let _ = diesel::delete(dsl::trash_table).execute(conn)?; - Ok(()) - } - - pub(crate) fn read(trash_id: &str, conn: &SqliteConnection) -> Result { - let trash_table = dsl::trash_table - .filter(trash_table::id.eq(trash_id)) - .first::(conn)?; - Ok(trash_table) - } - - pub(crate) fn delete_trash(trash_id: &str, conn: &SqliteConnection) -> Result<(), FlowyError> { - diesel_delete_table!(trash_table, trash_id, conn); - Ok(()) - } -} - -#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[table_name = "trash_table"] -pub(crate) struct TrashTable { - pub id: String, - pub name: String, - pub desc: String, - pub modified_time: i64, - pub create_time: i64, - pub ty: SqlTrashType, -} -// impl std::convert::From for Trash { -// fn from(table: TrashTable) -> Self { -// Trash { -// id: table.id, -// name: table.name, -// modified_time: table.modified_time, -// create_time: table.create_time, -// ty: table.ty.into(), -// } -// } -// } -// -impl std::convert::From for TrashRevision { - fn from(trash: TrashTable) -> Self { - TrashRevision { - id: trash.id, - name: trash.name, - modified_time: trash.modified_time, - create_time: trash.create_time, - ty: trash.ty.into(), - } - } -} - -impl std::convert::From for TrashTable { - fn from(trash_rev: TrashRevision) -> Self { - TrashTable { - id: trash_rev.id, - name: trash_rev.name, - desc: "".to_string(), - modified_time: trash_rev.modified_time, - create_time: trash_rev.create_time, - ty: trash_rev.ty.into(), - } - } -} - -#[derive(AsChangeset, Identifiable, Clone, Default, Debug)] -#[table_name = "trash_table"] -pub(crate) struct TrashChangeset { - pub id: String, - pub name: Option, - pub modified_time: i64, -} - -impl std::convert::From for TrashChangeset { - fn from(trash: TrashTable) -> Self { - TrashChangeset { - id: trash.id, - name: Some(trash.name), - modified_time: trash.modified_time, - } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] -#[repr(i32)] -#[sql_type = "Integer"] -pub(crate) enum SqlTrashType { - Unknown = 0, - View = 1, - App = 2, -} - -impl std::convert::From for SqlTrashType { - fn from(value: i32) -> Self { - match value { - 0 => SqlTrashType::Unknown, - 1 => SqlTrashType::View, - 2 => SqlTrashType::App, - _o => SqlTrashType::Unknown, - } - } -} - -impl_sql_integer_expression!(SqlTrashType); - -impl std::convert::From for TrashTypeRevision { - fn from(ty: SqlTrashType) -> Self { - match ty { - SqlTrashType::Unknown => TrashTypeRevision::Unknown, - SqlTrashType::View => TrashTypeRevision::TrashView, - SqlTrashType::App => TrashTypeRevision::TrashApp, - } - } -} - -impl std::convert::From for SqlTrashType { - fn from(ty: TrashTypeRevision) -> Self { - match ty { - TrashTypeRevision::Unknown => SqlTrashType::Unknown, - TrashTypeRevision::TrashView => SqlTrashType::View, - TrashTypeRevision::TrashApp => SqlTrashType::App, - } - } -} diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/v1_impl.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/v1_impl.rs deleted file mode 100644 index 1f6c517cfa..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/v1_impl.rs +++ /dev/null @@ -1,222 +0,0 @@ -use crate::services::persistence::{ - version_1::{ - app_sql::{AppChangeset, AppTableSql}, - view_sql::{ViewChangeset, ViewTableSql}, - workspace_sql::{WorkspaceChangeset, WorkspaceTableSql}, - }, - FolderPersistenceTransaction, TrashTableSql, -}; -use flowy_error::FlowyResult; -use flowy_sqlite::DBConnection; -use folder_model::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; - -/// V1Transaction is deprecated since version 0.0.2 version -pub struct V1Transaction<'a>(pub &'a DBConnection); - -impl<'a> FolderPersistenceTransaction for V1Transaction<'a> { - fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> { - WorkspaceTableSql::create_workspace(user_id, workspace_rev, self.0)?; - Ok(()) - } - - fn read_workspaces( - &self, - user_id: &str, - workspace_id: Option, - ) -> FlowyResult> { - let tables = WorkspaceTableSql::read_workspaces(user_id, workspace_id, self.0)?; - let workspaces = tables - .into_iter() - .map(WorkspaceRevision::from) - .collect::>(); - Ok(workspaces) - } - - fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> { - WorkspaceTableSql::update_workspace(changeset, self.0) - } - - fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> { - WorkspaceTableSql::delete_workspace(workspace_id, self.0) - } - - fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> { - AppTableSql::create_app(app_rev, self.0)?; - Ok(()) - } - - fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { - AppTableSql::update_app(changeset, self.0)?; - Ok(()) - } - - fn read_app(&self, app_id: &str) -> FlowyResult { - let app_revision: AppRevision = AppTableSql::read_app(app_id, self.0)?.into(); - Ok(app_revision) - } - - fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult> { - let tables = AppTableSql::read_workspace_apps(workspace_id, self.0)?; - let apps = tables - .into_iter() - .map(AppRevision::from) - .collect::>(); - Ok(apps) - } - - fn delete_app(&self, app_id: &str) -> FlowyResult { - let app_revision: AppRevision = AppTableSql::delete_app(app_id, self.0)?.into(); - Ok(app_revision) - } - - fn move_app(&self, _app_id: &str, _from: usize, _to: usize) -> FlowyResult<()> { - Ok(()) - } - - fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> { - ViewTableSql::create_view(view_rev, self.0)?; - Ok(()) - } - - fn read_view(&self, view_id: &str) -> FlowyResult { - let view_revision: ViewRevision = ViewTableSql::read_view(view_id, self.0)?.into(); - Ok(view_revision) - } - - fn read_views(&self, belong_to_id: &str) -> FlowyResult> { - let tables = ViewTableSql::read_views(belong_to_id, self.0)?; - let views = tables - .into_iter() - .map(ViewRevision::from) - .collect::>(); - Ok(views) - } - - fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { - ViewTableSql::update_view(changeset, self.0)?; - Ok(()) - } - - fn delete_view(&self, view_id: &str) -> FlowyResult { - let view_revision: ViewRevision = ViewTableSql::read_view(view_id, self.0)?.into(); - ViewTableSql::delete_view(view_id, self.0)?; - Ok(view_revision) - } - - fn move_view(&self, _view_id: &str, _from: usize, _to: usize) -> FlowyResult<()> { - Ok(()) - } - - fn create_trash(&self, trashes: Vec) -> FlowyResult<()> { - TrashTableSql::create_trash(trashes, self.0)?; - Ok(()) - } - - fn read_trash(&self, trash_id: Option) -> FlowyResult> { - match trash_id { - None => TrashTableSql::read_all(self.0), - Some(trash_id) => { - let trash_revision: TrashRevision = TrashTableSql::read(&trash_id, self.0)?.into(); - Ok(vec![trash_revision]) - }, - } - } - - fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { - match trash_ids { - None => TrashTableSql::delete_all(self.0), - Some(trash_ids) => { - for trash_id in &trash_ids { - TrashTableSql::delete_trash(trash_id, self.0)?; - } - Ok(()) - }, - } - } -} - -// https://www.reddit.com/r/rust/comments/droxdg/why_arent_traits_impld_for_boxdyn_trait/ -impl FolderPersistenceTransaction for Box -where - T: FolderPersistenceTransaction + ?Sized, -{ - fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> { - (**self).create_workspace(user_id, workspace_rev) - } - - fn read_workspaces( - &self, - user_id: &str, - workspace_id: Option, - ) -> FlowyResult> { - (**self).read_workspaces(user_id, workspace_id) - } - - fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> { - (**self).update_workspace(changeset) - } - - fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> { - (**self).delete_workspace(workspace_id) - } - - fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> { - (**self).create_app(app_rev) - } - - fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { - (**self).update_app(changeset) - } - - fn read_app(&self, app_id: &str) -> FlowyResult { - (**self).read_app(app_id) - } - - fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult> { - (**self).read_workspace_apps(workspace_id) - } - - fn delete_app(&self, app_id: &str) -> FlowyResult { - (**self).delete_app(app_id) - } - - fn move_app(&self, _app_id: &str, _from: usize, _to: usize) -> FlowyResult<()> { - Ok(()) - } - - fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> { - (**self).create_view(view_rev) - } - - fn read_view(&self, view_id: &str) -> FlowyResult { - (**self).read_view(view_id) - } - - fn read_views(&self, belong_to_id: &str) -> FlowyResult> { - (**self).read_views(belong_to_id) - } - - fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { - (**self).update_view(changeset) - } - - fn delete_view(&self, view_id: &str) -> FlowyResult { - (**self).delete_view(view_id) - } - - fn move_view(&self, _view_id: &str, _from: usize, _to: usize) -> FlowyResult<()> { - Ok(()) - } - - fn create_trash(&self, trashes: Vec) -> FlowyResult<()> { - (**self).create_trash(trashes) - } - - fn read_trash(&self, trash_id: Option) -> FlowyResult> { - (**self).read_trash(trash_id) - } - - fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { - (**self).delete_trash(trash_ids) - } -} diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs deleted file mode 100644 index 673c234aec..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs +++ /dev/null @@ -1,221 +0,0 @@ -use crate::{ - entities::{ - trash::{TrashPB, TrashType}, - view::UpdateViewParams, - }, - errors::FlowyError, - services::persistence::version_1::app_sql::AppTable, -}; -use diesel::sql_types::Integer; -use flowy_sqlite::{ - prelude::*, - schema::{view_table, view_table::dsl}, - SqliteConnection, -}; -use folder_model::{ViewDataFormatRevision, ViewLayoutTypeRevision, ViewRevision}; -use lib_infra::util::timestamp; - -pub struct ViewTableSql(); -impl ViewTableSql { - pub(crate) fn create_view( - view_rev: ViewRevision, - conn: &SqliteConnection, - ) -> Result<(), FlowyError> { - let view_table = ViewTable::new(view_rev); - match diesel_record_count!(view_table, &view_table.id, conn) { - 0 => diesel_insert_table!(view_table, view_table.clone(), conn), - _ => { - let changeset = ViewChangeset::from_table(view_table); - diesel_update_table!(view_table, changeset, conn) - }, - } - Ok(()) - } - - pub(crate) fn read_view(view_id: &str, conn: &SqliteConnection) -> Result { - // https://docs.diesel.rs/diesel/query_builder/struct.UpdateStatement.html - // let mut filter = - // dsl::view_table.filter(view_table::id.eq(view_id)).into_boxed(); - // if let Some(is_trash) = is_trash { - // filter = filter.filter(view_table::is_trash.eq(is_trash)); - // } - // let repeated_view = filter.first::(conn)?; - let view_table = dsl::view_table - .filter(view_table::id.eq(view_id)) - .first::(conn)?; - - Ok(view_table) - } - - // belong_to_id will be the app_id or view_id. - pub(crate) fn read_views( - belong_to_id: &str, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let view_tables = dsl::view_table - .filter(view_table::belong_to_id.eq(belong_to_id)) - .order(view_table::create_time.asc()) - .into_boxed() - .load::(conn)?; - - Ok(view_tables) - } - - pub(crate) fn update_view( - changeset: ViewChangeset, - conn: &SqliteConnection, - ) -> Result<(), FlowyError> { - diesel_update_table!(view_table, changeset, conn); - Ok(()) - } - - pub(crate) fn delete_view(view_id: &str, conn: &SqliteConnection) -> Result<(), FlowyError> { - diesel_delete_table!(view_table, view_id, conn); - Ok(()) - } -} - -#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[belongs_to(AppTable, foreign_key = "belong_to_id")] -#[table_name = "view_table"] -pub(crate) struct ViewTable { - pub id: String, - pub belong_to_id: String, - pub name: String, - pub desc: String, - pub modified_time: i64, - pub create_time: i64, - pub thumbnail: String, - pub view_type: SqlViewDataFormat, - pub version: i64, - pub is_trash: bool, - pub ext_data: String, -} - -impl ViewTable { - pub fn new(view_rev: ViewRevision) -> Self { - let data_type = match view_rev.data_format { - ViewDataFormatRevision::DeltaFormat => SqlViewDataFormat::Delta, - ViewDataFormatRevision::DatabaseFormat => SqlViewDataFormat::Database, - ViewDataFormatRevision::NodeFormat => SqlViewDataFormat::Tree, - }; - - ViewTable { - id: view_rev.id, - belong_to_id: view_rev.app_id, - name: view_rev.name, - desc: view_rev.desc, - modified_time: view_rev.modified_time, - create_time: view_rev.create_time, - thumbnail: view_rev.thumbnail, - view_type: data_type, - ext_data: view_rev.ext_data, - version: 0, - is_trash: false, - } - } -} - -impl std::convert::From for ViewRevision { - fn from(table: ViewTable) -> Self { - let data_format = match table.view_type { - SqlViewDataFormat::Delta => ViewDataFormatRevision::DeltaFormat, - SqlViewDataFormat::Database => ViewDataFormatRevision::DatabaseFormat, - SqlViewDataFormat::Tree => ViewDataFormatRevision::NodeFormat, - }; - - ViewRevision::new( - table.id, - table.belong_to_id, - table.name, - table.desc, - data_format, - // Store the view in ViewTable was deprecated since v0.0.2. - // No need to worry about layout. - ViewLayoutTypeRevision::Document, - table.modified_time, - table.create_time, - ) - } -} - -impl std::convert::From for TrashPB { - fn from(table: ViewTable) -> Self { - TrashPB { - id: table.id, - name: table.name, - modified_time: table.modified_time, - create_time: table.create_time, - ty: TrashType::TrashView, - } - } -} - -#[derive(AsChangeset, Identifiable, Clone, Default, Debug)] -#[table_name = "view_table"] -pub struct ViewChangeset { - pub id: String, - pub name: Option, - pub desc: Option, - pub thumbnail: Option, - pub modified_time: i64, -} - -impl ViewChangeset { - pub(crate) fn new(params: UpdateViewParams) -> Self { - ViewChangeset { - id: params.view_id, - name: params.name, - desc: params.desc, - thumbnail: params.thumbnail, - modified_time: timestamp(), - } - } - - pub(crate) fn from_table(table: ViewTable) -> Self { - ViewChangeset { - id: table.id, - name: Some(table.name), - desc: Some(table.desc), - thumbnail: Some(table.thumbnail), - modified_time: table.modified_time, - } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] -#[repr(i32)] -#[sql_type = "Integer"] -pub enum SqlViewDataFormat { - Delta = 0, - Database = 1, - Tree = 2, -} - -impl std::default::Default for SqlViewDataFormat { - fn default() -> Self { - SqlViewDataFormat::Delta - } -} - -impl std::convert::From for SqlViewDataFormat { - fn from(value: i32) -> Self { - match value { - 0 => SqlViewDataFormat::Delta, - 1 => SqlViewDataFormat::Database, - 2 => SqlViewDataFormat::Tree, - o => { - log::error!("Unsupported view type {}, fallback to ViewType::Block", o); - SqlViewDataFormat::Delta - }, - } - } -} - -impl SqlViewDataFormat { - pub fn value(&self) -> i32 { - *self as i32 - } -} - -impl_sql_integer_expression!(SqlViewDataFormat); diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/workspace_sql.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/workspace_sql.rs deleted file mode 100644 index 2abb483043..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/workspace_sql.rs +++ /dev/null @@ -1,129 +0,0 @@ -use crate::{entities::workspace::UpdateWorkspaceParams, errors::FlowyError}; -use diesel::SqliteConnection; -use flowy_sqlite::{ - prelude::*, - schema::{workspace_table, workspace_table::dsl}, -}; -use folder_model::WorkspaceRevision; - -pub(crate) struct WorkspaceTableSql(); -impl WorkspaceTableSql { - pub(crate) fn create_workspace( - user_id: &str, - workspace_rev: WorkspaceRevision, - conn: &SqliteConnection, - ) -> Result<(), FlowyError> { - let table = WorkspaceTable::new(workspace_rev, user_id); - match diesel_record_count!(workspace_table, &table.id, conn) { - 0 => diesel_insert_table!(workspace_table, table.clone(), conn), - _ => { - let changeset = WorkspaceChangeset::from_table(table); - diesel_update_table!(workspace_table, changeset, conn); - }, - } - Ok(()) - } - - pub(crate) fn read_workspaces( - user_id: &str, - workspace_id: Option, - conn: &SqliteConnection, - ) -> Result, FlowyError> { - let mut filter = dsl::workspace_table - .filter(workspace_table::user_id.eq(user_id)) - .order(workspace_table::create_time.asc()) - .into_boxed(); - - if let Some(workspace_id) = workspace_id { - filter = filter.filter(workspace_table::id.eq(workspace_id)); - }; - - let workspaces = filter.load::(conn)?; - - Ok(workspaces) - } - - #[allow(dead_code)] - pub(crate) fn update_workspace( - changeset: WorkspaceChangeset, - conn: &SqliteConnection, - ) -> Result<(), FlowyError> { - diesel_update_table!(workspace_table, changeset, conn); - Ok(()) - } - - #[allow(dead_code)] - pub(crate) fn delete_workspace( - workspace_id: &str, - conn: &SqliteConnection, - ) -> Result<(), FlowyError> { - diesel_delete_table!(workspace_table, workspace_id, conn); - Ok(()) - } -} - -#[derive(PartialEq, Eq, Clone, Debug, Queryable, Identifiable, Insertable)] -#[table_name = "workspace_table"] -pub struct WorkspaceTable { - pub id: String, - pub name: String, - pub desc: String, - pub modified_time: i64, - pub create_time: i64, - pub user_id: String, - pub version: i64, -} - -impl WorkspaceTable { - #[allow(dead_code)] - pub fn new(workspace_rev: WorkspaceRevision, user_id: &str) -> Self { - WorkspaceTable { - id: workspace_rev.id, - name: workspace_rev.name, - desc: workspace_rev.desc, - modified_time: workspace_rev.modified_time, - create_time: workspace_rev.create_time, - user_id: user_id.to_owned(), - version: 0, - } - } -} - -impl std::convert::From for WorkspaceRevision { - fn from(table: WorkspaceTable) -> Self { - WorkspaceRevision { - id: table.id, - name: table.name, - desc: table.desc, - apps: vec![], - modified_time: table.modified_time, - create_time: table.create_time, - } - } -} - -#[derive(AsChangeset, Identifiable, Clone, Default, Debug)] -#[table_name = "workspace_table"] -pub struct WorkspaceChangeset { - pub id: String, - pub name: Option, - pub desc: Option, -} - -impl WorkspaceChangeset { - pub fn new(params: UpdateWorkspaceParams) -> Self { - WorkspaceChangeset { - id: params.id, - name: params.name, - desc: params.desc, - } - } - - pub(crate) fn from_table(table: WorkspaceTable) -> Self { - WorkspaceChangeset { - id: table.id, - name: Some(table.name), - desc: Some(table.desc), - } - } -} diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/mod.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/mod.rs deleted file mode 100644 index ae76b104d5..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod v2_impl; diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs deleted file mode 100644 index 61373c61e1..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs +++ /dev/null @@ -1,245 +0,0 @@ -use crate::services::{ - folder_editor::FolderEditor, - persistence::{AppChangeset, FolderPersistenceTransaction, ViewChangeset, WorkspaceChangeset}, -}; -use flowy_error::{FlowyError, FlowyResult}; -use folder_model::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; -use std::sync::Arc; - -impl FolderPersistenceTransaction for FolderEditor { - fn create_workspace(&self, _user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> { - if let Some(change) = self.folder.write().create_workspace(workspace_rev)? { - self.apply_change(change)?; - } - Ok(()) - } - - fn read_workspaces( - &self, - _user_id: &str, - workspace_id: Option, - ) -> FlowyResult> { - let workspaces = self.folder.read().read_workspaces(workspace_id)?; - Ok(workspaces) - } - - fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> { - if let Some(change) = - self - .folder - .write() - .update_workspace(&changeset.id, changeset.name, changeset.desc)? - { - self.apply_change(change)?; - } - Ok(()) - } - - fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> { - if let Some(change) = self.folder.write().delete_workspace(workspace_id)? { - self.apply_change(change)?; - } - Ok(()) - } - - fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> { - if let Some(change) = self.folder.write().create_app(app_rev)? { - self.apply_change(change)?; - } - Ok(()) - } - - fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { - if let Some(change) = - self - .folder - .write() - .update_app(&changeset.id, changeset.name, changeset.desc)? - { - self.apply_change(change)?; - } - Ok(()) - } - - fn read_app(&self, app_id: &str) -> FlowyResult { - let app = self.folder.read().read_app(app_id)?; - Ok(app) - } - - fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult> { - let workspaces = self - .folder - .read() - .read_workspaces(Some(workspace_id.to_owned()))?; - match workspaces.first() { - None => Err( - FlowyError::record_not_found() - .context(format!("can't find workspace with id {}", workspace_id)), - ), - Some(workspace) => Ok(workspace.apps.clone()), - } - } - - fn delete_app(&self, app_id: &str) -> FlowyResult { - let app = self.folder.read().read_app(app_id)?; - if let Some(change) = self.folder.write().delete_app(app_id)? { - self.apply_change(change)?; - } - Ok(app) - } - - fn move_app(&self, app_id: &str, from: usize, to: usize) -> FlowyResult<()> { - if let Some(change) = self.folder.write().move_app(app_id, from, to)? { - self.apply_change(change)?; - } - Ok(()) - } - - fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> { - if let Some(change) = self.folder.write().create_view(view_rev)? { - self.apply_change(change)?; - } - Ok(()) - } - - fn read_view(&self, view_id: &str) -> FlowyResult { - let view = self.folder.read().read_view(view_id)?; - Ok(view) - } - - fn read_views(&self, belong_to_id: &str) -> FlowyResult> { - let views = self.folder.read().read_views(belong_to_id)?; - Ok(views) - } - - fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { - if let Some(change) = self.folder.write().update_view( - &changeset.id, - changeset.name, - changeset.desc, - changeset.modified_time, - )? { - self.apply_change(change)?; - } - Ok(()) - } - - fn delete_view(&self, view_id: &str) -> FlowyResult { - let view = self.folder.read().read_view(view_id)?; - if let Some(change) = self.folder.write().delete_view(&view.app_id, view_id)? { - self.apply_change(change)?; - } - Ok(view) - } - - fn move_view(&self, view_id: &str, from: usize, to: usize) -> FlowyResult<()> { - if let Some(change) = self.folder.write().move_view(view_id, from, to)? { - self.apply_change(change)?; - } - Ok(()) - } - - fn create_trash(&self, trashes: Vec) -> FlowyResult<()> { - if let Some(change) = self.folder.write().create_trash(trashes)? { - self.apply_change(change)?; - } - Ok(()) - } - - fn read_trash(&self, trash_id: Option) -> FlowyResult> { - let trash = self.folder.read().read_trash(trash_id)?; - Ok(trash) - } - - fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { - if let Some(change) = self.folder.write().delete_trash(trash_ids)? { - self.apply_change(change)?; - } - Ok(()) - } -} - -impl FolderPersistenceTransaction for Arc -where - T: FolderPersistenceTransaction + ?Sized, -{ - fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> { - (**self).create_workspace(user_id, workspace_rev) - } - - fn read_workspaces( - &self, - user_id: &str, - workspace_id: Option, - ) -> FlowyResult> { - (**self).read_workspaces(user_id, workspace_id) - } - - fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> { - (**self).update_workspace(changeset) - } - - fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> { - (**self).delete_workspace(workspace_id) - } - - fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> { - (**self).create_app(app_rev) - } - - fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { - (**self).update_app(changeset) - } - - fn read_app(&self, app_id: &str) -> FlowyResult { - (**self).read_app(app_id) - } - - fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult> { - (**self).read_workspace_apps(workspace_id) - } - - fn delete_app(&self, app_id: &str) -> FlowyResult { - (**self).delete_app(app_id) - } - - fn move_app(&self, app_id: &str, from: usize, to: usize) -> FlowyResult<()> { - (**self).move_app(app_id, from, to) - } - - fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> { - (**self).create_view(view_rev) - } - - fn read_view(&self, view_id: &str) -> FlowyResult { - (**self).read_view(view_id) - } - - fn read_views(&self, belong_to_id: &str) -> FlowyResult> { - (**self).read_views(belong_to_id) - } - - fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { - (**self).update_view(changeset) - } - - fn delete_view(&self, view_id: &str) -> FlowyResult { - (**self).delete_view(view_id) - } - - fn move_view(&self, view_id: &str, from: usize, to: usize) -> FlowyResult<()> { - (**self).move_view(view_id, from, to) - } - - fn create_trash(&self, trashes: Vec) -> FlowyResult<()> { - (**self).create_trash(trashes) - } - - fn read_trash(&self, trash_id: Option) -> FlowyResult> { - (**self).read_trash(trash_id) - } - - fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { - (**self).delete_trash(trash_ids) - } -} diff --git a/frontend/rust-lib/flowy-folder/src/services/trash/controller.rs b/frontend/rust-lib/flowy-folder/src/services/trash/controller.rs deleted file mode 100644 index 80294678c4..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/trash/controller.rs +++ /dev/null @@ -1,285 +0,0 @@ -use crate::{ - entities::trash::{RepeatedTrashIdPB, RepeatedTrashPB, TrashIdPB, TrashPB, TrashType}, - errors::{FlowyError, FlowyResult}, - event_map::{FolderCouldServiceV1, WorkspaceUser}, - notification::{send_anonymous_notification, FolderNotification}, - services::persistence::{FolderPersistence, FolderPersistenceTransaction}, -}; - -use folder_model::TrashRevision; -use std::{fmt::Formatter, sync::Arc}; -use tokio::sync::{broadcast, mpsc}; - -pub struct TrashController { - persistence: Arc, - notify: broadcast::Sender, - #[allow(dead_code)] - cloud_service: Arc, - #[allow(dead_code)] - user: Arc, -} - -impl TrashController { - pub fn new( - persistence: Arc, - cloud_service: Arc, - user: Arc, - ) -> Self { - let (tx, _) = broadcast::channel(10); - Self { - persistence, - notify: tx, - cloud_service, - user, - } - } - - #[tracing::instrument(level = "debug", skip(self), fields(putback) err)] - pub async fn putback(&self, trash_id: &str) -> FlowyResult<()> { - let (tx, mut rx) = mpsc::channel::>(1); - let trash = self - .persistence - .begin_transaction(|transaction| { - let mut repeated_trash = transaction.read_trash(Some(trash_id.to_owned()))?; - transaction.delete_trash(Some(vec![trash_id.to_owned()]))?; - notify_trash_changed(transaction.read_trash(None)?); - - if repeated_trash.is_empty() { - return Err(FlowyError::internal().context("Try to put back trash is not exists")); - } - Ok(repeated_trash.pop().unwrap()) - }) - .await?; - - let identifier = TrashIdPB { - id: trash.id, - ty: trash.ty.into(), - }; - - tracing::Span::current().record("putback", format!("{:?}", &identifier).as_str()); - let _ = self - .notify - .send(TrashEvent::Putback(vec![identifier].into(), tx)); - rx.recv().await.unwrap()?; - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self) err)] - pub async fn restore_all_trash(&self) -> FlowyResult<()> { - let trash_identifier: RepeatedTrashIdPB = self - .persistence - .begin_transaction(|transaction| { - let trash = transaction.read_trash(None); - let _ = transaction.delete_trash(None); - trash - }) - .await? - .into(); - - let (tx, mut rx) = mpsc::channel::>(1); - let _ = self.notify.send(TrashEvent::Putback(trash_identifier, tx)); - let _ = rx.recv().await; - - notify_trash_changed(RepeatedTrashPB { items: vec![] }); - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self), err)] - pub async fn delete_all_trash(&self) -> FlowyResult<()> { - let all_trash_identifiers: RepeatedTrashIdPB = self - .persistence - .begin_transaction(|transaction| transaction.read_trash(None)) - .await? - .into(); - - self.delete_with_identifiers(all_trash_identifiers).await?; - - notify_trash_changed(RepeatedTrashPB { items: vec![] }); - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self), err)] - pub async fn delete(&self, trash_identifiers: RepeatedTrashIdPB) -> FlowyResult<()> { - self - .delete_with_identifiers(trash_identifiers.clone()) - .await?; - let trash_revs = self - .persistence - .begin_transaction(|transaction| transaction.read_trash(None)) - .await?; - - notify_trash_changed(trash_revs); - - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self), fields(delete_trash_ids), err)] - pub async fn delete_with_identifiers( - &self, - trash_identifiers: RepeatedTrashIdPB, - ) -> FlowyResult<()> { - let (tx, mut rx) = mpsc::channel::>(1); - tracing::Span::current().record( - "delete_trash_ids", - format!("{}", trash_identifiers).as_str(), - ); - let _ = self - .notify - .send(TrashEvent::Delete(trash_identifiers.clone(), tx)); - - match rx.recv().await { - None => {}, - Some(result) => match result { - Ok(_) => {}, - Err(e) => log::error!("{}", e), - }, - } - self - .persistence - .begin_transaction(|transaction| { - let ids = trash_identifiers - .items - .into_iter() - .map(|item| item.id) - .collect::>(); - transaction.delete_trash(Some(ids)) - }) - .await?; - - Ok(()) - } - - // [[ transaction ]] - // https://www.tutlane.com/tutorial/sqlite/sqlite-transactions-begin-commit-rollback - // We can use these commands only when we are performing INSERT, UPDATE, and - // DELETE operations. It’s not possible for us to use these commands to - // CREATE and DROP tables operations because those are auto-commit in the - // database. - #[tracing::instrument( - name = "add_trash", - level = "debug", - skip(self, trash), - fields(trash_ids), - err - )] - pub async fn add>(&self, trash: Vec) -> Result<(), FlowyError> { - let (tx, mut rx) = mpsc::channel::>(1); - let trash_revs: Vec = trash.into_iter().map(|t| t.into()).collect(); - let identifiers = trash_revs - .iter() - .map(|t| t.into()) - .collect::>(); - - tracing::Span::current().record( - "trash_ids", - format!( - "{:?}", - identifiers - .iter() - .map(|identifier| format!("{:?}:{}", identifier.ty, identifier.id)) - .collect::>() - ) - .as_str(), - ); - - self - .persistence - .begin_transaction(|transaction| { - transaction.create_trash(trash_revs.clone())?; - notify_trash_changed(transaction.read_trash(None)?); - Ok(()) - }) - .await?; - let _ = self - .notify - .send(TrashEvent::NewTrash(identifiers.into(), tx)); - rx.recv().await.unwrap()?; - - Ok(()) - } - - pub fn subscribe(&self) -> broadcast::Receiver { - self.notify.subscribe() - } - - pub async fn read_trash(&self) -> Result { - let items: Vec = self - .persistence - .begin_transaction(|transaction| transaction.read_trash(None)) - .await? - .into_iter() - .map(|trash_rev| trash_rev.into()) - .collect(); - - Ok(RepeatedTrashPB { items }) - } - - pub fn read_trash_ids<'a>( - &self, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), - ) -> Result, FlowyError> { - let ids = transaction - .read_trash(None)? - .into_iter() - .map(|item| item.id) - .collect::>(); - Ok(ids) - } -} - -#[tracing::instrument(level = "debug", skip(repeated_trash), fields(n_trash))] -fn notify_trash_changed>(repeated_trash: T) { - let repeated_trash = repeated_trash.into(); - tracing::Span::current().record("n_trash", repeated_trash.len()); - send_anonymous_notification(FolderNotification::DidUpdateTrash) - .payload(repeated_trash) - .send(); -} - -#[derive(Clone)] -pub enum TrashEvent { - NewTrash(RepeatedTrashIdPB, mpsc::Sender>), - Putback(RepeatedTrashIdPB, mpsc::Sender>), - Delete(RepeatedTrashIdPB, mpsc::Sender>), -} - -impl std::fmt::Debug for TrashEvent { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - TrashEvent::NewTrash(identifiers, _) => f.write_str(&format!("{:?}", identifiers)), - TrashEvent::Putback(identifiers, _) => f.write_str(&format!("{:?}", identifiers)), - TrashEvent::Delete(identifiers, _) => f.write_str(&format!("{:?}", identifiers)), - } - } -} - -impl TrashEvent { - pub fn select(self, s: TrashType) -> Option { - match self { - TrashEvent::Putback(mut identifiers, sender) => { - identifiers.items.retain(|item| item.ty == s); - if identifiers.items.is_empty() { - None - } else { - Some(TrashEvent::Putback(identifiers, sender)) - } - }, - TrashEvent::Delete(mut identifiers, sender) => { - identifiers.items.retain(|item| item.ty == s); - if identifiers.items.is_empty() { - None - } else { - Some(TrashEvent::Delete(identifiers, sender)) - } - }, - TrashEvent::NewTrash(mut identifiers, sender) => { - identifiers.items.retain(|item| item.ty == s); - if identifiers.items.is_empty() { - None - } else { - Some(TrashEvent::NewTrash(identifiers, sender)) - } - }, - } - } -} diff --git a/frontend/rust-lib/flowy-folder/src/services/trash/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/trash/event_handler.rs deleted file mode 100644 index 3e829928c0..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/trash/event_handler.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::{ - entities::trash::{RepeatedTrashIdPB, RepeatedTrashPB, TrashIdPB}, - errors::FlowyError, - services::TrashController, -}; -use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult}; -use std::sync::Arc; - -#[tracing::instrument(level = "debug", skip(controller), err)] -pub(crate) async fn read_trash_handler( - controller: AFPluginState>, -) -> DataResult { - let repeated_trash = controller.read_trash().await?; - data_result_ok(repeated_trash) -} - -#[tracing::instrument(level = "debug", skip(identifier, controller), err)] -pub(crate) async fn putback_trash_handler( - identifier: AFPluginData, - controller: AFPluginState>, -) -> Result<(), FlowyError> { - controller.putback(&identifier.id).await?; - Ok(()) -} - -#[tracing::instrument(level = "debug", skip(identifiers, controller), err)] -pub(crate) async fn delete_trash_handler( - identifiers: AFPluginData, - controller: AFPluginState>, -) -> Result<(), FlowyError> { - controller.delete(identifiers.into_inner()).await?; - Ok(()) -} - -#[tracing::instrument(level = "debug", skip(controller), err)] -pub(crate) async fn restore_all_trash_handler( - controller: AFPluginState>, -) -> Result<(), FlowyError> { - controller.restore_all_trash().await?; - Ok(()) -} - -#[tracing::instrument(level = "debug", skip(controller), err)] -pub(crate) async fn delete_all_trash_handler( - controller: AFPluginState>, -) -> Result<(), FlowyError> { - controller.delete_all_trash().await?; - Ok(()) -} diff --git a/frontend/rust-lib/flowy-folder/src/services/trash/mod.rs b/frontend/rust-lib/flowy-folder/src/services/trash/mod.rs deleted file mode 100644 index e854517f17..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/trash/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod controller; -pub mod event_handler; diff --git a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs b/frontend/rust-lib/flowy-folder/src/services/view/controller.rs deleted file mode 100644 index 8d2e886ac9..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs +++ /dev/null @@ -1,536 +0,0 @@ -pub use crate::entities::view::ViewDataFormatPB; -use crate::entities::{AppPB, DeletedViewPB, ViewLayoutTypePB}; -use crate::manager::{ViewDataProcessor, ViewDataProcessorMap}; -use crate::{ - entities::{ - trash::{RepeatedTrashIdPB, TrashType}, - view::{CreateViewParams, UpdateViewParams, ViewPB}, - }, - errors::{FlowyError, FlowyResult}, - event_map::{FolderCouldServiceV1, WorkspaceUser}, - notification::{send_notification, FolderNotification}, - services::{ - persistence::{FolderPersistence, FolderPersistenceTransaction, ViewChangeset}, - TrashController, TrashEvent, - }, -}; -use bytes::Bytes; -use flowy_sqlite::kv::KV; -use folder_model::{gen_view_id, ViewRevision}; -use futures::{FutureExt, StreamExt}; -use lib_infra::util::timestamp; -use std::collections::HashMap; -use std::{collections::HashSet, sync::Arc}; - -const LATEST_VIEW_ID: &str = "latest_view_id"; - -pub struct ViewController { - user: Arc, - cloud_service: Arc, - persistence: Arc, - trash_controller: Arc, - data_processors: ViewDataProcessorMap, -} - -impl ViewController { - pub(crate) fn new( - user: Arc, - persistence: Arc, - cloud_service: Arc, - trash_controller: Arc, - data_processors: ViewDataProcessorMap, - ) -> Self { - Self { - user, - cloud_service, - persistence, - trash_controller, - data_processors, - } - } - - pub(crate) fn initialize(&self) -> Result<(), FlowyError> { - self.listen_trash_can_event(); - Ok(()) - } - - #[tracing::instrument(level = "trace", skip(self, params), fields(name = %params.name), err)] - pub(crate) async fn create_view_from_params( - &self, - params: CreateViewParams, - ) -> Result { - let processor = self.get_data_processor(params.data_format.clone())?; - let user_id = self.user.user_id()?; - match params.initial_data.is_empty() { - true => { - tracing::trace!("Create view with build-in data"); - processor - .create_view_with_built_in_data( - &user_id, - ¶ms.view_id, - ¶ms.name, - params.layout.clone(), - params.data_format.clone(), - params.ext.clone(), - ) - .await?; - }, - false => { - tracing::trace!("Create view with view data"); - processor - .create_view_with_custom_data( - &user_id, - ¶ms.view_id, - ¶ms.name, - params.initial_data.clone(), - params.layout.clone(), - params.ext.clone(), - ) - .await?; - }, - } - - let trash_controller = self.trash_controller.clone(); - let view_rev = self - .persistence - .begin_transaction(|transaction| { - let time = timestamp(); - let view_rev = ViewRevision::new( - params.view_id, - params.belong_to_id, - params.name, - params.desc, - params.data_format.into(), - params.layout.into(), - time, - time, - ); - let belong_to_id = view_rev.app_id.clone(); - transaction.create_view(view_rev.clone())?; - notify_views_changed(&belong_to_id, trash_controller, &transaction)?; - Ok(view_rev) - }) - .await?; - - Ok(view_rev) - } - - #[tracing::instrument(level = "debug", skip(self, view_id, view_data), err)] - pub(crate) async fn create_view( - &self, - view_id: &str, - name: &str, - data_type: ViewDataFormatPB, - layout_type: ViewLayoutTypePB, - view_data: Bytes, - ) -> Result<(), FlowyError> { - if view_data.is_empty() { - return Err(FlowyError::internal().context("The content of the view should not be empty")); - } - let user_id = self.user.user_id()?; - let processor = self.get_data_processor(data_type)?; - processor - .create_view_with_custom_data( - &user_id, - view_id, - name, - view_data.to_vec(), - layout_type, - HashMap::default(), - ) - .await?; - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self, view_id), err)] - pub(crate) async fn read_view(&self, view_id: &str) -> Result { - let view_rev = self - .persistence - .begin_transaction(|transaction| { - let view = transaction.read_view(view_id)?; - let trash_ids = self.trash_controller.read_trash_ids(&transaction)?; - if trash_ids.contains(&view.id) { - return Err(FlowyError::record_not_found()); - } - Ok(view) - }) - .await?; - Ok(view_rev) - } - - pub(crate) async fn read_local_views( - &self, - ids: Vec, - ) -> Result, FlowyError> { - self - .persistence - .begin_transaction(|transaction| { - let mut views = vec![]; - for view_id in ids { - views.push(transaction.read_view(&view_id)?); - } - Ok(views) - }) - .await - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub(crate) fn set_latest_view(&self, view_id: &str) -> Result<(), FlowyError> { - KV::set_str(LATEST_VIEW_ID, view_id.to_owned()); - Ok(()) - } - - #[tracing::instrument(level = "trace", skip(self))] - pub(crate) fn clear_latest_view(&self) { - let _ = KV::remove(LATEST_VIEW_ID); - } - - #[tracing::instrument(level = "debug", skip(self), err)] - pub(crate) async fn close_view(&self, view_id: &str) -> Result<(), FlowyError> { - let processor = self.get_data_processor_from_view_id(view_id).await?; - processor.close_view(view_id).await?; - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self), err)] - pub(crate) async fn move_view_to_trash(&self, view_id: &str) -> Result<(), FlowyError> { - if let Some(latest_view_id) = KV::get_str(LATEST_VIEW_ID) { - if latest_view_id == view_id { - let _ = KV::remove(LATEST_VIEW_ID); - } - } - - let deleted_view = self - .persistence - .begin_transaction(|transaction| { - let view = transaction.read_view(view_id)?; - let views = - read_belonging_views_on_local(&view.app_id, self.trash_controller.clone(), &transaction)?; - - let index = views - .iter() - .position(|view| view.id == view_id) - .map(|index| index as i32); - Ok(DeletedViewPB { - view_id: view_id.to_owned(), - index, - }) - }) - .await?; - - send_notification(view_id, FolderNotification::DidMoveViewToTrash) - .payload(deleted_view) - .send(); - - let processor = self.get_data_processor_from_view_id(view_id).await?; - processor.close_view(view_id).await?; - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self), err)] - pub(crate) async fn move_view( - &self, - view_id: &str, - from: usize, - to: usize, - ) -> Result<(), FlowyError> { - self - .persistence - .begin_transaction(|transaction| { - transaction.move_view(view_id, from, to)?; - let view = transaction.read_view(view_id)?; - notify_views_changed(&view.app_id, self.trash_controller.clone(), &transaction)?; - Ok(()) - }) - .await?; - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self), err)] - pub(crate) async fn duplicate_view(&self, view: ViewPB) -> Result<(), FlowyError> { - let view_rev = self - .persistence - .begin_transaction(|transaction| transaction.read_view(&view.id)) - .await?; - - let processor = self.get_data_processor(view_rev.data_format.clone())?; - let view_data = processor.get_view_data(&view).await?; - let duplicate_params = CreateViewParams { - belong_to_id: view_rev.app_id.clone(), - name: format!("{} (copy)", &view_rev.name), - desc: view_rev.desc, - thumbnail: view_rev.thumbnail, - data_format: view_rev.data_format.into(), - layout: view_rev.layout.into(), - initial_data: view_data.to_vec(), - view_id: gen_view_id(), - ext: Default::default(), - }; - - let _ = self.create_view_from_params(duplicate_params).await?; - Ok(()) - } - - // belong_to_id will be the app_id or view_id. - #[tracing::instrument(level = "trace", skip(self), err)] - pub(crate) async fn read_views_belong_to( - &self, - belong_to_id: &str, - ) -> Result, FlowyError> { - self - .persistence - .begin_transaction(|transaction| { - read_belonging_views_on_local(belong_to_id, self.trash_controller.clone(), &transaction) - }) - .await - } - - #[tracing::instrument(level = "debug", skip(self, params), err)] - pub(crate) async fn update_view( - &self, - params: UpdateViewParams, - ) -> Result { - let changeset = ViewChangeset::new(params.clone()); - let view_id = changeset.id.clone(); - let view_rev = self - .persistence - .begin_transaction(|transaction| { - transaction.update_view(changeset)?; - let view_rev = transaction.read_view(&view_id)?; - let view: ViewPB = view_rev.clone().into(); - send_notification(&view_id, FolderNotification::DidUpdateView) - .payload(view) - .send(); - notify_views_changed( - &view_rev.app_id, - self.trash_controller.clone(), - &transaction, - )?; - Ok(view_rev) - }) - .await?; - - let _ = self.update_view_on_server(params); - Ok(view_rev) - } - - pub(crate) async fn latest_visit_view(&self) -> FlowyResult> { - match KV::get_str(LATEST_VIEW_ID) { - None => Ok(None), - Some(view_id) => { - let view_rev = self - .persistence - .begin_transaction(|transaction| transaction.read_view(&view_id)) - .await?; - Ok(Some(view_rev)) - }, - } - } -} - -impl ViewController { - #[tracing::instrument(level = "debug", skip(self), err)] - fn update_view_on_server(&self, params: UpdateViewParams) -> Result<(), FlowyError> { - let token = self.user.token()?; - let server = self.cloud_service.clone(); - tokio::spawn(async move { - match server.update_view(&token, params).await { - Ok(_) => {}, - Err(e) => { - // TODO: retry? - log::error!("Update view failed: {:?}", e); - }, - } - }); - Ok(()) - } - - fn listen_trash_can_event(&self) { - let mut rx = self.trash_controller.subscribe(); - let persistence = self.persistence.clone(); - let data_processors = self.data_processors.clone(); - let trash_controller = self.trash_controller.clone(); - let _ = tokio::spawn(async move { - loop { - let mut stream = Box::pin(rx.recv().into_stream().filter_map(|result| async move { - match result { - Ok(event) => event.select(TrashType::TrashView), - Err(_e) => None, - } - })); - - if let Some(event) = stream.next().await { - handle_trash_event( - persistence.clone(), - data_processors.clone(), - trash_controller.clone(), - event, - ) - .await - } - } - }); - } - - async fn get_data_processor_from_view_id( - &self, - view_id: &str, - ) -> FlowyResult> { - let view = self - .persistence - .begin_transaction(|transaction| transaction.read_view(view_id)) - .await?; - self.get_data_processor(view.data_format) - } - - #[inline] - fn get_data_processor>( - &self, - data_type: T, - ) -> FlowyResult> { - let data_type = data_type.into(); - match self.data_processors.get(&data_type) { - None => Err(FlowyError::internal().context(format!( - "Get data processor failed. Unknown view data type: {:?}", - data_type - ))), - Some(processor) => Ok(processor.clone()), - } - } -} - -#[tracing::instrument(level = "trace", skip(persistence, data_processors, trash_can))] -async fn handle_trash_event( - persistence: Arc, - data_processors: ViewDataProcessorMap, - trash_can: Arc, - event: TrashEvent, -) { - match event { - TrashEvent::NewTrash(identifiers, ret) => { - let result = persistence - .begin_transaction(|transaction| { - let view_revs = read_local_views_with_transaction(identifiers, &transaction)?; - for view_rev in view_revs { - notify_views_changed(&view_rev.app_id, trash_can.clone(), &transaction)?; - notify_dart(view_rev.into(), FolderNotification::DidDeleteView); - } - Ok(()) - }) - .await; - let _ = ret.send(result).await; - }, - TrashEvent::Putback(identifiers, ret) => { - let result = persistence - .begin_transaction(|transaction| { - let view_revs = read_local_views_with_transaction(identifiers, &transaction)?; - for view_rev in view_revs { - notify_views_changed(&view_rev.app_id, trash_can.clone(), &transaction)?; - notify_dart(view_rev.into(), FolderNotification::DidRestoreView); - } - Ok(()) - }) - .await; - let _ = ret.send(result).await; - }, - TrashEvent::Delete(identifiers, ret) => { - let result = || async { - let views = persistence - .begin_transaction(|transaction| { - let mut notify_ids = HashSet::new(); - let mut views = vec![]; - for identifier in identifiers.items { - if let Ok(view_rev) = transaction.delete_view(&identifier.id) { - notify_ids.insert(view_rev.app_id.clone()); - views.push(view_rev); - } - } - for notify_id in notify_ids { - notify_views_changed(¬ify_id, trash_can.clone(), &transaction)?; - } - Ok(views) - }) - .await?; - - for view in views { - let data_type = view.data_format.clone().into(); - match get_data_processor(data_processors.clone(), &data_type) { - Ok(processor) => { - processor.close_view(&view.id).await?; - }, - Err(e) => tracing::error!("{}", e), - } - } - Ok(()) - }; - let _ = ret.send(result().await).await; - }, - } -} - -fn get_data_processor( - data_processors: ViewDataProcessorMap, - data_type: &ViewDataFormatPB, -) -> FlowyResult> { - match data_processors.get(data_type) { - None => Err(FlowyError::internal().context(format!( - "Get data processor failed. Unknown view data type: {:?}", - data_type - ))), - Some(processor) => Ok(processor.clone()), - } -} - -fn read_local_views_with_transaction<'a>( - identifiers: RepeatedTrashIdPB, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), -) -> Result, FlowyError> { - let mut view_revs = vec![]; - for identifier in identifiers.items { - view_revs.push(transaction.read_view(&identifier.id)?); - } - Ok(view_revs) -} - -fn notify_dart(view: ViewPB, notification: FolderNotification) { - send_notification(&view.id, notification) - .payload(view) - .send(); -} - -#[tracing::instrument( - level = "debug", - skip(belong_to_id, trash_controller, transaction), - fields(view_count), - err -)] -fn notify_views_changed<'a>( - belong_to_id: &str, - trash_controller: Arc, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), -) -> FlowyResult<()> { - let mut app_rev = transaction.read_app(belong_to_id)?; - let trash_ids = trash_controller.read_trash_ids(transaction)?; - app_rev - .belongings - .retain(|view| !trash_ids.contains(&view.id)); - let app: AppPB = app_rev.into(); - - send_notification(belong_to_id, FolderNotification::DidUpdateApp) - .payload(app) - .send(); - - Ok(()) -} - -fn read_belonging_views_on_local<'a>( - belong_to_id: &str, - trash_controller: Arc, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), -) -> FlowyResult> { - let mut view_revs = transaction.read_views(belong_to_id)?; - let trash_ids = trash_controller.read_trash_ids(transaction)?; - view_revs.retain(|view_table| !trash_ids.contains(&view_table.id)); - - Ok(view_revs) -} diff --git a/frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs deleted file mode 100644 index 1152275745..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::entities::view::{MoveFolderItemParams, MoveFolderItemPayloadPB, MoveFolderItemType}; -use crate::manager::FolderManager; -use crate::services::{notify_workspace_setting_did_change, AppController}; -use crate::{ - entities::{ - trash::TrashPB, - view::{ - CreateViewParams, CreateViewPayloadPB, RepeatedViewIdPB, UpdateViewParams, - UpdateViewPayloadPB, ViewIdPB, ViewPB, - }, - }, - errors::FlowyError, - services::{TrashController, ViewController}, -}; -use folder_model::TrashRevision; -use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult}; -use std::{convert::TryInto, sync::Arc}; - -pub(crate) async fn create_view_handler( - data: AFPluginData, - controller: AFPluginState>, -) -> DataResult { - let params: CreateViewParams = data.into_inner().try_into()?; - let view_rev = controller.create_view_from_params(params).await?; - data_result_ok(view_rev.into()) -} - -pub(crate) async fn read_view_handler( - data: AFPluginData, - controller: AFPluginState>, -) -> DataResult { - let view_id: ViewIdPB = data.into_inner(); - let view_rev = controller.read_view(&view_id.value).await?; - data_result_ok(view_rev.into()) -} - -#[tracing::instrument(level = "debug", skip(data, controller), err)] -pub(crate) async fn update_view_handler( - data: AFPluginData, - controller: AFPluginState>, -) -> Result<(), FlowyError> { - let params: UpdateViewParams = data.into_inner().try_into()?; - let _ = controller.update_view(params).await?; - - Ok(()) -} - -pub(crate) async fn delete_view_handler( - data: AFPluginData, - view_controller: AFPluginState>, - trash_controller: AFPluginState>, -) -> Result<(), FlowyError> { - let params: RepeatedViewIdPB = data.into_inner(); - for view_id in ¶ms.items { - let _ = view_controller.move_view_to_trash(view_id).await; - } - - let trash = view_controller - .read_local_views(params.items) - .await? - .into_iter() - .map(|view| { - let trash_rev: TrashRevision = view.into(); - trash_rev.into() - }) - .collect::>(); - - trash_controller.add(trash).await?; - Ok(()) -} - -pub(crate) async fn set_latest_view_handler( - data: AFPluginData, - folder: AFPluginState>, - controller: AFPluginState>, -) -> Result<(), FlowyError> { - let view_id: ViewIdPB = data.into_inner(); - controller.set_latest_view(&view_id.value)?; - notify_workspace_setting_did_change(&folder, &view_id).await?; - Ok(()) -} - -pub(crate) async fn close_view_handler( - data: AFPluginData, - controller: AFPluginState>, -) -> Result<(), FlowyError> { - let view_id: ViewIdPB = data.into_inner(); - controller.close_view(&view_id.value).await?; - Ok(()) -} - -#[tracing::instrument(level = "debug", skip_all, err)] -pub(crate) async fn move_item_handler( - data: AFPluginData, - view_controller: AFPluginState>, - app_controller: AFPluginState>, -) -> Result<(), FlowyError> { - let params: MoveFolderItemParams = data.into_inner().try_into()?; - match params.ty { - MoveFolderItemType::MoveApp => { - app_controller - .move_app(¶ms.item_id, params.from, params.to) - .await?; - }, - MoveFolderItemType::MoveView => { - view_controller - .move_view(¶ms.item_id, params.from, params.to) - .await?; - }, - } - Ok(()) -} - -#[tracing::instrument(level = "debug", skip(data, controller), err)] -pub(crate) async fn duplicate_view_handler( - data: AFPluginData, - controller: AFPluginState>, -) -> Result<(), FlowyError> { - let view: ViewPB = data.into_inner(); - controller.duplicate_view(view).await?; - Ok(()) -} diff --git a/frontend/rust-lib/flowy-folder/src/services/view/mod.rs b/frontend/rust-lib/flowy-folder/src/services/view/mod.rs deleted file mode 100644 index e854517f17..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/view/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod controller; -pub mod event_handler; diff --git a/frontend/rust-lib/flowy-folder/src/services/web_socket.rs b/frontend/rust-lib/flowy-folder/src/services/web_socket.rs deleted file mode 100644 index 0337fa0a57..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/web_socket.rs +++ /dev/null @@ -1,165 +0,0 @@ -use crate::services::FOLDER_SYNC_INTERVAL_IN_MILLIS; -use bytes::Bytes; -use flowy_client_sync::client_folder::{FolderOperations, FolderPad}; -use flowy_client_sync::make_operations_from_revisions; -use flowy_error::{FlowyError, FlowyResult}; -use flowy_revision::*; -use flowy_sqlite::ConnectionPool; -use lib_infra::future::{BoxResultFuture, FutureResult}; -use lib_ot::core::OperationTransform; -use parking_lot::RwLock; -use revision_model::{Revision, RevisionRange}; -use std::{sync::Arc, time::Duration}; -use ws_model::ws_revision::{ClientRevisionWSData, NewDocumentUser}; - -#[derive(Clone)] -pub struct FolderResolveOperations(pub FolderOperations); -impl OperationsDeserializer for FolderResolveOperations { - fn deserialize_revisions(revisions: Vec) -> FlowyResult { - Ok(FolderResolveOperations(make_operations_from_revisions( - revisions, - )?)) - } -} - -impl OperationsSerializer for FolderResolveOperations { - fn serialize_operations(&self) -> Bytes { - self.0.json_bytes() - } -} - -impl FolderResolveOperations { - pub fn into_inner(self) -> FolderOperations { - self.0 - } -} - -pub type FolderConflictController = - ConflictController>; - -#[allow(dead_code)] -pub(crate) async fn make_folder_ws_manager( - folder_id: &str, - rev_manager: Arc>>, - web_socket: Arc, - folder_pad: Arc>, -) -> Arc { - let ws_data_provider = Arc::new(WSDataProvider::new( - folder_id, - Arc::new(rev_manager.clone()), - )); - let resolver = Arc::new(FolderConflictResolver { folder_pad }); - let conflict_controller = - FolderConflictController::new(resolver, Arc::new(ws_data_provider.clone()), rev_manager); - let ws_data_stream = Arc::new(FolderRevisionWSDataStream::new(conflict_controller)); - let ws_data_sink = Arc::new(FolderWSDataSink(ws_data_provider)); - let ping_duration = Duration::from_millis(FOLDER_SYNC_INTERVAL_IN_MILLIS); - Arc::new(RevisionWebSocketManager::new( - "Folder", - folder_id, - web_socket, - ws_data_sink, - ws_data_stream, - ping_duration, - )) -} - -pub(crate) struct FolderWSDataSink(Arc); -impl RevisionWebSocketSink for FolderWSDataSink { - fn next(&self) -> FutureResult, FlowyError> { - let sink_provider = self.0.clone(); - FutureResult::new(async move { sink_provider.next().await }) - } -} - -struct FolderConflictResolver { - folder_pad: Arc>, -} - -impl ConflictResolver for FolderConflictResolver { - fn compose_operations( - &self, - operations: FolderResolveOperations, - ) -> BoxResultFuture { - let operations = operations.into_inner(); - let folder_pad = self.folder_pad.clone(); - Box::pin(async move { - let md5 = folder_pad.write().compose_remote_operations(operations)?; - Ok(md5.into()) - }) - } - - fn transform_operations( - &self, - operations: FolderResolveOperations, - ) -> BoxResultFuture, FlowyError> { - let folder_pad = self.folder_pad.clone(); - let operations = operations.into_inner(); - Box::pin(async move { - let read_guard = folder_pad.read(); - let mut server_operations: Option = None; - let client_operations: FolderResolveOperations; - if read_guard.is_empty() { - // Do nothing - client_operations = FolderResolveOperations(operations); - } else { - let (s_prime, c_prime) = read_guard.get_operations().transform(&operations)?; - client_operations = FolderResolveOperations(c_prime); - server_operations = Some(FolderResolveOperations(s_prime)); - } - drop(read_guard); - Ok(TransformOperations { - client_operations, - server_operations, - }) - }) - } - - fn reset_operations( - &self, - operations: FolderResolveOperations, - ) -> BoxResultFuture { - let folder_pad = self.folder_pad.clone(); - Box::pin(async move { - let md5 = folder_pad.write().reset_folder(operations.into_inner())?; - Ok(md5.into()) - }) - } -} - -struct FolderRevisionWSDataStream { - conflict_controller: Arc, -} - -impl FolderRevisionWSDataStream { - pub fn new(conflict_controller: FolderConflictController) -> Self { - Self { - conflict_controller: Arc::new(conflict_controller), - } - } -} - -impl RevisionWSDataStream for FolderRevisionWSDataStream { - fn receive_push_revision(&self, revisions: Vec) -> BoxResultFuture<(), FlowyError> { - let resolver = self.conflict_controller.clone(); - Box::pin(async move { resolver.receive_revisions(revisions).await }) - } - - fn receive_ack(&self, rev_id: i64) -> BoxResultFuture<(), FlowyError> { - let resolver = self.conflict_controller.clone(); - Box::pin(async move { resolver.ack_revision(rev_id).await }) - } - - fn receive_new_user_connect( - &self, - _new_user: NewDocumentUser, - ) -> BoxResultFuture<(), FlowyError> { - // Do nothing by now, just a placeholder for future extension. - Box::pin(async move { Ok(()) }) - } - - fn pull_revisions_in_range(&self, range: RevisionRange) -> BoxResultFuture<(), FlowyError> { - let resolver = self.conflict_controller.clone(); - Box::pin(async move { resolver.send_revisions(range).await }) - } -} diff --git a/frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs b/frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs deleted file mode 100644 index 733adfb349..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs +++ /dev/null @@ -1,280 +0,0 @@ -use crate::entities::workspace::*; -use crate::manager::FolderManager; -use crate::{ - errors::*, - event_map::{FolderCouldServiceV1, WorkspaceUser}, - notification::*, - services::{ - persistence::{FolderPersistence, FolderPersistenceTransaction, WorkspaceChangeset}, - read_workspace_apps, TrashController, - }, -}; -use flowy_sqlite::kv::KV; -use folder_model::{AppRevision, WorkspaceRevision}; -use lib_dispatch::prelude::ToBytes; -use std::sync::Arc; - -pub struct WorkspaceController { - pub user: Arc, - persistence: Arc, - pub(crate) trash_controller: Arc, - cloud_service: Arc, -} - -impl WorkspaceController { - pub(crate) fn new( - user: Arc, - persistence: Arc, - trash_can: Arc, - cloud_service: Arc, - ) -> Self { - Self { - user, - persistence, - trash_controller: trash_can, - cloud_service, - } - } - - pub(crate) async fn create_workspace_from_params( - &self, - params: CreateWorkspaceParams, - ) -> Result { - let workspace = self.create_workspace_on_server(params.clone()).await?; - let user_id = self.user.user_id()?; - let workspaces = self - .persistence - .begin_transaction(|transaction| { - transaction.create_workspace(&user_id, workspace.clone())?; - transaction.read_workspaces(&user_id, None) - }) - .await? - .into_iter() - .map(|workspace_rev| workspace_rev.into()) - .collect(); - let repeated_workspace = RepeatedWorkspacePB { items: workspaces }; - send_workspace_notification(FolderNotification::DidCreateWorkspace, repeated_workspace); - set_current_workspace(&user_id, &workspace.id); - Ok(workspace) - } - - #[allow(dead_code)] - pub(crate) async fn update_workspace( - &self, - params: UpdateWorkspaceParams, - ) -> Result<(), FlowyError> { - let changeset = WorkspaceChangeset::new(params.clone()); - let workspace_id = changeset.id.clone(); - let workspace = self - .persistence - .begin_transaction(|transaction| { - transaction.update_workspace(changeset)?; - let user_id = self.user.user_id()?; - self.read_workspace(workspace_id.clone(), &user_id, &transaction) - }) - .await?; - - send_workspace_notification(FolderNotification::DidUpdateWorkspace, workspace); - self.update_workspace_on_server(params)?; - - Ok(()) - } - - #[allow(dead_code)] - pub(crate) async fn delete_workspace(&self, workspace_id: &str) -> Result<(), FlowyError> { - let user_id = self.user.user_id()?; - let repeated_workspace = self - .persistence - .begin_transaction(|transaction| { - transaction.delete_workspace(workspace_id)?; - self.read_workspaces(None, &user_id, &transaction) - }) - .await?; - - send_workspace_notification(FolderNotification::DidDeleteWorkspace, repeated_workspace); - self.delete_workspace_on_server(workspace_id)?; - Ok(()) - } - - pub(crate) async fn open_workspace( - &self, - params: WorkspaceIdPB, - ) -> Result { - let user_id = self.user.user_id()?; - if let Some(workspace_id) = params.value { - let workspace = self - .persistence - .begin_transaction(|transaction| self.read_workspace(workspace_id, &user_id, &transaction)) - .await?; - set_current_workspace(&user_id, &workspace.id); - Ok(workspace) - } else { - Err(FlowyError::workspace_id().context("Opened workspace id should not be empty")) - } - } - - pub(crate) async fn read_current_workspace_apps(&self) -> Result, FlowyError> { - let user_id = self.user.user_id()?; - let workspace_id = get_current_workspace(&user_id)?; - let app_revs = self - .persistence - .begin_transaction(|transaction| { - read_workspace_apps(&workspace_id, self.trash_controller.clone(), &transaction) - }) - .await?; - // TODO: read from server - Ok(app_revs) - } - - #[tracing::instrument(level = "debug", skip(self, transaction), err)] - pub(crate) fn read_workspaces<'a>( - &self, - workspace_id: Option, - user_id: &str, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), - ) -> Result { - let workspace_id = workspace_id.to_owned(); - let trash_ids = self.trash_controller.read_trash_ids(transaction)?; - let workspaces = transaction - .read_workspaces(user_id, workspace_id)? - .into_iter() - .map(|mut workspace_rev| { - workspace_rev - .apps - .retain(|app_rev| !trash_ids.contains(&app_rev.id)); - workspace_rev.into() - }) - .collect(); - Ok(RepeatedWorkspacePB { items: workspaces }) - } - - pub(crate) fn read_workspace<'a>( - &self, - workspace_id: String, - user_id: &str, - transaction: &'a (dyn FolderPersistenceTransaction + 'a), - ) -> Result { - let mut workspaces = self - .read_workspaces(Some(workspace_id.clone()), user_id, transaction)? - .items; - if workspaces.is_empty() { - return Err( - FlowyError::record_not_found().context(format!("{} workspace not found", workspace_id)), - ); - } - debug_assert_eq!(workspaces.len(), 1); - let workspace = workspaces - .drain(..1) - .collect::>() - .pop() - .unwrap(); - Ok(workspace) - } -} - -impl WorkspaceController { - async fn create_workspace_on_server( - &self, - params: CreateWorkspaceParams, - ) -> Result { - let token = self.user.token()?; - self.cloud_service.create_workspace(&token, params).await - } - - fn update_workspace_on_server(&self, params: UpdateWorkspaceParams) -> Result<(), FlowyError> { - let (token, server) = (self.user.token()?, self.cloud_service.clone()); - tokio::spawn(async move { - match server.update_workspace(&token, params).await { - Ok(_) => {}, - Err(e) => { - // TODO: retry? - log::error!("Update workspace failed: {:?}", e); - }, - } - }); - Ok(()) - } - - fn delete_workspace_on_server(&self, workspace_id: &str) -> Result<(), FlowyError> { - let params = WorkspaceIdPB { - value: Some(workspace_id.to_string()), - }; - let (token, server) = (self.user.token()?, self.cloud_service.clone()); - tokio::spawn(async move { - match server.delete_workspace(&token, params).await { - Ok(_) => {}, - Err(e) => { - // TODO: retry? - log::error!("Delete workspace failed: {:?}", e); - }, - } - }); - Ok(()) - } -} - -pub async fn notify_workspace_setting_did_change( - folder_manager: &Arc, - view_id: &str, -) -> FlowyResult<()> { - let user_id = folder_manager.user.user_id()?; - let workspace_id = get_current_workspace(&user_id)?; - - let workspace_setting = folder_manager - .persistence - .begin_transaction(|transaction| { - let workspace = folder_manager.workspace_controller.read_workspace( - workspace_id.clone(), - &user_id, - &transaction, - )?; - - let setting = match transaction.read_view(view_id) { - Ok(latest_view) => WorkspaceSettingPB { - workspace, - latest_view: Some(latest_view.into()), - }, - Err(_) => WorkspaceSettingPB { - workspace, - latest_view: None, - }, - }; - - Ok(setting) - }) - .await?; - send_workspace_notification( - FolderNotification::DidUpdateWorkspaceSetting, - workspace_setting, - ); - Ok(()) -} - -/// The [CURRENT_WORKSPACE] represents as the current workspace that opened by the -/// user. Only one workspace can be opened at a time. -const CURRENT_WORKSPACE: &str = "current-workspace"; -fn send_workspace_notification(ty: FolderNotification, payload: T) { - send_notification(CURRENT_WORKSPACE, ty) - .payload(payload) - .send(); -} - -const CURRENT_WORKSPACE_ID: &str = "current_workspace_id"; - -pub fn set_current_workspace(_user_id: &str, workspace_id: &str) { - KV::set_str(CURRENT_WORKSPACE_ID, workspace_id.to_owned()); -} - -pub fn clear_current_workspace(_user_id: &str) { - let _ = KV::remove(CURRENT_WORKSPACE_ID); -} - -pub fn get_current_workspace(_user_id: &str) -> Result { - match KV::get_str(CURRENT_WORKSPACE_ID) { - None => Err( - FlowyError::record_not_found() - .context("Current workspace not found or should call open workspace first"), - ), - Some(workspace_id) => Ok(workspace_id), - } -} diff --git a/frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs deleted file mode 100644 index d15f86b1be..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::entities::{ - app::RepeatedAppPB, - view::ViewPB, - workspace::{RepeatedWorkspacePB, WorkspaceIdPB, WorkspaceSettingPB, *}, -}; -use crate::{ - errors::FlowyError, - manager::FolderManager, - services::{get_current_workspace, read_workspace_apps, WorkspaceController}, -}; -use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult}; -use std::{convert::TryInto, sync::Arc}; - -#[tracing::instrument(level = "debug", skip(data, controller), err)] -pub(crate) async fn create_workspace_handler( - data: AFPluginData, - controller: AFPluginState>, -) -> DataResult { - let controller = controller.get_ref().clone(); - let params: CreateWorkspaceParams = data.into_inner().try_into()?; - let workspace_rev = controller.create_workspace_from_params(params).await?; - data_result_ok(workspace_rev.into()) -} - -#[tracing::instrument(level = "debug", skip(controller), err)] -pub(crate) async fn read_workspace_apps_handler( - controller: AFPluginState>, -) -> DataResult { - let items = controller - .read_current_workspace_apps() - .await? - .into_iter() - .map(|app_rev| app_rev.into()) - .collect(); - let repeated_app = RepeatedAppPB { items }; - data_result_ok(repeated_app) -} - -#[tracing::instrument(level = "debug", skip(data, controller), err)] -pub(crate) async fn open_workspace_handler( - data: AFPluginData, - controller: AFPluginState>, -) -> DataResult { - let params: WorkspaceIdPB = data.into_inner(); - let workspaces = controller.open_workspace(params).await?; - data_result_ok(workspaces) -} - -#[tracing::instrument(level = "debug", skip(data, folder), err)] -pub(crate) async fn read_workspaces_handler( - data: AFPluginData, - folder: AFPluginState>, -) -> DataResult { - let params: WorkspaceIdPB = data.into_inner(); - let user_id = folder.user.user_id()?; - let workspace_controller = folder.workspace_controller.clone(); - - let trash_controller = folder.trash_controller.clone(); - let workspaces = folder - .persistence - .begin_transaction(|transaction| { - let mut workspaces = - workspace_controller.read_workspaces(params.value.clone(), &user_id, &transaction)?; - for workspace in workspaces.iter_mut() { - let apps = read_workspace_apps(&workspace.id, trash_controller.clone(), &transaction)? - .into_iter() - .map(|app_rev| app_rev.into()) - .collect(); - workspace.apps.items = apps; - } - Ok(workspaces) - }) - .await?; - data_result_ok(workspaces) -} - -#[tracing::instrument(level = "debug", skip(folder), err)] -pub async fn read_cur_workspace_handler( - folder: AFPluginState>, -) -> DataResult { - let user_id = folder.user.user_id()?; - let workspace_id = get_current_workspace(&user_id)?; - let workspace = folder - .persistence - .begin_transaction(|transaction| { - folder - .workspace_controller - .read_workspace(workspace_id, &user_id, &transaction) - }) - .await?; - - let latest_view: Option = folder - .view_controller - .latest_visit_view() - .await - .unwrap_or(None) - .map(|view_rev| view_rev.into()); - let setting = WorkspaceSettingPB { - workspace, - latest_view, - }; - data_result_ok(setting) -} diff --git a/frontend/rust-lib/flowy-folder/src/services/workspace/mod.rs b/frontend/rust-lib/flowy-folder/src/services/workspace/mod.rs deleted file mode 100644 index e854517f17..0000000000 --- a/frontend/rust-lib/flowy-folder/src/services/workspace/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod controller; -pub mod event_handler; diff --git a/frontend/rust-lib/flowy-folder/src/test_helper.rs b/frontend/rust-lib/flowy-folder/src/test_helper.rs deleted file mode 100644 index 5b9970ef3e..0000000000 --- a/frontend/rust-lib/flowy-folder/src/test_helper.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::entities::{data_format_from_layout, CreateViewParams, ViewLayoutTypePB}; -use crate::manager::FolderManager; -use crate::services::folder_editor::FolderEditor; -use folder_model::gen_view_id; -use std::collections::HashMap; -use std::sync::Arc; - -#[cfg(feature = "flowy_unit_test")] -impl FolderManager { - pub async fn folder_editor(&self) -> Arc { - self.folder_editor.read().await.clone().unwrap() - } - - pub async fn create_test_grid_view( - &self, - app_id: &str, - name: &str, - ext: HashMap, - ) -> String { - self - .create_test_view(app_id, name, ViewLayoutTypePB::Grid, ext) - .await - } - - pub async fn create_test_board_view( - &self, - app_id: &str, - name: &str, - ext: HashMap, - ) -> String { - self - .create_test_view(app_id, name, ViewLayoutTypePB::Board, ext) - .await - } - - async fn create_test_view( - &self, - app_id: &str, - name: &str, - layout: ViewLayoutTypePB, - ext: HashMap, - ) -> String { - let view_id = gen_view_id(); - let data_format = data_format_from_layout(&layout); - let params = CreateViewParams { - belong_to_id: app_id.to_string(), - name: name.to_string(), - desc: "".to_string(), - thumbnail: "".to_string(), - data_format, - layout, - view_id: view_id.clone(), - initial_data: vec![], - ext, - }; - self - .view_controller - .create_view_from_params(params) - .await - .unwrap(); - view_id - } -} diff --git a/frontend/rust-lib/flowy-folder/src/util.rs b/frontend/rust-lib/flowy-folder/src/util.rs deleted file mode 100644 index 62439713b3..0000000000 --- a/frontend/rust-lib/flowy-folder/src/util.rs +++ /dev/null @@ -1,80 +0,0 @@ -#![allow(clippy::type_complexity)] -use crate::event_map::{FolderCouldServiceV1, WorkspaceUser}; -use lib_infra::retry::Action; -use pin_project::pin_project; -use std::{ - future::Future, - marker::PhantomData, - pin::Pin, - sync::Arc, - task::{Context, Poll}, -}; - -pub(crate) type Builder = - Box) -> Fut + Send + Sync>; - -#[allow(dead_code)] -pub(crate) struct RetryAction { - token: String, - cloud_service: Arc, - user: Arc, - builder: Builder, - phantom: PhantomData<(T, E)>, -} - -impl RetryAction { - #[allow(dead_code)] - pub(crate) fn new( - cloud_service: Arc, - user: Arc, - builder: F, - ) -> Self - where - Fut: Future> + Send + Sync + 'static, - F: Fn(String, Arc) -> Fut + Send + Sync + 'static, - { - let token = user.token().unwrap_or_else(|_| "".to_owned()); - Self { - token, - cloud_service, - user, - builder: Box::new(builder), - phantom: PhantomData, - } - } -} - -impl Action for RetryAction -where - Fut: Future> + Send + Sync + 'static, - T: Send + Sync + 'static, - E: Send + Sync + 'static, -{ - type Future = Pin> + Send + Sync>>; - type Item = T; - type Error = E; - - fn run(&mut self) -> Self::Future { - let fut = (self.builder)(self.token.clone(), self.cloud_service.clone()); - Box::pin(RetryActionFut { fut: Box::pin(fut) }) - } -} - -#[pin_project] -struct RetryActionFut { - #[pin] - fut: Pin> + Send + Sync>>, -} - -impl Future for RetryActionFut -where - T: Send + Sync + 'static, - E: Send + Sync + 'static, -{ - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut this = self.project(); - this.fut.as_mut().poll(cx) - } -} diff --git a/frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs b/frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs deleted file mode 100644 index 15588b5c55..0000000000 --- a/frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs +++ /dev/null @@ -1,366 +0,0 @@ -use crate::script::{invalid_workspace_name_test_case, FolderScript::*, FolderTest}; -use flowy_folder::entities::view::ViewDataFormatPB; -use flowy_folder::entities::workspace::CreateWorkspacePayloadPB; -use flowy_revision_persistence::RevisionState; -use flowy_test::{event_builder::*, FlowySDKTest}; - -#[tokio::test] -async fn workspace_read_all() { - let mut test = FolderTest::new().await; - test.run_scripts(vec![ReadAllWorkspaces]).await; - assert!(!test.all_workspace.is_empty()); -} - -#[tokio::test] -async fn workspace_create() { - let mut test = FolderTest::new().await; - let name = "My new workspace".to_owned(); - let desc = "Daily routines".to_owned(); - test - .run_scripts(vec![CreateWorkspace { - name: name.clone(), - desc: desc.clone(), - }]) - .await; - - let workspace = test.workspace.clone(); - assert_eq!(workspace.name, name); - assert_eq!(workspace.desc, desc); - - test - .run_scripts(vec![ - ReadWorkspace(Some(workspace.id.clone())), - AssertWorkspace(workspace), - ]) - .await; -} - -#[tokio::test] -async fn workspace_read() { - let mut test = FolderTest::new().await; - let workspace = test.workspace.clone(); - - test - .run_scripts(vec![ - ReadWorkspace(Some(workspace.id.clone())), - AssertWorkspace(workspace), - ]) - .await; -} - -#[tokio::test] -async fn workspace_create_with_apps() { - let mut test = FolderTest::new().await; - test - .run_scripts(vec![CreateApp { - name: "App".to_string(), - desc: "App description".to_string(), - }]) - .await; - - let app = test.app.clone(); - test.run_scripts(vec![ReadApp(app.id)]).await; -} - -#[tokio::test] -async fn workspace_create_with_invalid_name() { - for (name, code) in invalid_workspace_name_test_case() { - let sdk = FlowySDKTest::default(); - let request = CreateWorkspacePayloadPB { - name, - desc: "".to_owned(), - }; - assert_eq!( - FolderEventBuilder::new(sdk) - .event(flowy_folder::event_map::FolderEvent::CreateWorkspace) - .payload(request) - .async_send() - .await - .error() - .code, - code.value() - ) - } -} - -#[tokio::test] -#[should_panic] -async fn app_delete() { - let mut test = FolderTest::new().await; - let app = test.app.clone(); - test.run_scripts(vec![DeleteApp, ReadApp(app.id)]).await; -} - -#[tokio::test] -async fn app_delete_then_restore() { - let mut test = FolderTest::new().await; - let app = test.app.clone(); - test - .run_scripts(vec![ - DeleteApp, - RestoreAppFromTrash, - ReadApp(app.id.clone()), - AssertApp(app), - ]) - .await; -} - -#[tokio::test] -async fn app_read() { - let mut test = FolderTest::new().await; - let app = test.app.clone(); - test - .run_scripts(vec![ReadApp(app.id.clone()), AssertApp(app)]) - .await; -} - -#[tokio::test] -async fn app_update() { - let mut test = FolderTest::new().await; - let app = test.app.clone(); - let new_name = "😁 hell world".to_owned(); - assert_ne!(app.name, new_name); - - test - .run_scripts(vec![ - UpdateApp { - name: Some(new_name.clone()), - desc: None, - }, - ReadApp(app.id), - ]) - .await; - assert_eq!(test.app.name, new_name); -} - -#[tokio::test] -async fn app_create_with_view() { - let mut test = FolderTest::new().await; - let mut app = test.app.clone(); - test - .run_scripts(vec![ - CreateView { - name: "View A".to_owned(), - desc: "View A description".to_owned(), - data_type: ViewDataFormatPB::DeltaFormat, - }, - CreateView { - name: "Grid".to_owned(), - desc: "Grid description".to_owned(), - data_type: ViewDataFormatPB::DatabaseFormat, - }, - ReadApp(app.id), - ]) - .await; - - app = test.app.clone(); - assert_eq!(app.belongings.len(), 3); - assert_eq!(app.belongings[1].name, "View A"); - assert_eq!(app.belongings[2].name, "Grid") -} - -#[tokio::test] -async fn view_update() { - let mut test = FolderTest::new().await; - let view = test.view.clone(); - let new_name = "😁 123".to_owned(); - assert_ne!(view.name, new_name); - - test - .run_scripts(vec![ - UpdateView { - name: Some(new_name.clone()), - desc: None, - }, - ReadView(view.id), - ]) - .await; - assert_eq!(test.view.name, new_name); -} - -#[tokio::test] -#[should_panic] -async fn view_delete() { - let mut test = FolderTest::new().await; - let view = test.view.clone(); - test.run_scripts(vec![DeleteView, ReadView(view.id)]).await; -} - -#[tokio::test] -async fn view_delete_then_restore() { - let mut test = FolderTest::new().await; - let view = test.view.clone(); - test - .run_scripts(vec![ - DeleteView, - RestoreViewFromTrash, - ReadView(view.id.clone()), - AssertView(view), - ]) - .await; -} - -#[tokio::test] -async fn view_delete_all() { - let mut test = FolderTest::new().await; - let app = test.app.clone(); - test - .run_scripts(vec![ - CreateView { - name: "View A".to_owned(), - desc: "View A description".to_owned(), - data_type: ViewDataFormatPB::DeltaFormat, - }, - CreateView { - name: "Grid".to_owned(), - desc: "Grid description".to_owned(), - data_type: ViewDataFormatPB::DatabaseFormat, - }, - ReadApp(app.id.clone()), - ]) - .await; - - assert_eq!(test.app.belongings.len(), 3); - let view_ids = test - .app - .belongings - .iter() - .map(|view| view.id.clone()) - .collect::>(); - test - .run_scripts(vec![DeleteViews(view_ids), ReadApp(app.id), ReadTrash]) - .await; - - assert_eq!(test.app.belongings.len(), 0); - assert_eq!(test.trash.len(), 3); -} - -#[tokio::test] -async fn view_delete_all_permanent() { - let mut test = FolderTest::new().await; - let app = test.app.clone(); - test - .run_scripts(vec![ - CreateView { - name: "View A".to_owned(), - desc: "View A description".to_owned(), - data_type: ViewDataFormatPB::DeltaFormat, - }, - ReadApp(app.id.clone()), - ]) - .await; - - let view_ids = test - .app - .belongings - .iter() - .map(|view| view.id.clone()) - .collect::>(); - test - .run_scripts(vec![ - DeleteViews(view_ids), - ReadApp(app.id), - DeleteAllTrash, - ReadTrash, - ]) - .await; - - assert_eq!(test.app.belongings.len(), 0); - assert_eq!(test.trash.len(), 0); -} - -#[tokio::test] -async fn folder_sync_revision_state() { - let mut test = FolderTest::new().await; - test - .run_scripts(vec![ - AssertRevisionState { - rev_id: 1, - state: RevisionState::Sync, - }, - AssertNextSyncRevId(Some(1)), - AssertRevisionState { - rev_id: 1, - state: RevisionState::Ack, - }, - ]) - .await; -} - -#[tokio::test] -async fn folder_sync_revision_seq() { - let mut test = FolderTest::new().await; - test - .run_scripts(vec![ - AssertRevisionState { - rev_id: 1, - state: RevisionState::Sync, - }, - AssertRevisionState { - rev_id: 2, - state: RevisionState::Sync, - }, - AssertNextSyncRevId(Some(1)), - AssertNextSyncRevId(Some(2)), - AssertRevisionState { - rev_id: 1, - state: RevisionState::Ack, - }, - AssertRevisionState { - rev_id: 2, - state: RevisionState::Ack, - }, - ]) - .await; -} - -// #[tokio::test] -// async fn folder_sync_revision_with_new_app() { -// let mut test = FolderTest::new().await; -// let app_name = "AppFlowy contributors".to_owned(); -// let app_desc = "Welcome to be a AppFlowy contributor".to_owned(); -// -// test.run_scripts(vec![ -// AssertNextSyncRevId(Some(1)), -// AssertNextSyncRevId(Some(2)), -// CreateApp { -// name: app_name.clone(), -// desc: app_desc.clone(), -// }, -// AssertCurrentRevId(3), -// AssertNextSyncRevId(Some(3)), -// AssertNextSyncRevId(None), -// ]) -// .await; -// -// let app = test.app.clone(); -// assert_eq!(app.name, app_name); -// assert_eq!(app.desc, app_desc); -// test.run_scripts(vec![ReadApp(app.id.clone()), AssertApp(app)]).await; -// } - -// #[tokio::test] -// async fn folder_sync_revision_with_new_view() { -// let mut test = FolderTest::new().await; -// let view_name = "AppFlowy features".to_owned(); -// let view_desc = "😁".to_owned(); -// -// test.run_scripts(vec![ -// AssertNextSyncRevId(Some(1)), -// AssertNextSyncRevId(Some(2)), -// CreateView { -// name: view_name.clone(), -// desc: view_desc.clone(), -// data_type: ViewDataFormatPB::DeltaFormat, -// }, -// AssertCurrentRevId(3), -// AssertNextSyncRevId(Some(3)), -// AssertNextSyncRevId(None), -// ]) -// .await; -// -// let view = test.view.clone(); -// assert_eq!(view.name, view_name); -// test.run_scripts(vec![ReadView(view.id.clone()), AssertView(view)]) -// .await; -// } diff --git a/frontend/rust-lib/flowy-folder/tests/workspace/main.rs b/frontend/rust-lib/flowy-folder/tests/workspace/main.rs deleted file mode 100644 index 7a47dda24d..0000000000 --- a/frontend/rust-lib/flowy-folder/tests/workspace/main.rs +++ /dev/null @@ -1,2 +0,0 @@ -// mod folder_test; -// mod script; diff --git a/frontend/rust-lib/flowy-folder/tests/workspace/script.rs b/frontend/rust-lib/flowy-folder/tests/workspace/script.rs deleted file mode 100644 index d7067cd21a..0000000000 --- a/frontend/rust-lib/flowy-folder/tests/workspace/script.rs +++ /dev/null @@ -1,468 +0,0 @@ -use flowy_folder::entities::view::{RepeatedViewIdPB, ViewIdPB}; -use flowy_folder::entities::workspace::WorkspaceIdPB; -use flowy_folder::entities::{ - app::{AppIdPB, CreateAppPayloadPB, UpdateAppPayloadPB}, - trash::{RepeatedTrashPB, TrashIdPB, TrashType}, - view::{CreateViewPayloadPB, UpdateViewPayloadPB}, - workspace::{CreateWorkspacePayloadPB, RepeatedWorkspacePB}, - ViewLayoutTypePB, -}; -use flowy_folder::entities::{ - app::{AppPB, RepeatedAppPB}, - trash::TrashPB, - view::{RepeatedViewPB, ViewDataFormatPB, ViewPB}, - workspace::WorkspacePB, -}; -use flowy_folder::event_map::FolderEvent::*; -use flowy_folder::{errors::ErrorCode, services::folder_editor::FolderEditor}; -use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS; -use flowy_revision_persistence::RevisionState; -use flowy_test::{event_builder::*, FlowySDKTest}; -use std::{sync::Arc, time::Duration}; -use tokio::time::sleep; - -pub enum FolderScript { - // Workspace - ReadAllWorkspaces, - CreateWorkspace { - name: String, - desc: String, - }, - // AssertWorkspaceRevisionJson(String), - AssertWorkspace(WorkspacePB), - ReadWorkspace(Option), - - // App - CreateApp { - name: String, - desc: String, - }, - // AssertAppRevisionJson(String), - AssertApp(AppPB), - ReadApp(String), - UpdateApp { - name: Option, - desc: Option, - }, - DeleteApp, - - // View - CreateView { - name: String, - desc: String, - data_type: ViewDataFormatPB, - }, - AssertView(ViewPB), - ReadView(String), - UpdateView { - name: Option, - desc: Option, - }, - DeleteView, - DeleteViews(Vec), - - // Trash - RestoreAppFromTrash, - RestoreViewFromTrash, - ReadTrash, - DeleteAllTrash, - - // Sync - #[allow(dead_code)] - AssertCurrentRevId(i64), - AssertNextSyncRevId(Option), - AssertRevisionState { - rev_id: i64, - state: RevisionState, - }, -} - -pub struct FolderTest { - pub sdk: FlowySDKTest, - pub all_workspace: Vec, - pub workspace: WorkspacePB, - pub app: AppPB, - pub view: ViewPB, - pub trash: Vec, - // pub folder_editor: -} - -impl FolderTest { - pub async fn new() -> Self { - let sdk = FlowySDKTest::default(); - let _ = sdk.init_user().await; - let mut workspace = create_workspace(&sdk, "FolderWorkspace", "Folder test workspace").await; - let mut app = create_app(&sdk, &workspace.id, "Folder App", "Folder test app").await; - let view = create_view( - &sdk, - &app.id, - "Folder View", - "Folder test view", - ViewLayoutTypePB::Document, - ) - .await; - app.belongings = RepeatedViewPB { - items: vec![view.clone()], - }; - - workspace.apps = RepeatedAppPB { - items: vec![app.clone()], - }; - Self { - sdk, - all_workspace: vec![], - workspace, - app, - view, - trash: vec![], - } - } - - pub async fn run_scripts(&mut self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - pub async fn run_script(&mut self, script: FolderScript) { - let sdk = &self.sdk; - let folder_editor: Arc = sdk.folder_manager.folder_editor().await; - let rev_manager = folder_editor.rev_manager(); - let cache = rev_manager.revision_cache().await; - - match script { - FolderScript::ReadAllWorkspaces => { - let all_workspace = read_workspace(sdk, None).await; - self.all_workspace = all_workspace; - }, - FolderScript::CreateWorkspace { name, desc } => { - let workspace = create_workspace(sdk, &name, &desc).await; - self.workspace = workspace; - }, - // FolderScript::AssertWorkspaceRevisionJson(expected_json) => { - // let workspace = read_workspace(sdk, Some(self.workspace.id.clone())) - // .await - // .pop() - // .unwrap(); - // let workspace_revision: WorkspaceRevision = workspace.into(); - // let json = serde_json::to_string(&workspace_revision).unwrap(); - // assert_eq!(json, expected_json); - // } - FolderScript::AssertWorkspace(workspace) => { - assert_eq!(self.workspace, workspace, "Workspace not equal"); - }, - FolderScript::ReadWorkspace(workspace_id) => { - let workspace = read_workspace(sdk, workspace_id).await.pop().unwrap(); - self.workspace = workspace; - }, - FolderScript::CreateApp { name, desc } => { - let app = create_app(sdk, &self.workspace.id, &name, &desc).await; - self.app = app; - }, - // FolderScript::AssertAppRevisionJson(expected_json) => { - // let app_revision: AppRevision = self.app.clone().into(); - // let json = serde_json::to_string(&app_revision).unwrap(); - // assert_eq!(json, expected_json); - // } - FolderScript::AssertApp(app) => { - assert_eq!(self.app, app, "App not equal"); - }, - FolderScript::ReadApp(app_id) => { - let app = read_app(sdk, &app_id).await; - self.app = app; - }, - FolderScript::UpdateApp { name, desc } => { - update_app(sdk, &self.app.id, name, desc).await; - }, - FolderScript::DeleteApp => { - delete_app(sdk, &self.app.id).await; - }, - - FolderScript::CreateView { - name, - desc, - data_type, - } => { - let layout = match data_type { - ViewDataFormatPB::DeltaFormat => ViewLayoutTypePB::Document, - ViewDataFormatPB::NodeFormat => ViewLayoutTypePB::Document, - ViewDataFormatPB::DatabaseFormat => ViewLayoutTypePB::Grid, - }; - let view = create_view(sdk, &self.app.id, &name, &desc, layout).await; - self.view = view; - }, - FolderScript::AssertView(view) => { - assert_eq!(self.view, view, "View not equal"); - }, - FolderScript::ReadView(view_id) => { - let view = read_view(sdk, &view_id).await; - self.view = view; - }, - FolderScript::UpdateView { name, desc } => { - update_view(sdk, &self.view.id, name, desc).await; - }, - FolderScript::DeleteView => { - delete_view(sdk, vec![self.view.id.clone()]).await; - }, - FolderScript::DeleteViews(view_ids) => { - delete_view(sdk, view_ids).await; - }, - FolderScript::RestoreAppFromTrash => { - restore_app_from_trash(sdk, &self.app.id).await; - }, - FolderScript::RestoreViewFromTrash => { - restore_view_from_trash(sdk, &self.view.id).await; - }, - FolderScript::ReadTrash => { - let mut trash = read_trash(sdk).await; - self.trash = trash.into_inner(); - }, - FolderScript::DeleteAllTrash => { - delete_all_trash(sdk).await; - self.trash = vec![]; - }, - FolderScript::AssertRevisionState { rev_id, state } => { - let record = cache.get(rev_id).await.unwrap(); - assert_eq!(record.state, state, "Revision state is not match"); - if let RevisionState::Ack = state { - // There is a defer action that writes the revisions to disk, so we wait here. - // Make sure everything is written. - sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await; - } - }, - FolderScript::AssertCurrentRevId(rev_id) => { - assert_eq!(rev_manager.rev_id(), rev_id, "Current rev_id is not match"); - }, - FolderScript::AssertNextSyncRevId(rev_id) => { - let next_revision = rev_manager.next_sync_revision().await.unwrap(); - if rev_id.is_none() { - assert!(next_revision.is_none(), "Next revision should be None"); - return; - } - let next_revision = next_revision.unwrap_or_else(|| { - panic!( - "Expected Next revision is {}, but receive None", - rev_id.unwrap() - ) - }); - let mut notify = rev_manager.ack_notify(); - let _ = notify.recv().await; - assert_eq!( - next_revision.rev_id, - rev_id.unwrap(), - "Revision id not match" - ); - }, - } - } -} - -pub fn invalid_workspace_name_test_case() -> Vec<(String, ErrorCode)> { - vec![ - ("".to_owned(), ErrorCode::WorkspaceNameInvalid), - ("1234".repeat(100), ErrorCode::WorkspaceNameTooLong), - ] -} - -pub async fn create_workspace(sdk: &FlowySDKTest, name: &str, desc: &str) -> WorkspacePB { - let request = CreateWorkspacePayloadPB { - name: name.to_owned(), - desc: desc.to_owned(), - }; - - FolderEventBuilder::new(sdk.clone()) - .event(CreateWorkspace) - .payload(request) - .async_send() - .await - .parse::() -} - -pub async fn read_workspace(sdk: &FlowySDKTest, workspace_id: Option) -> Vec { - let request = WorkspaceIdPB { - value: workspace_id, - }; - let mut repeated_workspace = FolderEventBuilder::new(sdk.clone()) - .event(ReadWorkspaces) - .payload(request.clone()) - .async_send() - .await - .parse::(); - - let workspaces; - if let Some(workspace_id) = &request.value { - workspaces = repeated_workspace - .into_inner() - .into_iter() - .filter(|workspace| &workspace.id == workspace_id) - .collect::>(); - debug_assert_eq!(workspaces.len(), 1); - } else { - workspaces = repeated_workspace.items; - } - - workspaces -} - -pub async fn create_app(sdk: &FlowySDKTest, workspace_id: &str, name: &str, desc: &str) -> AppPB { - let create_app_request = CreateAppPayloadPB { - workspace_id: workspace_id.to_owned(), - name: name.to_string(), - desc: desc.to_string(), - color_style: Default::default(), - }; - - FolderEventBuilder::new(sdk.clone()) - .event(CreateApp) - .payload(create_app_request) - .async_send() - .await - .parse::() -} - -pub async fn read_app(sdk: &FlowySDKTest, app_id: &str) -> AppPB { - let request = AppIdPB { - value: app_id.to_owned(), - }; - - FolderEventBuilder::new(sdk.clone()) - .event(ReadApp) - .payload(request) - .async_send() - .await - .parse::() -} - -pub async fn update_app( - sdk: &FlowySDKTest, - app_id: &str, - name: Option, - desc: Option, -) { - let request = UpdateAppPayloadPB { - app_id: app_id.to_string(), - name, - desc, - color_style: None, - is_trash: None, - }; - - FolderEventBuilder::new(sdk.clone()) - .event(UpdateApp) - .payload(request) - .async_send() - .await; -} - -pub async fn delete_app(sdk: &FlowySDKTest, app_id: &str) { - let request = AppIdPB { - value: app_id.to_string(), - }; - - FolderEventBuilder::new(sdk.clone()) - .event(DeleteApp) - .payload(request) - .async_send() - .await; -} - -pub async fn create_view( - sdk: &FlowySDKTest, - app_id: &str, - name: &str, - desc: &str, - layout: ViewLayoutTypePB, -) -> ViewPB { - let request = CreateViewPayloadPB { - belong_to_id: app_id.to_string(), - name: name.to_string(), - desc: desc.to_string(), - thumbnail: None, - layout, - initial_data: vec![], - ext: Default::default(), - }; - FolderEventBuilder::new(sdk.clone()) - .event(CreateView) - .payload(request) - .async_send() - .await - .parse::() -} - -pub async fn read_view(sdk: &FlowySDKTest, view_id: &str) -> ViewPB { - let view_id: ViewIdPB = view_id.into(); - FolderEventBuilder::new(sdk.clone()) - .event(ReadView) - .payload(view_id) - .async_send() - .await - .parse::() -} - -pub async fn update_view( - sdk: &FlowySDKTest, - view_id: &str, - name: Option, - desc: Option, -) { - let request = UpdateViewPayloadPB { - view_id: view_id.to_string(), - name, - desc, - thumbnail: None, - }; - FolderEventBuilder::new(sdk.clone()) - .event(UpdateView) - .payload(request) - .async_send() - .await; -} - -pub async fn delete_view(sdk: &FlowySDKTest, view_ids: Vec) { - let request = RepeatedViewIdPB { items: view_ids }; - FolderEventBuilder::new(sdk.clone()) - .event(DeleteView) - .payload(request) - .async_send() - .await; -} - -pub async fn read_trash(sdk: &FlowySDKTest) -> RepeatedTrashPB { - FolderEventBuilder::new(sdk.clone()) - .event(ReadTrash) - .async_send() - .await - .parse::() -} - -pub async fn restore_app_from_trash(sdk: &FlowySDKTest, app_id: &str) { - let id = TrashIdPB { - id: app_id.to_owned(), - ty: TrashType::TrashApp, - }; - FolderEventBuilder::new(sdk.clone()) - .event(PutbackTrash) - .payload(id) - .async_send() - .await; -} - -pub async fn restore_view_from_trash(sdk: &FlowySDKTest, view_id: &str) { - let id = TrashIdPB { - id: view_id.to_owned(), - ty: TrashType::TrashView, - }; - FolderEventBuilder::new(sdk.clone()) - .event(PutbackTrash) - .payload(id) - .async_send() - .await; -} - -pub async fn delete_all_trash(sdk: &FlowySDKTest) { - FolderEventBuilder::new(sdk.clone()) - .event(DeleteAllTrash) - .async_send() - .await; -} diff --git a/frontend/rust-lib/flowy-folder2/Cargo.toml b/frontend/rust-lib/flowy-folder2/Cargo.toml index 5be51b3f67..4ec7c71791 100644 --- a/frontend/rust-lib/flowy-folder2/Cargo.toml +++ b/frontend/rust-lib/flowy-folder2/Cargo.toml @@ -10,7 +10,7 @@ collab = { version = "0.1.0" } collab-folder = { version = "0.1.0" } appflowy-integrate = {version = "0.1.0" } -flowy-derive = { path = "../flowy-derive" } +flowy-derive = { path = "../../../shared-lib/flowy-derive" } flowy-notification = { path = "../flowy-notification" } parking_lot = "0.12.1" unicode-segmentation = "1.10" @@ -26,14 +26,14 @@ chrono = { version = "0.4.24"} strum = "0.21" strum_macros = "0.21" protobuf = {version = "2.28.0"} -flowy-document = { path = "../flowy-document" } +#flowy-document = { path = "../flowy-document" } [dev-dependencies] flowy-folder2 = { path = "../flowy-folder2"} flowy-test = { path = "../flowy-test" } [build-dependencies] -flowy-codegen = { path = "../flowy-codegen"} +flowy-codegen = { path = "../../../shared-lib/flowy-codegen"} [features] dart = ["flowy-codegen/dart", "flowy-notification/dart"] diff --git a/frontend/rust-lib/flowy-document/src/editor/READ_ME.json b/frontend/rust-lib/flowy-folder2/src/READ_ME.json similarity index 100% rename from frontend/rust-lib/flowy-document/src/editor/READ_ME.json rename to frontend/rust-lib/flowy-folder2/src/READ_ME.json diff --git a/frontend/rust-lib/flowy-folder2/src/user_default.rs b/frontend/rust-lib/flowy-folder2/src/user_default.rs index fc30d93c98..9e1d014907 100644 --- a/frontend/rust-lib/flowy-folder2/src/user_default.rs +++ b/frontend/rust-lib/flowy-folder2/src/user_default.rs @@ -1,11 +1,11 @@ -use crate::entities::{view_pb_with_child_views, WorkspacePB}; +use std::collections::HashMap; -use crate::view_ext::{gen_view_id, ViewDataProcessorMap}; use chrono::Utc; use collab_folder::core::{Belonging, Belongings, FolderData, View, ViewLayout, Workspace}; -use flowy_document::editor::initial_read_me; use nanoid::nanoid; -use std::collections::HashMap; + +use crate::entities::{view_pb_with_child_views, WorkspacePB}; +use crate::view_ext::{gen_view_id, ViewDataProcessorMap}; pub struct DefaultFolderBuilder(); impl DefaultFolderBuilder { @@ -100,3 +100,8 @@ fn workspace_pb_from_workspace( create_time: workspace.created_at, } } + +pub fn initial_read_me() -> String { + let document_content = include_str!("READ_ME.json"); + document_content.to_string() +} diff --git a/frontend/rust-lib/flowy-net/Cargo.toml b/frontend/rust-lib/flowy-net/Cargo.toml index 8dda2f5151..59aad0e8ea 100644 --- a/frontend/rust-lib/flowy-net/Cargo.toml +++ b/frontend/rust-lib/flowy-net/Cargo.toml @@ -7,22 +7,12 @@ edition = "2018" [dependencies] lib-dispatch = { path = "../lib-dispatch" } -flowy-error = { path = "../flowy-error", features = ["adaptor_sync", "adaptor_reqwest", "adaptor_server_error"] } -flowy-derive = { path = "../flowy-derive" } -flowy-client-sync = { path = "../flowy-client-sync"} -folder-model = { path = "../../../shared-lib/folder-model" } -revision-model = { path = "../../../shared-lib/revision-model"} -document-model = { path = "../../../shared-lib/document-model"} -ws-model = { path = "../../../shared-lib/ws-model"} -flowy-server-sync = { path = "../../../shared-lib/flowy-server-sync"} -flowy-client-ws = { path = "../../../shared-lib/flowy-client-ws"} -flowy-client-network-config= { path = "../../../shared-lib/flowy-client-network-config"} -flowy-sync = { path = "../../../shared-lib/flowy-sync"} -user-model = { path = "../../../shared-lib/user-model"} +flowy-error = { path = "../flowy-error", features = ["adaptor_reqwest", "adaptor_server_error"] } +flowy-derive = { path = "../../../shared-lib/flowy-derive" } flowy-folder2 = { path = "../flowy-folder2" } flowy-document2 = { path = "../flowy-document2" } flowy-user = { path = "../flowy-user" } -flowy-document = { path = "../flowy-document" } +#flowy-document = { path = "../flowy-document" } lazy_static = "1.4.0" lib-infra = { path = "../../../shared-lib/lib-infra" } protobuf = {version = "2.28.0"} @@ -61,4 +51,4 @@ ts = [ ] [build-dependencies] -flowy-codegen = { path = "../flowy-codegen"} +flowy-codegen = { path = "../../../shared-lib/flowy-codegen"} diff --git a/frontend/rust-lib/flowy-net/src/entities/network_state.rs b/frontend/rust-lib/flowy-net/src/entities/network_state.rs index 34ea01c511..b1be1005a6 100644 --- a/frontend/rust-lib/flowy-net/src/entities/network_state.rs +++ b/frontend/rust-lib/flowy-net/src/entities/network_state.rs @@ -1,4 +1,3 @@ -use flowy_client_ws::NetworkType; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; #[derive(ProtoBuf_Enum, Debug, Clone, Eq, PartialEq)] @@ -28,19 +27,6 @@ impl std::default::Default for NetworkTypePB { } } -impl std::convert::From for NetworkType { - fn from(ty: NetworkTypePB) -> Self { - match ty { - NetworkTypePB::Unknown => NetworkType::Unknown, - NetworkTypePB::Wifi => NetworkType::Wifi, - NetworkTypePB::Cell => NetworkType::Cell, - NetworkTypePB::Ethernet => NetworkType::Ethernet, - NetworkTypePB::Bluetooth => NetworkType::Bluetooth, - NetworkTypePB::VPN => NetworkType::VPN, - } - } -} - #[derive(ProtoBuf, Debug, Default, Clone)] pub struct NetworkStatePB { #[pb(index = 1)] diff --git a/frontend/rust-lib/flowy-net/src/event_map.rs b/frontend/rust-lib/flowy-net/src/event_map.rs index 1d314d1497..2081d49510 100644 --- a/frontend/rust-lib/flowy-net/src/event_map.rs +++ b/frontend/rust-lib/flowy-net/src/event_map.rs @@ -1,14 +1,13 @@ -use crate::handlers::*; -use flowy_client_ws::FlowyWebSocketConnect; -use flowy_derive::{Flowy_Event, ProtoBuf_Enum}; -use lib_dispatch::prelude::*; -use std::sync::Arc; use strum_macros::Display; -pub fn init(ws_conn: Arc) -> AFPlugin { +use flowy_derive::{Flowy_Event, ProtoBuf_Enum}; +use lib_dispatch::prelude::*; + +use crate::handlers::*; + +pub fn init() -> AFPlugin { AFPlugin::new() .name("Flowy-Network") - .state(ws_conn) .event(NetworkEvent::UpdateNetworkType, update_network_ty) } diff --git a/frontend/rust-lib/flowy-net/src/handlers/mod.rs b/frontend/rust-lib/flowy-net/src/handlers/mod.rs index 05c0d788c6..064b87b1a1 100644 --- a/frontend/rust-lib/flowy-net/src/handlers/mod.rs +++ b/frontend/rust-lib/flowy-net/src/handlers/mod.rs @@ -1,15 +1,9 @@ -use crate::entities::NetworkStatePB; -use flowy_client_ws::{FlowyWebSocketConnect, NetworkType}; use flowy_error::FlowyError; -use lib_dispatch::prelude::{AFPluginData, AFPluginState}; -use std::sync::Arc; +use lib_dispatch::prelude::AFPluginData; -#[tracing::instrument(level = "debug", skip(data, ws_manager))] -pub async fn update_network_ty( - data: AFPluginData, - ws_manager: AFPluginState>, -) -> Result<(), FlowyError> { - let network_type: NetworkType = data.into_inner().ty.into(); - ws_manager.update_network_type(network_type); +use crate::entities::NetworkStatePB; + +#[tracing::instrument(level = "debug", skip_all)] +pub async fn update_network_ty(_data: AFPluginData) -> Result<(), FlowyError> { Ok(()) } diff --git a/frontend/rust-lib/flowy-net/src/http_server/document.rs b/frontend/rust-lib/flowy-net/src/http_server/document.rs deleted file mode 100644 index 63c27ebbb4..0000000000 --- a/frontend/rust-lib/flowy-net/src/http_server/document.rs +++ /dev/null @@ -1,127 +0,0 @@ -use crate::request::{HttpRequestBuilder, ResponseMiddleware}; -use crate::response::HttpResponse; -use document_model::document::{ - CreateDocumentParams, DocumentId, DocumentInfo, ResetDocumentParams, -}; -use flowy_client_network_config::{ClientServerConfiguration, HEADER_TOKEN}; -use flowy_document::DocumentCloudService; -use flowy_error::FlowyError; -use lazy_static::lazy_static; -use lib_infra::future::FutureResult; -use std::sync::Arc; - -pub struct DocumentCloudServiceImpl { - config: ClientServerConfiguration, -} - -impl DocumentCloudServiceImpl { - pub fn new(config: ClientServerConfiguration) -> Self { - Self { config } - } -} - -impl DocumentCloudService for DocumentCloudServiceImpl { - fn create_document( - &self, - token: &str, - params: CreateDocumentParams, - ) -> FutureResult<(), FlowyError> { - let token = token.to_owned(); - let url = self.config.doc_url(); - FutureResult::new(async move { create_document_request(&token, params, &url).await }) - } - - fn fetch_document( - &self, - token: &str, - params: DocumentId, - ) -> FutureResult, FlowyError> { - let token = token.to_owned(); - let url = self.config.doc_url(); - FutureResult::new(async move { read_document_request(&token, params, &url).await }) - } - - fn update_document_content( - &self, - token: &str, - params: ResetDocumentParams, - ) -> FutureResult<(), FlowyError> { - let token = token.to_owned(); - let url = self.config.doc_url(); - FutureResult::new(async move { reset_doc_request(&token, params, &url).await }) - } -} - -pub async fn create_document_request( - token: &str, - params: CreateDocumentParams, - url: &str, -) -> Result<(), FlowyError> { - request_builder() - .post(url) - .header(HEADER_TOKEN, token) - .json(params)? - .send() - .await?; - Ok(()) -} - -pub async fn read_document_request( - token: &str, - params: DocumentId, - url: &str, -) -> Result, FlowyError> { - let doc = request_builder() - .get(url) - .header(HEADER_TOKEN, token) - .json(params)? - .option_json_response() - .await?; - - Ok(doc) -} - -pub async fn reset_doc_request( - token: &str, - params: ResetDocumentParams, - url: &str, -) -> Result<(), FlowyError> { - request_builder() - .patch(url) - .header(HEADER_TOKEN, token) - .json(params)? - .send() - .await?; - Ok(()) -} - -fn request_builder() -> HttpRequestBuilder { - HttpRequestBuilder::new().middleware(MIDDLEWARE.clone()) -} - -lazy_static! { - pub(crate) static ref MIDDLEWARE: Arc = - Arc::new(DocumentResponseMiddleware {}); -} - -pub(crate) struct DocumentResponseMiddleware {} -impl ResponseMiddleware for DocumentResponseMiddleware { - fn receive_response(&self, token: &Option, response: &HttpResponse) { - if let Some(error) = &response.error { - if error.is_unauthorized() { - tracing::error!("document user is unauthorized"); - - match token { - None => {}, - Some(_token) => { - // let error = - // FlowyError::new(ErrorCode::UserUnauthorized, ""); - // observable(token, - // WorkspaceObservable::UserUnauthorized).error(error). - // build() - }, - } - } - } - } -} diff --git a/frontend/rust-lib/flowy-net/src/http_server/mod.rs b/frontend/rust-lib/flowy-net/src/http_server/mod.rs index a7b831d704..d5e9de07ec 100644 --- a/frontend/rust-lib/flowy-net/src/http_server/mod.rs +++ b/frontend/rust-lib/flowy-net/src/http_server/mod.rs @@ -1,2 +1 @@ -pub mod document; -pub mod user; +pub mod self_host; diff --git a/shared-lib/flowy-client-network-config/src/configuration.rs b/frontend/rust-lib/flowy-net/src/http_server/self_host/configuration.rs similarity index 92% rename from shared-lib/flowy-client-network-config/src/configuration.rs rename to frontend/rust-lib/flowy-net/src/http_server/self_host/configuration.rs index d7a8bcc79d..54909825fd 100644 --- a/shared-lib/flowy-client-network-config/src/configuration.rs +++ b/frontend/rust-lib/flowy-net/src/http_server/self_host/configuration.rs @@ -1,6 +1,8 @@ +use std::convert::{TryFrom, TryInto}; + use config::FileFormat; use serde_aux::field_attributes::deserialize_number_from_string; -use std::convert::{TryFrom, TryInto}; + pub const HEADER_TOKEN: &str = "token"; #[derive(serde::Deserialize, Clone, Debug)] @@ -14,7 +16,7 @@ pub struct ClientServerConfiguration { pub fn get_client_server_configuration() -> Result { let mut settings = config::Config::default(); - let base = include_str!("../configuration/base.yaml"); + let base = include_str!("./configuration/base.yaml"); settings.merge(config::File::from_str(base, FileFormat::Yaml).required(true))?; let environment: Environment = std::env::var("APP_ENVIRONMENT") @@ -23,8 +25,8 @@ pub fn get_client_server_configuration() -> Result include_str!("../configuration/local.yaml"), - Environment::Production => include_str!("../configuration/production.yaml"), + Environment::Local => include_str!("./configuration/local.yaml"), + Environment::Production => include_str!("./configuration/production.yaml"), }; settings.merge(config::File::from_str(custom, FileFormat::Yaml).required(true))?; diff --git a/shared-lib/flowy-client-network-config/configuration/base.yaml b/frontend/rust-lib/flowy-net/src/http_server/self_host/configuration/base.yaml similarity index 100% rename from shared-lib/flowy-client-network-config/configuration/base.yaml rename to frontend/rust-lib/flowy-net/src/http_server/self_host/configuration/base.yaml diff --git a/shared-lib/flowy-client-network-config/configuration/local.yaml b/frontend/rust-lib/flowy-net/src/http_server/self_host/configuration/local.yaml similarity index 100% rename from shared-lib/flowy-client-network-config/configuration/local.yaml rename to frontend/rust-lib/flowy-net/src/http_server/self_host/configuration/local.yaml diff --git a/shared-lib/flowy-client-network-config/configuration/production.yaml b/frontend/rust-lib/flowy-net/src/http_server/self_host/configuration/production.yaml similarity index 100% rename from shared-lib/flowy-client-network-config/configuration/production.yaml rename to frontend/rust-lib/flowy-net/src/http_server/self_host/configuration/production.yaml diff --git a/frontend/rust-lib/flowy-net/src/http_server/self_host/mod.rs b/frontend/rust-lib/flowy-net/src/http_server/self_host/mod.rs new file mode 100644 index 0000000000..2cd9f5266c --- /dev/null +++ b/frontend/rust-lib/flowy-net/src/http_server/self_host/mod.rs @@ -0,0 +1,2 @@ +pub mod configuration; +pub mod user; diff --git a/frontend/rust-lib/flowy-net/src/http_server/user.rs b/frontend/rust-lib/flowy-net/src/http_server/self_host/user.rs similarity index 93% rename from frontend/rust-lib/flowy-net/src/http_server/user.rs rename to frontend/rust-lib/flowy-net/src/http_server/self_host/user.rs index c619a847d8..a8151d1871 100644 --- a/frontend/rust-lib/flowy-net/src/http_server/user.rs +++ b/frontend/rust-lib/flowy-net/src/http_server/self_host/user.rs @@ -1,10 +1,13 @@ -use crate::request::HttpRequestBuilder; -use flowy_client_network_config::{ClientServerConfiguration, HEADER_TOKEN}; use flowy_error::FlowyError; -use flowy_user::entities::UserProfilePB; +use flowy_user::entities::{ + SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, + UserProfilePB, +}; use flowy_user::event_map::UserCloudService; use lib_infra::future::FutureResult; -use user_model::*; + +use crate::http_server::self_host::configuration::{ClientServerConfiguration, HEADER_TOKEN}; +use crate::request::HttpRequestBuilder; pub struct UserHttpCloudService { config: ClientServerConfiguration, diff --git a/frontend/rust-lib/flowy-net/src/lib.rs b/frontend/rust-lib/flowy-net/src/lib.rs index 0056548cae..d0f148701f 100644 --- a/frontend/rust-lib/flowy-net/src/lib.rs +++ b/frontend/rust-lib/flowy-net/src/lib.rs @@ -6,5 +6,3 @@ pub mod local_server; pub mod protobuf; mod request; mod response; - -pub use flowy_client_network_config::{get_client_server_configuration, ClientServerConfiguration}; diff --git a/frontend/rust-lib/flowy-net/src/local_server/context.rs b/frontend/rust-lib/flowy-net/src/local_server/context.rs new file mode 100644 index 0000000000..e564f37561 --- /dev/null +++ b/frontend/rust-lib/flowy-net/src/local_server/context.rs @@ -0,0 +1,11 @@ +use crate::http_server::self_host::configuration::ClientServerConfiguration; +use crate::local_server::LocalServer; + +pub struct LocalServerContext { + pub local_server: LocalServer, +} + +pub fn build_server(_config: &ClientServerConfiguration) -> LocalServerContext { + let local_server = LocalServer::new(); + LocalServerContext { local_server } +} diff --git a/frontend/rust-lib/flowy-net/src/local_server/mod.rs b/frontend/rust-lib/flowy-net/src/local_server/mod.rs index a36b2ef9e7..ef14ac204a 100644 --- a/frontend/rust-lib/flowy-net/src/local_server/mod.rs +++ b/frontend/rust-lib/flowy-net/src/local_server/mod.rs @@ -1,30 +1,5 @@ -use flowy_client_network_config::ClientServerConfiguration; -use tokio::sync::{broadcast, mpsc}; - -mod persistence; -mod server; -mod ws; - +pub use context::*; pub use server::*; -pub use ws::*; -pub struct LocalServerContext { - pub local_ws: LocalWebSocket, - pub local_server: LocalServer, -} - -pub fn build_server(_config: &ClientServerConfiguration) -> LocalServerContext { - let (client_ws_sender, server_ws_receiver) = mpsc::unbounded_channel(); - let (server_ws_sender, _) = broadcast::channel(16); - - // server_ws_sender -> client_ws_receiver - // server_ws_receiver <- client_ws_sender - let local_ws = LocalWebSocket::new(server_ws_receiver, server_ws_sender.clone()); - let client_ws_receiver = server_ws_sender; - let local_server = LocalServer::new(client_ws_sender, client_ws_receiver); - - LocalServerContext { - local_ws, - local_server, - } -} +mod context; +mod server; diff --git a/frontend/rust-lib/flowy-net/src/local_server/persistence.rs b/frontend/rust-lib/flowy-net/src/local_server/persistence.rs deleted file mode 100644 index 5d032860df..0000000000 --- a/frontend/rust-lib/flowy-net/src/local_server/persistence.rs +++ /dev/null @@ -1,186 +0,0 @@ -use document_model::document::DocumentInfo; -use flowy_client_sync::{errors::SyncError, util::make_document_info_from_revisions}; -use flowy_server_sync::server_folder::make_folder_from_revisions; -use flowy_sync::ext::{DocumentCloudPersistence, FolderCloudPersistence}; -use folder_model::folder::FolderInfo; -use lib_infra::future::BoxResultFuture; -use revision_model::Revision; -use std::{ - fmt::{Debug, Formatter}, - sync::Arc, -}; - -// For the moment, we use memory to cache the data, it will be implemented with -// other storage. Like the Firestore,Dropbox.etc. -pub trait RevisionCloudStorage: Send + Sync { - fn set_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError>; - fn get_revisions( - &self, - object_id: &str, - rev_ids: Option>, - ) -> BoxResultFuture, SyncError>; - fn reset_object( - &self, - object_id: &str, - revisions: Vec, - ) -> BoxResultFuture<(), SyncError>; -} - -pub(crate) struct LocalDocumentCloudPersistence { - storage: Arc, -} - -impl Debug for LocalDocumentCloudPersistence { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str("LocalRevisionCloudPersistence") - } -} - -impl std::default::Default for LocalDocumentCloudPersistence { - fn default() -> Self { - LocalDocumentCloudPersistence { - storage: Arc::new(MemoryDocumentCloudStorage::default()), - } - } -} - -impl FolderCloudPersistence for LocalDocumentCloudPersistence { - fn read_folder(&self, _user_id: &str, folder_id: &str) -> BoxResultFuture { - let storage = self.storage.clone(); - let folder_id = folder_id.to_owned(); - Box::pin(async move { - let revisions = storage.get_revisions(&folder_id, None).await?; - match make_folder_from_revisions(&folder_id, revisions)? { - Some(folder_info) => Ok(folder_info), - None => Err(SyncError::record_not_found()), - } - }) - } - - fn create_folder( - &self, - _user_id: &str, - folder_id: &str, - revisions: Vec, - ) -> BoxResultFuture, SyncError> { - let folder_id = folder_id.to_owned(); - let storage = self.storage.clone(); - Box::pin(async move { - storage.set_revisions(revisions.clone()).await?; - make_folder_from_revisions(&folder_id, revisions) - }) - } - - fn save_folder_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError> { - let storage = self.storage.clone(); - Box::pin(async move { - storage.set_revisions(revisions).await?; - Ok(()) - }) - } - - fn read_folder_revisions( - &self, - folder_id: &str, - rev_ids: Option>, - ) -> BoxResultFuture, SyncError> { - let folder_id = folder_id.to_owned(); - let storage = self.storage.clone(); - Box::pin(async move { storage.get_revisions(&folder_id, rev_ids).await }) - } - - fn reset_folder( - &self, - folder_id: &str, - revisions: Vec, - ) -> BoxResultFuture<(), SyncError> { - let storage = self.storage.clone(); - let folder_id = folder_id.to_owned(); - Box::pin(async move { - storage.reset_object(&folder_id, revisions).await?; - Ok(()) - }) - } -} - -impl DocumentCloudPersistence for LocalDocumentCloudPersistence { - fn read_document(&self, doc_id: &str) -> BoxResultFuture { - let storage = self.storage.clone(); - let doc_id = doc_id.to_owned(); - Box::pin(async move { - let repeated_revision = storage.get_revisions(&doc_id, None).await?; - match make_document_info_from_revisions(&doc_id, repeated_revision)? { - Some(document_info) => Ok(document_info), - None => Err(SyncError::record_not_found()), - } - }) - } - - fn create_document( - &self, - doc_id: &str, - revisions: Vec, - ) -> BoxResultFuture, SyncError> { - let doc_id = doc_id.to_owned(); - let storage = self.storage.clone(); - Box::pin(async move { - storage.set_revisions(revisions.clone()).await?; - make_document_info_from_revisions(&doc_id, revisions) - }) - } - - fn read_document_revisions( - &self, - doc_id: &str, - rev_ids: Option>, - ) -> BoxResultFuture, SyncError> { - let doc_id = doc_id.to_owned(); - let storage = self.storage.clone(); - Box::pin(async move { storage.get_revisions(&doc_id, rev_ids).await }) - } - - fn save_document_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError> { - let storage = self.storage.clone(); - Box::pin(async move { - storage.set_revisions(revisions).await?; - Ok(()) - }) - } - - fn reset_document( - &self, - doc_id: &str, - revisions: Vec, - ) -> BoxResultFuture<(), SyncError> { - let storage = self.storage.clone(); - let doc_id = doc_id.to_owned(); - Box::pin(async move { - storage.reset_object(&doc_id, revisions).await?; - Ok(()) - }) - } -} - -#[derive(Default)] -struct MemoryDocumentCloudStorage {} -impl RevisionCloudStorage for MemoryDocumentCloudStorage { - fn set_revisions(&self, _revisions: Vec) -> BoxResultFuture<(), SyncError> { - Box::pin(async move { Ok(()) }) - } - - fn get_revisions( - &self, - _object_id: &str, - _rev_ids: Option>, - ) -> BoxResultFuture, SyncError> { - Box::pin(async move { Ok(vec![]) }) - } - - fn reset_object( - &self, - _object_id: &str, - _revisions: Vec, - ) -> BoxResultFuture<(), SyncError> { - Box::pin(async move { Ok(()) }) - } -} diff --git a/frontend/rust-lib/flowy-net/src/local_server/server.rs b/frontend/rust-lib/flowy-net/src/local_server/server.rs index c124567de9..528155a5cf 100644 --- a/frontend/rust-lib/flowy-net/src/local_server/server.rs +++ b/frontend/rust-lib/flowy-net/src/local_server/server.rs @@ -1,61 +1,28 @@ -use crate::local_server::persistence::LocalDocumentCloudPersistence; -use async_stream::stream; -use bytes::Bytes; -use document_model::document::{ - CreateDocumentParams, DocumentId, DocumentInfo, ResetDocumentParams, -}; -use flowy_client_sync::errors::SyncError; -use flowy_document::DocumentCloudService; -use flowy_error::{internal_error, FlowyError}; -use flowy_server_sync::server_document::ServerDocumentManager; -use flowy_server_sync::server_folder::ServerFolderManager; -use flowy_sync::{RevisionSyncResponse, RevisionUser}; -use flowy_user::entities::UserProfilePB; -use flowy_user::event_map::UserCloudService; - -use flowy_user::uid::UserIDGenerator; -use futures_util::stream::StreamExt; -use lib_infra::future::FutureResult; -use lib_ws::{WSChannel, WebSocketRawMessage}; - use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; -use std::{ - convert::{TryFrom, TryInto}, - fmt::Debug, - sync::Arc, +use tokio::sync::mpsc; + +use flowy_error::FlowyError; +use flowy_user::entities::{ + SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, + UserProfilePB, }; -use tokio::sync::{broadcast, mpsc, mpsc::UnboundedSender}; -use user_model::*; -use ws_model::ws_revision::{ClientRevisionWSData, ClientRevisionWSDataType}; +use flowy_user::event_map::UserCloudService; +use flowy_user::uid::UserIDGenerator; +use lib_infra::future::FutureResult; + lazy_static! { static ref ID_GEN: Mutex = Mutex::new(UserIDGenerator::new(1)); } +#[derive(Default)] pub struct LocalServer { - doc_manager: Arc, - folder_manager: Arc, stop_tx: RwLock>>, - client_ws_sender: mpsc::UnboundedSender, - client_ws_receiver: broadcast::Sender, } impl LocalServer { - pub fn new( - client_ws_sender: mpsc::UnboundedSender, - client_ws_receiver: broadcast::Sender, - ) -> Self { - let persistence = Arc::new(LocalDocumentCloudPersistence::default()); - let doc_manager = Arc::new(ServerDocumentManager::new(persistence.clone())); - let folder_manager = Arc::new(ServerFolderManager::new(persistence)); - let stop_tx = RwLock::new(None); - LocalServer { - doc_manager, - folder_manager, - stop_tx, - client_ws_sender, - client_ws_receiver, - } + pub fn new() -> Self { + Self::default() } pub async fn stop(&self) { @@ -64,206 +31,6 @@ impl LocalServer { let _ = stop_tx.send(()).await; } } - - pub fn run(&self) { - let (stop_tx, stop_rx) = mpsc::channel(1); - *self.stop_tx.write() = Some(stop_tx); - let runner = LocalWebSocketRunner { - doc_manager: self.doc_manager.clone(), - folder_manager: self.folder_manager.clone(), - stop_rx: Some(stop_rx), - client_ws_sender: self.client_ws_sender.clone(), - client_ws_receiver: Some(self.client_ws_receiver.subscribe()), - }; - tokio::spawn(runner.run()); - } -} - -struct LocalWebSocketRunner { - doc_manager: Arc, - folder_manager: Arc, - stop_rx: Option>, - client_ws_sender: mpsc::UnboundedSender, - client_ws_receiver: Option>, -} - -impl LocalWebSocketRunner { - pub async fn run(mut self) { - let mut stop_rx = self.stop_rx.take().expect("Only run once"); - let mut client_ws_receiver = self.client_ws_receiver.take().expect("Only run once"); - let stream = stream! { - loop { - tokio::select! { - result = client_ws_receiver.recv() => { - match result { - Ok(msg) => yield msg, - Err(_e) => {}, - } - }, - _ = stop_rx.recv() => { - tracing::trace!("[LocalWebSocketRunner] stop"); - break - }, - }; - } - }; - stream - .for_each(|message| async { - match self.handle_message(message).await { - Ok(_) => {}, - Err(e) => tracing::error!("[LocalWebSocketRunner]: {}", e), - } - }) - .await; - } - - async fn handle_message(&self, message: WebSocketRawMessage) -> Result<(), FlowyError> { - let bytes = Bytes::from(message.data); - let client_data = ClientRevisionWSData::try_from(bytes).map_err(internal_error)?; - match message.channel { - WSChannel::Document => { - self - .handle_document_client_data(client_data, "".to_owned()) - .await?; - Ok(()) - }, - WSChannel::Folder => { - self - .handle_folder_client_data(client_data, "".to_owned()) - .await?; - Ok(()) - }, - WSChannel::Database => { - todo!("Implement database web socket channel") - }, - } - } - - pub async fn handle_folder_client_data( - &self, - client_data: ClientRevisionWSData, - user_id: String, - ) -> Result<(), SyncError> { - tracing::trace!( - "[LocalFolderServer] receive: {}:{}-{:?} ", - client_data.object_id, - client_data.rev_id, - client_data.ty, - ); - let client_ws_sender = self.client_ws_sender.clone(); - let user = Arc::new(LocalRevisionUser { - user_id, - client_ws_sender, - channel: WSChannel::Folder, - }); - let ty = client_data.ty.clone(); - match ty { - ClientRevisionWSDataType::ClientPushRev => { - self - .folder_manager - .handle_client_revisions(user, client_data) - .await?; - }, - ClientRevisionWSDataType::ClientPing => { - self - .folder_manager - .handle_client_ping(user, client_data) - .await?; - }, - } - Ok(()) - } - - pub async fn handle_document_client_data( - &self, - client_data: ClientRevisionWSData, - user_id: String, - ) -> Result<(), SyncError> { - tracing::trace!( - "[LocalDocumentServer] receive: {}:{}-{:?} ", - client_data.object_id, - client_data.rev_id, - client_data.ty, - ); - let client_ws_sender = self.client_ws_sender.clone(); - let user = Arc::new(LocalRevisionUser { - user_id, - client_ws_sender, - channel: WSChannel::Document, - }); - let ty = client_data.ty.clone(); - match ty { - ClientRevisionWSDataType::ClientPushRev => { - self - .doc_manager - .handle_client_revisions(user, client_data) - .await?; - }, - ClientRevisionWSDataType::ClientPing => { - self - .doc_manager - .handle_client_ping(user, client_data) - .await?; - }, - } - Ok(()) - } -} - -#[derive(Debug)] -struct LocalRevisionUser { - user_id: String, - client_ws_sender: mpsc::UnboundedSender, - channel: WSChannel, -} - -impl RevisionUser for LocalRevisionUser { - fn user_id(&self) -> String { - self.user_id.clone() - } - - fn receive(&self, resp: RevisionSyncResponse) { - let sender = self.client_ws_sender.clone(); - let send_fn = - |sender: UnboundedSender, msg: WebSocketRawMessage| match sender - .send(msg) - { - Ok(_) => {}, - Err(e) => { - tracing::error!("LocalDocumentUser send message failed: {}", e); - }, - }; - let channel = self.channel.clone(); - - tokio::spawn(async move { - match resp { - RevisionSyncResponse::Pull(data) => { - let bytes: Bytes = data.try_into().unwrap(); - let msg = WebSocketRawMessage { - channel, - data: bytes.to_vec(), - }; - send_fn(sender, msg); - }, - RevisionSyncResponse::Push(data) => { - let bytes: Bytes = data.try_into().unwrap(); - let msg = WebSocketRawMessage { - channel, - data: bytes.to_vec(), - }; - send_fn(sender, msg); - }, - RevisionSyncResponse::Ack(data) => { - let bytes: Bytes = data.try_into().unwrap(); - let msg = WebSocketRawMessage { - channel, - data: bytes.to_vec(), - }; - send_fn(sender, msg); - }, - } - }); - } } impl UserCloudService for LocalServer { @@ -311,29 +78,3 @@ impl UserCloudService for LocalServer { "ws://localhost:8000/ws/".to_owned() } } - -impl DocumentCloudService for LocalServer { - fn create_document( - &self, - _token: &str, - _params: CreateDocumentParams, - ) -> FutureResult<(), FlowyError> { - FutureResult::new(async { Ok(()) }) - } - - fn fetch_document( - &self, - _token: &str, - _params: DocumentId, - ) -> FutureResult, FlowyError> { - FutureResult::new(async { Ok(None) }) - } - - fn update_document_content( - &self, - _token: &str, - _params: ResetDocumentParams, - ) -> FutureResult<(), FlowyError> { - FutureResult::new(async { Ok(()) }) - } -} diff --git a/frontend/rust-lib/flowy-net/src/local_server/ws.rs b/frontend/rust-lib/flowy-net/src/local_server/ws.rs deleted file mode 100644 index c02778061d..0000000000 --- a/frontend/rust-lib/flowy-net/src/local_server/ws.rs +++ /dev/null @@ -1,95 +0,0 @@ -use dashmap::DashMap; -use flowy_client_ws::{FlowyRawWebSocket, FlowyWebSocket, WSErrorCode}; -use futures_util::future::BoxFuture; -use lib_infra::future::FutureResult; -use lib_ws::{WSChannel, WSConnectState, WSMessageReceiver, WebSocketRawMessage}; -use parking_lot::RwLock; -use std::sync::Arc; -use tokio::sync::{broadcast, broadcast::Receiver, mpsc::UnboundedReceiver}; - -pub struct LocalWebSocket { - user_id: Arc>>, - receivers: Arc>>, - state_sender: broadcast::Sender, - server_ws_receiver: RwLock>>, - server_ws_sender: broadcast::Sender, -} - -impl LocalWebSocket { - pub fn new( - server_ws_receiver: UnboundedReceiver, - server_ws_sender: broadcast::Sender, - ) -> Self { - let user_id = Arc::new(RwLock::new(None)); - let receivers = Arc::new(DashMap::new()); - let server_ws_receiver = RwLock::new(Some(server_ws_receiver)); - let (state_sender, _) = broadcast::channel(16); - LocalWebSocket { - user_id, - receivers, - state_sender, - server_ws_receiver, - server_ws_sender, - } - } -} - -impl FlowyRawWebSocket for LocalWebSocket { - fn initialize(&self) -> FutureResult<(), WSErrorCode> { - let mut server_ws_receiver = self - .server_ws_receiver - .write() - .take() - .expect("Only take once"); - let receivers = self.receivers.clone(); - tokio::spawn(async move { - while let Some(message) = server_ws_receiver.recv().await { - match receivers.get(&message.channel) { - None => tracing::error!("Can't find any handler for message: {:?}", message), - Some(receiver) => receiver.receive_message(message.clone()), - } - } - }); - FutureResult::new(async { Ok(()) }) - } - - fn start_connect(&self, _addr: String, user_id: i64) -> FutureResult<(), WSErrorCode> { - *self.user_id.write() = Some(user_id); - FutureResult::new(async { Ok(()) }) - } - - fn stop_connect(&self) -> FutureResult<(), WSErrorCode> { - FutureResult::new(async { Ok(()) }) - } - - fn subscribe_connect_state(&self) -> BoxFuture> { - let subscribe = self.state_sender.subscribe(); - Box::pin(async move { subscribe }) - } - - fn reconnect(&self, _count: usize) -> FutureResult<(), WSErrorCode> { - FutureResult::new(async { Ok(()) }) - } - - fn add_msg_receiver(&self, receiver: Arc) -> Result<(), WSErrorCode> { - tracing::trace!("Local web socket add ws receiver: {:?}", receiver.source()); - self.receivers.insert(receiver.source(), receiver); - Ok(()) - } - - fn ws_msg_sender(&self) -> FutureResult>, WSErrorCode> { - let ws: Arc = - Arc::new(LocalWebSocketAdaptor(self.server_ws_sender.clone())); - FutureResult::new(async move { Ok(Some(ws)) }) - } -} - -#[derive(Clone)] -struct LocalWebSocketAdaptor(broadcast::Sender); - -impl FlowyWebSocket for LocalWebSocketAdaptor { - fn send(&self, msg: WebSocketRawMessage) -> Result<(), WSErrorCode> { - let _ = self.0.send(msg); - Ok(()) - } -} diff --git a/frontend/rust-lib/flowy-net/src/request.rs b/frontend/rust-lib/flowy-net/src/request.rs index 3452aecc61..8facdf9d65 100644 --- a/frontend/rust-lib/flowy-net/src/request.rs +++ b/frontend/rust-lib/flowy-net/src/request.rs @@ -1,17 +1,20 @@ -use crate::response::HttpResponse; -use bytes::Bytes; -use flowy_client_network_config::HEADER_TOKEN; -use flowy_error::FlowyError; -use hyper::http; -use protobuf::ProtobufError; -use reqwest::{header::HeaderMap, Client, Method, Response}; use std::{ convert::{TryFrom, TryInto}, sync::Arc, time::Duration, }; + +use bytes::Bytes; +use hyper::http; +use protobuf::ProtobufError; +use reqwest::{header::HeaderMap, Client, Method, Response}; use tokio::sync::oneshot; +use flowy_error::FlowyError; + +use crate::http_server::self_host::configuration::HEADER_TOKEN; +use crate::response::HttpResponse; + pub trait ResponseMiddleware { fn receive_response(&self, token: &Option, response: &HttpResponse); } @@ -43,6 +46,7 @@ impl HttpRequestBuilder { HttpRequestBuilder::default() } + #[allow(dead_code)] pub fn middleware(mut self, middleware: Arc) -> Self where T: 'static + ResponseMiddleware + Send + Sync, @@ -147,6 +151,7 @@ impl HttpRequestBuilder { } } + #[allow(dead_code)] pub async fn option_json_response(self) -> Result, FlowyError> where T: serde::de::DeserializeOwned + 'static, diff --git a/frontend/rust-lib/flowy-net/src/response.rs b/frontend/rust-lib/flowy-net/src/response.rs index 8e84d493b0..fe88b832e9 100644 --- a/frontend/rust-lib/flowy-net/src/response.rs +++ b/frontend/rust-lib/flowy-net/src/response.rs @@ -17,6 +17,7 @@ pub struct HttpError { } impl HttpError { + #[allow(dead_code)] pub fn is_unauthorized(&self) -> bool { self.code == ErrorCode::UserUnauthorized } diff --git a/frontend/rust-lib/flowy-notification/Cargo.toml b/frontend/rust-lib/flowy-notification/Cargo.toml index a7c88fd92b..7157f375f8 100644 --- a/frontend/rust-lib/flowy-notification/Cargo.toml +++ b/frontend/rust-lib/flowy-notification/Cargo.toml @@ -12,11 +12,11 @@ tracing = { version = "0.1", features = ["log"] } bytes = { version = "1.4" } serde = "1.0" -flowy-derive = { path = "../flowy-derive" } +flowy-derive = { path = "../../../shared-lib/flowy-derive" } lib-dispatch = { path = "../lib-dispatch" } [build-dependencies] -flowy-codegen = { path = "../flowy-codegen" } +flowy-codegen = { path = "../../../shared-lib/flowy-codegen" } [features] dart = ["flowy-codegen/dart"] diff --git a/frontend/rust-lib/flowy-revision-persistence/Cargo.toml b/frontend/rust-lib/flowy-revision-persistence/Cargo.toml deleted file mode 100644 index 97252604d6..0000000000 --- a/frontend/rust-lib/flowy-revision-persistence/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "flowy-revision-persistence" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -flowy-error = { path = "../flowy-error" } -revision-model = { path = "../../../shared-lib/revision-model" } - -[features] -rev-file = [] diff --git a/frontend/rust-lib/flowy-revision-persistence/src/disk_cache_impl/file_persistence.rs b/frontend/rust-lib/flowy-revision-persistence/src/disk_cache_impl/file_persistence.rs deleted file mode 100644 index 6d0ae839b0..0000000000 --- a/frontend/rust-lib/flowy-revision-persistence/src/disk_cache_impl/file_persistence.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::{RevisionChangeset, RevisionDiskCache, SyncRecord}; -use flowy_error::FlowyResult; -use revision_model::RevisionRange; - -pub struct FileRevisionDiskCache { - path: String, -} - -pub type FileRevisionDiskCacheConnection = (); - -impl RevisionDiskCache for FileRevisionDiskCache { - type Error = (); - - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - Ok(()) - } - - fn get_connection(&self) -> Result { - return Ok(()); - } - - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - Ok(vec![]) - } - - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - Ok(vec![]) - } - - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - Ok(()) - } - - fn delete_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result<(), Self::Error> { - Ok(()) - } - - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error> { - todo!() - } -} diff --git a/frontend/rust-lib/flowy-revision-persistence/src/disk_cache_impl/mod.rs b/frontend/rust-lib/flowy-revision-persistence/src/disk_cache_impl/mod.rs deleted file mode 100644 index e4b00e9b19..0000000000 --- a/frontend/rust-lib/flowy-revision-persistence/src/disk_cache_impl/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(feature = "rev-file")] -pub mod file_persistence; diff --git a/frontend/rust-lib/flowy-revision-persistence/src/lib.rs b/frontend/rust-lib/flowy-revision-persistence/src/lib.rs deleted file mode 100644 index a3cc3d689a..0000000000 --- a/frontend/rust-lib/flowy-revision-persistence/src/lib.rs +++ /dev/null @@ -1,147 +0,0 @@ -mod disk_cache_impl; - -use flowy_error::{FlowyError, FlowyResult}; -use revision_model::{Revision, RevisionRange}; -use std::fmt::Debug; -use std::sync::Arc; - -pub trait RevisionDiskCache: Sync + Send { - type Error: Debug; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error>; - - fn get_connection(&self) -> Result; - - // Read all the records if the rev_ids is None - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error>; - - // Read the revision which rev_id >= range.start && rev_id <= range.end - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error>; - - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()>; - - // Delete all the records if the rev_ids is None - fn delete_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result<(), Self::Error>; - - // Delete and insert will be executed in the same transaction. - // It deletes all the records if the deleted_rev_ids is None and then insert the new records - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error>; -} - -impl RevisionDiskCache for Arc -where - T: RevisionDiskCache, -{ - type Error = FlowyError; - - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - (**self).create_revision_records(revision_records) - } - - fn get_connection(&self) -> Result { - (**self).get_connection() - } - - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - (**self).read_revision_records(object_id, rev_ids) - } - - fn read_revision_records_with_range( - &self, - object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - (**self).read_revision_records_with_range(object_id, range) - } - - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - (**self).update_revision_record(changesets) - } - - fn delete_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result<(), Self::Error> { - (**self).delete_revision_records(object_id, rev_ids) - } - - fn delete_and_insert_records( - &self, - object_id: &str, - deleted_rev_ids: Option>, - inserted_records: Vec, - ) -> Result<(), Self::Error> { - (**self).delete_and_insert_records(object_id, deleted_rev_ids, inserted_records) - } -} - -#[derive(Clone, Debug)] -pub struct SyncRecord { - pub revision: Revision, - pub state: RevisionState, - pub write_to_disk: bool, -} - -impl SyncRecord { - pub fn new(revision: Revision) -> Self { - Self { - revision, - state: RevisionState::Sync, - write_to_disk: true, - } - } - - pub fn ack(&mut self) { - self.state = RevisionState::Ack; - } -} - -pub struct RevisionChangeset { - pub object_id: String, - pub rev_id: i64, - pub state: RevisionState, -} - -/// Sync: revision is not synced to the server -/// Ack: revision is synced to the server -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum RevisionState { - Sync = 0, - Ack = 1, -} - -impl RevisionState { - pub fn is_need_sync(&self) -> bool { - match self { - RevisionState::Sync => true, - RevisionState::Ack => false, - } - } -} - -impl AsRef for RevisionState { - fn as_ref(&self) -> &RevisionState { - self - } -} diff --git a/frontend/rust-lib/flowy-revision/Cargo.toml b/frontend/rust-lib/flowy-revision/Cargo.toml deleted file mode 100644 index 7eb844e338..0000000000 --- a/frontend/rust-lib/flowy-revision/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "flowy-revision" -version = "0.1.0" -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -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" } -flowy-error = { path = "../flowy-error" } -flowy-revision-persistence= { path = "../flowy-revision-persistence" } -tracing = { version = "0.1", features = ["log"] } -tokio = { version = "1.26", features = ["sync"]} -bytes = { version = "1.4" } -strum = "0.21" -strum_macros = "0.21" -dashmap = "5" -serde = { version = "1.0", features = ["derive"] } -futures-util = "0.3.26" -futures = "0.3.26" -async-stream = "0.3.4" -serde_json = {version = "1.0"} - -[dev-dependencies] -nanoid = "0.4.0" -flowy-revision = {path = "../flowy-revision", features = ["flowy_unit_test"]} -serde = { version = "1.0", features = ["derive"] } -serde_json = { version = "1.0" } -parking_lot = "0.12.1" - -[features] -flowy_unit_test = [] diff --git a/frontend/rust-lib/flowy-revision/src/cache/memory.rs b/frontend/rust-lib/flowy-revision/src/cache/memory.rs deleted file mode 100644 index 9eaff2dbb6..0000000000 --- a/frontend/rust-lib/flowy-revision/src/cache/memory.rs +++ /dev/null @@ -1,154 +0,0 @@ -use crate::REVISION_WRITE_INTERVAL_IN_MILLIS; -use dashmap::DashMap; -use flowy_error::{FlowyError, FlowyResult}; -use flowy_revision_persistence::SyncRecord; -use revision_model::RevisionRange; -use std::{borrow::Cow, sync::Arc, time::Duration}; -use tokio::{sync::RwLock, task::JoinHandle}; - -pub(crate) trait RevisionMemoryCacheDelegate: Send + Sync { - fn send_sync(&self, records: Vec) -> FlowyResult<()>; - fn receive_ack(&self, object_id: &str, rev_id: i64); -} - -pub(crate) struct RevisionMemoryCache { - object_id: String, - revs_map: Arc>, - delegate: Arc, - defer_write_revs: Arc>>, - defer_save: RwLock>>, -} - -impl RevisionMemoryCache { - pub(crate) fn new(object_id: &str, delegate: Arc) -> Self { - RevisionMemoryCache { - object_id: object_id.to_owned(), - revs_map: Arc::new(DashMap::new()), - delegate, - defer_write_revs: Arc::new(RwLock::new(vec![])), - defer_save: RwLock::new(None), - } - } - - pub(crate) fn contains(&self, rev_id: &i64) -> bool { - self.revs_map.contains_key(rev_id) - } - - pub(crate) async fn add<'a>(&'a self, record: Cow<'a, SyncRecord>) { - let record = match record { - Cow::Borrowed(record) => record.clone(), - Cow::Owned(record) => record, - }; - - let rev_id = record.revision.rev_id; - self.revs_map.insert(rev_id, record); - - let mut write_guard = self.defer_write_revs.write().await; - if !write_guard.contains(&rev_id) { - write_guard.push(rev_id); - drop(write_guard); - self.tick_checkpoint().await; - } - } - - pub(crate) async fn ack(&self, rev_id: &i64) { - match self.revs_map.get_mut(rev_id) { - None => {}, - Some(mut record) => record.ack(), - } - - if self.defer_write_revs.read().await.contains(rev_id) { - self.tick_checkpoint().await; - } else { - // The revision must be saved on disk if the pending_write_revs - // doesn't contains the rev_id. - self.delegate.receive_ack(&self.object_id, *rev_id); - } - } - - pub(crate) async fn get(&self, rev_id: &i64) -> Option { - self.revs_map.get(rev_id).map(|r| r.value().clone()) - } - - pub(crate) fn remove(&self, rev_id: &i64) { - let _ = self.revs_map.remove(rev_id); - } - - pub(crate) fn remove_with_range(&self, range: &RevisionRange) { - for rev_id in range.iter() { - self.remove(&rev_id); - } - } - - pub(crate) async fn get_with_range( - &self, - range: &RevisionRange, - ) -> Result, FlowyError> { - let revs = range - .iter() - .flat_map(|rev_id| self.revs_map.get(&rev_id).map(|record| record.clone())) - .collect::>(); - Ok(revs) - } - - pub(crate) fn number_of_sync_records(&self) -> usize { - self.revs_map.len() - } - - pub(crate) async fn reset_with_revisions(&self, revision_records: Vec) { - self.revs_map.clear(); - if let Some(handler) = self.defer_save.write().await.take() { - handler.abort(); - } - - let mut write_guard = self.defer_write_revs.write().await; - write_guard.clear(); - for record in revision_records { - write_guard.push(record.revision.rev_id); - self.revs_map.insert(record.revision.rev_id, record); - } - drop(write_guard); - - self.tick_checkpoint().await; - } - - async fn tick_checkpoint(&self) { - // https://github.com/async-graphql/async-graphql/blob/ed8449beec3d9c54b94da39bab33cec809903953/src/dataloader/mod.rs#L362 - if let Some(handler) = self.defer_save.write().await.take() { - handler.abort(); - } - - if self.defer_write_revs.read().await.is_empty() { - return; - } - - let rev_map = self.revs_map.clone(); - let pending_write_revs = self.defer_write_revs.clone(); - let delegate = self.delegate.clone(); - - *self.defer_save.write().await = Some(tokio::spawn(async move { - tokio::time::sleep(Duration::from_millis(REVISION_WRITE_INTERVAL_IN_MILLIS)).await; - let mut revs_write_guard = pending_write_revs.write().await; - // It may cause performance issues because we hold the write lock of the - // rev_order and the lock will be released after the checkpoint has been written - // to the disk. - // - // Use saturating_sub and split_off ? - // https://stackoverflow.com/questions/28952411/what-is-the-idiomatic-way-to-pop-the-last-n-elements-in-a-mutable-vec - let mut save_records: Vec = vec![]; - revs_write_guard - .iter() - .for_each(|rev_id| match rev_map.get(rev_id) { - None => {}, - Some(value) => { - save_records.push(value.value().clone()); - }, - }); - - if delegate.send_sync(save_records).is_ok() { - revs_write_guard.clear(); - drop(revs_write_guard); - } - })); - } -} diff --git a/frontend/rust-lib/flowy-revision/src/cache/mod.rs b/frontend/rust-lib/flowy-revision/src/cache/mod.rs deleted file mode 100644 index c9d1f8bd22..0000000000 --- a/frontend/rust-lib/flowy-revision/src/cache/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub(crate) mod memory; -pub mod reset; diff --git a/frontend/rust-lib/flowy-revision/src/cache/reset.rs b/frontend/rust-lib/flowy-revision/src/cache/reset.rs deleted file mode 100644 index b6179e7022..0000000000 --- a/frontend/rust-lib/flowy-revision/src/cache/reset.rs +++ /dev/null @@ -1,117 +0,0 @@ -use crate::{RevisionLoader, RevisionPersistence, RevisionPersistenceConfiguration}; -use bytes::Bytes; -use flowy_error::{FlowyError, FlowyResult}; -use flowy_revision_persistence::{RevisionDiskCache, SyncRecord}; -use revision_model::Revision; -use serde::{Deserialize, Serialize}; -use std::str::FromStr; -use std::sync::Arc; - -pub trait RevisionResettable { - fn target_id(&self) -> &str; - - // String in json format - fn reset_data(&self, revisions: Vec) -> FlowyResult; - - // String in json format - fn default_target_rev_str(&self) -> FlowyResult; - - fn read_record(&self) -> Option; - - fn set_record(&self, record: String); -} - -pub struct RevisionStructReset { - target: T, - disk_cache: Arc>, -} - -impl RevisionStructReset -where - T: RevisionResettable, - C: 'static, -{ - pub fn new(object: T, disk_cache: Arc>) -> Self { - Self { - target: object, - disk_cache, - } - } - - pub async fn run(&self) -> FlowyResult<()> { - match self.target.read_record() { - None => { - self.reset_object().await?; - self.save_migrate_record()?; - }, - Some(s) => { - let mut record = - MigrationObjectRecord::from_str(&s).map_err(|e| FlowyError::serde().context(e))?; - let rev_str = self.target.default_target_rev_str()?; - if record.len < rev_str.len() { - self.reset_object().await?; - record.len = rev_str.len(); - self.target.set_record(record.to_string()); - } - }, - } - Ok(()) - } - - async fn reset_object(&self) -> FlowyResult<()> { - let configuration = RevisionPersistenceConfiguration::new(2, false); - let rev_persistence = Arc::new(RevisionPersistence::from_disk_cache( - self.target.target_id(), - self.disk_cache.clone(), - configuration, - )); - let revisions = RevisionLoader { - object_id: self.target.target_id().to_owned(), - cloud: None, - rev_persistence, - } - .load_revisions() - .await?; - - let bytes = self.target.reset_data(revisions)?; - let revision = Revision::initial_revision(self.target.target_id(), bytes); - let record = SyncRecord::new(revision); - - tracing::trace!("Reset {} revision record object", self.target.target_id()); - let _ = self - .disk_cache - .delete_and_insert_records(self.target.target_id(), None, vec![record]); - - Ok(()) - } - - fn save_migrate_record(&self) -> FlowyResult<()> { - let rev_str = self.target.default_target_rev_str()?; - let record = MigrationObjectRecord { - object_id: self.target.target_id().to_owned(), - len: rev_str.len(), - }; - self.target.set_record(record.to_string()); - Ok(()) - } -} - -#[derive(Serialize, Deserialize)] -struct MigrationObjectRecord { - object_id: String, - len: usize, -} - -impl FromStr for MigrationObjectRecord { - type Err = serde_json::Error; - - fn from_str(s: &str) -> Result { - serde_json::from_str::(s) - } -} - -impl ToString for MigrationObjectRecord { - fn to_string(&self) -> String { - serde_json::to_string(self).unwrap_or_else(|_| "".to_string()) - } -} diff --git a/frontend/rust-lib/flowy-revision/src/conflict_resolve.rs b/frontend/rust-lib/flowy-revision/src/conflict_resolve.rs deleted file mode 100644 index 83442ed351..0000000000 --- a/frontend/rust-lib/flowy-revision/src/conflict_resolve.rs +++ /dev/null @@ -1,177 +0,0 @@ -use crate::{RevisionMD5, RevisionManager}; -use bytes::Bytes; -use flowy_error::{FlowyError, FlowyResult}; -use lib_infra::future::BoxResultFuture; -use revision_model::{Revision, RevisionRange}; -use std::sync::Arc; - -pub struct TransformOperations { - pub client_operations: Operations, - pub server_operations: Option, -} - -pub trait OperationsDeserializer: Send + Sync { - fn deserialize_revisions(revisions: Vec) -> FlowyResult; -} - -pub trait OperationsSerializer: Send + Sync { - fn serialize_operations(&self) -> Bytes; -} - -pub struct ConflictOperations(T); -pub trait ConflictResolver -where - Operations: Send + Sync, -{ - fn compose_operations(&self, operations: Operations) -> BoxResultFuture; - fn transform_operations( - &self, - operations: Operations, - ) -> BoxResultFuture, FlowyError>; - fn reset_operations(&self, operations: Operations) -> BoxResultFuture; -} - -pub trait ConflictRevisionSink: Send + Sync + 'static { - fn send(&self, revisions: Vec) -> BoxResultFuture<(), FlowyError>; - fn ack(&self, rev_id: i64) -> BoxResultFuture<(), FlowyError>; -} - -pub struct ConflictController -where - Operations: Send + Sync, -{ - resolver: Arc + Send + Sync>, - rev_sink: Arc, - rev_manager: Arc>, -} - -impl ConflictController -where - Operations: Clone + Send + Sync, - Connection: 'static, -{ - pub fn new( - resolver: Arc + Send + Sync>, - rev_sink: Arc, - rev_manager: Arc>, - ) -> Self { - Self { - resolver, - rev_sink, - rev_manager, - } - } -} - -impl ConflictController -where - Operations: OperationsSerializer + OperationsDeserializer + Clone + Send + Sync, - Connection: Send + Sync + 'static, -{ - pub async fn receive_revisions(&self, revisions: Vec) -> FlowyResult<()> { - if revisions.is_empty() { - return Ok(()); - } - - match self.handle_revision(revisions).await? { - None => {}, - Some(server_revision) => { - self.rev_sink.send(vec![server_revision]).await?; - }, - } - Ok(()) - } - - pub async fn ack_revision(&self, rev_id: i64) -> FlowyResult<()> { - self.rev_sink.ack(rev_id).await?; - Ok(()) - } - - pub async fn send_revisions(&self, range: RevisionRange) -> FlowyResult<()> { - let revisions = self.rev_manager.get_revisions_in_range(range).await?; - self.rev_sink.send(revisions).await?; - Ok(()) - } - - async fn handle_revision(&self, mut revisions: Vec) -> FlowyResult> { - let first_revision = revisions.first().unwrap(); - if let Some(local_revision) = self.rev_manager.get_revision(first_revision.rev_id).await { - if local_revision.md5 == first_revision.md5 { - // The local revision is equal to the pushed revision. Just ignore it. - revisions = revisions.split_off(1); - if revisions.is_empty() { - return Ok(None); - } - } else { - return Ok(None); - } - } - - let new_operations = Operations::deserialize_revisions(revisions.clone())?; - let TransformOperations { - client_operations, - server_operations, - } = self.resolver.transform_operations(new_operations).await?; - - match server_operations { - None => { - // The server_prime is None means the client local revisions conflict with the - // // server, and it needs to override the client delta. - let md5 = self.resolver.reset_operations(client_operations).await?; - debug_assert!(md5.is_equal(&revisions.last().unwrap().md5)); - self.rev_manager.reset_object(revisions).await?; - Ok(None) - }, - Some(server_operations) => { - let md5 = self - .resolver - .compose_operations(client_operations.clone()) - .await?; - for revision in &revisions { - self.rev_manager.add_remote_revision(revision).await?; - } - let (client_revision, server_revision) = make_client_and_server_revision( - &self.rev_manager, - client_operations, - Some(server_operations), - md5, - ); - self - .rev_manager - .add_remote_revision(&client_revision) - .await?; - Ok(server_revision) - }, - } - } -} - -fn make_client_and_server_revision( - rev_manager: &Arc>, - client_operations: Operations, - server_operations: Option, - md5: RevisionMD5, -) -> (Revision, Option) -where - Operations: OperationsSerializer, - Connection: 'static, -{ - let (base_rev_id, rev_id) = rev_manager.next_rev_id_pair(); - let bytes = client_operations.serialize_operations(); - let client_revision = Revision::new( - &rev_manager.object_id, - base_rev_id, - rev_id, - bytes, - md5.clone(), - ); - - match server_operations { - None => (client_revision, None), - Some(operations) => { - let bytes = operations.serialize_operations(); - let server_revision = Revision::new(&rev_manager.object_id, base_rev_id, rev_id, bytes, md5); - (client_revision, Some(server_revision)) - }, - } -} diff --git a/frontend/rust-lib/flowy-revision/src/history/mod.rs b/frontend/rust-lib/flowy-revision/src/history/mod.rs deleted file mode 100644 index 9de42831ed..0000000000 --- a/frontend/rust-lib/flowy-revision/src/history/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod persistence; -mod rev_history; - -pub use persistence::*; -pub use rev_history::*; diff --git a/frontend/rust-lib/flowy-revision/src/history/persistence.rs b/frontend/rust-lib/flowy-revision/src/history/persistence.rs deleted file mode 100644 index c40ca02664..0000000000 --- a/frontend/rust-lib/flowy-revision/src/history/persistence.rs +++ /dev/null @@ -1,78 +0,0 @@ -use crate::history::RevisionHistoryDiskCache; -use flowy_error::{internal_error, FlowyResult}; -use flowy_sqlite::{ - prelude::*, - schema::{rev_history, rev_history::dsl}, - ConnectionPool, -}; -use revision_model::Revision; -use std::sync::Arc; - -pub struct SQLiteRevisionHistoryPersistence { - object_id: String, - pool: Arc, -} - -impl SQLiteRevisionHistoryPersistence { - pub fn new(object_id: &str, pool: Arc) -> Self { - let object_id = object_id.to_owned(); - Self { object_id, pool } - } -} - -impl RevisionHistoryDiskCache for SQLiteRevisionHistoryPersistence { - fn write_history(&self, revision: Revision) -> FlowyResult<()> { - let record = ( - dsl::object_id.eq(revision.object_id), - dsl::start_rev_id.eq(revision.base_rev_id), - dsl::end_rev_id.eq(revision.rev_id), - dsl::data.eq(revision.delta_data), - ); - let conn = self.pool.get().map_err(internal_error)?; - - let _ = insert_or_ignore_into(dsl::rev_history) - .values(vec![record]) - .execute(&*conn)?; - Ok(()) - } - - fn read_histories(&self) -> FlowyResult> { - let conn = self.pool.get().map_err(internal_error)?; - let records: Vec = dsl::rev_history - .filter(dsl::object_id.eq(&self.object_id)) - .load::(&*conn)?; - - Ok(records - .into_iter() - .map(|record| record.into()) - .collect::>()) - } -} - -#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] -#[table_name = "rev_history"] -struct RevisionRecord { - id: i32, - object_id: String, - start_rev_id: i64, - end_rev_id: i64, - data: Vec, -} - -pub struct RevisionHistory { - pub object_id: String, - pub start_rev_id: i64, - pub end_rev_id: i64, - pub data: Vec, -} - -impl std::convert::From for RevisionHistory { - fn from(record: RevisionRecord) -> Self { - RevisionHistory { - object_id: record.object_id, - start_rev_id: record.start_rev_id, - end_rev_id: record.end_rev_id, - data: record.data, - } - } -} diff --git a/frontend/rust-lib/flowy-revision/src/history/rev_history.rs b/frontend/rust-lib/flowy-revision/src/history/rev_history.rs deleted file mode 100644 index c18a91733d..0000000000 --- a/frontend/rust-lib/flowy-revision/src/history/rev_history.rs +++ /dev/null @@ -1,201 +0,0 @@ -use crate::{RevisionCompactor, RevisionHistory}; -use async_stream::stream; - -use flowy_error::{FlowyError, FlowyResult}; -use futures_util::future::BoxFuture; -use futures_util::stream::StreamExt; -use futures_util::FutureExt; -use revision_model::Revision; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::{mpsc, RwLock}; -use tokio::time::interval; - -pub trait RevisionHistoryDiskCache: Send + Sync { - fn write_history(&self, revision: Revision) -> FlowyResult<()>; - - fn read_histories(&self) -> FlowyResult>; -} - -pub struct RevisionHistoryManager { - user_id: String, - stop_tx: mpsc::Sender<()>, - config: RevisionHistoryConfig, - revisions: Arc>>, - disk_cache: Arc, -} - -impl RevisionHistoryManager { - pub fn new( - user_id: &str, - object_id: &str, - config: RevisionHistoryConfig, - disk_cache: Arc, - rev_compactor: Arc, - ) -> Self { - let revisions = Arc::new(RwLock::new(vec![])); - let stop_tx = - spawn_history_checkpoint_runner(user_id, object_id, &disk_cache, &revisions, rev_compactor, &config); - let user_id = user_id.to_owned(); - Self { - user_id, - stop_tx, - config, - revisions, - disk_cache, - } - } - - pub async fn add_revision(&self, revision: &Revision) { - self.revisions.write().await.push(revision.clone()); - } - - pub async fn read_revision_histories(&self) -> FlowyResult> { - self.disk_cache.read_histories() - } -} - -pub struct RevisionHistoryConfig { - check_duration: Duration, -} - -impl std::default::Default for RevisionHistoryConfig { - fn default() -> Self { - Self { - check_duration: Duration::from_secs(5), - } - } -} - -fn spawn_history_checkpoint_runner( - user_id: &str, - object_id: &str, - disk_cache: &Arc, - revisions: &Arc>>, - rev_compactor: Arc, - config: &RevisionHistoryConfig, -) -> mpsc::Sender<()> { - let user_id = user_id.to_string(); - let object_id = object_id.to_string(); - let disk_cache = disk_cache.clone(); - let revisions = revisions.clone(); - - let (checkpoint_tx, checkpoint_rx) = mpsc::channel(1); - let (stop_tx, stop_rx) = mpsc::channel(1); - let checkpoint_sender = FixedDurationCheckpointSender { - user_id, - object_id, - checkpoint_tx, - disk_cache, - revisions, - rev_compactor, - duration: config.check_duration, - }; - tokio::spawn(HistoryCheckpointRunner::new(stop_rx, checkpoint_rx).run()); - tokio::spawn(checkpoint_sender.run()); - stop_tx -} - -struct HistoryCheckpointRunner { - stop_rx: Option>, - checkpoint_rx: Option>, -} - -impl HistoryCheckpointRunner { - fn new(stop_rx: mpsc::Receiver<()>, checkpoint_rx: mpsc::Receiver) -> Self { - Self { - stop_rx: Some(stop_rx), - checkpoint_rx: Some(checkpoint_rx), - } - } - - async fn run(mut self) { - let mut stop_rx = self.stop_rx.take().expect("It should only run once"); - let mut checkpoint_rx = self.checkpoint_rx.take().expect("It should only run once"); - let stream = stream! { - loop { - tokio::select! { - result = checkpoint_rx.recv() => { - match result { - Some(checkpoint) => yield checkpoint, - None => {}, - } - }, - _ = stop_rx.recv() => { - tracing::trace!("Checkpoint runner exit"); - break - }, - }; - } - }; - - stream - .for_each(|checkpoint| async move { - checkpoint.write().await; - }) - .await; - } -} - -struct HistoryCheckpoint { - user_id: String, - object_id: String, - revisions: Vec, - disk_cache: Arc, - rev_compactor: Arc, -} - -impl HistoryCheckpoint { - async fn write(self) { - if self.revisions.is_empty() { - return; - } - - let result = || { - let revision = self - .rev_compactor - .compact(&self.user_id, &self.object_id, self.revisions)?; - let _ = self.disk_cache.write_history(revision)?; - Ok::<(), FlowyError>(()) - }; - - match result() { - Ok(_) => {} - Err(e) => tracing::error!("Write history checkout failed: {:?}", e), - } - } -} - -struct FixedDurationCheckpointSender { - user_id: String, - object_id: String, - checkpoint_tx: mpsc::Sender, - disk_cache: Arc, - revisions: Arc>>, - rev_compactor: Arc, - duration: Duration, -} - -impl FixedDurationCheckpointSender { - fn run(self) -> BoxFuture<'static, ()> { - async move { - let mut interval = interval(self.duration); - let checkpoint_revisions: Vec = self.revisions.write().await.drain(..).collect(); - let checkpoint = HistoryCheckpoint { - user_id: self.user_id.clone(), - object_id: self.object_id.clone(), - revisions: checkpoint_revisions, - disk_cache: self.disk_cache.clone(), - rev_compactor: self.rev_compactor.clone(), - }; - match self.checkpoint_tx.send(checkpoint).await { - Ok(_) => { - interval.tick().await; - self.run(); - } - Err(_) => {} - } - } - .boxed() - } -} diff --git a/frontend/rust-lib/flowy-revision/src/lib.rs b/frontend/rust-lib/flowy-revision/src/lib.rs deleted file mode 100644 index e3f4e57295..0000000000 --- a/frontend/rust-lib/flowy-revision/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -mod cache; -mod conflict_resolve; -mod rev_manager; -mod rev_persistence; -mod rev_queue; -mod rev_snapshot; -mod ws_manager; - -pub use cache::*; -pub use conflict_resolve::*; -pub use rev_manager::*; -pub use rev_persistence::*; -pub use rev_snapshot::*; -pub use ws_manager::*; diff --git a/frontend/rust-lib/flowy-revision/src/rev_manager.rs b/frontend/rust-lib/flowy-revision/src/rev_manager.rs deleted file mode 100644 index b9438aaa43..0000000000 --- a/frontend/rust-lib/flowy-revision/src/rev_manager.rs +++ /dev/null @@ -1,457 +0,0 @@ -use crate::rev_queue::{RevCommandSender, RevisionCommand, RevisionQueue}; -use crate::{ - RevisionPersistence, RevisionSnapshotController, RevisionSnapshotData, - RevisionSnapshotPersistence, WSDataProviderDataSource, -}; -use bytes::Bytes; -use flowy_error::{internal_error, FlowyError, FlowyResult}; -use lib_infra::future::FutureResult; -use lib_infra::util::md5; -use revision_model::{Revision, RevisionRange}; -use std::sync::atomic::AtomicI64; -use std::sync::atomic::Ordering::SeqCst; -use std::sync::Arc; -use tokio::sync::{mpsc, oneshot}; - -pub trait RevisionCloudService: Send + Sync { - /// Read the object's revision from remote - /// Returns a list of revisions that used to build the object - /// # Arguments - /// - /// * `user_id`: the id of the user - /// * `object_id`: the id of the object - /// - fn fetch_object(&self, user_id: &str, object_id: &str) - -> FutureResult, FlowyError>; -} - -pub trait RevisionObjectDeserializer: Send + Sync { - type Output; - /// Deserialize the list of revisions into an concrete object type. - /// - /// # Arguments - /// - /// * `object_id`: the id of the object - /// * `revisions`: a list of revisions that represent the object - /// - fn deserialize_revisions(object_id: &str, revisions: Vec) -> FlowyResult; - - fn recover_from_revisions(revisions: Vec) -> Option<(Self::Output, i64)>; -} - -pub trait RevisionObjectSerializer: Send + Sync { - /// Serialize a list of revisions into one in `Bytes` format - /// - /// * `revisions`: a list of revisions will be serialized to `Bytes` - /// - fn combine_revisions(revisions: Vec) -> FlowyResult; -} - -/// `RevisionCompress` is used to compress multiple revisions into one revision -/// -pub trait RevisionMergeable: Send + Sync { - fn merge_revisions( - &self, - object_id: &str, - mut revisions: Vec, - ) -> FlowyResult { - if revisions.is_empty() { - return Err(FlowyError::internal().context("Can't compact the empty revisions")); - } - - if revisions.len() == 1 { - return Ok(revisions.pop().unwrap()); - } - - // Select the last version, making sure version numbers don't overlap - let last_revision = revisions.last().unwrap(); - let (base_rev_id, rev_id) = last_revision.pair_rev_id(); - let md5 = last_revision.md5.clone(); - let bytes = self.combine_revisions(revisions)?; - Ok(Revision::new(object_id, base_rev_id, rev_id, bytes, md5)) - } - - fn combine_revisions(&self, revisions: Vec) -> FlowyResult; -} - -pub struct RevisionManager { - pub object_id: String, - rev_id_counter: Arc, - rev_persistence: Arc>, - rev_snapshot: Arc>, - rev_compress: Arc, - #[cfg(feature = "flowy_unit_test")] - rev_ack_notifier: tokio::sync::broadcast::Sender, - rev_queue: RevCommandSender, -} - -impl RevisionManager { - pub fn new( - object_id: &str, - rev_persistence: RevisionPersistence, - rev_compress: Compress, - snapshot_persistence: Snapshot, - ) -> Self - where - Snapshot: 'static + RevisionSnapshotPersistence, - Compress: 'static + RevisionMergeable, - { - let rev_id_counter = Arc::new(RevIdCounter::new(0)); - let rev_compress = Arc::new(rev_compress); - let rev_persistence = Arc::new(rev_persistence); - let rev_snapshot = RevisionSnapshotController::new( - object_id, - snapshot_persistence, - rev_id_counter.clone(), - rev_persistence.clone(), - rev_compress.clone(), - ); - let (rev_queue, receiver) = mpsc::channel(1000); - let queue = RevisionQueue::new( - object_id.to_owned(), - rev_id_counter.clone(), - rev_persistence.clone(), - rev_compress.clone(), - receiver, - ); - tokio::spawn(queue.run()); - Self { - object_id: object_id.to_string(), - rev_id_counter, - rev_persistence, - rev_snapshot: Arc::new(rev_snapshot), - rev_compress, - #[cfg(feature = "flowy_unit_test")] - rev_ack_notifier: tokio::sync::broadcast::channel(1).0, - rev_queue, - } - } - - #[tracing::instrument(name = "revision_manager_initialize", level = "trace", skip_all, fields(deserializer, object_id, deserialize_revisions) err)] - pub async fn initialize( - &mut self, - _cloud: Option>, - ) -> FlowyResult - where - De: RevisionObjectDeserializer, - { - let revision_records = self.rev_persistence.load_all_records(&self.object_id)?; - tracing::Span::current().record("object_id", self.object_id.as_str()); - tracing::Span::current().record("deserializer", std::any::type_name::()); - let revisions: Vec = revision_records - .iter() - .map(|record| record.revision.clone()) - .collect(); - tracing::Span::current().record("deserialize_revisions", revisions.len()); - let last_rev_id = revisions - .last() - .as_ref() - .map(|revision| revision.rev_id) - .unwrap_or(0); - match De::deserialize_revisions(&self.object_id, revisions.clone()) { - Ok(object) => { - self - .rev_persistence - .sync_revision_records(&revision_records) - .await?; - self.rev_id_counter.set(last_rev_id); - Ok(object) - }, - Err(e) => match self.rev_snapshot.restore_from_snapshot::(last_rev_id) { - None => { - tracing::info!("[Restore] iterate restore from each revision"); - let (output, recover_rev_id) = De::recover_from_revisions(revisions).ok_or(e)?; - tracing::info!( - "[Restore] last_rev_id:{}, recover_rev_id: {}", - last_rev_id, - recover_rev_id - ); - self.rev_id_counter.set(recover_rev_id); - // delete the revisions whose rev_id is greater than recover_rev_id - if recover_rev_id < last_rev_id { - let range = RevisionRange { - start: recover_rev_id + 1, - end: last_rev_id, - }; - tracing::info!("[Restore] delete revisions in range: {}", range); - let _ = self.rev_persistence.delete_revisions_from_range(range); - } - Ok(output) - }, - Some((object, snapshot_rev)) => { - let snapshot_rev_id = snapshot_rev.rev_id; - let _ = self.rev_persistence.reset(vec![snapshot_rev]).await; - // revision_records.retain(|record| record.revision.rev_id <= snapshot_rev_id); - // let _ = self.rev_persistence.sync_revision_records(&revision_records).await?; - self.rev_id_counter.set(snapshot_rev_id); - Ok(object) - }, - }, - } - } - - pub async fn close(&self) { - let _ = self - .rev_persistence - .merge_lagging_revisions(&self.rev_compress) - .await; - } - - pub async fn generate_snapshot(&self) { - self.rev_snapshot.generate_snapshot().await; - } - - pub async fn read_snapshot( - &self, - rev_id: Option, - ) -> FlowyResult> { - match rev_id { - None => self.rev_snapshot.read_last_snapshot(), - Some(rev_id) => self.rev_snapshot.read_snapshot(rev_id), - } - } - - pub async fn load_revisions(&self) -> FlowyResult> { - let revisions = RevisionLoader { - object_id: self.object_id.clone(), - cloud: None, - rev_persistence: self.rev_persistence.clone(), - } - .load_revisions() - .await?; - Ok(revisions) - } - - #[tracing::instrument(level = "trace", skip(self, revisions), err)] - pub async fn reset_object(&self, revisions: Vec) -> FlowyResult<()> { - let rev_id = pair_rev_id_from_revisions(&revisions).1; - self.rev_persistence.reset(revisions).await?; - self.rev_id_counter.set(rev_id); - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self, revision), err)] - pub async fn add_remote_revision(&self, revision: &Revision) -> Result<(), FlowyError> { - if revision.bytes.is_empty() { - return Err(FlowyError::internal().context("Remote revisions is empty")); - } - - self.rev_persistence.add_ack_revision(revision).await?; - self.rev_id_counter.set(revision.rev_id); - Ok(()) - } - - /// Adds the revision that generated by user editing - // #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn add_local_revision( - &self, - data: Bytes, - object_md5: String, - ) -> Result { - if data.is_empty() { - return Err(FlowyError::internal().context("The data of the revisions is empty")); - } - self.rev_snapshot.generate_snapshot_if_need(); - let (ret, rx) = oneshot::channel(); - self - .rev_queue - .send(RevisionCommand::RevisionData { - data, - object_md5, - ret, - }) - .await - .map_err(internal_error)?; - rx.await.map_err(internal_error)? - } - - #[tracing::instrument(level = "debug", skip(self), err)] - pub async fn ack_revision(&self, rev_id: i64) -> Result<(), FlowyError> { - if self.rev_persistence.ack_revision(rev_id).await.is_ok() { - #[cfg(feature = "flowy_unit_test")] - let _ = self.rev_ack_notifier.send(rev_id); - } - Ok(()) - } - - /// Returns the current revision id - pub fn rev_id(&self) -> i64 { - self.rev_id_counter.value() - } - - pub async fn next_sync_rev_id(&self) -> Option { - self.rev_persistence.next_sync_rev_id().await - } - - pub fn next_rev_id_pair(&self) -> (i64, i64) { - let cur = self.rev_id_counter.value(); - let next = self.rev_id_counter.next_id(); - (cur, next) - } - - pub fn number_of_sync_revisions(&self) -> usize { - self.rev_persistence.number_of_sync_records() - } - - pub fn number_of_revisions_in_disk(&self) -> usize { - self.rev_persistence.number_of_records_in_disk() - } - - pub async fn get_revisions_in_range( - &self, - range: RevisionRange, - ) -> Result, FlowyError> { - let revisions = self.rev_persistence.revisions_in_range(&range).await?; - Ok(revisions) - } - - pub async fn next_sync_revision(&self) -> FlowyResult> { - self.rev_persistence.next_sync_revision().await - } - - pub async fn get_revision(&self, rev_id: i64) -> Option { - self - .rev_persistence - .get(rev_id) - .await - .map(|record| record.revision) - } -} - -impl WSDataProviderDataSource for Arc> { - fn next_revision(&self) -> FutureResult, FlowyError> { - let rev_manager = self.clone(); - FutureResult::new(async move { rev_manager.next_sync_revision().await }) - } - - fn ack_revision(&self, rev_id: i64) -> FutureResult<(), FlowyError> { - let rev_manager = self.clone(); - FutureResult::new(async move { (*rev_manager).ack_revision(rev_id).await }) - } - - fn current_rev_id(&self) -> i64 { - self.rev_id() - } -} - -#[cfg(feature = "flowy_unit_test")] -impl RevisionManager { - pub async fn revision_cache(&self) -> Arc> { - self.rev_persistence.clone() - } - pub fn ack_notify(&self) -> tokio::sync::broadcast::Receiver { - self.rev_ack_notifier.subscribe() - } - pub fn get_all_revision_records( - &self, - ) -> FlowyResult> { - self.rev_persistence.load_all_records(&self.object_id) - } -} - -pub struct RevisionLoader { - pub object_id: String, - pub cloud: Option>, - pub rev_persistence: Arc>, -} - -impl RevisionLoader { - pub async fn load_revisions(&self) -> Result, FlowyError> { - let records = self.rev_persistence.load_all_records(&self.object_id)?; - let revisions = records - .into_iter() - .map(|record| record.revision) - .collect::<_>(); - Ok(revisions) - } -} - -/// Represents as the md5 of the revision object after applying the -/// revision. For example, RevisionMD5 will be the md5 of the document -/// content. -#[derive(Debug, Clone)] -pub struct RevisionMD5(String); - -impl RevisionMD5 { - pub fn from_bytes>(bytes: T) -> Result { - Ok(RevisionMD5(md5(bytes))) - } - - pub fn into_inner(self) -> String { - self.0 - } - - pub fn is_equal(&self, s: &str) -> bool { - self.0 == s - } -} - -impl std::convert::From for String { - fn from(md5: RevisionMD5) -> Self { - md5.0 - } -} - -impl std::convert::From<&str> for RevisionMD5 { - fn from(s: &str) -> Self { - Self(s.to_owned()) - } -} -impl std::convert::From for RevisionMD5 { - fn from(s: String) -> Self { - Self(s) - } -} - -impl std::ops::Deref for RevisionMD5 { - type Target = String; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl PartialEq for RevisionMD5 { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl std::cmp::Eq for RevisionMD5 {} - -fn pair_rev_id_from_revisions(revisions: &[Revision]) -> (i64, i64) { - let mut rev_id = 0; - revisions.iter().for_each(|revision| { - if rev_id < revision.rev_id { - rev_id = revision.rev_id; - } - }); - - if rev_id > 0 { - (rev_id - 1, rev_id) - } else { - (0, rev_id) - } -} - -#[derive(Debug)] -pub struct RevIdCounter(pub AtomicI64); - -impl RevIdCounter { - pub fn new(n: i64) -> Self { - Self(AtomicI64::new(n)) - } - - pub fn next_id(&self) -> i64 { - let _ = self.0.fetch_add(1, SeqCst); - self.value() - } - - pub fn value(&self) -> i64 { - self.0.load(SeqCst) - } - - pub fn set(&self, n: i64) { - let _ = self.0.fetch_update(SeqCst, SeqCst, |_| Some(n)); - } -} diff --git a/frontend/rust-lib/flowy-revision/src/rev_persistence.rs b/frontend/rust-lib/flowy-revision/src/rev_persistence.rs deleted file mode 100644 index 59c0b1209b..0000000000 --- a/frontend/rust-lib/flowy-revision/src/rev_persistence.rs +++ /dev/null @@ -1,478 +0,0 @@ -use crate::cache::memory::RevisionMemoryCacheDelegate; -use crate::memory::RevisionMemoryCache; -use crate::RevisionMergeable; -use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_revision_persistence::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; -use revision_model::{Revision, RevisionRange}; -use std::collections::{HashMap, VecDeque}; - -use std::{borrow::Cow, sync::Arc}; -use tokio::sync::RwLock; -use tokio::task::spawn_blocking; - -pub const REVISION_WRITE_INTERVAL_IN_MILLIS: u64 = 600; - -#[derive(Clone)] -pub struct RevisionPersistenceConfiguration { - // If the number of revisions that didn't sync to the server greater than the max_merge_len - // then these revisions will be merged into one revision. - max_merge_len: usize, - - /// Indicates that the revisions that didn't sync to the server can be merged into one when - /// `merge_lagging_revisions` get called. - merge_lagging: bool, -} - -impl RevisionPersistenceConfiguration { - pub fn new(merge_max_length: usize, merge_lagging: bool) -> Self { - debug_assert!(merge_max_length > 1); - if merge_max_length > 1 { - Self { - max_merge_len: merge_max_length, - merge_lagging, - } - } else { - Self { - max_merge_len: 100, - merge_lagging, - } - } - } -} - -impl std::default::Default for RevisionPersistenceConfiguration { - fn default() -> Self { - Self { - max_merge_len: 100, - merge_lagging: false, - } - } -} - -/// Represents as the persistence of revisions including memory or disk cache. -/// The generic parameter, `Connection`, represents as the disk backend's connection. -/// If the backend is SQLite, then the Connect will be SQLiteConnect. -pub struct RevisionPersistence { - object_id: String, - disk_cache: Arc>, - memory_cache: Arc, - sync_seq: RwLock, - configuration: RevisionPersistenceConfiguration, -} - -impl RevisionPersistence -where - Connection: 'static, -{ - pub fn new( - object_id: &str, - disk_cache: C, - configuration: RevisionPersistenceConfiguration, - ) -> RevisionPersistence - where - C: 'static + RevisionDiskCache, - { - let disk_cache = - Arc::new(disk_cache) as Arc>; - Self::from_disk_cache(object_id, disk_cache, configuration) - } - - pub fn from_disk_cache( - object_id: &str, - disk_cache: Arc>, - configuration: RevisionPersistenceConfiguration, - ) -> RevisionPersistence { - let object_id = object_id.to_owned(); - let sync_seq = RwLock::new(DeferSyncSequence::new()); - let memory_cache = Arc::new(RevisionMemoryCache::new( - &object_id, - Arc::new(disk_cache.clone()), - )); - Self { - object_id, - disk_cache, - memory_cache, - sync_seq, - configuration, - } - } - - /// Save the revision that comes from remote to disk. - #[tracing::instrument(level = "trace", skip(self, revision), fields(rev_id, object_id=%self.object_id), err)] - pub(crate) async fn add_ack_revision(&self, revision: &Revision) -> FlowyResult<()> { - tracing::Span::current().record("rev_id", revision.rev_id); - self.add(revision.clone(), RevisionState::Ack, true).await - } - - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn merge_lagging_revisions<'a>( - &'a self, - rev_compress: &Arc, - ) -> FlowyResult<()> { - if !self.configuration.merge_lagging { - return Ok(()); - } - - let mut sync_seq = self.sync_seq.write().await; - let compact_seq = sync_seq.pop_merge_rev_ids(); - if !compact_seq.is_empty() { - let range = RevisionRange { - start: *compact_seq.front().unwrap(), - end: *compact_seq.back().unwrap(), - }; - - let revisions = self.revisions_in_range(&range).await?; - let rev_ids = range.to_rev_ids(); - debug_assert_eq!(range.len() as usize, revisions.len()); - // compact multiple revisions into one - let merged_revision = rev_compress.merge_revisions(&self.object_id, revisions)?; - tracing::Span::current().record("rev_id", merged_revision.rev_id); - - let record = SyncRecord { - revision: merged_revision, - state: RevisionState::Sync, - write_to_disk: true, - }; - self - .disk_cache - .delete_and_insert_records(&self.object_id, Some(rev_ids), vec![record])?; - } - Ok(()) - } - - /// Sync the each records' revisions to remote if its state is `RevisionState::Sync`. - /// - pub(crate) async fn sync_revision_records(&self, records: &[SyncRecord]) -> FlowyResult<()> { - let mut sync_seq = self.sync_seq.write().await; - for record in records { - if record.state == RevisionState::Sync { - self - .add(record.revision.clone(), RevisionState::Sync, false) - .await?; - sync_seq.recv(record.revision.rev_id)?; // Sync the records if their state is RevisionState::Sync. - } - } - Ok(()) - } - - /// Save the revision to disk and append it to the end of the sync sequence. - /// The returned value,rev_id, will be different with the passed-in revision's rev_id if - /// multiple revisions are merged into one. - #[tracing::instrument(level = "trace", skip_all, fields(rev_id, compact_range, object_id=%self.object_id), err)] - pub(crate) async fn add_local_revision<'a>( - &'a self, - new_revision: Revision, - rev_compress: &Arc, - ) -> FlowyResult { - let mut sync_seq = self.sync_seq.write().await; - - // Before the new_revision is pushed into the sync_seq, we check if the current `merge_length` of the - // sync_seq is less equal to or greater than the `merge_max_length`. If yes, it's needs to merged - // with the new_revision into one revision. - let mut merge_rev_ids = VecDeque::default(); - // tracing::info!("{}", compact_seq) - if sync_seq.merge_length >= self.configuration.max_merge_len - 1 { - merge_rev_ids.extend(sync_seq.pop_merge_rev_ids()); - } - if !merge_rev_ids.is_empty() { - let range = RevisionRange { - start: *merge_rev_ids.front().unwrap(), - end: *merge_rev_ids.back().unwrap(), - }; - - tracing::Span::current().record("compact_range", format!("{}", range).as_str()); - let mut revisions = self.revisions_in_range(&range).await?; - debug_assert_eq!(range.len() as usize, revisions.len()); - // append the new revision - revisions.push(new_revision); - - // compact multiple revisions into one - let merged_revision = rev_compress.merge_revisions(&self.object_id, revisions)?; - let new_rev_id = merged_revision.rev_id; - tracing::Span::current().record("rev_id", merged_revision.rev_id); - sync_seq.recv(new_rev_id)?; - - // replace the revisions in range with compact revision - self.compact(&range, merged_revision).await?; - Ok(new_rev_id) - } else { - let rev_id = new_revision.rev_id; - tracing::Span::current().record("rev_id", rev_id); - self.add(new_revision, RevisionState::Sync, true).await?; - sync_seq.merge_recv(rev_id)?; - Ok(rev_id) - } - } - - /// Remove the revision with rev_id from the sync sequence. - pub(crate) async fn ack_revision(&self, rev_id: i64) -> FlowyResult<()> { - if self.sync_seq.write().await.ack(&rev_id).is_ok() { - self.memory_cache.ack(&rev_id).await; - } - Ok(()) - } - - pub(crate) async fn next_sync_revision(&self) -> FlowyResult> { - match self.sync_seq.read().await.next_rev_id() { - None => Ok(None), - Some(rev_id) => Ok(self.get(rev_id).await.map(|record| record.revision)), - } - } - - pub(crate) async fn next_sync_rev_id(&self) -> Option { - self.sync_seq.read().await.next_rev_id() - } - - pub(crate) fn number_of_sync_records(&self) -> usize { - self.memory_cache.number_of_sync_records() - } - - pub(crate) fn number_of_records_in_disk(&self) -> usize { - match self.disk_cache.read_revision_records(&self.object_id, None) { - Ok(records) => records.len(), - Err(e) => { - tracing::error!("Read revision records failed: {:?}", e); - 0 - }, - } - } - - /// The cache gets reset while it conflicts with the remote revisions. - #[tracing::instrument(level = "trace", skip(self, revisions), err)] - pub(crate) async fn reset(&self, revisions: Vec) -> FlowyResult<()> { - let records = revisions - .into_iter() - .map(|revision| SyncRecord { - revision, - state: RevisionState::Sync, - write_to_disk: false, - }) - .collect::>(); - - self - .disk_cache - .delete_and_insert_records(&self.object_id, None, records.clone())?; - self.memory_cache.reset_with_revisions(records).await; - self.sync_seq.write().await.clear(); - Ok(()) - } - - async fn add( - &self, - revision: Revision, - state: RevisionState, - write_to_disk: bool, - ) -> FlowyResult<()> { - if self.memory_cache.contains(&revision.rev_id) { - tracing::warn!( - "Duplicate revision: {}:{}-{:?}", - self.object_id, - revision.rev_id, - state - ); - return Ok(()); - } - let record = SyncRecord { - revision, - state, - write_to_disk, - }; - - self.memory_cache.add(Cow::Owned(record)).await; - Ok(()) - } - - async fn compact(&self, range: &RevisionRange, new_revision: Revision) -> FlowyResult<()> { - self.memory_cache.remove_with_range(range); - let rev_ids = range.to_rev_ids(); - self - .disk_cache - .delete_revision_records(&self.object_id, Some(rev_ids))?; - self.add(new_revision, RevisionState::Sync, true).await?; - Ok(()) - } - - pub async fn get(&self, rev_id: i64) -> Option { - match self.memory_cache.get(&rev_id).await { - None => match self - .disk_cache - .read_revision_records(&self.object_id, Some(vec![rev_id])) - { - Ok(mut records) => { - let record = records.pop()?; - assert!(records.is_empty()); - Some(record) - }, - Err(e) => { - tracing::error!("{}", e); - None - }, - }, - Some(revision) => Some(revision), - } - } - - pub fn load_all_records(&self, object_id: &str) -> FlowyResult> { - let mut record_ids = HashMap::new(); - let mut records = vec![]; - for record in self.disk_cache.read_revision_records(object_id, None)? { - let rev_id = record.revision.rev_id; - if record_ids.get(&rev_id).is_none() { - records.push(record); - } - record_ids.insert(rev_id, rev_id); - } - Ok(records) - } - - // Read the revision which rev_id >= range.start && rev_id <= range.end - pub async fn revisions_in_range(&self, range: &RevisionRange) -> FlowyResult> { - let range = range.clone(); - let mut records = self.memory_cache.get_with_range(&range).await?; - let range_len = range.len() as usize; - if records.len() != range_len { - let disk_cache = self.disk_cache.clone(); - let object_id = self.object_id.clone(); - records = - spawn_blocking(move || disk_cache.read_revision_records_with_range(&object_id, &range)) - .await - .map_err(internal_error)??; - - if records.len() != range_len { - tracing::error!( - "Expect revision len {},but receive {}", - range_len, - records.len() - ); - } - } - Ok( - records - .into_iter() - .map(|record| record.revision) - .collect::>(), - ) - } - - pub fn delete_revisions_from_range(&self, range: RevisionRange) -> FlowyResult<()> { - self - .disk_cache - .delete_revision_records(&self.object_id, Some(range.to_rev_ids()))?; - Ok(()) - } -} - -impl RevisionMemoryCacheDelegate for Arc> { - fn send_sync(&self, mut records: Vec) -> FlowyResult<()> { - records.retain(|record| record.write_to_disk); - if !records.is_empty() { - tracing::Span::current().record( - "checkpoint_result", - format!("{} records were saved", records.len()).as_str(), - ); - self.create_revision_records(records)?; - } - Ok(()) - } - - fn receive_ack(&self, object_id: &str, rev_id: i64) { - let changeset = RevisionChangeset { - object_id: object_id.to_string(), - rev_id, - state: RevisionState::Ack, - }; - match self.update_revision_record(vec![changeset]) { - Ok(_) => {}, - Err(e) => tracing::error!("{}", e), - } - } -} - -#[derive(Default)] -struct DeferSyncSequence { - rev_ids: VecDeque, - merge_start: Option, - merge_length: usize, -} - -impl DeferSyncSequence { - fn new() -> Self { - DeferSyncSequence::default() - } - - /// Pushes the new_rev_id to the end of the list and marks this new_rev_id is mergeable. - /// - fn merge_recv(&mut self, new_rev_id: i64) -> FlowyResult<()> { - self.recv(new_rev_id)?; - - self.merge_length += 1; - if self.merge_start.is_none() && !self.rev_ids.is_empty() { - self.merge_start = Some(self.rev_ids.len() - 1); - } - Ok(()) - } - - /// Pushes the new_rev_id to the end of the list. - fn recv(&mut self, new_rev_id: i64) -> FlowyResult<()> { - // The last revision's rev_id must be greater than the new one. - if let Some(rev_id) = self.rev_ids.back() { - if *rev_id >= new_rev_id { - tracing::error!("The new revision's id must be greater than {}", rev_id); - return Ok(()); - } - } - self.rev_ids.push_back(new_rev_id); - Ok(()) - } - - /// Removes the rev_id from the list - fn ack(&mut self, rev_id: &i64) -> FlowyResult<()> { - let cur_rev_id = self.rev_ids.front().cloned(); - if let Some(pop_rev_id) = cur_rev_id { - if &pop_rev_id != rev_id { - let desc = format!( - "The ack rev_id:{} is not equal to the current rev_id:{}", - rev_id, pop_rev_id - ); - return Err(FlowyError::internal().context(desc)); - } - - let mut compact_rev_id = None; - if let Some(compact_index) = self.merge_start { - compact_rev_id = self.rev_ids.get(compact_index).cloned(); - } - - let pop_rev_id = self.rev_ids.pop_front(); - if let (Some(compact_rev_id), Some(pop_rev_id)) = (compact_rev_id, pop_rev_id) { - if compact_rev_id <= pop_rev_id && self.merge_length > 0 { - self.merge_length -= 1; - } - } - } - Ok(()) - } - - fn next_rev_id(&self) -> Option { - self.rev_ids.front().cloned() - } - - fn clear(&mut self) { - self.merge_start = None; - self.merge_length = 0; - self.rev_ids.clear(); - } - - // Returns the rev_ids into one except the current synchronizing rev_id. - fn pop_merge_rev_ids(&mut self) -> VecDeque { - let mut compact_seq = VecDeque::with_capacity(self.rev_ids.len()); - if let Some(start) = self.merge_start { - if start < self.rev_ids.len() { - let seq = self.rev_ids.split_off(start); - compact_seq.extend(seq); - } - } - self.merge_start = None; - self.merge_length = 0; - compact_seq - } -} diff --git a/frontend/rust-lib/flowy-revision/src/rev_queue.rs b/frontend/rust-lib/flowy-revision/src/rev_queue.rs deleted file mode 100644 index 6e848f27db..0000000000 --- a/frontend/rust-lib/flowy-revision/src/rev_queue.rs +++ /dev/null @@ -1,100 +0,0 @@ -#![allow(clippy::while_let_loop)] -use crate::{RevIdCounter, RevisionMergeable, RevisionPersistence}; -use async_stream::stream; -use bytes::Bytes; -use flowy_error::FlowyError; -use futures::stream::StreamExt; -use revision_model::Revision; -use std::sync::Arc; -use tokio::sync::mpsc::{Receiver, Sender}; -use tokio::sync::oneshot; - -#[derive(Debug)] -pub(crate) enum RevisionCommand { - RevisionData { - data: Bytes, - object_md5: String, - ret: Ret, - }, -} - -/// [RevisionQueue] is used to keep the [RevisionCommand] processing in order. -pub(crate) struct RevisionQueue { - object_id: String, - rev_id_counter: Arc, - rev_persistence: Arc>, - rev_compress: Arc, - receiver: Option, -} - -impl RevisionQueue -where - Connection: 'static, -{ - pub fn new( - object_id: String, - rev_id_counter: Arc, - rev_persistence: Arc>, - rev_compress: Arc, - receiver: RevCommandReceiver, - ) -> Self { - Self { - object_id, - rev_id_counter, - rev_persistence, - rev_compress, - receiver: Some(receiver), - } - } - - pub async fn run(mut self) { - let mut receiver = self.receiver.take().expect("Only take once"); - let object_id = self.object_id.clone(); - let stream = stream! { - loop { - match receiver.recv().await { - Some(msg) => yield msg, - None => { - tracing::trace!("{}'s RevQueue exist", &object_id); - break - }, - } - } - }; - stream - .for_each(|command| async { - match self.handle_command(command).await { - Ok(_) => {}, - Err(e) => tracing::error!("[RevQueue]: {}", e), - } - }) - .await; - } - - async fn handle_command(&self, command: RevisionCommand) -> Result<(), FlowyError> { - match command { - RevisionCommand::RevisionData { - data, - object_md5: data_md5, - ret, - } => { - let base_rev_id = self.rev_id_counter.value(); - let rev_id = self.rev_id_counter.next_id(); - let revision = Revision::new(&self.object_id, base_rev_id, rev_id, data, data_md5); - - let new_rev_id = self - .rev_persistence - .add_local_revision(revision, &self.rev_compress) - .await?; - - self.rev_id_counter.set(new_rev_id); - let _ = ret.send(Ok(new_rev_id)); - }, - } - Ok(()) - } -} - -pub(crate) type RevCommandSender = Sender; -pub(crate) type RevCommandReceiver = Receiver; -pub(crate) type Ret = oneshot::Sender>; diff --git a/frontend/rust-lib/flowy-revision/src/rev_snapshot.rs b/frontend/rust-lib/flowy-revision/src/rev_snapshot.rs deleted file mode 100644 index 6a316d58f2..0000000000 --- a/frontend/rust-lib/flowy-revision/src/rev_snapshot.rs +++ /dev/null @@ -1,181 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_variables)] -use crate::{RevIdCounter, RevisionMergeable, RevisionObjectDeserializer, RevisionPersistence}; -use bytes::Bytes; -use flowy_error::FlowyResult; -use revision_model::Revision; -use std::sync::atomic::AtomicI64; -use std::sync::atomic::Ordering::SeqCst; -use std::sync::Arc; - -pub trait RevisionSnapshotPersistence: Send + Sync { - fn should_generate_snapshot_from_range(&self, start_rev_id: i64, current_rev_id: i64) -> bool { - (current_rev_id - start_rev_id) >= AUTO_GEN_SNAPSHOT_PER_10_REVISION - } - - fn write_snapshot(&self, rev_id: i64, data: Vec) -> FlowyResult<()>; - - fn read_snapshot(&self, rev_id: i64) -> FlowyResult>; - - fn read_last_snapshot(&self) -> FlowyResult>; -} - -pub trait RevisionSnapshotDataGenerator: Send + Sync { - fn generate_snapshot_data(&self) -> Option; -} - -const AUTO_GEN_SNAPSHOT_PER_10_REVISION: i64 = 10; - -pub struct RevisionSnapshotController { - object_id: String, - rev_snapshot_persistence: Arc, - rev_snapshot_data: Option>, - rev_id_counter: Arc, - rev_persistence: Arc>, - rev_compress: Arc, - start_rev_id: AtomicI64, -} - -impl RevisionSnapshotController -where - Connection: 'static, -{ - pub fn new( - object_id: &str, - disk_cache: D, - rev_id_counter: Arc, - revision_persistence: Arc>, - revision_compress: Arc, - ) -> Self - where - D: RevisionSnapshotPersistence + 'static, - { - let rev_snapshot_persistence = Arc::new(disk_cache); - Self { - object_id: object_id.to_string(), - rev_snapshot_persistence, - rev_id_counter, - start_rev_id: AtomicI64::new(0), - rev_snapshot_data: None, - rev_persistence: revision_persistence, - rev_compress: revision_compress, - } - } - - pub async fn set_snapshot_data_generator( - &mut self, - generator: Arc, - ) { - self.rev_snapshot_data = Some(generator); - } - - pub async fn generate_snapshot(&self) { - if let Some((rev_id, bytes)) = self.generate_snapshot_data() { - if let Err(e) = self - .rev_snapshot_persistence - .write_snapshot(rev_id, bytes.to_vec()) - { - tracing::error!("Save snapshot failed: {}", e); - } - } - } - - /// Find the nearest revision base on the passed-in rev_id - #[tracing::instrument(level = "trace", skip_all)] - pub fn restore_from_snapshot(&self, rev_id: i64) -> Option<(B::Output, Revision)> - where - B: RevisionObjectDeserializer, - { - tracing::info!("[Restore] Try to find if {} has snapshot", self.object_id); - let snapshot = self.rev_snapshot_persistence.read_last_snapshot().ok()??; - let snapshot_rev_id = snapshot.rev_id; - let revision = Revision::new( - &self.object_id, - snapshot.base_rev_id, - snapshot.rev_id, - snapshot.data, - "".to_owned(), - ); - tracing::info!( - "[Restore] Try to restore from snapshot: {}, {}", - snapshot.base_rev_id, - snapshot.rev_id - ); - let object = B::deserialize_revisions(&self.object_id, vec![revision.clone()]).ok()?; - tracing::info!( - "[Restore] Restore {} from snapshot with rev_id: {}", - self.object_id, - snapshot_rev_id - ); - - Some((object, revision)) - } - - pub fn generate_snapshot_if_need(&self) { - let current_rev_id = self.rev_id_counter.value(); - let start_rev_id = self.get_start_rev_id(); - if current_rev_id <= start_rev_id { - return; - } - if self - .rev_snapshot_persistence - .should_generate_snapshot_from_range(start_rev_id, current_rev_id) - { - if let Some((rev_id, bytes)) = self.generate_snapshot_data() { - let disk_cache = self.rev_snapshot_persistence.clone(); - tokio::spawn(async move { - let _ = disk_cache.write_snapshot(rev_id, bytes.to_vec()); - }); - } - self.set_start_rev_id(current_rev_id); - } - } - - fn generate_snapshot_data(&self) -> Option<(i64, Bytes)> { - let revisions = self - .rev_persistence - .load_all_records(&self.object_id) - .map(|records| { - records - .into_iter() - .map(|record| record.revision) - .collect::>() - }) - .ok()?; - - if revisions.is_empty() { - return None; - } - - let data = self.rev_compress.combine_revisions(revisions).ok()?; - let rev_id = self.rev_id_counter.value(); - Some((rev_id, data)) - } - - fn get_start_rev_id(&self) -> i64 { - self.start_rev_id.load(SeqCst) - } - - fn set_start_rev_id(&self, rev_id: i64) { - let _ = self - .start_rev_id - .fetch_update(SeqCst, SeqCst, |_| Some(rev_id)); - } -} - -impl std::ops::Deref for RevisionSnapshotController { - type Target = Arc; - - fn deref(&self) -> &Self::Target { - &self.rev_snapshot_persistence - } -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct RevisionSnapshotData { - pub rev_id: i64, - pub base_rev_id: i64, - pub timestamp: i64, - pub data: Bytes, -} diff --git a/frontend/rust-lib/flowy-revision/src/ws_manager.rs b/frontend/rust-lib/flowy-revision/src/ws_manager.rs deleted file mode 100644 index 208b85bfe9..0000000000 --- a/frontend/rust-lib/flowy-revision/src/ws_manager.rs +++ /dev/null @@ -1,455 +0,0 @@ -use crate::ConflictRevisionSink; -use async_stream::stream; - -use flowy_error::{FlowyError, FlowyResult}; -use futures_util::{future::BoxFuture, stream::StreamExt}; -use lib_infra::future::{BoxResultFuture, FutureResult}; -use lib_ws::WSConnectState; -use revision_model::{Revision, RevisionRange}; -use std::{collections::VecDeque, fmt::Formatter, sync::Arc}; -use tokio::{ - sync::{ - broadcast, mpsc, - mpsc::{Receiver, Sender}, - RwLock, - }, - time::{interval, Duration}, -}; -use ws_model::ws_revision::{ - ClientRevisionWSData, NewDocumentUser, ServerRevisionWSData, WSRevisionPayload, -}; - -// The consumer consumes the messages pushed by the web socket. -pub trait RevisionWSDataStream: Send + Sync { - fn receive_push_revision(&self, revisions: Vec) -> BoxResultFuture<(), FlowyError>; - fn receive_ack(&self, rev_id: i64) -> BoxResultFuture<(), FlowyError>; - fn receive_new_user_connect(&self, new_user: NewDocumentUser) -> BoxResultFuture<(), FlowyError>; - fn pull_revisions_in_range(&self, range: RevisionRange) -> BoxResultFuture<(), FlowyError>; -} - -// The sink provides the data that will be sent through the web socket to the -// server. -pub trait RevisionWebSocketSink: Send + Sync { - fn next(&self) -> FutureResult, FlowyError>; -} - -pub type WSStateReceiver = tokio::sync::broadcast::Receiver; -pub trait RevisionWebSocket: Send + Sync + 'static { - fn send(&self, data: ClientRevisionWSData) -> BoxResultFuture<(), FlowyError>; - fn subscribe_state_changed(&self) -> BoxFuture; -} - -pub struct RevisionWebSocketManager { - pub object_name: String, - pub object_id: String, - ws_data_sink: Arc, - ws_data_stream: Arc, - rev_web_socket: Arc, - pub ws_passthrough_tx: Sender, - ws_passthrough_rx: Option>, - pub state_passthrough_tx: broadcast::Sender, - stop_sync_tx: SinkStopTx, -} - -impl std::fmt::Display for RevisionWebSocketManager { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{}RevisionWebSocketManager", self.object_name)) - } -} -impl RevisionWebSocketManager { - pub fn new( - object_name: &str, - object_id: &str, - rev_web_socket: Arc, - ws_data_sink: Arc, - ws_data_stream: Arc, - ping_duration: Duration, - ) -> Self { - let (ws_passthrough_tx, ws_passthrough_rx) = mpsc::channel(1000); - let (stop_sync_tx, _) = tokio::sync::broadcast::channel(2); - let object_id = object_id.to_string(); - let object_name = object_name.to_string(); - let (state_passthrough_tx, _) = broadcast::channel(2); - let mut manager = RevisionWebSocketManager { - object_id, - object_name, - ws_data_sink, - ws_data_stream, - rev_web_socket, - ws_passthrough_tx, - ws_passthrough_rx: Some(ws_passthrough_rx), - state_passthrough_tx, - stop_sync_tx, - }; - manager.run(ping_duration); - manager - } - - fn run(&mut self, ping_duration: Duration) { - let ws_passthrough_rx = self.ws_passthrough_rx.take().expect("Only take once"); - let sink = RevisionWSSink::new( - &self.object_id, - &self.object_name, - self.ws_data_sink.clone(), - self.rev_web_socket.clone(), - self.stop_sync_tx.subscribe(), - ping_duration, - ); - let stream = RevisionWSStream::new( - &self.object_name, - &self.object_id, - self.ws_data_stream.clone(), - ws_passthrough_rx, - self.stop_sync_tx.subscribe(), - ); - tokio::spawn(sink.run()); - tokio::spawn(stream.run()); - } - - pub fn scribe_state(&self) -> broadcast::Receiver { - self.state_passthrough_tx.subscribe() - } - - pub fn stop(&self) { - if self.stop_sync_tx.send(()).is_ok() { - tracing::trace!("{} stop sync", self.object_id) - } - } - - #[tracing::instrument(level = "debug", skip(self, data), err)] - pub async fn receive_ws_data(&self, data: ServerRevisionWSData) -> Result<(), FlowyError> { - self.ws_passthrough_tx.send(data).await.map_err(|e| { - let err_msg = format!("{} passthrough error: {}", self.object_id, e); - FlowyError::internal().context(err_msg) - })?; - Ok(()) - } - - pub fn connect_state_changed(&self, state: WSConnectState) { - match self.state_passthrough_tx.send(state) { - Ok(_) => {}, - Err(e) => tracing::error!("{}", e), - } - } -} - -impl std::ops::Drop for RevisionWebSocketManager { - fn drop(&mut self) { - tracing::trace!("{} was dropped", self) - } -} - -pub struct RevisionWSStream { - object_name: String, - object_id: String, - consumer: Arc, - ws_msg_rx: Option>, - stop_rx: Option, -} - -impl std::fmt::Display for RevisionWSStream { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{}RevisionWSStream", self.object_name)) - } -} - -impl std::ops::Drop for RevisionWSStream { - fn drop(&mut self) { - tracing::trace!("{} was dropped", self) - } -} - -impl RevisionWSStream { - pub fn new( - object_name: &str, - object_id: &str, - consumer: Arc, - ws_msg_rx: mpsc::Receiver, - stop_rx: SinkStopRx, - ) -> Self { - RevisionWSStream { - object_name: object_name.to_string(), - object_id: object_id.to_owned(), - consumer, - ws_msg_rx: Some(ws_msg_rx), - stop_rx: Some(stop_rx), - } - } - - pub async fn run(mut self) { - let mut receiver = self.ws_msg_rx.take().expect("Only take once"); - let mut stop_rx = self.stop_rx.take().expect("Only take once"); - let object_id = self.object_id.clone(); - let name = format!("{}", &self); - let stream = stream! { - loop { - tokio::select! { - result = receiver.recv() => { - match result { - Some(msg) => { - yield msg - }, - None => { - tracing::debug!("[{}]:{} loop exit", name, object_id); - break; - }, - } - }, - _ = stop_rx.recv() => { - tracing::debug!("[{}]:{} loop exit", name, object_id); - break - }, - }; - } - }; - - stream - .for_each(|msg| async { - match self.handle_message(msg).await { - Ok(_) => {}, - Err(e) => tracing::error!("[{}]:{} error: {}", &self, self.object_id, e), - } - }) - .await; - } - - async fn handle_message(&self, msg: ServerRevisionWSData) -> FlowyResult<()> { - let ServerRevisionWSData { object_id, payload } = msg; - match payload { - WSRevisionPayload::ServerPushRev { revisions } => { - tracing::trace!("[{}]: new push revision: {}", self, object_id); - self.consumer.receive_push_revision(revisions).await?; - }, - WSRevisionPayload::ServerPullRev { range } => { - tracing::trace!("[{}]: new pull: {}:{:?}", self, object_id, range); - self.consumer.pull_revisions_in_range(range).await?; - }, - WSRevisionPayload::ServerAck { rev_id } => { - tracing::trace!("[{}]: new ack: {}:{}", self, object_id, rev_id); - let _ = self.consumer.receive_ack(rev_id).await; - }, - WSRevisionPayload::UserConnect { user } => { - let _ = self.consumer.receive_new_user_connect(user).await; - }, - } - Ok(()) - } -} - -type SinkStopRx = broadcast::Receiver<()>; -type SinkStopTx = broadcast::Sender<()>; -pub struct RevisionWSSink { - object_id: String, - object_name: String, - provider: Arc, - rev_web_socket: Arc, - stop_rx: Option, - ping_duration: Duration, -} - -impl RevisionWSSink { - pub fn new( - object_id: &str, - object_name: &str, - provider: Arc, - rev_web_socket: Arc, - stop_rx: SinkStopRx, - ping_duration: Duration, - ) -> Self { - Self { - object_id: object_id.to_owned(), - object_name: object_name.to_owned(), - provider, - rev_web_socket, - stop_rx: Some(stop_rx), - ping_duration, - } - } - - pub async fn run(mut self) { - let (tx, mut rx) = mpsc::channel(1); - let mut stop_rx = self.stop_rx.take().expect("Only take once"); - let object_id = self.object_id.clone(); - tokio::spawn(tick(tx, self.ping_duration)); - let name = format!("{}", self); - let stream = stream! { - loop { - tokio::select! { - result = rx.recv() => { - match result { - Some(msg) => yield msg, - None => break, - } - }, - _ = stop_rx.recv() => { - tracing::trace!("[{}]:{} loop exit", name, object_id); - break - }, - }; - } - }; - stream - .for_each(|_| async { - match self.send_next_revision().await { - Ok(_) => {}, - Err(e) => tracing::error!("[{}] send failed, {:?}", self, e), - } - }) - .await; - } - - async fn send_next_revision(&self) -> FlowyResult<()> { - match self.provider.next().await? { - None => { - tracing::trace!("[{}]: Finish synchronizing revisions", self); - Ok(()) - }, - Some(data) => { - tracing::trace!( - "[{}]: send {}:{}-{:?}", - self, - data.object_id, - data.rev_id, - data.ty - ); - self.rev_web_socket.send(data).await - }, - } - } -} - -async fn tick(sender: mpsc::Sender<()>, duration: Duration) { - let mut interval = interval(duration); - while sender.send(()).await.is_ok() { - interval.tick().await; - } -} - -impl std::fmt::Display for RevisionWSSink { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{}RevisionWSSink", self.object_name)) - } -} - -impl std::ops::Drop for RevisionWSSink { - fn drop(&mut self) { - tracing::trace!("{} was dropped", self) - } -} - -#[derive(Clone)] -enum Source { - Custom, - Revision, -} - -pub trait WSDataProviderDataSource: Send + Sync { - fn next_revision(&self) -> FutureResult, FlowyError>; - fn ack_revision(&self, rev_id: i64) -> FutureResult<(), FlowyError>; - fn current_rev_id(&self) -> i64; -} - -#[derive(Clone)] -pub struct WSDataProvider { - object_id: String, - rev_ws_data_list: Arc>>, - data_source: Arc, - current_source: Arc>, -} - -impl WSDataProvider { - pub fn new(object_id: &str, data_source: Arc) -> Self { - WSDataProvider { - object_id: object_id.to_owned(), - rev_ws_data_list: Arc::new(RwLock::new(VecDeque::new())), - data_source, - current_source: Arc::new(RwLock::new(Source::Custom)), - } - } - - pub async fn push_data(&self, data: ClientRevisionWSData) { - self.rev_ws_data_list.write().await.push_back(data); - } - - pub async fn next(&self) -> FlowyResult> { - let source = self.current_source.read().await.clone(); - let data = match source { - Source::Custom => match self.rev_ws_data_list.read().await.front() { - None => { - *self.current_source.write().await = Source::Revision; - Ok(None) - }, - Some(data) => Ok(Some(data.clone())), - }, - Source::Revision => { - if !self.rev_ws_data_list.read().await.is_empty() { - *self.current_source.write().await = Source::Custom; - return Ok(None); - } - - match self.data_source.next_revision().await? { - Some(rev) => Ok(Some(ClientRevisionWSData::from_revisions( - &self.object_id, - vec![rev], - ))), - None => Ok(Some(ClientRevisionWSData::ping( - &self.object_id, - self.data_source.current_rev_id(), - ))), - } - }, - }; - data - } - - pub async fn ack_data(&self, rev_id: i64) -> FlowyResult<()> { - let source = self.current_source.read().await.clone(); - match source { - Source::Custom => { - let should_pop = match self.rev_ws_data_list.read().await.front() { - None => false, - Some(val) => { - if val.rev_id == rev_id { - true - } else { - tracing::error!( - "The front element's {} is not equal to the {}", - val.rev_id, - rev_id - ); - false - } - }, - }; - if should_pop { - let _ = self.rev_ws_data_list.write().await.pop_front(); - } - Ok(()) - }, - Source::Revision => { - self.data_source.ack_revision(rev_id).await?; - Ok::<(), FlowyError>(()) - }, - } - } -} - -impl ConflictRevisionSink for Arc { - fn send(&self, revisions: Vec) -> BoxResultFuture<(), FlowyError> { - let sink = self.clone(); - Box::pin(async move { - sink - .push_data(ClientRevisionWSData::from_revisions( - &sink.object_id, - revisions, - )) - .await; - Ok(()) - }) - } - - fn ack(&self, rev_id: i64) -> BoxResultFuture<(), FlowyError> { - let sink = self.clone(); - Box::pin(async move { sink.ack_data(rev_id).await }) - } -} diff --git a/frontend/rust-lib/flowy-revision/tests/main.rs b/frontend/rust-lib/flowy-revision/tests/main.rs deleted file mode 100644 index 3eb8b414b2..0000000000 --- a/frontend/rust-lib/flowy-revision/tests/main.rs +++ /dev/null @@ -1 +0,0 @@ -mod revision_test; diff --git a/frontend/rust-lib/flowy-revision/tests/revision_test/local_revision_test.rs b/frontend/rust-lib/flowy-revision/tests/revision_test/local_revision_test.rs deleted file mode 100644 index 64b44af4f5..0000000000 --- a/frontend/rust-lib/flowy-revision/tests/revision_test/local_revision_test.rs +++ /dev/null @@ -1,299 +0,0 @@ -use crate::revision_test::script::{RevisionScript::*, RevisionTest}; - -#[tokio::test] -async fn revision_sync_test() { - let test = RevisionTest::new().await; - let rev_id = 1; - test - .run_script(AddLocalRevision { - content: "123".to_string(), - }) - .await; - - test - .run_script(AssertNextSyncRevisionId { - rev_id: Some(rev_id), - }) - .await; - test.run_script(AckRevision { rev_id }).await; - test - .run_script(AssertNextSyncRevisionId { rev_id: None }) - .await; -} - -#[tokio::test] -async fn revision_compress_2_revisions_with_2_threshold_test() { - let test = RevisionTest::new_with_configuration(2).await; - test - .run_script(AddLocalRevision2 { - content: "123".to_string(), - }) - .await; - - test - .run_script(AddLocalRevision2 { - content: "456".to_string(), - }) - .await; - - test - .run_scripts(vec![ - AssertNextSyncRevisionId { rev_id: Some(2) }, - AssertNextSyncRevisionContent { - expected: "123456".to_string(), - }, - AckRevision { rev_id: 2 }, - AssertNextSyncRevisionId { rev_id: None }, - ]) - .await; -} - -#[tokio::test] -async fn revision_compress_4_revisions_with_threshold_2_test() { - let test = RevisionTest::new_with_configuration(2).await; - test - .run_script(AddLocalRevision { - content: "1".to_string(), - }) - .await; - - test - .run_script(AddLocalRevision { - content: "2".to_string(), - }) - .await; - - test - .run_script(AddLocalRevision { - content: "3".to_string(), - }) - .await; - - test - .run_script(AddLocalRevision { - content: "4".to_string(), - }) - .await; - - test - .run_scripts(vec![ - AssertNumberOfSyncRevisions { num: 2 }, - AssertNextSyncRevisionId { rev_id: Some(2) }, - AssertNextSyncRevisionContent { - expected: "12".to_string(), - }, - AckRevision { rev_id: 2 }, - AssertNextSyncRevisionId { rev_id: Some(4) }, - AssertNextSyncRevisionContent { - expected: "34".to_string(), - }, - ]) - .await; -} - -#[tokio::test] -async fn revision_compress_8_revisions_with_threshold_4_test() { - let merge_len = 4; - let test = RevisionTest::new_with_configuration(merge_len).await; - test - .run_script(AddLocalRevision { - content: "1".to_string(), - }) - .await; - - test - .run_script(AddLocalRevision { - content: "2".to_string(), - }) - .await; - - test - .run_script(AddLocalRevision { - content: "3".to_string(), - }) - .await; - - test - .run_script(AddLocalRevision { - content: "4".to_string(), - }) - .await; - - test - .run_script(AddLocalRevision { - content: "a".to_string(), - }) - .await; - - test - .run_script(AddLocalRevision { - content: "b".to_string(), - }) - .await; - - test - .run_script(AddLocalRevision { - content: "c".to_string(), - }) - .await; - - test - .run_script(AddLocalRevision { - content: "d".to_string(), - }) - .await; - - test - .run_scripts(vec![ - AssertNumberOfSyncRevisions { num: 2 }, - AssertNextSyncRevisionId { - rev_id: Some(merge_len), - }, - AssertNextSyncRevisionContent { - expected: "1234".to_string(), - }, - AckRevision { rev_id: merge_len }, - AssertNextSyncRevisionId { - rev_id: Some(merge_len * 2), - }, - AssertNextSyncRevisionContent { - expected: "abcd".to_string(), - }, - AckRevision { - rev_id: merge_len * 2, - }, - AssertNextSyncRevisionId { rev_id: None }, - ]) - .await; -} - -#[tokio::test] -async fn revision_merge_per_5_revision_test() { - let merge_len = 5; - let test = RevisionTest::new_with_configuration(merge_len).await; - for i in 0..20 { - let content = format!("{}", i); - test.run_script(AddLocalRevision { content }).await; - } - - test - .run_scripts(vec![ - AssertNumberOfSyncRevisions { num: 4 }, - AssertNextSyncRevisionContent { - expected: "01234".to_string(), - }, - AckRevision { rev_id: merge_len }, - AssertNextSyncRevisionContent { - expected: "56789".to_string(), - }, - AckRevision { - rev_id: merge_len * 2, - }, - AssertNextSyncRevisionContent { - expected: "1011121314".to_string(), - }, - AckRevision { - rev_id: merge_len * 3, - }, - AssertNextSyncRevisionContent { - expected: "1516171819".to_string(), - }, - AckRevision { - rev_id: merge_len * 4, - }, - AssertNextSyncRevisionId { rev_id: None }, - ]) - .await; -} - -#[tokio::test] -async fn revision_merge_per_100_revision_test() { - let test = RevisionTest::new_with_configuration(100).await; - for i in 0..1000 { - let content = format!("{}", i); - test.run_script(AddLocalRevision { content }).await; - } - - test - .run_scripts(vec![AssertNumberOfSyncRevisions { num: 10 }]) - .await; -} - -#[tokio::test] -async fn revision_merge_per_100_revision_test2() { - let test = RevisionTest::new_with_configuration(100).await; - for i in 0..50 { - test - .run_script(AddLocalRevision { - content: format!("{}", i), - }) - .await; - } - - test - .run_scripts(vec![AssertNumberOfSyncRevisions { num: 50 }]) - .await; -} - -// #[tokio::test] -// async fn revision_merge_per_1000_revision_test() { -// let test = RevisionTest::new_with_configuration(1000).await; -// for i in 0..100000 { -// test -// .run_script(AddLocalRevision { -// content: format!("{}", i), -// }) -// .await; -// } -// -// test -// .run_scripts(vec![AssertNumberOfSyncRevisions { num: 100 }]) -// .await; -// } - -#[tokio::test] -async fn revision_compress_revision_test() { - let test = RevisionTest::new_with_configuration(2).await; - test - .run_scripts(vec![ - AddLocalRevision2 { - content: "1".to_string(), - }, - AddLocalRevision2 { - content: "2".to_string(), - }, - AddLocalRevision2 { - content: "3".to_string(), - }, - AddLocalRevision2 { - content: "4".to_string(), - }, - AssertNumberOfSyncRevisions { num: 2 }, - ]) - .await; -} - -#[tokio::test] -async fn revision_compress_revision_while_recv_ack_test() { - let test = RevisionTest::new_with_configuration(2).await; - test - .run_scripts(vec![ - AddLocalRevision2 { - content: "1".to_string(), - }, - AckRevision { rev_id: 1 }, - AddLocalRevision2 { - content: "2".to_string(), - }, - AckRevision { rev_id: 2 }, - AddLocalRevision2 { - content: "3".to_string(), - }, - AckRevision { rev_id: 3 }, - AddLocalRevision2 { - content: "4".to_string(), - }, - AssertNumberOfSyncRevisions { num: 4 }, - ]) - .await; -} diff --git a/frontend/rust-lib/flowy-revision/tests/revision_test/mod.rs b/frontend/rust-lib/flowy-revision/tests/revision_test/mod.rs deleted file mode 100644 index f0362f1436..0000000000 --- a/frontend/rust-lib/flowy-revision/tests/revision_test/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod local_revision_test; -mod revision_disk_test; -mod script; diff --git a/frontend/rust-lib/flowy-revision/tests/revision_test/revision_disk_test.rs b/frontend/rust-lib/flowy-revision/tests/revision_test/revision_disk_test.rs deleted file mode 100644 index e0bc66b00a..0000000000 --- a/frontend/rust-lib/flowy-revision/tests/revision_test/revision_disk_test.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::revision_test::script::RevisionScript::*; -use crate::revision_test::script::{InvalidRevisionObject, RevisionTest}; - -#[tokio::test] -async fn revision_write_to_disk_test() { - let test = RevisionTest::new_with_configuration(2).await; - test - .run_script(AddLocalRevision { - content: "123".to_string(), - }) - .await; - - test - .run_scripts(vec![ - AssertNumberOfRevisionsInDisk { num: 0 }, - WaitWhenWriteToDisk, - AssertNumberOfRevisionsInDisk { num: 1 }, - ]) - .await; -} - -#[tokio::test] -async fn revision_write_to_disk_with_merge_test() { - let test = RevisionTest::new_with_configuration(100).await; - for i in 0..1000 { - test - .run_script(AddLocalRevision { - content: format!("{}", i), - }) - .await; - } - - test - .run_scripts(vec![ - AssertNumberOfRevisionsInDisk { num: 0 }, - AssertNumberOfSyncRevisions { num: 10 }, - WaitWhenWriteToDisk, - AssertNumberOfRevisionsInDisk { num: 10 }, - ]) - .await; -} - -#[tokio::test] -async fn revision_read_from_disk_test() { - let test = RevisionTest::new_with_configuration(2).await; - test - .run_scripts(vec![ - AddLocalRevision { - content: "123".to_string(), - }, - AssertNumberOfRevisionsInDisk { num: 0 }, - WaitWhenWriteToDisk, - AssertNumberOfRevisionsInDisk { num: 1 }, - ]) - .await; - - let test = RevisionTest::new_with_other(test).await; - test - .run_scripts(vec![ - AssertNextSyncRevisionId { rev_id: Some(1) }, - AddLocalRevision { - content: "456".to_string(), - }, - AckRevision { rev_id: 1 }, - AssertNextSyncRevisionId { rev_id: Some(2) }, - ]) - .await; -} - -#[tokio::test] -async fn revision_read_from_disk_with_invalid_record_test() { - let test = RevisionTest::new_with_configuration(2).await; - test - .run_scripts(vec![AddLocalRevision { - content: "123".to_string(), - }]) - .await; - - test - .run_scripts(vec![ - AddInvalidLocalRevision { - bytes: InvalidRevisionObject::new().to_bytes(), - }, - WaitWhenWriteToDisk, - ]) - .await; - - let test = RevisionTest::new_with_other(test).await; - test - .run_scripts(vec![AssertNextSyncRevisionContent { - expected: "123".to_string(), - }]) - .await; -} diff --git a/frontend/rust-lib/flowy-revision/tests/revision_test/script.rs b/frontend/rust-lib/flowy-revision/tests/revision_test/script.rs deleted file mode 100644 index 22824b88c9..0000000000 --- a/frontend/rust-lib/flowy-revision/tests/revision_test/script.rs +++ /dev/null @@ -1,354 +0,0 @@ -use bytes::Bytes; -use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_revision::{ - RevisionManager, RevisionMergeable, RevisionObjectDeserializer, RevisionPersistence, - RevisionPersistenceConfiguration, RevisionSnapshotData, RevisionSnapshotPersistence, - REVISION_WRITE_INTERVAL_IN_MILLIS, -}; -use flowy_revision_persistence::{RevisionChangeset, RevisionDiskCache, SyncRecord}; - -use lib_infra::util::md5; -use nanoid::nanoid; -use parking_lot::RwLock; -use revision_model::{Revision, RevisionRange}; -use serde::{Deserialize, Serialize}; -use std::sync::Arc; -use std::time::Duration; - -pub enum RevisionScript { - AddLocalRevision { content: String }, - AddLocalRevision2 { content: String }, - AddInvalidLocalRevision { bytes: Vec }, - AckRevision { rev_id: i64 }, - AssertNextSyncRevisionId { rev_id: Option }, - AssertNumberOfSyncRevisions { num: usize }, - AssertNumberOfRevisionsInDisk { num: usize }, - AssertNextSyncRevisionContent { expected: String }, - WaitWhenWriteToDisk, -} - -pub struct RevisionTest { - object_id: String, - configuration: RevisionPersistenceConfiguration, - rev_manager: Arc>, -} - -impl RevisionTest { - pub async fn new() -> Self { - Self::new_with_configuration(2).await - } - - pub async fn new_with_configuration(max_merge_len: i64) -> Self { - let object_id = nanoid!(6); - let configuration = RevisionPersistenceConfiguration::new(max_merge_len as usize, false); - let disk_cache = RevisionDiskCacheMock::new(vec![]); - let persistence = RevisionPersistence::new(&object_id, disk_cache, configuration.clone()); - let compress = RevisionMergeableMock {}; - let snapshot = RevisionSnapshotMock {}; - let mut rev_manager = RevisionManager::new(&object_id, persistence, compress, snapshot); - rev_manager - .initialize::(None) - .await - .unwrap(); - Self { - object_id, - configuration, - rev_manager: Arc::new(rev_manager), - } - } - - pub async fn new_with_other(old_test: RevisionTest) -> Self { - let records = old_test.rev_manager.get_all_revision_records().unwrap(); - let disk_cache = RevisionDiskCacheMock::new(records); - let configuration = old_test.configuration; - let persistence = - RevisionPersistence::new(&old_test.object_id, disk_cache, configuration.clone()); - - let compress = RevisionMergeableMock {}; - let snapshot = RevisionSnapshotMock {}; - let mut rev_manager = - RevisionManager::new(&old_test.object_id, persistence, compress, snapshot); - rev_manager - .initialize::(None) - .await - .unwrap(); - Self { - object_id: old_test.object_id, - configuration, - rev_manager: Arc::new(rev_manager), - } - } - pub async fn run_scripts(&self, scripts: Vec) { - for script in scripts { - self.run_script(script).await; - } - } - - pub async fn run_script(&self, script: RevisionScript) { - match script { - RevisionScript::AddLocalRevision { content } => { - let object = RevisionObjectMock::new(&content); - let bytes = object.to_bytes(); - let md5 = md5(&bytes); - self - .rev_manager - .add_local_revision(Bytes::from(bytes), md5) - .await - .unwrap(); - }, - RevisionScript::AddLocalRevision2 { content } => { - let object = RevisionObjectMock::new(&content); - let bytes = object.to_bytes(); - let md5 = md5(&bytes); - self - .rev_manager - .add_local_revision(Bytes::from(bytes), md5) - .await - .unwrap(); - }, - RevisionScript::AddInvalidLocalRevision { bytes } => { - let md5 = md5(&bytes); - self - .rev_manager - .add_local_revision(Bytes::from(bytes), md5) - .await - .unwrap(); - }, - RevisionScript::AckRevision { rev_id } => { - // - self.rev_manager.ack_revision(rev_id).await.unwrap() - }, - RevisionScript::AssertNextSyncRevisionId { rev_id } => { - assert_eq!(self.rev_manager.next_sync_rev_id().await, rev_id) - }, - RevisionScript::AssertNumberOfSyncRevisions { num } => { - assert_eq!(self.rev_manager.number_of_sync_revisions(), num) - }, - RevisionScript::AssertNumberOfRevisionsInDisk { num } => { - assert_eq!(self.rev_manager.number_of_revisions_in_disk(), num) - }, - RevisionScript::AssertNextSyncRevisionContent { expected } => { - // - let rev_id = self.rev_manager.next_sync_rev_id().await.unwrap(); - let revision = self.rev_manager.get_revision(rev_id).await.unwrap(); - let object = RevisionObjectMock::from_bytes(&revision.bytes).unwrap(); - assert_eq!(object.content, expected); - }, - RevisionScript::WaitWhenWriteToDisk => { - let milliseconds = 2 * REVISION_WRITE_INTERVAL_IN_MILLIS; - tokio::time::sleep(Duration::from_millis(milliseconds)).await; - }, - } - } -} - -pub struct RevisionDiskCacheMock { - records: RwLock>, -} - -impl RevisionDiskCacheMock { - pub fn new(records: Vec) -> Self { - Self { - records: RwLock::new(records), - } - } -} - -impl RevisionDiskCache for RevisionDiskCacheMock { - type Error = FlowyError; - - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { - self.records.write().extend(revision_records); - Ok(()) - } - - fn get_connection(&self) -> Result { - todo!() - } - - fn read_revision_records( - &self, - _object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error> { - match rev_ids { - None => Ok(self.records.read().clone()), - Some(rev_ids) => Ok( - self - .records - .read() - .iter() - .filter(|record| rev_ids.contains(&record.revision.rev_id)) - .cloned() - .collect::>(), - ), - } - } - - fn read_revision_records_with_range( - &self, - _object_id: &str, - range: &RevisionRange, - ) -> Result, Self::Error> { - let read_guard = self.records.read(); - let records = range - .iter() - .flat_map(|rev_id| { - read_guard - .iter() - .find(|record| record.revision.rev_id == rev_id) - .cloned() - }) - .collect::>(); - Ok(records) - } - - fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { - for changeset in changesets { - if let Some(record) = self - .records - .write() - .iter_mut() - .find(|record| record.revision.rev_id == changeset.rev_id) - { - record.state = changeset.state; - } - } - Ok(()) - } - - fn delete_revision_records( - &self, - _object_id: &str, - rev_ids: Option>, - ) -> Result<(), Self::Error> { - match rev_ids { - None => {}, - Some(rev_ids) => { - for rev_id in rev_ids { - if let Some(index) = self - .records - .read() - .iter() - .position(|record| record.revision.rev_id == rev_id) - { - self.records.write().remove(index); - } - } - }, - } - Ok(()) - } - - fn delete_and_insert_records( - &self, - _object_id: &str, - _deleted_rev_ids: Option>, - _inserted_records: Vec, - ) -> Result<(), Self::Error> { - todo!() - } -} - -pub struct RevisionConnectionMock {} -pub struct RevisionSnapshotMock {} -impl RevisionSnapshotPersistence for RevisionSnapshotMock { - fn write_snapshot(&self, _rev_id: i64, _data: Vec) -> FlowyResult<()> { - Ok(()) - } - - fn read_snapshot(&self, _rev_id: i64) -> FlowyResult> { - Ok(None) - } - - fn read_last_snapshot(&self) -> FlowyResult> { - Ok(None) - } -} - -pub struct RevisionMergeableMock {} - -impl RevisionMergeable for RevisionMergeableMock { - fn combine_revisions(&self, revisions: Vec) -> FlowyResult { - let mut object = RevisionObjectMock::new(""); - for revision in revisions { - if let Ok(other) = RevisionObjectMock::from_bytes(&revision.bytes) { - object.compose(other)?; - } - } - Ok(Bytes::from(object.to_bytes())) - } -} - -#[derive(Serialize, Deserialize)] -pub struct InvalidRevisionObject { - data: String, -} - -impl InvalidRevisionObject { - pub fn new() -> Self { - InvalidRevisionObject { - data: "".to_string(), - } - } - pub(crate) fn to_bytes(&self) -> Vec { - serde_json::to_vec(self).unwrap() - } - - // fn from_bytes(bytes: &[u8]) -> Self { - // serde_json::from_slice(bytes).unwrap() - // } -} - -#[derive(Serialize, Deserialize)] -pub struct RevisionObjectMock { - content: String, -} - -impl RevisionObjectMock { - pub fn new(s: &str) -> Self { - Self { - content: s.to_owned(), - } - } - - pub fn compose(&mut self, other: RevisionObjectMock) -> FlowyResult<()> { - self.content.push_str(other.content.as_str()); - Ok(()) - } - - pub fn to_bytes(&self) -> Vec { - serde_json::to_vec(self).unwrap() - } - - pub fn from_bytes(bytes: &[u8]) -> FlowyResult { - serde_json::from_slice(bytes).map_err(internal_error) - } -} - -pub struct RevisionObjectMockSerde(); -impl RevisionObjectDeserializer for RevisionObjectMockSerde { - type Output = RevisionObjectMock; - - fn deserialize_revisions( - _object_id: &str, - revisions: Vec, - ) -> FlowyResult { - let mut object = RevisionObjectMock::new(""); - if revisions.is_empty() { - return Ok(object); - } - - for revision in revisions { - if let Ok(revision_object) = RevisionObjectMock::from_bytes(&revision.bytes) { - object.compose(revision_object)?; - } - } - - Ok(object) - } - - fn recover_from_revisions(_revisions: Vec) -> Option<(Self::Output, i64)> { - None - } -} diff --git a/frontend/rust-lib/flowy-test/Cargo.toml b/frontend/rust-lib/flowy-test/Cargo.toml index 4f454a925c..0d67f68c07 100644 --- a/frontend/rust-lib/flowy-test/Cargo.toml +++ b/frontend/rust-lib/flowy-test/Cargo.toml @@ -10,10 +10,8 @@ flowy-core = { path = "../flowy-core" } flowy-user = { path = "../flowy-user"} flowy-net = { path = "../flowy-net"} flowy-folder2 = { path = "../flowy-folder2", features = ["test_helper"] } -flowy-document= { path = "../flowy-document" } +#flowy-document= { path = "../flowy-document" } lib-dispatch = { path = "../lib-dispatch" } - -flowy-client-sync = { path = "../flowy-client-sync"} lib-ot = { path = "../../../shared-lib/lib-ot" } lib-infra = { path = "../../../shared-lib/lib-infra" } diff --git a/frontend/rust-lib/flowy-test/src/lib.rs b/frontend/rust-lib/flowy-test/src/lib.rs index 29e8a1a2b9..4aa9daf3cf 100644 --- a/frontend/rust-lib/flowy-test/src/lib.rs +++ b/frontend/rust-lib/flowy-test/src/lib.rs @@ -1,17 +1,18 @@ -pub mod event_builder; -pub mod helper; +use nanoid::nanoid; + +use flowy_core::{AppFlowyCore, AppFlowyCoreConfig}; +use flowy_net::http_server::self_host::configuration::get_client_server_configuration; +use flowy_user::entities::UserProfilePB; use crate::helper::*; -use flowy_core::{AppFlowyCore, AppFlowyCoreConfig}; -use flowy_document::entities::DocumentVersionPB; -use flowy_net::get_client_server_configuration; -use flowy_user::entities::UserProfilePB; -use nanoid::nanoid; +pub mod event_builder; +pub mod helper; pub mod prelude { - pub use crate::{event_builder::*, helper::*, *}; pub use lib_dispatch::prelude::*; + + pub use crate::{event_builder::*, helper::*, *}; } #[derive(Clone)] @@ -29,16 +30,15 @@ impl std::ops::Deref for FlowySDKTest { impl std::default::Default for FlowySDKTest { fn default() -> Self { - Self::new(DocumentVersionPB::V0) + Self::new() } } impl FlowySDKTest { - pub fn new(document_version: DocumentVersionPB) -> Self { + pub fn new() -> Self { let server_config = get_client_server_configuration().unwrap(); - let config = AppFlowyCoreConfig::new(&root_dir(), nanoid!(6), server_config) - .with_document_version(document_version) - .log_filter("info", vec![]); + let config = + AppFlowyCoreConfig::new(&root_dir(), nanoid!(6), server_config).log_filter("info", vec![]); let sdk = std::thread::spawn(|| AppFlowyCore::new(config)) .join() .unwrap(); @@ -55,8 +55,4 @@ impl FlowySDKTest { init_user_setting(self.inner.dispatcher()).await; context.user_profile } - - pub fn document_version(&self) -> DocumentVersionPB { - self.inner.config.document.version.clone() - } } diff --git a/frontend/rust-lib/flowy-user/Cargo.toml b/frontend/rust-lib/flowy-user/Cargo.toml index b19284da3a..cbeef480cf 100644 --- a/frontend/rust-lib/flowy-user/Cargo.toml +++ b/frontend/rust-lib/flowy-user/Cargo.toml @@ -6,10 +6,9 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -flowy-derive = { path = "../flowy-derive" } +flowy-derive = { path = "../../../shared-lib/flowy-derive" } flowy-sqlite = { path = "../flowy-sqlite", optional = true } -flowy-error = { path = "../flowy-error", features = ["adaptor_database", "adaptor_dispatch", "adaptor_user"] } -user-model = { path = "../../../shared-lib/user-model" } +flowy-error = { path = "../flowy-error", features = ["adaptor_database", "adaptor_dispatch"] } lib-infra = { path = "../../../shared-lib/lib-infra" } flowy-notification = { path = "../flowy-notification" } lib-dispatch = { path = "../lib-dispatch" } @@ -29,10 +28,18 @@ parking_lot = "0.12.1" strum = "0.21" strum_macros = "0.21" tokio = { version = "1.26", features = ["rt"] } +validator = "0.16.0" +unicode-segmentation = "1.10" +fancy-regex = "0.11.0" [dev-dependencies] flowy-test = { path = "../flowy-test" } nanoid = "0.4.0" +fake = "2.0.0" +rand = "0.8.4" +quickcheck = "1.0.3" +rand_core = "0.6.2" +quickcheck_macros = "1.0" [features] default = ["rev-sqlite"] @@ -41,4 +48,4 @@ dart = ["flowy-codegen/dart", "flowy-notification/dart"] ts = ["flowy-codegen/ts", "flowy-notification/ts"] [build-dependencies] -flowy-codegen = { path = "../flowy-codegen"} +flowy-codegen = { path = "../../../shared-lib/flowy-codegen"} diff --git a/frontend/rust-lib/flowy-user/src/entities/auth.rs b/frontend/rust-lib/flowy-user/src/entities/auth.rs index 1478676c83..fd1f3b7dcb 100644 --- a/frontend/rust-lib/flowy-user/src/entities/auth.rs +++ b/frontend/rust-lib/flowy-user/src/entities/auth.rs @@ -1,7 +1,11 @@ -use crate::errors::ErrorCode; -use flowy_derive::ProtoBuf; use std::convert::TryInto; -use user_model::{SignInParams, SignUpParams, UserEmail, UserName, UserPassword}; + +use serde::{Deserialize, Serialize}; + +use flowy_derive::ProtoBuf; + +use crate::entities::parser::*; +use crate::errors::ErrorCode; #[derive(ProtoBuf, Default)] pub struct SignInPayloadPB { @@ -56,3 +60,91 @@ impl TryInto for SignUpPayloadPB { }) } } + +#[derive(Default, Serialize, Deserialize, Debug)] +pub struct SignInParams { + pub email: String, + pub password: String, + pub name: String, +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct SignInResponse { + pub user_id: i64, + pub name: String, + pub email: String, + pub token: String, +} + +#[derive(Serialize, Deserialize, Default, Debug)] +pub struct SignUpParams { + pub email: String, + pub name: String, + pub password: String, +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct SignUpResponse { + pub user_id: i64, + pub name: String, + pub email: String, + pub token: String, +} + +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +pub struct UserProfile { + pub id: i64, + pub email: String, + pub name: String, + pub token: String, + pub icon_url: String, + pub openai_key: String, +} + +#[derive(Serialize, Deserialize, Default, Clone, Debug)] +pub struct UpdateUserProfileParams { + pub id: i64, + pub name: Option, + pub email: Option, + pub password: Option, + pub icon_url: Option, + pub openai_key: Option, +} + +impl UpdateUserProfileParams { + pub fn new(id: i64) -> Self { + Self { + id, + name: None, + email: None, + password: None, + icon_url: None, + openai_key: None, + } + } + + pub fn name(mut self, name: &str) -> Self { + self.name = Some(name.to_owned()); + self + } + + pub fn email(mut self, email: &str) -> Self { + self.email = Some(email.to_owned()); + self + } + + pub fn password(mut self, password: &str) -> Self { + self.password = Some(password.to_owned()); + self + } + + pub fn icon_url(mut self, icon_url: &str) -> Self { + self.icon_url = Some(icon_url.to_owned()); + self + } + + pub fn openai_key(mut self, openai_key: &str) -> Self { + self.openai_key = Some(openai_key.to_owned()); + self + } +} diff --git a/frontend/rust-lib/flowy-user/src/entities/mod.rs b/frontend/rust-lib/flowy-user/src/entities/mod.rs index e7214db5ca..18e64885dc 100644 --- a/frontend/rust-lib/flowy-user/src/entities/mod.rs +++ b/frontend/rust-lib/flowy-user/src/entities/mod.rs @@ -3,5 +3,6 @@ pub use user_profile::*; pub use user_setting::*; pub mod auth; +pub mod parser; mod user_profile; mod user_setting; diff --git a/shared-lib/user-model/src/parser/mod.rs b/frontend/rust-lib/flowy-user/src/entities/parser/mod.rs similarity index 100% rename from shared-lib/user-model/src/parser/mod.rs rename to frontend/rust-lib/flowy-user/src/entities/parser/mod.rs diff --git a/shared-lib/user-model/src/parser/user_email.rs b/frontend/rust-lib/flowy-user/src/entities/parser/user_email.rs similarity index 88% rename from shared-lib/user-model/src/parser/user_email.rs rename to frontend/rust-lib/flowy-user/src/entities/parser/user_email.rs index 176fd0f34b..a3ec143317 100644 --- a/shared-lib/user-model/src/parser/user_email.rs +++ b/frontend/rust-lib/flowy-user/src/entities/parser/user_email.rs @@ -1,19 +1,19 @@ -use crate::errors::UserErrorCode; +use flowy_error::ErrorCode; use validator::validate_email; #[derive(Debug)] pub struct UserEmail(pub String); impl UserEmail { - pub fn parse(s: String) -> Result { + pub fn parse(s: String) -> Result { if s.trim().is_empty() { - return Err(UserErrorCode::EmailIsEmpty); + return Err(ErrorCode::EmailIsEmpty); } if validate_email(&s) { Ok(Self(s)) } else { - Err(UserErrorCode::EmailFormatInvalid) + Err(ErrorCode::EmailFormatInvalid) } } } @@ -26,11 +26,12 @@ impl AsRef for UserEmail { #[cfg(test)] mod tests { - use super::*; use fake::{faker::internet::en::SafeEmail, Fake}; use rand::prelude::StdRng; use rand_core::SeedableRng; + use super::*; + #[test] fn empty_string_is_rejected() { let email = "".to_string(); diff --git a/shared-lib/user-model/src/parser/user_icon.rs b/frontend/rust-lib/flowy-user/src/entities/parser/user_icon.rs similarity index 63% rename from shared-lib/user-model/src/parser/user_icon.rs rename to frontend/rust-lib/flowy-user/src/entities/parser/user_icon.rs index 9b088eda20..7f970bb89b 100644 --- a/shared-lib/user-model/src/parser/user_icon.rs +++ b/frontend/rust-lib/flowy-user/src/entities/parser/user_icon.rs @@ -1,10 +1,10 @@ -use crate::errors::UserErrorCode; +use flowy_error::ErrorCode; #[derive(Debug)] pub struct UserIcon(pub String); impl UserIcon { - pub fn parse(s: String) -> Result { + pub fn parse(s: String) -> Result { Ok(Self(s)) } } diff --git a/shared-lib/user-model/src/parser/user_id.rs b/frontend/rust-lib/flowy-user/src/entities/parser/user_id.rs similarity index 64% rename from shared-lib/user-model/src/parser/user_id.rs rename to frontend/rust-lib/flowy-user/src/entities/parser/user_id.rs index 77f7ba624b..d97031600a 100644 --- a/shared-lib/user-model/src/parser/user_id.rs +++ b/frontend/rust-lib/flowy-user/src/entities/parser/user_id.rs @@ -1,13 +1,13 @@ -use crate::errors::UserErrorCode; +use flowy_error::ErrorCode; #[derive(Debug)] pub struct UserId(pub String); impl UserId { - pub fn parse(s: String) -> Result { + pub fn parse(s: String) -> Result { let is_empty_or_whitespace = s.trim().is_empty(); if is_empty_or_whitespace { - return Err(UserErrorCode::UserIdInvalid); + return Err(ErrorCode::UserIdInvalid); } Ok(Self(s)) } diff --git a/shared-lib/user-model/src/parser/user_name.rs b/frontend/rust-lib/flowy-user/src/entities/parser/user_name.rs similarity index 87% rename from shared-lib/user-model/src/parser/user_name.rs rename to frontend/rust-lib/flowy-user/src/entities/parser/user_name.rs index 4a8d8b0856..b0b7e7b3a3 100644 --- a/shared-lib/user-model/src/parser/user_name.rs +++ b/frontend/rust-lib/flowy-user/src/entities/parser/user_name.rs @@ -1,14 +1,15 @@ -use crate::errors::UserErrorCode; use unicode_segmentation::UnicodeSegmentation; +use flowy_error::ErrorCode; + #[derive(Debug)] pub struct UserName(pub String); impl UserName { - pub fn parse(s: String) -> Result { + pub fn parse(s: String) -> Result { let is_empty_or_whitespace = s.trim().is_empty(); if is_empty_or_whitespace { - return Err(UserErrorCode::UserNameIsEmpty); + return Err(ErrorCode::UserNameIsEmpty); } // A grapheme is defined by the Unicode standard as a "user-perceived" // character: `å` is a single grapheme, but it is composed of two characters @@ -19,14 +20,14 @@ impl UserName { // the recommended one. let is_too_long = s.graphemes(true).count() > 256; if is_too_long { - return Err(UserErrorCode::UserNameTooLong); + return Err(ErrorCode::UserNameTooLong); } let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}']; let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g)); if contains_forbidden_characters { - return Err(UserErrorCode::UserNameContainForbiddenCharacters); + return Err(ErrorCode::UserNameContainForbiddenCharacters); } Ok(Self(s)) diff --git a/shared-lib/user-model/src/parser/user_openai_key.rs b/frontend/rust-lib/flowy-user/src/entities/parser/user_openai_key.rs similarity index 64% rename from shared-lib/user-model/src/parser/user_openai_key.rs rename to frontend/rust-lib/flowy-user/src/entities/parser/user_openai_key.rs index 17a87ae048..6fbe80c2d2 100644 --- a/shared-lib/user-model/src/parser/user_openai_key.rs +++ b/frontend/rust-lib/flowy-user/src/entities/parser/user_openai_key.rs @@ -1,10 +1,10 @@ -use crate::errors::UserErrorCode; +use flowy_error::ErrorCode; #[derive(Debug)] pub struct UserOpenaiKey(pub String); impl UserOpenaiKey { - pub fn parse(s: String) -> Result { + pub fn parse(s: String) -> Result { Ok(Self(s)) } } diff --git a/shared-lib/user-model/src/parser/user_password.rs b/frontend/rust-lib/flowy-user/src/entities/parser/user_password.rs similarity index 80% rename from shared-lib/user-model/src/parser/user_password.rs rename to frontend/rust-lib/flowy-user/src/entities/parser/user_password.rs index 9b113b5b0f..354dedfbdd 100644 --- a/shared-lib/user-model/src/parser/user_password.rs +++ b/frontend/rust-lib/flowy-user/src/entities/parser/user_password.rs @@ -1,29 +1,30 @@ -use crate::errors::UserErrorCode; -use fancy_regex::Regex; use lazy_static::lazy_static; use unicode_segmentation::UnicodeSegmentation; +use fancy_regex::Regex; +use flowy_error::ErrorCode; + #[derive(Debug)] pub struct UserPassword(pub String); impl UserPassword { - pub fn parse(s: String) -> Result { + pub fn parse(s: String) -> Result { if s.trim().is_empty() { - return Err(UserErrorCode::PasswordIsEmpty); + return Err(ErrorCode::PasswordIsEmpty); } if s.graphemes(true).count() > 100 { - return Err(UserErrorCode::PasswordTooLong); + return Err(ErrorCode::PasswordTooLong); } let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}']; let contains_forbidden_characters = s.chars().any(|g| forbidden_characters.contains(&g)); if contains_forbidden_characters { - return Err(UserErrorCode::PasswordContainsForbidCharacters); + return Err(ErrorCode::PasswordContainsForbidCharacters); } if !validate_password(&s) { - return Err(UserErrorCode::PasswordFormatInvalid); + return Err(ErrorCode::PasswordFormatInvalid); } Ok(Self(s)) diff --git a/shared-lib/user-model/src/parser/user_workspace.rs b/frontend/rust-lib/flowy-user/src/entities/parser/user_workspace.rs similarity index 64% rename from shared-lib/user-model/src/parser/user_workspace.rs rename to frontend/rust-lib/flowy-user/src/entities/parser/user_workspace.rs index ecee8bccd2..a02bf3ec45 100644 --- a/shared-lib/user-model/src/parser/user_workspace.rs +++ b/frontend/rust-lib/flowy-user/src/entities/parser/user_workspace.rs @@ -1,13 +1,13 @@ -use crate::errors::UserErrorCode; +use flowy_error::ErrorCode; #[derive(Debug)] pub struct UserWorkspace(pub String); impl UserWorkspace { - pub fn parse(s: String) -> Result { + pub fn parse(s: String) -> Result { let is_empty_or_whitespace = s.trim().is_empty(); if is_empty_or_whitespace { - return Err(UserErrorCode::WorkspaceIdInvalid); + return Err(ErrorCode::WorkspaceIdInvalid); } Ok(Self(s)) } diff --git a/frontend/rust-lib/flowy-user/src/entities/user_profile.rs b/frontend/rust-lib/flowy-user/src/entities/user_profile.rs index 04f6562681..a535d2b026 100644 --- a/frontend/rust-lib/flowy-user/src/entities/user_profile.rs +++ b/frontend/rust-lib/flowy-user/src/entities/user_profile.rs @@ -1,9 +1,10 @@ -use crate::errors::ErrorCode; -use flowy_derive::ProtoBuf; use std::convert::TryInto; -use user_model::{ - UpdateUserProfileParams, UserEmail, UserIcon, UserName, UserOpenaiKey, UserPassword, UserProfile, -}; + +use flowy_derive::ProtoBuf; + +use crate::entities::parser::{UserEmail, UserIcon, UserName, UserOpenaiKey, UserPassword}; +use crate::entities::{UpdateUserProfileParams, UserProfile}; +use crate::errors::ErrorCode; #[derive(Default, ProtoBuf)] pub struct UserTokenPB { diff --git a/frontend/rust-lib/flowy-user/src/event_handler.rs b/frontend/rust-lib/flowy-user/src/event_handler.rs index edf9aebd1e..6f1eb6ade2 100644 --- a/frontend/rust-lib/flowy-user/src/event_handler.rs +++ b/frontend/rust-lib/flowy-user/src/event_handler.rs @@ -1,10 +1,12 @@ -use crate::entities::*; -use crate::services::UserSession; +use std::{convert::TryInto, sync::Arc}; + use flowy_error::FlowyError; use flowy_sqlite::kv::KV; use lib_dispatch::prelude::*; -use std::{convert::TryInto, sync::Arc}; -use user_model::{SignInParams, SignUpParams, UpdateUserProfileParams}; + +use crate::entities::*; +use crate::entities::{SignInParams, SignUpParams, UpdateUserProfileParams}; +use crate::services::UserSession; // tracing instrument 👉🏻 https://docs.rs/tracing/0.1.26/tracing/attr.instrument.html #[tracing::instrument(level = "debug", name = "sign_in", skip(data, session), fields(email = %data.email), err)] diff --git a/frontend/rust-lib/flowy-user/src/event_map.rs b/frontend/rust-lib/flowy-user/src/event_map.rs index 7981a5e89c..7862fc015e 100644 --- a/frontend/rust-lib/flowy-user/src/event_map.rs +++ b/frontend/rust-lib/flowy-user/src/event_map.rs @@ -1,15 +1,18 @@ -use crate::entities::UserProfilePB; -use crate::event_handler::*; -use crate::{errors::FlowyError, services::UserSession}; +use std::sync::Arc; + +use strum_macros::Display; + use flowy_derive::{Flowy_Event, ProtoBuf_Enum}; use flowy_error::FlowyResult; use lib_dispatch::prelude::*; use lib_infra::future::{Fut, FutureResult}; -use std::sync::Arc; -use strum_macros::Display; -use user_model::{ + +use crate::entities::UserProfilePB; +use crate::entities::{ SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfile, }; +use crate::event_handler::*; +use crate::{errors::FlowyError, services::UserSession}; pub fn init(user_session: Arc) -> AFPlugin { AFPlugin::new() diff --git a/frontend/rust-lib/flowy-user/src/services/database.rs b/frontend/rust-lib/flowy-user/src/services/database.rs index a76f159044..af0569e5ac 100644 --- a/frontend/rust-lib/flowy-user/src/services/database.rs +++ b/frontend/rust-lib/flowy-user/src/services/database.rs @@ -8,7 +8,8 @@ use parking_lot::RwLock; use flowy_error::FlowyError; use flowy_sqlite::ConnectionPool; use flowy_sqlite::{schema::user_table, DBConnection, Database}; -use user_model::{SignInResponse, SignUpResponse, UpdateUserProfileParams, UserProfile}; + +use crate::entities::{SignInResponse, SignUpResponse, UpdateUserProfileParams, UserProfile}; pub struct UserDB { db_dir: String, diff --git a/frontend/rust-lib/flowy-user/src/services/user_session.rs b/frontend/rust-lib/flowy-user/src/services/user_session.rs index 6983a1ecd2..a2c76285e3 100644 --- a/frontend/rust-lib/flowy-user/src/services/user_session.rs +++ b/frontend/rust-lib/flowy-user/src/services/user_session.rs @@ -11,10 +11,10 @@ use flowy_sqlite::{ schema::{user_table, user_table::dsl}, DBConnection, ExpressionMethods, UserDatabaseConnection, }; -use user_model::{ + +use crate::entities::{ SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfile, }; - use crate::entities::{UserProfilePB, UserSettingPB}; use crate::event_map::UserStatusCallback; use crate::{ @@ -24,9 +24,6 @@ use crate::{ services::database::{UserDB, UserTable, UserTableChangeset}, }; -// lazy_static! { -// static ref ID_GEN: Mutex = Mutex::new(UserIDGenerator::new(1)); -// } pub struct UserSessionConfig { root_dir: String, diff --git a/frontend/scripts/makefile/tests.toml b/frontend/scripts/makefile/tests.toml index 483869250f..cb173e15fd 100644 --- a/frontend/scripts/makefile/tests.toml +++ b/frontend/scripts/makefile/tests.toml @@ -44,7 +44,7 @@ env = { RUST_LOG = "info" } description = "Run rust-lib unit tests" script = ''' cd rust-lib -cargo test --no-default-features --features="sync, rev-sqlite" +cargo test --no-default-features --features="rev-sqlite" ''' [tasks.shared_lib_unit_test] @@ -100,7 +100,7 @@ script = [""" CARGO_INCREMENTAL=0 \ RUSTFLAGS='-C instrument-coverage' \ LLVM_PROFILE_FILE='prof-%p-%m.profraw' \ - cargo test --no-default-features --features="sync,rev-sqlite" + cargo test --no-default-features --features="rev-sqlite" """] [tasks.run_sharedlib_coverage_tests] diff --git a/shared-lib/Cargo.lock b/shared-lib/Cargo.lock index 7437eb1e5f..e743c3327f 100644 --- a/shared-lib/Cargo.lock +++ b/shared-lib/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -20,34 +29,6 @@ dependencies = [ "libc", ] -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - -[[package]] -name = "async-stream" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "async-trait" version = "0.1.64" @@ -56,7 +37,7 @@ checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -65,7 +46,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -82,6 +63,15 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "basic-toml" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0de75129aa8d0cceaf750b89013f0e08804d6ec61416da787b35ad0d7cddf1" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -112,6 +102,25 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.12.0" @@ -157,6 +166,53 @@ dependencies = [ "winapi", ] +[[package]] +name = "chrono-tz" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf 0.10.1", +] + +[[package]] +name = "chrono-tz-build" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" +dependencies = [ + "parse-zoneinfo", + "phf 0.10.1", + "phf_codegen", +] + +[[package]] +name = "cmd_lib" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ba0f413777386d37f85afa5242f277a7b461905254c1af3c339d4af06800f62" +dependencies = [ + "cmd_lib_macros", + "faccess", + "lazy_static", + "log", + "os_pipe", +] + +[[package]] +name = "cmd_lib_macros" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e66605092ff6c6e37e0246601ae6c3f62dc1880e0599359b5f303497c112dc0" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -168,15 +224,18 @@ dependencies = [ ] [[package]] -name = "config" -version = "0.10.1" +name = "console" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" +checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" dependencies = [ + "encode_unicode", "lazy_static", - "nom", - "serde", - "yaml-rust", + "libc", + "regex", + "terminal_size", + "unicode-width", + "winapi", ] [[package]] @@ -194,6 +253,16 @@ dependencies = [ "libc", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "cxx" version = "1.0.91" @@ -218,7 +287,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 1.0.109", ] [[package]] @@ -235,7 +304,7 @@ checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -246,20 +315,14 @@ checksum = "4c8858831f7781322e539ea39e72449c46b059638250c14344fec8d0aa6e539c" dependencies = [ "cfg-if", "num_cpus", - "parking_lot 0.12.1", + "parking_lot", ] [[package]] -name = "database-model" -version = "0.1.0" -dependencies = [ - "bytes", - "indexmap", - "nanoid", - "serde", - "serde_json", - "serde_repr", -] +name = "deunicode" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" [[package]] name = "digest" @@ -271,14 +334,27 @@ dependencies = [ ] [[package]] -name = "document-model" -version = "0.1.0" +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "revision-model", - "serde", - "serde_json", + "block-buffer 0.10.4", + "crypto-common", ] +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "env_logger" version = "0.8.4" @@ -293,86 +369,105 @@ dependencies = [ ] [[package]] -name = "fake" -version = "2.5.0" +name = "errno" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d68f517805463f3a896a9d29c1d6ff09d3579ded64a7201b4069f8f9c0d52fd" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ - "rand", + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "faccess" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ae66425802d6a903e268ae1a08b8c38ba143520f227a205edf4e9c7e3e26d5" +dependencies = [ + "bitflags", + "libc", + "winapi", ] [[package]] name = "fancy-regex" -version = "0.11.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +checksum = "0678ab2d46fa5195aaf59ad034c083d351377d4af57f3e073c074d0da3e3c766" dependencies = [ "bit-set", "regex", ] [[package]] -name = "flowy-client-network-config" -version = "0.1.0" +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ - "config", - "serde", - "serde-aux", - "serde_json", + "instant", ] [[package]] -name = "flowy-client-ws" +name = "flowy-ast" version = "0.1.0" dependencies = [ - "futures-util", - "lib-infra", - "lib-ws", - "parking_lot 0.12.1", - "serde", - "serde_repr", - "thiserror", - "tokio", - "tracing", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "flowy-server-sync" +name = "flowy-codegen" version = "0.1.0" dependencies = [ - "async-stream", - "bytes", - "dashmap", - "document-model", - "flowy-sync", - "folder-model", - "futures", - "lib-infra", - "lib-ot", + "cmd_lib", + "console", + "fancy-regex", + "flowy-ast", + "itertools", + "lazy_static", "log", - "revision-model", + "phf 0.8.0", + "protoc-bin-vendored", + "protoc-rust", + "quote", "serde", - "tokio", - "tracing", - "ws-model", + "serde_json", + "similar", + "syn 1.0.109", + "tera", + "toml", + "walkdir", ] [[package]] -name = "flowy-sync" +name = "flowy-derive" version = "0.1.0" dependencies = [ - "document-model", - "folder-model", - "lib-infra", - "lib-ot", - "parking_lot 0.12.1", - "revision-model", - "serde", - "strum", - "strum_macros", + "dashmap", + "flowy-ast", + "flowy-codegen", + "lazy_static", + "log", + "proc-macro2", + "quote", + "serde_json", + "syn 1.0.109", "tokio", - "tracing", - "ws-model", + "trybuild", + "walkdir", ] [[package]] @@ -381,16 +476,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 = "form_urlencoded" version = "1.1.0" @@ -456,7 +541,7 @@ checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -499,6 +584,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.3" @@ -510,6 +606,36 @@ dependencies = [ "wasi 0.10.0+wasi-snapshot-preview1", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" +dependencies = [ + "aho-corasick 0.7.18", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -534,6 +660,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "http" version = "0.2.5" @@ -551,6 +683,15 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "humantime" version = "2.1.0" @@ -581,17 +722,6 @@ dependencies = [ "cxx-build", ] -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.3.0" @@ -602,6 +732,23 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "ignore" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +dependencies = [ + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "1.9.2" @@ -628,6 +775,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -649,19 +816,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lexical-core" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" -dependencies = [ - "arrayvec", - "bitflags", - "cfg-if", - "ryu", - "static_assertions", -] - [[package]] name = "lib-infra" version = "0.1.0" @@ -672,7 +826,7 @@ dependencies = [ "futures-core", "md5", "pin-project", - "rand", + "rand 0.8.5", "tokio", ] @@ -706,7 +860,7 @@ dependencies = [ "futures-util", "lib-infra", "log", - "parking_lot 0.12.1", + "parking_lot", "pin-project", "protobuf", "serde", @@ -725,6 +879,12 @@ version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + [[package]] name = "link-cplusplus" version = "1.0.8" @@ -735,10 +895,10 @@ dependencies = [ ] [[package]] -name = "linked-hash-map" -version = "0.5.6" +name = "linux-raw-sys" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" [[package]] name = "lock_api" @@ -758,12 +918,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "md5" version = "0.7.0" @@ -772,9 +926,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "mio" @@ -788,26 +942,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand", -] - -[[package]] -name = "nom" -version = "5.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" -dependencies = [ - "lexical-core", - "memchr", - "version_check", -] - [[package]] name = "num-integer" version = "0.1.44" @@ -833,7 +967,7 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -850,14 +984,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] -name = "parking_lot" -version = "0.11.2" +name = "os_pipe" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213" dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.5", + "libc", + "winapi", ] [[package]] @@ -867,21 +1000,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.3", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -892,17 +1011,153 @@ checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.10", "smallvec", "windows-sys 0.36.1", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", +] + [[package]] name = "percent-encoding" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "pest" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.16", +] + +[[package]] +name = "pest_meta" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros", + "phf_shared 0.8.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", + "uncased", +] + [[package]] name = "pin-project" version = "1.0.12" @@ -920,7 +1175,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -942,10 +1197,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" [[package]] -name = "proc-macro2" -version = "1.0.47" +name = "proc-macro-error" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4ec6d5fe0b140acb27c9a0444118cf55bfbb4e0b259739429abb4521dd67c16" dependencies = [ "unicode-ident", ] @@ -957,36 +1242,109 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] -name = "quickcheck" -version = "1.0.3" +name = "protobuf-codegen" +version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +checksum = "033460afb75cf755fcfc16dfaed20b86468082a2ea24e05ac35ab4a099a017d6" dependencies = [ - "env_logger", - "log", - "rand", + "protobuf", ] [[package]] -name = "quickcheck_macros" -version = "0.9.1" +name = "protoc" +version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608c156fd8e97febc07dc9c2e2c80bf74cfc6ef26893eae3daf8bc2bc94a4b7f" +checksum = "a0218039c514f9e14a5060742ecd50427f8ac4f85a6dc58f2ddb806e318c55ee" dependencies = [ - "proc-macro2", - "quote", - "syn", + "log", + "which", +] + +[[package]] +name = "protoc-bin-vendored" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005ca8623e5633e298ad1f917d8be0a44bcf406bf3cde3b80e63003e49a3f27d" +dependencies = [ + "protoc-bin-vendored-linux-aarch_64", + "protoc-bin-vendored-linux-ppcle_64", + "protoc-bin-vendored-linux-x86_32", + "protoc-bin-vendored-linux-x86_64", + "protoc-bin-vendored-macos-x86_64", + "protoc-bin-vendored-win32", +] + +[[package]] +name = "protoc-bin-vendored-linux-aarch_64" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb9fc9cce84c8694b6ea01cc6296617b288b703719b725b8c9c65f7c5874435" + +[[package]] +name = "protoc-bin-vendored-linux-ppcle_64" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d2a07dcf7173a04d49974930ccbfb7fd4d74df30ecfc8762cf2f895a094516" + +[[package]] +name = "protoc-bin-vendored-linux-x86_32" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54fef0b04fcacba64d1d80eed74a20356d96847da8497a59b0a0a436c9165b0" + +[[package]] +name = "protoc-bin-vendored-linux-x86_64" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8782f2ce7d43a9a5c74ea4936f001e9e8442205c244f7a3d4286bd4c37bc924" + +[[package]] +name = "protoc-bin-vendored-macos-x86_64" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5de656c7ee83f08e0ae5b81792ccfdc1d04e7876b1d9a38e6876a9e09e02537" + +[[package]] +name = "protoc-bin-vendored-win32" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9653c3ed92974e34c5a6e0a510864dab979760481714c172e0a34e437cb98804" + +[[package]] +name = "protoc-rust" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f8a182bb17c485f20bdc4274a8c39000a61024cfe461c799b50fec77267838" +dependencies = [ + "protobuf", + "protobuf-codegen", + "protoc", + "tempfile", ] [[package]] name = "quote" -version = "1.0.10" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + [[package]] name = "rand" version = "0.8.5" @@ -994,8 +1352,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -1005,7 +1373,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -1014,7 +1391,25 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.3", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", ] [[package]] @@ -1027,30 +1422,43 @@ dependencies = [ ] [[package]] -name = "regex" -version = "1.5.4" +name = "redox_syscall" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "aho-corasick", + "bitflags", +] + +[[package]] +name = "regex" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +dependencies = [ + "aho-corasick 1.0.1", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" [[package]] -name = "revision-model" -version = "0.1.0" +name = "rustix" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b24138615de35e32031d041a09032ef3487a616d901ca4db224e7d557efae2" dependencies = [ - "bytes", - "md5", - "serde", - "serde_json", + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", ] [[package]] @@ -1059,6 +1467,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -1073,33 +1490,22 @@ checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] -[[package]] -name = "serde-aux" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905f2fc9f3d1574e8b5923a58118240021f01d4e239673937ffb9f42707a4f22" -dependencies = [ - "chrono", - "serde", - "serde_json", -] - [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.16", ] [[package]] @@ -1121,29 +1527,7 @@ checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "serial_test" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0bccbcf40c8938196944a3da0e133e031a33f4d6b72db3bda3cc556e361905d" -dependencies = [ - "lazy_static", - "parking_lot 0.11.2", - "serial_test_derive", -] - -[[package]] -name = "serial_test_derive" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2acd6defeddb41eb60bb468f8825d0cfd0c2a76bc03bfd235b6a1dc4f6a1ad5" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1152,13 +1536,24 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if", "cpufeatures", - "digest", + "digest 0.9.0", "opaque-debug", ] +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.6", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1168,12 +1563,33 @@ dependencies = [ "libc", ] +[[package]] +name = "similar" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec" + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slab" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +[[package]] +name = "slug" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" +dependencies = [ + "deunicode", +] + [[package]] name = "smallvec" version = "1.7.0" @@ -1190,12 +1606,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strum" version = "0.21.0" @@ -1211,20 +1621,67 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "syn" -version = "1.0.105" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "tera" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a665751302f22a03c56721e23094e4dc22b04a80f381e6737a07bf7a7c70c0" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand 0.8.5", + "regex", + "serde", + "serde_json", + "slug", + "thread_local", + "unic-segment", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -1235,23 +1692,42 @@ dependencies = [ ] [[package]] -name = "thiserror" -version = "1.0.30" +name = "terminal_size" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.16", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", ] [[package]] @@ -1292,7 +1768,7 @@ dependencies = [ "memchr", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -1308,7 +1784,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1324,6 +1800,15 @@ dependencies = [ "tungstenite", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "tracing" version = "0.1.29" @@ -1345,7 +1830,7 @@ checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1357,6 +1842,21 @@ dependencies = [ "lazy_static", ] +[[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" @@ -1369,7 +1869,7 @@ dependencies = [ "http", "httparse", "log", - "rand", + "rand 0.8.5", "sha-1", "thiserror", "url", @@ -1382,6 +1882,71 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" +[[package]] +name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + +[[package]] +name = "uncased" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" +dependencies = [ + "version_check", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -1422,59 +1987,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", - "idna 0.3.0", + "idna", "percent-encoding", ] -[[package]] -name = "user-model" -version = "0.1.0" -dependencies = [ - "fake", - "fancy-regex", - "futures", - "lazy_static", - "nanoid", - "quickcheck", - "quickcheck_macros", - "rand", - "rand_core", - "serde", - "serde_repr", - "serial_test", - "thiserror", - "tracing", - "unicode-segmentation", - "validator", -] - [[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "validator" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ad5bf234c7d3ad1042e5252b7eddb2c4669ee23f32c7dd0e9b7705f07ef591" -dependencies = [ - "idna 0.2.3", - "lazy_static", - "regex", - "serde", - "serde_derive", - "serde_json", - "url", -] - [[package]] name = "version_check" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -1508,7 +2052,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -1530,7 +2074,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1541,6 +2085,17 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1591,7 +2146,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", ] [[package]] @@ -1600,21 +2164,42 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.1", "windows_aarch64_msvc 0.42.1", "windows_i686_gnu 0.42.1", "windows_i686_msvc 0.42.1", "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.1", "windows_x86_64_msvc 0.42.1", ] +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" @@ -1627,6 +2212,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.36.1" @@ -1639,6 +2230,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.36.1" @@ -1651,6 +2248,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" @@ -1663,12 +2266,24 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" @@ -1682,21 +2297,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] -name = "ws-model" -version = "0.1.0" -dependencies = [ - "bytes", - "revision-model", - "serde", - "serde_json", - "serde_repr", -] - -[[package]] -name = "yaml-rust" -version = "0.4.5" +name = "windows_x86_64_msvc" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/shared-lib/Cargo.toml b/shared-lib/Cargo.toml index f57931e069..a22f84d67c 100644 --- a/shared-lib/Cargo.toml +++ b/shared-lib/Cargo.toml @@ -3,16 +3,9 @@ members = [ "lib-ot", "lib-ws", "lib-infra", - "database-model", - "folder-model", - "document-model", - "ws-model", - "revision-model", - "flowy-sync", - "flowy-server-sync", - "user-model", - "flowy-client-network-config", - "flowy-client-ws", + "flowy-derive", + "flowy-ast", + "flowy-codegen", ] [profile.dev] diff --git a/shared-lib/database-model/Cargo.toml b/shared-lib/database-model/Cargo.toml deleted file mode 100644 index 55ee6a5527..0000000000 --- a/shared-lib/database-model/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "database-model" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -bytes = "1.4" -serde = { version = "1.0", features = ["derive", "rc"] } -serde_json = {version = "1.0"} -serde_repr = "0.1" -nanoid = "0.4.0" -indexmap = {version = "1.9.2", features = ["serde"]} diff --git a/shared-lib/database-model/src/block_rev.rs b/shared-lib/database-model/src/block_rev.rs deleted file mode 100644 index 9adc48da10..0000000000 --- a/shared-lib/database-model/src/block_rev.rs +++ /dev/null @@ -1,85 +0,0 @@ -use indexmap::IndexMap; -use nanoid::nanoid; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::sync::Arc; - -pub fn gen_row_id() -> String { - nanoid!(6) -} - -pub const DEFAULT_ROW_HEIGHT: i32 = 42; - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct DatabaseBlockRevision { - pub block_id: String, - pub rows: Vec>, -} - -pub type FieldId = String; -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct RowRevision { - pub id: String, - pub block_id: String, - /// cells contains key/value pairs. - /// key: field id, - /// value: CellMeta - #[serde(with = "indexmap::serde_seq")] - pub cells: IndexMap, - pub height: i32, - pub visibility: bool, -} - -impl RowRevision { - pub fn new(block_id: &str) -> Self { - Self { - id: gen_row_id(), - block_id: block_id.to_owned(), - cells: Default::default(), - height: DEFAULT_ROW_HEIGHT, - visibility: true, - } - } -} -#[derive(Debug, Clone, Default)] -pub struct RowChangeset { - pub row_id: String, - pub height: Option, - pub visibility: Option, - // Contains the key/value changes represents as the update of the cells. For example, - // if there is one cell was changed, then the `cell_by_field_id` will only have one key/value. - pub cell_by_field_id: HashMap, -} - -impl RowChangeset { - pub fn new(row_id: String) -> Self { - Self { - row_id, - height: None, - visibility: None, - cell_by_field_id: Default::default(), - } - } - - pub fn is_empty(&self) -> bool { - self.height.is_none() && self.visibility.is_none() && self.cell_by_field_id.is_empty() - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct CellRevision { - #[serde(rename = "data")] - pub type_cell_data: String, -} - -impl CellRevision { - pub fn new(data: String) -> Self { - Self { - type_cell_data: data, - } - } - - pub fn is_empty(&self) -> bool { - self.type_cell_data.is_empty() - } -} diff --git a/shared-lib/database-model/src/database_rev.rs b/shared-lib/database-model/src/database_rev.rs deleted file mode 100644 index b017ce6622..0000000000 --- a/shared-lib/database-model/src/database_rev.rs +++ /dev/null @@ -1,260 +0,0 @@ -use crate::{DatabaseBlockRevision, LayoutSetting}; -use bytes::Bytes; -use indexmap::IndexMap; -use nanoid::nanoid; -use serde::{Deserialize, Serialize}; -use serde_repr::{Deserialize_repr, Serialize_repr}; -use std::sync::Arc; - -pub fn gen_database_id() -> String { - // nanoid calculator https://zelark.github.io/nano-id-cc/ - format!("d:{}", nanoid!(10)) -} - -pub fn gen_block_id() -> String { - format!("b:{}", nanoid!(10)) -} - -pub fn gen_field_id() -> String { - nanoid!(6) -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct DatabaseRevision { - #[serde(rename = "grid_id")] - pub database_id: String, - pub fields: Vec>, - pub blocks: Vec>, -} - -impl DatabaseRevision { - pub fn new(database_id: &str) -> Self { - Self { - database_id: database_id.to_owned(), - fields: vec![], - blocks: vec![], - } - } - - pub fn from_build_context( - database_id: &str, - field_revs: Vec>, - block_metas: Vec, - ) -> Self { - Self { - database_id: database_id.to_owned(), - fields: field_revs, - blocks: block_metas.into_iter().map(Arc::new).collect(), - } - } -} - -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct DatabaseBlockMetaRevision { - pub block_id: String, - pub start_row_index: i32, - pub row_count: i32, -} - -impl DatabaseBlockMetaRevision { - pub fn len(&self) -> i32 { - self.row_count - } - - pub fn is_empty(&self) -> bool { - self.row_count == 0 - } -} - -impl DatabaseBlockMetaRevision { - pub fn new() -> Self { - DatabaseBlockMetaRevision { - block_id: gen_block_id(), - ..Default::default() - } - } -} - -pub struct DatabaseBlockMetaRevisionChangeset { - pub block_id: String, - pub start_row_index: Option, - pub row_count: Option, -} - -impl DatabaseBlockMetaRevisionChangeset { - pub fn from_row_count(block_id: String, row_count: i32) -> Self { - Self { - block_id, - start_row_index: None, - row_count: Some(row_count), - } - } -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize, Eq, PartialEq)] -pub struct FieldRevision { - pub id: String, - - pub name: String, - - pub desc: String, - - #[serde(rename = "field_type")] - pub ty: FieldTypeRevision, - - pub frozen: bool, - - pub visibility: bool, - - pub width: i32, - - /// type_options contains key/value pairs - /// key: id of the FieldType - /// value: type-option data that can be parsed into specified TypeOptionStruct. - /// - /// For example, CheckboxTypeOption, MultiSelectTypeOption etc. - #[serde(with = "indexmap::serde_seq")] - pub type_options: IndexMap, - - #[serde(default = "DEFAULT_IS_PRIMARY_VALUE")] - pub is_primary: bool, -} - -impl AsRef for FieldRevision { - fn as_ref(&self) -> &FieldRevision { - self - } -} - -const DEFAULT_IS_PRIMARY_VALUE: fn() -> bool = || false; - -impl FieldRevision { - pub fn new>( - name: &str, - desc: &str, - field_type: T, - width: i32, - is_primary: bool, - ) -> Self { - Self { - id: gen_field_id(), - name: name.to_string(), - desc: desc.to_string(), - ty: field_type.into(), - frozen: false, - visibility: true, - width, - type_options: Default::default(), - is_primary, - } - } - - pub fn insert_type_option(&mut self, type_option: &T) - where - T: TypeOptionDataSerializer + ?Sized, - { - let id = self.ty.to_string(); - self.type_options.insert(id, type_option.json_str()); - } - - pub fn get_type_option( - &self, - field_type_rev: FieldTypeRevision, - ) -> Option { - let id = field_type_rev.to_string(); - self.type_options.get(&id).map(|s| T::from_json_str(s)) - } - - pub fn insert_type_option_str(&mut self, field_type: &FieldTypeRevision, json_str: String) { - let id = field_type.to_string(); - self.type_options.insert(id, json_str); - } - - pub fn get_type_option_str>(&self, field_type: T) -> Option<&str> { - let field_type_rev = field_type.into(); - let id = field_type_rev.to_string(); - self.type_options.get(&id).map(|s| s.as_str()) - } -} - -/// The macro [impl_type_option] will implement the [TypeOptionDataSerializer] for the type that -/// supports the serde trait and the TryInto trait. -pub trait TypeOptionDataSerializer { - fn json_str(&self) -> String; - fn protobuf_bytes(&self) -> Bytes; -} - -/// The macro [impl_type_option] will implement the [TypeOptionDataDeserializer] for the type that -/// supports the serde trait and the TryFrom trait. -pub trait TypeOptionDataDeserializer { - fn from_json_str(s: &str) -> Self; - fn from_protobuf_bytes(bytes: Bytes) -> Self; -} - -#[derive(Clone, Default, Deserialize, Serialize)] -pub struct BuildDatabaseContext { - pub field_revs: Vec>, - pub block_metas: Vec, - pub blocks: Vec, - pub layout_setting: LayoutSetting, - - // String in JSON format. It can be deserialized into [GridViewRevision] - pub database_view_data: String, -} - -impl BuildDatabaseContext { - pub fn new() -> Self { - Self::default() - } -} - -impl std::convert::From for Bytes { - fn from(ctx: BuildDatabaseContext) -> Self { - let bytes = serde_json::to_vec(&ctx).unwrap_or_else(|_| vec![]); - Bytes::from(bytes) - } -} - -impl std::convert::TryFrom for BuildDatabaseContext { - type Error = serde_json::Error; - - fn try_from(bytes: Bytes) -> Result { - let ctx: BuildDatabaseContext = serde_json::from_slice(&bytes)?; - Ok(ctx) - } -} - -pub type FieldTypeRevision = u8; -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CalendarLayoutSetting { - pub layout_ty: CalendarLayout, - pub first_day_of_week: i32, - pub show_weekends: bool, - pub show_week_numbers: bool, - pub layout_field_id: String, -} - -impl CalendarLayoutSetting { - pub fn new(layout_field_id: String) -> Self { - CalendarLayoutSetting { - layout_ty: CalendarLayout::default(), - first_day_of_week: DEFAULT_FIRST_DAY_OF_WEEK, - show_weekends: DEFAULT_SHOW_WEEKENDS, - show_week_numbers: DEFAULT_SHOW_WEEK_NUMBERS, - layout_field_id, - } - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Default, Serialize_repr, Deserialize_repr)] -#[repr(u8)] -pub enum CalendarLayout { - #[default] - MonthLayout = 0, - WeekLayout = 1, - DayLayout = 2, -} - -pub const DEFAULT_FIRST_DAY_OF_WEEK: i32 = 0; -pub const DEFAULT_SHOW_WEEKENDS: bool = true; -pub const DEFAULT_SHOW_WEEK_NUMBERS: bool = true; diff --git a/shared-lib/database-model/src/filter_rev.rs b/shared-lib/database-model/src/filter_rev.rs deleted file mode 100644 index 4f4e0e1ccd..0000000000 --- a/shared-lib/database-model/src/filter_rev.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::FieldTypeRevision; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct FilterRevision { - pub id: String, - pub field_id: String, - pub field_type: FieldTypeRevision, - pub condition: u8, - #[serde(default)] - pub content: String, -} diff --git a/shared-lib/database-model/src/group_rev.rs b/shared-lib/database-model/src/group_rev.rs deleted file mode 100644 index 7ffef5b28d..0000000000 --- a/shared-lib/database-model/src/group_rev.rs +++ /dev/null @@ -1,202 +0,0 @@ -use crate::{gen_database_group_id, FieldTypeRevision}; -use serde::{Deserialize, Serialize}; -use serde_json::Error; -use serde_repr::*; - -pub trait GroupConfigurationContentSerde: Sized + Send + Sync { - fn from_json(s: &str) -> Result; - fn to_json(&self) -> Result; -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct GroupConfigurationRevision { - pub id: String, - pub field_id: String, - pub field_type_rev: FieldTypeRevision, - pub groups: Vec, - // This content is serde in Json format - pub content: String, -} - -impl GroupConfigurationRevision { - pub fn new( - field_id: String, - field_type: FieldTypeRevision, - content: T, - ) -> Result - where - T: GroupConfigurationContentSerde, - { - let content = content.to_json()?; - Ok(Self { - id: gen_database_group_id(), - field_id, - field_type_rev: field_type, - groups: vec![], - content, - }) - } -} - -#[derive(Default, Serialize, Deserialize)] -pub struct TextGroupConfigurationRevision { - pub hide_empty: bool, -} - -impl GroupConfigurationContentSerde for TextGroupConfigurationRevision { - fn from_json(s: &str) -> Result { - serde_json::from_str(s) - } - fn to_json(&self) -> Result { - serde_json::to_string(self) - } -} - -#[derive(Default, Serialize, Deserialize)] -pub struct NumberGroupConfigurationRevision { - pub hide_empty: bool, -} - -impl GroupConfigurationContentSerde for NumberGroupConfigurationRevision { - fn from_json(s: &str) -> Result { - serde_json::from_str(s) - } - fn to_json(&self) -> Result { - serde_json::to_string(self) - } -} - -#[derive(Default, Serialize, Deserialize)] -pub struct URLGroupConfigurationRevision { - pub hide_empty: bool, -} - -impl GroupConfigurationContentSerde for URLGroupConfigurationRevision { - fn from_json(s: &str) -> Result { - serde_json::from_str(s) - } - fn to_json(&self) -> Result { - serde_json::to_string(self) - } -} - -#[derive(Default, Serialize, Deserialize)] -pub struct CheckboxGroupConfigurationRevision { - pub hide_empty: bool, -} - -impl GroupConfigurationContentSerde for CheckboxGroupConfigurationRevision { - fn from_json(s: &str) -> Result { - serde_json::from_str(s) - } - - fn to_json(&self) -> Result { - serde_json::to_string(self) - } -} - -#[derive(Default, Serialize, Deserialize)] -pub struct SelectOptionGroupConfigurationRevision { - pub hide_empty: bool, -} - -impl GroupConfigurationContentSerde for SelectOptionGroupConfigurationRevision { - fn from_json(s: &str) -> Result { - serde_json::from_str(s) - } - - fn to_json(&self) -> Result { - serde_json::to_string(self) - } -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -pub struct GroupRevision { - pub id: String, - - #[serde(default)] - pub name: String, - - #[serde(default = "GROUP_REV_VISIBILITY")] - pub visible: bool, -} - -const GROUP_REV_VISIBILITY: fn() -> bool = || true; - -impl GroupRevision { - /// Create a new GroupRevision - /// - /// # Arguments - /// - /// * `id`: identifier for this group revision. This id must be unique. - /// * `group_name`: the name of this group - /// - /// returns: GroupRevision - pub fn new(id: String, group_name: String) -> Self { - Self { - id, - name: group_name, - visible: true, - } - } - - pub fn update_with_other(&mut self, other: &GroupRevision) { - self.visible = other.visible - } -} - -#[derive(Default, Serialize, Deserialize)] -pub struct DateGroupConfigurationRevision { - pub hide_empty: bool, - pub condition: DateCondition, -} - -impl GroupConfigurationContentSerde for DateGroupConfigurationRevision { - fn from_json(s: &str) -> Result { - serde_json::from_str(s) - } - fn to_json(&self) -> Result { - serde_json::to_string(self) - } -} - -#[derive(Serialize_repr, Deserialize_repr)] -#[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 - } -} - -#[cfg(test)] -mod tests { - use crate::{GroupConfigurationRevision, SelectOptionGroupConfigurationRevision}; - - #[test] - fn group_configuration_serde_test() { - let content = SelectOptionGroupConfigurationRevision { hide_empty: false }; - let rev = GroupConfigurationRevision::new("1".to_owned(), 2, content).unwrap(); - let json = serde_json::to_string(&rev).unwrap(); - - let rev: GroupConfigurationRevision = serde_json::from_str(&json).unwrap(); - let _content: SelectOptionGroupConfigurationRevision = - serde_json::from_str(&rev.content).unwrap(); - } - - #[test] - fn group_configuration_serde_test2() { - let content = SelectOptionGroupConfigurationRevision { hide_empty: false }; - let content_json = serde_json::to_string(&content).unwrap(); - let rev = GroupConfigurationRevision::new("1".to_owned(), 2, content).unwrap(); - - assert_eq!(rev.content, content_json); - } -} diff --git a/shared-lib/database-model/src/lib.rs b/shared-lib/database-model/src/lib.rs deleted file mode 100644 index 812f235b84..0000000000 --- a/shared-lib/database-model/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -mod block_rev; -mod database_rev; -mod filter_rev; -mod group_rev; -mod setting_rev; -mod sort_rev; -mod view_rev; - -pub use block_rev::*; -pub use database_rev::*; -pub use filter_rev::*; -pub use group_rev::*; -pub use setting_rev::*; -pub use sort_rev::*; -pub use view_rev::*; diff --git a/shared-lib/database-model/src/setting_rev.rs b/shared-lib/database-model/src/setting_rev.rs deleted file mode 100644 index 61f9b20536..0000000000 --- a/shared-lib/database-model/src/setting_rev.rs +++ /dev/null @@ -1,187 +0,0 @@ -use crate::{ - FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision, SortRevision, -}; -use indexmap::IndexMap; -use nanoid::nanoid; -use serde::{Deserialize, Serialize}; -use std::fmt::Debug; -use std::sync::Arc; - -pub fn gen_database_filter_id() -> String { - nanoid!(6) -} - -pub fn gen_database_group_id() -> String { - nanoid!(6) -} - -pub fn gen_database_sort_id() -> String { - nanoid!(6) -} - -pub type FilterConfiguration = Configuration; - -pub type GroupConfiguration = Configuration; - -pub type SortConfiguration = Configuration; - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -#[serde(transparent)] -pub struct Configuration -where - T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, -{ - /// Key: field_id - /// Value: this value contains key/value. - /// Key: FieldType, - /// Value: the corresponding objects. - #[serde(with = "indexmap::serde_seq")] - inner: IndexMap>, -} - -impl Configuration -where - T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, -{ - pub fn get_mut_objects( - &mut self, - field_id: &str, - field_type: &FieldTypeRevision, - ) -> Option<&mut Vec>> { - let value = self - .inner - .get_mut(field_id) - .and_then(|object_map| object_map.get_mut(field_type)); - if value.is_none() { - eprintln!( - "[Configuration] Can't find the {:?} with", - std::any::type_name::() - ); - } - value - } - - pub fn get_object( - &self, - field_id: &str, - field_type: &FieldTypeRevision, - predicate: impl Fn(&Arc) -> bool, - ) -> Option> { - let objects = self.get_objects(field_id, field_type)?; - let index = objects.iter().position(predicate)?; - objects.get(index).cloned() - } - - pub fn get_mut_object( - &mut self, - field_id: &str, - field_type: &FieldTypeRevision, - predicate: impl Fn(&Arc) -> bool, - ) -> Option<&mut Arc> { - let objects = self.get_mut_objects(field_id, field_type)?; - let index = objects.iter().position(predicate)?; - objects.get_mut(index) - } - - pub fn get_objects( - &self, - field_id: &str, - field_type_rev: &FieldTypeRevision, - ) -> Option>> { - self - .inner - .get(field_id) - .and_then(|object_map| object_map.get(field_type_rev)) - .cloned() - } - - pub fn get_objects_by_field_revs(&self, field_revs: &[Arc]) -> Vec> { - // Get the objects according to the FieldType, so we need iterate the field_revs. - let objects = field_revs - .iter() - .flat_map(|field_rev| { - let field_type = &field_rev.ty; - let field_id = &field_rev.id; - - let object_rev_map = self.inner.get(field_id)?; - let objects: Vec> = object_rev_map.get(field_type)?.clone(); - Some(objects) - }) - .flatten() - .collect::>>(); - objects - } - - pub fn get_all_objects(&self) -> Vec> { - self - .inner - .values() - .flat_map(|map| map.all_objects()) - .collect() - } - - /// add object to the end of the list - pub fn add_object(&mut self, field_id: &str, field_type: &FieldTypeRevision, object: T) { - let object_rev_map = self - .inner - .entry(field_id.to_string()) - .or_insert_with(ObjectIndexMap::::new); - - object_rev_map - .entry(field_type.to_owned()) - .or_insert_with(Vec::new) - .push(Arc::new(object)) - } - - pub fn clear(&mut self) { - self.inner.clear() - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -#[serde(transparent)] -pub struct ObjectIndexMap -where - T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, -{ - #[serde(with = "indexmap::serde_seq")] - pub object_by_field_type: IndexMap>>, -} - -impl ObjectIndexMap -where - T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, -{ - pub fn new() -> Self { - ObjectIndexMap::default() - } - - pub fn all_objects(&self) -> Vec> { - self - .object_by_field_type - .values() - .flatten() - .cloned() - .collect() - } -} - -impl std::ops::Deref for ObjectIndexMap -where - T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, -{ - type Target = IndexMap>>; - - fn deref(&self) -> &Self::Target { - &self.object_by_field_type - } -} - -impl std::ops::DerefMut for ObjectIndexMap -where - T: Debug + Clone + Default + serde::Serialize + serde::de::DeserializeOwned + 'static, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.object_by_field_type - } -} diff --git a/shared-lib/database-model/src/sort_rev.rs b/shared-lib/database-model/src/sort_rev.rs deleted file mode 100644 index ce2f83e95d..0000000000 --- a/shared-lib/database-model/src/sort_rev.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::FieldTypeRevision; -use serde::{Deserialize, Serialize}; -use serde_repr::*; - -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct SortRevision { - pub id: String, - pub field_id: String, - pub field_type: FieldTypeRevision, - pub condition: SortCondition, -} - -#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Hash, Clone, Debug)] -#[repr(u8)] -pub enum SortCondition { - Ascending = 0, - Descending = 1, -} - -impl std::convert::From for SortCondition { - fn from(num: u8) -> Self { - match num { - 0 => SortCondition::Ascending, - 1 => SortCondition::Descending, - _ => SortCondition::Ascending, - } - } -} - -impl std::default::Default for SortCondition { - fn default() -> Self { - Self::Ascending - } -} - -impl std::convert::From for u8 { - fn from(condition: SortCondition) -> Self { - condition as u8 - } -} diff --git a/shared-lib/database-model/src/view_rev.rs b/shared-lib/database-model/src/view_rev.rs deleted file mode 100644 index 8a723c3e37..0000000000 --- a/shared-lib/database-model/src/view_rev.rs +++ /dev/null @@ -1,124 +0,0 @@ -use crate::{FilterConfiguration, GroupConfiguration, SortConfiguration}; -use indexmap::IndexMap; -use nanoid::nanoid; -use serde::{Deserialize, Serialize}; -use serde_repr::*; - -#[allow(dead_code)] -pub fn gen_grid_view_id() -> String { - nanoid!(6) -} - -#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize_repr, Deserialize_repr)] -#[repr(u8)] -pub enum LayoutRevision { - Grid = 0, - Board = 1, - Calendar = 2, -} - -impl ToString for LayoutRevision { - fn to_string(&self) -> String { - let layout_rev = self.clone() as u8; - layout_rev.to_string() - } -} - -impl std::default::Default for LayoutRevision { - fn default() -> Self { - LayoutRevision::Grid - } -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct DatabaseViewRevision { - pub view_id: String, - - #[serde(rename = "grid_id")] - pub database_id: String, - - #[serde(default)] - pub name: String, - - #[serde(default = "DEFAULT_BASE_VALUE")] - pub is_base: bool, - - pub layout: LayoutRevision, - - #[serde(default)] - pub layout_settings: LayoutSetting, - - #[serde(default)] - pub filters: FilterConfiguration, - - #[serde(default)] - pub groups: GroupConfiguration, - - #[serde(default)] - pub sorts: SortConfiguration, -} - -const DEFAULT_BASE_VALUE: fn() -> bool = || true; - -impl DatabaseViewRevision { - pub fn new( - database_id: String, - view_id: String, - is_base: bool, - name: String, - layout: LayoutRevision, - ) -> Self { - DatabaseViewRevision { - database_id, - view_id, - layout, - is_base, - name, - layout_settings: Default::default(), - filters: Default::default(), - groups: Default::default(), - sorts: Default::default(), - } - } - - pub fn from_json(json: String) -> Result { - serde_json::from_str(&json) - } -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(transparent)] -pub struct LayoutSetting { - #[serde(with = "indexmap::serde_seq")] - inner: IndexMap, -} - -impl LayoutSetting { - pub fn new() -> Self { - Self { - inner: Default::default(), - } - } - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } -} - -impl std::ops::Deref for LayoutSetting { - type Target = IndexMap; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl std::ops::DerefMut for LayoutSetting { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct RowOrderRevision { - pub row_id: String, -} diff --git a/shared-lib/document-model/Cargo.toml b/shared-lib/document-model/Cargo.toml deleted file mode 100644 index eeb9f22233..0000000000 --- a/shared-lib/document-model/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "document-model" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -serde = { version = "1.0" } -serde_json = { version = "1.0" } -revision-model = { path = "../revision-model"} \ No newline at end of file diff --git a/shared-lib/document-model/src/document.rs b/shared-lib/document-model/src/document.rs deleted file mode 100644 index 8e8f654637..0000000000 --- a/shared-lib/document-model/src/document.rs +++ /dev/null @@ -1,69 +0,0 @@ -use revision_model::Revision; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Default, Debug, Clone)] -pub struct CreateDocumentParams { - pub doc_id: String, - pub revisions: Vec, -} - -#[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)] -pub struct DocumentInfo { - pub doc_id: String, - pub data: Vec, - pub rev_id: i64, - pub base_rev_id: i64, -} - -impl std::convert::TryFrom for DocumentInfo { - type Error = String; - - fn try_from(revision: Revision) -> Result { - if !revision.is_initial() { - return Err("Revision's rev_id should be 0 when creating the document".to_string()); - } - - Ok(DocumentInfo { - doc_id: revision.object_id, - data: revision.bytes, - rev_id: revision.rev_id, - base_rev_id: revision.base_rev_id, - }) - } -} - -#[derive(Serialize, Deserialize, Default, Debug, Clone)] -pub struct ResetDocumentParams { - pub doc_id: String, - pub revisions: Vec, -} - -#[derive(Serialize, Deserialize, Default, Debug, Clone)] -pub struct DocumentId { - pub value: String, -} -impl AsRef for DocumentId { - fn as_ref(&self) -> &str { - &self.value - } -} - -impl std::convert::From for DocumentId { - fn from(value: String) -> Self { - DocumentId { value } - } -} - -impl std::convert::From for String { - fn from(block_id: DocumentId) -> Self { - block_id.value - } -} - -impl std::convert::From<&String> for DocumentId { - fn from(s: &String) -> Self { - DocumentId { - value: s.to_owned(), - } - } -} diff --git a/shared-lib/document-model/src/lib.rs b/shared-lib/document-model/src/lib.rs deleted file mode 100644 index b3316091b4..0000000000 --- a/shared-lib/document-model/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod document; diff --git a/frontend/rust-lib/flowy-ast/Cargo.toml b/shared-lib/flowy-ast/Cargo.toml similarity index 100% rename from frontend/rust-lib/flowy-ast/Cargo.toml rename to shared-lib/flowy-ast/Cargo.toml diff --git a/frontend/rust-lib/flowy-ast/src/ast.rs b/shared-lib/flowy-ast/src/ast.rs similarity index 100% rename from frontend/rust-lib/flowy-ast/src/ast.rs rename to shared-lib/flowy-ast/src/ast.rs diff --git a/frontend/rust-lib/flowy-ast/src/ctxt.rs b/shared-lib/flowy-ast/src/ctxt.rs similarity index 100% rename from frontend/rust-lib/flowy-ast/src/ctxt.rs rename to shared-lib/flowy-ast/src/ctxt.rs diff --git a/frontend/rust-lib/flowy-ast/src/event_attrs.rs b/shared-lib/flowy-ast/src/event_attrs.rs similarity index 100% rename from frontend/rust-lib/flowy-ast/src/event_attrs.rs rename to shared-lib/flowy-ast/src/event_attrs.rs diff --git a/frontend/rust-lib/flowy-ast/src/lib.rs b/shared-lib/flowy-ast/src/lib.rs similarity index 100% rename from frontend/rust-lib/flowy-ast/src/lib.rs rename to shared-lib/flowy-ast/src/lib.rs diff --git a/frontend/rust-lib/flowy-ast/src/node_attrs.rs b/shared-lib/flowy-ast/src/node_attrs.rs similarity index 100% rename from frontend/rust-lib/flowy-ast/src/node_attrs.rs rename to shared-lib/flowy-ast/src/node_attrs.rs diff --git a/frontend/rust-lib/flowy-ast/src/pb_attrs.rs b/shared-lib/flowy-ast/src/pb_attrs.rs similarity index 100% rename from frontend/rust-lib/flowy-ast/src/pb_attrs.rs rename to shared-lib/flowy-ast/src/pb_attrs.rs diff --git a/frontend/rust-lib/flowy-ast/src/symbol.rs b/shared-lib/flowy-ast/src/symbol.rs similarity index 100% rename from frontend/rust-lib/flowy-ast/src/symbol.rs rename to shared-lib/flowy-ast/src/symbol.rs diff --git a/frontend/rust-lib/flowy-ast/src/ty_ext.rs b/shared-lib/flowy-ast/src/ty_ext.rs similarity index 100% rename from frontend/rust-lib/flowy-ast/src/ty_ext.rs rename to shared-lib/flowy-ast/src/ty_ext.rs diff --git a/shared-lib/flowy-client-network-config/Cargo.toml b/shared-lib/flowy-client-network-config/Cargo.toml deleted file mode 100644 index 3913275b5c..0000000000 --- a/shared-lib/flowy-client-network-config/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "flowy-client-network-config" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -config = { version = "0.10.1", default-features = false, features = ["yaml"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -serde-aux = "1.1.0" diff --git a/shared-lib/flowy-client-network-config/src/lib.rs b/shared-lib/flowy-client-network-config/src/lib.rs deleted file mode 100644 index f1d68e4caa..0000000000 --- a/shared-lib/flowy-client-network-config/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod configuration; -pub use configuration::*; diff --git a/shared-lib/flowy-client-ws/Cargo.toml b/shared-lib/flowy-client-ws/Cargo.toml deleted file mode 100644 index 088e061991..0000000000 --- a/shared-lib/flowy-client-ws/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "flowy-client-ws" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -lib-ws = { path = "../lib-ws" } -lib-infra = { path = "../lib-infra" } -futures-util = "0.3.26" -tokio = { version = "1.26", features = ["sync"]} -parking_lot = "0.12.1" -tracing = { version = "0.1", features = ["log"] } -thiserror = "1.0" -serde_repr = "0.1" -serde = "1.0" diff --git a/shared-lib/flowy-client-ws/src/connection.rs b/shared-lib/flowy-client-ws/src/connection.rs deleted file mode 100644 index ff547a87b3..0000000000 --- a/shared-lib/flowy-client-ws/src/connection.rs +++ /dev/null @@ -1,176 +0,0 @@ -use futures_util::future::BoxFuture; -use lib_infra::future::FutureResult; -use lib_ws::WSController; -pub use lib_ws::{WSConnectState, WSMessageReceiver, WebSocketRawMessage}; -use parking_lot::RwLock; -use serde_repr::*; -use std::sync::Arc; -use thiserror::Error; -use tokio::sync::broadcast; - -#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize_repr, Deserialize_repr)] -#[repr(u8)] -pub enum WSErrorCode { - #[error("Internal error")] - Internal = 0, -} - -pub trait FlowyRawWebSocket: Send + Sync { - fn initialize(&self) -> FutureResult<(), WSErrorCode>; - fn start_connect(&self, addr: String, user_id: i64) -> FutureResult<(), WSErrorCode>; - fn stop_connect(&self) -> FutureResult<(), WSErrorCode>; - fn subscribe_connect_state(&self) -> BoxFuture>; - fn reconnect(&self, count: usize) -> FutureResult<(), WSErrorCode>; - fn add_msg_receiver(&self, receiver: Arc) -> Result<(), WSErrorCode>; - fn ws_msg_sender(&self) -> FutureResult>, WSErrorCode>; -} - -pub trait FlowyWebSocket: Send + Sync { - fn send(&self, msg: WebSocketRawMessage) -> Result<(), WSErrorCode>; -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum NetworkType { - Unknown = 0, - Wifi = 1, - Cell = 2, - Ethernet = 3, - Bluetooth = 4, - VPN = 5, -} - -impl std::default::Default for NetworkType { - fn default() -> Self { - NetworkType::Unknown - } -} - -impl NetworkType { - pub fn is_connect(&self) -> bool { - !matches!(self, NetworkType::Unknown | NetworkType::Bluetooth) - } -} - -pub struct FlowyWebSocketConnect { - inner: Arc, - connect_type: RwLock, - status_notifier: broadcast::Sender, - addr: String, -} - -impl FlowyWebSocketConnect { - pub fn new(addr: String) -> Self { - let ws = Arc::new(Arc::new(WSController::new())); - let (status_notifier, _) = broadcast::channel(10); - FlowyWebSocketConnect { - inner: ws, - connect_type: RwLock::new(NetworkType::default()), - status_notifier, - addr, - } - } - - pub fn from_local(addr: String, ws: Arc) -> Self { - let (status_notifier, _) = broadcast::channel(10); - FlowyWebSocketConnect { - inner: ws, - connect_type: RwLock::new(NetworkType::default()), - status_notifier, - addr, - } - } - - pub async fn init(&self) { - match self.inner.initialize().await { - Ok(_) => {}, - Err(e) => tracing::error!("FlowyWebSocketConnect init error: {:?}", e), - } - } - - pub async fn start(&self, token: String, user_id: i64) -> Result<(), WSErrorCode> { - let addr = format!("{}/{}", self.addr, &token); - self.inner.stop_connect().await?; - self.inner.start_connect(addr, user_id).await?; - Ok(()) - } - - pub async fn stop(&self) { - let _ = self.inner.stop_connect().await; - } - - pub fn update_network_type(&self, new_type: NetworkType) { - tracing::debug!("Network new state: {:?}", new_type); - let old_type = self.connect_type.read().clone(); - let _ = self.status_notifier.send(new_type.clone()); - - if old_type != new_type { - tracing::debug!("Connect type switch from {:?} to {:?}", old_type, new_type); - match (old_type.is_connect(), new_type.is_connect()) { - (false, true) => { - let ws_controller = self.inner.clone(); - tokio::spawn(async move { retry_connect(ws_controller, 100).await }); - }, - (true, false) => { - // - }, - _ => {}, - } - - *self.connect_type.write() = new_type; - } - } - - pub async fn subscribe_websocket_state(&self) -> broadcast::Receiver { - self.inner.subscribe_connect_state().await - } - - pub fn subscribe_network_ty(&self) -> broadcast::Receiver { - self.status_notifier.subscribe() - } - - pub fn add_ws_message_receiver( - &self, - receiver: Arc, - ) -> Result<(), WSErrorCode> { - self.inner.add_msg_receiver(receiver)?; - Ok(()) - } - - pub async fn web_socket(&self) -> Result>, WSErrorCode> { - self.inner.ws_msg_sender().await - } -} - -#[tracing::instrument(level = "debug", skip(ws_conn))] -pub fn listen_on_websocket(ws_conn: Arc) { - let raw_web_socket = ws_conn.inner.clone(); - let _ = tokio::spawn(async move { - let mut notify = ws_conn.inner.subscribe_connect_state().await; - loop { - match notify.recv().await { - Ok(state) => { - tracing::info!("Websocket state changed: {}", state); - match state { - WSConnectState::Init => {}, - WSConnectState::Connected => {}, - WSConnectState::Connecting => {}, - WSConnectState::Disconnected => retry_connect(raw_web_socket.clone(), 100).await, - } - }, - Err(e) => { - tracing::error!("Websocket state notify error: {:?}", e); - break; - }, - } - } - }); -} - -async fn retry_connect(ws: Arc, count: usize) { - match ws.reconnect(count).await { - Ok(_) => {}, - Err(e) => { - tracing::error!("websocket connect failed: {:?}", e); - }, - } -} diff --git a/shared-lib/flowy-client-ws/src/lib.rs b/shared-lib/flowy-client-ws/src/lib.rs deleted file mode 100644 index 15b00f693a..0000000000 --- a/shared-lib/flowy-client-ws/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod connection; -mod ws; - -pub use connection::*; -pub use ws::*; diff --git a/shared-lib/flowy-client-ws/src/ws.rs b/shared-lib/flowy-client-ws/src/ws.rs deleted file mode 100644 index 93ad852f7a..0000000000 --- a/shared-lib/flowy-client-ws/src/ws.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::connection::{FlowyRawWebSocket, FlowyWebSocket}; -use crate::WSErrorCode; -use futures_util::future::BoxFuture; -use lib_infra::future::FutureResult; -pub use lib_ws::{WSConnectState, WSMessageReceiver, WebSocketRawMessage}; -use lib_ws::{WSController, WSSender}; -use std::sync::Arc; -use tokio::sync::broadcast::Receiver; - -impl FlowyRawWebSocket for Arc { - fn initialize(&self) -> FutureResult<(), WSErrorCode> { - FutureResult::new(async { Ok(()) }) - } - - fn start_connect(&self, addr: String, _user_id: i64) -> FutureResult<(), WSErrorCode> { - let cloned_ws = self.clone(); - FutureResult::new(async move { - cloned_ws.start(addr).await.map_err(internal_error)?; - Ok(()) - }) - } - - fn stop_connect(&self) -> FutureResult<(), WSErrorCode> { - let controller = self.clone(); - FutureResult::new(async move { - controller.stop().await; - Ok(()) - }) - } - - fn subscribe_connect_state(&self) -> BoxFuture> { - let cloned_ws = self.clone(); - Box::pin(async move { cloned_ws.subscribe_state().await }) - } - - fn reconnect(&self, count: usize) -> FutureResult<(), WSErrorCode> { - let cloned_ws = self.clone(); - FutureResult::new(async move { - cloned_ws.retry(count).await.map_err(internal_error)?; - Ok(()) - }) - } - - fn add_msg_receiver(&self, receiver: Arc) -> Result<(), WSErrorCode> { - self - .add_ws_message_receiver(receiver) - .map_err(internal_error)?; - Ok(()) - } - - fn ws_msg_sender(&self) -> FutureResult>, WSErrorCode> { - let cloned_self = self.clone(); - FutureResult::new(async move { - match cloned_self - .ws_message_sender() - .await - .map_err(internal_error)? - { - None => Ok(None), - Some(sender) => { - let sender = sender as Arc; - Ok(Some(sender)) - }, - } - }) - } -} - -impl FlowyWebSocket for WSSender { - fn send(&self, msg: WebSocketRawMessage) -> Result<(), WSErrorCode> { - self.send_msg(msg).map_err(internal_error)?; - Ok(()) - } -} - -fn internal_error(_e: T) -> WSErrorCode -where - T: std::fmt::Debug, -{ - WSErrorCode::Internal -} diff --git a/frontend/rust-lib/flowy-codegen/Cargo.toml b/shared-lib/flowy-codegen/Cargo.toml similarity index 100% rename from frontend/rust-lib/flowy-codegen/Cargo.toml rename to shared-lib/flowy-codegen/Cargo.toml diff --git a/frontend/rust-lib/flowy-codegen/src/ast.rs b/shared-lib/flowy-codegen/src/ast.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/ast.rs rename to shared-lib/flowy-codegen/src/ast.rs diff --git a/frontend/rust-lib/flowy-codegen/src/dart_event/dart_event.rs b/shared-lib/flowy-codegen/src/dart_event/dart_event.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/dart_event/dart_event.rs rename to shared-lib/flowy-codegen/src/dart_event/dart_event.rs diff --git a/frontend/rust-lib/flowy-codegen/src/dart_event/event_template.rs b/shared-lib/flowy-codegen/src/dart_event/event_template.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/dart_event/event_template.rs rename to shared-lib/flowy-codegen/src/dart_event/event_template.rs diff --git a/frontend/rust-lib/flowy-codegen/src/dart_event/event_template.tera b/shared-lib/flowy-codegen/src/dart_event/event_template.tera similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/dart_event/event_template.tera rename to shared-lib/flowy-codegen/src/dart_event/event_template.tera diff --git a/frontend/rust-lib/flowy-codegen/src/dart_event/mod.rs b/shared-lib/flowy-codegen/src/dart_event/mod.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/dart_event/mod.rs rename to shared-lib/flowy-codegen/src/dart_event/mod.rs diff --git a/frontend/rust-lib/flowy-codegen/src/flowy_toml.rs b/shared-lib/flowy-codegen/src/flowy_toml.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/flowy_toml.rs rename to shared-lib/flowy-codegen/src/flowy_toml.rs diff --git a/frontend/rust-lib/flowy-codegen/src/lib.rs b/shared-lib/flowy-codegen/src/lib.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/lib.rs rename to shared-lib/flowy-codegen/src/lib.rs diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/ast.rs b/shared-lib/flowy-codegen/src/protobuf_file/ast.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/protobuf_file/ast.rs rename to shared-lib/flowy-codegen/src/protobuf_file/ast.rs diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/mod.rs b/shared-lib/flowy-codegen/src/protobuf_file/mod.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/protobuf_file/mod.rs rename to shared-lib/flowy-codegen/src/protobuf_file/mod.rs diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/proto_gen.rs b/shared-lib/flowy-codegen/src/protobuf_file/proto_gen.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/protobuf_file/proto_gen.rs rename to shared-lib/flowy-codegen/src/protobuf_file/proto_gen.rs diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/proto_info.rs b/shared-lib/flowy-codegen/src/protobuf_file/proto_info.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/protobuf_file/proto_info.rs rename to shared-lib/flowy-codegen/src/protobuf_file/proto_info.rs diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/derive_meta/derive_meta.rs b/shared-lib/flowy-codegen/src/protobuf_file/template/derive_meta/derive_meta.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/protobuf_file/template/derive_meta/derive_meta.rs rename to shared-lib/flowy-codegen/src/protobuf_file/template/derive_meta/derive_meta.rs diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/derive_meta/derive_meta.tera b/shared-lib/flowy-codegen/src/protobuf_file/template/derive_meta/derive_meta.tera similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/protobuf_file/template/derive_meta/derive_meta.tera rename to shared-lib/flowy-codegen/src/protobuf_file/template/derive_meta/derive_meta.tera diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/derive_meta/mod.rs b/shared-lib/flowy-codegen/src/protobuf_file/template/derive_meta/mod.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/protobuf_file/template/derive_meta/mod.rs rename to shared-lib/flowy-codegen/src/protobuf_file/template/derive_meta/mod.rs diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/mod.rs b/shared-lib/flowy-codegen/src/protobuf_file/template/mod.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/protobuf_file/template/mod.rs rename to shared-lib/flowy-codegen/src/protobuf_file/template/mod.rs diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/enum.tera b/shared-lib/flowy-codegen/src/protobuf_file/template/proto_file/enum.tera similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/enum.tera rename to shared-lib/flowy-codegen/src/protobuf_file/template/proto_file/enum.tera diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/enum_template.rs b/shared-lib/flowy-codegen/src/protobuf_file/template/proto_file/enum_template.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/enum_template.rs rename to shared-lib/flowy-codegen/src/protobuf_file/template/proto_file/enum_template.rs diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/mod.rs b/shared-lib/flowy-codegen/src/protobuf_file/template/proto_file/mod.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/mod.rs rename to shared-lib/flowy-codegen/src/protobuf_file/template/proto_file/mod.rs diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct.tera b/shared-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct.tera similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct.tera rename to shared-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct.tera diff --git a/frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct_template.rs b/shared-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct_template.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct_template.rs rename to shared-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct_template.rs diff --git a/frontend/rust-lib/flowy-codegen/src/ts_event/event_template.rs b/shared-lib/flowy-codegen/src/ts_event/event_template.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/ts_event/event_template.rs rename to shared-lib/flowy-codegen/src/ts_event/event_template.rs diff --git a/frontend/rust-lib/flowy-codegen/src/ts_event/event_template.tera b/shared-lib/flowy-codegen/src/ts_event/event_template.tera similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/ts_event/event_template.tera rename to shared-lib/flowy-codegen/src/ts_event/event_template.tera diff --git a/frontend/rust-lib/flowy-codegen/src/ts_event/mod.rs b/shared-lib/flowy-codegen/src/ts_event/mod.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/ts_event/mod.rs rename to shared-lib/flowy-codegen/src/ts_event/mod.rs diff --git a/frontend/rust-lib/flowy-codegen/src/util.rs b/shared-lib/flowy-codegen/src/util.rs similarity index 100% rename from frontend/rust-lib/flowy-codegen/src/util.rs rename to shared-lib/flowy-codegen/src/util.rs diff --git a/frontend/rust-lib/flowy-derive/.gitignore b/shared-lib/flowy-derive/.gitignore similarity index 100% rename from frontend/rust-lib/flowy-derive/.gitignore rename to shared-lib/flowy-derive/.gitignore diff --git a/frontend/rust-lib/flowy-derive/Cargo.toml b/shared-lib/flowy-derive/Cargo.toml similarity index 100% rename from frontend/rust-lib/flowy-derive/Cargo.toml rename to shared-lib/flowy-derive/Cargo.toml diff --git a/frontend/rust-lib/flowy-derive/src/dart_event/mod.rs b/shared-lib/flowy-derive/src/dart_event/mod.rs similarity index 100% rename from frontend/rust-lib/flowy-derive/src/dart_event/mod.rs rename to shared-lib/flowy-derive/src/dart_event/mod.rs diff --git a/frontend/rust-lib/flowy-derive/src/lib.rs b/shared-lib/flowy-derive/src/lib.rs similarity index 100% rename from frontend/rust-lib/flowy-derive/src/lib.rs rename to shared-lib/flowy-derive/src/lib.rs diff --git a/frontend/rust-lib/flowy-derive/src/node/mod.rs b/shared-lib/flowy-derive/src/node/mod.rs similarity index 100% rename from frontend/rust-lib/flowy-derive/src/node/mod.rs rename to shared-lib/flowy-derive/src/node/mod.rs diff --git a/frontend/rust-lib/flowy-derive/src/proto_buf/deserialize.rs b/shared-lib/flowy-derive/src/proto_buf/deserialize.rs similarity index 100% rename from frontend/rust-lib/flowy-derive/src/proto_buf/deserialize.rs rename to shared-lib/flowy-derive/src/proto_buf/deserialize.rs diff --git a/frontend/rust-lib/flowy-derive/src/proto_buf/enum_serde.rs b/shared-lib/flowy-derive/src/proto_buf/enum_serde.rs similarity index 100% rename from frontend/rust-lib/flowy-derive/src/proto_buf/enum_serde.rs rename to shared-lib/flowy-derive/src/proto_buf/enum_serde.rs diff --git a/frontend/rust-lib/flowy-derive/src/proto_buf/mod.rs b/shared-lib/flowy-derive/src/proto_buf/mod.rs similarity index 100% rename from frontend/rust-lib/flowy-derive/src/proto_buf/mod.rs rename to shared-lib/flowy-derive/src/proto_buf/mod.rs diff --git a/frontend/rust-lib/flowy-derive/src/proto_buf/serialize.rs b/shared-lib/flowy-derive/src/proto_buf/serialize.rs similarity index 100% rename from frontend/rust-lib/flowy-derive/src/proto_buf/serialize.rs rename to shared-lib/flowy-derive/src/proto_buf/serialize.rs diff --git a/frontend/rust-lib/flowy-derive/src/proto_buf/util.rs b/shared-lib/flowy-derive/src/proto_buf/util.rs similarity index 100% rename from frontend/rust-lib/flowy-derive/src/proto_buf/util.rs rename to shared-lib/flowy-derive/src/proto_buf/util.rs diff --git a/frontend/rust-lib/flowy-derive/tests/progress.rs b/shared-lib/flowy-derive/tests/progress.rs similarity index 100% rename from frontend/rust-lib/flowy-derive/tests/progress.rs rename to shared-lib/flowy-derive/tests/progress.rs diff --git a/shared-lib/flowy-server-sync/Cargo.toml b/shared-lib/flowy-server-sync/Cargo.toml deleted file mode 100644 index 123e452fc0..0000000000 --- a/shared-lib/flowy-server-sync/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "flowy-server-sync" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -revision-model = { path = "../revision-model" } -ws-model = { path = "../ws-model" } -document-model = { path = "../document-model" } -folder-model = { path = "../folder-model" } -flowy-sync = { path = "../flowy-sync" } -bytes = "1.4" -log = "0.4.17" -tokio = { version = "1.26", features = ["full"] } -serde = { version = "1.0", features = ["derive", "rc"] } -lib-ot = { path = "../lib-ot" } -lib-infra = { path = "../lib-infra" } -dashmap = "5" -futures = "0.3.26" -async-stream = "0.3.4" -tracing = { version = "0.1", features = ["log"] } diff --git a/shared-lib/flowy-server-sync/src/lib.rs b/shared-lib/flowy-server-sync/src/lib.rs deleted file mode 100644 index 9990e45b04..0000000000 --- a/shared-lib/flowy-server-sync/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod server_document; -pub mod server_folder; diff --git a/shared-lib/flowy-server-sync/src/server_document/document_manager.rs b/shared-lib/flowy-server-sync/src/server_document/document_manager.rs deleted file mode 100644 index a0ff3dc672..0000000000 --- a/shared-lib/flowy-server-sync/src/server_document/document_manager.rs +++ /dev/null @@ -1,339 +0,0 @@ -use crate::server_document::document_pad::ServerDocument; -use async_stream::stream; -use dashmap::DashMap; -use document_model::document::DocumentInfo; -use flowy_sync::errors::{internal_sync_error, SyncError, SyncResult}; -use flowy_sync::ext::DocumentCloudPersistence; -use flowy_sync::{RevisionSyncResponse, RevisionSynchronizer, RevisionUser}; -use futures::stream::StreamExt; -use lib_ot::core::AttributeHashMap; -use lib_ot::text_delta::DeltaTextOperations; -use revision_model::Revision; -use std::{collections::HashMap, sync::Arc}; -use tokio::{ - sync::{mpsc, oneshot, RwLock}, - task::spawn_blocking, -}; -use ws_model::ws_revision::{ClientRevisionWSData, ServerRevisionWSDataBuilder}; - -pub struct ServerDocumentManager { - document_handlers: Arc>>>, - persistence: Arc, -} - -impl ServerDocumentManager { - pub fn new(persistence: Arc) -> Self { - Self { - document_handlers: Arc::new(RwLock::new(HashMap::new())), - persistence, - } - } - - pub async fn handle_client_revisions( - &self, - user: Arc, - client_data: ClientRevisionWSData, - ) -> Result<(), SyncError> { - let cloned_user = user.clone(); - let ack_id = client_data.rev_id; - let object_id = client_data.object_id; - - let result = match self.get_document_handler(&object_id).await { - None => { - tracing::trace!( - "Can't find the document. Creating the document {}", - object_id - ); - let _ = self - .create_document(&object_id, client_data.revisions) - .await - .map_err(|e| { - SyncError::internal().context(format!("Server create document failed: {}", e)) - })?; - Ok(()) - }, - Some(handler) => { - handler.apply_revisions(user, client_data.revisions).await?; - Ok(()) - }, - }; - - if result.is_ok() { - cloned_user.receive(RevisionSyncResponse::Ack( - ServerRevisionWSDataBuilder::build_ack_message(&object_id, ack_id), - )); - } - result - } - - pub async fn handle_client_ping( - &self, - user: Arc, - client_data: ClientRevisionWSData, - ) -> Result<(), SyncError> { - let rev_id = client_data.rev_id; - let doc_id = client_data.object_id.clone(); - match self.get_document_handler(&doc_id).await { - None => { - tracing::trace!("Document:{} doesn't exist, ignore client ping", doc_id); - Ok(()) - }, - Some(handler) => { - handler.apply_ping(rev_id, user).await?; - Ok(()) - }, - } - } - - pub async fn handle_document_reset( - &self, - doc_id: &str, - mut revisions: Vec, - ) -> Result<(), SyncError> { - revisions.sort_by(|a, b| a.rev_id.cmp(&b.rev_id)); - - match self.get_document_handler(doc_id).await { - None => { - tracing::warn!("Document:{} doesn't exist, ignore document reset", doc_id); - Ok(()) - }, - Some(handler) => { - handler.apply_document_reset(revisions).await?; - Ok(()) - }, - } - } - - async fn get_document_handler(&self, doc_id: &str) -> Option> { - if let Some(handler) = self.document_handlers.read().await.get(doc_id).cloned() { - return Some(handler); - } - - let mut write_guard = self.document_handlers.write().await; - match self.persistence.read_document(doc_id).await { - Ok(doc) => { - let handler = self.create_document_handler(doc).await.unwrap(); - write_guard.insert(doc_id.to_owned(), handler.clone()); - drop(write_guard); - Some(handler) - }, - Err(_) => None, - } - } - - async fn create_document( - &self, - doc_id: &str, - revisions: Vec, - ) -> Result, SyncError> { - match self.persistence.create_document(doc_id, revisions).await? { - None => Err(SyncError::internal().context("Create document info from revisions failed")), - Some(doc) => { - let handler = self.create_document_handler(doc).await?; - self - .document_handlers - .write() - .await - .insert(doc_id.to_owned(), handler.clone()); - Ok(handler) - }, - } - } - - #[tracing::instrument(level = "debug", skip(self, doc), err)] - async fn create_document_handler( - &self, - doc: DocumentInfo, - ) -> Result, SyncError> { - let persistence = self.persistence.clone(); - let handle = spawn_blocking(|| OpenDocumentHandler::new(doc, persistence)) - .await - .map_err(|e| { - SyncError::internal().context(format!("Create document handler failed: {}", e)) - })?; - Ok(Arc::new(handle?)) - } -} - -impl std::ops::Drop for ServerDocumentManager { - fn drop(&mut self) { - log::trace!("ServerDocumentManager was dropped"); - } -} - -type DocumentRevisionSynchronizer = RevisionSynchronizer; - -struct OpenDocumentHandler { - doc_id: String, - sender: mpsc::Sender, - users: DashMap>, -} - -impl OpenDocumentHandler { - fn new( - doc: DocumentInfo, - persistence: Arc, - ) -> Result { - let doc_id = doc.doc_id.clone(); - let (sender, receiver) = mpsc::channel(1000); - let users = DashMap::new(); - - let operations = DeltaTextOperations::from_bytes(&doc.data)?; - let sync_object = ServerDocument::from_operations(&doc_id, operations); - let synchronizer = Arc::new(DocumentRevisionSynchronizer::new( - doc.rev_id, - sync_object, - persistence, - )); - - let queue = DocumentCommandRunner::new(&doc.doc_id, receiver, synchronizer); - tokio::task::spawn(queue.run()); - Ok(Self { - doc_id, - sender, - users, - }) - } - - #[tracing::instrument( - name = "server_document_apply_revision", - level = "trace", - skip(self, user, revisions), - err - )] - async fn apply_revisions( - &self, - user: Arc, - revisions: Vec, - ) -> Result<(), SyncError> { - let (ret, rx) = oneshot::channel(); - self.users.insert(user.user_id(), user.clone()); - let msg = DocumentCommand::ApplyRevisions { - user, - revisions, - ret, - }; - - self.send(msg, rx).await? - } - - async fn apply_ping(&self, rev_id: i64, user: Arc) -> Result<(), SyncError> { - let (ret, rx) = oneshot::channel(); - self.users.insert(user.user_id(), user.clone()); - let msg = DocumentCommand::Ping { user, rev_id, ret }; - self.send(msg, rx).await? - } - - #[tracing::instrument(level = "debug", skip(self, revisions), err)] - async fn apply_document_reset(&self, revisions: Vec) -> Result<(), SyncError> { - let (ret, rx) = oneshot::channel(); - let msg = DocumentCommand::Reset { revisions, ret }; - self.send(msg, rx).await? - } - - async fn send(&self, msg: DocumentCommand, rx: oneshot::Receiver) -> SyncResult { - self - .sender - .send(msg) - .await - .map_err(|e| SyncError::internal().context(format!("Send document command failed: {}", e)))?; - rx.await.map_err(internal_sync_error) - } -} - -impl std::ops::Drop for OpenDocumentHandler { - fn drop(&mut self) { - tracing::trace!("{} OpenDocHandle was dropped", self.doc_id); - } -} - -// #[derive(Debug)] -enum DocumentCommand { - ApplyRevisions { - user: Arc, - revisions: Vec, - ret: oneshot::Sender>, - }, - Ping { - user: Arc, - rev_id: i64, - ret: oneshot::Sender>, - }, - Reset { - revisions: Vec, - ret: oneshot::Sender>, - }, -} - -struct DocumentCommandRunner { - pub doc_id: String, - receiver: Option>, - synchronizer: Arc, -} - -impl DocumentCommandRunner { - fn new( - doc_id: &str, - receiver: mpsc::Receiver, - synchronizer: Arc, - ) -> Self { - Self { - doc_id: doc_id.to_owned(), - receiver: Some(receiver), - synchronizer, - } - } - - async fn run(mut self) { - let mut receiver = self - .receiver - .take() - .expect("DocumentCommandRunner's receiver should only take one time"); - - let stream = stream! { - while let Some(msg) = receiver.recv().await { - yield msg; - } - }; - stream.for_each(|msg| self.handle_message(msg)).await; - } - - async fn handle_message(&self, msg: DocumentCommand) { - match msg { - DocumentCommand::ApplyRevisions { - user, - revisions, - ret, - } => { - let result = self - .synchronizer - .sync_revisions(user, revisions) - .await - .map_err(internal_sync_error); - let _ = ret.send(result); - }, - DocumentCommand::Ping { user, rev_id, ret } => { - let result = self - .synchronizer - .pong(user, rev_id) - .await - .map_err(internal_sync_error); - let _ = ret.send(result); - }, - DocumentCommand::Reset { revisions, ret } => { - let result = self - .synchronizer - .reset(revisions) - .await - .map_err(internal_sync_error); - let _ = ret.send(result); - }, - } - } -} - -impl std::ops::Drop for DocumentCommandRunner { - fn drop(&mut self) { - tracing::trace!("{} DocumentCommandQueue was dropped", self.doc_id); - } -} diff --git a/shared-lib/flowy-server-sync/src/server_document/document_pad.rs b/shared-lib/flowy-server-sync/src/server_document/document_pad.rs deleted file mode 100644 index a7e2da1210..0000000000 --- a/shared-lib/flowy-server-sync/src/server_document/document_pad.rs +++ /dev/null @@ -1,46 +0,0 @@ -use flowy_sync::errors::SyncError; -use flowy_sync::{RevisionOperations, RevisionSyncObject}; -use lib_ot::{core::*, text_delta::DeltaTextOperations}; - -pub struct ServerDocument { - document_id: String, - operations: DeltaTextOperations, -} - -impl ServerDocument { - pub fn from_operations(document_id: &str, operations: DeltaTextOperations) -> Self { - let document_id = document_id.to_owned(); - ServerDocument { - document_id, - operations, - } - } -} - -impl RevisionSyncObject for ServerDocument { - fn object_id(&self) -> &str { - &self.document_id - } - - fn object_json(&self) -> String { - self.operations.json_str() - } - - fn compose(&mut self, other: &DeltaTextOperations) -> Result<(), SyncError> { - let operations = self.operations.compose(other)?; - self.operations = operations; - Ok(()) - } - - fn transform( - &self, - other: &DeltaTextOperations, - ) -> Result<(DeltaTextOperations, DeltaTextOperations), SyncError> { - let value = self.operations.transform(other)?; - Ok(value) - } - - fn set_operations(&mut self, operations: RevisionOperations) { - self.operations = operations; - } -} diff --git a/shared-lib/flowy-server-sync/src/server_document/mod.rs b/shared-lib/flowy-server-sync/src/server_document/mod.rs deleted file mode 100644 index 413933015a..0000000000 --- a/shared-lib/flowy-server-sync/src/server_document/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod document_manager; -mod document_pad; - -pub use document_manager::*; diff --git a/shared-lib/flowy-server-sync/src/server_folder/folder_manager.rs b/shared-lib/flowy-server-sync/src/server_folder/folder_manager.rs deleted file mode 100644 index 6784708dd3..0000000000 --- a/shared-lib/flowy-server-sync/src/server_folder/folder_manager.rs +++ /dev/null @@ -1,291 +0,0 @@ -use crate::server_folder::folder_pad::{FolderOperations, FolderRevisionSynchronizer}; -use crate::server_folder::ServerFolder; -use async_stream::stream; -use flowy_sync::errors::{internal_sync_error, SyncError, SyncResult}; -use flowy_sync::ext::FolderCloudPersistence; -use flowy_sync::{RevisionSyncResponse, RevisionUser}; -use folder_model::FolderInfo; -use futures::stream::StreamExt; -use revision_model::Revision; -use std::{collections::HashMap, sync::Arc}; -use tokio::{ - sync::{mpsc, oneshot, RwLock}, - task::spawn_blocking, -}; -use ws_model::ws_revision::{ClientRevisionWSData, ServerRevisionWSDataBuilder}; - -pub struct ServerFolderManager { - folder_handlers: Arc>>>, - persistence: Arc, -} - -impl ServerFolderManager { - pub fn new(persistence: Arc) -> Self { - Self { - folder_handlers: Arc::new(RwLock::new(HashMap::new())), - persistence, - } - } - - pub async fn handle_client_revisions( - &self, - user: Arc, - client_data: ClientRevisionWSData, - ) -> Result<(), SyncError> { - let cloned_user = user.clone(); - let ack_id = client_data.rev_id; - let folder_id = client_data.object_id; - let user_id = user.user_id(); - - let result = match self.get_folder_handler(&user_id, &folder_id).await { - None => { - let _ = self - .create_folder(&user_id, &folder_id, client_data.revisions) - .await - .map_err(|e| { - SyncError::internal().context(format!("Server create folder failed: {:?}", e)) - })?; - Ok(()) - }, - Some(handler) => { - handler.apply_revisions(user, client_data.revisions).await?; - Ok(()) - }, - }; - - if result.is_ok() { - cloned_user.receive(RevisionSyncResponse::Ack( - ServerRevisionWSDataBuilder::build_ack_message(&folder_id, ack_id), - )); - } - result - } - - pub async fn handle_client_ping( - &self, - user: Arc, - client_data: ClientRevisionWSData, - ) -> Result<(), SyncError> { - let user_id = user.user_id(); - let rev_id = client_data.rev_id; - let folder_id = client_data.object_id.clone(); - match self.get_folder_handler(&user_id, &folder_id).await { - None => { - tracing::trace!("Folder:{} doesn't exist, ignore client ping", folder_id); - Ok(()) - }, - Some(handler) => { - handler.apply_ping(rev_id, user).await?; - Ok(()) - }, - } - } - - async fn get_folder_handler( - &self, - user_id: &str, - folder_id: &str, - ) -> Option> { - let folder_id = folder_id.to_owned(); - if let Some(handler) = self.folder_handlers.read().await.get(&folder_id).cloned() { - return Some(handler); - } - - let mut write_guard = self.folder_handlers.write().await; - match self.persistence.read_folder(user_id, &folder_id).await { - Ok(folder_info) => { - let handler = self - .create_folder_handler(folder_info) - .await - .map_err(internal_sync_error) - .unwrap(); - write_guard.insert(folder_id, handler.clone()); - drop(write_guard); - Some(handler) - }, - Err(_) => None, - } - } - - async fn create_folder_handler( - &self, - folder_info: FolderInfo, - ) -> Result, SyncError> { - let persistence = self.persistence.clone(); - let handle = spawn_blocking(|| OpenFolderHandler::new(folder_info, persistence)) - .await - .map_err(|e| SyncError::internal().context(format!("Create folder handler failed: {}", e)))?; - Ok(Arc::new(handle?)) - } - - #[tracing::instrument(level = "debug", skip(self, revisions), err)] - async fn create_folder( - &self, - user_id: &str, - folder_id: &str, - revisions: Vec, - ) -> Result, SyncError> { - match self - .persistence - .create_folder(user_id, folder_id, revisions) - .await? - { - Some(folder_info) => { - let handler = self.create_folder_handler(folder_info).await?; - self - .folder_handlers - .write() - .await - .insert(folder_id.to_owned(), handler.clone()); - Ok(handler) - }, - None => Err(SyncError::internal().context(String::new())), - } - } -} - -struct OpenFolderHandler { - folder_id: String, - sender: mpsc::Sender, -} - -impl OpenFolderHandler { - fn new( - folder_info: FolderInfo, - persistence: Arc, - ) -> SyncResult { - let (sender, receiver) = mpsc::channel(1000); - let folder_id = folder_info.folder_id.clone(); - let operations = FolderOperations::from_bytes(&folder_info.text)?; - let sync_object = ServerFolder::from_operations(&folder_id, operations); - let synchronizer = Arc::new(FolderRevisionSynchronizer::new( - folder_info.rev_id, - sync_object, - persistence, - )); - - let queue = FolderCommandRunner::new(&folder_id, receiver, synchronizer); - tokio::task::spawn(queue.run()); - - Ok(Self { folder_id, sender }) - } - - #[tracing::instrument( - name = "server_folder_apply_revision", - level = "trace", - skip(self, user, revisions), - err - )] - async fn apply_revisions( - &self, - user: Arc, - revisions: Vec, - ) -> SyncResult<()> { - let (ret, rx) = oneshot::channel(); - let msg = FolderCommand::ApplyRevisions { - user, - revisions, - ret, - }; - - self.send(msg, rx).await? - } - - async fn apply_ping(&self, rev_id: i64, user: Arc) -> Result<(), SyncError> { - let (ret, rx) = oneshot::channel(); - let msg = FolderCommand::Ping { user, rev_id, ret }; - self.send(msg, rx).await? - } - - async fn send(&self, msg: FolderCommand, rx: oneshot::Receiver) -> SyncResult { - self - .sender - .send(msg) - .await - .map_err(|e| SyncError::internal().context(format!("Send folder command failed: {}", e)))?; - rx.await.map_err(internal_sync_error) - } -} - -impl std::ops::Drop for OpenFolderHandler { - fn drop(&mut self) { - tracing::trace!("{} OpenFolderHandler was dropped", self.folder_id); - } -} - -enum FolderCommand { - ApplyRevisions { - user: Arc, - revisions: Vec, - ret: oneshot::Sender>, - }, - Ping { - user: Arc, - rev_id: i64, - ret: oneshot::Sender>, - }, -} - -struct FolderCommandRunner { - folder_id: String, - receiver: Option>, - synchronizer: Arc, -} -impl FolderCommandRunner { - fn new( - folder_id: &str, - receiver: mpsc::Receiver, - synchronizer: Arc, - ) -> Self { - Self { - folder_id: folder_id.to_owned(), - receiver: Some(receiver), - synchronizer, - } - } - - async fn run(mut self) { - let mut receiver = self - .receiver - .take() - .expect("FolderCommandRunner's receiver should only take one time"); - - let stream = stream! { - while let Some(msg) = receiver.recv().await { - yield msg; - } - }; - stream.for_each(|msg| self.handle_message(msg)).await; - } - - async fn handle_message(&self, msg: FolderCommand) { - match msg { - FolderCommand::ApplyRevisions { - user, - revisions, - ret, - } => { - let result = self - .synchronizer - .sync_revisions(user, revisions) - .await - .map_err(internal_sync_error); - let _ = ret.send(result); - }, - FolderCommand::Ping { user, rev_id, ret } => { - let result = self - .synchronizer - .pong(user, rev_id) - .await - .map_err(internal_sync_error); - let _ = ret.send(result); - }, - } - } -} - -impl std::ops::Drop for FolderCommandRunner { - fn drop(&mut self) { - tracing::trace!("{} FolderCommandRunner was dropped", self.folder_id); - } -} diff --git a/shared-lib/flowy-server-sync/src/server_folder/folder_pad.rs b/shared-lib/flowy-server-sync/src/server_folder/folder_pad.rs deleted file mode 100644 index c9f9f70228..0000000000 --- a/shared-lib/flowy-server-sync/src/server_folder/folder_pad.rs +++ /dev/null @@ -1,82 +0,0 @@ -use flowy_sync::errors::SyncError; -use flowy_sync::{RevisionOperations, RevisionSyncObject, RevisionSynchronizer}; -use folder_model::FolderInfo; -use lib_ot::core::{DeltaOperationBuilder, DeltaOperations, EmptyAttributes, OperationTransform}; -use revision_model::Revision; - -pub type FolderRevisionSynchronizer = RevisionSynchronizer; -pub type FolderOperations = DeltaOperations; -pub type FolderOperationsBuilder = DeltaOperationBuilder; - -pub struct ServerFolder { - folder_id: String, - operations: FolderOperations, -} - -impl ServerFolder { - pub fn from_operations(folder_id: &str, operations: FolderOperations) -> Self { - Self { - folder_id: folder_id.to_owned(), - operations, - } - } -} - -impl RevisionSyncObject for ServerFolder { - fn object_id(&self) -> &str { - &self.folder_id - } - - fn object_json(&self) -> String { - self.operations.json_str() - } - - fn compose(&mut self, other: &FolderOperations) -> Result<(), SyncError> { - let operations = self.operations.compose(other)?; - self.operations = operations; - Ok(()) - } - - fn transform( - &self, - other: &FolderOperations, - ) -> Result<(FolderOperations, FolderOperations), SyncError> { - let value = self.operations.transform(other)?; - Ok(value) - } - - fn set_operations(&mut self, operations: RevisionOperations) { - self.operations = operations; - } -} - -#[inline] -pub fn make_folder_from_revisions( - folder_id: &str, - revisions: Vec, -) -> Result, SyncError> { - if revisions.is_empty() { - return Ok(None); - } - - let mut folder_delta = FolderOperations::new(); - let mut base_rev_id = 0; - let mut rev_id = 0; - for revision in revisions { - base_rev_id = revision.base_rev_id; - rev_id = revision.rev_id; - if revision.bytes.is_empty() { - tracing::warn!("revision delta_data is empty"); - } - let delta = FolderOperations::from_bytes(revision.bytes)?; - folder_delta = folder_delta.compose(&delta)?; - } - - let text = folder_delta.json_str(); - Ok(Some(FolderInfo { - folder_id: folder_id.to_string(), - text, - rev_id, - base_rev_id, - })) -} diff --git a/shared-lib/flowy-server-sync/src/server_folder/mod.rs b/shared-lib/flowy-server-sync/src/server_folder/mod.rs deleted file mode 100644 index 7858b783c6..0000000000 --- a/shared-lib/flowy-server-sync/src/server_folder/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod folder_manager; -mod folder_pad; - -pub use folder_manager::*; -pub use folder_pad::*; diff --git a/shared-lib/flowy-sync/Cargo.toml b/shared-lib/flowy-sync/Cargo.toml deleted file mode 100644 index a7fdbcb12a..0000000000 --- a/shared-lib/flowy-sync/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "flowy-sync" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -lib-ot = { path = "../lib-ot" } -lib-infra = { path = "../lib-infra" } -revision-model = { path = "../revision-model" } -folder-model = { path = "../folder-model" } -ws-model = { path = "../ws-model" } -document-model = { path = "../document-model" } -strum = "0.21" -strum_macros = "0.21" -parking_lot = "0.12.1" -tokio = { version = "1.26", features = ["full"] } -serde = { version = "1.0", features = ["derive", "rc"] } -tracing = { version = "0.1", features = ["log"] } diff --git a/shared-lib/flowy-sync/src/errors.rs b/shared-lib/flowy-sync/src/errors.rs deleted file mode 100644 index cc2a822ea2..0000000000 --- a/shared-lib/flowy-sync/src/errors.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::fmt; -use std::fmt::Debug; -use strum_macros::Display; - -macro_rules! static_error { - ($name:ident, $status:expr) => { - #[allow(non_snake_case, missing_docs)] - pub fn $name() -> SyncError { - SyncError { - code: $status, - msg: format!("{}", $status), - } - } - }; -} - -pub type SyncResult = std::result::Result; - -#[derive(Debug, Clone)] -pub struct SyncError { - pub code: ErrorCode, - pub msg: String, -} - -impl SyncError { - fn new(code: ErrorCode, msg: &str) -> Self { - Self { - code, - msg: msg.to_owned(), - } - } - - pub fn context(mut self, error: T) -> Self { - self.msg = format!("{:?}", error); - self - } - - static_error!(serde, ErrorCode::SerdeError); - static_error!(internal, ErrorCode::InternalError); - static_error!(undo, ErrorCode::UndoFail); - static_error!(redo, ErrorCode::RedoFail); - static_error!(out_of_bound, ErrorCode::OutOfBound); - static_error!(record_not_found, ErrorCode::RecordNotFound); - static_error!(revision_conflict, ErrorCode::RevisionConflict); - static_error!( - can_not_delete_primary_field, - ErrorCode::CannotDeleteThePrimaryField - ); - static_error!( - unexpected_empty_revision, - ErrorCode::UnexpectedEmptyRevision - ); -} - -impl fmt::Display for SyncError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}: {}", &self.code, &self.msg) - } -} - -#[derive(Debug, Clone, Display, PartialEq, Eq)] -pub enum ErrorCode { - DocumentIdInvalid = 0, - DocumentNotfound = 1, - UndoFail = 2, - RedoFail = 3, - OutOfBound = 4, - RevisionConflict = 5, - RecordNotFound = 6, - CannotDeleteThePrimaryField = 7, - UnexpectedEmptyRevision = 8, - SerdeError = 100, - InternalError = 101, -} - -impl std::convert::From for SyncError { - fn from(error: lib_ot::errors::OTError) -> Self { - SyncError::new(ErrorCode::InternalError, "").context(error) - } -} - -// impl std::convert::From for SyncError { -// fn from(e: protobuf::ProtobufError) -> Self { -// SyncError::internal().context(e) -// } -// } - -pub fn internal_sync_error(e: T) -> SyncError -where - T: std::fmt::Debug, -{ - SyncError::internal().context(e) -} diff --git a/shared-lib/flowy-sync/src/ext.rs b/shared-lib/flowy-sync/src/ext.rs deleted file mode 100644 index 7fc5bd8251..0000000000 --- a/shared-lib/flowy-sync/src/ext.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::errors::SyncError; -use crate::RevisionSyncPersistence; -use document_model::document::DocumentInfo; -use folder_model::FolderInfo; -use lib_infra::future::BoxResultFuture; -use revision_model::Revision; -use std::fmt::Debug; -use std::sync::Arc; - -pub trait FolderCloudPersistence: Send + Sync + Debug { - fn read_folder(&self, user_id: &str, folder_id: &str) -> BoxResultFuture; - - fn create_folder( - &self, - user_id: &str, - folder_id: &str, - revisions: Vec, - ) -> BoxResultFuture, SyncError>; - - fn save_folder_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError>; - - fn read_folder_revisions( - &self, - folder_id: &str, - rev_ids: Option>, - ) -> BoxResultFuture, SyncError>; - - fn reset_folder( - &self, - folder_id: &str, - revisions: Vec, - ) -> BoxResultFuture<(), SyncError>; -} - -impl RevisionSyncPersistence for Arc { - fn read_revisions( - &self, - object_id: &str, - rev_ids: Option>, - ) -> BoxResultFuture, SyncError> { - (**self).read_folder_revisions(object_id, rev_ids) - } - - fn save_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError> { - (**self).save_folder_revisions(revisions) - } - - fn reset_object( - &self, - object_id: &str, - revisions: Vec, - ) -> BoxResultFuture<(), SyncError> { - (**self).reset_folder(object_id, revisions) - } -} - -pub trait DocumentCloudPersistence: Send + Sync + Debug { - fn read_document(&self, doc_id: &str) -> BoxResultFuture; - - fn create_document( - &self, - doc_id: &str, - revisions: Vec, - ) -> BoxResultFuture, SyncError>; - - fn read_document_revisions( - &self, - doc_id: &str, - rev_ids: Option>, - ) -> BoxResultFuture, SyncError>; - - fn save_document_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError>; - - fn reset_document( - &self, - doc_id: &str, - revisions: Vec, - ) -> BoxResultFuture<(), SyncError>; -} - -impl RevisionSyncPersistence for Arc { - fn read_revisions( - &self, - object_id: &str, - rev_ids: Option>, - ) -> BoxResultFuture, SyncError> { - (**self).read_document_revisions(object_id, rev_ids) - } - - fn save_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError> { - (**self).save_document_revisions(revisions) - } - - fn reset_object( - &self, - object_id: &str, - revisions: Vec, - ) -> BoxResultFuture<(), SyncError> { - (**self).reset_document(object_id, revisions) - } -} diff --git a/shared-lib/flowy-sync/src/lib.rs b/shared-lib/flowy-sync/src/lib.rs deleted file mode 100644 index 7fbd918fc4..0000000000 --- a/shared-lib/flowy-sync/src/lib.rs +++ /dev/null @@ -1,310 +0,0 @@ -pub mod errors; -pub mod ext; -pub mod util; - -use crate::errors::SyncError; -use crate::util::{make_operations_from_revisions, next, pair_rev_id_from_revision_pbs}; -use lib_infra::future::BoxResultFuture; -use lib_ot::core::{DeltaOperations, OperationAttributes}; -use parking_lot::RwLock; -use revision_model::{Revision, RevisionRange}; -use serde::de::DeserializeOwned; -use std::cmp::Ordering; -use std::fmt::Debug; -use std::sync::atomic::AtomicI64; -use std::sync::atomic::Ordering::SeqCst; -use std::sync::Arc; -use std::time::Duration; -use ws_model::ws_revision::{ServerRevisionWSData, ServerRevisionWSDataBuilder}; - -pub type RevisionOperations = DeltaOperations; - -pub trait RevisionUser: Send + Sync + Debug { - fn user_id(&self) -> String; - fn receive(&self, resp: RevisionSyncResponse); -} - -pub enum RevisionSyncResponse { - Pull(ServerRevisionWSData), - Push(ServerRevisionWSData), - Ack(ServerRevisionWSData), -} - -pub trait RevisionSyncObject: Send + Sync + 'static { - fn object_id(&self) -> &str; - - fn object_json(&self) -> String; - - fn compose(&mut self, other: &RevisionOperations) -> Result<(), SyncError>; - - fn transform( - &self, - other: &RevisionOperations, - ) -> Result<(RevisionOperations, RevisionOperations), SyncError>; - - fn set_operations(&mut self, operations: RevisionOperations); -} - -pub trait RevisionSyncPersistence: Send + Sync + 'static { - fn read_revisions( - &self, - object_id: &str, - rev_ids: Option>, - ) -> BoxResultFuture, SyncError>; - - fn save_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError>; - - fn reset_object( - &self, - object_id: &str, - revisions: Vec, - ) -> BoxResultFuture<(), SyncError>; -} - -impl RevisionSyncPersistence for Arc -where - T: RevisionSyncPersistence + Sized, -{ - fn read_revisions( - &self, - object_id: &str, - rev_ids: Option>, - ) -> BoxResultFuture, SyncError> { - (**self).read_revisions(object_id, rev_ids) - } - - fn save_revisions(&self, revisions: Vec) -> BoxResultFuture<(), SyncError> { - (**self).save_revisions(revisions) - } - - fn reset_object( - &self, - object_id: &str, - revisions: Vec, - ) -> BoxResultFuture<(), SyncError> { - (**self).reset_object(object_id, revisions) - } -} - -pub struct RevisionSynchronizer { - object_id: String, - rev_id: AtomicI64, - object: Arc>>, - persistence: Arc, -} - -impl RevisionSynchronizer -where - Attribute: OperationAttributes + DeserializeOwned + serde::Serialize + 'static, -{ - pub fn new(rev_id: i64, sync_object: S, persistence: P) -> RevisionSynchronizer - where - S: RevisionSyncObject, - P: RevisionSyncPersistence, - { - let object = Arc::new(RwLock::new(sync_object)); - let persistence = Arc::new(persistence); - let object_id = object.read().object_id().to_owned(); - RevisionSynchronizer { - object_id, - rev_id: AtomicI64::new(rev_id), - object, - persistence, - } - } - - #[tracing::instrument(level = "trace", skip(self, user, revisions), err)] - pub async fn sync_revisions( - &self, - user: Arc, - revisions: Vec, - ) -> Result<(), SyncError> { - let object_id = self.object_id.clone(); - if revisions.is_empty() { - // Return all the revisions to client - let revisions = self.persistence.read_revisions(&object_id, None).await?; - let data = ServerRevisionWSDataBuilder::build_push_message(&object_id, revisions); - user.receive(RevisionSyncResponse::Push(data)); - return Ok(()); - } - - let server_base_rev_id = self.rev_id.load(SeqCst); - let first_revision = revisions.first().unwrap().clone(); - if self - .is_applied_before(&first_revision, &self.persistence) - .await - { - // Server has received this revision before, so ignore the following revisions - return Ok(()); - } - - match server_base_rev_id.cmp(&first_revision.rev_id) { - Ordering::Less => { - let server_rev_id = next(server_base_rev_id); - if server_base_rev_id == first_revision.base_rev_id - || server_rev_id == first_revision.rev_id - { - // The rev is in the right order, just compose it. - for revision in revisions.iter() { - self.compose_revision(revision)?; - } - self.persistence.save_revisions(revisions).await?; - } else { - // The server ops is outdated, pull the missing revision from the client. - let range = RevisionRange { - start: server_rev_id, - end: first_revision.rev_id, - }; - let msg = ServerRevisionWSDataBuilder::build_pull_message(&self.object_id, range); - user.receive(RevisionSyncResponse::Pull(msg)); - } - }, - Ordering::Equal => { - // Do nothing - tracing::trace!( - "Applied {} revision rev_id is the same as cur_rev_id", - self.object_id - ); - }, - Ordering::Greater => { - // The client ops is outdated. Transform the client revision ops and then - // send the prime ops to the client. Client should compose the this prime - // ops. - let from_rev_id = first_revision.rev_id; - let to_rev_id = server_base_rev_id; - self - .push_revisions_to_user(user, from_rev_id, to_rev_id) - .await; - }, - } - Ok(()) - } - - #[tracing::instrument(level = "trace", skip(self, user), fields(server_rev_id), err)] - pub async fn pong( - &self, - user: Arc, - client_rev_id: i64, - ) -> Result<(), SyncError> { - let object_id = self.object_id.clone(); - let server_rev_id = self.rev_id(); - tracing::Span::current().record("server_rev_id", &server_rev_id); - match server_rev_id.cmp(&client_rev_id) { - Ordering::Less => { - tracing::trace!( - "Client should not send ping and the server should pull the revisions from the client" - ) - }, - Ordering::Equal => tracing::trace!("{} is up to date.", object_id), - Ordering::Greater => { - // The client ops is outdated. Transform the client revision ops and then - // send the prime ops to the client. Client should compose the this prime - // ops. - let from_rev_id = client_rev_id; - let to_rev_id = server_rev_id; - tracing::trace!("Push revisions to user"); - self - .push_revisions_to_user(user, from_rev_id, to_rev_id) - .await; - }, - } - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self, revisions), fields(object_id), err)] - pub async fn reset(&self, revisions: Vec) -> Result<(), SyncError> { - let object_id = self.object_id.clone(); - tracing::Span::current().record("object_id", &object_id.as_str()); - let (_, rev_id) = pair_rev_id_from_revision_pbs(&revisions); - let operations = make_operations_from_revisions(revisions.clone())?; - self.persistence.reset_object(&object_id, revisions).await?; - self.object.write().set_operations(operations); - let _ = self.rev_id.fetch_update(SeqCst, SeqCst, |_e| Some(rev_id)); - Ok(()) - } - - pub fn object_json(&self) -> String { - self.object.read().object_json() - } - - fn compose_revision(&self, revision: &Revision) -> Result<(), SyncError> { - let operations = RevisionOperations::::from_bytes(&revision.bytes)?; - self.compose_operations(operations)?; - let _ = self - .rev_id - .fetch_update(SeqCst, SeqCst, |_e| Some(revision.rev_id)); - Ok(()) - } - - #[tracing::instrument(level = "debug", skip(self, revision))] - fn transform_revision( - &self, - revision: &Revision, - ) -> Result<(RevisionOperations, RevisionOperations), SyncError> { - let client_operations = RevisionOperations::::from_bytes(&revision.bytes)?; - let result = self.object.read().transform(&client_operations)?; - Ok(result) - } - - fn compose_operations(&self, operations: RevisionOperations) -> Result<(), SyncError> { - if operations.is_empty() { - tracing::warn!("Composed operations is empty"); - } - - match self.object.try_write_for(Duration::from_millis(300)) { - None => tracing::error!("Failed to acquire write lock of object"), - Some(mut write_guard) => { - write_guard.compose(&operations)?; - }, - } - Ok(()) - } - - pub(crate) fn rev_id(&self) -> i64 { - self.rev_id.load(SeqCst) - } - - async fn is_applied_before( - &self, - new_revision: &Revision, - persistence: &Arc, - ) -> bool { - let rev_ids = Some(vec![new_revision.rev_id]); - if let Ok(revisions) = persistence.read_revisions(&self.object_id, rev_ids).await { - if let Some(revision) = revisions.first() { - if revision.md5 == new_revision.md5 { - return true; - } - } - }; - - false - } - - async fn push_revisions_to_user(&self, user: Arc, from: i64, to: i64) { - let rev_ids: Vec = (from..=to).collect(); - tracing::debug!("Push revision: {} -> {} to client", from, to); - match self - .persistence - .read_revisions(&self.object_id, Some(rev_ids.clone())) - .await - { - Ok(revisions) => { - if !rev_ids.is_empty() && revisions.is_empty() { - tracing::trace!( - "{}: can not read the revisions in range {:?}", - self.object_id, - rev_ids - ); - // assert_eq!(revisions.is_empty(), rev_ids.is_empty(),); - } - - let data = ServerRevisionWSDataBuilder::build_push_message(&self.object_id, revisions); - user.receive(RevisionSyncResponse::Push(data)); - }, - Err(e) => { - tracing::error!("{:?}", e); - }, - }; - } -} diff --git a/shared-lib/flowy-sync/src/util.rs b/shared-lib/flowy-sync/src/util.rs deleted file mode 100644 index 9cf930c319..0000000000 --- a/shared-lib/flowy-sync/src/util.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::errors::{SyncError, SyncResult}; - -use lib_ot::core::{DeltaOperations, OperationAttributes, OperationTransform}; -use revision_model::Revision; -use serde::de::DeserializeOwned; - -pub fn pair_rev_id_from_revision_pbs(revisions: &[Revision]) -> (i64, i64) { - let mut rev_id = 0; - revisions.iter().for_each(|revision| { - if rev_id < revision.rev_id { - rev_id = revision.rev_id; - } - }); - - if rev_id > 0 { - (rev_id - 1, rev_id) - } else { - (0, rev_id) - } -} - -#[tracing::instrument(level = "trace", skip(revisions), err)] -pub fn make_operations_from_revisions(revisions: Vec) -> SyncResult> -where - T: OperationAttributes + DeserializeOwned + OperationAttributes + serde::Serialize, -{ - let mut new_operations = DeltaOperations::::new(); - for revision in revisions { - if revision.bytes.is_empty() { - return Err(SyncError::unexpected_empty_revision().context("Unexpected Empty revision")); - } - let operations = DeltaOperations::::from_bytes(revision.bytes).map_err(|e| { - let err_msg = format!("Deserialize revision failed: {:?}", e); - SyncError::internal().context(err_msg) - })?; - - new_operations = new_operations.compose(&operations)?; - } - Ok(new_operations) -} - -#[inline] -pub fn next(rev_id: i64) -> i64 { - rev_id + 1 -} diff --git a/shared-lib/folder-model/Cargo.toml b/shared-lib/folder-model/Cargo.toml deleted file mode 100644 index ea3804b6ef..0000000000 --- a/shared-lib/folder-model/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "folder-model" -version = "0.1.0" -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -nanoid = "0.4.0" -chrono = { version = "0.4" } -serde = { version = "1.0", features = ["derive", "rc"] } -serde_repr = "0.1" diff --git a/shared-lib/folder-model/src/app_rev.rs b/shared-lib/folder-model/src/app_rev.rs deleted file mode 100644 index c20575f807..0000000000 --- a/shared-lib/folder-model/src/app_rev.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::{TrashRevision, TrashTypeRevision, ViewRevision}; -use nanoid::nanoid; -use serde::{Deserialize, Serialize}; - -pub fn gen_app_id() -> String { - nanoid!(10) -} -#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct AppRevision { - pub id: String, - - pub workspace_id: String, - - pub name: String, - - pub desc: String, - - pub belongings: Vec, - - #[serde(default)] - pub version: i64, - - #[serde(default)] - pub modified_time: i64, - - #[serde(default)] - pub create_time: i64, -} - -impl std::convert::From for TrashRevision { - fn from(app_rev: AppRevision) -> Self { - TrashRevision { - id: app_rev.id, - name: app_rev.name, - modified_time: app_rev.modified_time, - create_time: app_rev.create_time, - ty: TrashTypeRevision::TrashApp, - } - } -} diff --git a/shared-lib/folder-model/src/folder.rs b/shared-lib/folder-model/src/folder.rs deleted file mode 100644 index d5a1904414..0000000000 --- a/shared-lib/folder-model/src/folder.rs +++ /dev/null @@ -1,9 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)] -pub struct FolderInfo { - pub folder_id: String, - pub text: String, - pub rev_id: i64, - pub base_rev_id: i64, -} diff --git a/shared-lib/folder-model/src/folder_rev.rs b/shared-lib/folder-model/src/folder_rev.rs deleted file mode 100644 index 2c7637eb47..0000000000 --- a/shared-lib/folder-model/src/folder_rev.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::{TrashRevision, WorkspaceRevision}; -use serde::de::{MapAccess, Visitor}; -use serde::{de, Deserialize, Deserializer, Serialize}; -use std::fmt; -use std::sync::Arc; - -#[derive(Debug, Default, Serialize, Clone, Eq, PartialEq)] -pub struct FolderRevision { - pub workspaces: Vec>, - pub trash: Vec>, -} - -impl<'de> Deserialize<'de> for FolderRevision { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct FolderVisitor<'a>(&'a mut Option); - impl<'de, 'a> Visitor<'de> for FolderVisitor<'a> { - type Value = (); - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("Expect struct FolderRevision") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut workspaces: Option> = None; - let mut trash: Option> = None; - while let Some(key) = map.next_key::()? { - if key == "workspaces" && workspaces.is_none() { - workspaces = Some(map.next_value::>()?); - } - if key == "trash" && trash.is_none() { - trash = Some(map.next_value::>()?); - } - } - - if let Some(workspaces) = workspaces { - *self.0 = Some(FolderRevision { - workspaces: workspaces.into_iter().map(Arc::new).collect(), - trash: trash - .unwrap_or_default() - .into_iter() - .map(Arc::new) - .collect(), - }); - Ok(()) - } else { - Err(de::Error::missing_field("workspaces")) - } - } - } - - let mut folder_rev: Option = None; - const FIELDS: &[&str] = &["workspaces", "trash"]; - let _ = serde::Deserializer::deserialize_struct( - deserializer, - "FolderRevision", - FIELDS, - FolderVisitor(&mut folder_rev), - ); - - match folder_rev { - None => Err(de::Error::missing_field("workspaces or trash")), - Some(folder_rev) => Ok(folder_rev), - } - } -} diff --git a/shared-lib/folder-model/src/lib.rs b/shared-lib/folder-model/src/lib.rs deleted file mode 100644 index 42337516c2..0000000000 --- a/shared-lib/folder-model/src/lib.rs +++ /dev/null @@ -1,17 +0,0 @@ -#[macro_use] -mod macros; - -mod app_rev; -pub mod folder; -mod folder_rev; -mod trash_rev; -pub mod user_default; -mod view_rev; -mod workspace_rev; - -pub use app_rev::*; -pub use folder::*; -pub use folder_rev::*; -pub use trash_rev::*; -pub use view_rev::*; -pub use workspace_rev::*; diff --git a/shared-lib/folder-model/src/macros.rs b/shared-lib/folder-model/src/macros.rs deleted file mode 100644 index 95e3c2d638..0000000000 --- a/shared-lib/folder-model/src/macros.rs +++ /dev/null @@ -1,43 +0,0 @@ -#[macro_export] -macro_rules! impl_def_and_def_mut { - ($target:ident, $item: ident) => { - impl std::ops::Deref for $target { - type Target = Vec<$item>; - - fn deref(&self) -> &Self::Target { - &self.items - } - } - impl std::ops::DerefMut for $target { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.items - } - } - - impl $target { - #[allow(dead_code)] - pub fn into_inner(self) -> Vec<$item> { - self.items - } - - #[allow(dead_code)] - pub fn push(&mut self, item: $item) { - if self.items.contains(&item) { - log::error!("add duplicate item: {:?}", item); - return; - } - - self.items.push(item); - } - - #[allow(dead_code)] - pub fn take_items(&mut self) -> Vec<$item> { - std::mem::take(&mut self.items) - } - - pub fn first_or_crash(&self) -> &$item { - self.items.first().unwrap() - } - } - }; -} diff --git a/shared-lib/folder-model/src/trash_rev.rs b/shared-lib/folder-model/src/trash_rev.rs deleted file mode 100644 index 73b8526ebf..0000000000 --- a/shared-lib/folder-model/src/trash_rev.rs +++ /dev/null @@ -1,135 +0,0 @@ -use serde::de::Visitor; -use serde::{Deserialize, Serialize}; -use serde_repr::*; -use std::fmt; -#[derive(Default, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -pub struct TrashRevision { - pub id: String, - - pub name: String, - - #[serde(default)] - pub modified_time: i64, - - #[serde(default)] - pub create_time: i64, - - pub ty: TrashTypeRevision, -} - -#[derive(Eq, PartialEq, Debug, Clone, Hash, Serialize_repr)] -#[repr(u8)] -pub enum TrashTypeRevision { - Unknown = 0, - TrashView = 1, - TrashApp = 2, -} -impl<'de> serde::Deserialize<'de> for TrashTypeRevision { - fn deserialize(deserializer: D) -> core::result::Result - where - D: serde::Deserializer<'de>, - { - struct TrashTypeVisitor(); - - impl<'de> Visitor<'de> for TrashTypeVisitor { - type Value = TrashTypeRevision; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("expected enum TrashTypeRevision with type: u8") - } - - fn visit_i8(self, v: i8) -> Result - where - E: serde::de::Error, - { - self.visit_u8(v as u8) - } - - fn visit_i16(self, v: i16) -> Result - where - E: serde::de::Error, - { - self.visit_u8(v as u8) - } - - fn visit_i32(self, v: i32) -> Result - where - E: serde::de::Error, - { - self.visit_u8(v as u8) - } - - fn visit_i64(self, v: i64) -> Result - where - E: serde::de::Error, - { - self.visit_u8(v as u8) - } - - fn visit_u8(self, v: u8) -> Result - where - E: serde::de::Error, - { - let ty = match v { - 0 => TrashTypeRevision::Unknown, - 1 => TrashTypeRevision::TrashView, - 2 => TrashTypeRevision::TrashApp, - _ => TrashTypeRevision::Unknown, - }; - - Ok(ty) - } - - fn visit_u16(self, v: u16) -> Result - where - E: serde::de::Error, - { - self.visit_u8(v as u8) - } - - fn visit_u32(self, v: u32) -> Result - where - E: serde::de::Error, - { - self.visit_u8(v as u8) - } - - fn visit_u64(self, v: u64) -> Result - where - E: serde::de::Error, - { - self.visit_u8(v as u8) - } - - fn visit_str(self, s: &str) -> Result - where - E: serde::de::Error, - { - let value = match s { - "Unknown" => TrashTypeRevision::Unknown, - "TrashView" => TrashTypeRevision::TrashView, - "TrashApp" => TrashTypeRevision::TrashApp, - _ => TrashTypeRevision::Unknown, - }; - Ok(value) - } - } - - deserializer.deserialize_any(TrashTypeVisitor()) - } -} -impl std::default::Default for TrashTypeRevision { - fn default() -> Self { - TrashTypeRevision::Unknown - } -} - -impl std::convert::From for u8 { - fn from(rev: TrashTypeRevision) -> Self { - match rev { - TrashTypeRevision::Unknown => 0, - TrashTypeRevision::TrashView => 1, - TrashTypeRevision::TrashApp => 2, - } - } -} diff --git a/shared-lib/folder-model/src/user_default.rs b/shared-lib/folder-model/src/user_default.rs deleted file mode 100644 index 348b322415..0000000000 --- a/shared-lib/folder-model/src/user_default.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::{ - gen_app_id, gen_view_id, gen_workspace_id, AppRevision, ViewDataFormatRevision, - ViewLayoutTypeRevision, ViewRevision, WorkspaceRevision, -}; -use chrono::Utc; - -pub fn create_default_workspace() -> WorkspaceRevision { - let time = Utc::now(); - let workspace_id = gen_workspace_id(); - let name = "Workspace".to_string(); - let desc = "".to_string(); - - let apps = vec![create_default_app(workspace_id.to_string(), time)]; - - WorkspaceRevision { - id: workspace_id, - name, - desc, - apps, - modified_time: time.timestamp(), - create_time: time.timestamp(), - } -} - -fn create_default_app(workspace_id: String, time: chrono::DateTime) -> AppRevision { - let app_id = gen_app_id(); - let name = "⭐️ Getting started".to_string(); - let desc = "".to_string(); - - let views = vec![create_default_view(app_id.to_string(), time)]; - - AppRevision { - id: app_id, - workspace_id, - name, - desc, - belongings: views, - version: 0, - modified_time: time.timestamp(), - create_time: time.timestamp(), - } -} - -fn create_default_view(app_id: String, time: chrono::DateTime) -> ViewRevision { - let view_id = gen_view_id(); - let name = "Read me".to_string(); - - ViewRevision::new( - view_id, - app_id, - name, - "".to_string(), - ViewDataFormatRevision::DeltaFormat, - ViewLayoutTypeRevision::Document, - time.timestamp(), - time.timestamp(), - ) -} diff --git a/shared-lib/folder-model/src/view_rev.rs b/shared-lib/folder-model/src/view_rev.rs deleted file mode 100644 index 3817f86a46..0000000000 --- a/shared-lib/folder-model/src/view_rev.rs +++ /dev/null @@ -1,117 +0,0 @@ -use crate::{TrashRevision, TrashTypeRevision}; -use nanoid::nanoid; -use serde::{Deserialize, Serialize}; -use serde_repr::*; -pub fn gen_view_id() -> String { - format!("v:{}", nanoid!(10)) -} - -#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct ViewRevision { - pub id: String, - - #[serde(rename = "belong_to_id")] - pub app_id: String, - - pub name: String, - - pub desc: String, - - #[serde(default)] - #[serde(rename = "data_type")] - pub data_format: ViewDataFormatRevision, - - // #[deprecated] - version: i64, - - pub belongings: Vec, - - #[serde(default)] - pub modified_time: i64, - - #[serde(default)] - pub create_time: i64, - - #[serde(default)] - pub ext_data: String, - - #[serde(default)] - pub thumbnail: String, - - #[serde(default = "DEFAULT_PLUGIN_TYPE")] - #[serde(rename = "plugin_type")] - pub layout: ViewLayoutTypeRevision, -} - -impl ViewRevision { - #[allow(clippy::too_many_arguments)] - pub fn new( - id: String, - app_id: String, - name: String, - desc: String, - data_format: ViewDataFormatRevision, - layout: ViewLayoutTypeRevision, - create_time: i64, - modified_time: i64, - ) -> Self { - Self { - id, - app_id, - name, - desc, - data_format, - version: 0, - belongings: vec![], - modified_time, - create_time, - ext_data: "".to_string(), - thumbnail: "".to_string(), - layout, - } - } -} - -const DEFAULT_PLUGIN_TYPE: fn() -> ViewLayoutTypeRevision = || ViewLayoutTypeRevision::Document; - -impl std::convert::From for TrashRevision { - fn from(view_rev: ViewRevision) -> Self { - TrashRevision { - id: view_rev.id, - name: view_rev.name, - modified_time: view_rev.modified_time, - create_time: view_rev.create_time, - ty: TrashTypeRevision::TrashView, - } - } -} - -#[derive(Eq, PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] -#[repr(u8)] -pub enum ViewDataFormatRevision { - DeltaFormat = 0, - DatabaseFormat = 1, - NodeFormat = 2, -} - -impl std::default::Default for ViewDataFormatRevision { - fn default() -> Self { - ViewDataFormatRevision::DeltaFormat - } -} - -#[derive(Eq, PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] -#[repr(u8)] -pub enum ViewLayoutTypeRevision { - Document = 0, - // The for historical reasons, the value of Grid is not 1. - Grid = 3, - Board = 4, - Calendar = 5, -} - -impl std::default::Default for ViewLayoutTypeRevision { - fn default() -> Self { - ViewLayoutTypeRevision::Document - } -} diff --git a/shared-lib/folder-model/src/workspace_rev.rs b/shared-lib/folder-model/src/workspace_rev.rs deleted file mode 100644 index 9af2f6616d..0000000000 --- a/shared-lib/folder-model/src/workspace_rev.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::AppRevision; -use nanoid::nanoid; -use serde::{Deserialize, Serialize}; -pub fn gen_workspace_id() -> String { - nanoid!(10) -} -#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct WorkspaceRevision { - pub id: String, - - pub name: String, - - pub desc: String, - - pub apps: Vec, - - #[serde(default)] - pub modified_time: i64, - - #[serde(default)] - pub create_time: i64, -} diff --git a/shared-lib/revision-model/Cargo.toml b/shared-lib/revision-model/Cargo.toml deleted file mode 100644 index 6efc018b80..0000000000 --- a/shared-lib/revision-model/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "revision-model" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -serde = { version = "1.0" } -serde_json = { version = "1.0" } -md5 = "0.7.0" -bytes = "1.4" diff --git a/shared-lib/revision-model/src/lib.rs b/shared-lib/revision-model/src/lib.rs deleted file mode 100644 index 5766cc66ce..0000000000 --- a/shared-lib/revision-model/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod revision; - -pub use revision::*; diff --git a/shared-lib/revision-model/src/revision.rs b/shared-lib/revision-model/src/revision.rs deleted file mode 100644 index 1861141861..0000000000 --- a/shared-lib/revision-model/src/revision.rs +++ /dev/null @@ -1,121 +0,0 @@ -use bytes::Bytes; -use serde::{Deserialize, Serialize}; -use std::{convert::TryFrom, fmt::Formatter, ops::RangeInclusive}; - -#[derive(PartialEq, Eq, Clone, Default, Serialize, Deserialize)] -pub struct Revision { - pub base_rev_id: i64, - pub rev_id: i64, - pub bytes: Vec, - pub md5: String, - pub object_id: String, -} - -impl std::convert::From> for Revision { - fn from(data: Vec) -> Self { - let bytes = Bytes::from(data); - Revision::try_from(bytes).unwrap() - } -} - -impl std::convert::TryFrom for Revision { - type Error = serde_json::Error; - - fn try_from(bytes: Bytes) -> Result { - serde_json::from_slice(&bytes) - } -} - -impl Revision { - pub fn new>( - object_id: &str, - base_rev_id: i64, - rev_id: i64, - bytes: Bytes, - md5: T, - ) -> Revision { - let object_id = object_id.to_owned(); - let bytes = bytes.to_vec(); - let base_rev_id = base_rev_id; - let rev_id = rev_id; - - if base_rev_id != 0 { - debug_assert!(base_rev_id <= rev_id); - } - - Self { - base_rev_id, - rev_id, - bytes, - md5: md5.into(), - object_id, - } - } - - pub fn is_empty(&self) -> bool { - self.base_rev_id == self.rev_id - } - - pub fn pair_rev_id(&self) -> (i64, i64) { - (self.base_rev_id, self.rev_id) - } - - pub fn is_initial(&self) -> bool { - self.rev_id == 0 - } - - pub fn initial_revision(object_id: &str, bytes: Bytes) -> Self { - let md5 = format!("{:x}", md5::compute(&bytes)); - Self::new(object_id, 0, 0, bytes, md5) - } - - pub fn to_bytes(&self) -> Bytes { - let bytes = serde_json::to_vec(self).unwrap(); - Bytes::from(bytes) - } -} - -impl std::fmt::Debug for Revision { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - f.write_fmt(format_args!("object_id {}, ", self.object_id))?; - f.write_fmt(format_args!("base_rev_id {}, ", self.base_rev_id))?; - f.write_fmt(format_args!("rev_id {}, ", self.rev_id))?; - Ok(()) - } -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct RevisionRange { - pub start: i64, - pub end: i64, -} - -impl std::fmt::Display for RevisionRange { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("[{},{}]", self.start, self.end)) - } -} - -impl RevisionRange { - pub fn len(&self) -> u64 { - debug_assert!(self.end >= self.start); - if self.end >= self.start { - (self.end - self.start + 1) as u64 - } else { - 0 - } - } - - pub fn is_empty(&self) -> bool { - self.end == self.start - } - - pub fn iter(&self) -> RangeInclusive { - // debug_assert!(self.start != self.end); - RangeInclusive::new(self.start, self.end) - } - - pub fn to_rev_ids(&self) -> Vec { - self.iter().collect::>() - } -} diff --git a/shared-lib/user-model/Cargo.toml b/shared-lib/user-model/Cargo.toml deleted file mode 100644 index 70ed444a82..0000000000 --- a/shared-lib/user-model/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "user-model" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -serde = { version = "1.0" } -validator = "0.16.0" -unicode-segmentation = "1.10" -fancy-regex = "0.11.0" -lazy_static = "1.4.0" -tracing = { version = "0.1", features = ["log"] } -thiserror = "1.0" -serde_repr = "0.1" - -[dev-dependencies] -nanoid = "0.4.0" -quickcheck = "1.0.3" -quickcheck_macros = "0.9.1" -fake = "2.5.0" -#claim = "0.5.0" -futures = "0.3.26" -serial_test = "0.5.1" -rand_core = "0.6.4" -rand = "0.8.5" diff --git a/shared-lib/user-model/src/errors.rs b/shared-lib/user-model/src/errors.rs deleted file mode 100644 index a121297d84..0000000000 --- a/shared-lib/user-model/src/errors.rs +++ /dev/null @@ -1,50 +0,0 @@ -use serde_repr::*; -use thiserror::Error; - -#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize_repr, Deserialize_repr)] -#[repr(u8)] -pub enum UserErrorCode { - #[error("Internal error")] - Internal = 0, - - #[error("Workspace id can not be empty or whitespace")] - WorkspaceIdInvalid = 1, - - #[error("Email can not be empty or whitespace")] - EmailIsEmpty = 2, - - #[error("Email format is not valid")] - EmailFormatInvalid = 3, - - #[error("user id is empty or whitespace")] - UserIdInvalid = 4, - - #[error("User name contain forbidden characters")] - UserNameContainForbiddenCharacters = 5, - - #[error("User name can not be empty or whitespace")] - UserNameIsEmpty = 6, - - #[error("User not exist")] - UserNotExist = 7, - - #[error("Password can not be empty or whitespace")] - PasswordIsEmpty = 8, - - #[error("Password format too long")] - PasswordTooLong = 9, - - #[error("Password contains forbidden characters.")] - PasswordContainsForbidCharacters = 10, - - #[error( - "Password should contain a minimum of 6 characters with 1 special 1 letter and 1 numeric" - )] - PasswordFormatInvalid = 11, - - #[error("Password not match")] - PasswordNotMatch = 12, - - #[error("User name is too long")] - UserNameTooLong = 13, -} diff --git a/shared-lib/user-model/src/lib.rs b/shared-lib/user-model/src/lib.rs deleted file mode 100644 index 4d4645c910..0000000000 --- a/shared-lib/user-model/src/lib.rs +++ /dev/null @@ -1,94 +0,0 @@ -pub mod errors; -pub mod parser; - -pub use parser::*; - -use serde::{Deserialize, Serialize}; - -#[derive(Default, Serialize, Deserialize, Debug)] -pub struct SignInParams { - pub email: String, - pub password: String, - pub name: String, -} - -#[derive(Debug, Default, Serialize, Deserialize, Clone)] -pub struct SignInResponse { - pub user_id: i64, - pub name: String, - pub email: String, - pub token: String, -} - -#[derive(Serialize, Deserialize, Default, Debug)] -pub struct SignUpParams { - pub email: String, - pub name: String, - pub password: String, -} - -#[derive(Serialize, Deserialize, Debug, Default, Clone)] -pub struct SignUpResponse { - pub user_id: i64, - pub name: String, - pub email: String, - pub token: String, -} - -#[derive(Serialize, Deserialize, Default, Debug, Clone)] -pub struct UserProfile { - pub id: i64, - pub email: String, - pub name: String, - pub token: String, - pub icon_url: String, - pub openai_key: String, -} - -#[derive(Serialize, Deserialize, Default, Clone, Debug)] -pub struct UpdateUserProfileParams { - pub id: i64, - pub name: Option, - pub email: Option, - pub password: Option, - pub icon_url: Option, - pub openai_key: Option, -} - -impl UpdateUserProfileParams { - pub fn new(id: i64) -> Self { - Self { - id, - name: None, - email: None, - password: None, - icon_url: None, - openai_key: None, - } - } - - pub fn name(mut self, name: &str) -> Self { - self.name = Some(name.to_owned()); - self - } - - pub fn email(mut self, email: &str) -> Self { - self.email = Some(email.to_owned()); - self - } - - pub fn password(mut self, password: &str) -> Self { - self.password = Some(password.to_owned()); - self - } - - pub fn icon_url(mut self, icon_url: &str) -> Self { - self.icon_url = Some(icon_url.to_owned()); - self - } - - pub fn openai_key(mut self, openai_key: &str) -> Self { - self.openai_key = Some(openai_key.to_owned()); - self - } -} diff --git a/shared-lib/ws-model/Cargo.toml b/shared-lib/ws-model/Cargo.toml deleted file mode 100644 index 0766be02ce..0000000000 --- a/shared-lib/ws-model/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "ws-model" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -serde = { version = "1.0" } -serde_json = { version = "1.0" } -serde_repr = "0.1" -revision-model = { path = "../revision-model"} -bytes = "1.4" diff --git a/shared-lib/ws-model/src/lib.rs b/shared-lib/ws-model/src/lib.rs deleted file mode 100644 index 6ebee9ba34..0000000000 --- a/shared-lib/ws-model/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod ws_revision; diff --git a/shared-lib/ws-model/src/ws_revision.rs b/shared-lib/ws-model/src/ws_revision.rs deleted file mode 100644 index 3737dce935..0000000000 --- a/shared-lib/ws-model/src/ws_revision.rs +++ /dev/null @@ -1,133 +0,0 @@ -use bytes::Bytes; -use revision_model::{Revision, RevisionRange}; -use serde::{Deserialize, Serialize}; -use serde_repr::*; - -#[derive(Debug, Clone, Serialize_repr, Deserialize_repr, Eq, PartialEq, Hash)] -#[repr(u8)] -pub enum ClientRevisionWSDataType { - ClientPushRev = 0, - ClientPing = 1, -} - -impl Default for ClientRevisionWSDataType { - fn default() -> Self { - ClientRevisionWSDataType::ClientPushRev - } -} - -#[derive(Serialize, Deserialize, Default, Debug, Clone)] -pub struct ClientRevisionWSData { - pub object_id: String, - pub ty: ClientRevisionWSDataType, - pub revisions: Vec, - pub rev_id: i64, -} - -impl ClientRevisionWSData { - pub fn from_revisions(object_id: &str, revisions: Vec) -> Self { - let rev_id = match revisions.first() { - None => 0, - Some(revision) => revision.rev_id, - }; - - Self { - object_id: object_id.to_owned(), - ty: ClientRevisionWSDataType::ClientPushRev, - revisions, - rev_id, - } - } - - pub fn ping(object_id: &str, rev_id: i64) -> Self { - Self { - object_id: object_id.to_owned(), - ty: ClientRevisionWSDataType::ClientPing, - revisions: vec![], - rev_id, - } - } -} - -impl std::convert::TryFrom for ClientRevisionWSData { - type Error = serde_json::Error; - - fn try_from(bytes: Bytes) -> Result { - serde_json::from_slice(&bytes) - } -} - -impl std::convert::TryFrom for Bytes { - type Error = serde_json::Error; - - fn try_from(bytes: ClientRevisionWSData) -> Result { - serde_json::to_vec(&bytes).map(Bytes::from) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum WSRevisionPayload { - ServerAck { rev_id: i64 }, - ServerPushRev { revisions: Vec }, - ServerPullRev { range: RevisionRange }, - UserConnect { user: NewDocumentUser }, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ServerRevisionWSData { - pub object_id: String, - pub payload: WSRevisionPayload, -} - -impl std::convert::TryFrom for ServerRevisionWSData { - type Error = serde_json::Error; - - fn try_from(bytes: Bytes) -> Result { - serde_json::from_slice(&bytes) - } -} - -impl std::convert::TryFrom for Bytes { - type Error = serde_json::Error; - - fn try_from(bytes: ServerRevisionWSData) -> Result { - serde_json::to_vec(&bytes).map(Bytes::from) - } -} - -impl ServerRevisionWSData { - pub fn to_bytes(&self) -> Vec { - serde_json::to_vec(&self).unwrap_or_default() - } -} - -pub struct ServerRevisionWSDataBuilder(); -impl ServerRevisionWSDataBuilder { - pub fn build_push_message(object_id: &str, revisions: Vec) -> ServerRevisionWSData { - ServerRevisionWSData { - object_id: object_id.to_string(), - payload: WSRevisionPayload::ServerPushRev { revisions }, - } - } - - pub fn build_pull_message(object_id: &str, range: RevisionRange) -> ServerRevisionWSData { - ServerRevisionWSData { - object_id: object_id.to_string(), - payload: WSRevisionPayload::ServerPullRev { range }, - } - } - - pub fn build_ack_message(object_id: &str, rev_id: i64) -> ServerRevisionWSData { - ServerRevisionWSData { - object_id: object_id.to_string(), - payload: WSRevisionPayload::ServerAck { rev_id }, - } - } -} - -#[derive(Serialize, Deserialize, Default, Debug, Clone)] -pub struct NewDocumentUser { - pub user_id: String, - pub doc_id: String, - pub latest_rev_id: i64, -}