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 # ignore the deb filegit
frontend/package frontend/package
frontend/*.deb frontend/*.deb
**/Cargo.toml.bak

View File

@ -70,6 +70,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"getrandom 0.2.10",
"once_cell", "once_cell",
"version_check", "version_check",
] ]
@ -107,6 +108,12 @@ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
] ]
[[package]]
name = "allocator-api2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]] [[package]]
name = "android-tzdata" name = "android-tzdata"
version = "0.1.1" version = "0.1.1"
@ -137,33 +144,12 @@ version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 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]] [[package]]
name = "appflowy_tauri" name = "appflowy_tauri"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"bytes", "bytes",
"flowy-core", "flowy-core",
"flowy-net",
"flowy-notification", "flowy-notification",
"lib-dispatch", "lib-dispatch",
"serde", "serde",
@ -243,6 +229,15 @@ dependencies = [
"system-deps 6.1.1", "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]] [[package]]
name = "atomic_refcell" name = "atomic_refcell"
version = "0.1.10" version = "0.1.10"
@ -367,7 +362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
dependencies = [ dependencies = [
"borsh-derive", "borsh-derive",
"hashbrown 0.13.2", "hashbrown 0.12.3",
] ]
[[package]] [[package]]
@ -433,6 +428,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5" checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5"
dependencies = [ dependencies = [
"memchr", "memchr",
"once_cell",
"regex-automata",
"serde", "serde",
] ]
@ -592,18 +589,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.26" version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
"js-sys", "js-sys",
"num-traits", "num-traits",
"serde", "serde",
"time 0.1.45",
"wasm-bindgen", "wasm-bindgen",
"winapi", "windows-targets",
] ]
[[package]] [[package]]
@ -671,6 +667,32 @@ dependencies = [
"libloading", "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]] [[package]]
name = "cmd_lib" name = "cmd_lib"
version = "1.3.0" version = "1.3.0"
@ -730,7 +752,7 @@ dependencies = [
[[package]] [[package]]
name = "collab" name = "collab"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -749,7 +771,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-database" name = "collab-database"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -779,7 +801,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-define" name = "collab-define"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -791,7 +813,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-derive" name = "collab-derive"
version = "0.1.0" 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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -803,7 +825,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-document" name = "collab-document"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -823,7 +845,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-folder" name = "collab-folder"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -840,10 +862,30 @@ dependencies = [
"tracing", "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]] [[package]]
name = "collab-persistence" name = "collab-persistence"
version = "0.1.0" 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 = [ dependencies = [
"async-trait", "async-trait",
"bincode", "bincode",
@ -864,7 +906,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-plugins" name = "collab-plugins"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -872,7 +914,6 @@ dependencies = [
"collab-define", "collab-define",
"collab-persistence", "collab-persistence",
"collab-sync-protocol", "collab-sync-protocol",
"collab-ws",
"futures-util", "futures-util",
"lib0", "lib0",
"parking_lot", "parking_lot",
@ -893,10 +934,11 @@ dependencies = [
[[package]] [[package]]
name = "collab-sync-protocol" name = "collab-sync-protocol"
version = "0.1.0" 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 = [ dependencies = [
"bytes", "bytes",
"collab", "collab",
"collab-define",
"md5", "md5",
"serde", "serde",
"serde_json", "serde_json",
@ -907,7 +949,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-user" name = "collab-user"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -920,24 +962,6 @@ dependencies = [
"tracing", "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]] [[package]]
name = "color_quant" name = "color_quant"
version = "1.1.0" version = "1.1.0"
@ -1036,6 +1060,21 @@ dependencies = [
"libc", "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]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.3.2" version = "1.3.2"
@ -1079,6 +1118,16 @@ dependencies = [
"scopeguard", "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]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.16" version = "0.8.16"
@ -1315,6 +1364,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
[[package]]
name = "dotenvy"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]] [[package]]
name = "dtoa" name = "dtoa"
version = "1.0.6" version = "1.0.6"
@ -1347,6 +1402,9 @@ name = "either"
version = "1.8.1" version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "embed-resource" name = "embed-resource"
@ -1418,6 +1476,12 @@ dependencies = [
"backtrace", "backtrace",
] ]
[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]] [[package]]
name = "faccess" name = "faccess"
version = "0.2.4" version = "0.2.4"
@ -1522,7 +1586,7 @@ dependencies = [
"console", "console",
"fancy-regex 0.10.0", "fancy-regex 0.10.0",
"flowy-ast", "flowy-ast",
"itertools", "itertools 0.10.5",
"lazy_static", "lazy_static",
"log", "log",
"phf 0.8.0", "phf 0.8.0",
@ -1556,9 +1620,12 @@ dependencies = [
name = "flowy-core" name = "flowy-core"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"appflowy-integrate", "anyhow",
"bytes", "bytes",
"collab",
"collab-define", "collab-define",
"collab-integrate",
"collab-plugins",
"diesel", "diesel",
"flowy-config", "flowy-config",
"flowy-database-deps", "flowy-database-deps",
@ -1568,7 +1635,6 @@ dependencies = [
"flowy-error", "flowy-error",
"flowy-folder-deps", "flowy-folder-deps",
"flowy-folder2", "flowy-folder2",
"flowy-net",
"flowy-server", "flowy-server",
"flowy-server-config", "flowy-server-config",
"flowy-sqlite", "flowy-sqlite",
@ -1604,7 +1670,6 @@ name = "flowy-database2"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"appflowy-integrate",
"async-stream", "async-stream",
"async-trait", "async-trait",
"bytes", "bytes",
@ -1613,6 +1678,7 @@ dependencies = [
"collab", "collab",
"collab-database", "collab-database",
"collab-define", "collab-define",
"collab-integrate",
"csv", "csv",
"dashmap", "dashmap",
"fancy-regex 0.10.0", "fancy-regex 0.10.0",
@ -1673,11 +1739,11 @@ name = "flowy-document2"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"appflowy-integrate",
"bytes", "bytes",
"collab", "collab",
"collab-define", "collab-define",
"collab-document", "collab-document",
"collab-integrate",
"flowy-codegen", "flowy-codegen",
"flowy-derive", "flowy-derive",
"flowy-document-deps", "flowy-document-deps",
@ -1719,6 +1785,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
"client-api",
"collab-database", "collab-database",
"collab-document", "collab-document",
"flowy-codegen", "flowy-codegen",
@ -1751,12 +1818,12 @@ dependencies = [
name = "flowy-folder2" name = "flowy-folder2"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"appflowy-integrate",
"bytes", "bytes",
"chrono", "chrono",
"collab", "collab",
"collab-define", "collab-define",
"collab-folder", "collab-folder",
"collab-integrate",
"flowy-codegen", "flowy-codegen",
"flowy-derive", "flowy-derive",
"flowy-error", "flowy-error",
@ -1776,17 +1843,6 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "flowy-net"
version = "0.1.0"
dependencies = [
"bytes",
"flowy-codegen",
"lib-dispatch",
"protobuf",
"tracing",
]
[[package]] [[package]]
name = "flowy-notification" name = "flowy-notification"
version = "0.1.0" version = "0.1.0"
@ -1808,6 +1864,7 @@ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
"chrono", "chrono",
"client-api",
"collab", "collab",
"collab-define", "collab-define",
"collab-document", "collab-document",
@ -1901,7 +1958,6 @@ name = "flowy-user"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"appflowy-integrate",
"base64 0.21.2", "base64 0.21.2",
"bytes", "bytes",
"chrono", "chrono",
@ -1910,6 +1966,7 @@ dependencies = [
"collab-define", "collab-define",
"collab-document", "collab-document",
"collab-folder", "collab-folder",
"collab-integrate",
"collab-user", "collab-user",
"diesel", "diesel",
"diesel_derives", "diesel_derives",
@ -2045,6 +2102,17 @@ dependencies = [
"futures-util", "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]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.28" version = "0.3.28"
@ -2376,6 +2444,15 @@ dependencies = [
"system-deps 6.1.1", "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]] [[package]]
name = "gtk" name = "gtk"
version = "0.15.5" version = "0.15.5"
@ -2473,6 +2550,19 @@ name = "hashbrown"
version = "0.14.0" version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" 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]] [[package]]
name = "heck" name = "heck"
@ -2488,6 +2578,9 @@ name = "heck"
version = "0.4.1" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
@ -2681,6 +2774,12 @@ dependencies = [
"unicode-normalization", "unicode-normalization",
] ]
[[package]]
name = "if_chain"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
[[package]] [[package]]
name = "ignore" name = "ignore"
version = "0.4.20" version = "0.4.20"
@ -2785,6 +2884,15 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itertools"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "0.4.8" version = "0.4.8"
@ -3338,6 +3446,15 @@ dependencies = [
"minimal-lexical", "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]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.46.0" version = "0.46.0"
@ -3468,6 +3585,17 @@ dependencies = [
"windows-sys 0.42.0", "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]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.55" version = "0.10.55"
@ -3595,6 +3723,12 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]] [[package]]
name = "pathdiff" name = "pathdiff"
version = "0.2.1" version = "0.2.1"
@ -3863,7 +3997,7 @@ dependencies = [
"line-wrap", "line-wrap",
"quick-xml", "quick-xml",
"serde", "serde",
"time 0.3.22", "time",
] ]
[[package]] [[package]]
@ -4665,9 +4799,9 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.164" version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -4685,9 +4819,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.164" version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4696,9 +4830,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.99" version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [ dependencies = [
"itoa 1.0.6", "itoa 1.0.6",
"ryu", "ryu",
@ -4707,9 +4841,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_repr" name = "serde_repr"
version = "0.1.12" version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4750,7 +4884,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_with_macros", "serde_with_macros",
"time 0.3.22", "time",
] ]
[[package]] [[package]]
@ -4834,6 +4968,21 @@ dependencies = [
"lazy_static", "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]] [[package]]
name = "shlex" name = "shlex"
version = "1.1.0" version = "1.1.0"
@ -4966,6 +5115,99 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 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]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.2.0" version = "1.2.0"
@ -4987,6 +5229,20 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 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]] [[package]]
name = "string_cache" name = "string_cache"
version = "0.8.7" version = "0.8.7"
@ -5280,7 +5536,7 @@ dependencies = [
"sha2", "sha2",
"tauri-utils", "tauri-utils",
"thiserror", "thiserror",
"time 0.3.22", "time",
"uuid", "uuid",
"walkdir", "walkdir",
] ]
@ -5445,18 +5701,18 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.40" version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.40" version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -5483,17 +5739,6 @@ dependencies = [
"once_cell", "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]] [[package]]
name = "time" name = "time"
version = "0.3.22" version = "0.3.22"
@ -5978,6 +6223,12 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "unicode_categories"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]] [[package]]
name = "universal-hash" name = "universal-hash"
version = "0.5.1" version = "0.5.1"
@ -5996,9 +6247,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]] [[package]]
name = "url" name = "url"
version = "2.4.0" version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"idna", "idna",
@ -6014,11 +6265,12 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.3.4" version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81" checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
dependencies = [ dependencies = [
"getrandom 0.2.10", "getrandom 0.2.10",
"serde",
"sha1_smol", "sha1_smol",
] ]
@ -6035,6 +6287,33 @@ dependencies = [
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"url", "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]] [[package]]
@ -6112,12 +6391,6 @@ version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 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]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" 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"] } lib-dispatch = { path = "../../rust-lib/lib-dispatch", features = ["use_serde"] }
flowy-core = { path = "../../rust-lib/flowy-core", features = ["rev-sqlite", "ts"] } flowy-core = { path = "../../rust-lib/flowy-core", features = ["rev-sqlite", "ts"] }
flowy-notification = { path = "../../rust-lib/flowy-notification", features = ["ts"] } flowy-notification = { path = "../../rust-lib/flowy-notification", features = ["ts"] }
flowy-net = { path = "../../rust-lib/flowy-net" }
[features] [features]
# by default Tauri runs in production mode # by default Tauri runs in production mode
@ -34,24 +33,33 @@ default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"] custom-protocol = ["tauri/custom-protocol"]
[patch.crates-io] [patch.crates-io]
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "8f8f6a" }
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" } # Please using the following command to update the revision id
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } # Current directory: frontend
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } # Run the script:
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } # scripts/tool/update_collab_rev.sh new_rev_id
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } # ⚠️⚠️⚠️️
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } 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 = { path = "../../../../AppFlowy-Collab/collab" }
#collab-folder = { path = "../../../../AppFlowy-Collab/collab-folder" } #collab-folder = { path = "../../../../AppFlowy-Collab/collab-folder" }
#collab-document = { path = "../../../../AppFlowy-Collab/collab-document" } #collab-document = { path = "../../../../AppFlowy-Collab/collab-document" }
#collab-database = { path = "../../../../AppFlowy-Collab/collab-database" } #collab-database = { path = "../../../../AppFlowy-Collab/collab-database" }
#appflowy-integrate = { path = "../../../../AppFlowy-Collab/appflowy-integrate" }
#collab-plugins = { path = "../../../../AppFlowy-Collab/collab-plugins" } #collab-plugins = { path = "../../../../AppFlowy-Collab/collab-plugins" }
#collab-persistence = { path = "../../../../AppFlowy-Collab/collab-persistence" }
#collab-user = { path = "../../../../AppFlowy-Collab/collab-user" } #collab-user = { path = "../../../../AppFlowy-Collab/collab-user" }
#collab-define = { path = "../../../../AppFlowy-Collab/collab-define" } #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 = [ members = [
"lib-dispatch", "lib-dispatch",
"lib-log", "lib-log",
"flowy-net",
"flowy-core", "flowy-core",
"dart-ffi", "dart-ffi",
"flowy-user", "flowy-user",
@ -23,8 +22,33 @@ members = [
"flowy-config", "flowy-config",
"flowy-encrypt", "flowy-encrypt",
"flowy-storage", "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] [profile.dev]
opt-level = 0 opt-level = 0
lto = false lto = false
@ -49,20 +73,30 @@ lto = false
incremental = false incremental = false
[patch.crates-io] [patch.crates-io]
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "8f8f6a" }
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" } # Please using the following command to update the revision id
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } # Current directory: frontend
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } # Run the script:
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } # scripts/tool/update_collab_rev.sh new_rev_id
collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } # ⚠️⚠️⚠️️
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 = { path = "../AppFlowy-Collab/collab" }
#collab-folder = { path = "../AppFlowy-Collab/collab-folder" } #collab-folder = { path = "../AppFlowy-Collab/collab-folder" }
#collab-database= { path = "../AppFlowy-Collab/collab-database" } #collab-database= { path = "../AppFlowy-Collab/collab-database" }
#collab-document = { path = "../AppFlowy-Collab/collab-document" } #collab-document = { path = "../AppFlowy-Collab/collab-document" }
#collab-plugins = { path = "../AppFlowy-Collab/collab-plugins" } #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-user = { path = "../AppFlowy-Collab/collab-user" }
#collab-define = { path = "../AppFlowy-Collab/collab-define" } #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" lazy_static = "1.4.0"
parking_lot = "0.12.1" parking_lot = "0.12.1"
tracing = { version = "0.1", features = ["log"] } tracing = { version = "0.1", features = ["log"] }
appflowy-integrate = {version = "0.1.0" }
lib-dispatch = { path = "../lib-dispatch" } # workspace
flowy-core = { path = "../flowy-core" } lib-dispatch = { workspace = true }
flowy-notification = { path = "../flowy-notification" } flowy-core = { workspace = true }
flowy-net = { path = "../flowy-net" } 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-derive = { path = "../../../shared-lib/flowy-derive" }
flowy-server = { path = "../flowy-server" }
flowy-server-config = { path = "../flowy-server-config" }
[features] [features]
default = ["dart", "rev-sqlite"] 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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [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" } flowy-derive = { path = "../../../shared-lib/flowy-derive" }
lib-dispatch = { path = "../lib-dispatch" }
protobuf = {version = "2.28.0"} protobuf = {version = "2.28.0"}
bytes = { version = "1.4" } bytes = { version = "1.4" }
flowy-error = { path = "../flowy-error" }
strum_macros = "0.21" strum_macros = "0.21"
[build-dependencies] [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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
lib-dispatch = { path = "../lib-dispatch" } lib-dispatch = { workspace = true }
lib-log = { path = "../lib-log" } lib-log = { workspace = true }
flowy-user = { path = "../flowy-user" } flowy-user = { workspace = true }
flowy-user-deps = { path = "../flowy-user-deps" } flowy-user-deps = { workspace = true }
flowy-net = { path = "../flowy-net" } flowy-folder2 = { workspace = true }
flowy-folder2 = { path = "../flowy-folder2" } flowy-folder-deps = { workspace = true }
flowy-folder-deps = { path = "../flowy-folder-deps" } flowy-database2 = { workspace = true }
flowy-database2 = { path = "../flowy-database2" } flowy-database-deps = { workspace = true }
flowy-database-deps = { path = "../flowy-database-deps" } flowy-sqlite = { workspace = true }
flowy-sqlite = { path = "../flowy-sqlite" } flowy-document2 = { workspace = true }
flowy-document2 = { path = "../flowy-document2" } flowy-document-deps = { workspace = true }
flowy-document-deps = { path = "../flowy-document-deps" } flowy-error = { workspace = true }
flowy-error = { path = "../flowy-error" } flowy-task = { workspace = true }
flowy-task = { path = "../flowy-task" } flowy-server = { workspace = true }
flowy-server = { path = "../flowy-server" } flowy-server-config = { workspace = true }
flowy-server-config = { path = "../flowy-server-config" } flowy-config = { workspace = true }
flowy-config = { path = "../flowy-config" } collab-integrate = { workspace = true, features = ["supabase_integrate", "appflowy_cloud_integrate", "snapshot_plugin"] }
appflowy-integrate = { version = "0.1.0", features = ["postgres_storage_plugin", "snapshot_plugin"] }
collab-define = { version = "0.1.0" } 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"] } diesel = { version = "1.4.8", features = ["sqlite"] }
uuid = { version = "1.3.3", features = ["v4"] } uuid = { version = "1.3.3", features = ["v4"] }
flowy-storage = { path = "../flowy-storage" } flowy-storage = { workspace = true }
tracing = { version = "0.1", features = ["log"] } tracing = { version = "0.1", features = ["log"] }
futures-core = { version = "0.3", default-features = false } futures-core = { version = "0.3", default-features = false }
@ -35,6 +36,7 @@ bytes = "1.4"
tokio = { version = "1.26", features = ["full"] } tokio = { version = "1.26", features = ["full"] }
console-subscriber = { version = "0.1.8", optional = true } console-subscriber = { version = "0.1.8", optional = true }
parking_lot = "0.12.1" parking_lot = "0.12.1"
anyhow = "1.0.75"
lib-infra = { path = "../../../shared-lib/lib-infra" } lib-infra = { path = "../../../shared-lib/lib-infra" }
serde = "1.0" serde = "1.0"
@ -49,7 +51,6 @@ native_sync = []
use_bunyan = ["lib-log/use_bunyan"] use_bunyan = ["lib-log/use_bunyan"]
dart = [ dart = [
"flowy-user/dart", "flowy-user/dart",
"flowy-net/dart",
"flowy-folder2/dart", "flowy-folder2/dart",
"flowy-database2/dart", "flowy-database2/dart",
"flowy-document2/dart", "flowy-document2/dart",
@ -57,7 +58,6 @@ dart = [
] ]
ts = [ ts = [
"flowy-user/ts", "flowy-user/ts",
"flowy-net/ts",
"flowy-folder2/ts", "flowy-folder2/ts",
"flowy-database2/ts", "flowy-database2/ts",
"flowy-document2/ts", "flowy-document2/ts",
@ -67,3 +67,4 @@ rev-sqlite = [
"flowy-user/rev-sqlite", "flowy-user/rev-sqlite",
] ]
openssl_vendored = ["flowy-sqlite/openssl_vendored"] openssl_vendored = ["flowy-sqlite/openssl_vendored"]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,18 +2,11 @@ use std::collections::HashMap;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::sync::{Arc, Weak}; 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 parking_lot::RwLock;
use serde_repr::*; use serde_repr::*;
use flowy_database_deps::cloud::*; use collab_integrate::YrsDocAction;
use flowy_document2::deps::DocumentData;
use flowy_document_deps::cloud::{DocumentCloudService, DocumentSnapshot};
use flowy_error::{ErrorCode, FlowyError, FlowyResult}; 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::configuration::appflowy_cloud_server_configuration;
use flowy_server::af_cloud::AFCloudServer; use flowy_server::af_cloud::AFCloudServer;
use flowy_server::local_server::{LocalServer, LocalServerDB}; 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::{AppFlowyEncryption, AppFlowyServer, EncryptionImpl};
use flowy_server_config::supabase_config::SupabaseConfiguration; use flowy_server_config::supabase_config::SupabaseConfiguration;
use flowy_sqlite::kv::StorePreferences; use flowy_sqlite::kv::StorePreferences;
use flowy_storage::{FileStorageService, StorageObject};
use flowy_user::event_map::UserCloudServiceProvider;
use flowy_user::services::database::{ use flowy_user::services::database::{
get_user_profile, get_user_workspace, open_collab_db, open_user_db, get_user_profile, get_user_workspace, open_collab_db, open_user_db,
}; };
use flowy_user_deps::cloud::UserCloudService; use flowy_user_deps::cloud::UserCloudService;
use flowy_user_deps::entities::*; use flowy_user_deps::entities::*;
use lib_infra::future::FutureResult;
use crate::AppFlowyCoreConfig; 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)] #[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize_repr, Deserialize_repr)]
#[repr(u8)] #[repr(u8)]
pub enum ServerProviderType { pub enum ServerType {
/// Local server provider. /// Local server provider.
/// Offline mode, no user authentication and the data is stored locally. /// Offline mode, no user authentication and the data is stored locally.
Local = 0, Local = 0,
@ -45,48 +35,48 @@ pub enum ServerProviderType {
/// progress. /// progress.
AppFlowyCloud = 1, AppFlowyCloud = 1,
/// Supabase server provider. /// 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, Supabase = 2,
} }
impl Display for ServerProviderType { impl Display for ServerType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self { match self {
ServerProviderType::Local => write!(f, "Local"), ServerType::Local => write!(f, "Local"),
ServerProviderType::AppFlowyCloud => write!(f, "AppFlowyCloud"), ServerType::AppFlowyCloud => write!(f, "AppFlowyCloud"),
ServerProviderType::Supabase => write!(f, "Supabase"), ServerType::Supabase => write!(f, "Supabase"),
} }
} }
} }
/// The [AppFlowyServerProvider] provides list of [AppFlowyServer] base on the [AuthType]. Using /// The [ServerProvider] provides list of [AppFlowyServer] base on the [AuthType]. Using
/// the auth type, the [AppFlowyServerProvider] will create a new [AppFlowyServer] if it doesn't /// the auth type, the [ServerProvider] will create a new [AppFlowyServer] if it doesn't
/// exist. /// exist.
/// Each server implements the [AppFlowyServer] trait, which provides the [UserCloudService], etc. /// Each server implements the [AppFlowyServer] trait, which provides the [UserCloudService], etc.
pub struct AppFlowyServerProvider { pub struct ServerProvider {
config: AppFlowyCoreConfig, config: AppFlowyCoreConfig,
provider_type: RwLock<ServerProviderType>, server_type: RwLock<ServerType>,
providers: RwLock<HashMap<ServerProviderType, Arc<dyn AppFlowyServer>>>, providers: RwLock<HashMap<ServerType, Arc<dyn AppFlowyServer>>>,
encryption: RwLock<Arc<dyn AppFlowyEncryption>>, pub(crate) encryption: RwLock<Arc<dyn AppFlowyEncryption>>,
store_preferences: Weak<StorePreferences>, pub(crate) store_preferences: Weak<StorePreferences>,
cache_user_service: RwLock<HashMap<ServerProviderType, Arc<dyn UserCloudService>>>, pub(crate) cache_user_service: RwLock<HashMap<ServerType, Arc<dyn UserCloudService>>>,
device_id: Arc<RwLock<String>>, pub(crate) device_id: Arc<RwLock<String>>,
enable_sync: RwLock<bool>, pub(crate) enable_sync: RwLock<bool>,
uid: Arc<RwLock<Option<i64>>>, pub(crate) uid: Arc<RwLock<Option<i64>>>,
} }
impl AppFlowyServerProvider { impl ServerProvider {
pub fn new( pub fn new(
config: AppFlowyCoreConfig, config: AppFlowyCoreConfig,
provider_type: ServerProviderType, provider_type: ServerType,
store_preferences: Weak<StorePreferences>, store_preferences: Weak<StorePreferences>,
) -> Self { ) -> Self {
let encryption = EncryptionImpl::new(None); let encryption = EncryptionImpl::new(None);
Self { Self {
config, config,
provider_type: RwLock::new(provider_type), server_type: RwLock::new(provider_type),
device_id: Default::default(), device_id: Arc::new(RwLock::new(uuid::Uuid::new_v4().to_string())),
providers: RwLock::new(HashMap::new()), providers: RwLock::new(HashMap::new()),
enable_sync: RwLock::new(true), enable_sync: RwLock::new(true),
encryption: RwLock::new(Arc::new(encryption)), encryption: RwLock::new(Arc::new(encryption)),
@ -96,42 +86,51 @@ impl AppFlowyServerProvider {
} }
} }
pub fn provider_type(&self) -> ServerProviderType { pub fn get_server_type(&self) -> ServerType {
self.provider_type.read().clone() 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. /// Returns a [AppFlowyServer] trait implementation base on the provider_type.
fn get_provider( pub(crate) fn get_server(
&self, &self,
provider_type: &ServerProviderType, server_type: &ServerType,
) -> FlowyResult<Arc<dyn AppFlowyServer>> { ) -> 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()); return Ok(provider.clone());
} }
let server = match provider_type { let server = match server_type {
ServerProviderType::Local => { ServerType::Local => {
let local_db = Arc::new(LocalServerDBImpl { let local_db = Arc::new(LocalServerDBImpl {
storage_path: self.config.storage_path.clone(), storage_path: self.config.storage_path.clone(),
}); });
let server = Arc::new(LocalServer::new(local_db)); let server = Arc::new(LocalServer::new(local_db));
Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server) Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
}, },
ServerProviderType::AppFlowyCloud => { ServerType::AppFlowyCloud => {
let config = appflowy_cloud_server_configuration().map_err(|e| { let config = appflowy_cloud_server_configuration().map_err(|e| {
FlowyError::new( FlowyError::new(
ErrorCode::InvalidAuthConfig, ErrorCode::InvalidAuthConfig,
format!( format!(
"Missing self host config: {:?}. Error: {:?}", "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) Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
}, },
ServerProviderType::Supabase => { ServerType::Supabase => {
let config = match SupabaseConfiguration::from_env() { let config = match SupabaseConfiguration::from_env() {
Ok(config) => config, Ok(config) => config,
Err(e) => { Err(e) => {
@ -155,287 +154,30 @@ impl AppFlowyServerProvider {
self self
.providers .providers
.write() .write()
.insert(provider_type.clone(), server.clone()); .insert(server_type.clone(), server.clone());
Ok(server) Ok(server)
} }
} }
impl FileStorageService for AppFlowyServerProvider { impl From<AuthType> for ServerType {
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 {
fn from(auth_provider: AuthType) -> Self { fn from(auth_provider: AuthType) -> Self {
match auth_provider { match auth_provider {
AuthType::Local => ServerProviderType::Local, AuthType::Local => ServerType::Local,
AuthType::SelfHosted => ServerProviderType::AppFlowyCloud, AuthType::SelfHosted => ServerType::AppFlowyCloud,
AuthType::Supabase => ServerProviderType::Supabase, AuthType::Supabase => ServerType::Supabase,
} }
} }
} }
impl From<&AuthType> for ServerProviderType { impl From<&AuthType> for ServerType {
fn from(auth_provider: &AuthType) -> Self { fn from(auth_provider: &AuthType) -> Self {
Self::from(auth_provider.clone()) Self::from(auth_provider.clone())
} }
} }
pub fn current_server_provider(store_preferences: &Arc<StorePreferences>) -> ServerProviderType { pub fn current_server_provider(store_preferences: &Arc<StorePreferences>) -> ServerType {
match store_preferences.get_object::<ServerProviderType>(SERVER_PROVIDER_TYPE_KEY) { match store_preferences.get_object::<ServerType>(SERVER_PROVIDER_TYPE_KEY) {
None => ServerProviderType::Local, None => ServerType::Local,
Some(provider_type) => provider_type, 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 tokio::sync::RwLock;
use collab_integrate::collab_builder::{AppFlowyCollabBuilder, CollabSource};
use flowy_database2::DatabaseManager; use flowy_database2::DatabaseManager;
use flowy_document2::manager::DocumentManager; use flowy_document2::manager::DocumentManager;
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
@ -31,9 +31,7 @@ use module::make_plugins;
pub use module::*; pub use module::*;
use crate::deps_resolve::*; use crate::deps_resolve::*;
use crate::integrate::server::{ use crate::integrate::server::{current_server_provider, ServerProvider, ServerType};
current_server_provider, AppFlowyServerProvider, ServerProviderType,
};
mod deps_resolve; mod deps_resolve;
mod integrate; mod integrate;
@ -121,7 +119,7 @@ pub struct AppFlowyCore {
pub folder_manager: Arc<FolderManager>, pub folder_manager: Arc<FolderManager>,
pub database_manager: Arc<DatabaseManager>, pub database_manager: Arc<DatabaseManager>,
pub event_dispatcher: Arc<AFPluginDispatcher>, pub event_dispatcher: Arc<AFPluginDispatcher>,
pub server_provider: Arc<AppFlowyServerProvider>, pub server_provider: Arc<ServerProvider>,
pub task_dispatcher: Arc<RwLock<TaskDispatcher>>, pub task_dispatcher: Arc<RwLock<TaskDispatcher>>,
pub store_preference: Arc<StorePreferences>, pub store_preference: Arc<StorePreferences>,
} }
@ -147,7 +145,7 @@ impl AppFlowyCore {
runtime.spawn(TaskRunner::run(task_dispatcher.clone())); runtime.spawn(TaskRunner::run(task_dispatcher.clone()));
let provider_type = current_server_provider(&store_preference); let provider_type = current_server_provider(&store_preference);
let server_provider = Arc::new(AppFlowyServerProvider::new( let server_provider = Arc::new(ServerProvider::new(
config.clone(), config.clone(),
provider_type, provider_type,
Arc::downgrade(&store_preference), Arc::downgrade(&store_preference),
@ -282,7 +280,7 @@ struct UserStatusCallbackImpl {
folder_manager: Arc<FolderManager>, folder_manager: Arc<FolderManager>,
database_manager: Arc<DatabaseManager>, database_manager: Arc<DatabaseManager>,
document_manager: Arc<DocumentManager>, document_manager: Arc<DocumentManager>,
server_provider: Arc<AppFlowyServerProvider>, server_provider: Arc<ServerProvider>,
#[allow(dead_code)] #[allow(dead_code)]
config: AppFlowyCoreConfig, config: AppFlowyCoreConfig,
} }
@ -298,7 +296,8 @@ impl UserStatusCallback for UserStatusCallbackImpl {
_device_id: &str, _device_id: &str,
) -> Fut<FlowyResult<()>> { ) -> Fut<FlowyResult<()>> {
let user_workspace = user_workspace.clone(); 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 folder_manager = self.folder_manager.clone();
let database_manager = self.database_manager.clone(); let database_manager = self.database_manager.clone();
let document_manager = self.document_manager.clone(); let document_manager = self.document_manager.clone();
@ -315,7 +314,6 @@ impl UserStatusCallback for UserStatusCallbackImpl {
} }
to_fut(async move { to_fut(async move {
collab_builder.initialize(user_workspace.id.clone());
folder_manager folder_manager
.initialize( .initialize(
user_id, user_id,
@ -419,13 +417,13 @@ impl UserStatusCallback for UserStatusCallbackImpl {
fn open_workspace(&self, user_id: i64, user_workspace: &UserWorkspace) -> Fut<FlowyResult<()>> { fn open_workspace(&self, user_id: i64, user_workspace: &UserWorkspace) -> Fut<FlowyResult<()>> {
let user_workspace = user_workspace.clone(); 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 folder_manager = self.folder_manager.clone();
let database_manager = self.database_manager.clone(); let database_manager = self.database_manager.clone();
let document_manager = self.document_manager.clone(); let document_manager = self.document_manager.clone();
to_fut(async move { to_fut(async move {
collab_builder.initialize(user_workspace.id.clone());
folder_manager folder_manager
.initialize_with_workspace_id(user_id, &user_workspace.id) .initialize_with_workspace_id(user_id, &user_workspace.id)
.await?; .await?;
@ -449,12 +447,12 @@ impl UserStatusCallback for UserStatusCallbackImpl {
} }
} }
impl From<ServerProviderType> for CollabStorageType { impl From<ServerType> for CollabSource {
fn from(server_provider: ServerProviderType) -> Self { fn from(server_provider: ServerType) -> Self {
match server_provider { match server_provider {
ServerProviderType::Local => CollabStorageType::Local, ServerType::Local => CollabSource::Local,
ServerProviderType::AppFlowyCloud => CollabStorageType::Local, ServerType::AppFlowyCloud => CollabSource::Local,
ServerProviderType::Supabase => CollabStorageType::Supabase, ServerType::Supabase => CollabSource::Supabase,
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1464,7 +1464,7 @@ impl DatabaseViewData for DatabaseViewDataImpl {
field_id: &str, field_id: &str,
visibility: Option<FieldVisibility>, 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 new_field_settings = if let Some(field_settings) = field_settings_map.get(field_id) {
let mut field_settings = field_settings.to_owned(); let mut field_settings = field_settings.to_owned();

View File

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

View File

@ -292,7 +292,7 @@ mod tests {
let native_timestamp = 1647251762; let native_timestamp = 1647251762;
let native = NaiveDateTime::from_timestamp_opt(native_timestamp, 0).unwrap(); 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 // utc_timestamp doesn't carry timezone
let utc_timestamp = utc.timestamp(); let utc_timestamp = utc.timestamp();
assert_eq!(native_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) // Mon Mar 14 2022 17:56:02 GMT+0800 (China Standard Time)
let gmt_8_offset = FixedOffset::east_opt(8 * 3600).unwrap(); 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!( let china_local_time = format!(
"{}", "{}",
china_local.format_with_items(StrftimeItems::new(&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]. /// The [DateTypeOption] is used by [FieldType::Date], [FieldType::LastEditedTime], and [FieldType::CreatedTime].
/// So, storing the field type is necessary to distinguish the field type. /// So, storing the field type is necessary to distinguish the field type.
/// Most of the cases, each [FieldType] has its own [TypeOption] implementation. /// 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 struct DateTypeOption {
pub date_format: DateFormat, pub date_format: DateFormat,
pub time_format: TimeFormat, pub time_format: TimeFormat,
pub timezone_id: String, 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 { impl TypeOption for DateTypeOption {
type CellData = DateCellData; type CellData = DateCellData;
type CellChangeset = DateCellChangeset; type CellChangeset = DateCellChangeset;
@ -113,7 +103,7 @@ impl DateTypeOption {
if let Some(timestamp) = timestamp { if let Some(timestamp) = timestamp {
let naive = chrono::NaiveDateTime::from_timestamp_opt(*timestamp, 0).unwrap(); let naive = chrono::NaiveDateTime::from_timestamp_opt(*timestamp, 0).unwrap();
let offset = self.get_timezone_offset(naive); 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 fmt = self.date_format.format_str();
let date = format!("{}", date_time.format(fmt)); let date = format!("{}", date_time.format(fmt));

View File

@ -108,7 +108,7 @@ impl TimestampTypeOption {
if let Some(timestamp) = timestamp { if let Some(timestamp) = timestamp {
let naive = chrono::NaiveDateTime::from_timestamp_opt(*timestamp, 0).unwrap(); let naive = chrono::NaiveDateTime::from_timestamp_opt(*timestamp, 0).unwrap();
let offset = Local::now().offset().fix(); 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 fmt = self.date_format.format_str();
let date = format!("{}", date_time.format(fmt)); 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 { match field_type {
FieldType::RichText => RichTextTypeOption::default().into(), FieldType::RichText => RichTextTypeOption::default().into(),
FieldType::Number => NumberTypeOption::default().into(), FieldType::Number => NumberTypeOption::default().into(),
FieldType::DateTime => DateTypeOption { FieldType::DateTime => DateTypeOption::default().into(),
..Default::default()
}
.into(),
FieldType::LastEditedTime | FieldType::CreatedTime => TimestampTypeOption { FieldType::LastEditedTime | FieldType::CreatedTime => TimestampTypeOption {
field_type: field_type.clone(), field_type: field_type.clone(),
date_format: DateFormat::Friendly, date_format: DateFormat::Friendly,
time_format: TimeFormat::TwelveHour, time_format: TimeFormat::TwelveHour,
include_time: true, include_time: true,
..Default::default()
} }
.into(), .into(),
FieldType::SingleSelect => SingleSelectTypeOption::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(), Err(_) => *Local::now().offset(),
}; };
DateTime::<Local>::from_utc(naive, offset) DateTime::<Local>::from_naive_utc_and_offset(naive, offset)
}, },
None => DateTime::default(), None => DateTime::default(),
} }

View File

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

View File

@ -9,14 +9,14 @@ edition = "2021"
collab = { version = "0.1.0" } collab = { version = "0.1.0" }
collab-document = { version = "0.1.0" } collab-document = { version = "0.1.0" }
collab-define = { version = "0.1.0" } collab-define = { version = "0.1.0" }
appflowy-integrate = {version = "0.1.0" } collab-integrate = { workspace = true }
flowy-document-deps = { path = "../flowy-document-deps" } flowy-document-deps = { workspace = true }
flowy-storage = { path = "../flowy-storage" } flowy-storage = { workspace = true }
flowy-derive = { path = "../../../shared-lib/flowy-derive" } 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"] } 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" } lib-infra = { path = "../../../shared-lib/lib-infra" }
protobuf = {version = "2.28.0"} protobuf = {version = "2.28.0"}

View File

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

View File

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

View File

@ -12,18 +12,19 @@ bytes = "1.4"
anyhow = "1.0" anyhow = "1.0"
thiserror = "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_json = {version = "1.0", optional = true}
serde_repr = { version = "0.1" } serde_repr = { version = "0.1" }
serde = "1.0" serde = "1.0"
reqwest = { version = "0.11.14", optional = true, features = ["native-tls-vendored"] } 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} r2d2 = { version = "0.8", optional = true}
url = { version = "2.2", optional = true } url = { version = "2.2", optional = true }
collab-database = { version = "0.1.0", optional = true } collab-database = { version = "0.1.0", optional = true }
collab-document = { version = "0.1.0", optional = true } collab-document = { version = "0.1.0", optional = true }
tokio-postgres = { version = "0.7.8", optional = true } tokio-postgres = { version = "0.7.8", optional = true }
tokio = { version = "1.0", optional = true } tokio = { version = "1.0", optional = true }
client-api = { version = "0.1.0", optional = true }
[features] [features]
impl_from_dispatch_error = ["lib-dispatch"] 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_postgres = ["tokio-postgres"]
impl_from_tokio= ["tokio"] impl_from_tokio= ["tokio"]
impl_from_url= ["url"] impl_from_url= ["url"]
impl_from_appflowy_cloud = ["client-api"]
dart = ["flowy-codegen/dart"] dart = ["flowy-codegen/dart"]
ts = ["flowy-codegen/ts"] ts = ["flowy-codegen/ts"]

View File

@ -238,8 +238,18 @@ pub enum ErrorCode {
#[error("Parse url failed")] #[error("Parse url failed")]
InvalidURL = 78, InvalidURL = 78,
#[error("Require Email Confirmation, Sign in after email confirmation")]
AwaitingEmailConfirmation = 79,
#[error("Text id is empty")] #[error("Text id is empty")]
TextIdIsEmpty = 79, TextIdIsEmpty = 80,
#[error("Record already exists")]
RecordAlreadyExists = 81,
#[error("Missing payload")]
MissingPayload = 82,
} }
impl ErrorCode { 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")] #[cfg(feature = "impl_from_tokio")]
mod tokio; mod tokio;
#[cfg(feature = "impl_from_appflowy_cloud")]
mod cloud;
#[cfg(feature = "impl_from_url")] #[cfg(feature = "impl_from_url")]
mod url; mod url;

View File

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

View File

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

View File

@ -2,8 +2,6 @@ use std::collections::HashSet;
use std::ops::Deref; use std::ops::Deref;
use std::sync::{Arc, Weak}; 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::{CollabRawData, MutexCollab};
use collab::core::collab_state::SyncState; use collab::core::collab_state::SyncState;
use collab_define::CollabType; use collab_define::CollabType;
@ -16,6 +14,8 @@ use tokio_stream::wrappers::WatchStream;
use tokio_stream::StreamExt; use tokio_stream::StreamExt;
use tracing::{event, Level}; use tracing::{event, Level};
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use collab_integrate::{CollabPersistenceConfig, RocksCollabDB, YrsDocAction};
use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_folder_deps::cloud::{gen_view_id, FolderCloudService}; 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" serde = "1.0"
flowy-derive = { path = "../../../shared-lib/flowy-derive" } flowy-derive = { path = "../../../shared-lib/flowy-derive" }
lib-dispatch = { path = "../lib-dispatch" } lib-dispatch = { workspace = true }
[build-dependencies] [build-dependencies]
flowy-codegen = { path = "../../../shared-lib/flowy-codegen" } 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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
flowy-error = { path = "../flowy-error" } flowy-error = { workspace = true }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View File

@ -23,25 +23,26 @@ bytes = { version = "1.0.1", features = ["serde"] }
tokio-retry = "0.3" tokio-retry = "0.3"
anyhow = "1.0" anyhow = "1.0"
uuid = { version = "1.3.3", features = ["v4"] } 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 = { 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-document = { version = "0.1.0" }
collab-define = { version = "0.1.0" } collab-define = { version = "0.1.0" }
hex = "0.4.3" hex = "0.4.3"
postgrest = "1.0" postgrest = "1.0"
lib-infra = { path = "../../../shared-lib/lib-infra" } lib-infra = { path = "../../../shared-lib/lib-infra" }
flowy-user-deps = { path = "../flowy-user-deps" } flowy-user-deps = { workspace = true }
flowy-folder-deps = { path = "../flowy-folder-deps" } flowy-folder-deps = { workspace = true }
flowy-database-deps = { path = "../flowy-database-deps" } flowy-database-deps = { workspace = true }
flowy-document-deps = { path = "../flowy-document-deps" } flowy-document-deps = { workspace = true }
flowy-error = { path = "../flowy-error", features = ["impl_from_postgres", "impl_from_serde", "impl_from_reqwest", "impl_from_url"] } flowy-error = { workspace = true, features = ["impl_from_postgres", "impl_from_serde", "impl_from_reqwest", "impl_from_url", "impl_from_appflowy_cloud"] }
flowy-server-config = { path = "../flowy-server-config" } flowy-server-config = { workspace = true }
flowy-encrypt = { path = "../flowy-encrypt" } flowy-encrypt = { workspace = true }
flowy-storage = { path = "../flowy-storage" } flowy-storage = { workspace = true }
mime_guess = "2.0" mime_guess = "2.0"
url = "2.4" url = "2.4"
tokio-util = "0.7" tokio-util = "0.7"
client-api = { version = "0.1.0" }
[dev-dependencies] [dev-dependencies]
uuid = { version = "1.3.3", features = ["v4"] } 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))?; settings.merge(config::File::from_str(base, FileFormat::Yaml).required(true))?;
let environment: Environment = std::env::var("APP_ENVIRONMENT") let environment: Environment = std::env::var("APP_ENVIRONMENT")
.unwrap_or_else(|_| "local".into()) .unwrap_or_else(|_| "local".to_owned())
.try_into() .try_into()
.expect("Failed to parse APP_ENVIRONMENT."); .expect("Failed to parse APP_ENVIRONMENT.");
@ -43,42 +43,6 @@ impl AFCloudConfiguration {
format!("{}://{}:{}", self.http_scheme, self.host, self.port) 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 { pub fn ws_addr(&self) -> String {
format!("{}://{}:{}/ws", self.ws_scheme, self.host, self.port) 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; 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( fn get_collab_update(
&self, &self,
_object_id: &str, _object_id: &str,

View File

@ -3,9 +3,14 @@ use anyhow::Error;
use flowy_document_deps::cloud::*; use flowy_document_deps::cloud::*;
use lib_infra::future::FutureResult; 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> { fn get_document_updates(&self, _document_id: &str) -> FutureResult<Vec<Vec<u8>>, Error> {
FutureResult::new(async move { Ok(vec![]) }) FutureResult::new(async move { Ok(vec![]) })
} }

View File

@ -1,24 +1,18 @@
use anyhow::Error; use anyhow::Error;
use flowy_folder_deps::cloud::{ use flowy_folder_deps::cloud::{FolderCloudService, FolderData, FolderSnapshot, Workspace};
gen_workspace_id, FolderCloudService, FolderData, FolderSnapshot, Workspace,
};
use lib_infra::future::FutureResult; use lib_infra::future::FutureResult;
use lib_infra::util::timestamp;
pub(crate) struct AFCloudFolderCloudServiceImpl(); use crate::af_cloud::AFServer;
impl FolderCloudService for AFCloudFolderCloudServiceImpl { pub(crate) struct AFCloudFolderCloudServiceImpl<T>(pub T);
fn create_workspace(&self, _uid: i64, name: &str) -> FutureResult<Workspace, Error> {
let name = name.to_string(); impl<T> FolderCloudService for AFCloudFolderCloudServiceImpl<T>
FutureResult::new(async move { where
Ok(Workspace { T: AFServer,
id: gen_workspace_id().to_string(), {
name: name.to_string(), fn create_workspace(&self, _uid: i64, _name: &str) -> FutureResult<Workspace, Error> {
child_views: Default::default(), FutureResult::new(async move { todo!() })
created_at: timestamp(),
})
})
} }
fn get_folder_data(&self, _workspace_id: &str) -> FutureResult<Option<FolderData>, Error> { 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 anyhow::Error;
use collab_define::CollabObject; use collab_define::CollabObject;
use flowy_error::{ErrorCode, FlowyError}; use flowy_error::FlowyError;
use flowy_user_deps::cloud::UserCloudService; use flowy_user_deps::cloud::UserCloudService;
use flowy_user_deps::entities::*; use flowy_user_deps::entities::*;
use lib_infra::box_any::BoxAny; use lib_infra::box_any::BoxAny;
use lib_infra::future::FutureResult; use lib_infra::future::FutureResult;
use crate::af_cloud::configuration::{AFCloudConfiguration, HEADER_TOKEN}; use crate::af_cloud::{AFCloudClient, AFServer};
use crate::request::HttpRequestBuilder;
pub(crate) struct AFCloudUserAuthServiceImpl { pub(crate) struct AFCloudUserAuthServiceImpl<T> {
config: AFCloudConfiguration, server: T,
} }
impl AFCloudUserAuthServiceImpl { impl<T> AFCloudUserAuthServiceImpl<T> {
pub(crate) fn new(config: AFCloudConfiguration) -> Self { pub(crate) fn new(server: T) -> Self {
Self { config } Self { server }
} }
} }
impl UserCloudService for AFCloudUserAuthServiceImpl { impl<T> UserCloudService for AFCloudUserAuthServiceImpl<T>
where
T: AFServer,
{
fn sign_up(&self, params: BoxAny) -> FutureResult<SignUpResponse, Error> { 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 { FutureResult::new(async move {
let params = params.unbox_or_error::<SignUpParams>()?; 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) Ok(resp)
}) })
} }
fn sign_in(&self, params: BoxAny) -> FutureResult<SignInResponse, Error> { fn sign_in(&self, _params: BoxAny) -> FutureResult<SignInResponse, Error> {
let url = self.config.sign_in_url(); todo!()
FutureResult::new(async move {
let params = params.unbox_or_error::<SignInParams>()?;
let resp = user_sign_in_request(params, &url).await?;
Ok(resp)
})
} }
fn sign_out(&self, token: Option<String>) -> FutureResult<(), Error> { fn sign_out(&self, _token: Option<String>) -> FutureResult<(), Error> {
match token { todo!()
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 update_user( fn update_user(
&self, &self,
credential: UserCredentials, _credential: UserCredentials,
params: UpdateUserProfileParams, _params: UpdateUserProfileParams,
) -> FutureResult<(), Error> { ) -> FutureResult<(), Error> {
match credential.token { todo!()
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(())
})
},
}
} }
fn get_user_profile( fn get_user_profile(
&self, &self,
credential: UserCredentials, _credential: UserCredentials,
) -> FutureResult<Option<UserProfile>, Error> { ) -> FutureResult<Option<UserProfile>, Error> {
let url = self.config.user_profile_url(); todo!()
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))
},
}
})
} }
fn get_user_workspaces( fn get_user_workspaces(
@ -145,53 +109,30 @@ impl UserCloudService for AFCloudUserAuthServiceImpl {
} }
pub async fn user_sign_up_request( pub async fn user_sign_up_request(
client: Arc<AFCloudClient>,
params: SignUpParams, params: SignUpParams,
url: &str,
) -> Result<SignUpResponse, FlowyError> { ) -> Result<SignUpResponse, FlowyError> {
let response = request_builder().post(url).json(params)?.response().await?; client
Ok(response) .read()
} .await
.sign_up(&params.email, &params.password)
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()
.await?; .await?;
Ok(()) todo!()
} // tracing::info!("User signed up: {:?}", user);
// match user.confirmed_at {
pub async fn get_user_profile_request(token: &str, url: &str) -> Result<UserProfile, FlowyError> { // Some(_) => {
let user_profile = request_builder() // // User is already confirmed, help her/him to sign in
.get(url) // let token = client.sign_in_password(&params.email, &params.password).await?;
.header(HEADER_TOKEN, token) //
.response() // // TODO:
.await?; // // Query workspace list
Ok(user_profile) // // Query user profile
} //
// todo!()
pub async fn update_user_profile_request( // },
token: &str, // None => Err(FlowyError::new(
params: UpdateUserProfileParams, // ErrorCode::AwaitingEmailConfirmation,
url: &str, // "Awaiting email confirmation".to_string(),
) -> Result<(), FlowyError> { // )),
request_builder() // }
.patch(url)
.header(HEADER_TOKEN, token)
.json(params)?
.send()
.await?;
Ok(())
}
fn request_builder() -> HttpRequestBuilder {
HttpRequestBuilder::new()
} }

View File

@ -1,13 +1,19 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use collab_define::CollabObject; use anyhow::Error;
use collab_plugins::cloud_storage::RemoteCollabStorage; 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_database_deps::cloud::DatabaseCloudService;
use flowy_document_deps::cloud::DocumentCloudService; use flowy_document_deps::cloud::DocumentCloudService;
use flowy_error::{ErrorCode, FlowyError};
use flowy_folder_deps::cloud::FolderCloudService; use flowy_folder_deps::cloud::FolderCloudService;
use flowy_storage::FileStorageService; use flowy_storage::FileStorageService;
use flowy_user_deps::cloud::UserCloudService; use flowy_user_deps::cloud::UserCloudService;
use lib_infra::future::FutureResult;
use crate::af_cloud::configuration::AFCloudConfiguration; use crate::af_cloud::configuration::AFCloudConfiguration;
use crate::af_cloud::impls::{ use crate::af_cloud::impls::{
@ -16,38 +22,178 @@ use crate::af_cloud::impls::{
}; };
use crate::AppFlowyServer; use crate::AppFlowyServer;
pub(crate) type AFCloudClient = RwLock<client_api::Client>;
pub struct AFCloudServer { pub struct AFCloudServer {
#[allow(dead_code)]
pub(crate) config: AFCloudConfiguration, 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 { impl AFCloudServer {
pub fn new(config: AFCloudConfiguration) -> Self { pub fn new(
Self { config } 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 { 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> { 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> { 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> { 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> { 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>> { fn collab_ws_channel(
None &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>> { fn file_storage(&self) -> Option<Arc<dyn FileStorageService>> {
None 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 af_cloud;
pub mod local_server; pub mod local_server;
mod request; // mod request;
mod response; mod response;
mod server; mod server;
pub mod supabase; pub mod supabase;

View File

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

View File

@ -1,5 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use client_api::ws::WebSocketChannel;
use collab_define::CollabObject; use collab_define::CollabObject;
use collab_plugins::cloud_storage::RemoteCollabStorage; use collab_plugins::cloud_storage::RemoteCollabStorage;
use parking_lot::RwLock; use parking_lot::RwLock;
@ -9,6 +10,7 @@ use flowy_document_deps::cloud::DocumentCloudService;
use flowy_folder_deps::cloud::FolderCloudService; use flowy_folder_deps::cloud::FolderCloudService;
use flowy_storage::FileStorageService; use flowy_storage::FileStorageService;
use flowy_user_deps::cloud::UserCloudService; use flowy_user_deps::cloud::UserCloudService;
use lib_infra::future::FutureResult;
pub trait AppFlowyEncryption: Send + Sync + 'static { pub trait AppFlowyEncryption: Send + Sync + 'static {
fn get_secret(&self) -> Option<String>; fn get_secret(&self) -> Option<String>;
@ -85,7 +87,16 @@ pub trait AppFlowyServer: Send + Sync + 'static {
/// # Returns /// # Returns
/// ///
/// An `Option` that might contain an `Arc` wrapping the `RemoteCollabStorage` interface. /// 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>>; 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> { async fn get_all_updates(&self, object: &CollabObject) -> Result<Vec<Vec<u8>>, Error> {
let postgrest = self.server.try_get_weak_postgrest()?; let postgrest = self.server.try_get_weak_postgrest()?;
let action = let action = FetchObjectUpdateAction::new(
FetchObjectUpdateAction::new(object.object_id.clone(), object.ty.clone(), postgrest); object.object_id.clone(),
object.collab_type.clone(),
postgrest,
);
let updates = action.run().await?; let updates = action.run().await?;
Ok(updates) Ok(updates)
} }
@ -140,10 +143,14 @@ where
update: Vec<u8>, update: Vec<u8>,
) -> Result<(), Error> { ) -> Result<(), Error> {
if let Some(postgrest) = self.server.get_postgrest() { if let Some(postgrest) = self.server.get_postgrest() {
let workspace_id = object.get_workspace_id().ok_or(anyhow::anyhow!( send_update(
"Can't get the workspace id in CollabObject" object.workspace_id.clone(),
))?; object,
send_update(workspace_id, object, update, &postgrest, &self.secret()).await?; update,
&postgrest,
&self.secret(),
)
.await?;
} }
Ok(()) Ok(())
@ -156,16 +163,14 @@ where
init_update: Vec<u8>, init_update: Vec<u8>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let postgrest = self.server.try_get_postgrest()?; 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 the update_items is empty, we can send the init_update directly
if update_items.is_empty() { if update_items.is_empty() {
send_update( send_update(
workspace_id, object.workspace_id.clone(),
object, object,
init_update, init_update,
&postgrest, &postgrest,
@ -197,18 +202,13 @@ pub(crate) async fn flush_collab_with_update(
) -> Result<(), Error> { ) -> Result<(), Error> {
// 2.Merge the updates into one and then delete the merged updates // 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 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 value_size = merge_result.new_update.len() as i32;
let md5 = md5(&merge_result.new_update); let md5 = md5(&merge_result.new_update);
tracing::trace!( tracing::trace!(
"Flush collab id:{} type:{} is_encrypt: {}", "Flush collab id:{} type:{} is_encrypt: {}",
object.object_id, object.object_id,
object.ty, object.collab_type,
secret.is_some() secret.is_some()
); );
let (new_update, encrypt) = let (new_update, encrypt) =
@ -219,11 +219,11 @@ pub(crate) async fn flush_collab_with_update(
.insert("encrypt", encrypt) .insert("encrypt", encrypt)
.insert("md5", md5) .insert("md5", md5)
.insert("value_size", value_size) .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("uid", object.uid)
.insert("workspace_id", workspace_id) .insert("workspace_id", &object.workspace_id)
.insert("removed_keys", merge_result.merged_keys) .insert("removed_keys", merge_result.merged_keys)
.insert("did", object.get_device_id()) .insert("did", &object.device_id)
.build(); .build();
postgrest postgrest
@ -247,13 +247,13 @@ pub(crate) async fn send_update(
let (update, encrypt) = SupabaseBinaryColumnEncoder::encode(update, encryption_secret)?; let (update, encrypt) = SupabaseBinaryColumnEncoder::encode(update, encryption_secret)?;
let builder = InsertParamsBuilder::new() let builder = InsertParamsBuilder::new()
.insert("oid", object.object_id.clone()) .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("value", update)
.insert("encrypt", encrypt) .insert("encrypt", encrypt)
.insert("uid", object.uid) .insert("uid", object.uid)
.insert("md5", md5) .insert("md5", md5)
.insert("workspace_id", workspace_id) .insert("workspace_id", workspace_id)
.insert("did", object.get_device_id()) .insert("did", &object.device_id)
.insert("value_size", value_size); .insert("value_size", value_size);
let params = builder.build(); let params = builder.build();

View File

@ -150,7 +150,7 @@ pub async fn create_snapshot(
.insert( .insert(
InsertParamsBuilder::new() InsertParamsBuilder::new()
.insert(AF_COLLAB_SNAPSHOT_OID_COLUMN, object.object_id.clone()) .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_ENCRYPT_COLUMN, encrypt)
.insert(AF_COLLAB_SNAPSHOT_BLOB_COLUMN, snapshot) .insert(AF_COLLAB_SNAPSHOT_BLOB_COLUMN, snapshot)
.insert(AF_COLLAB_SNAPSHOT_BLOB_SIZE_COLUMN, value_size) .insert(AF_COLLAB_SNAPSHOT_BLOB_SIZE_COLUMN, value_size)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@ use collab::core::collab::MutexCollab;
use collab::core::origin::CollabOrigin; use collab::core::origin::CollabOrigin;
use collab::preclude::updates::decoder::Decode; use collab::preclude::updates::decoder::Decode;
use collab::preclude::{merge_updates_v1, JsonValue, Update}; 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::entities::{DatabasePB, DatabaseViewIdPB, RepeatedDatabaseSnapshotPB};
use flowy_database2::event_map::DatabaseEvent::*; use flowy_database2::event_map::DatabaseEvent::*;

View File

@ -2,6 +2,7 @@ use std::collections::HashMap;
use assert_json_diff::assert_json_eq; use assert_json_diff::assert_json_eq;
use collab_database::rows::database_row_document_id_from_row_id; use collab_database::rows::database_row_document_id_from_row_id;
use collab_define::CollabType;
use collab_document::blocks::DocumentData; use collab_document::blocks::DocumentData;
use collab_folder::core::FolderData; use collab_folder::core::FolderData;
use nanoid::nanoid; use nanoid::nanoid;
@ -9,7 +10,7 @@ use serde_json::json;
use flowy_core::DEFAULT_NAME; use flowy_core::DEFAULT_NAME;
use flowy_encrypt::decrypt_text; 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::document::document_event::DocumentEventTest;
use flowy_test::event_builder::EventBuilder; use flowy_test::event_builder::EventBuilder;
use flowy_test::FlowyCoreTest; use flowy_test::FlowyCoreTest;

View File

@ -7,12 +7,12 @@ edition = "2021"
[dependencies] [dependencies]
lib-infra = { path = "../../../shared-lib/lib-infra" } lib-infra = { path = "../../../shared-lib/lib-infra" }
flowy-error = { path = "../flowy-error" } flowy-error = { workspace = true }
uuid = { version = "1.3.3", features = ["v4"] } uuid = { version = "1.3.3", features = ["v4"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
collab-define = { version = "0.1.0" } collab-define = { version = "0.1.0" }
serde_json = {version = "1.0"} serde_json = { version = "1.0"}
serde_repr = "0.1" 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" anyhow = "1.0.71"
tokio = { version = "1.26", features = ["sync"] } tokio = { version = "1.26", features = ["sync"] }

View File

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

View File

@ -453,6 +453,7 @@ pub async fn reset_workspace_handler(
"The workspace id is empty", "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(()) Ok(())
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,11 @@
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use appflowy_integrate::RocksCollabDB;
use collab::core::collab::{CollabRawData, MutexCollab}; use collab::core::collab::{CollabRawData, MutexCollab};
use collab_define::reminder::Reminder; use collab_define::reminder::Reminder;
use collab_define::CollabType; use collab_define::CollabType;
use collab_user::core::{MutexUserAwareness, UserAwareness}; use collab_user::core::{MutexUserAwareness, UserAwareness};
use collab_integrate::RocksCollabDB;
use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use crate::entities::ReminderPB; 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 /// 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. /// open a workspace on a new device that hasn't fully synchronized with the server.
pub async fn reset_workspace(&self, reset: ResetWorkspacePB) -> FlowyResult<()> { pub async fn reset_workspace(
let collab_object = &self,
CollabObject::new(reset.uid, reset.workspace_id.clone(), CollabType::Folder) reset: ResetWorkspacePB,
.with_workspace_id(reset.workspace_id); device_id: String,
) -> FlowyResult<()> {
let collab_object = CollabObject::new(
reset.uid,
reset.workspace_id.clone(),
CollabType::Folder,
reset.workspace_id.clone(),
device_id,
);
self self
.cloud_services .cloud_services
.get_user_service()? .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", "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]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]] [[package]]
name = "basic-toml" name = "basic-toml"
version = "0.1.2" version = "0.1.2"
@ -99,15 +82,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 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]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@ -133,12 +107,6 @@ version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.4.0" version = "1.4.0"
@ -327,22 +295,13 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" 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]] [[package]]
name = "digest" name = "digest"
version = "0.10.6" version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [ dependencies = [
"block-buffer 0.10.4", "block-buffer",
"crypto-common", "crypto-common",
] ]
@ -358,19 +317,6 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 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]] [[package]]
name = "errno" name = "errno"
version = "0.3.1" version = "0.3.1"
@ -479,104 +425,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 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]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.26" version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" 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]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.4" version = "0.14.4"
@ -669,23 +523,6 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 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]] [[package]]
name = "humansize" name = "humansize"
version = "2.1.3" version = "2.1.3"
@ -695,12 +532,6 @@ dependencies = [
"libm", "libm",
] ]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.53" version = "0.1.53"
@ -725,16 +556,6 @@ dependencies = [
"cxx-build", "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]] [[package]]
name = "ignore" name = "ignore"
version = "0.4.20" version = "0.4.20"
@ -851,32 +672,6 @@ dependencies = [
"tracing", "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]] [[package]]
name = "libc" name = "libc"
version = "0.2.139" version = "0.2.139"
@ -981,12 +776,6 @@ version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]] [[package]]
name = "os_pipe" name = "os_pipe"
version = "0.9.2" version = "0.9.2"
@ -1188,12 +977,6 @@ version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.15" version = "0.2.15"
@ -1523,30 +1306,6 @@ dependencies = [
"serde", "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]] [[package]]
name = "sha2" name = "sha2"
version = "0.10.6" version = "0.10.6"
@ -1555,7 +1314,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
"digest 0.10.6", "digest",
] ]
[[package]] [[package]]
@ -1579,12 +1338,6 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[package]]
name = "slab"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
[[package]] [[package]]
name = "slug" name = "slug"
version = "0.1.4" version = "0.1.4"
@ -1734,21 +1487,6 @@ dependencies = [
"once_cell", "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]] [[package]]
name = "tokio" name = "tokio"
version = "1.26.0" version = "1.26.0"
@ -1780,19 +1518,6 @@ dependencies = [
"syn 1.0.109", "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]] [[package]]
name = "toml" name = "toml"
version = "0.5.11" version = "0.5.11"
@ -1850,25 +1575,6 @@ dependencies = [
"termcolor", "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]] [[package]]
name = "typenum" name = "typenum"
version = "1.14.0" version = "1.14.0"
@ -1940,27 +1646,12 @@ dependencies = [
"unic-common", "unic-common",
] ]
[[package]]
name = "unicode-bidi"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.5" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 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]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.10.1" version = "1.10.1"
@ -1973,23 +1664,6 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 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]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.3" version = "0.9.3"

View File

@ -1,7 +1,6 @@
[workspace] [workspace]
members = [ members = [
"lib-ot", "lib-ot",
"lib-ws",
"lib-infra", "lib-infra",
"flowy-derive", "flowy-derive",
"flowy-ast", "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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [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" } bytes = { version = "1.4" }
pin-project = "1.0.12" pin-project = "1.0.12"
futures-core = { version = "0.3" } 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);
}
}