feat: integrate appflowy-cloud (#3359)

* feat: draft: code dependency

* chore: update ref

* feat: signup using client_api

* feat: support auto sign_in after sign_up if already confirmed(WIP)

* chore: update collab commit id

* chore: fix compile errors

* chore: user AFServer trait to provide optional service

* chore: refactor workspace

* chore: disable aws config

* chore: return ws connect

* chore: update collab rev

* chore: fmt and clippy

* chore: fix test

* chore: update chrono version

* chore: add script to update the collab crates commit id

* chore: update

---------

Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
Zack 2023-09-17 17:14:34 +08:00 committed by GitHub
parent cecd4f48ab
commit 1c84ee1d53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
94 changed files with 2681 additions and 2898 deletions

2
.gitignore vendored
View File

@ -41,3 +41,5 @@ pubspec.lock
# ignore the deb filegit
frontend/package
frontend/*.deb
**/Cargo.toml.bak

View File

@ -70,6 +70,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"cfg-if",
"getrandom 0.2.10",
"once_cell",
"version_check",
]
@ -107,6 +108,12 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "allocator-api2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "android-tzdata"
version = "0.1.1"
@ -137,33 +144,12 @@ version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "appflowy-integrate"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
dependencies = [
"anyhow",
"collab",
"collab-database",
"collab-define",
"collab-document",
"collab-folder",
"collab-persistence",
"collab-plugins",
"futures",
"parking_lot",
"serde",
"serde_json",
"tracing",
]
[[package]]
name = "appflowy_tauri"
version = "0.0.0"
dependencies = [
"bytes",
"flowy-core",
"flowy-net",
"flowy-notification",
"lib-dispatch",
"serde",
@ -243,6 +229,15 @@ dependencies = [
"system-deps 6.1.1",
]
[[package]]
name = "atoi"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
dependencies = [
"num-traits",
]
[[package]]
name = "atomic_refcell"
version = "0.1.10"
@ -367,7 +362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
dependencies = [
"borsh-derive",
"hashbrown 0.13.2",
"hashbrown 0.12.3",
]
[[package]]
@ -433,6 +428,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5"
dependencies = [
"memchr",
"once_cell",
"regex-automata",
"serde",
]
@ -592,18 +589,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.26"
version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"time 0.1.45",
"wasm-bindgen",
"winapi",
"windows-targets",
]
[[package]]
@ -671,6 +667,32 @@ dependencies = [
"libloading",
]
[[package]]
name = "client-api"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=8f8f6a#8f8f6af0f9fa1229d43e0dcbc54c62cc41ccaa3b"
dependencies = [
"anyhow",
"bytes",
"collab-sync-protocol",
"futures-util",
"gotrue-entity",
"opener",
"reqwest",
"serde",
"serde_json",
"serde_repr",
"shared_entity",
"storage-entity",
"thiserror",
"tokio",
"tokio-retry",
"tokio-stream",
"tokio-tungstenite",
"tracing",
"url",
]
[[package]]
name = "cmd_lib"
version = "1.3.0"
@ -730,7 +752,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
dependencies = [
"anyhow",
"async-trait",
@ -749,7 +771,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
dependencies = [
"anyhow",
"async-trait",
@ -779,7 +801,7 @@ dependencies = [
[[package]]
name = "collab-define"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
dependencies = [
"anyhow",
"collab",
@ -791,7 +813,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
dependencies = [
"proc-macro2",
"quote",
@ -803,7 +825,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
dependencies = [
"anyhow",
"collab",
@ -823,7 +845,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
dependencies = [
"anyhow",
"chrono",
@ -840,10 +862,30 @@ dependencies = [
"tracing",
]
[[package]]
name = "collab-integrate"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"collab",
"collab-database",
"collab-define",
"collab-document",
"collab-folder",
"collab-persistence",
"collab-plugins",
"futures",
"parking_lot",
"serde",
"serde_json",
"tracing",
]
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
dependencies = [
"async-trait",
"bincode",
@ -864,7 +906,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
dependencies = [
"anyhow",
"async-trait",
@ -872,7 +914,6 @@ dependencies = [
"collab-define",
"collab-persistence",
"collab-sync-protocol",
"collab-ws",
"futures-util",
"lib0",
"parking_lot",
@ -893,10 +934,11 @@ dependencies = [
[[package]]
name = "collab-sync-protocol"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
dependencies = [
"bytes",
"collab",
"collab-define",
"md5",
"serde",
"serde_json",
@ -907,7 +949,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20eff3#20eff314f7963e30bdbe85c860060269b12197ef"
dependencies = [
"anyhow",
"collab",
@ -920,24 +962,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "collab-ws"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e"
dependencies = [
"bytes",
"collab-sync-protocol",
"futures-util",
"serde",
"serde_json",
"thiserror",
"tokio",
"tokio-retry",
"tokio-stream",
"tokio-tungstenite",
"tracing",
]
[[package]]
name = "color_quant"
version = "1.1.0"
@ -1036,6 +1060,21 @@ dependencies = [
"libc",
]
[[package]]
name = "crc"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe"
dependencies = [
"crc-catalog",
]
[[package]]
name = "crc-catalog"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484"
[[package]]
name = "crc32fast"
version = "1.3.2"
@ -1079,6 +1118,16 @@ dependencies = [
"scopeguard",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
@ -1315,6 +1364,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
[[package]]
name = "dotenvy"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "dtoa"
version = "1.0.6"
@ -1347,6 +1402,9 @@ name = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
dependencies = [
"serde",
]
[[package]]
name = "embed-resource"
@ -1418,6 +1476,12 @@ dependencies = [
"backtrace",
]
[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "faccess"
version = "0.2.4"
@ -1522,7 +1586,7 @@ dependencies = [
"console",
"fancy-regex 0.10.0",
"flowy-ast",
"itertools",
"itertools 0.10.5",
"lazy_static",
"log",
"phf 0.8.0",
@ -1556,9 +1620,12 @@ dependencies = [
name = "flowy-core"
version = "0.1.0"
dependencies = [
"appflowy-integrate",
"anyhow",
"bytes",
"collab",
"collab-define",
"collab-integrate",
"collab-plugins",
"diesel",
"flowy-config",
"flowy-database-deps",
@ -1568,7 +1635,6 @@ dependencies = [
"flowy-error",
"flowy-folder-deps",
"flowy-folder2",
"flowy-net",
"flowy-server",
"flowy-server-config",
"flowy-sqlite",
@ -1604,7 +1670,6 @@ name = "flowy-database2"
version = "0.1.0"
dependencies = [
"anyhow",
"appflowy-integrate",
"async-stream",
"async-trait",
"bytes",
@ -1613,6 +1678,7 @@ dependencies = [
"collab",
"collab-database",
"collab-define",
"collab-integrate",
"csv",
"dashmap",
"fancy-regex 0.10.0",
@ -1673,11 +1739,11 @@ name = "flowy-document2"
version = "0.1.0"
dependencies = [
"anyhow",
"appflowy-integrate",
"bytes",
"collab",
"collab-define",
"collab-document",
"collab-integrate",
"flowy-codegen",
"flowy-derive",
"flowy-document-deps",
@ -1719,6 +1785,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"bytes",
"client-api",
"collab-database",
"collab-document",
"flowy-codegen",
@ -1751,12 +1818,12 @@ dependencies = [
name = "flowy-folder2"
version = "0.1.0"
dependencies = [
"appflowy-integrate",
"bytes",
"chrono",
"collab",
"collab-define",
"collab-folder",
"collab-integrate",
"flowy-codegen",
"flowy-derive",
"flowy-error",
@ -1776,17 +1843,6 @@ dependencies = [
"uuid",
]
[[package]]
name = "flowy-net"
version = "0.1.0"
dependencies = [
"bytes",
"flowy-codegen",
"lib-dispatch",
"protobuf",
"tracing",
]
[[package]]
name = "flowy-notification"
version = "0.1.0"
@ -1808,6 +1864,7 @@ dependencies = [
"anyhow",
"bytes",
"chrono",
"client-api",
"collab",
"collab-define",
"collab-document",
@ -1901,7 +1958,6 @@ name = "flowy-user"
version = "0.1.0"
dependencies = [
"anyhow",
"appflowy-integrate",
"base64 0.21.2",
"bytes",
"chrono",
@ -1910,6 +1966,7 @@ dependencies = [
"collab-define",
"collab-document",
"collab-folder",
"collab-integrate",
"collab-user",
"diesel",
"diesel_derives",
@ -2045,6 +2102,17 @@ dependencies = [
"futures-util",
]
[[package]]
name = "futures-intrusive"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
dependencies = [
"futures-core",
"lock_api",
"parking_lot",
]
[[package]]
name = "futures-io"
version = "0.3.28"
@ -2376,6 +2444,15 @@ dependencies = [
"system-deps 6.1.1",
]
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=8f8f6a#8f8f6af0f9fa1229d43e0dcbc54c62cc41ccaa3b"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "gtk"
version = "0.15.5"
@ -2473,6 +2550,19 @@ name = "hashbrown"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
dependencies = [
"ahash 0.8.3",
"allocator-api2",
]
[[package]]
name = "hashlink"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [
"hashbrown 0.14.0",
]
[[package]]
name = "heck"
@ -2488,6 +2578,9 @@ name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
@ -2681,6 +2774,12 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "if_chain"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
[[package]]
name = "ignore"
version = "0.4.20"
@ -2785,6 +2884,15 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.8"
@ -3338,6 +3446,15 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "normpath"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec60c60a693226186f5d6edf073232bfb6464ed97eb22cf3b01c1e8198fd97f5"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@ -3468,6 +3585,17 @@ dependencies = [
"windows-sys 0.42.0",
]
[[package]]
name = "opener"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c62dcb6174f9cb326eac248f07e955d5d559c272730b6c03e396b443b562788"
dependencies = [
"bstr",
"normpath",
"winapi",
]
[[package]]
name = "openssl"
version = "0.10.55"
@ -3595,6 +3723,12 @@ dependencies = [
"regex",
]
[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "pathdiff"
version = "0.2.1"
@ -3863,7 +3997,7 @@ dependencies = [
"line-wrap",
"quick-xml",
"serde",
"time 0.3.22",
"time",
]
[[package]]
@ -4665,9 +4799,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.164"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [
"serde_derive",
]
@ -4685,9 +4819,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.164"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
@ -4696,9 +4830,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.99"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [
"itoa 1.0.6",
"ryu",
@ -4707,9 +4841,9 @@ dependencies = [
[[package]]
name = "serde_repr"
version = "0.1.12"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
dependencies = [
"proc-macro2",
"quote",
@ -4750,7 +4884,7 @@ dependencies = [
"serde",
"serde_json",
"serde_with_macros",
"time 0.3.22",
"time",
]
[[package]]
@ -4834,6 +4968,21 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "shared_entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=8f8f6a#8f8f6af0f9fa1229d43e0dcbc54c62cc41ccaa3b"
dependencies = [
"anyhow",
"opener",
"reqwest",
"serde",
"serde_json",
"serde_repr",
"thiserror",
"url",
]
[[package]]
name = "shlex"
version = "1.1.0"
@ -4966,6 +5115,99 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "sqlformat"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85"
dependencies = [
"itertools 0.11.0",
"nom 7.1.3",
"unicode_categories",
]
[[package]]
name = "sqlx"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e58421b6bc416714d5115a2ca953718f6c621a51b68e4f4922aea5a4391a721"
dependencies = [
"sqlx-core",
"sqlx-macros",
]
[[package]]
name = "sqlx-core"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd4cef4251aabbae751a3710927945901ee1d97ee96d757f6880ebb9a79bfd53"
dependencies = [
"ahash 0.8.3",
"atoi",
"byteorder",
"bytes",
"crc",
"crossbeam-queue",
"dotenvy",
"either",
"event-listener",
"futures-channel",
"futures-core",
"futures-intrusive",
"futures-io",
"futures-util",
"hashlink",
"hex",
"indexmap 2.0.0",
"log",
"memchr",
"once_cell",
"paste",
"percent-encoding",
"serde",
"sha2",
"smallvec",
"sqlformat",
"thiserror",
"tracing",
"url",
]
[[package]]
name = "sqlx-macros"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "208e3165167afd7f3881b16c1ef3f2af69fa75980897aac8874a0696516d12c2"
dependencies = [
"proc-macro2",
"quote",
"sqlx-core",
"sqlx-macros-core",
"syn 1.0.109",
]
[[package]]
name = "sqlx-macros-core"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a4a8336d278c62231d87f24e8a7a74898156e34c1c18942857be2acb29c7dfc"
dependencies = [
"dotenvy",
"either",
"heck 0.4.1",
"hex",
"once_cell",
"proc-macro2",
"quote",
"serde",
"serde_json",
"sha2",
"sqlx-core",
"syn 1.0.109",
"tempfile",
"url",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@ -4987,6 +5229,20 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "storage-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=8f8f6a#8f8f6af0f9fa1229d43e0dcbc54c62cc41ccaa3b"
dependencies = [
"chrono",
"collab-define",
"serde",
"serde_json",
"sqlx",
"uuid",
"validator",
]
[[package]]
name = "string_cache"
version = "0.8.7"
@ -5280,7 +5536,7 @@ dependencies = [
"sha2",
"tauri-utils",
"thiserror",
"time 0.3.22",
"time",
"uuid",
"walkdir",
]
@ -5445,18 +5701,18 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
[[package]]
name = "thiserror"
version = "1.0.40"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.40"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
dependencies = [
"proc-macro2",
"quote",
@ -5483,17 +5739,6 @@ dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "time"
version = "0.3.22"
@ -5978,6 +6223,12 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "unicode_categories"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "universal-hash"
version = "0.5.1"
@ -5996,9 +6247,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
dependencies = [
"form_urlencoded",
"idna",
@ -6014,11 +6265,12 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "uuid"
version = "1.3.4"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81"
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
dependencies = [
"getrandom 0.2.10",
"serde",
"sha1_smol",
]
@ -6035,6 +6287,33 @@ dependencies = [
"serde_derive",
"serde_json",
"url",
"validator_derive",
]
[[package]]
name = "validator_derive"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc44ca3088bb3ba384d9aecf40c6a23a676ce23e09bdaca2073d99c207f864af"
dependencies = [
"if_chain",
"lazy_static",
"proc-macro-error",
"proc-macro2",
"quote",
"regex",
"syn 1.0.109",
"validator_types",
]
[[package]]
name = "validator_types"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "111abfe30072511849c5910134e8baf8dc05de4c0e5903d681cbd5c9c4d611e3"
dependencies = [
"proc-macro2",
"syn 1.0.109",
]
[[package]]
@ -6112,12 +6391,6 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"

View File

@ -23,7 +23,6 @@ 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
@ -34,24 +33,33 @@ default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"]
[patch.crates-io]
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "8f8f6a" }
# ⚠️⚠️⚠️
# Please using the following command to update the revision id
# Current directory: frontend
# Run the script:
# scripts/tool/update_collab_rev.sh new_rev_id
# ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-sync-protocol = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
#collab = { path = "../../../../AppFlowy-Collab/collab" }
#collab-folder = { path = "../../../../AppFlowy-Collab/collab-folder" }
#collab-document = { path = "../../../../AppFlowy-Collab/collab-document" }
#collab-database = { path = "../../../../AppFlowy-Collab/collab-database" }
#appflowy-integrate = { path = "../../../../AppFlowy-Collab/appflowy-integrate" }
#collab-plugins = { path = "../../../../AppFlowy-Collab/collab-plugins" }
#collab-persistence = { path = "../../../../AppFlowy-Collab/collab-persistence" }
#collab-user = { path = "../../../../AppFlowy-Collab/collab-user" }
#collab-define = { path = "../../../../AppFlowy-Collab/collab-define" }
#collab-sync-protocol = { path = "../../../../AppFlowy-Collab/collab-sync-protocol" }

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,6 @@
members = [
"lib-dispatch",
"lib-log",
"flowy-net",
"flowy-core",
"dart-ffi",
"flowy-user",
@ -23,8 +22,33 @@ members = [
"flowy-config",
"flowy-encrypt",
"flowy-storage",
"collab-integrate",
]
[workspace.dependencies]
lib-dispatch = { workspace = true, path = "lib-dispatch" }
lib-log = { workspace = true, path = "lib-log" }
flowy-core = { workspace = true, path = "flowy-core" }
dart-ffi = { workspace = true, path = "dart-ffi" }
flowy-user = { workspace = true, path = "flowy-user" }
flowy-user-deps = { workspace = true, path = "flowy-user-deps" }
flowy-sqlite = { workspace = true, path = "flowy-sqlite" }
flowy-folder2 = { workspace = true, path = "flowy-folder2" }
flowy-folder-deps = { workspace = true, path = "flowy-folder-deps" }
flowy-notification = { workspace = true, path = "flowy-notification" }
flowy-document2 = { workspace = true, path = "flowy-document2" }
flowy-document-deps = { workspace = true, path = "flowy-document-deps" }
flowy-error = { workspace = true, path = "flowy-error" }
flowy-database2 = { workspace = true, path = "flowy-database2" }
flowy-database-deps = { workspace = true, path = "flowy-database-deps" }
flowy-task = { workspace = true, path = "flowy-task" }
flowy-server = { workspace = true, path = "flowy-server" }
flowy-server-config = { workspace = true, path = "flowy-server-config" }
flowy-config = { workspace = true, path = "flowy-config" }
flowy-encrypt = { workspace = true, path = "flowy-encrypt" }
flowy-storage = { workspace = true, path = "flowy-storage" }
collab-integrate = { workspace = true, path = "collab-integrate" }
[profile.dev]
opt-level = 0
lto = false
@ -49,20 +73,30 @@ lto = false
incremental = false
[patch.crates-io]
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" }
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "8f8f6a" }
# ⚠️⚠️⚠️
# Please using the following command to update the revision id
# Current directory: frontend
# Run the script:
# scripts/tool/update_collab_rev.sh new_rev_id
# ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-sync-protocol = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20eff3" }
#collab = { path = "../AppFlowy-Collab/collab" }
#collab-folder = { path = "../AppFlowy-Collab/collab-folder" }
#collab-database= { path = "../AppFlowy-Collab/collab-database" }
#collab-document = { path = "../AppFlowy-Collab/collab-document" }
#collab-plugins = { path = "../AppFlowy-Collab/collab-plugins" }
#appflowy-integrate = { path = "../AppFlowy-Collab/appflowy-integrate" }
#collab-persistence = { path = "../AppFlowy-Collab/collab-persistence" }
#collab-user = { path = "../AppFlowy-Collab/collab-user" }
#collab-define = { path = "../AppFlowy-Collab/collab-define" }
#collab-sync-protocol = { path = "../AppFlowy-Collab/collab-sync-protocol" }

View File

@ -0,0 +1,28 @@
[package]
name = "collab-integrate"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
collab = { version = "0.1.0" }
collab-persistence = { version = "0.1.0", features = ["rocksdb_persistence"] }
collab-folder = { version = "0.1.0" }
collab-database = { version = "0.1.0" }
collab-plugins = { version = "0.1.0" }
collab-document = { version = "0.1.0" }
collab-define = { version = "0.1.0" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0"
tracing = "0.1"
parking_lot = "0.12.1"
futures = "0.3"
async-trait = "0.1.73"
[features]
default = []
supabase_integrate = ["collab-plugins/postgres_storage_plugin", "collab-plugins/rocksdb_plugin"]
appflowy_cloud_integrate = ["collab-plugins/sync_plugin", "collab-plugins/rocksdb_plugin"]
snapshot_plugin = ["collab-plugins/snapshot_plugin"]

View File

@ -0,0 +1,261 @@
use std::fmt::Debug;
use std::sync::{Arc, Weak};
use anyhow::Error;
use async_trait::async_trait;
use collab::core::collab::{CollabRawData, MutexCollab};
use collab::preclude::{CollabBuilder, CollabPlugin};
use collab_define::{CollabObject, CollabType};
use collab_persistence::kv::rocks_kv::RocksCollabDB;
use collab_plugins::cloud_storage::network_state::{CollabNetworkReachability, CollabNetworkState};
use collab_plugins::local_storage::rocksdb::RocksdbDiskPlugin;
use collab_plugins::local_storage::CollabPersistenceConfig;
use collab_plugins::snapshot::{CollabSnapshotPlugin, SnapshotPersistence};
use futures::executor::block_on;
use parking_lot::{Mutex, RwLock};
#[derive(Clone, Debug)]
pub enum CollabSource {
Local,
AFCloud,
Supabase,
}
pub enum CollabPluginContext {
Local,
AppFlowyCloud {
uid: i64,
collab_object: CollabObject,
local_collab: Weak<MutexCollab>,
},
Supabase {
uid: i64,
collab_object: CollabObject,
local_collab: Weak<MutexCollab>,
local_collab_db: Weak<RocksCollabDB>,
},
}
#[async_trait]
pub trait CollabStorageProvider: Send + Sync + 'static {
fn storage_source(&self) -> CollabSource;
async fn get_plugins(
&self,
context: CollabPluginContext,
) -> Vec<Arc<dyn collab::core::collab_plugin::CollabPlugin>>;
fn is_sync_enabled(&self) -> bool;
}
#[async_trait]
impl<T> CollabStorageProvider for Arc<T>
where
T: CollabStorageProvider,
{
fn storage_source(&self) -> CollabSource {
(**self).storage_source()
}
async fn get_plugins(&self, context: CollabPluginContext) -> Vec<Arc<dyn CollabPlugin>> {
(**self).get_plugins(context).await
}
fn is_sync_enabled(&self) -> bool {
(**self).is_sync_enabled()
}
}
pub struct AppFlowyCollabBuilder {
network_reachability: CollabNetworkReachability,
workspace_id: RwLock<Option<String>>,
cloud_storage: RwLock<Arc<dyn CollabStorageProvider>>,
snapshot_persistence: Mutex<Option<Arc<dyn SnapshotPersistence>>>,
device_id: Mutex<String>,
}
impl AppFlowyCollabBuilder {
pub fn new<T: CollabStorageProvider>(storage_provider: T) -> Self {
Self {
network_reachability: CollabNetworkReachability::new(),
workspace_id: Default::default(),
cloud_storage: RwLock::new(Arc::new(storage_provider)),
snapshot_persistence: Default::default(),
device_id: Default::default(),
}
}
pub fn set_snapshot_persistence(&self, snapshot_persistence: Arc<dyn SnapshotPersistence>) {
*self.snapshot_persistence.lock() = Some(snapshot_persistence);
}
pub fn initialize(&self, workspace_id: String) {
*self.workspace_id.write() = Some(workspace_id);
}
pub fn set_sync_device(&self, device_id: String) {
*self.device_id.lock() = device_id;
}
pub fn update_network(&self, reachable: bool) {
if reachable {
self
.network_reachability
.set_state(CollabNetworkState::Connected)
} else {
self
.network_reachability
.set_state(CollabNetworkState::Disconnected)
}
}
fn collab_object(
&self,
uid: i64,
object_id: &str,
collab_type: CollabType,
) -> Result<CollabObject, Error> {
let workspace_id = self.workspace_id.read().clone().ok_or_else(|| {
anyhow::anyhow!("When using supabase plugin, the workspace_id should not be empty")
})?;
Ok(CollabObject::new(
uid,
object_id.to_string(),
collab_type,
workspace_id,
self.device_id.lock().clone(),
))
}
/// Creates a new collaboration builder with the default configuration.
///
/// This function will initiate the creation of a [MutexCollab] object if it does not already exist.
/// To check for the existence of the object prior to creation, you should utilize a transaction
/// returned by the [read_txn] method of the [RocksCollabDB]. Then, invoke the [is_exist] method
/// to confirm the object's presence.
///
/// # Parameters
/// - `uid`: The user ID associated with the collaboration.
/// - `object_id`: A string reference representing the ID of the object.
/// - `object_type`: The type of the collaboration, defined by the [CollabType] enum.
/// - `raw_data`: The raw data of the collaboration object, defined by the [CollabRawData] type.
/// - `collab_db`: A weak reference to the [RocksCollabDB].
///
pub fn build(
&self,
uid: i64,
object_id: &str,
object_type: CollabType,
raw_data: CollabRawData,
collab_db: Weak<RocksCollabDB>,
) -> Result<Arc<MutexCollab>, Error> {
self.build_with_config(
uid,
object_id,
object_type,
collab_db,
raw_data,
&CollabPersistenceConfig::default(),
)
}
/// Creates a new collaboration builder with the custom configuration.
///
/// This function will initiate the creation of a [MutexCollab] object if it does not already exist.
/// To check for the existence of the object prior to creation, you should utilize a transaction
/// returned by the [read_txn] method of the [RocksCollabDB]. Then, invoke the [is_exist] method
/// to confirm the object's presence.
///
/// # Parameters
/// - `uid`: The user ID associated with the collaboration.
/// - `object_id`: A string reference representing the ID of the object.
/// - `object_type`: The type of the collaboration, defined by the [CollabType] enum.
/// - `raw_data`: The raw data of the collaboration object, defined by the [CollabRawData] type.
/// - `collab_db`: A weak reference to the [RocksCollabDB].
///
pub fn build_with_config(
&self,
uid: i64,
object_id: &str,
object_type: CollabType,
collab_db: Weak<RocksCollabDB>,
collab_raw_data: CollabRawData,
config: &CollabPersistenceConfig,
) -> Result<Arc<MutexCollab>, Error> {
let collab = Arc::new(
CollabBuilder::new(uid, object_id)
.with_raw_data(collab_raw_data)
.with_plugin(RocksdbDiskPlugin::new_with_config(
uid,
collab_db.clone(),
config.clone(),
))
.with_device_id(self.device_id.lock().clone())
.build()?,
);
{
let cloud_storage = self.cloud_storage.read();
let cloud_storage_type = cloud_storage.storage_source();
let collab_object = self.collab_object(uid, object_id, object_type)?;
match cloud_storage_type {
CollabSource::AFCloud => {
#[cfg(feature = "appflowy_cloud_integrate")]
{
//
}
},
CollabSource::Supabase => {
#[cfg(feature = "supabase_integrate")]
{
let local_collab = Arc::downgrade(&collab);
let local_collab_db = collab_db.clone();
let plugins = block_on(cloud_storage.get_plugins(CollabPluginContext::Supabase {
uid,
collab_object: collab_object.clone(),
local_collab,
local_collab_db,
}));
for plugin in plugins {
collab.lock().add_plugin(plugin);
}
}
},
CollabSource::Local => {},
}
if let Some(snapshot_persistence) = self.snapshot_persistence.lock().as_ref() {
if config.enable_snapshot {
let snapshot_plugin = CollabSnapshotPlugin::new(
uid,
collab_object,
snapshot_persistence.clone(),
collab_db,
config.snapshot_per_update,
);
// tracing::trace!("add snapshot plugin: {}", object_id);
collab.lock().add_plugin(Arc::new(snapshot_plugin));
}
}
}
block_on(collab.async_initialize());
Ok(collab)
}
}
pub struct DefaultCollabStorageProvider();
#[async_trait]
impl CollabStorageProvider for DefaultCollabStorageProvider {
fn storage_source(&self) -> CollabSource {
CollabSource::Local
}
async fn get_plugins(&self, _context: CollabPluginContext) -> Vec<Arc<dyn CollabPlugin>> {
vec![]
}
fn is_sync_enabled(&self) -> bool {
false
}
}

View File

@ -0,0 +1,70 @@
use std::str::FromStr;
use serde::{Deserialize, Serialize};
pub enum CollabDBPluginProvider {
AWS,
Supabase,
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct CollabPluginConfig {
/// Only one of the following two fields should be set.
aws_config: Option<AWSDynamoDBConfig>,
}
impl CollabPluginConfig {
pub fn from_env() -> Self {
let aws_config = AWSDynamoDBConfig::from_env();
Self { aws_config }
}
pub fn aws_config(&self) -> Option<&AWSDynamoDBConfig> {
self.aws_config.as_ref()
}
}
impl CollabPluginConfig {}
impl FromStr for CollabPluginConfig {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s)
}
}
pub const AWS_ACCESS_KEY_ID: &str = "AWS_ACCESS_KEY_ID";
pub const AWS_SECRET_ACCESS_KEY: &str = "AWS_SECRET_ACCESS_KEY";
pub const AWS_REGION: &str = "AWS_REGION";
// To enable this test, you should set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in your environment variables.
// or create the ~/.aws/credentials file following the instructions in https://docs.aws.amazon.com/sdk-for-rust/latest/dg/credentials.html
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
pub struct AWSDynamoDBConfig {
pub access_key_id: String,
pub secret_access_key: String,
// Region list: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html
pub region: String,
pub enable: bool,
}
impl AWSDynamoDBConfig {
pub fn from_env() -> Option<Self> {
let access_key_id = std::env::var(AWS_ACCESS_KEY_ID).ok()?;
let secret_access_key = std::env::var(AWS_SECRET_ACCESS_KEY).ok()?;
let region = std::env::var(AWS_REGION).unwrap_or_else(|_| "us-east-1".to_string());
Some(Self {
access_key_id,
secret_access_key,
region,
enable: true,
})
}
pub fn write_env(&self) {
std::env::set_var(AWS_ACCESS_KEY_ID, &self.access_key_id);
std::env::set_var(AWS_SECRET_ACCESS_KEY, &self.secret_access_key);
std::env::set_var(AWS_REGION, &self.region);
}
}

View File

@ -0,0 +1,26 @@
pub use collab::core::collab::MutexCollab;
pub use collab::preclude::Snapshot;
pub use collab_persistence::doc::YrsDocAction;
pub use collab_persistence::error::PersistenceError;
#[cfg(any(
feature = "appflowy_cloud_integrate",
feature = "supabase_integrate",
feature = "rocksdb_plugin"
))]
pub use collab_persistence::kv::rocks_kv::RocksCollabDB;
pub use collab_persistence::snapshot::CollabSnapshot;
#[cfg(feature = "supabase_integrate")]
pub use collab_plugins::cloud_storage::*;
#[cfg(any(
feature = "appflowy_cloud_integrate",
feature = "supabase_integrate",
feature = "rocksdb_plugin"
))]
pub use collab_plugins::local_storage::CollabPersistenceConfig;
#[cfg(feature = "snapshot_plugin")]
pub use collab_plugins::snapshot::{
calculate_snapshot_diff, try_encode_snapshot, SnapshotPersistence,
};
pub mod collab_builder;
pub mod config;

View File

@ -24,15 +24,15 @@ crossbeam-utils = "0.8.15"
lazy_static = "1.4.0"
parking_lot = "0.12.1"
tracing = { version = "0.1", features = ["log"] }
appflowy-integrate = {version = "0.1.0" }
lib-dispatch = { path = "../lib-dispatch" }
flowy-core = { path = "../flowy-core" }
flowy-notification = { path = "../flowy-notification" }
flowy-net = { path = "../flowy-net" }
# workspace
lib-dispatch = { workspace = true }
flowy-core = { workspace = true }
flowy-notification = { workspace = true }
flowy-server = { workspace = true }
flowy-server-config = { workspace = true }
collab-integrate = { workspace = true }
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
flowy-server = { path = "../flowy-server" }
flowy-server-config = { path = "../flowy-server-config" }
[features]
default = ["dart", "rev-sqlite"]

View File

@ -6,12 +6,14 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
flowy-sqlite = { path = "../flowy-sqlite" }
# workspace
flowy-sqlite = { workspace = true }
lib-dispatch = { workspace = true }
flowy-error = { workspace = true }
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
lib-dispatch = { path = "../lib-dispatch" }
protobuf = {version = "2.28.0"}
bytes = { version = "1.4" }
flowy-error = { path = "../flowy-error" }
strum_macros = "0.21"
[build-dependencies]

View File

@ -6,28 +6,29 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lib-dispatch = { path = "../lib-dispatch" }
lib-log = { path = "../lib-log" }
flowy-user = { path = "../flowy-user" }
flowy-user-deps = { path = "../flowy-user-deps" }
flowy-net = { path = "../flowy-net" }
flowy-folder2 = { path = "../flowy-folder2" }
flowy-folder-deps = { path = "../flowy-folder-deps" }
flowy-database2 = { path = "../flowy-database2" }
flowy-database-deps = { path = "../flowy-database-deps" }
flowy-sqlite = { path = "../flowy-sqlite" }
flowy-document2 = { path = "../flowy-document2" }
flowy-document-deps = { path = "../flowy-document-deps" }
flowy-error = { path = "../flowy-error" }
flowy-task = { path = "../flowy-task" }
flowy-server = { path = "../flowy-server" }
flowy-server-config = { path = "../flowy-server-config" }
flowy-config = { path = "../flowy-config" }
appflowy-integrate = { version = "0.1.0", features = ["postgres_storage_plugin", "snapshot_plugin"] }
lib-dispatch = { workspace = true }
lib-log = { workspace = true }
flowy-user = { workspace = true }
flowy-user-deps = { workspace = true }
flowy-folder2 = { workspace = true }
flowy-folder-deps = { workspace = true }
flowy-database2 = { workspace = true }
flowy-database-deps = { workspace = true }
flowy-sqlite = { workspace = true }
flowy-document2 = { workspace = true }
flowy-document-deps = { workspace = true }
flowy-error = { workspace = true }
flowy-task = { workspace = true }
flowy-server = { workspace = true }
flowy-server-config = { workspace = true }
flowy-config = { workspace = true }
collab-integrate = { workspace = true, features = ["supabase_integrate", "appflowy_cloud_integrate", "snapshot_plugin"] }
collab-define = { version = "0.1.0" }
collab-plugins = { version = "0.1.0", features = ["sync_plugin"] }
collab = { version = "0.1.0" }
diesel = { version = "1.4.8", features = ["sqlite"] }
uuid = { version = "1.3.3", features = ["v4"] }
flowy-storage = { path = "../flowy-storage" }
flowy-storage = { workspace = true }
tracing = { version = "0.1", features = ["log"] }
futures-core = { version = "0.3", default-features = false }
@ -35,6 +36,7 @@ bytes = "1.4"
tokio = { version = "1.26", features = ["full"] }
console-subscriber = { version = "0.1.8", optional = true }
parking_lot = "0.12.1"
anyhow = "1.0.75"
lib-infra = { path = "../../../shared-lib/lib-infra" }
serde = "1.0"
@ -49,7 +51,6 @@ native_sync = []
use_bunyan = ["lib-log/use_bunyan"]
dart = [
"flowy-user/dart",
"flowy-net/dart",
"flowy-folder2/dart",
"flowy-database2/dart",
"flowy-document2/dart",
@ -57,7 +58,6 @@ dart = [
]
ts = [
"flowy-user/ts",
"flowy-net/ts",
"flowy-folder2/ts",
"flowy-database2/ts",
"flowy-document2/ts",
@ -67,3 +67,4 @@ rev-sqlite = [
"flowy-user/rev-sqlite",
]
openssl_vendored = ["flowy-sqlite/openssl_vendored"]

View File

@ -1,10 +1,10 @@
use std::sync::Weak;
use appflowy_integrate::{
calculate_snapshot_diff, CollabSnapshot, PersistenceError, SnapshotPersistence,
};
use diesel::SqliteConnection;
use collab_integrate::{
calculate_snapshot_diff, CollabSnapshot, PersistenceError, SnapshotPersistence,
};
use flowy_error::FlowyError;
use flowy_sqlite::{
insert_or_ignore_into,

View File

@ -1,9 +1,9 @@
use std::sync::{Arc, Weak};
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
use appflowy_integrate::RocksCollabDB;
use tokio::sync::RwLock;
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use collab_integrate::RocksCollabDB;
use flowy_database2::{DatabaseManager, DatabaseUser};
use flowy_database_deps::cloud::DatabaseCloudService;
use flowy_error::FlowyError;

View File

@ -1,8 +1,7 @@
use std::sync::{Arc, Weak};
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
use appflowy_integrate::RocksCollabDB;
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use collab_integrate::RocksCollabDB;
use flowy_database2::DatabaseManager;
use flowy_document2::manager::{DocumentManager, DocumentUser};
use flowy_document_deps::cloud::DocumentCloudService;

View File

@ -2,11 +2,11 @@ use std::collections::HashMap;
use std::convert::TryFrom;
use std::sync::{Arc, Weak};
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
use appflowy_integrate::RocksCollabDB;
use bytes::Bytes;
use tokio::sync::RwLock;
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use collab_integrate::RocksCollabDB;
use flowy_database2::entities::DatabaseLayoutPB;
use flowy_database2::services::share::csv::CSVFormat;
use flowy_database2::template::{make_default_board, make_default_calendar, make_default_grid};

View File

@ -1 +1,2 @@
pub(crate) mod server;
mod trait_impls;

View File

@ -2,18 +2,11 @@ use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::sync::{Arc, Weak};
use appflowy_integrate::collab_builder::{CollabStorageProvider, CollabStorageType};
use appflowy_integrate::{RemoteCollabStorage, YrsDocAction};
use bytes::Bytes;
use collab_define::{CollabObject, CollabType};
use parking_lot::RwLock;
use serde_repr::*;
use flowy_database_deps::cloud::*;
use flowy_document2::deps::DocumentData;
use flowy_document_deps::cloud::{DocumentCloudService, DocumentSnapshot};
use collab_integrate::YrsDocAction;
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_folder_deps::cloud::*;
use flowy_server::af_cloud::configuration::appflowy_cloud_server_configuration;
use flowy_server::af_cloud::AFCloudServer;
use flowy_server::local_server::{LocalServer, LocalServerDB};
@ -21,22 +14,19 @@ use flowy_server::supabase::SupabaseServer;
use flowy_server::{AppFlowyEncryption, AppFlowyServer, EncryptionImpl};
use flowy_server_config::supabase_config::SupabaseConfiguration;
use flowy_sqlite::kv::StorePreferences;
use flowy_storage::{FileStorageService, StorageObject};
use flowy_user::event_map::UserCloudServiceProvider;
use flowy_user::services::database::{
get_user_profile, get_user_workspace, open_collab_db, open_user_db,
};
use flowy_user_deps::cloud::UserCloudService;
use flowy_user_deps::entities::*;
use lib_infra::future::FutureResult;
use crate::AppFlowyCoreConfig;
const SERVER_PROVIDER_TYPE_KEY: &str = "server_provider_type";
pub(crate) const SERVER_PROVIDER_TYPE_KEY: &str = "server_provider_type";
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum ServerProviderType {
pub enum ServerType {
/// Local server provider.
/// Offline mode, no user authentication and the data is stored locally.
Local = 0,
@ -45,48 +35,48 @@ pub enum ServerProviderType {
/// progress.
AppFlowyCloud = 1,
/// Supabase server provider.
/// It uses supabase's postgresql database to store data and user authentication.
/// It uses supabase postgresql database to store data and user authentication.
Supabase = 2,
}
impl Display for ServerProviderType {
impl Display for ServerType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ServerProviderType::Local => write!(f, "Local"),
ServerProviderType::AppFlowyCloud => write!(f, "AppFlowyCloud"),
ServerProviderType::Supabase => write!(f, "Supabase"),
ServerType::Local => write!(f, "Local"),
ServerType::AppFlowyCloud => write!(f, "AppFlowyCloud"),
ServerType::Supabase => write!(f, "Supabase"),
}
}
}
/// The [AppFlowyServerProvider] provides list of [AppFlowyServer] base on the [AuthType]. Using
/// the auth type, the [AppFlowyServerProvider] will create a new [AppFlowyServer] if it doesn't
/// The [ServerProvider] provides list of [AppFlowyServer] base on the [AuthType]. Using
/// the auth type, the [ServerProvider] will create a new [AppFlowyServer] if it doesn't
/// exist.
/// Each server implements the [AppFlowyServer] trait, which provides the [UserCloudService], etc.
pub struct AppFlowyServerProvider {
pub struct ServerProvider {
config: AppFlowyCoreConfig,
provider_type: RwLock<ServerProviderType>,
providers: RwLock<HashMap<ServerProviderType, Arc<dyn AppFlowyServer>>>,
encryption: RwLock<Arc<dyn AppFlowyEncryption>>,
store_preferences: Weak<StorePreferences>,
cache_user_service: RwLock<HashMap<ServerProviderType, Arc<dyn UserCloudService>>>,
server_type: RwLock<ServerType>,
providers: RwLock<HashMap<ServerType, Arc<dyn AppFlowyServer>>>,
pub(crate) encryption: RwLock<Arc<dyn AppFlowyEncryption>>,
pub(crate) store_preferences: Weak<StorePreferences>,
pub(crate) cache_user_service: RwLock<HashMap<ServerType, Arc<dyn UserCloudService>>>,
device_id: Arc<RwLock<String>>,
enable_sync: RwLock<bool>,
uid: Arc<RwLock<Option<i64>>>,
pub(crate) device_id: Arc<RwLock<String>>,
pub(crate) enable_sync: RwLock<bool>,
pub(crate) uid: Arc<RwLock<Option<i64>>>,
}
impl AppFlowyServerProvider {
impl ServerProvider {
pub fn new(
config: AppFlowyCoreConfig,
provider_type: ServerProviderType,
provider_type: ServerType,
store_preferences: Weak<StorePreferences>,
) -> Self {
let encryption = EncryptionImpl::new(None);
Self {
config,
provider_type: RwLock::new(provider_type),
device_id: Default::default(),
server_type: RwLock::new(provider_type),
device_id: Arc::new(RwLock::new(uuid::Uuid::new_v4().to_string())),
providers: RwLock::new(HashMap::new()),
enable_sync: RwLock::new(true),
encryption: RwLock::new(Arc::new(encryption)),
@ -96,42 +86,51 @@ impl AppFlowyServerProvider {
}
}
pub fn provider_type(&self) -> ServerProviderType {
self.provider_type.read().clone()
pub fn get_server_type(&self) -> ServerType {
self.server_type.read().clone()
}
pub fn set_server_type(&self, server_type: ServerType) {
*self.server_type.write() = server_type;
}
/// Returns a [AppFlowyServer] trait implementation base on the provider_type.
fn get_provider(
pub(crate) fn get_server(
&self,
provider_type: &ServerProviderType,
server_type: &ServerType,
) -> FlowyResult<Arc<dyn AppFlowyServer>> {
if let Some(provider) = self.providers.read().get(provider_type) {
if let Some(provider) = self.providers.read().get(server_type) {
return Ok(provider.clone());
}
let server = match provider_type {
ServerProviderType::Local => {
let server = match server_type {
ServerType::Local => {
let local_db = Arc::new(LocalServerDBImpl {
storage_path: self.config.storage_path.clone(),
});
let server = Arc::new(LocalServer::new(local_db));
Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
},
ServerProviderType::AppFlowyCloud => {
ServerType::AppFlowyCloud => {
let config = appflowy_cloud_server_configuration().map_err(|e| {
FlowyError::new(
ErrorCode::InvalidAuthConfig,
format!(
"Missing self host config: {:?}. Error: {:?}",
provider_type, e
server_type, e
),
)
})?;
let server = Arc::new(AFCloudServer::new(config));
tracing::trace!("🔑AppFlowy cloud config: {:?}", config);
let server = Arc::new(AFCloudServer::new(
config,
*self.enable_sync.read(),
self.device_id.clone(),
));
Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
},
ServerProviderType::Supabase => {
ServerType::Supabase => {
let config = match SupabaseConfiguration::from_env() {
Ok(config) => config,
Err(e) => {
@ -155,287 +154,30 @@ impl AppFlowyServerProvider {
self
.providers
.write()
.insert(provider_type.clone(), server.clone());
.insert(server_type.clone(), server.clone());
Ok(server)
}
}
impl FileStorageService for AppFlowyServerProvider {
fn create_object(&self, object: StorageObject) -> FutureResult<String, FlowyError> {
let server = self.get_provider(&self.provider_type.read());
FutureResult::new(async move {
let storage = server?.file_storage().ok_or(FlowyError::internal())?;
storage.create_object(object).await
})
}
fn delete_object_by_url(&self, object_url: String) -> FutureResult<(), FlowyError> {
let server = self.get_provider(&self.provider_type.read());
FutureResult::new(async move {
let storage = server?.file_storage().ok_or(FlowyError::internal())?;
storage.delete_object_by_url(object_url).await
})
}
fn get_object_by_url(&self, object_url: String) -> FutureResult<Bytes, FlowyError> {
let server = self.get_provider(&self.provider_type.read());
FutureResult::new(async move {
let storage = server?.file_storage().ok_or(FlowyError::internal())?;
storage.get_object_by_url(object_url).await
})
}
}
impl UserCloudServiceProvider for AppFlowyServerProvider {
fn set_enable_sync(&self, uid: i64, enable_sync: bool) {
match self.get_provider(&self.provider_type.read()) {
Ok(server) => {
server.set_enable_sync(uid, enable_sync);
*self.enable_sync.write() = enable_sync;
*self.uid.write() = Some(uid);
},
Err(e) => tracing::error!("🔴Failed to enable sync: {:?}", e),
}
}
fn set_encrypt_secret(&self, secret: String) {
tracing::info!("🔑Set encrypt secret");
self.encryption.write().set_secret(secret);
}
/// When user login, the provider type is set by the [AuthType] and save to disk for next use.
///
/// Each [AuthType] has a corresponding [ServerProviderType]. The [ServerProviderType] is used
/// to create a new [AppFlowyServer] if it doesn't exist. Once the [ServerProviderType] is set,
/// it will be used when user open the app again.
///
fn set_auth_type(&self, auth_type: AuthType) {
let provider_type: ServerProviderType = auth_type.into();
*self.provider_type.write() = provider_type.clone();
match self.store_preferences.upgrade() {
None => tracing::error!("🔴Failed to update server provider type: store preferences is drop"),
Some(store_preferences) => {
match store_preferences.set_object(SERVER_PROVIDER_TYPE_KEY, provider_type.clone()) {
Ok(_) => tracing::trace!("Update server provider type to: {:?}", provider_type),
Err(e) => {
tracing::error!("🔴Failed to update server provider type: {:?}", e);
},
}
},
}
}
fn set_device_id(&self, device_id: &str) {
*self.device_id.write() = device_id.to_string();
}
/// Returns the [UserCloudService] base on the current [ServerProviderType].
/// Creates a new [AppFlowyServer] if it doesn't exist.
fn get_user_service(&self) -> Result<Arc<dyn UserCloudService>, FlowyError> {
if let Some(user_service) = self
.cache_user_service
.read()
.get(&self.provider_type.read())
{
return Ok(user_service.clone());
}
let provider_type = self.provider_type.read().clone();
let user_service = self.get_provider(&provider_type)?.user_service();
self
.cache_user_service
.write()
.insert(provider_type, user_service.clone());
Ok(user_service)
}
fn service_name(&self) -> String {
self.provider_type.read().to_string()
}
}
impl FolderCloudService for AppFlowyServerProvider {
fn create_workspace(&self, uid: i64, name: &str) -> FutureResult<Workspace, Error> {
let server = self.get_provider(&self.provider_type.read());
let name = name.to_string();
FutureResult::new(async move { server?.folder_service().create_workspace(uid, &name).await })
}
fn get_folder_data(&self, workspace_id: &str) -> FutureResult<Option<FolderData>, Error> {
let server = self.get_provider(&self.provider_type.read());
let workspace_id = workspace_id.to_string();
FutureResult::new(async move {
server?
.folder_service()
.get_folder_data(&workspace_id)
.await
})
}
fn get_folder_snapshots(
&self,
workspace_id: &str,
limit: usize,
) -> FutureResult<Vec<FolderSnapshot>, Error> {
let workspace_id = workspace_id.to_string();
let server = self.get_provider(&self.provider_type.read());
FutureResult::new(async move {
server?
.folder_service()
.get_folder_snapshots(&workspace_id, limit)
.await
})
}
fn get_folder_updates(&self, workspace_id: &str, uid: i64) -> FutureResult<Vec<Vec<u8>>, Error> {
let workspace_id = workspace_id.to_string();
let server = self.get_provider(&self.provider_type.read());
FutureResult::new(async move {
server?
.folder_service()
.get_folder_updates(&workspace_id, uid)
.await
})
}
fn service_name(&self) -> String {
self
.get_provider(&self.provider_type.read())
.map(|provider| provider.folder_service().service_name())
.unwrap_or_default()
}
}
impl DatabaseCloudService for AppFlowyServerProvider {
fn get_collab_update(
&self,
object_id: &str,
object_ty: CollabType,
) -> FutureResult<CollabObjectUpdate, Error> {
let server = self.get_provider(&self.provider_type.read());
let database_id = object_id.to_string();
FutureResult::new(async move {
server?
.database_service()
.get_collab_update(&database_id, object_ty)
.await
})
}
fn batch_get_collab_updates(
&self,
object_ids: Vec<String>,
object_ty: CollabType,
) -> FutureResult<CollabObjectUpdateByOid, Error> {
let server = self.get_provider(&self.provider_type.read());
FutureResult::new(async move {
server?
.database_service()
.batch_get_collab_updates(object_ids, object_ty)
.await
})
}
fn get_collab_snapshots(
&self,
object_id: &str,
limit: usize,
) -> FutureResult<Vec<DatabaseSnapshot>, Error> {
let server = self.get_provider(&self.provider_type.read());
let database_id = object_id.to_string();
FutureResult::new(async move {
server?
.database_service()
.get_collab_snapshots(&database_id, limit)
.await
})
}
}
impl DocumentCloudService for AppFlowyServerProvider {
fn get_document_updates(&self, document_id: &str) -> FutureResult<Vec<Vec<u8>>, Error> {
let server = self.get_provider(&self.provider_type.read());
let document_id = document_id.to_string();
FutureResult::new(async move {
server?
.document_service()
.get_document_updates(&document_id)
.await
})
}
fn get_document_snapshots(
&self,
document_id: &str,
limit: usize,
) -> FutureResult<Vec<DocumentSnapshot>, Error> {
let server = self.get_provider(&self.provider_type.read());
let document_id = document_id.to_string();
FutureResult::new(async move {
server?
.document_service()
.get_document_snapshots(&document_id, limit)
.await
})
}
fn get_document_data(&self, document_id: &str) -> FutureResult<Option<DocumentData>, Error> {
let server = self.get_provider(&self.provider_type.read());
let document_id = document_id.to_string();
FutureResult::new(async move {
server?
.document_service()
.get_document_data(&document_id)
.await
})
}
}
impl CollabStorageProvider for AppFlowyServerProvider {
fn storage_type(&self) -> CollabStorageType {
self.provider_type().into()
}
fn get_storage(
&self,
collab_object: &CollabObject,
storage_type: &CollabStorageType,
) -> Option<Arc<dyn RemoteCollabStorage>> {
match storage_type {
CollabStorageType::Local => None,
CollabStorageType::AWS => None,
CollabStorageType::Supabase => self
.get_provider(&ServerProviderType::Supabase)
.ok()
.and_then(|provider| provider.collab_storage(collab_object)),
}
}
fn is_sync_enabled(&self) -> bool {
*self.enable_sync.read()
}
}
impl From<AuthType> for ServerProviderType {
impl From<AuthType> for ServerType {
fn from(auth_provider: AuthType) -> Self {
match auth_provider {
AuthType::Local => ServerProviderType::Local,
AuthType::SelfHosted => ServerProviderType::AppFlowyCloud,
AuthType::Supabase => ServerProviderType::Supabase,
AuthType::Local => ServerType::Local,
AuthType::SelfHosted => ServerType::AppFlowyCloud,
AuthType::Supabase => ServerType::Supabase,
}
}
}
impl From<&AuthType> for ServerProviderType {
impl From<&AuthType> for ServerType {
fn from(auth_provider: &AuthType) -> Self {
Self::from(auth_provider.clone())
}
}
pub fn current_server_provider(store_preferences: &Arc<StorePreferences>) -> ServerProviderType {
match store_preferences.get_object::<ServerProviderType>(SERVER_PROVIDER_TYPE_KEY) {
None => ServerProviderType::Local,
pub fn current_server_provider(store_preferences: &Arc<StorePreferences>) -> ServerType {
match store_preferences.get_object::<ServerType>(SERVER_PROVIDER_TYPE_KEY) {
None => ServerType::Local,
Some(provider_type) => provider_type,
}
}

View File

@ -0,0 +1,321 @@
use std::sync::Arc;
use anyhow::Error;
use bytes::Bytes;
use collab::core::origin::{CollabClient, CollabOrigin};
use collab::preclude::CollabPlugin;
use collab_define::CollabType;
use collab_plugins::sync_plugin::{SyncObject, SyncPlugin};
use collab_integrate::collab_builder::{CollabPluginContext, CollabSource, CollabStorageProvider};
use collab_integrate::postgres::SupabaseDBPlugin;
use flowy_database_deps::cloud::{
CollabObjectUpdate, CollabObjectUpdateByOid, DatabaseCloudService, DatabaseSnapshot,
};
use flowy_document2::deps::DocumentData;
use flowy_document_deps::cloud::{DocumentCloudService, DocumentSnapshot};
use flowy_error::FlowyError;
use flowy_folder_deps::cloud::{FolderCloudService, FolderData, FolderSnapshot, Workspace};
use flowy_storage::{FileStorageService, StorageObject};
use flowy_user::event_map::UserCloudServiceProvider;
use flowy_user_deps::cloud::UserCloudService;
use flowy_user_deps::entities::AuthType;
use lib_infra::async_trait::async_trait;
use lib_infra::future::FutureResult;
use crate::integrate::server::{ServerProvider, ServerType, SERVER_PROVIDER_TYPE_KEY};
impl FileStorageService for ServerProvider {
fn create_object(&self, object: StorageObject) -> FutureResult<String, FlowyError> {
let server = self.get_server(&self.get_server_type());
FutureResult::new(async move {
let storage = server?.file_storage().ok_or(FlowyError::internal())?;
storage.create_object(object).await
})
}
fn delete_object_by_url(&self, object_url: String) -> FutureResult<(), FlowyError> {
let server = self.get_server(&self.get_server_type());
FutureResult::new(async move {
let storage = server?.file_storage().ok_or(FlowyError::internal())?;
storage.delete_object_by_url(object_url).await
})
}
fn get_object_by_url(&self, object_url: String) -> FutureResult<Bytes, FlowyError> {
let server = self.get_server(&self.get_server_type());
FutureResult::new(async move {
let storage = server?.file_storage().ok_or(FlowyError::internal())?;
storage.get_object_by_url(object_url).await
})
}
}
impl UserCloudServiceProvider for ServerProvider {
fn set_enable_sync(&self, uid: i64, enable_sync: bool) {
match self.get_server(&self.get_server_type()) {
Ok(server) => {
server.set_enable_sync(uid, enable_sync);
*self.enable_sync.write() = enable_sync;
*self.uid.write() = Some(uid);
},
Err(e) => tracing::error!("🔴Failed to enable sync: {:?}", e),
}
}
fn set_encrypt_secret(&self, secret: String) {
tracing::info!("🔑Set encrypt secret");
self.encryption.write().set_secret(secret);
}
/// When user login, the provider type is set by the [AuthType] and save to disk for next use.
///
/// Each [AuthType] has a corresponding [ServerType]. The [ServerType] is used
/// to create a new [AppFlowyServer] if it doesn't exist. Once the [ServerType] is set,
/// it will be used when user open the app again.
///
fn set_auth_type(&self, auth_type: AuthType) {
let server_type: ServerType = auth_type.into();
self.set_server_type(server_type.clone());
match self.store_preferences.upgrade() {
None => tracing::error!("🔴Failed to update server provider type: store preferences is drop"),
Some(store_preferences) => {
match store_preferences.set_object(SERVER_PROVIDER_TYPE_KEY, server_type.clone()) {
Ok(_) => tracing::trace!("Update server provider type to: {:?}", server_type),
Err(e) => {
tracing::error!("🔴Failed to update server provider type: {:?}", e);
},
}
},
}
}
fn set_device_id(&self, device_id: &str) {
if device_id.is_empty() {
tracing::error!("🔴Device id is empty");
return;
}
*self.device_id.write() = device_id.to_string();
}
/// Returns the [UserCloudService] base on the current [ServerType].
/// Creates a new [AppFlowyServer] if it doesn't exist.
fn get_user_service(&self) -> Result<Arc<dyn UserCloudService>, FlowyError> {
if let Some(user_service) = self.cache_user_service.read().get(&self.get_server_type()) {
return Ok(user_service.clone());
}
let server_type = self.get_server_type();
let user_service = self.get_server(&server_type)?.user_service();
self
.cache_user_service
.write()
.insert(server_type, user_service.clone());
Ok(user_service)
}
fn service_name(&self) -> String {
self.get_server_type().to_string()
}
}
impl FolderCloudService for ServerProvider {
fn create_workspace(&self, uid: i64, name: &str) -> FutureResult<Workspace, Error> {
let server = self.get_server(&self.get_server_type());
let name = name.to_string();
FutureResult::new(async move { server?.folder_service().create_workspace(uid, &name).await })
}
fn get_folder_data(&self, workspace_id: &str) -> FutureResult<Option<FolderData>, Error> {
let server = self.get_server(&self.get_server_type());
let workspace_id = workspace_id.to_string();
FutureResult::new(async move {
server?
.folder_service()
.get_folder_data(&workspace_id)
.await
})
}
fn get_folder_snapshots(
&self,
workspace_id: &str,
limit: usize,
) -> FutureResult<Vec<FolderSnapshot>, Error> {
let workspace_id = workspace_id.to_string();
let server = self.get_server(&self.get_server_type());
FutureResult::new(async move {
server?
.folder_service()
.get_folder_snapshots(&workspace_id, limit)
.await
})
}
fn get_folder_updates(&self, workspace_id: &str, uid: i64) -> FutureResult<Vec<Vec<u8>>, Error> {
let workspace_id = workspace_id.to_string();
let server = self.get_server(&self.get_server_type());
FutureResult::new(async move {
server?
.folder_service()
.get_folder_updates(&workspace_id, uid)
.await
})
}
fn service_name(&self) -> String {
self
.get_server(&self.get_server_type())
.map(|provider| provider.folder_service().service_name())
.unwrap_or_default()
}
}
impl DatabaseCloudService for ServerProvider {
fn get_collab_update(
&self,
object_id: &str,
object_ty: CollabType,
) -> FutureResult<CollabObjectUpdate, Error> {
let server = self.get_server(&self.get_server_type());
let database_id = object_id.to_string();
FutureResult::new(async move {
server?
.database_service()
.get_collab_update(&database_id, object_ty)
.await
})
}
fn batch_get_collab_updates(
&self,
object_ids: Vec<String>,
object_ty: CollabType,
) -> FutureResult<CollabObjectUpdateByOid, Error> {
let server = self.get_server(&self.get_server_type());
FutureResult::new(async move {
server?
.database_service()
.batch_get_collab_updates(object_ids, object_ty)
.await
})
}
fn get_collab_snapshots(
&self,
object_id: &str,
limit: usize,
) -> FutureResult<Vec<DatabaseSnapshot>, Error> {
let server = self.get_server(&self.get_server_type());
let database_id = object_id.to_string();
FutureResult::new(async move {
server?
.database_service()
.get_collab_snapshots(&database_id, limit)
.await
})
}
}
impl DocumentCloudService for ServerProvider {
fn get_document_updates(&self, document_id: &str) -> FutureResult<Vec<Vec<u8>>, Error> {
let server = self.get_server(&self.get_server_type());
let document_id = document_id.to_string();
FutureResult::new(async move {
server?
.document_service()
.get_document_updates(&document_id)
.await
})
}
fn get_document_snapshots(
&self,
document_id: &str,
limit: usize,
) -> FutureResult<Vec<DocumentSnapshot>, Error> {
let server = self.get_server(&self.get_server_type());
let document_id = document_id.to_string();
FutureResult::new(async move {
server?
.document_service()
.get_document_snapshots(&document_id, limit)
.await
})
}
fn get_document_data(&self, document_id: &str) -> FutureResult<Option<DocumentData>, Error> {
let server = self.get_server(&self.get_server_type());
let document_id = document_id.to_string();
FutureResult::new(async move {
server?
.document_service()
.get_document_data(&document_id)
.await
})
}
}
#[async_trait]
impl CollabStorageProvider for ServerProvider {
fn storage_source(&self) -> CollabSource {
self.get_server_type().into()
}
async fn get_plugins(&self, context: CollabPluginContext) -> Vec<Arc<dyn CollabPlugin>> {
let mut plugins: Vec<Arc<dyn CollabPlugin>> = vec![];
match context {
CollabPluginContext::Local => {},
CollabPluginContext::AppFlowyCloud {
uid: _,
collab_object,
local_collab,
} => {
if let Ok(server) = self.get_server(&ServerType::AppFlowyCloud) {
match server.collab_ws_channel(&collab_object.object_id).await {
Ok(Some(channel)) => {
let origin = CollabOrigin::Client(CollabClient::new(
collab_object.uid,
collab_object.device_id.clone(),
));
let sync_object = SyncObject::from(collab_object);
let (sink, stream) = (channel.sink(), channel.stream());
let sync_plugin = SyncPlugin::new(origin, sync_object, local_collab, sink, stream);
plugins.push(Arc::new(sync_plugin));
},
Ok(None) => {},
Err(err) => tracing::error!("🔴Failed to get collab ws channel: {:?}", err),
}
}
},
CollabPluginContext::Supabase {
uid,
collab_object,
local_collab,
local_collab_db,
} => {
if let Some(remote_collab_storage) = self
.get_server(&ServerType::Supabase)
.ok()
.and_then(|provider| provider.collab_storage(&collab_object))
{
plugins.push(Arc::new(SupabaseDBPlugin::new(
uid,
collab_object,
local_collab,
1,
remote_collab_storage,
local_collab_db,
)));
}
},
}
plugins
}
fn is_sync_enabled(&self) -> bool {
*self.enable_sync.read()
}
}

View File

@ -10,9 +10,9 @@ use std::{
},
};
use appflowy_integrate::collab_builder::{AppFlowyCollabBuilder, CollabStorageType};
use tokio::sync::RwLock;
use collab_integrate::collab_builder::{AppFlowyCollabBuilder, CollabSource};
use flowy_database2::DatabaseManager;
use flowy_document2::manager::DocumentManager;
use flowy_error::FlowyResult;
@ -31,9 +31,7 @@ use module::make_plugins;
pub use module::*;
use crate::deps_resolve::*;
use crate::integrate::server::{
current_server_provider, AppFlowyServerProvider, ServerProviderType,
};
use crate::integrate::server::{current_server_provider, ServerProvider, ServerType};
mod deps_resolve;
mod integrate;
@ -121,7 +119,7 @@ pub struct AppFlowyCore {
pub folder_manager: Arc<FolderManager>,
pub database_manager: Arc<DatabaseManager>,
pub event_dispatcher: Arc<AFPluginDispatcher>,
pub server_provider: Arc<AppFlowyServerProvider>,
pub server_provider: Arc<ServerProvider>,
pub task_dispatcher: Arc<RwLock<TaskDispatcher>>,
pub store_preference: Arc<StorePreferences>,
}
@ -147,7 +145,7 @@ impl AppFlowyCore {
runtime.spawn(TaskRunner::run(task_dispatcher.clone()));
let provider_type = current_server_provider(&store_preference);
let server_provider = Arc::new(AppFlowyServerProvider::new(
let server_provider = Arc::new(ServerProvider::new(
config.clone(),
provider_type,
Arc::downgrade(&store_preference),
@ -282,7 +280,7 @@ struct UserStatusCallbackImpl {
folder_manager: Arc<FolderManager>,
database_manager: Arc<DatabaseManager>,
document_manager: Arc<DocumentManager>,
server_provider: Arc<AppFlowyServerProvider>,
server_provider: Arc<ServerProvider>,
#[allow(dead_code)]
config: AppFlowyCoreConfig,
}
@ -298,7 +296,8 @@ impl UserStatusCallback for UserStatusCallbackImpl {
_device_id: &str,
) -> Fut<FlowyResult<()>> {
let user_workspace = user_workspace.clone();
let collab_builder = self.collab_builder.clone();
self.collab_builder.initialize(user_workspace.id.clone());
let folder_manager = self.folder_manager.clone();
let database_manager = self.database_manager.clone();
let document_manager = self.document_manager.clone();
@ -315,7 +314,6 @@ impl UserStatusCallback for UserStatusCallbackImpl {
}
to_fut(async move {
collab_builder.initialize(user_workspace.id.clone());
folder_manager
.initialize(
user_id,
@ -419,13 +417,13 @@ impl UserStatusCallback for UserStatusCallbackImpl {
fn open_workspace(&self, user_id: i64, user_workspace: &UserWorkspace) -> Fut<FlowyResult<()>> {
let user_workspace = user_workspace.clone();
let collab_builder = self.collab_builder.clone();
self.collab_builder.initialize(user_workspace.id.clone());
let folder_manager = self.folder_manager.clone();
let database_manager = self.database_manager.clone();
let document_manager = self.document_manager.clone();
to_fut(async move {
collab_builder.initialize(user_workspace.id.clone());
folder_manager
.initialize_with_workspace_id(user_id, &user_workspace.id)
.await?;
@ -449,12 +447,12 @@ impl UserStatusCallback for UserStatusCallbackImpl {
}
}
impl From<ServerProviderType> for CollabStorageType {
fn from(server_provider: ServerProviderType) -> Self {
impl From<ServerType> for CollabSource {
fn from(server_provider: ServerType) -> Self {
match server_provider {
ServerProviderType::Local => CollabStorageType::Local,
ServerProviderType::AppFlowyCloud => CollabStorageType::Local,
ServerProviderType::Supabase => CollabStorageType::Supabase,
ServerType::Local => CollabSource::Local,
ServerType::AppFlowyCloud => CollabSource::Local,
ServerType::Supabase => CollabSource::Supabase,
}
}
}

View File

@ -18,14 +18,12 @@ pub fn make_plugins(
.unwrap();
let user_plugin = flowy_user::event_map::init(user_session);
let folder_plugin = flowy_folder2::event_map::init(folder_manager);
let network_plugin = flowy_net::event_map::init();
let database_plugin = flowy_database2::event_map::init(database_manager);
let document_plugin2 = flowy_document2::event_map::init(document_manager2);
let config_plugin = flowy_config::event_map::init(store_preferences);
vec![
user_plugin,
folder_plugin,
network_plugin,
database_plugin,
document_plugin2,
config_plugin,

View File

@ -7,6 +7,6 @@ edition = "2021"
[dependencies]
lib-infra = { path = "../../../shared-lib/lib-infra" }
flowy-error = { path = "../flowy-error" }
flowy-error = { workspace = true }
collab-define = { version = "0.1.0" }
anyhow = "1.0.71"

View File

@ -9,24 +9,24 @@ edition = "2021"
collab = { version = "0.1.0" }
collab-database = { version = "0.1.0" }
collab-define = { version = "0.1.0" }
appflowy-integrate = {version = "0.1.0" }
flowy-database-deps = { path = "../flowy-database-deps" }
collab-integrate = { workspace = true }
flowy-database-deps = { workspace = true }
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
flowy-notification = { path = "../flowy-notification" }
flowy-notification = { workspace = true }
parking_lot = "0.12.1"
protobuf = {version = "2.28.0"}
flowy-error = { path = "../flowy-error", features = ["impl_from_dispatch_error", "impl_from_collab"]}
lib-dispatch = { path = "../lib-dispatch" }
flowy-error = { workspace = true, features = ["impl_from_dispatch_error", "impl_from_collab"]}
lib-dispatch = { workspace = true }
tokio = { version = "1.26", features = ["sync"] }
flowy-task= { path = "../flowy-task" }
flowy-task= { workspace = true }
bytes = { version = "1.4" }
tracing = { version = "0.1", features = ["log"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = {version = "1.0"}
serde_repr = "0.1"
lib-infra = { path = "../../../shared-lib/lib-infra" }
chrono = { version = "0.4.22", default-features = false, features = ["clock"] }
chrono = { version = "0.4.27", default-features = false, features = ["clock"] }
rust_decimal = "1.28.1"
rusty-money = {version = "0.4.1", features = ["iso"]}
lazy_static = "1.4.0"

View File

@ -1,8 +1,6 @@
use std::collections::HashMap;
use std::sync::{Arc, Weak};
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
use appflowy_integrate::{CollabPersistenceConfig, RocksCollabDB};
use collab::core::collab::{CollabRawData, MutexCollab};
use collab_database::blocks::BlockEvent;
use collab_database::database::{DatabaseData, YrsDocAction};
@ -15,6 +13,8 @@ use collab_database::views::{CreateDatabaseParams, CreateViewParams, DatabaseLay
use collab_define::CollabType;
use tokio::sync::RwLock;
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use collab_integrate::{CollabPersistenceConfig, RocksCollabDB};
use flowy_database_deps::cloud::DatabaseCloudService;
use flowy_error::{internal_error, FlowyError, FlowyResult};
use flowy_task::TaskDispatcher;

View File

@ -1464,7 +1464,7 @@ impl DatabaseViewData for DatabaseViewDataImpl {
field_id: &str,
visibility: Option<FieldVisibility>,
) {
let field_settings_map = self.get_field_settings(view_id, &vec![field_id.to_string()]);
let field_settings_map = self.get_field_settings(view_id, &[field_id.to_string()]);
let new_field_settings = if let Some(field_settings) = field_settings_map.get(field_id) {
let mut field_settings = field_settings.to_owned();

View File

@ -910,10 +910,7 @@ impl DatabaseViewEditor {
.send();
}
pub async fn v_get_field_settings(
&self,
field_ids: &Vec<String>,
) -> HashMap<String, FieldSettings> {
pub async fn v_get_field_settings(&self, field_ids: &[String]) -> HashMap<String, FieldSettings> {
self.delegate.get_field_settings(&self.view_id, field_ids)
}

View File

@ -292,7 +292,7 @@ mod tests {
let native_timestamp = 1647251762;
let native = NaiveDateTime::from_timestamp_opt(native_timestamp, 0).unwrap();
let utc = chrono::DateTime::<chrono::Utc>::from_utc(native, chrono::Utc);
let utc = chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(native, chrono::Utc);
// utc_timestamp doesn't carry timezone
let utc_timestamp = utc.timestamp();
assert_eq!(native_timestamp, utc_timestamp);
@ -304,7 +304,8 @@ mod tests {
// 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::<chrono::Local>::from_utc(native, gmt_8_offset);
let china_local =
chrono::DateTime::<chrono::Local>::from_naive_utc_and_offset(native, gmt_8_offset);
let china_local_time = format!(
"{}",
china_local.format_with_items(StrftimeItems::new(&format))

View File

@ -22,23 +22,13 @@ use crate::services::sort::SortCondition;
/// The [DateTypeOption] is used by [FieldType::Date], [FieldType::LastEditedTime], and [FieldType::CreatedTime].
/// So, storing the field type is necessary to distinguish the field type.
/// Most of the cases, each [FieldType] has its own [TypeOption] implementation.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct DateTypeOption {
pub date_format: DateFormat,
pub time_format: TimeFormat,
pub timezone_id: String,
}
impl Default for DateTypeOption {
fn default() -> Self {
Self {
date_format: Default::default(),
time_format: Default::default(),
timezone_id: Default::default(),
}
}
}
impl TypeOption for DateTypeOption {
type CellData = DateCellData;
type CellChangeset = DateCellChangeset;
@ -113,7 +103,7 @@ impl DateTypeOption {
if let Some(timestamp) = timestamp {
let naive = chrono::NaiveDateTime::from_timestamp_opt(*timestamp, 0).unwrap();
let offset = self.get_timezone_offset(naive);
let date_time = DateTime::<Local>::from_utc(naive, offset);
let date_time = DateTime::<Local>::from_naive_utc_and_offset(naive, offset);
let fmt = self.date_format.format_str();
let date = format!("{}", date_time.format(fmt));

View File

@ -108,7 +108,7 @@ impl TimestampTypeOption {
if let Some(timestamp) = timestamp {
let naive = chrono::NaiveDateTime::from_timestamp_opt(*timestamp, 0).unwrap();
let offset = Local::now().offset().fix();
let date_time = DateTime::<Local>::from_utc(naive, offset);
let date_time = DateTime::<Local>::from_naive_utc_and_offset(naive, offset);
let fmt = self.date_format.format_str();
let date = format!("{}", date_time.format(fmt));

View File

@ -262,16 +262,12 @@ pub fn default_type_option_data_from_type(field_type: &FieldType) -> TypeOptionD
match field_type {
FieldType::RichText => RichTextTypeOption::default().into(),
FieldType::Number => NumberTypeOption::default().into(),
FieldType::DateTime => DateTypeOption {
..Default::default()
}
.into(),
FieldType::DateTime => DateTypeOption::default().into(),
FieldType::LastEditedTime | FieldType::CreatedTime => TimestampTypeOption {
field_type: field_type.clone(),
date_format: DateFormat::Friendly,
time_format: TimeFormat::TwelveHour,
include_time: true,
..Default::default()
}
.into(),
FieldType::SingleSelect => SingleSelectTypeOption::default().into(),

View File

@ -443,7 +443,7 @@ fn date_time_from_timestamp(timestamp: Option<i64>, timezone_id: &str) -> DateTi
Err(_) => *Local::now().offset(),
};
DateTime::<Local>::from_utc(naive, offset)
DateTime::<Local>::from_naive_utc_and_offset(naive, offset)
},
None => DateTime::default(),
}

View File

@ -7,6 +7,6 @@ edition = "2021"
[dependencies]
lib-infra = { path = "../../../shared-lib/lib-infra" }
flowy-error = { path = "../flowy-error" }
flowy-error = { workspace = true }
collab-document = { version = "0.1.0" }
anyhow = "1.0.71"

View File

@ -9,14 +9,14 @@ edition = "2021"
collab = { version = "0.1.0" }
collab-document = { version = "0.1.0" }
collab-define = { version = "0.1.0" }
appflowy-integrate = {version = "0.1.0" }
flowy-document-deps = { path = "../flowy-document-deps" }
flowy-storage = { path = "../flowy-storage" }
collab-integrate = { workspace = true }
flowy-document-deps = { workspace = true }
flowy-storage = { workspace = true }
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
flowy-notification = { path = "../flowy-notification" }
flowy-notification = { workspace = true }
flowy-error = { path = "../flowy-error", features = ["impl_from_serde", "impl_from_sqlite", "impl_from_dispatch_error", "impl_from_collab"] }
lib-dispatch = { path = "../lib-dispatch" }
lib-dispatch = { workspace = true }
lib-infra = { path = "../../../shared-lib/lib-infra" }
protobuf = {version = "2.28.0"}

View File

@ -1,8 +1,6 @@
use std::sync::Weak;
use std::{collections::HashMap, sync::Arc};
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
use appflowy_integrate::RocksCollabDB;
use collab::core::collab::MutexCollab;
use collab_define::CollabType;
use collab_document::blocks::DocumentData;
@ -11,6 +9,8 @@ use collab_document::document_data::default_document_data;
use collab_document::YrsDocAction;
use parking_lot::RwLock;
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use collab_integrate::RocksCollabDB;
use flowy_document_deps::cloud::DocumentCloudService;
use flowy_error::{internal_error, FlowyError, FlowyResult};
use flowy_storage::FileStorageService;

View File

@ -2,8 +2,6 @@ use std::ops::Deref;
use std::sync::Arc;
use anyhow::Error;
use appflowy_integrate::collab_builder::{AppFlowyCollabBuilder, DefaultCollabStorageProvider};
use appflowy_integrate::RocksCollabDB;
use bytes::Bytes;
use collab_document::blocks::DocumentData;
use collab_document::document_data::default_document_data;
@ -12,6 +10,8 @@ use parking_lot::Once;
use tempfile::TempDir;
use tracing_subscriber::{fmt::Subscriber, util::SubscriberInitExt, EnvFilter};
use collab_integrate::collab_builder::{AppFlowyCollabBuilder, DefaultCollabStorageProvider};
use collab_integrate::RocksCollabDB;
use flowy_document2::document::MutexDocument;
use flowy_document2::manager::{DocumentManager, DocumentUser};
use flowy_document_deps::cloud::*;
@ -57,18 +57,15 @@ impl FakeUser {
}
impl DocumentUser for FakeUser {
fn user_id(&self) -> Result<i64, flowy_error::FlowyError> {
fn user_id(&self) -> Result<i64, FlowyError> {
Ok(1)
}
fn token(&self) -> Result<Option<String>, flowy_error::FlowyError> {
fn token(&self) -> Result<Option<String>, FlowyError> {
Ok(None)
}
fn collab_db(
&self,
_uid: i64,
) -> Result<std::sync::Weak<RocksCollabDB>, flowy_error::FlowyError> {
fn collab_db(&self, _uid: i64) -> Result<std::sync::Weak<RocksCollabDB>, FlowyError> {
Ok(Arc::downgrade(&self.collab_db))
}
}
@ -92,6 +89,7 @@ pub fn db() -> Arc<RocksCollabDB> {
pub fn default_collab_builder() -> Arc<AppFlowyCollabBuilder> {
let builder = AppFlowyCollabBuilder::new(DefaultCollabStorageProvider());
builder.set_sync_device(uuid::Uuid::new_v4().to_string());
builder.initialize(uuid::Uuid::new_v4().to_string());
Arc::new(builder)
}

View File

@ -12,18 +12,19 @@ bytes = "1.4"
anyhow = "1.0"
thiserror = "1.0"
lib-dispatch = { path = "../lib-dispatch", optional = true }
lib-dispatch = { workspace = true, optional = true }
serde_json = {version = "1.0", optional = true}
serde_repr = { version = "0.1" }
serde = "1.0"
reqwest = { version = "0.11.14", optional = true, features = ["native-tls-vendored"] }
flowy-sqlite = { path = "../flowy-sqlite", optional = true}
flowy-sqlite = { workspace = true, optional = true}
r2d2 = { version = "0.8", optional = true}
url = { version = "2.2", optional = true }
collab-database = { version = "0.1.0", optional = true }
collab-document = { version = "0.1.0", optional = true }
tokio-postgres = { version = "0.7.8", optional = true }
tokio = { version = "1.0", optional = true }
client-api = { version = "0.1.0", optional = true }
[features]
impl_from_dispatch_error = ["lib-dispatch"]
@ -34,6 +35,7 @@ impl_from_collab = ["collab-database", "collab-document", "impl_from_reqwest"]
impl_from_postgres = ["tokio-postgres"]
impl_from_tokio= ["tokio"]
impl_from_url= ["url"]
impl_from_appflowy_cloud = ["client-api"]
dart = ["flowy-codegen/dart"]
ts = ["flowy-codegen/ts"]

View File

@ -238,8 +238,18 @@ pub enum ErrorCode {
#[error("Parse url failed")]
InvalidURL = 78,
#[error("Require Email Confirmation, Sign in after email confirmation")]
AwaitingEmailConfirmation = 79,
#[error("Text id is empty")]
TextIdIsEmpty = 79,
TextIdIsEmpty = 80,
#[error("Record already exists")]
RecordAlreadyExists = 81,
#[error("Missing payload")]
MissingPayload = 82,
}
impl ErrorCode {

View File

@ -0,0 +1,27 @@
use client_api::error::AppError;
use crate::{ErrorCode, FlowyError};
impl From<AppError> for FlowyError {
fn from(error: AppError) -> Self {
let code = match error.code {
client_api::error::ErrorCode::Ok => ErrorCode::Internal,
client_api::error::ErrorCode::Unhandled => ErrorCode::Internal,
client_api::error::ErrorCode::RecordNotFound => ErrorCode::RecordNotFound,
client_api::error::ErrorCode::RecordAlreadyExists => ErrorCode::RecordAlreadyExists,
client_api::error::ErrorCode::InvalidEmail => ErrorCode::EmailFormatInvalid,
client_api::error::ErrorCode::InvalidPassword => ErrorCode::PasswordFormatInvalid,
client_api::error::ErrorCode::OAuthError => ErrorCode::UserUnauthorized,
client_api::error::ErrorCode::MissingPayload => ErrorCode::MissingPayload,
client_api::error::ErrorCode::StorageError => ErrorCode::Internal,
client_api::error::ErrorCode::OpenError => ErrorCode::Internal,
client_api::error::ErrorCode::InvalidUrl => ErrorCode::InvalidURL,
client_api::error::ErrorCode::InvalidRequestParams => ErrorCode::InvalidParams,
client_api::error::ErrorCode::UrlMissingParameter => ErrorCode::InvalidParams,
client_api::error::ErrorCode::InvalidOAuthProvider => ErrorCode::InvalidAuthConfig,
client_api::error::ErrorCode::NotLoggedIn => ErrorCode::UserUnauthorized,
};
FlowyError::new(code, error.message)
}
}

View File

@ -22,5 +22,7 @@ mod postgres;
#[cfg(feature = "impl_from_tokio")]
mod tokio;
#[cfg(feature = "impl_from_appflowy_cloud")]
mod cloud;
#[cfg(feature = "impl_from_url")]
mod url;

View File

@ -7,7 +7,7 @@ edition = "2021"
[dependencies]
lib-infra = { path = "../../../shared-lib/lib-infra" }
flowy-error = { path = "../flowy-error" }
flowy-error = { workspace = true }
collab-folder = { version = "0.1.0" }
uuid = { version = "1.3.3", features = ["v4"] }
anyhow = "1.0.71"

View File

@ -9,29 +9,29 @@ edition = "2021"
collab = { version = "0.1.0" }
collab-folder = { version = "0.1.0" }
collab-define = { version = "0.1.0" }
appflowy-integrate = {version = "0.1.0" }
flowy-folder-deps = { path = "../flowy-folder-deps" }
collab-integrate = { workspace = true }
flowy-folder-deps = { workspace = true }
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
flowy-notification = { path = "../flowy-notification" }
flowy-notification = { workspace = true }
parking_lot = "0.12.1"
unicode-segmentation = "1.10"
tracing = { version = "0.1", features = ["log"] }
flowy-error = { path = "../flowy-error", features = ["impl_from_dispatch_error"]}
lib-dispatch = { path = "../lib-dispatch" }
lib-dispatch = { workspace = true }
bytes = { version = "1.4" }
lib-infra = { path = "../../../shared-lib/lib-infra" }
tokio = { version = "1.26", features = ["full"] }
nanoid = "0.4.0"
lazy_static = "1.4.0"
chrono = { version = "0.4.22", default-features = false, features = ["clock"] }
chrono = { version = "0.4.27", default-features = false, features = ["clock"] }
strum_macros = "0.21"
protobuf = {version = "2.28.0"}
uuid = { version = "1.3.3", features = ["v4"] }
tokio-stream = { version = "0.1.14", features = ["sync"] }
[dev-dependencies]
flowy-folder2 = { path = "../flowy-folder2"}
flowy-folder2 = { workspace = true }
flowy-test = { path = "../flowy-test", default-features = false }
[build-dependencies]

View File

@ -2,8 +2,6 @@ use std::collections::HashSet;
use std::ops::Deref;
use std::sync::{Arc, Weak};
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
use appflowy_integrate::{CollabPersistenceConfig, RocksCollabDB, YrsDocAction};
use collab::core::collab::{CollabRawData, MutexCollab};
use collab::core::collab_state::SyncState;
use collab_define::CollabType;
@ -16,6 +14,8 @@ use tokio_stream::wrappers::WatchStream;
use tokio_stream::StreamExt;
use tracing::{event, Level};
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use collab_integrate::{CollabPersistenceConfig, RocksCollabDB, YrsDocAction};
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_folder_deps::cloud::{gen_view_id, FolderCloudService};

View File

@ -1,25 +0,0 @@
[package]
name = "flowy-net"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lib-dispatch = { path = "../lib-dispatch" }
protobuf = {version = "2.28.0"}
bytes = { version = "1.4" }
tracing = { version = "0.1"}
[features]
http_server = []
dart = [
"flowy-codegen/dart",
]
ts = [
"flowy-codegen/ts",
]
[build-dependencies]
flowy-codegen = { path = "../../../shared-lib/flowy-codegen"}

View File

@ -1,3 +0,0 @@
# Check out the FlowyConfig (located in flowy_toml.rs) for more details.
proto_input = ["src/event_map.rs", "src/entities"]
event_files = ["src/event_map.rs"]

View File

@ -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);
}

View File

@ -1,2 +0,0 @@
mod network_state;
pub use network_state::*;

View File

@ -1,29 +0,0 @@
// use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
//
// #[derive(ProtoBuf_Enum, Debug, Clone, Eq, PartialEq, Default)]
// pub enum NetworkTypePB {
// #[default]
// Unknown = 0,
// Wifi = 1,
// Cell = 2,
// Ethernet = 3,
// Bluetooth = 4,
// VPN = 5,
// }
//
// impl NetworkTypePB {
// pub fn is_connect(&self) -> bool {
// match self {
// NetworkTypePB::Unknown | NetworkTypePB::Bluetooth => false,
// NetworkTypePB::Wifi | NetworkTypePB::Cell | NetworkTypePB::Ethernet | NetworkTypePB::VPN => {
// true
// },
// }
// }
// }
//
// #[derive(ProtoBuf, Debug, Default, Clone)]
// pub struct NetworkStatePB {
// #[pb(index = 1)]
// pub ty: NetworkTypePB,
// }

View File

@ -1,5 +0,0 @@
use lib_dispatch::prelude::*;
pub fn init() -> AFPlugin {
AFPlugin::new().name("Flowy-Network")
}

View File

@ -1,3 +0,0 @@
// pub async fn update_network_ty(_data: AFPluginData<NetworkStatePB>) -> Result<(), FlowyError> {
// Ok(())
// }

View File

@ -1,3 +0,0 @@
pub mod entities;
pub mod event_map;
mod handlers;

View File

@ -13,7 +13,7 @@ bytes = { version = "1.4" }
serde = "1.0"
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
lib-dispatch = { path = "../lib-dispatch" }
lib-dispatch = { workspace = true }
[build-dependencies]
flowy-codegen = { path = "../../../shared-lib/flowy-codegen" }

View File

@ -6,5 +6,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
flowy-error = { path = "../flowy-error" }
flowy-error = { workspace = true }
serde = { version = "1.0", features = ["derive"] }

View File

@ -23,25 +23,26 @@ bytes = { version = "1.0.1", features = ["serde"] }
tokio-retry = "0.3"
anyhow = "1.0"
uuid = { version = "1.3.3", features = ["v4"] }
chrono = { version = "0.4.22", default-features = false, features = ["clock"] }
chrono = { version = "0.4.27", default-features = false, features = ["clock"] }
collab = { version = "0.1.0" }
collab-plugins = { version = "0.1.0" }
collab-plugins = { version = "0.1.0", features = ["sync_plugin"] }
collab-document = { version = "0.1.0" }
collab-define = { version = "0.1.0" }
hex = "0.4.3"
postgrest = "1.0"
lib-infra = { path = "../../../shared-lib/lib-infra" }
flowy-user-deps = { path = "../flowy-user-deps" }
flowy-folder-deps = { path = "../flowy-folder-deps" }
flowy-database-deps = { path = "../flowy-database-deps" }
flowy-document-deps = { path = "../flowy-document-deps" }
flowy-error = { path = "../flowy-error", features = ["impl_from_postgres", "impl_from_serde", "impl_from_reqwest", "impl_from_url"] }
flowy-server-config = { path = "../flowy-server-config" }
flowy-encrypt = { path = "../flowy-encrypt" }
flowy-storage = { path = "../flowy-storage" }
flowy-user-deps = { workspace = true }
flowy-folder-deps = { workspace = true }
flowy-database-deps = { workspace = true }
flowy-document-deps = { workspace = true }
flowy-error = { workspace = true, features = ["impl_from_postgres", "impl_from_serde", "impl_from_reqwest", "impl_from_url", "impl_from_appflowy_cloud"] }
flowy-server-config = { workspace = true }
flowy-encrypt = { workspace = true }
flowy-storage = { workspace = true }
mime_guess = "2.0"
url = "2.4"
tokio-util = "0.7"
client-api = { version = "0.1.0" }
[dev-dependencies]
uuid = { version = "1.3.3", features = ["v4"] }

View File

@ -20,7 +20,7 @@ pub fn appflowy_cloud_server_configuration() -> Result<AFCloudConfiguration, con
settings.merge(config::File::from_str(base, FileFormat::Yaml).required(true))?;
let environment: Environment = std::env::var("APP_ENVIRONMENT")
.unwrap_or_else(|_| "local".into())
.unwrap_or_else(|_| "local".to_owned())
.try_into()
.expect("Failed to parse APP_ENVIRONMENT.");
@ -43,42 +43,6 @@ impl AFCloudConfiguration {
format!("{}://{}:{}", self.http_scheme, self.host, self.port)
}
pub fn sign_up_url(&self) -> String {
format!("{}/api/register", self.base_url())
}
pub fn sign_in_url(&self) -> String {
format!("{}/api/auth", self.base_url())
}
pub fn sign_out_url(&self) -> String {
format!("{}/api/auth", self.base_url())
}
pub fn user_profile_url(&self) -> String {
format!("{}/api/user", self.base_url())
}
pub fn workspace_url(&self) -> String {
format!("{}/api/workspace", self.base_url())
}
pub fn app_url(&self) -> String {
format!("{}/api/app", self.base_url())
}
pub fn view_url(&self) -> String {
format!("{}/api/view", self.base_url())
}
pub fn doc_url(&self) -> String {
format!("{}/api/doc", self.base_url())
}
pub fn trash_url(&self) -> String {
format!("{}/api/trash", self.base_url())
}
pub fn ws_addr(&self) -> String {
format!("{}://{}:{}/ws", self.ws_scheme, self.host, self.port)
}

View File

@ -6,9 +6,14 @@ use flowy_database_deps::cloud::{
};
use lib_infra::future::FutureResult;
pub(crate) struct AFCloudDatabaseCloudServiceImpl();
use crate::af_cloud::AFServer;
impl DatabaseCloudService for AFCloudDatabaseCloudServiceImpl {
pub(crate) struct AFCloudDatabaseCloudServiceImpl<T>(pub T);
impl<T> DatabaseCloudService for AFCloudDatabaseCloudServiceImpl<T>
where
T: AFServer,
{
fn get_collab_update(
&self,
_object_id: &str,

View File

@ -3,9 +3,14 @@ use anyhow::Error;
use flowy_document_deps::cloud::*;
use lib_infra::future::FutureResult;
pub(crate) struct AFCloudDocumentCloudServiceImpl();
use crate::af_cloud::AFServer;
impl DocumentCloudService for AFCloudDocumentCloudServiceImpl {
pub(crate) struct AFCloudDocumentCloudServiceImpl<T>(pub T);
impl<T> DocumentCloudService for AFCloudDocumentCloudServiceImpl<T>
where
T: AFServer,
{
fn get_document_updates(&self, _document_id: &str) -> FutureResult<Vec<Vec<u8>>, Error> {
FutureResult::new(async move { Ok(vec![]) })
}

View File

@ -1,24 +1,18 @@
use anyhow::Error;
use flowy_folder_deps::cloud::{
gen_workspace_id, FolderCloudService, FolderData, FolderSnapshot, Workspace,
};
use flowy_folder_deps::cloud::{FolderCloudService, FolderData, FolderSnapshot, Workspace};
use lib_infra::future::FutureResult;
use lib_infra::util::timestamp;
pub(crate) struct AFCloudFolderCloudServiceImpl();
use crate::af_cloud::AFServer;
impl FolderCloudService for AFCloudFolderCloudServiceImpl {
fn create_workspace(&self, _uid: i64, name: &str) -> FutureResult<Workspace, Error> {
let name = name.to_string();
FutureResult::new(async move {
Ok(Workspace {
id: gen_workspace_id().to_string(),
name: name.to_string(),
child_views: Default::default(),
created_at: timestamp(),
})
})
pub(crate) struct AFCloudFolderCloudServiceImpl<T>(pub T);
impl<T> FolderCloudService for AFCloudFolderCloudServiceImpl<T>
where
T: AFServer,
{
fn create_workspace(&self, _uid: i64, _name: &str) -> FutureResult<Workspace, Error> {
FutureResult::new(async move { todo!() })
}
fn get_folder_data(&self, _workspace_id: &str) -> FutureResult<Option<FolderData>, Error> {

View File

@ -1,96 +1,60 @@
use std::sync::Arc;
use anyhow::Error;
use collab_define::CollabObject;
use flowy_error::{ErrorCode, FlowyError};
use flowy_error::FlowyError;
use flowy_user_deps::cloud::UserCloudService;
use flowy_user_deps::entities::*;
use lib_infra::box_any::BoxAny;
use lib_infra::future::FutureResult;
use crate::af_cloud::configuration::{AFCloudConfiguration, HEADER_TOKEN};
use crate::request::HttpRequestBuilder;
use crate::af_cloud::{AFCloudClient, AFServer};
pub(crate) struct AFCloudUserAuthServiceImpl {
config: AFCloudConfiguration,
pub(crate) struct AFCloudUserAuthServiceImpl<T> {
server: T,
}
impl AFCloudUserAuthServiceImpl {
pub(crate) fn new(config: AFCloudConfiguration) -> Self {
Self { config }
impl<T> AFCloudUserAuthServiceImpl<T> {
pub(crate) fn new(server: T) -> Self {
Self { server }
}
}
impl UserCloudService for AFCloudUserAuthServiceImpl {
impl<T> UserCloudService for AFCloudUserAuthServiceImpl<T>
where
T: AFServer,
{
fn sign_up(&self, params: BoxAny) -> FutureResult<SignUpResponse, Error> {
let url = self.config.sign_up_url();
let try_get_client = self.server.try_get_client();
FutureResult::new(async move {
let params = params.unbox_or_error::<SignUpParams>()?;
let resp = user_sign_up_request(params, &url).await?;
let resp = user_sign_up_request(try_get_client?, params).await?;
Ok(resp)
})
}
fn sign_in(&self, params: BoxAny) -> FutureResult<SignInResponse, Error> {
let url = self.config.sign_in_url();
FutureResult::new(async move {
let params = params.unbox_or_error::<SignInParams>()?;
let resp = user_sign_in_request(params, &url).await?;
Ok(resp)
})
fn sign_in(&self, _params: BoxAny) -> FutureResult<SignInResponse, Error> {
todo!()
}
fn sign_out(&self, token: Option<String>) -> FutureResult<(), Error> {
match token {
None => FutureResult::new(async {
Err(FlowyError::new(ErrorCode::InvalidParams, "Token should not be empty").into())
}),
Some(token) => {
let token = token;
let url = self.config.sign_out_url();
FutureResult::new(async move {
let _ = user_sign_out_request(&token, &url).await;
Ok(())
})
},
}
fn sign_out(&self, _token: Option<String>) -> FutureResult<(), Error> {
todo!()
}
fn update_user(
&self,
credential: UserCredentials,
params: UpdateUserProfileParams,
_credential: UserCredentials,
_params: UpdateUserProfileParams,
) -> FutureResult<(), Error> {
match credential.token {
None => FutureResult::new(async {
Err(FlowyError::new(ErrorCode::InvalidParams, "Token should not be empty").into())
}),
Some(token) => {
let token = token;
let url = self.config.user_profile_url();
FutureResult::new(async move {
update_user_profile_request(&token, params, &url).await?;
Ok(())
})
},
}
todo!()
}
fn get_user_profile(
&self,
credential: UserCredentials,
_credential: UserCredentials,
) -> FutureResult<Option<UserProfile>, Error> {
let url = self.config.user_profile_url();
FutureResult::new(async move {
match credential.token {
None => {
Err(FlowyError::new(ErrorCode::UnexpectedEmpty, "Token should not be empty").into())
},
Some(token) => {
let profile = get_user_profile_request(&token, &url).await?;
Ok(Some(profile))
},
}
})
todo!()
}
fn get_user_workspaces(
@ -145,53 +109,30 @@ impl UserCloudService for AFCloudUserAuthServiceImpl {
}
pub async fn user_sign_up_request(
client: Arc<AFCloudClient>,
params: SignUpParams,
url: &str,
) -> Result<SignUpResponse, FlowyError> {
let response = request_builder().post(url).json(params)?.response().await?;
Ok(response)
}
pub async fn user_sign_in_request(
params: SignInParams,
url: &str,
) -> Result<SignInResponse, FlowyError> {
let response = request_builder().post(url).json(params)?.response().await?;
Ok(response)
}
pub async fn user_sign_out_request(token: &str, url: &str) -> Result<(), FlowyError> {
request_builder()
.delete(url)
.header(HEADER_TOKEN, token)
.send()
client
.read()
.await
.sign_up(&params.email, &params.password)
.await?;
Ok(())
}
pub async fn get_user_profile_request(token: &str, url: &str) -> Result<UserProfile, FlowyError> {
let user_profile = request_builder()
.get(url)
.header(HEADER_TOKEN, token)
.response()
.await?;
Ok(user_profile)
}
pub async fn update_user_profile_request(
token: &str,
params: UpdateUserProfileParams,
url: &str,
) -> Result<(), FlowyError> {
request_builder()
.patch(url)
.header(HEADER_TOKEN, token)
.json(params)?
.send()
.await?;
Ok(())
}
fn request_builder() -> HttpRequestBuilder {
HttpRequestBuilder::new()
todo!()
// tracing::info!("User signed up: {:?}", user);
// match user.confirmed_at {
// Some(_) => {
// // User is already confirmed, help her/him to sign in
// let token = client.sign_in_password(&params.email, &params.password).await?;
//
// // TODO:
// // Query workspace list
// // Query user profile
//
// todo!()
// },
// None => Err(FlowyError::new(
// ErrorCode::AwaitingEmailConfirmation,
// "Awaiting email confirmation".to_string(),
// )),
// }
}

View File

@ -1,13 +1,19 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use collab_define::CollabObject;
use collab_plugins::cloud_storage::RemoteCollabStorage;
use anyhow::Error;
use client_api::notify::{TokenState, TokenStateReceiver};
use client_api::ws::{BusinessID, WSClient, WSClientConfig, WebSocketChannel};
use client_api::Client;
use tokio::sync::RwLock;
use flowy_database_deps::cloud::DatabaseCloudService;
use flowy_document_deps::cloud::DocumentCloudService;
use flowy_error::{ErrorCode, FlowyError};
use flowy_folder_deps::cloud::FolderCloudService;
use flowy_storage::FileStorageService;
use flowy_user_deps::cloud::UserCloudService;
use lib_infra::future::FutureResult;
use crate::af_cloud::configuration::AFCloudConfiguration;
use crate::af_cloud::impls::{
@ -16,38 +22,178 @@ use crate::af_cloud::impls::{
};
use crate::AppFlowyServer;
pub(crate) type AFCloudClient = RwLock<client_api::Client>;
pub struct AFCloudServer {
#[allow(dead_code)]
pub(crate) config: AFCloudConfiguration,
pub(crate) client: Arc<AFCloudClient>,
enable_sync: AtomicBool,
#[allow(dead_code)]
device_id: Arc<parking_lot::RwLock<String>>,
ws_client: Arc<RwLock<WSClient>>,
}
impl AFCloudServer {
pub fn new(config: AFCloudConfiguration) -> Self {
Self { config }
pub fn new(
config: AFCloudConfiguration,
enable_sync: bool,
device_id: Arc<parking_lot::RwLock<String>>,
) -> Self {
let http_client = reqwest::Client::new();
let api_client = client_api::Client::from(http_client, &config.base_url(), &config.ws_addr());
let token_state_rx = api_client.subscribe_token_state();
let enable_sync = AtomicBool::new(enable_sync);
let ws_client = WSClient::new(WSClientConfig {
buffer_capacity: 100,
ping_per_secs: 2,
retry_connect_per_pings: 5,
});
let ws_client = Arc::new(RwLock::new(ws_client));
let api_client = Arc::new(RwLock::new(api_client));
spawn_ws_conn(&device_id, token_state_rx, &ws_client, &api_client);
Self {
config,
client: api_client,
enable_sync,
device_id,
ws_client,
}
}
fn get_client(&self) -> Option<Arc<AFCloudClient>> {
if self.enable_sync.load(Ordering::SeqCst) {
Some(self.client.clone())
} else {
None
}
}
}
impl AppFlowyServer for AFCloudServer {
fn set_enable_sync(&self, uid: i64, enable: bool) {
tracing::info!("{} cloud sync: {}", uid, enable);
self.enable_sync.store(enable, Ordering::SeqCst);
}
fn user_service(&self) -> Arc<dyn UserCloudService> {
Arc::new(AFCloudUserAuthServiceImpl::new(self.config.clone()))
let server = AFServerImpl(self.get_client());
Arc::new(AFCloudUserAuthServiceImpl::new(server))
}
fn folder_service(&self) -> Arc<dyn FolderCloudService> {
Arc::new(AFCloudFolderCloudServiceImpl())
let server = AFServerImpl(self.get_client());
Arc::new(AFCloudFolderCloudServiceImpl(server))
}
fn database_service(&self) -> Arc<dyn DatabaseCloudService> {
Arc::new(AFCloudDatabaseCloudServiceImpl())
let server = AFServerImpl(self.get_client());
Arc::new(AFCloudDatabaseCloudServiceImpl(server))
}
fn document_service(&self) -> Arc<dyn DocumentCloudService> {
Arc::new(AFCloudDocumentCloudServiceImpl())
let server = AFServerImpl(self.get_client());
Arc::new(AFCloudDocumentCloudServiceImpl(server))
}
fn collab_storage(&self, _collab_object: &CollabObject) -> Option<Arc<dyn RemoteCollabStorage>> {
None
fn collab_ws_channel(
&self,
object_id: &str,
) -> FutureResult<Option<Arc<WebSocketChannel>>, anyhow::Error> {
if self.enable_sync.load(Ordering::SeqCst) {
let object_id = object_id.to_string();
let weak_ws_client = Arc::downgrade(&self.ws_client);
FutureResult::new(async move {
match weak_ws_client.upgrade() {
None => {
tracing::warn!("🟡Collab WS client is dropped");
Ok(None)
},
Some(ws_client) => Ok(
ws_client
.read()
.await
.subscribe(BusinessID::CollabId, object_id)
.await
.ok(),
),
}
})
} else {
FutureResult::new(async { Ok(None) })
}
}
fn file_storage(&self) -> Option<Arc<dyn FileStorageService>> {
None
}
}
/// Spawns a new asynchronous task to handle WebSocket connections based on token state.
///
/// This function listens to the `token_state_rx` channel for token state updates. Depending on the
/// received state, it either refreshes the WebSocket connection or disconnects from it.
fn spawn_ws_conn(
device_id: &Arc<parking_lot::RwLock<String>>,
mut token_state_rx: TokenStateReceiver,
ws_client: &Arc<RwLock<WSClient>>,
api_client: &Arc<RwLock<Client>>,
) {
let weak_device_id = Arc::downgrade(device_id);
let weak_ws_client = Arc::downgrade(ws_client);
let weak_api_client = Arc::downgrade(api_client);
tokio::spawn(async move {
while let Ok(token_state) = token_state_rx.recv().await {
tracing::info!("🟢Token state: {:?}", token_state);
match token_state {
TokenState::Refresh => {
if let (Some(api_client), Some(ws_client), Some(device_id)) = (
weak_api_client.upgrade(),
weak_ws_client.upgrade(),
weak_device_id.upgrade(),
) {
let device_id = device_id.read().clone();
if let Ok(ws_addr) = api_client.read().await.ws_url(&device_id) {
tracing::info!("🟢Connecting to websocket");
let _ = ws_client.write().await.connect(ws_addr).await;
}
}
},
TokenState::Invalid => {
if let Some(ws_client) = weak_ws_client.upgrade() {
tracing::info!("🟡Disconnecting from websocket");
ws_client.write().await.disconnect().await;
}
},
}
}
});
}
pub trait AFServer: Send + Sync + 'static {
fn get_client(&self) -> Option<Arc<AFCloudClient>>;
fn try_get_client(&self) -> Result<Arc<AFCloudClient>, Error>;
}
#[derive(Clone)]
pub struct AFServerImpl(pub Option<Arc<AFCloudClient>>);
impl AFServer for AFServerImpl {
fn get_client(&self) -> Option<Arc<AFCloudClient>> {
self.0.clone()
}
fn try_get_client(&self) -> Result<Arc<AFCloudClient>, Error> {
match self.0.clone() {
None => Err(
FlowyError::new(
ErrorCode::DataSyncRequired,
"Data Sync is disabled, please enable it first",
)
.into(),
),
Some(client) => Ok(client),
}
}
}

View File

@ -2,7 +2,7 @@ pub use server::*;
pub mod af_cloud;
pub mod local_server;
mod request;
// mod request;
mod response;
mod server;
pub mod supabase;

View File

@ -1,7 +1,5 @@
use std::sync::Arc;
use collab_define::CollabObject;
use collab_plugins::cloud_storage::RemoteCollabStorage;
use parking_lot::RwLock;
use tokio::sync::mpsc;
@ -70,10 +68,6 @@ impl AppFlowyServer for LocalServer {
Arc::new(LocalServerDocumentCloudServiceImpl())
}
fn collab_storage(&self, _collab_object: &CollabObject) -> Option<Arc<dyn RemoteCollabStorage>> {
None
}
fn file_storage(&self) -> Option<Arc<dyn FileStorageService>> {
None
}

View File

@ -1,5 +1,6 @@
use std::sync::Arc;
use client_api::ws::WebSocketChannel;
use collab_define::CollabObject;
use collab_plugins::cloud_storage::RemoteCollabStorage;
use parking_lot::RwLock;
@ -9,6 +10,7 @@ use flowy_document_deps::cloud::DocumentCloudService;
use flowy_folder_deps::cloud::FolderCloudService;
use flowy_storage::FileStorageService;
use flowy_user_deps::cloud::UserCloudService;
use lib_infra::future::FutureResult;
pub trait AppFlowyEncryption: Send + Sync + 'static {
fn get_secret(&self) -> Option<String>;
@ -85,7 +87,16 @@ pub trait AppFlowyServer: Send + Sync + 'static {
/// # Returns
///
/// An `Option` that might contain an `Arc` wrapping the `RemoteCollabStorage` interface.
fn collab_storage(&self, collab_object: &CollabObject) -> Option<Arc<dyn RemoteCollabStorage>>;
fn collab_storage(&self, _collab_object: &CollabObject) -> Option<Arc<dyn RemoteCollabStorage>> {
None
}
fn collab_ws_channel(
&self,
_object_id: &str,
) -> FutureResult<Option<Arc<WebSocketChannel>>, anyhow::Error> {
FutureResult::new(async { Ok(None) })
}
fn file_storage(&self) -> Option<Arc<dyn FileStorageService>>;
}

View File

@ -62,8 +62,11 @@ where
async fn get_all_updates(&self, object: &CollabObject) -> Result<Vec<Vec<u8>>, Error> {
let postgrest = self.server.try_get_weak_postgrest()?;
let action =
FetchObjectUpdateAction::new(object.object_id.clone(), object.ty.clone(), postgrest);
let action = FetchObjectUpdateAction::new(
object.object_id.clone(),
object.collab_type.clone(),
postgrest,
);
let updates = action.run().await?;
Ok(updates)
}
@ -140,10 +143,14 @@ where
update: Vec<u8>,
) -> Result<(), Error> {
if let Some(postgrest) = self.server.get_postgrest() {
let workspace_id = object.get_workspace_id().ok_or(anyhow::anyhow!(
"Can't get the workspace id in CollabObject"
))?;
send_update(workspace_id, object, update, &postgrest, &self.secret()).await?;
send_update(
object.workspace_id.clone(),
object,
update,
&postgrest,
&self.secret(),
)
.await?;
}
Ok(())
@ -156,16 +163,14 @@ where
init_update: Vec<u8>,
) -> Result<(), Error> {
let postgrest = self.server.try_get_postgrest()?;
let workspace_id = object
.get_workspace_id()
.ok_or(anyhow::anyhow!("Invalid workspace id"))?;
let update_items = get_updates_from_server(&object.object_id, &object.ty, &postgrest).await?;
let update_items =
get_updates_from_server(&object.object_id, &object.collab_type, &postgrest).await?;
// If the update_items is empty, we can send the init_update directly
if update_items.is_empty() {
send_update(
workspace_id,
object.workspace_id.clone(),
object,
init_update,
&postgrest,
@ -197,18 +202,13 @@ pub(crate) async fn flush_collab_with_update(
) -> Result<(), Error> {
// 2.Merge the updates into one and then delete the merged updates
let merge_result = spawn_blocking(move || merge_updates(update_items, update)).await??;
let workspace_id = object
.get_workspace_id()
.ok_or(anyhow::anyhow!("Invalid workspace id"))?;
let value_size = merge_result.new_update.len() as i32;
let md5 = md5(&merge_result.new_update);
tracing::trace!(
"Flush collab id:{} type:{} is_encrypt: {}",
object.object_id,
object.ty,
object.collab_type,
secret.is_some()
);
let (new_update, encrypt) =
@ -219,11 +219,11 @@ pub(crate) async fn flush_collab_with_update(
.insert("encrypt", encrypt)
.insert("md5", md5)
.insert("value_size", value_size)
.insert("partition_key", partition_key(&object.ty))
.insert("partition_key", partition_key(&object.collab_type))
.insert("uid", object.uid)
.insert("workspace_id", workspace_id)
.insert("workspace_id", &object.workspace_id)
.insert("removed_keys", merge_result.merged_keys)
.insert("did", object.get_device_id())
.insert("did", &object.device_id)
.build();
postgrest
@ -247,13 +247,13 @@ pub(crate) async fn send_update(
let (update, encrypt) = SupabaseBinaryColumnEncoder::encode(update, encryption_secret)?;
let builder = InsertParamsBuilder::new()
.insert("oid", object.object_id.clone())
.insert("partition_key", partition_key(&object.ty))
.insert("partition_key", partition_key(&object.collab_type))
.insert("value", update)
.insert("encrypt", encrypt)
.insert("uid", object.uid)
.insert("md5", md5)
.insert("workspace_id", workspace_id)
.insert("did", object.get_device_id())
.insert("did", &object.device_id)
.insert("value_size", value_size);
let params = builder.build();

View File

@ -150,7 +150,7 @@ pub async fn create_snapshot(
.insert(
InsertParamsBuilder::new()
.insert(AF_COLLAB_SNAPSHOT_OID_COLUMN, object.object_id.clone())
.insert("name", object.ty.to_string())
.insert("name", object.collab_type.to_string())
.insert(AF_COLLAB_SNAPSHOT_ENCRYPT_COLUMN, encrypt)
.insert(AF_COLLAB_SNAPSHOT_BLOB_COLUMN, snapshot)
.insert(AF_COLLAB_SNAPSHOT_BLOB_SIZE_COLUMN, value_size)

View File

@ -292,9 +292,12 @@ where
.upgrade()
.ok_or(anyhow::anyhow!("postgrest is not available"))?;
let updates =
get_updates_from_server(&collab_object.object_id, &collab_object.ty, &postgrest)
.await?;
let updates = get_updates_from_server(
&collab_object.object_id,
&collab_object.collab_type,
&postgrest,
)
.await?;
flush_collab_with_update(
&collab_object,

View File

@ -9,7 +9,7 @@ mod supabase_test;
pub fn setup_log() {
static START: Once = Once::new();
START.call_once(|| {
let level = "debug";
let level = "trace";
let mut filters = vec![];
filters.push(format!("flowy_server={}", level));
std::env::set_var("RUST_LOG", filters.join(","));

View File

@ -10,7 +10,7 @@ use crate::supabase_test::util::{
};
#[tokio::test]
async fn supabase_create_workspace_test() {
async fn supabase_create_database_test() {
if get_supabase_ci_config().is_none() {
return;
}
@ -27,13 +27,13 @@ async fn supabase_create_workspace_test() {
for _i in 0..3 {
let row_id = uuid::Uuid::new_v4().to_string();
row_ids.push(row_id.clone());
let collab_object = CollabObject {
object_id: row_id,
uid: user.user_id,
ty: CollabType::DatabaseRow,
meta: Default::default(),
}
.with_workspace_id(user.latest_workspace.id.clone());
let collab_object = CollabObject::new(
user.user_id,
row_id,
CollabType::DatabaseRow,
user.latest_workspace.id.clone(),
"fake_device_id".to_string(),
);
collab_service
.send_update(&collab_object, 0, vec![1, 2, 3])
.await

View File

@ -39,13 +39,13 @@ async fn supabase_get_folder_test() {
let params = third_party_sign_up_param(uuid);
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
let collab_object = CollabObject {
object_id: user.latest_workspace.id.clone(),
uid: user.user_id,
ty: CollabType::Folder,
meta: Default::default(),
}
.with_workspace_id(user.latest_workspace.id.clone());
let collab_object = CollabObject::new(
user.user_id,
user.latest_workspace.id.clone(),
CollabType::Folder,
user.latest_workspace.id.clone(),
"fake_device_id".to_string(),
);
let doc = Doc::with_client_id(1);
let map = { doc.get_or_insert_map("map") };
@ -113,13 +113,13 @@ async fn supabase_duplicate_updates_test() {
let params = third_party_sign_up_param(uuid);
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
let collab_object = CollabObject {
object_id: user.latest_workspace.id.clone(),
uid: user.user_id,
ty: CollabType::Folder,
meta: Default::default(),
}
.with_workspace_id(user.latest_workspace.id.clone());
let collab_object = CollabObject::new(
user.user_id,
user.latest_workspace.id.clone(),
CollabType::Folder,
user.latest_workspace.id.clone(),
"fake_device_id".to_string(),
);
let doc = Doc::with_client_id(1);
let map = { doc.get_or_insert_map("map") };
let mut duplicated_updates = vec![];
@ -220,13 +220,13 @@ async fn supabase_diff_state_vector_test() {
let params = third_party_sign_up_param(uuid);
let user: SignUpResponse = user_service.sign_up(BoxAny::new(params)).await.unwrap();
let collab_object = CollabObject {
object_id: user.latest_workspace.id.clone(),
uid: user.user_id,
ty: CollabType::Folder,
meta: Default::default(),
}
.with_workspace_id(user.latest_workspace.id.clone());
let collab_object = CollabObject::new(
user.user_id,
user.latest_workspace.id.clone(),
CollabType::Folder,
user.latest_workspace.id.clone(),
"fake_device_id".to_string(),
);
let doc = Doc::with_client_id(1);
let map = { doc.get_or_insert_map("map") };
let array = { doc.get_or_insert_array("array") };

View File

@ -14,4 +14,4 @@ bytes = "1.0.1"
mime_guess = "2.0"
lib-infra = { path = "../../../shared-lib/lib-infra" }
url = "2.2.2"
flowy-error = { path = "../flowy-error", features = ["impl_from_reqwest"] }
flowy-error = { workspace = true, features = ["impl_from_reqwest"] }

View File

@ -6,24 +6,23 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
flowy-core = { path = "../flowy-core" }
flowy-user = { path = "../flowy-user"}
flowy-user-deps = { path = "../flowy-user-deps"}
flowy-net = { path = "../flowy-net"}
flowy-core = { workspace = true }
flowy-user = { workspace = true }
flowy-user-deps = { workspace = true }
flowy-folder2 = { path = "../flowy-folder2", features = ["test_helper"] }
flowy-folder-deps = { path = "../flowy-folder-deps" }
flowy-folder-deps = { workspace = true }
flowy-database2 = { path = "../flowy-database2" }
flowy-database-deps = { path = "../flowy-database-deps" }
flowy-database-deps = { workspace = true }
flowy-document2 = { path = "../flowy-document2" }
flowy-document-deps = { path = "../flowy-document-deps" }
flowy-encrypt = { path = "../flowy-encrypt" }
lib-dispatch = { path = "../lib-dispatch" }
flowy-document-deps = { workspace = true }
flowy-encrypt = { workspace = true }
lib-dispatch = { workspace = true }
lib-infra = { path = "../../../shared-lib/lib-infra" }
flowy-server = { path = "../flowy-server" }
flowy-server-config = { path = "../flowy-server-config" }
flowy-notification = { path = "../flowy-notification" }
flowy-server-config = { workspace = true }
flowy-notification = { workspace = true }
anyhow = "1.0.71"
flowy-storage = { path = "../flowy-storage" }
flowy-storage = { workspace = true }
serde = { version = "1.0", features = ["derive"] }
serde_json = {version = "1.0"}
@ -46,6 +45,7 @@ collab-document = { version = "0.1.0" }
collab-folder = { version = "0.1.0" }
collab-database = { version = "0.1.0" }
collab-plugins = { version = "0.1.0" }
collab-define = { version = "0.1.0" }
assert-json-diff = "2.0.2"
tokio-postgres = { version = "0.7.8" }
zip = "0.6.6"

View File

@ -527,7 +527,7 @@ async fn update_date_cell_event_test() {
let error = test
.update_date_cell(DateChangesetPB {
cell_id: cell_path,
date: Some(timestamp.clone()),
date: Some(timestamp),
time: None,
include_time: None,
clear_flag: None,

View File

@ -5,7 +5,7 @@ use collab::core::collab::MutexCollab;
use collab::core::origin::CollabOrigin;
use collab::preclude::updates::decoder::Decode;
use collab::preclude::{merge_updates_v1, JsonValue, Update};
use collab_plugins::cloud_storage::CollabType;
use collab_define::CollabType;
use flowy_database2::entities::{DatabasePB, DatabaseViewIdPB, RepeatedDatabaseSnapshotPB};
use flowy_database2::event_map::DatabaseEvent::*;

View File

@ -2,6 +2,7 @@ use std::collections::HashMap;
use assert_json_diff::assert_json_eq;
use collab_database::rows::database_row_document_id_from_row_id;
use collab_define::CollabType;
use collab_document::blocks::DocumentData;
use collab_folder::core::FolderData;
use nanoid::nanoid;
@ -9,7 +10,7 @@ use serde_json::json;
use flowy_core::DEFAULT_NAME;
use flowy_encrypt::decrypt_text;
use flowy_server::supabase::define::{CollabType, USER_EMAIL, USER_UUID};
use flowy_server::supabase::define::{USER_EMAIL, USER_UUID};
use flowy_test::document::document_event::DocumentEventTest;
use flowy_test::event_builder::EventBuilder;
use flowy_test::FlowyCoreTest;

View File

@ -7,12 +7,12 @@ edition = "2021"
[dependencies]
lib-infra = { path = "../../../shared-lib/lib-infra" }
flowy-error = { path = "../flowy-error" }
flowy-error = { workspace = true }
uuid = { version = "1.3.3", features = ["v4"] }
serde = { version = "1.0", features = ["derive"] }
collab-define = { version = "0.1.0" }
serde_json = {version = "1.0"}
serde_json = { version = "1.0"}
serde_repr = "0.1"
chrono = { version = "0.4.22", default-features = false, features = ["clock", "serde"] }
chrono = { version = "0.4.27", default-features = false, features = ["clock", "serde"] }
anyhow = "1.0.71"
tokio = { version = "1.26", features = ["sync"] }

View File

@ -7,22 +7,22 @@ edition = "2018"
[dependencies]
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
flowy-sqlite = { path = "../flowy-sqlite", optional = true }
flowy-encrypt = { path = "../flowy-encrypt" }
flowy-error = { path = "../flowy-error", features = ["impl_from_sqlite", "impl_from_dispatch_error"] }
flowy-folder-deps = { path = "../flowy-folder-deps" }
flowy-sqlite = { workspace = true, optional = true }
flowy-encrypt = { workspace = true }
flowy-error = { workspace = true, features = ["impl_from_sqlite", "impl_from_dispatch_error"] }
flowy-folder-deps = { workspace = true }
lib-infra = { path = "../../../shared-lib/lib-infra" }
flowy-notification = { path = "../flowy-notification" }
flowy-server-config = { path = "../flowy-server-config" }
lib-dispatch = { path = "../lib-dispatch" }
appflowy-integrate = { version = "0.1.0" }
flowy-notification = { workspace = true }
flowy-server-config = { workspace = true }
lib-dispatch = { workspace = true }
collab-integrate = { workspace = true }
collab = { version = "0.1.0" }
collab-folder = { version = "0.1.0" }
collab-document = { version = "0.1.0" }
collab-database = { version = "0.1.0" }
collab-user = { version = "0.1.0" }
collab-define = { version = "0.1.0" }
flowy-user-deps = { path = "../flowy-user-deps" }
flowy-user-deps = { workspace = true }
anyhow = "1.0.75"
tracing = { version = "0.1", features = ["log"] }
@ -43,7 +43,7 @@ validator = "0.16.0"
unicode-segmentation = "1.10"
fancy-regex = "0.11.0"
uuid = { version = "1.3.3", features = [ "v4"] }
chrono = { version = "0.4.22", default-features = false, features = ["clock"] }
chrono = { version = "0.4.27", default-features = false, features = ["clock"] }
base64 = "^0.21"
[dev-dependencies]

View File

@ -453,6 +453,7 @@ pub async fn reset_workspace_handler(
"The workspace id is empty",
));
}
manager.reset_workspace(reset_pb).await?;
let session = manager.get_session()?;
manager.reset_workspace(reset_pb, session.device_id).await?;
Ok(())
}

View File

@ -1,13 +1,13 @@
use std::string::ToString;
use std::sync::{Arc, Weak};
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
use appflowy_integrate::RocksCollabDB;
use collab_user::core::MutexUserAwareness;
use serde_json::Value;
use tokio::sync::{Mutex, RwLock};
use uuid::Uuid;
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use collab_integrate::RocksCollabDB;
use flowy_error::{internal_error, ErrorCode, FlowyResult};
use flowy_sqlite::kv::StorePreferences;
use flowy_sqlite::schema::user_table;
@ -597,6 +597,7 @@ impl UserManager {
if let Err(err) = sync_user_data_to_cloud(
self.cloud_services.get_user_service()?,
"",
new_user,
&new_collab_db,
)

View File

@ -1,12 +1,12 @@
use std::sync::Arc;
use appflowy_integrate::{RocksCollabDB, YrsDocAction};
use collab::core::collab::MutexCollab;
use collab::core::origin::{CollabClient, CollabOrigin};
use collab_document::document::Document;
use collab_document::document_data::default_document_data;
use collab_folder::core::Folder;
use collab_integrate::{RocksCollabDB, YrsDocAction};
use flowy_error::{internal_error, FlowyResult};
use crate::migrations::migration::UserDataMigration;

View File

@ -3,7 +3,6 @@ use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use anyhow::anyhow;
use appflowy_integrate::{PersistenceError, RocksCollabDB, YrsDocAction};
use collab::core::collab::MutexCollab;
use collab::core::origin::{CollabClient, CollabOrigin};
use collab::preclude::Collab;
@ -15,6 +14,7 @@ use collab_database::user::DatabaseWithViewsArray;
use collab_folder::core::Folder;
use parking_lot::{Mutex, RwLock};
use collab_integrate::{PersistenceError, RocksCollabDB, YrsDocAction};
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_folder_deps::cloud::gen_view_id;

View File

@ -1,9 +1,9 @@
use std::sync::Arc;
use appflowy_integrate::RocksCollabDB;
use chrono::NaiveDateTime;
use diesel::{RunQueryDsl, SqliteConnection};
use collab_integrate::RocksCollabDB;
use flowy_error::FlowyResult;
use flowy_sqlite::schema::user_data_migration_records;
use flowy_sqlite::ConnectionPool;

View File

@ -4,7 +4,6 @@ use std::pin::Pin;
use std::sync::Arc;
use anyhow::{anyhow, Error};
use appflowy_integrate::{PersistenceError, RocksCollabDB, YrsDocAction};
use collab::core::collab::MutexCollab;
use collab::preclude::Collab;
use collab_database::database::get_database_row_ids;
@ -14,6 +13,7 @@ use collab_define::{CollabObject, CollabType};
use collab_folder::core::{Folder, View, ViewLayout};
use parking_lot::Mutex;
use collab_integrate::{PersistenceError, RocksCollabDB, YrsDocAction};
use flowy_error::FlowyResult;
use flowy_user_deps::cloud::UserCloudService;
@ -22,16 +22,27 @@ use crate::migrations::MigrationUser;
#[tracing::instrument(level = "info", skip_all, err)]
pub async fn sync_user_data_to_cloud(
user_service: Arc<dyn UserCloudService>,
device_id: &str,
new_user: &MigrationUser,
collab_db: &Arc<RocksCollabDB>,
) -> FlowyResult<()> {
let workspace_id = new_user.session.user_workspace.id.clone();
let uid = new_user.session.user_id;
let folder = Arc::new(sync_folder(uid, &workspace_id, collab_db, user_service.clone()).await?);
let folder = Arc::new(
sync_folder(
uid,
&workspace_id,
device_id,
collab_db,
user_service.clone(),
)
.await?,
);
let database_records = sync_database_views(
uid,
&workspace_id,
device_id,
&new_user.session.user_workspace.database_views_aggregate_id,
collab_db,
user_service.clone(),
@ -46,6 +57,7 @@ pub async fn sync_user_data_to_cloud(
folder.clone(),
database_records.clone(),
workspace_id.to_string(),
device_id.to_string(),
view,
collab_db.clone(),
user_service.clone(),
@ -63,6 +75,7 @@ fn sync_views(
folder: Arc<MutexFolder>,
database_records: Vec<Arc<DatabaseWithViews>>,
workspace_id: String,
device_id: String,
view: Arc<View>,
collab_db: Arc<RocksCollabDB>,
user_service: Arc<dyn UserCloudService>,
@ -77,8 +90,13 @@ fn sync_views(
object_id
);
let collab_object =
CollabObject::new(uid, object_id, collab_type).with_workspace_id(workspace_id.to_string());
let collab_object = CollabObject::new(
uid,
object_id,
collab_type,
workspace_id.to_string(),
device_id.clone(),
);
match view.layout {
ViewLayout::Document => {
@ -108,8 +126,13 @@ fn sync_views(
tracing::debug!("sync row: {}", row_id);
let document_id = database_row_document_id_from_row_id(&row_id);
let database_row_collab_object = CollabObject::new(uid, row_id, CollabType::DatabaseRow)
.with_workspace_id(workspace_id.to_string());
let database_row_collab_object = CollabObject::new(
uid,
row_id,
CollabType::DatabaseRow,
workspace_id.to_string(),
device_id.clone(),
);
let database_row_update =
get_collab_init_update(uid, &database_row_collab_object, &collab_db)?;
tracing::info!(
@ -122,8 +145,13 @@ fn sync_views(
.create_collab_object(&database_row_collab_object, database_row_update)
.await;
let database_row_document = CollabObject::new(uid, document_id, CollabType::Document)
.with_workspace_id(workspace_id.to_string());
let database_row_document = CollabObject::new(
uid,
document_id,
CollabType::Document,
workspace_id.to_string(),
device_id.to_string(),
);
// sync document in the row if exist
if let Ok(document_update) =
get_collab_init_update(uid, &database_row_document, &collab_db)
@ -149,6 +177,7 @@ fn sync_views(
folder.clone(),
database_records.clone(),
workspace_id.clone(),
device_id.to_string(),
child_view,
collab_db.clone(),
user_service.clone(),
@ -210,6 +239,7 @@ fn get_database_init_update(
async fn sync_folder(
uid: i64,
workspace_id: &str,
device_id: &str,
collab_db: &Arc<RocksCollabDB>,
user_service: Arc<dyn UserCloudService>,
) -> Result<MutexFolder, Error> {
@ -231,8 +261,13 @@ async fn sync_folder(
)
};
let collab_object = CollabObject::new(uid, workspace_id.to_string(), CollabType::Folder)
.with_workspace_id(workspace_id.to_string());
let collab_object = CollabObject::new(
uid,
workspace_id.to_string(),
CollabType::Folder,
workspace_id.to_string(),
device_id.to_string(),
);
tracing::info!(
"sync object: {} with update: {}",
collab_object,
@ -251,6 +286,7 @@ async fn sync_folder(
async fn sync_database_views(
uid: i64,
workspace_id: &str,
device_id: &str,
database_views_aggregate_id: &str,
collab_db: &Arc<RocksCollabDB>,
user_service: Arc<dyn UserCloudService>,
@ -259,8 +295,9 @@ async fn sync_database_views(
uid,
database_views_aggregate_id.to_string(),
CollabType::WorkspaceDatabase,
)
.with_workspace_id(workspace_id.to_string());
workspace_id.to_string(),
device_id.to_string(),
);
// Use the temporary result to short the lifetime of the TransactionMut
let result = {

View File

@ -1,10 +1,10 @@
use std::path::PathBuf;
use std::{collections::HashMap, sync::Arc, time::Duration};
use appflowy_integrate::RocksCollabDB;
use lazy_static::lazy_static;
use parking_lot::RwLock;
use collab_integrate::RocksCollabDB;
use flowy_error::{ErrorCode, FlowyError};
use flowy_sqlite::schema::user_workspace_table;
use flowy_sqlite::ConnectionPool;

View File

@ -1,11 +1,11 @@
use std::sync::{Arc, Weak};
use appflowy_integrate::RocksCollabDB;
use collab::core::collab::{CollabRawData, MutexCollab};
use collab_define::reminder::Reminder;
use collab_define::CollabType;
use collab_user::core::{MutexUserAwareness, UserAwareness};
use collab_integrate::RocksCollabDB;
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use crate::entities::ReminderPB;

View File

@ -89,10 +89,18 @@ impl UserManager {
/// Reset the remote workspace using local workspace data. This is useful when a user wishes to
/// open a workspace on a new device that hasn't fully synchronized with the server.
pub async fn reset_workspace(&self, reset: ResetWorkspacePB) -> FlowyResult<()> {
let collab_object =
CollabObject::new(reset.uid, reset.workspace_id.clone(), CollabType::Folder)
.with_workspace_id(reset.workspace_id);
pub async fn reset_workspace(
&self,
reset: ResetWorkspacePB,
device_id: String,
) -> FlowyResult<()> {
let collab_object = CollabObject::new(
reset.uid,
reset.workspace_id.clone(),
CollabType::Folder,
reset.workspace_id.clone(),
device_id,
);
self
.cloud_services
.get_user_service()?

View File

@ -0,0 +1,30 @@
#!/bin/bash
# Ensure a new revision ID is provided
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <new_revision_id>"
exit 1
fi
NEW_REV="$1"
echo "New revision: $NEW_REV"
directories=("rust-lib" "appflowy_tauri/src-tauri")
for dir in "${directories[@]}"; do
echo "Updating $dir"
cd "$dir"
sed -i.bak "/^collab[[:alnum:]-]*[[:space:]]*=/s/rev = \"[a-fA-F0-9]\{6,40\}\"/rev = \"$NEW_REV\"/g" Cargo.toml
# Detect changed crates
collab_crates=($(grep -E '^collab[a-zA-Z0-9_-]* =' Cargo.toml | awk -F'=' '{print $1}' | tr -d ' '))
# Update only the changed crates in Cargo.lock
for crate in "${collab_crates[@]}"; do
echo "Updating $crate"
cargo update -p $crate
done
cd ..
done

330
shared-lib/Cargo.lock generated
View File

@ -46,29 +46,12 @@ dependencies = [
"syn 1.0.109",
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "basic-toml"
version = "0.1.2"
@ -99,15 +82,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -133,12 +107,6 @@ version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.4.0"
@ -327,22 +295,13 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690"
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]]
name = "digest"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [
"block-buffer 0.10.4",
"block-buffer",
"crypto-common",
]
@ -358,19 +317,6 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "env_logger"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "errno"
version = "0.3.1"
@ -479,104 +425,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
[[package]]
name = "futures-executor"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
[[package]]
name = "futures-macro"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "futures-sink"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
[[package]]
name = "futures-task"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
[[package]]
name = "futures-util"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.4"
@ -669,23 +523,6 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "http"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "httparse"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
[[package]]
name = "humansize"
version = "2.1.3"
@ -695,12 +532,6 @@ dependencies = [
"libm",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "iana-time-zone"
version = "0.1.53"
@ -725,16 +556,6 @@ dependencies = [
"cxx-build",
]
[[package]]
name = "idna"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "ignore"
version = "0.4.20"
@ -851,32 +672,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "lib-ws"
version = "0.1.0"
dependencies = [
"bytes",
"dashmap",
"env_logger",
"futures",
"futures-channel",
"futures-core",
"futures-util",
"lib-infra",
"log",
"parking_lot",
"pin-project",
"protobuf",
"serde",
"serde_json",
"serde_repr",
"strum_macros",
"tokio",
"tokio-tungstenite",
"tracing",
"url",
]
[[package]]
name = "libc"
version = "0.2.139"
@ -981,12 +776,6 @@ version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "os_pipe"
version = "0.9.2"
@ -1188,12 +977,6 @@ version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "ppv-lite86"
version = "0.2.15"
@ -1523,30 +1306,6 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_repr"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "sha-1"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
dependencies = [
"block-buffer 0.9.0",
"cfg-if",
"cpufeatures",
"digest 0.9.0",
"opaque-debug",
]
[[package]]
name = "sha2"
version = "0.10.6"
@ -1555,7 +1314,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.10.6",
"digest",
]
[[package]]
@ -1579,12 +1338,6 @@ 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"
@ -1734,21 +1487,6 @@ dependencies = [
"once_cell",
]
[[package]]
name = "tinyvec"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.26.0"
@ -1780,19 +1518,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "tokio-tungstenite"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8"
dependencies = [
"futures-util",
"log",
"pin-project",
"tokio",
"tungstenite",
]
[[package]]
name = "toml"
version = "0.5.11"
@ -1850,25 +1575,6 @@ dependencies = [
"termcolor",
]
[[package]]
name = "tungstenite"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5"
dependencies = [
"base64",
"byteorder",
"bytes",
"http",
"httparse",
"log",
"rand 0.8.5",
"sha-1",
"thiserror",
"url",
"utf-8",
]
[[package]]
name = "typenum"
version = "1.14.0"
@ -1940,27 +1646,12 @@ dependencies = [
"unic-common",
]
[[package]]
name = "unicode-bidi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
[[package]]
name = "unicode-ident"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
[[package]]
name = "unicode-normalization"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
@ -1973,23 +1664,6 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "url"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "version_check"
version = "0.9.3"

View File

@ -1,7 +1,6 @@
[workspace]
members = [
"lib-ot",
"lib-ws",
"lib-infra",
"flowy-derive",
"flowy-ast",

View File

@ -6,7 +6,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = { version = "0.4.22", default-features = false, features = ["clock"] }
chrono = { version = "0.4.27", default-features = false, features = ["clock"] }
bytes = { version = "1.4" }
pin-project = "1.0.12"
futures-core = { version = "0.3" }

View File

@ -1,32 +0,0 @@
[package]
name = "lib-ws"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde_repr = "0.1"
serde = "1.0"
serde_json = {version = "1.0"}
lib-infra = { path = "../lib-infra" }
tokio-tungstenite = "0.15"
futures-util = "0.3.26"
futures-channel = "0.3.26"
tokio = { version = "1.26", features = ["full"]}
futures = "0.3.26"
bytes = "1.4"
pin-project = "1.0"
futures-core = { version = "0.3", default-features = false }
url = "2.3.1"
log = "0.4"
tracing = { version = "0.1", features = ["log"] }
protobuf = {version = "2.28.0"}
strum_macros = "0.21"
parking_lot = "0.12.1"
dashmap = "5"
[dev-dependencies]
tokio = { version = "1.26", features = ["full"]}
env_logger = "0.8.4"

View File

@ -1,223 +0,0 @@
#![allow(clippy::all)]
use crate::{
errors::{internal_error, WSError},
MsgReceiver, MsgSender,
};
use futures_core::{future::BoxFuture, ready};
use futures_util::{FutureExt, StreamExt};
use pin_project::pin_project;
use std::{
fmt,
future::Future,
pin::Pin,
task::{Context, Poll},
};
use tokio::net::TcpStream;
use tokio_tungstenite::{
connect_async,
tungstenite::{handshake::client::Response, Error, Message},
MaybeTlsStream, WebSocketStream,
};
type WsConnectResult = Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), Error>;
#[pin_project]
pub struct WSConnectionFuture {
msg_tx: Option<MsgSender>,
ws_rx: Option<MsgReceiver>,
#[pin]
fut: Pin<Box<dyn Future<Output = WsConnectResult> + Send + Sync>>,
}
impl WSConnectionFuture {
pub fn new(msg_tx: MsgSender, ws_rx: MsgReceiver, addr: String) -> Self {
WSConnectionFuture {
msg_tx: Some(msg_tx),
ws_rx: Some(ws_rx),
fut: Box::pin(async move { connect_async(&addr).await }),
}
}
}
impl Future for WSConnectionFuture {
type Output = Result<WSStream, WSError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// [[pin]]
// poll async function. The following methods not work.
// 1.
// let f = connect_async("");
// pin_mut!(f);
// ready!(Pin::new(&mut a).poll(cx))
//
// 2.ready!(Pin::new(&mut Box::pin(connect_async(""))).poll(cx))
//
// An async method calls poll multiple times and might return to the executor. A
// single poll call can only return to the executor once and will get
// resumed through another poll invocation. the connect_async call multiple time
// from the beginning. So I use fut to hold the future and continue to
// poll it. (Fix me if i was wrong)
loop {
return match ready!(self.as_mut().project().fut.poll(cx)) {
Ok((stream, _)) => {
tracing::debug!("[WebSocket]: connect success");
let (msg_tx, ws_rx) = (
self
.msg_tx
.take()
.expect("[WebSocket]: WSConnection should be call once "),
self
.ws_rx
.take()
.expect("[WebSocket]: WSConnection should be call once "),
);
Poll::Ready(Ok(WSStream::new(msg_tx, ws_rx, stream)))
},
Err(error) => {
tracing::debug!("[WebSocket]: ❌ connect failed: {:?}", error);
Poll::Ready(Err(error.into()))
},
};
}
}
}
type Fut = BoxFuture<'static, Result<(), WSError>>;
#[pin_project]
pub struct WSStream {
#[allow(dead_code)]
msg_tx: MsgSender,
#[pin]
inner: Option<(Fut, Fut)>,
}
impl WSStream {
pub fn new(
msg_tx: MsgSender,
ws_rx: MsgReceiver,
stream: WebSocketStream<MaybeTlsStream<TcpStream>>,
) -> Self {
let (ws_write, ws_read) = stream.split();
Self {
msg_tx: msg_tx.clone(),
inner: Some((
Box::pin(async move {
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let read = async {
ws_read
.for_each(|message| async {
match tx.send(send_message(msg_tx.clone(), message)) {
Ok(_) => {},
Err(e) => log::error!("[WebSocket]: WSStream sender closed unexpectedly: {} ", e),
}
})
.await;
Ok(())
};
let read_ret = async {
loop {
match rx.recv().await {
None => {
return Err(
WSError::internal()
.context("[WebSocket]: WSStream receiver closed unexpectedly"),
);
},
Some(result) => {
if result.is_err() {
return result;
}
},
}
}
};
futures::pin_mut!(read);
futures::pin_mut!(read_ret);
return tokio::select! {
result = read => result,
result = read_ret => result,
};
}),
Box::pin(async move {
let result = ws_rx
.map(Ok)
.forward(ws_write)
.await
.map_err(internal_error);
result
}),
)),
}
}
}
impl fmt::Debug for WSStream {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WSStream").finish()
}
}
impl Future for WSStream {
type Output = Result<(), WSError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let (mut ws_read, mut ws_write) = self.inner.take().unwrap();
match ws_read.poll_unpin(cx) {
Poll::Ready(l) => Poll::Ready(l),
Poll::Pending => {
//
match ws_write.poll_unpin(cx) {
Poll::Ready(r) => Poll::Ready(r),
Poll::Pending => {
self.inner = Some((ws_read, ws_write));
Poll::Pending
},
}
},
}
}
}
fn send_message(msg_tx: MsgSender, message: Result<Message, Error>) -> Result<(), WSError> {
match message {
Ok(Message::Binary(bytes)) => msg_tx
.unbounded_send(Message::Binary(bytes))
.map_err(internal_error),
Ok(_) => Ok(()),
Err(e) => Err(WSError::internal().context(e)),
}
}
#[allow(dead_code)]
pub struct Retry<F> {
f: F,
#[allow(dead_code)]
retry_time: usize,
addr: String,
}
impl<F> Retry<F>
where
F: Fn(&str),
{
#[allow(dead_code)]
pub fn new(addr: &str, f: F) -> Self {
Self {
f,
retry_time: 3,
addr: addr.to_owned(),
}
}
}
impl<F> Future for Retry<F>
where
F: Fn(&str),
{
type Output = ();
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
(self.f)(&self.addr);
Poll::Ready(())
}
}

View File

@ -1,101 +0,0 @@
use futures_channel::mpsc::TrySendError;
use serde::{Deserialize, Serialize};
use serde_repr::*;
use std::fmt::Debug;
use strum_macros::Display;
use tokio::sync::oneshot::error::RecvError;
use tokio_tungstenite::tungstenite::{http::StatusCode, Message};
use url::ParseError;
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct WSError {
pub code: ErrorCode,
pub msg: String,
}
macro_rules! static_ws_error {
($name:ident, $status:expr) => {
#[allow(non_snake_case, missing_docs)]
pub fn $name() -> WSError {
WSError {
code: $status,
msg: format!("{}", $status),
}
}
};
}
impl WSError {
#[allow(dead_code)]
pub(crate) fn new(code: ErrorCode) -> WSError {
WSError {
code,
msg: "".to_string(),
}
}
pub fn context<T: Debug>(mut self, error: T) -> Self {
self.msg = format!("{:?}", error);
self
}
static_ws_error!(internal, ErrorCode::InternalError);
static_ws_error!(unsupported_message, ErrorCode::UnsupportedMessage);
static_ws_error!(unauthorized, ErrorCode::Unauthorized);
}
pub(crate) fn internal_error<T>(e: T) -> WSError
where
T: std::fmt::Debug,
{
WSError::internal().context(e)
}
#[derive(Debug, Clone, Serialize_repr, Deserialize_repr, Display, PartialEq, Eq)]
#[repr(u8)]
#[derive(Default)]
pub enum ErrorCode {
#[default]
InternalError = 0,
UnsupportedMessage = 1,
Unauthorized = 2,
}
impl std::convert::From<url::ParseError> for WSError {
fn from(error: ParseError) -> Self {
WSError::internal().context(error)
}
}
impl std::convert::From<protobuf::ProtobufError> for WSError {
fn from(error: protobuf::ProtobufError) -> Self {
WSError::internal().context(error)
}
}
impl std::convert::From<futures_channel::mpsc::TrySendError<Message>> for WSError {
fn from(error: TrySendError<Message>) -> Self {
WSError::internal().context(error)
}
}
impl std::convert::From<RecvError> for WSError {
fn from(error: RecvError) -> Self {
WSError::internal().context(error)
}
}
impl std::convert::From<tokio_tungstenite::tungstenite::Error> for WSError {
fn from(error: tokio_tungstenite::tungstenite::Error) -> Self {
match error {
tokio_tungstenite::tungstenite::Error::Http(response) => {
if response.status() == StatusCode::UNAUTHORIZED {
WSError::unauthorized()
} else {
WSError::internal().context(response)
}
},
_ => WSError::internal().context(error),
}
}
}

View File

@ -1,7 +0,0 @@
pub mod connect;
pub mod errors;
mod msg;
mod ws;
pub use msg::*;
pub use ws::*;

View File

@ -1,46 +0,0 @@
use serde::{Deserialize, Serialize};
use serde_repr::*;
use tokio_tungstenite::tungstenite::Message as TokioMessage;
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct WebSocketRawMessage {
pub channel: WSChannel,
pub data: Vec<u8>,
}
impl WebSocketRawMessage {
pub fn to_bytes(&self) -> Vec<u8> {
serde_json::to_vec(&self).unwrap_or_default()
}
pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Self {
serde_json::from_slice(bytes.as_ref()).unwrap_or_default()
}
}
// The lib-ws crate should not contain business logic.So WSChannel should be removed into another place.
#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, Eq, PartialEq, Hash)]
#[repr(u8)]
#[derive(Default)]
pub enum WSChannel {
#[default]
Document = 0,
Folder = 1,
Database = 2,
}
impl ToString for WSChannel {
fn to_string(&self) -> String {
match self {
WSChannel::Document => "0".to_string(),
WSChannel::Folder => "1".to_string(),
WSChannel::Database => "2".to_string(),
}
}
}
impl std::convert::From<WebSocketRawMessage> for TokioMessage {
fn from(msg: WebSocketRawMessage) -> Self {
TokioMessage::Binary(msg.to_bytes())
}
}

View File

@ -1,435 +0,0 @@
#![allow(clippy::type_complexity)]
use crate::{
connect::{WSConnectionFuture, WSStream},
errors::WSError,
WSChannel, WebSocketRawMessage,
};
use dashmap::DashMap;
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures_core::{ready, Stream};
use lib_infra::retry::{Action, FixedInterval, Retry};
use pin_project::pin_project;
use std::{
fmt::Formatter,
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll},
time::Duration,
};
use tokio::sync::{broadcast, oneshot, RwLock};
use tokio_tungstenite::tungstenite::{
protocol::{frame::coding::CloseCode, CloseFrame},
Message,
};
pub type MsgReceiver = UnboundedReceiver<Message>;
pub type MsgSender = UnboundedSender<Message>;
type Handlers = DashMap<WSChannel, Arc<dyn WSMessageReceiver>>;
pub trait WSMessageReceiver: Sync + Send + 'static {
fn source(&self) -> WSChannel;
fn receive_message(&self, msg: WebSocketRawMessage);
}
pub struct WSController {
handlers: Handlers,
addr: Arc<RwLock<Option<String>>>,
sender: Arc<RwLock<Option<Arc<WSSender>>>>,
conn_state_notify: Arc<RwLock<WSConnectStateNotifier>>,
}
impl std::fmt::Display for WSController {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("WebSocket")
}
}
impl std::default::Default for WSController {
fn default() -> Self {
Self {
handlers: DashMap::new(),
addr: Arc::new(RwLock::new(None)),
sender: Arc::new(RwLock::new(None)),
conn_state_notify: Arc::new(RwLock::new(WSConnectStateNotifier::default())),
}
}
}
impl WSController {
pub fn new() -> Self {
WSController::default()
}
pub fn add_ws_message_receiver(
&self,
handler: Arc<dyn WSMessageReceiver>,
) -> Result<(), WSError> {
let source = handler.source();
if self.handlers.contains_key(&source) {
log::error!("{:?} is already registered", source);
}
self.handlers.insert(source, handler);
Ok(())
}
pub async fn start(&self, addr: String) -> Result<(), WSError> {
*self.addr.write().await = Some(addr.clone());
let strategy = FixedInterval::from_millis(5000).take(3);
self.connect(addr, strategy).await
}
pub async fn stop(&self) {
if self
.conn_state_notify
.read()
.await
.conn_state
.is_connected()
{
tracing::trace!("[{}] stop", self);
self
.conn_state_notify
.write()
.await
.update_state(WSConnectState::Disconnected);
}
}
async fn connect<T, I>(&self, addr: String, strategy: T) -> Result<(), WSError>
where
T: IntoIterator<IntoIter = I, Item = Duration>,
I: Iterator<Item = Duration> + Send + 'static,
{
let mut conn_state_notify = self.conn_state_notify.write().await;
let conn_state = conn_state_notify.conn_state.clone();
if conn_state.is_connected() || conn_state.is_connecting() {
return Ok(());
}
let (ret, rx) = oneshot::channel::<Result<(), WSError>>();
*self.addr.write().await = Some(addr.clone());
let action = WSConnectAction {
addr,
handlers: self.handlers.clone(),
};
let retry = Retry::new(strategy, action);
conn_state_notify.update_state(WSConnectState::Connecting);
drop(conn_state_notify);
let cloned_conn_state = self.conn_state_notify.clone();
let cloned_sender = self.sender.clone();
tracing::trace!("[{}] start connecting", self);
tokio::spawn(async move {
match retry.await {
Ok(result) => {
let WSConnectResult {
stream,
handlers_fut,
sender,
} = result;
cloned_conn_state
.write()
.await
.update_state(WSConnectState::Connected);
*cloned_sender.write().await = Some(Arc::new(sender));
let _ = ret.send(Ok(()));
spawn_stream_and_handlers(stream, handlers_fut).await;
},
Err(e) => {
cloned_conn_state
.write()
.await
.update_state(WSConnectState::Disconnected);
let _ = ret.send(Err(WSError::internal().context(e)));
},
}
});
rx.await?
}
pub async fn retry(&self, count: usize) -> Result<(), WSError> {
if !self
.conn_state_notify
.read()
.await
.conn_state
.is_disconnected()
{
return Ok(());
}
tracing::trace!("[WebSocket]: retry connect...");
let strategy = FixedInterval::from_millis(5000).take(count);
let addr = self
.addr
.read()
.await
.as_ref()
.expect("Retry web socket connection failed, should call start_connect first")
.clone();
self.connect(addr, strategy).await
}
pub async fn subscribe_state(&self) -> broadcast::Receiver<WSConnectState> {
self.conn_state_notify.read().await.notify.subscribe()
}
pub async fn ws_message_sender(&self) -> Result<Option<Arc<WSSender>>, WSError> {
let sender = self.sender.read().await.clone();
match sender {
None => match self.conn_state_notify.read().await.conn_state {
WSConnectState::Disconnected => {
let msg = "WebSocket is disconnected";
Err(WSError::internal().context(msg))
},
_ => Ok(None),
},
Some(sender) => Ok(Some(sender)),
}
}
}
async fn spawn_stream_and_handlers(stream: WSStream, handlers: WSHandlerFuture) {
tokio::select! {
result = stream => {
if let Err(e) = result {
tracing::error!("WSStream error: {:?}", e);
}
},
result = handlers => tracing::debug!("handlers completed {:?}", result),
};
}
#[pin_project]
pub struct WSHandlerFuture {
#[pin]
msg_rx: MsgReceiver,
handlers: Handlers,
}
impl WSHandlerFuture {
fn new(handlers: Handlers, msg_rx: MsgReceiver) -> Self {
Self { msg_rx, handlers }
}
fn handler_ws_message(&self, message: Message) {
if let Message::Binary(bytes) = message {
self.handle_binary_message(bytes)
}
}
fn handle_binary_message(&self, bytes: Vec<u8>) {
let msg = WebSocketRawMessage::from_bytes(bytes);
match self.handlers.get(&msg.channel) {
None => log::error!("Can't find any handler for message: {:?}", msg),
Some(handler) => handler.receive_message(msg),
}
}
}
impl Future for WSHandlerFuture {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
loop {
match ready!(self.as_mut().project().msg_rx.poll_next(cx)) {
None => {
return Poll::Ready(());
},
Some(message) => self.handler_ws_message(message),
}
}
}
}
#[derive(Debug, Clone)]
pub struct WSSender(MsgSender);
impl WSSender {
pub fn send_msg<T: Into<WebSocketRawMessage>>(&self, msg: T) -> Result<(), WSError> {
let msg = msg.into();
self
.0
.unbounded_send(msg.into())
.map_err(|e| WSError::internal().context(e))?;
Ok(())
}
pub fn send_text(&self, source: &WSChannel, text: &str) -> Result<(), WSError> {
let msg = WebSocketRawMessage {
channel: source.clone(),
data: text.as_bytes().to_vec(),
};
self.send_msg(msg)
}
pub fn send_binary(&self, source: &WSChannel, bytes: Vec<u8>) -> Result<(), WSError> {
let msg = WebSocketRawMessage {
channel: source.clone(),
data: bytes,
};
self.send_msg(msg)
}
pub fn send_disconnect(&self, reason: &str) -> Result<(), WSError> {
let frame = CloseFrame {
code: CloseCode::Normal,
reason: reason.to_owned().into(),
};
let msg = Message::Close(Some(frame));
self
.0
.unbounded_send(msg)
.map_err(|e| WSError::internal().context(e))?;
Ok(())
}
}
struct WSConnectAction {
addr: String,
handlers: Handlers,
}
impl Action for WSConnectAction {
type Future = Pin<Box<dyn Future<Output = Result<Self::Item, Self::Error>> + Send + Sync>>;
type Item = WSConnectResult;
type Error = WSError;
fn run(&mut self) -> Self::Future {
let addr = self.addr.clone();
let handlers = self.handlers.clone();
Box::pin(WSConnectActionFut::new(addr, handlers))
}
}
struct WSConnectResult {
stream: WSStream,
handlers_fut: WSHandlerFuture,
sender: WSSender,
}
#[pin_project]
struct WSConnectActionFut {
addr: String,
#[pin]
conn: WSConnectionFuture,
handlers_fut: Option<WSHandlerFuture>,
sender: Option<WSSender>,
}
impl WSConnectActionFut {
fn new(addr: String, handlers: Handlers) -> Self {
// Stream User
// ┌───────────────┐ ┌──────────────┐
// ┌──────┐ │ ┌─────────┐ │ ┌────────┐ │ ┌────────┐ │
// │Server│──────┼─▶│ ws_read │──┼───▶│ msg_tx │───┼─▶│ msg_rx │ │
// └──────┘ │ └─────────┘ │ └────────┘ │ └────────┘ │
// ▲ │ │ │ │
// │ │ ┌─────────┐ │ ┌────────┐ │ ┌────────┐ │
// └─────────┼──│ws_write │◀─┼────│ ws_rx │◀──┼──│ ws_tx │ │
// │ └─────────┘ │ └────────┘ │ └────────┘ │
// └───────────────┘ └──────────────┘
let (msg_tx, msg_rx) = futures_channel::mpsc::unbounded();
let (ws_tx, ws_rx) = futures_channel::mpsc::unbounded();
let sender = WSSender(ws_tx);
let handlers_fut = WSHandlerFuture::new(handlers, msg_rx);
let conn = WSConnectionFuture::new(msg_tx, ws_rx, addr.clone());
Self {
addr,
conn,
handlers_fut: Some(handlers_fut),
sender: Some(sender),
}
}
}
impl Future for WSConnectActionFut {
type Output = Result<WSConnectResult, WSError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.project();
match ready!(this.conn.as_mut().poll(cx)) {
Ok(stream) => {
let handlers_fut = this.handlers_fut.take().expect("Only take once");
let sender = this.sender.take().expect("Only take once");
Poll::Ready(Ok(WSConnectResult {
stream,
handlers_fut,
sender,
}))
},
Err(e) => Poll::Ready(Err(e)),
}
}
}
#[derive(Clone, Eq, PartialEq)]
pub enum WSConnectState {
Init,
Connecting,
Connected,
Disconnected,
}
impl WSConnectState {
fn is_connected(&self) -> bool {
self == &WSConnectState::Connected
}
fn is_connecting(&self) -> bool {
self == &WSConnectState::Connecting
}
fn is_disconnected(&self) -> bool {
self == &WSConnectState::Disconnected || self == &WSConnectState::Init
}
}
impl std::fmt::Display for WSConnectState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
WSConnectState::Init => f.write_str("Init"),
WSConnectState::Connected => f.write_str("Connected"),
WSConnectState::Connecting => f.write_str("Connecting"),
WSConnectState::Disconnected => f.write_str("Disconnected"),
}
}
}
impl std::fmt::Debug for WSConnectState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!("{}", self))
}
}
struct WSConnectStateNotifier {
conn_state: WSConnectState,
notify: Arc<broadcast::Sender<WSConnectState>>,
}
impl std::default::Default for WSConnectStateNotifier {
fn default() -> Self {
let (state_notify, _) = broadcast::channel(16);
Self {
conn_state: WSConnectState::Init,
notify: Arc::new(state_notify),
}
}
}
impl WSConnectStateNotifier {
fn update_state(&mut self, new_state: WSConnectState) {
if self.conn_state == new_state {
return;
}
tracing::debug!(
"WebSocket connect state did change: {} -> {}",
self.conn_state,
new_state
);
self.conn_state = new_state.clone();
let _ = self.notify.send(new_state);
}
}