fix: af cloud sync auth (#3873)

* feat: open workspace

* chore: update env docs

* fix: invalid user callback

* fix: token invalid

* chore: update

* chore: update

* chore: update

* chore: fix test

* chore: fix tauri build
This commit is contained in:
Nathan.fooo 2023-11-05 14:00:24 +08:00 committed by GitHub
parent b35d6131d4
commit 1025b6d553
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 658 additions and 478 deletions

View File

@ -1,23 +1,33 @@
# Initial Setup # Initial Setup
# 1. Copy the dev.env file to .env:
# cp dev.env .env
# 2. Alternatively, you can generate the .env file using the "Generate Env File" task in VSCode.
# Configuring Cloud Type # 1. Copy the dev.env file to .env:
# This configuration file is used to specify the cloud type and the necessary configurations for each cloud type. The available options are: # cp dev.env .env
# Update the environment parameters as needed.
# 2. Generate the env.dart from this .env file:
# You can use the "Generate Env File" task in VSCode.
# Alternatively, execute the following commands:
# cd appflowy_flutter
# dart run build_runner clean && dart run build_runner build --delete-conflicting-outputs
# Cloud Type Configuration
# Use this configuration file to specify the cloud type and its associated settings. The available cloud types are:
# Local: 0 # Local: 0
# Supabase: 1 # Supabase: 1
# AppFlowy Cloud: 2 # AppFlowy Cloud: 2
# By default, it's set to Local.
CLOUD_TYPE=0 CLOUD_TYPE=0
# Supabase Configuration # Supabase Configuration
# If you're using Supabase (CLOUD_TYPE=1), you need to provide the following configurations: # If using Supabase (CLOUD_TYPE=1), provide the following details:
SUPABASE_URL=replace-with-your-supabase-url SUPABASE_URL=replace-with-your-supabase-url
SUPABASE_ANON_KEY=replace-with-your-supabase-key SUPABASE_ANON_KEY=replace-with-your-supabase-key
# AppFlowy Cloud Configuration # AppFlowy Cloud Configuration
# If you're using AppFlowy Cloud (CLOUD_TYPE=2), you need to provide the following configurations: # If using AppFlowy Cloud (CLOUD_TYPE=2), provide the following details:
APPFLOWY_CLOUD_BASE_URL=replace-with-your-appflowy-cloud-url APPFLOWY_CLOUD_BASE_URL=replace-with-your-appflowy-cloud-url
APPFLOWY_CLOUD_WS_BASE_URL=replace-with-your-appflowy-cloud-ws-url APPFLOWY_CLOUD_WS_BASE_URL=replace-with-your-appflowy-cloud-ws-url
APPFLOWY_CLOUD_GOTRUE_URL=replace-with-your-appflowy-cloud-gotrue-url APPFLOWY_CLOUD_GOTRUE_URL=replace-with-your-appflowy-cloud-gotrue-url

View File

@ -1,10 +1,7 @@
import 'package:json_annotation/json_annotation.dart'; // ignore_for_file: non_constant_identifier_names
// Run `dart run build_runner build` to generate the json serialization If the import 'package:json_annotation/json_annotation.dart';
// file `env_serde.g.dart` is existed, delete it first. part 'backend_env.g.dart';
//
// the file `env_serde.g.dart` will be generated in the same directory.
part 'env_serde.g.dart';
@JsonSerializable() @JsonSerializable()
class AppFlowyEnv { class AppFlowyEnv {

View File

@ -1,8 +1,9 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:appflowy/env/backend_env.dart';
import 'package:appflowy/env/env.dart'; import 'package:appflowy/env/env.dart';
import 'package:appflowy_backend/appflowy_backend.dart'; import 'package:appflowy_backend/appflowy_backend.dart';
import 'package:appflowy_backend/env_serde.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
@ -23,8 +24,10 @@ class InitRustSDKTask extends LaunchTask {
Future<void> initialize(LaunchContext context) async { Future<void> initialize(LaunchContext context) async {
final dir = directory ?? await appFlowyApplicationDataDirectory(); final dir = directory ?? await appFlowyApplicationDataDirectory();
// Pass the environment variables to the Rust SDK
final env = getAppFlowyEnv(); final env = getAppFlowyEnv();
context.getIt<FlowySDK>().setEnv(env); context.getIt<FlowySDK>().setEnv(jsonEncode(env.toJson()));
await context.getIt<FlowySDK>().init(dir); await context.getIt<FlowySDK>().init(dir);
} }

View File

@ -100,14 +100,9 @@ class UserBackendService {
return Future.value(left([])); return Future.value(left([]));
} }
Future<Either<WorkspacePB, FlowyError>> openWorkspace(String workspaceId) { Future<Either<Unit, FlowyError>> openWorkspace(String workspaceId) {
final request = WorkspaceIdPB.create()..value = workspaceId; final payload = UserWorkspaceIdPB.create()..workspaceId = workspaceId;
return FolderEventOpenWorkspace(request).send().then((result) { return UserEventOpenWorkspace(payload).send();
return result.fold(
(workspace) => left(workspace),
(error) => right(error),
);
});
} }
Future<Either<WorkspacePB, FlowyError>> getCurrentWorkspace() { Future<Either<WorkspacePB, FlowyError>> getCurrentWorkspace() {

View File

@ -101,6 +101,5 @@ Widget _renderCreateButton(BuildContext context) {
// same method as in mobile // same method as in mobile
void _popToWorkspace(BuildContext context, WorkspacePB workspace) { void _popToWorkspace(BuildContext context, WorkspacePB workspace) {
context.read<WorkspaceBloc>().add(WorkspaceEvent.openWorkspace(workspace));
context.pop(workspace.id); context.pop(workspace.id);
} }

View File

@ -6,7 +6,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
// TODO(yijing): needs refactor when multiple workspaces are supported // TODO(yijing): needs refactor when multiple workspaces are supported
@ -139,6 +138,5 @@ class _MobileWorkspaceStartScreenState
// same method as in desktop // same method as in desktop
void _popToWorkspace(BuildContext context, WorkspacePB workspace) { void _popToWorkspace(BuildContext context, WorkspacePB workspace) {
context.read<WorkspaceBloc>().add(WorkspaceEvent.openWorkspace(workspace));
context.pop(workspace.id); context.pop(workspace.id);
} }

View File

@ -19,9 +19,6 @@ class WorkspaceBloc extends Bloc<WorkspaceEvent, WorkspaceState> {
initial: (e) async { initial: (e) async {
await _fetchWorkspaces(emit); await _fetchWorkspaces(emit);
}, },
openWorkspace: (e) async {
await _openWorkspace(e.workspace, emit);
},
createWorkspace: (e) async { createWorkspace: (e) async {
await _createWorkspace(e.name, e.desc, emit); await _createWorkspace(e.name, e.desc, emit);
}, },
@ -57,22 +54,6 @@ class WorkspaceBloc extends Bloc<WorkspaceEvent, WorkspaceState> {
); );
} }
Future<void> _openWorkspace(
WorkspacePB workspace,
Emitter<WorkspaceState> emit,
) async {
final result = await userService.openWorkspace(workspace.id);
emit(
result.fold(
(workspaces) => state.copyWith(successOrFailure: left(unit)),
(error) {
Log.error(error);
return state.copyWith(successOrFailure: right(error));
},
),
);
}
Future<void> _createWorkspace( Future<void> _createWorkspace(
String name, String name,
String desc, String desc,
@ -98,8 +79,6 @@ class WorkspaceEvent with _$WorkspaceEvent {
const factory WorkspaceEvent.initial() = Initial; const factory WorkspaceEvent.initial() = Initial;
const factory WorkspaceEvent.createWorkspace(String name, String desc) = const factory WorkspaceEvent.createWorkspace(String name, String desc) =
CreateWorkspace; CreateWorkspace;
const factory WorkspaceEvent.openWorkspace(WorkspacePB workspace) =
OpenWorkspace;
const factory WorkspaceEvent.workspacesReveived( const factory WorkspaceEvent.workspacesReveived(
Either<List<WorkspacePB>, FlowyError> workspacesOrFail, Either<List<WorkspacePB>, FlowyError> workspacesOrFail,
) = WorkspacesReceived; ) = WorkspacesReceived;

View File

@ -1,11 +1,9 @@
export 'package:async/async.dart'; export 'package:async/async.dart';
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:async'; import 'dart:async';
import 'package:appflowy_backend/rust_stream.dart'; import 'package:appflowy_backend/rust_stream.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'dart:ffi'; import 'dart:ffi';
import 'env_serde.dart';
import 'ffi.dart' as ffi; import 'ffi.dart' as ffi;
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
@ -37,8 +35,7 @@ class FlowySDK {
ffi.init_sdk(sdkDir.path.toNativeUtf8()); ffi.init_sdk(sdkDir.path.toNativeUtf8());
} }
void setEnv(AppFlowyEnv env) { void setEnv(String envStr) {
final jsonStr = jsonEncode(env.toJson()); ffi.set_env(envStr.toNativeUtf8());
ffi.set_env(jsonStr.toNativeUtf8());
} }
} }

View File

@ -135,6 +135,21 @@ 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 = "app-error"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [
"anyhow",
"reqwest",
"serde",
"serde_json",
"serde_repr",
"thiserror",
"url",
"uuid",
]
[[package]] [[package]]
name = "appflowy_tauri" name = "appflowy_tauri"
version = "0.0.0" version = "0.0.0"
@ -445,7 +460,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
dependencies = [ dependencies = [
"borsh-derive", "borsh-derive",
"hashbrown 0.12.3", "hashbrown 0.13.2",
] ]
[[package]] [[package]]
@ -753,9 +768,10 @@ dependencies = [
[[package]] [[package]]
name = "client-api" name = "client-api"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error",
"bytes", "bytes",
"collab", "collab",
"collab-entity", "collab-entity",
@ -845,7 +861,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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -864,7 +880,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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -894,7 +910,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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -906,7 +922,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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -926,7 +942,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-entity" name = "collab-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -940,7 +956,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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -982,7 +998,7 @@ dependencies = [
[[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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bincode", "bincode",
@ -1003,7 +1019,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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1030,7 +1046,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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -1429,9 +1445,10 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]] [[package]]
name = "database-entity" name = "database-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error",
"chrono", "chrono",
"collab-entity", "collab-entity",
"serde", "serde",
@ -2064,7 +2081,7 @@ dependencies = [
"nanoid", "nanoid",
"parking_lot", "parking_lot",
"protobuf", "protobuf",
"scraper 0.18.0", "scraper 0.18.1",
"serde", "serde",
"serde_json", "serde_json",
"strum_macros 0.21.1", "strum_macros 0.21.1",
@ -2778,7 +2795,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue" name = "gotrue"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"futures-util", "futures-util",
@ -2794,9 +2811,10 @@ dependencies = [
[[package]] [[package]]
name = "gotrue-entity" name = "gotrue-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error",
"jsonwebtoken", "jsonwebtoken",
"lazy_static", "lazy_static",
"reqwest", "reqwest",
@ -3229,7 +3247,7 @@ dependencies = [
[[package]] [[package]]
name = "infra" name = "infra"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"reqwest", "reqwest",
@ -3479,7 +3497,6 @@ dependencies = [
"tracing-appender", "tracing-appender",
"tracing-bunyan-formatter", "tracing-bunyan-formatter",
"tracing-core", "tracing-core",
"tracing-log 0.2.0",
"tracing-subscriber", "tracing-subscriber",
] ]
@ -4904,8 +4921,9 @@ dependencies = [
[[package]] [[package]]
name = "realtime-entity" name = "realtime-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [ dependencies = [
"anyhow",
"bytes", "bytes",
"collab", "collab",
"collab-entity", "collab-entity",
@ -5358,9 +5376,9 @@ dependencies = [
[[package]] [[package]]
name = "scraper" name = "scraper"
version = "0.18.0" version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3693f9a0203d49a7ba8f38aa915316b3d535c1862d03dae7009cb71a3408b36a" checksum = "585480e3719b311b78a573db1c9d9c4c1f8010c2dee4cc59c2efe58ea4dbc3e1"
dependencies = [ dependencies = [
"ahash 0.8.3", "ahash 0.8.3",
"cssparser 0.31.2", "cssparser 0.31.2",
@ -5642,9 +5660,10 @@ dependencies = [
[[package]] [[package]]
name = "shared_entity" name = "shared_entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error",
"collab-entity", "collab-entity",
"database-entity", "database-entity",
"gotrue-entity", "gotrue-entity",
@ -6689,7 +6708,7 @@ dependencies = [
"time", "time",
"tracing", "tracing",
"tracing-core", "tracing-core",
"tracing-log 0.1.3", "tracing-log",
"tracing-subscriber", "tracing-subscriber",
] ]
@ -6714,17 +6733,6 @@ dependencies = [
"tracing-core", "tracing-core",
] ]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]] [[package]]
name = "tracing-serde" name = "tracing-serde"
version = "0.1.3" version = "0.1.3"
@ -6752,7 +6760,7 @@ dependencies = [
"thread_local", "thread_local",
"tracing", "tracing",
"tracing-core", "tracing-core",
"tracing-log 0.1.3", "tracing-log",
"tracing-serde", "tracing-serde",
] ]

View File

@ -38,7 +38,7 @@ custom-protocol = ["tauri/custom-protocol"]
# Run the script: # Run the script:
# scripts/tool/update_client_api_rev.sh new_rev_id # scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c87d0f05e988a02e9272a42722b304289be320e4" } client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c739029d99517282d2ec1593523a47b51a85231b" }
# Please use the following script to update collab. # Please use the following script to update collab.
# Working directory: frontend # Working directory: frontend
# #
@ -48,14 +48,14 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c87
# To switch to the local path, run: # To switch to the local path, run:
# scripts/tool/update_collab_source.sh # scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }

View File

@ -15,13 +15,11 @@ import {
SignInPayloadPB, SignInPayloadPB,
SignUpPayloadPB, SignUpPayloadPB,
UpdateUserProfilePayloadPB, UpdateUserProfilePayloadPB,
WorkspaceIdPB,
WorkspacePB, WorkspacePB,
WorkspaceSettingPB, WorkspaceSettingPB,
} from '@/services/backend'; } from '@/services/backend';
import { import {
FolderEventCreateWorkspace, FolderEventCreateWorkspace,
FolderEventOpenWorkspace,
FolderEventGetCurrentWorkspaceSetting, FolderEventGetCurrentWorkspaceSetting,
FolderEventReadCurrentWorkspace, FolderEventReadCurrentWorkspace,
} from '@/services/backend/events/flowy-folder2'; } from '@/services/backend/events/flowy-folder2';
@ -67,12 +65,6 @@ export class UserBackendService {
return FolderEventReadCurrentWorkspace(); return FolderEventReadCurrentWorkspace();
}; };
openWorkspace = (workspaceId: string) => {
const payload = WorkspaceIdPB.fromObject({ value: workspaceId });
return FolderEventOpenWorkspace(payload);
};
createWorkspace = async (params: { name: string; desc: string }): Promise<WorkspacePB> => { createWorkspace = async (params: { name: string; desc: string }): Promise<WorkspacePB> => {
const payload = CreateWorkspacePayloadPB.fromObject({ name: params.name, desc: params.desc }); const payload = CreateWorkspacePayloadPB.fromObject({ name: params.name, desc: params.desc });
const result = await FolderEventCreateWorkspace(payload); const result = await FolderEventCreateWorkspace(payload);

View File

@ -1,12 +1,12 @@
import { import {
FolderEventCreateWorkspace, FolderEventCreateWorkspace,
CreateWorkspacePayloadPB, CreateWorkspacePayloadPB,
FolderEventOpenWorkspace,
FolderEventDeleteWorkspace, FolderEventDeleteWorkspace,
WorkspaceIdPB, WorkspaceIdPB,
FolderEventReadWorkspaceViews, FolderEventReadWorkspaceViews,
FolderEventReadCurrentWorkspace, FolderEventReadCurrentWorkspace,
} from '@/services/backend/events/flowy-folder2'; } from '@/services/backend/events/flowy-folder2';
import { UserEventOpenWorkspace, UserWorkspaceIdPB } from '@/services/backend/events/flowy-user';
export class WorkspaceBackendService { export class WorkspaceBackendService {
constructor() { constructor() {
@ -24,11 +24,11 @@ export class WorkspaceBackendService {
}; };
openWorkspace = async (workspaceId: string) => { openWorkspace = async (workspaceId: string) => {
const payload = new WorkspaceIdPB({ const payload = new UserWorkspaceIdPB({
value: workspaceId, workspace_id: workspaceId,
}); });
return FolderEventOpenWorkspace(payload); return UserEventOpenWorkspace(payload);
}; };
deleteWorkspace = async (workspaceId: string) => { deleteWorkspace = async (workspaceId: string) => {

View File

@ -121,6 +121,21 @@ 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 = "app-error"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [
"anyhow",
"reqwest",
"serde",
"serde_json",
"serde_repr",
"thiserror",
"url",
"uuid",
]
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.5.2" version = "0.5.2"
@ -452,7 +467,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
dependencies = [ dependencies = [
"borsh-derive", "borsh-derive",
"hashbrown 0.12.3", "hashbrown 0.13.2",
] ]
[[package]] [[package]]
@ -651,9 +666,10 @@ dependencies = [
[[package]] [[package]]
name = "client-api" name = "client-api"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error",
"bytes", "bytes",
"collab", "collab",
"collab-entity", "collab-entity",
@ -712,7 +728,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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -731,7 +747,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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -761,7 +777,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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -773,7 +789,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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -793,7 +809,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-entity" name = "collab-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -807,7 +823,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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -849,7 +865,7 @@ dependencies = [
[[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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bincode", "bincode",
@ -870,7 +886,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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -897,7 +913,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=01f92f7bb#01f92f7bb204a3aeed24b345d504dfd36d3d9fcb" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bab20052#bab200529ef0306194fa8618cc8708878b01ce04"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -1256,9 +1272,10 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]] [[package]]
name = "database-entity" name = "database-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error",
"chrono", "chrono",
"collab-entity", "collab-entity",
"serde", "serde",
@ -1885,7 +1902,7 @@ dependencies = [
"nanoid", "nanoid",
"parking_lot", "parking_lot",
"protobuf", "protobuf",
"scraper 0.18.0", "scraper 0.18.1",
"serde", "serde",
"serde_json", "serde_json",
"strum_macros 0.21.1", "strum_macros 0.21.1",
@ -2437,7 +2454,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue" name = "gotrue"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"futures-util", "futures-util",
@ -2453,9 +2470,10 @@ dependencies = [
[[package]] [[package]]
name = "gotrue-entity" name = "gotrue-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error",
"jsonwebtoken", "jsonwebtoken",
"lazy_static", "lazy_static",
"reqwest", "reqwest",
@ -2813,7 +2831,7 @@ dependencies = [
[[package]] [[package]]
name = "infra" name = "infra"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"reqwest", "reqwest",
@ -2979,7 +2997,6 @@ dependencies = [
"tracing-appender", "tracing-appender",
"tracing-bunyan-formatter", "tracing-bunyan-formatter",
"tracing-core", "tracing-core",
"tracing-log 0.2.0",
"tracing-subscriber", "tracing-subscriber",
] ]
@ -4254,8 +4271,9 @@ dependencies = [
[[package]] [[package]]
name = "realtime-entity" name = "realtime-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [ dependencies = [
"anyhow",
"bytes", "bytes",
"collab", "collab",
"collab-entity", "collab-entity",
@ -4705,9 +4723,9 @@ dependencies = [
[[package]] [[package]]
name = "scraper" name = "scraper"
version = "0.18.0" version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3693f9a0203d49a7ba8f38aa915316b3d535c1862d03dae7009cb71a3408b36a" checksum = "585480e3719b311b78a573db1c9d9c4c1f8010c2dee4cc59c2efe58ea4dbc3e1"
dependencies = [ dependencies = [
"ahash 0.8.3", "ahash 0.8.3",
"cssparser", "cssparser",
@ -4810,9 +4828,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.105" version = "1.0.108"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -4891,9 +4909,10 @@ dependencies = [
[[package]] [[package]]
name = "shared_entity" name = "shared_entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c87d0f05e988a02e9272a42722b304289be320e4#c87d0f05e988a02e9272a42722b304289be320e4" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c739029d99517282d2ec1593523a47b51a85231b#c739029d99517282d2ec1593523a47b51a85231b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error",
"collab-entity", "collab-entity",
"database-entity", "database-entity",
"gotrue-entity", "gotrue-entity",
@ -5654,7 +5673,7 @@ dependencies = [
"time", "time",
"tracing", "tracing",
"tracing-core", "tracing-core",
"tracing-log 0.1.3", "tracing-log",
"tracing-subscriber", "tracing-subscriber",
] ]
@ -5679,17 +5698,6 @@ dependencies = [
"tracing-core", "tracing-core",
] ]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]] [[package]]
name = "tracing-serde" name = "tracing-serde"
version = "0.1.3" version = "0.1.3"
@ -5717,7 +5725,7 @@ dependencies = [
"thread_local", "thread_local",
"tracing", "tracing",
"tracing-core", "tracing-core",
"tracing-log 0.1.3", "tracing-log",
"tracing-serde", "tracing-serde",
] ]

View File

@ -82,7 +82,7 @@ incremental = false
# Run the script: # Run the script:
# scripts/tool/update_client_api_rev.sh new_rev_id # scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c87d0f05e988a02e9272a42722b304289be320e4" } client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c739029d99517282d2ec1593523a47b51a85231b" }
# Please use the following script to update collab. # Please use the following script to update collab.
# Working directory: frontend # Working directory: frontend
# #
@ -92,11 +92,11 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c87
# To switch to the local path, run: # To switch to the local path, run:
# scripts/tool/update_collab_source.sh # scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "01f92f7bb" } collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "bab20052" }

View File

@ -144,19 +144,10 @@ pub struct ViewTest {
pub workspace: WorkspacePB, pub workspace: WorkspacePB,
pub child_view: ViewPB, pub child_view: ViewPB,
} }
impl ViewTest { impl ViewTest {
#[allow(dead_code)] #[allow(dead_code)]
pub async fn new(sdk: &EventIntegrationTest, layout: ViewLayoutPB, data: Vec<u8>) -> Self { pub async fn new(sdk: &EventIntegrationTest, layout: ViewLayoutPB, data: Vec<u8>) -> Self {
let workspace = sdk.folder_manager.get_current_workspace().await.unwrap(); let workspace = sdk.folder_manager.get_current_workspace().await.unwrap();
let payload = WorkspaceIdPB {
value: workspace.id.clone(),
};
let _ = EventBuilder::new(sdk.clone())
.event(OpenWorkspace)
.payload(payload)
.async_send()
.await;
let payload = CreateViewPayloadPB { let payload = CreateViewPayloadPB {
parent_view_id: workspace.id.clone(), parent_view_id: workspace.id.clone(),

View File

@ -22,7 +22,7 @@ async fn af_cloud_edit_document_test() {
let rx = test let rx = test
.notification_sender .notification_sender
.subscribe_with_condition::<DocumentSyncStatePB, _>(&document_id, |pb| pb.is_finish); .subscribe_with_condition::<DocumentSyncStatePB, _>(&document_id, |pb| pb.is_finish);
receive_with_timeout(rx, Duration::from_secs(15)) receive_with_timeout(rx, Duration::from_secs(25))
.await .await
.unwrap(); .unwrap();

View File

@ -5,7 +5,6 @@ use std::sync::{Arc, Weak};
use parking_lot::RwLock; use parking_lot::RwLock;
use serde_repr::*; use serde_repr::*;
use collab_integrate::YrsDocAction;
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
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};
@ -14,9 +13,7 @@ use flowy_server::{AppFlowyEncryption, AppFlowyServer, EncryptionImpl};
use flowy_server_config::af_cloud_config::AFCloudConfiguration; use flowy_server_config::af_cloud_config::AFCloudConfiguration;
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_user::services::database::{ use flowy_user::services::database::{get_user_profile, get_user_workspace, 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::*;
@ -195,14 +192,4 @@ impl LocalServerDB for LocalServerDBImpl {
let user_workspace = get_user_workspace(&sqlite_db, uid)?; let user_workspace = get_user_workspace(&sqlite_db, uid)?;
Ok(user_workspace) Ok(user_workspace)
} }
fn get_collab_updates(&self, uid: i64, object_id: &str) -> Result<Vec<Vec<u8>>, FlowyError> {
let collab_db = open_collab_db(&self.storage_path, uid)?;
let read_txn = collab_db.read_txn();
let updates = read_txn.get_all_updates(uid, object_id).map_err(|e| {
FlowyError::internal().with_context(format!("Failed to open collab db: {:?}", e))
})?;
Ok(updates)
}
} }

View File

@ -17,7 +17,9 @@ use flowy_database_deps::cloud::{
use flowy_document2::deps::DocumentData; use flowy_document2::deps::DocumentData;
use flowy_document_deps::cloud::{DocumentCloudService, DocumentSnapshot}; use flowy_document_deps::cloud::{DocumentCloudService, DocumentSnapshot};
use flowy_error::FlowyError; use flowy_error::FlowyError;
use flowy_folder_deps::cloud::{FolderCloudService, FolderData, FolderSnapshot, Workspace}; use flowy_folder_deps::cloud::{
FolderCloudService, FolderData, FolderSnapshot, Workspace, WorkspaceRecord,
};
use flowy_storage::{FileStorageService, StorageObject}; use flowy_storage::{FileStorageService, StorageObject};
use flowy_user::event_map::UserCloudServiceProvider; use flowy_user::event_map::UserCloudServiceProvider;
use flowy_user_deps::cloud::UserCloudService; use flowy_user_deps::cloud::UserCloudService;
@ -140,6 +142,17 @@ impl FolderCloudService for ServerProvider {
FutureResult::new(async move { server?.folder_service().create_workspace(uid, &name).await }) FutureResult::new(async move { server?.folder_service().create_workspace(uid, &name).await })
} }
fn open_workspace(&self, workspace_id: &str) -> FutureResult<(), Error> {
let workspace_id = workspace_id.to_string();
let server = self.get_server(&self.get_server_type());
FutureResult::new(async move { server?.folder_service().open_workspace(&workspace_id).await })
}
fn get_all_workspace(&self) -> FutureResult<Vec<WorkspaceRecord>, Error> {
let server = self.get_server(&self.get_server_type());
FutureResult::new(async move { server?.folder_service().get_all_workspace().await })
}
fn get_folder_data( fn get_folder_data(
&self, &self,
workspace_id: &str, workspace_id: &str,
@ -245,7 +258,7 @@ impl DocumentCloudService for ServerProvider {
&self, &self,
document_id: &str, document_id: &str,
workspace_id: &str, workspace_id: &str,
) -> FutureResult<Vec<Vec<u8>>, Error> { ) -> FutureResult<Vec<Vec<u8>>, FlowyError> {
let workspace_id = workspace_id.to_string(); let workspace_id = workspace_id.to_string();
let document_id = document_id.to_string(); let document_id = document_id.to_string();
let server = self.get_server(&self.get_server_type()); let server = self.get_server(&self.get_server_type());
@ -308,7 +321,7 @@ impl CollabStorageProvider for ServerProvider {
to_fut(async move { to_fut(async move {
let mut plugins: Vec<Arc<dyn CollabPlugin>> = vec![]; let mut plugins: Vec<Arc<dyn CollabPlugin>> = vec![];
match server.collab_ws_channel(&collab_object.object_id).await { match server.collab_ws_channel(&collab_object.object_id).await {
Ok(Some((channel, ws_connect_state))) => { Ok(Some((channel, ws_connect_state, is_connected))) => {
let origin = CollabOrigin::Client(CollabClient::new( let origin = CollabOrigin::Client(CollabClient::new(
collab_object.uid, collab_object.uid,
collab_object.device_id.clone(), collab_object.device_id.clone(),
@ -316,7 +329,7 @@ impl CollabStorageProvider for ServerProvider {
let sync_object = SyncObject::from(collab_object); let sync_object = SyncObject::from(collab_object);
let (sink, stream) = (channel.sink(), channel.stream()); let (sink, stream) = (channel.sink(), channel.stream());
let sink_config = SinkConfig::new() let sink_config = SinkConfig::new()
.send_timeout(6) .send_timeout(8)
.with_strategy(SinkStrategy::FixInterval(Duration::from_secs(2))); .with_strategy(SinkStrategy::FixInterval(Duration::from_secs(2)));
let sync_plugin = SyncPlugin::new( let sync_plugin = SyncPlugin::new(
origin, origin,
@ -326,6 +339,7 @@ impl CollabStorageProvider for ServerProvider {
sink_config, sink_config,
stream, stream,
Some(channel), Some(channel),
!is_connected,
ws_connect_state, ws_connect_state,
); );
plugins.push(Arc::new(sync_plugin)); plugins.push(Arc::new(sync_plugin));

View File

@ -1,12 +1,13 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Context; use anyhow::Context;
use tracing::event;
use collab_integrate::collab_builder::AppFlowyCollabBuilder; use collab_integrate::collab_builder::AppFlowyCollabBuilder;
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;
use flowy_folder2::manager::{FolderInitializeDataSource, FolderManager}; use flowy_folder2::manager::{FolderInitDataSource, FolderManager};
use flowy_user::event_map::{UserCloudServiceProvider, UserStatusCallback}; use flowy_user::event_map::{UserCloudServiceProvider, UserStatusCallback};
use flowy_user_deps::cloud::UserCloudConfig; use flowy_user_deps::cloud::UserCloudConfig;
use flowy_user_deps::entities::{AuthType, UserProfile, UserWorkspace}; use flowy_user_deps::entities::{AuthType, UserProfile, UserWorkspace};
@ -59,7 +60,7 @@ impl UserStatusCallback for UserStatusCallbackImpl {
.initialize( .initialize(
user_id, user_id,
&user_workspace.id, &user_workspace.id,
FolderInitializeDataSource::LocalDisk { FolderInitDataSource::LocalDisk {
create_if_not_exist: false, create_if_not_exist: false,
}, },
) )
@ -82,8 +83,9 @@ impl UserStatusCallback for UserStatusCallbackImpl {
&self, &self,
user_id: i64, user_id: i64,
user_workspace: &UserWorkspace, user_workspace: &UserWorkspace,
_device_id: &str, device_id: &str,
) -> Fut<FlowyResult<()>> { ) -> Fut<FlowyResult<()>> {
let device_id = device_id.to_owned();
let user_id = user_id.to_owned(); let user_id = user_id.to_owned();
let user_workspace = user_workspace.clone(); let user_workspace = user_workspace.clone();
let folder_manager = self.folder_manager.clone(); let folder_manager = self.folder_manager.clone();
@ -91,6 +93,13 @@ impl UserStatusCallback for UserStatusCallbackImpl {
let document_manager = self.document_manager.clone(); let document_manager = self.document_manager.clone();
to_fut(async move { to_fut(async move {
event!(
tracing::Level::TRACE,
"Notify did sign in: latest_workspace: {:?}, device_id: {}",
user_workspace,
device_id
);
folder_manager folder_manager
.initialize_with_workspace_id(user_id, &user_workspace.id) .initialize_with_workspace_id(user_id, &user_workspace.id)
.await?; .await?;
@ -113,8 +122,9 @@ impl UserStatusCallback for UserStatusCallbackImpl {
is_new_user: bool, is_new_user: bool,
user_profile: &UserProfile, user_profile: &UserProfile,
user_workspace: &UserWorkspace, user_workspace: &UserWorkspace,
_device_id: &str, device_id: &str,
) -> Fut<FlowyResult<()>> { ) -> Fut<FlowyResult<()>> {
let device_id = device_id.to_owned();
let user_profile = user_profile.clone(); let user_profile = user_profile.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();
@ -122,12 +132,20 @@ impl UserStatusCallback for UserStatusCallbackImpl {
let document_manager = self.document_manager.clone(); let document_manager = self.document_manager.clone();
to_fut(async move { to_fut(async move {
event!(
tracing::Level::TRACE,
"Notify did sign up: is new: {} user_workspace: {:?}, device_id: {}",
is_new_user,
user_workspace,
device_id
);
folder_manager folder_manager
.initialize_with_new_user( .initialize_with_new_user(
user_profile.uid, user_profile.uid,
&user_profile.token, &user_profile.token,
is_new_user, is_new_user,
FolderInitializeDataSource::LocalDisk { FolderInitDataSource::LocalDisk {
create_if_not_exist: true, create_if_not_exist: true,
}, },
&user_workspace.id, &user_workspace.id,

View File

@ -196,7 +196,6 @@ impl AppFlowyCore {
let cloned_user_session = Arc::downgrade(&user_manager); let cloned_user_session = Arc::downgrade(&user_manager);
if let Some(user_session) = cloned_user_session.upgrade() { if let Some(user_session) = cloned_user_session.upgrade() {
event!(tracing::Level::DEBUG, "init user session",);
if let Err(err) = user_session if let Err(err) = user_session
.init(user_status_callback, collab_interact_impl) .init(user_status_callback, collab_interact_impl)
.await .await

View File

@ -13,7 +13,7 @@ use collab_database::views::{CreateDatabaseParams, CreateViewParams, DatabaseLay
use collab_entity::CollabType; use collab_entity::CollabType;
use futures::executor::block_on; use futures::executor::block_on;
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tracing::{instrument, trace}; use tracing::{event, instrument, trace};
use collab_integrate::collab_builder::AppFlowyCollabBuilder; use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use collab_integrate::{CollabPersistenceConfig, RocksCollabDB}; use collab_integrate::{CollabPersistenceConfig, RocksCollabDB};
@ -80,6 +80,11 @@ impl DatabaseManager {
workspace_id: String, workspace_id: String,
database_views_aggregate_id: String, database_views_aggregate_id: String,
) -> FlowyResult<()> { ) -> FlowyResult<()> {
// Clear all existing tasks
self.task_scheduler.write().await.clear_task();
// Release all existing editors
self.editors.write().await.clear();
let collab_db = self.user.collab_db(uid)?; let collab_db = self.user.collab_db(uid)?;
let collab_builder = UserDatabaseCollabServiceImpl { let collab_builder = UserDatabaseCollabServiceImpl {
workspace_id: workspace_id.clone(), workspace_id: workspace_id.clone(),
@ -114,7 +119,11 @@ impl DatabaseManager {
} }
// Construct the workspace database. // Construct the workspace database.
trace!("open workspace database: {}", &database_views_aggregate_id); event!(
tracing::Level::INFO,
"open aggregate database views object: {}",
&database_views_aggregate_id
);
let collab = collab_builder.build_collab_with_config( let collab = collab_builder.build_collab_with_config(
uid, uid,
&database_views_aggregate_id, &database_views_aggregate_id,

View File

@ -1,6 +1,7 @@
use anyhow::Error; use anyhow::Error;
pub use collab_document::blocks::DocumentData; pub use collab_document::blocks::DocumentData;
use flowy_error::FlowyError;
use lib_infra::future::FutureResult; use lib_infra::future::FutureResult;
/// A trait for document cloud service. /// A trait for document cloud service.
@ -11,7 +12,7 @@ pub trait DocumentCloudService: Send + Sync + 'static {
&self, &self,
document_id: &str, document_id: &str,
workspace_id: &str, workspace_id: &str,
) -> FutureResult<Vec<Vec<u8>>, Error>; ) -> FutureResult<Vec<Vec<u8>>, FlowyError>;
fn get_document_snapshots( fn get_document_snapshots(
&self, &self,

View File

@ -1,14 +1,14 @@
use std::sync::Weak; use std::sync::Weak;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use collab::core::collab::MutexCollab; use collab::core::collab::{CollabRawData, MutexCollab};
use collab_document::blocks::DocumentData; use collab_document::blocks::DocumentData;
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_collab_data, default_document_data};
use collab_document::YrsDocAction; use collab_document::YrsDocAction;
use collab_entity::CollabType; use collab_entity::CollabType;
use parking_lot::RwLock; use parking_lot::RwLock;
use tracing::instrument; use tracing::{event, instrument};
use collab_integrate::collab_builder::AppFlowyCollabBuilder; use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use collab_integrate::RocksCollabDB; use collab_integrate::RocksCollabDB;
@ -109,10 +109,29 @@ impl DocumentManager {
let mut updates = vec![]; let mut updates = vec![];
if !self.is_doc_exist(doc_id)? { if !self.is_doc_exist(doc_id)? {
// Try to get the document from the cloud service // Try to get the document from the cloud service
updates = self let result: Result<CollabRawData, FlowyError> = self
.cloud_service .cloud_service
.get_document_updates(&self.user.workspace_id()?, doc_id) .get_document_updates(doc_id, &self.user.workspace_id()?)
.await?; .await;
updates = match result {
Ok(data) => data,
Err(err) => {
if err.is_record_not_found() {
// The document's ID exists in the cloud, but its content does not.
// This occurs when user A's document hasn't finished syncing and user B tries to open it.
// As a result, a blank document is created for user B.
event!(
tracing::Level::INFO,
"can't find the document in the cloud, doc_id: {}",
doc_id
);
default_document_collab_data(doc_id)
} else {
return Err(err);
}
},
}
} }
let uid = self.user.user_id()?; let uid = self.user.user_id()?;

View File

@ -129,7 +129,7 @@ impl DocumentCloudService for LocalTestDocumentCloudServiceImpl {
&self, &self,
_document_id: &str, _document_id: &str,
_workspace_id: &str, _workspace_id: &str,
) -> FutureResult<Vec<Vec<u8>>, Error> { ) -> FutureResult<Vec<Vec<u8>>, FlowyError> {
FutureResult::new(async move { Ok(vec![]) }) FutureResult::new(async move { Ok(vec![]) })
} }

View File

@ -98,9 +98,6 @@ pub enum ErrorCode {
#[error("user id is empty or whitespace")] #[error("user id is empty or whitespace")]
UserIdInvalid = 30, UserIdInvalid = 30,
#[error("User not exist")]
UserNotExist = 31,
#[error("Text is too long")] #[error("Text is too long")]
TextTooLong = 32, TextTooLong = 32,

View File

@ -55,6 +55,10 @@ impl FlowyError {
self.code == ErrorCode::RecordNotFound self.code == ErrorCode::RecordNotFound
} }
pub fn is_unauthorized(&self) -> bool {
self.code == ErrorCode::UserUnauthorized || self.code == ErrorCode::RecordNotFound
}
static_flowy_error!(internal, ErrorCode::Internal); static_flowy_error!(internal, ErrorCode::Internal);
static_flowy_error!(record_not_found, ErrorCode::RecordNotFound); static_flowy_error!(record_not_found, ErrorCode::RecordNotFound);
static_flowy_error!(workspace_name, ErrorCode::WorkspaceNameInvalid); static_flowy_error!(workspace_name, ErrorCode::WorkspaceNameInvalid);
@ -87,7 +91,6 @@ impl FlowyError {
); );
static_flowy_error!(name_empty, ErrorCode::UserNameIsEmpty); static_flowy_error!(name_empty, ErrorCode::UserNameIsEmpty);
static_flowy_error!(user_id, ErrorCode::UserIdInvalid); static_flowy_error!(user_id, ErrorCode::UserIdInvalid);
static_flowy_error!(user_not_exist, ErrorCode::UserNotExist);
static_flowy_error!(text_too_long, ErrorCode::TextTooLong); static_flowy_error!(text_too_long, ErrorCode::TextTooLong);
static_flowy_error!(invalid_data, ErrorCode::InvalidParams); static_flowy_error!(invalid_data, ErrorCode::InvalidParams);
static_flowy_error!(out_of_bounds, ErrorCode::OutOfBounds); static_flowy_error!(out_of_bounds, ErrorCode::OutOfBounds);

View File

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

View File

@ -6,8 +6,16 @@ use lib_infra::future::FutureResult;
/// [FolderCloudService] represents the cloud service for folder. /// [FolderCloudService] represents the cloud service for folder.
pub trait FolderCloudService: Send + Sync + 'static { pub trait FolderCloudService: Send + Sync + 'static {
/// Creates a new workspace for the user.
/// Returns error if the cloud service doesn't support multiple workspaces
fn create_workspace(&self, uid: i64, name: &str) -> FutureResult<Workspace, Error>; fn create_workspace(&self, uid: i64, name: &str) -> FutureResult<Workspace, Error>;
fn open_workspace(&self, workspace_id: &str) -> FutureResult<(), Error>;
/// Returns all workspaces of the user.
/// Returns vec![] if the cloud service doesn't support multiple workspaces
fn get_all_workspace(&self) -> FutureResult<Vec<WorkspaceRecord>, Error>;
fn get_folder_data( fn get_folder_data(
&self, &self,
workspace_id: &str, workspace_id: &str,
@ -39,3 +47,10 @@ pub fn gen_workspace_id() -> Uuid {
pub fn gen_view_id() -> Uuid { pub fn gen_view_id() -> Uuid {
uuid::Uuid::new_v4() uuid::Uuid::new_v4()
} }
#[derive(Debug)]
pub struct WorkspaceRecord {
pub id: String,
pub name: String,
pub created_at: i64,
}

View File

@ -38,6 +38,14 @@ pub(crate) async fn create_workspace_handler(
}) })
} }
#[tracing::instrument(level = "debug", skip_all, err)]
pub(crate) async fn get_all_workspace_handler(
_data: AFPluginData<CreateWorkspacePayloadPB>,
_folder: AFPluginState<Weak<FolderManager>>,
) -> DataResult<RepeatedWorkspacePB, FlowyError> {
todo!()
}
#[tracing::instrument(level = "debug", skip(folder), err)] #[tracing::instrument(level = "debug", skip(folder), err)]
pub(crate) async fn get_workspace_views_handler( pub(crate) async fn get_workspace_views_handler(
folder: AFPluginState<Weak<FolderManager>>, folder: AFPluginState<Weak<FolderManager>>,
@ -48,32 +56,12 @@ pub(crate) async fn get_workspace_views_handler(
data_result_ok(repeated_view) data_result_ok(repeated_view)
} }
#[tracing::instrument(level = "debug", skip(data, folder), err)]
pub(crate) async fn open_workspace_handler(
data: AFPluginData<WorkspaceIdPB>,
folder: AFPluginState<Weak<FolderManager>>,
) -> DataResult<WorkspacePB, FlowyError> {
let folder = upgrade_folder(folder)?;
let workspace_id = data.into_inner().value;
if workspace_id.is_empty() {
Err(FlowyError::workspace_id().with_context("workspace id should not be empty"))
} else {
let workspace = folder.open_workspace(&workspace_id).await?;
let views = folder.get_workspace_views(&workspace_id).await?;
let workspace_pb: WorkspacePB = (workspace, views).into();
data_result_ok(workspace_pb)
}
}
#[tracing::instrument(level = "debug", skip(folder), err)] #[tracing::instrument(level = "debug", skip(folder), err)]
pub(crate) async fn read_current_workspace_setting_handler( pub(crate) async fn read_current_workspace_setting_handler(
folder: AFPluginState<Weak<FolderManager>>, folder: AFPluginState<Weak<FolderManager>>,
) -> DataResult<WorkspaceSettingPB, FlowyError> { ) -> DataResult<WorkspaceSettingPB, FlowyError> {
let folder = upgrade_folder(folder)?; let folder = upgrade_folder(folder)?;
let setting = folder let setting = folder.get_workspace_setting_pb().await?;
.get_workspace_setting_pb()
.await
.ok_or(FlowyError::record_not_found())?;
data_result_ok(setting) data_result_ok(setting)
} }
@ -82,10 +70,7 @@ pub(crate) async fn read_current_workspace_handler(
folder: AFPluginState<Weak<FolderManager>>, folder: AFPluginState<Weak<FolderManager>>,
) -> DataResult<WorkspacePB, FlowyError> { ) -> DataResult<WorkspacePB, FlowyError> {
let folder = upgrade_folder(folder)?; let folder = upgrade_folder(folder)?;
let workspace = folder let workspace = folder.get_workspace_pb().await?;
.get_workspace_pb()
.await
.ok_or(FlowyError::record_not_found())?;
data_result_ok(workspace) data_result_ok(workspace)
} }

View File

@ -12,9 +12,8 @@ pub fn init(folder: Weak<FolderManager>) -> AFPlugin {
AFPlugin::new().name("Flowy-Folder").state(folder) AFPlugin::new().name("Flowy-Folder").state(folder)
// Workspace // Workspace
.event(FolderEvent::CreateWorkspace, create_workspace_handler) .event(FolderEvent::CreateWorkspace, create_workspace_handler)
.event(FolderEvent::GetCurrentWorkspaceSetting, read_current_workspace_setting_handler) .event(FolderEvent::GetCurrentWorkspaceSetting, read_current_workspace_setting_handler)
.event(FolderEvent::ReadCurrentWorkspace, read_current_workspace_handler) .event(FolderEvent::ReadCurrentWorkspace, read_current_workspace_handler)
.event(FolderEvent::OpenWorkspace, open_workspace_handler)
.event(FolderEvent::ReadWorkspaceViews, get_workspace_views_handler) .event(FolderEvent::ReadWorkspaceViews, get_workspace_views_handler)
// View // View
.event(FolderEvent::CreateView, create_view_handler) .event(FolderEvent::CreateView, create_view_handler)
@ -59,10 +58,6 @@ pub enum FolderEvent {
#[event(input = "WorkspaceIdPB")] #[event(input = "WorkspaceIdPB")]
DeleteWorkspace = 3, DeleteWorkspace = 3,
/// Open the workspace and mark it as the current workspace
#[event(input = "WorkspaceIdPB", output = "WorkspacePB")]
OpenWorkspace = 4,
/// Return a list of views of the current workspace. /// Return a list of views of the current workspace.
/// Only the first level of child views are included. /// Only the first level of child views are included.
#[event(input = "WorkspaceIdPB", output = "RepeatedViewPB")] #[event(input = "WorkspaceIdPB", output = "RepeatedViewPB")]

View File

@ -1,4 +1,5 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt::{Display, Formatter};
use std::ops::Deref; use std::ops::Deref;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
@ -124,68 +125,50 @@ impl FolderManager {
Ok(views) Ok(views)
} }
/// Called immediately after the application launched fi the user already sign in/sign up. /// Called immediately after the application launched if the user already sign in/sign up.
#[tracing::instrument(level = "info", skip(self, initial_data), err)] #[tracing::instrument(level = "info", skip(self, initial_data), err)]
pub async fn initialize( pub async fn initialize(
&self, &self,
uid: i64, uid: i64,
workspace_id: &str, workspace_id: &str,
initial_data: FolderInitializeDataSource, initial_data: FolderInitDataSource,
) -> FlowyResult<()> { ) -> FlowyResult<()> {
// Update the workspace id
event!(
Level::INFO,
"Init current workspace: {} from: {}",
workspace_id,
initial_data
);
*self.workspace_id.write() = Some(workspace_id.to_string()); *self.workspace_id.write() = Some(workspace_id.to_string());
let workspace_id = workspace_id.to_string(); let workspace_id = workspace_id.to_string();
if let Ok(collab_db) = self.user.collab_db(uid) {
let (view_tx, view_rx) = tokio::sync::broadcast::channel(100);
let (trash_tx, trash_rx) = tokio::sync::broadcast::channel(100);
let folder_notifier = FolderNotify {
view_change_tx: view_tx,
trash_change_tx: trash_tx,
};
let folder = match initial_data { // Get the collab db for the user with given user id.
FolderInitializeDataSource::LocalDisk { let collab_db = self.user.collab_db(uid)?;
create_if_not_exist,
} => { let (view_tx, view_rx) = tokio::sync::broadcast::channel(100);
let is_exist = is_exist_in_local_disk(&self.user, &workspace_id).unwrap_or(false); let (trash_tx, trash_rx) = tokio::sync::broadcast::channel(100);
if is_exist { let folder_notifier = FolderNotify {
event!(Level::INFO, "Restore folder from local disk"); view_change_tx: view_tx,
let collab = self trash_change_tx: trash_tx,
.collab_for_folder(uid, &workspace_id, collab_db, vec![]) };
.await?;
Folder::open(UserId::from(uid), collab, Some(folder_notifier))? let folder = match initial_data {
} else if create_if_not_exist { FolderInitDataSource::LocalDisk {
event!(Level::INFO, "Create folder with default folder builder"); create_if_not_exist,
let folder_data = } => {
DefaultFolderBuilder::build(uid, workspace_id.to_string(), &self.operation_handlers) let is_exist = is_exist_in_local_disk(&self.user, &workspace_id).unwrap_or(false);
.await; if is_exist {
let collab = self event!(Level::INFO, "Restore folder from local disk");
.collab_for_folder(uid, &workspace_id, collab_db, vec![])
.await?;
Folder::create(
UserId::from(uid),
collab,
Some(folder_notifier),
folder_data,
)
} else {
return Err(FlowyError::new(
ErrorCode::RecordNotFound,
"Can't find any workspace data",
));
}
},
FolderInitializeDataSource::Cloud(raw_data) => {
event!(Level::INFO, "Restore folder from cloud service");
if raw_data.is_empty() {
return Err(workspace_data_not_sync_error(uid, &workspace_id));
}
let collab = self let collab = self
.collab_for_folder(uid, &workspace_id, collab_db, raw_data) .collab_for_folder(uid, &workspace_id, collab_db, vec![])
.await?; .await?;
Folder::open(UserId::from(uid), collab, Some(folder_notifier))? Folder::open(UserId::from(uid), collab, Some(folder_notifier))?
}, } else if create_if_not_exist {
FolderInitializeDataSource::FolderData(folder_data) => { event!(Level::INFO, "Create folder with default folder builder");
event!(Level::INFO, "Restore folder with passed-in folder data"); let folder_data =
DefaultFolderBuilder::build(uid, workspace_id.to_string(), &self.operation_handlers)
.await;
let collab = self let collab = self
.collab_for_folder(uid, &workspace_id, collab_db, vec![]) .collab_for_folder(uid, &workspace_id, collab_db, vec![])
.await?; .await?;
@ -195,24 +178,45 @@ impl FolderManager {
Some(folder_notifier), Some(folder_notifier),
folder_data, folder_data,
) )
}, } else {
}; return Err(FlowyError::new(
ErrorCode::RecordNotFound,
"Can't find any workspace data",
));
}
},
FolderInitDataSource::Cloud(raw_data) => {
event!(Level::INFO, "Restore folder from cloud service");
if raw_data.is_empty() {
return Err(workspace_data_not_sync_error(uid, &workspace_id));
}
let collab = self
.collab_for_folder(uid, &workspace_id, collab_db, raw_data)
.await?;
Folder::open(UserId::from(uid), collab, Some(folder_notifier))?
},
FolderInitDataSource::FolderData(folder_data) => {
event!(Level::INFO, "Restore folder with passed-in folder data");
let collab = self
.collab_for_folder(uid, &workspace_id, collab_db, vec![])
.await?;
Folder::create(
UserId::from(uid),
collab,
Some(folder_notifier),
folder_data,
)
},
};
tracing::debug!("Current workspace_id: {}", workspace_id); let folder_state_rx = folder.subscribe_sync_state();
let folder_state_rx = folder.subscribe_sync_state(); *self.mutex_folder.lock() = Some(folder);
*self.mutex_folder.lock() = Some(folder);
let weak_mutex_folder = Arc::downgrade(&self.mutex_folder);
subscribe_folder_sync_state_changed(
workspace_id.clone(),
folder_state_rx,
&weak_mutex_folder,
);
subscribe_folder_snapshot_state_changed(workspace_id, &weak_mutex_folder);
subscribe_folder_trash_changed(trash_rx, &weak_mutex_folder);
subscribe_folder_view_changed(view_rx, &weak_mutex_folder);
}
let weak_mutex_folder = Arc::downgrade(&self.mutex_folder);
subscribe_folder_sync_state_changed(workspace_id.clone(), folder_state_rx, &weak_mutex_folder);
subscribe_folder_snapshot_state_changed(workspace_id, &weak_mutex_folder);
subscribe_folder_trash_changed(trash_rx, &weak_mutex_folder);
subscribe_folder_view_changed(view_rx, &weak_mutex_folder);
Ok(()) Ok(())
} }
@ -239,7 +243,7 @@ impl FolderManager {
/// Initialize the folder with the given workspace id. /// Initialize the folder with the given workspace id.
/// Fetch the folder updates from the cloud service and initialize the folder. /// Fetch the folder updates from the cloud service and initialize the folder.
#[tracing::instrument(level = "debug", skip(self, user_id), err)] #[tracing::instrument(skip(self, user_id), err)]
pub async fn initialize_with_workspace_id( pub async fn initialize_with_workspace_id(
&self, &self,
user_id: i64, user_id: i64,
@ -250,7 +254,8 @@ impl FolderManager {
.get_folder_updates(workspace_id, user_id) .get_folder_updates(workspace_id, user_id)
.await?; .await?;
info!( event!(
Level::INFO,
"Get folder updates via {}, number of updates: {}", "Get folder updates via {}, number of updates: {}",
self.cloud_service.service_name(), self.cloud_service.service_name(),
folder_updates.len() folder_updates.len()
@ -260,7 +265,7 @@ impl FolderManager {
.initialize( .initialize(
user_id, user_id,
workspace_id, workspace_id,
FolderInitializeDataSource::Cloud(folder_updates), FolderInitDataSource::Cloud(folder_updates),
) )
.await?; .await?;
Ok(()) Ok(())
@ -268,18 +273,13 @@ impl FolderManager {
/// Initialize the folder for the new user. /// Initialize the folder for the new user.
/// Using the [DefaultFolderBuilder] to create the default workspace for the new user. /// Using the [DefaultFolderBuilder] to create the default workspace for the new user.
#[instrument( #[instrument(level = "info", skip_all, err)]
name = "folder_initialize_with_new_user",
level = "debug",
skip_all,
err
)]
pub async fn initialize_with_new_user( pub async fn initialize_with_new_user(
&self, &self,
user_id: i64, user_id: i64,
_token: &str, _token: &str,
is_new: bool, is_new: bool,
data_source: FolderInitializeDataSource, data_source: FolderInitDataSource,
workspace_id: &str, workspace_id: &str,
) -> FlowyResult<()> { ) -> FlowyResult<()> {
// Create the default workspace if the user is new // Create the default workspace if the user is new
@ -306,7 +306,7 @@ impl FolderManager {
.initialize( .initialize(
user_id, user_id,
workspace_id, workspace_id,
FolderInitializeDataSource::Cloud(folder_updates), FolderInitDataSource::Cloud(folder_updates),
) )
.await?; .await?;
}, },
@ -348,20 +348,24 @@ impl FolderManager {
self.with_folder(|| None, |folder| folder.get_current_workspace()) self.with_folder(|| None, |folder| folder.get_current_workspace())
} }
pub async fn get_workspace_setting_pb(&self) -> Option<WorkspaceSettingPB> { pub async fn get_workspace_setting_pb(&self) -> FlowyResult<WorkspaceSettingPB> {
let workspace_id = self.get_current_workspace_id().await.ok()?; let workspace_id = self.get_current_workspace_id().await?;
let latest_view = self.get_current_view().await; let latest_view = self.get_current_view().await;
Some(WorkspaceSettingPB { Ok(WorkspaceSettingPB {
workspace_id, workspace_id,
latest_view, latest_view,
}) })
} }
pub async fn get_workspace_pb(&self) -> Option<WorkspacePB> { pub async fn get_workspace_pb(&self) -> FlowyResult<WorkspacePB> {
let workspace_pb = { let workspace_pb = {
let guard = self.mutex_folder.lock(); let guard = self.mutex_folder.lock();
let folder = guard.as_ref()?; let folder = guard
let workspace = folder.get_current_workspace()?; .as_ref()
.ok_or(FlowyError::internal().with_context("folder is not initialized"))?;
let workspace = folder.get_current_workspace().ok_or(
FlowyError::record_not_found().with_context("Can't find the current workspace id "),
)?;
let views = folder let views = folder
.views .views
@ -378,7 +382,7 @@ impl FolderManager {
} }
}; };
Some(workspace_pb) Ok(workspace_pb)
} }
async fn get_current_workspace_id(&self) -> FlowyResult<String> { async fn get_current_workspace_id(&self) -> FlowyResult<String> {
@ -1274,7 +1278,7 @@ impl Deref for MutexFolder {
unsafe impl Sync for MutexFolder {} unsafe impl Sync for MutexFolder {}
unsafe impl Send for MutexFolder {} unsafe impl Send for MutexFolder {}
pub enum FolderInitializeDataSource { pub enum FolderInitDataSource {
/// It means using the data stored on local disk to initialize the folder /// It means using the data stored on local disk to initialize the folder
LocalDisk { create_if_not_exist: bool }, LocalDisk { create_if_not_exist: bool },
/// If there is no data stored on local disk, we will use the data from the server to initialize the folder /// If there is no data stored on local disk, we will use the data from the server to initialize the folder
@ -1283,6 +1287,16 @@ pub enum FolderInitializeDataSource {
FolderData(FolderData), FolderData(FolderData),
} }
impl Display for FolderInitDataSource {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
FolderInitDataSource::LocalDisk { .. } => f.write_fmt(format_args!("LocalDisk")),
FolderInitDataSource::Cloud(_) => f.write_fmt(format_args!("Cloud")),
FolderInitDataSource::FolderData(_) => f.write_fmt(format_args!("Custom FolderData")),
}
}
}
fn is_exist_in_local_disk(user: &Arc<dyn FolderUser>, doc_id: &str) -> FlowyResult<bool> { fn is_exist_in_local_disk(user: &Arc<dyn FolderUser>, doc_id: &str) -> FlowyResult<bool> {
let uid = user.user_id()?; let uid = user.user_id()?;
if let Some(collab_db) = user.collab_db(uid)?.upgrade() { if let Some(collab_db) = user.collab_db(uid)?.upgrade() {

View File

@ -20,7 +20,7 @@ where
&self, &self,
document_id: &str, document_id: &str,
workspace_id: &str, workspace_id: &str,
) -> FutureResult<Vec<Vec<u8>>, Error> { ) -> FutureResult<Vec<Vec<u8>>, FlowyError> {
let workspace_id = workspace_id.to_string(); let workspace_id = workspace_id.to_string();
let try_get_client = self.0.try_get_client(); let try_get_client = self.0.try_get_client();
let document_id = document_id.to_string(); let document_id = document_id.to_string();

View File

@ -4,7 +4,9 @@ use collab::core::origin::CollabOrigin;
use collab_entity::CollabType; use collab_entity::CollabType;
use flowy_error::FlowyError; use flowy_error::FlowyError;
use flowy_folder_deps::cloud::{Folder, FolderCloudService, FolderData, FolderSnapshot, Workspace}; use flowy_folder_deps::cloud::{
Folder, FolderCloudService, FolderData, FolderSnapshot, Workspace, WorkspaceRecord,
};
use lib_infra::future::FutureResult; use lib_infra::future::FutureResult;
use crate::af_cloud::AFServer; use crate::af_cloud::AFServer;
@ -19,6 +21,35 @@ where
FutureResult::new(async move { Err(anyhow!("Not support yet")) }) FutureResult::new(async move { Err(anyhow!("Not support yet")) })
} }
fn open_workspace(&self, workspace_id: &str) -> FutureResult<(), Error> {
let workspace_id = workspace_id.to_string();
let try_get_client = self.0.try_get_client();
FutureResult::new(async move {
let client = try_get_client?;
let _ = client.open_workspace(&workspace_id).await?;
Ok(())
})
}
fn get_all_workspace(&self) -> FutureResult<Vec<WorkspaceRecord>, Error> {
let try_get_client = self.0.try_get_client();
FutureResult::new(async move {
let client = try_get_client?;
let records = client
.get_user_workspace_info()
.await?
.workspaces
.into_iter()
.map(|af_workspace| WorkspaceRecord {
id: af_workspace.workspace_id.to_string(),
name: af_workspace.workspace_name,
created_at: af_workspace.created_at.timestamp(),
})
.collect::<Vec<_>>();
Ok(records)
})
}
fn get_folder_data( fn get_folder_data(
&self, &self,
workspace_id: &str, workspace_id: &str,

View File

@ -113,7 +113,17 @@ where
}) })
} }
fn get_all_user_workspaces(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> { fn open_workspace(&self, workspace_id: &str) -> FutureResult<UserWorkspace, FlowyError> {
let try_get_client = self.server.try_get_client();
let workspace_id = workspace_id.to_string();
FutureResult::new(async move {
let client = try_get_client?;
let af_workspace = client.open_workspace(&workspace_id).await?;
Ok(to_user_workspace(af_workspace))
})
}
fn get_all_workspace(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> {
let try_get_client = self.server.try_get_client(); let try_get_client = self.server.try_get_client();
FutureResult::new(async move { FutureResult::new(async move {
let workspaces = try_get_client?.get_workspaces().await?; let workspaces = try_get_client?.get_workspaces().await?;

View File

@ -4,12 +4,12 @@ use std::sync::Arc;
use anyhow::Error; use anyhow::Error;
use client_api::notify::{TokenState, TokenStateReceiver}; use client_api::notify::{TokenState, TokenStateReceiver};
use client_api::ws::{ use client_api::ws::{
BusinessID, WSClient, WSClientConfig, WSConnectStateReceiver, WebSocketChannel, BusinessID, ConnectState, WSClient, WSClientConfig, WSConnectStateReceiver, WebSocketChannel,
}; };
use client_api::Client; use client_api::Client;
use tokio::sync::watch; use tokio::sync::watch;
use tokio_stream::wrappers::WatchStream; use tokio_stream::wrappers::WatchStream;
use tracing::{error, info}; use tracing::{error, event, info};
use flowy_database_deps::cloud::DatabaseCloudService; use flowy_database_deps::cloud::DatabaseCloudService;
use flowy_document_deps::cloud::DocumentCloudService; use flowy_document_deps::cloud::DocumentCloudService;
@ -145,7 +145,8 @@ impl AppFlowyServer for AFCloudServer {
fn collab_ws_channel( fn collab_ws_channel(
&self, &self,
object_id: &str, object_id: &str,
) -> FutureResult<Option<(Arc<WebSocketChannel>, WSConnectStateReceiver)>, anyhow::Error> { ) -> FutureResult<Option<(Arc<WebSocketChannel>, WSConnectStateReceiver, bool)>, anyhow::Error>
{
if self.enable_sync.load(Ordering::SeqCst) { if self.enable_sync.load(Ordering::SeqCst) {
let object_id = object_id.to_string(); let object_id = object_id.to_string();
let weak_ws_client = Arc::downgrade(&self.ws_client); let weak_ws_client = Arc::downgrade(&self.ws_client);
@ -155,7 +156,7 @@ impl AppFlowyServer for AFCloudServer {
Some(ws_client) => { Some(ws_client) => {
let channel = ws_client.subscribe(BusinessID::CollabId, object_id).ok(); let channel = ws_client.subscribe(BusinessID::CollabId, object_id).ok();
let connect_state_recv = ws_client.subscribe_connect_state(); let connect_state_recv = ws_client.subscribe_connect_state();
Ok(channel.map(|c| (c, connect_state_recv))) Ok(channel.map(|c| (c, connect_state_recv, ws_client.is_connected())))
}, },
} }
}) })
@ -190,24 +191,33 @@ fn spawn_ws_conn(
if let Some(ws_client) = weak_ws_client.upgrade() { if let Some(ws_client) = weak_ws_client.upgrade() {
let mut state_recv = ws_client.subscribe_connect_state(); let mut state_recv = ws_client.subscribe_connect_state();
while let Ok(state) = state_recv.recv().await { while let Ok(state) = state_recv.recv().await {
if !state.is_timeout() { info!("[websocket] state: {:?}", state);
continue; match state {
} ConnectState::PingTimeout => {
// Try to reconnect if the connection is timed out.
// Try to reconnect if the connection is timed out. if let (Some(api_client), Some(device_id)) =
if let (Some(api_client), Some(device_id)) = (weak_api_client.upgrade(), weak_device_id.upgrade())
(weak_api_client.upgrade(), weak_device_id.upgrade()) {
{ if enable_sync.load(Ordering::SeqCst) {
if enable_sync.load(Ordering::SeqCst) { let device_id = device_id.read().clone();
info!("🟢websocket state: {:?}, reconnecting", state); match api_client.ws_url(&device_id) {
let device_id = device_id.read().clone(); Ok(ws_addr) => {
match api_client.ws_url(&device_id) { event!(tracing::Level::INFO, "🟢reconnecting websocket");
Ok(ws_addr) => { let _ = ws_client.connect(ws_addr).await;
let _ = ws_client.connect(ws_addr).await; },
}, Err(err) => error!("Failed to get ws url: {}", err),
Err(err) => error!("Failed to get ws url: {}", err), }
}
} }
} },
ConnectState::Unauthorized => {
if let Some(api_client) = weak_api_client.upgrade() {
if enable_sync.load(Ordering::SeqCst) {
let _ = api_client.refresh().await;
}
}
},
_ => {},
} }
} }
} }

View File

@ -1,6 +1,7 @@
use anyhow::Error; use anyhow::Error;
use flowy_document_deps::cloud::*; use flowy_document_deps::cloud::*;
use flowy_error::FlowyError;
use lib_infra::future::FutureResult; use lib_infra::future::FutureResult;
pub(crate) struct LocalServerDocumentCloudServiceImpl(); pub(crate) struct LocalServerDocumentCloudServiceImpl();
@ -10,7 +11,7 @@ impl DocumentCloudService for LocalServerDocumentCloudServiceImpl {
&self, &self,
_document_id: &str, _document_id: &str,
_workspace_id: &str, _workspace_id: &str,
) -> FutureResult<Vec<Vec<u8>>, Error> { ) -> FutureResult<Vec<Vec<u8>>, FlowyError> {
FutureResult::new(async move { Ok(vec![]) }) FutureResult::new(async move { Ok(vec![]) })
} }

View File

@ -3,7 +3,7 @@ use std::sync::Arc;
use anyhow::Error; use anyhow::Error;
use flowy_folder_deps::cloud::{ use flowy_folder_deps::cloud::{
gen_workspace_id, FolderCloudService, FolderData, FolderSnapshot, Workspace, gen_workspace_id, FolderCloudService, FolderData, FolderSnapshot, Workspace, WorkspaceRecord,
}; };
use lib_infra::future::FutureResult; use lib_infra::future::FutureResult;
use lib_infra::util::timestamp; use lib_infra::util::timestamp;
@ -11,6 +11,7 @@ use lib_infra::util::timestamp;
use crate::local_server::LocalServerDB; use crate::local_server::LocalServerDB;
pub(crate) struct LocalServerFolderCloudServiceImpl { pub(crate) struct LocalServerFolderCloudServiceImpl {
#[allow(dead_code)]
pub db: Arc<dyn LocalServerDB>, pub db: Arc<dyn LocalServerDB>,
} }
@ -27,6 +28,14 @@ impl FolderCloudService for LocalServerFolderCloudServiceImpl {
}) })
} }
fn open_workspace(&self, _workspace_id: &str) -> FutureResult<(), Error> {
FutureResult::new(async { Ok(()) })
}
fn get_all_workspace(&self) -> FutureResult<Vec<WorkspaceRecord>, Error> {
FutureResult::new(async { Ok(vec![]) })
}
fn get_folder_data( fn get_folder_data(
&self, &self,
_workspace_id: &str, _workspace_id: &str,
@ -43,18 +52,12 @@ impl FolderCloudService for LocalServerFolderCloudServiceImpl {
FutureResult::new(async move { Ok(vec![]) }) FutureResult::new(async move { Ok(vec![]) })
} }
fn get_folder_updates(&self, workspace_id: &str, uid: i64) -> FutureResult<Vec<Vec<u8>>, Error> { fn get_folder_updates(
let weak_db = Arc::downgrade(&self.db); &self,
let workspace_id = workspace_id.to_string(); _workspace_id: &str,
FutureResult::new(async move { _uid: i64,
match weak_db.upgrade() { ) -> FutureResult<Vec<Vec<u8>>, Error> {
None => Ok(vec![]), FutureResult::new(async move { Ok(vec![]) })
Some(db) => {
let updates = db.get_collab_updates(uid, &workspace_id)?;
Ok(updates)
},
}
})
} }
fn service_name(&self) -> String { fn service_name(&self) -> String {

View File

@ -116,7 +116,13 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
FutureResult::new(async { result }) FutureResult::new(async { result })
} }
fn get_all_user_workspaces(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> { fn open_workspace(&self, _workspace_id: &str) -> FutureResult<UserWorkspace, FlowyError> {
FutureResult::new(async {
Err(FlowyError::not_support().with_context("local server doesn't support open workspace"))
})
}
fn get_all_workspace(&self, _uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> {
FutureResult::new(async { Ok(vec![]) }) FutureResult::new(async { Ok(vec![]) })
} }

View File

@ -23,7 +23,6 @@ use crate::AppFlowyServer;
pub trait LocalServerDB: Send + Sync + 'static { pub trait LocalServerDB: Send + Sync + 'static {
fn get_user_profile(&self, uid: i64) -> Result<UserProfile, FlowyError>; fn get_user_profile(&self, uid: i64) -> Result<UserProfile, FlowyError>;
fn get_user_workspace(&self, uid: i64) -> Result<Option<UserWorkspace>, FlowyError>; fn get_user_workspace(&self, uid: i64) -> Result<Option<UserWorkspace>, FlowyError>;
fn get_collab_updates(&self, uid: i64, object_id: &str) -> Result<Vec<Vec<u8>>, FlowyError>;
} }
pub struct LocalServer { pub struct LocalServer {

View File

@ -104,7 +104,8 @@ pub trait AppFlowyServer: Send + Sync + 'static {
fn collab_ws_channel( fn collab_ws_channel(
&self, &self,
_object_id: &str, _object_id: &str,
) -> FutureResult<Option<(Arc<WebSocketChannel>, WSConnectStateReceiver)>, anyhow::Error> { ) -> FutureResult<Option<(Arc<WebSocketChannel>, WSConnectStateReceiver, bool)>, anyhow::Error>
{
FutureResult::new(async { Ok(None) }) FutureResult::new(async { Ok(None) })
} }

View File

@ -32,7 +32,7 @@ where
&self, &self,
document_id: &str, document_id: &str,
workspace_id: &str, workspace_id: &str,
) -> FutureResult<Vec<Vec<u8>>, Error> { ) -> FutureResult<Vec<Vec<u8>>, FlowyError> {
let try_get_postgrest = self.server.try_get_weak_postgrest(); let try_get_postgrest = self.server.try_get_weak_postgrest();
let document_id = document_id.to_string(); let document_id = document_id.to_string();
let (tx, rx) = channel(); let (tx, rx) = channel();
@ -43,7 +43,7 @@ where
let action = FetchObjectUpdateAction::new(document_id, CollabType::Document, postgrest); let action = FetchObjectUpdateAction::new(document_id, CollabType::Document, postgrest);
let updates = action.run_with_fix_interval(5, 10).await?; let updates = action.run_with_fix_interval(5, 10).await?;
if updates.is_empty() { if updates.is_empty() {
return Err(FlowyError::collab_not_sync().into()); return Err(FlowyError::collab_not_sync());
} }
Ok(updates) Ok(updates)
} }

View File

@ -9,6 +9,7 @@ use tokio::sync::oneshot::channel;
use flowy_folder_deps::cloud::{ use flowy_folder_deps::cloud::{
gen_workspace_id, Folder, FolderCloudService, FolderData, FolderSnapshot, Workspace, gen_workspace_id, Folder, FolderCloudService, FolderData, FolderSnapshot, Workspace,
WorkspaceRecord,
}; };
use lib_dispatch::prelude::af_spawn; use lib_dispatch::prelude::af_spawn;
use lib_infra::future::FutureResult; use lib_infra::future::FutureResult;
@ -69,6 +70,14 @@ where
}) })
} }
fn open_workspace(&self, _workspace_id: &str) -> FutureResult<(), Error> {
FutureResult::new(async { Ok(()) })
}
fn get_all_workspace(&self) -> FutureResult<Vec<WorkspaceRecord>, Error> {
FutureResult::new(async { Ok(vec![]) })
}
fn get_folder_data( fn get_folder_data(
&self, &self,
workspace_id: &str, workspace_id: &str,

View File

@ -226,7 +226,13 @@ where
}) })
} }
fn get_all_user_workspaces(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> { fn open_workspace(&self, _workspace_id: &str) -> FutureResult<UserWorkspace, FlowyError> {
FutureResult::new(async {
Err(FlowyError::not_support().with_context("supabase server doesn't support open workspace"))
})
}
fn get_all_workspace(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, Error> {
let try_get_postgrest = self.server.try_get_postgrest(); let try_get_postgrest = self.server.try_get_postgrest();
FutureResult::new(async move { FutureResult::new(async move {
let postgrest = try_get_postgrest?; let postgrest = try_get_postgrest?;

View File

@ -1,17 +1,17 @@
use crate::queue::TaskQueue;
use crate::store::TaskStore;
use crate::{Task, TaskContent, TaskId, TaskState};
use anyhow::Error;
use lib_infra::future::BoxResultFuture;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use anyhow::Error;
use tokio::sync::{watch, RwLock}; use tokio::sync::{watch, RwLock};
use tokio::time::interval; use tokio::time::interval;
use lib_infra::future::BoxResultFuture;
use crate::queue::TaskQueue;
use crate::store::TaskStore;
use crate::{Task, TaskContent, TaskId, TaskState};
pub struct TaskDispatcher { pub struct TaskDispatcher {
queue: TaskQueue, queue: TaskQueue,
store: TaskStore, store: TaskStore,
@ -122,6 +122,9 @@ impl TaskDispatcher {
} }
} }
pub fn clear_task(&mut self) {
self.store.clear();
}
pub fn next_task_id(&self) -> TaskId { pub fn next_task_id(&self) -> TaskId {
self.store.next_task_id() self.store.next_task_id()
} }

View File

@ -93,8 +93,10 @@ pub trait UserCloudService: Send + Sync + 'static {
/// return None if the user is not found /// return None if the user is not found
fn get_user_profile(&self, credential: UserCredentials) -> FutureResult<UserProfile, FlowyError>; fn get_user_profile(&self, credential: UserCredentials) -> FutureResult<UserProfile, FlowyError>;
fn open_workspace(&self, workspace_id: &str) -> FutureResult<UserWorkspace, FlowyError>;
/// Return the all the workspaces of the user /// Return the all the workspaces of the user
fn get_all_user_workspaces(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, Error>; fn get_all_workspace(&self, uid: i64) -> FutureResult<Vec<UserWorkspace>, Error>;
fn add_workspace_member( fn add_workspace_member(
&self, &self,

View File

@ -138,7 +138,7 @@ pub struct UserWorkspace {
pub id: String, pub id: String,
pub name: String, pub name: String,
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
/// The database storage id is used indexing all the database in current workspace. /// The database storage id is used indexing all the database views in current workspace.
#[serde(rename = "database_storage_id")] #[serde(rename = "database_storage_id")]
pub database_views_aggregate_id: String, pub database_views_aggregate_id: String,
} }

View File

@ -1,9 +1,12 @@
use std::convert::TryInto; use std::convert::TryInto;
use validator::Validate;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_user_deps::entities::*; use flowy_user_deps::entities::*;
use crate::entities::parser::{UserEmail, UserIcon, UserName, UserOpenaiKey, UserPassword}; use crate::entities::parser::{UserEmail, UserIcon, UserName, UserOpenaiKey, UserPassword};
use crate::entities::required_not_empty_str;
use crate::entities::AuthTypePB; use crate::entities::AuthTypePB;
use crate::errors::ErrorCode; use crate::errors::ErrorCode;
use crate::services::entities::HistoricalUser; use crate::services::entities::HistoricalUser;
@ -217,10 +220,11 @@ impl From<Vec<UserWorkspace>> for RepeatedUserWorkspacePB {
} }
} }
#[derive(ProtoBuf, Default, Debug, Clone)] #[derive(ProtoBuf, Default, Debug, Clone, Validate)]
pub struct UserWorkspacePB { pub struct UserWorkspacePB {
#[pb(index = 1)] #[pb(index = 1)]
pub id: String, #[validate(custom = "required_not_empty_str")]
pub workspace_id: String,
#[pb(index = 2)] #[pb(index = 2)]
pub name: String, pub name: String,
@ -229,7 +233,7 @@ pub struct UserWorkspacePB {
impl From<UserWorkspace> for UserWorkspacePB { impl From<UserWorkspace> for UserWorkspacePB {
fn from(value: UserWorkspace) -> Self { fn from(value: UserWorkspace) -> Self {
Self { Self {
id: value.id, workspace_id: value.id,
name: value.name, name: value.name,
} }
} }

View File

@ -103,3 +103,10 @@ impl From<Role> for AFRolePB {
} }
} }
} }
#[derive(ProtoBuf, Default, Clone, Validate)]
pub struct UserWorkspaceIdPB {
#[pb(index = 1)]
#[validate(custom = "required_not_empty_str")]
pub workspace_id: String,
}

View File

@ -2,7 +2,6 @@ use std::sync::Weak;
use std::{convert::TryInto, sync::Arc}; use std::{convert::TryInto, sync::Arc};
use serde_json::Value; use serde_json::Value;
use tracing::event;
use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_sqlite::kv::StorePreferences; use flowy_sqlite::kv::StorePreferences;
@ -105,17 +104,11 @@ pub async fn get_user_profile_handler(
user_profile.email = "".to_string(); user_profile.email = "".to_string();
} }
event!(
tracing::Level::DEBUG,
"Get user profile: {:?}",
user_profile
);
data_result_ok(user_profile.into()) data_result_ok(user_profile.into())
} }
#[tracing::instrument(level = "debug", skip(manager))] #[tracing::instrument(level = "debug", skip(manager))]
pub async fn sign_out(manager: AFPluginState<Weak<UserManager>>) -> Result<(), FlowyError> { pub async fn sign_out_handler(manager: AFPluginState<Weak<UserManager>>) -> Result<(), FlowyError> {
let manager = upgrade_manager(manager)?; let manager = upgrade_manager(manager)?;
manager.sign_out().await?; manager.sign_out().await?;
Ok(()) Ok(())
@ -425,7 +418,7 @@ pub async fn get_cloud_config_handler(
} }
#[tracing::instrument(level = "debug", skip(manager), err)] #[tracing::instrument(level = "debug", skip(manager), err)]
pub async fn get_all_user_workspace_handler( pub async fn get_all_workspace_handler(
manager: AFPluginState<Weak<UserManager>>, manager: AFPluginState<Weak<UserManager>>,
) -> DataResult<RepeatedUserWorkspacePB, FlowyError> { ) -> DataResult<RepeatedUserWorkspacePB, FlowyError> {
let manager = upgrade_manager(manager)?; let manager = upgrade_manager(manager)?;
@ -436,12 +429,12 @@ pub async fn get_all_user_workspace_handler(
#[tracing::instrument(level = "debug", skip(data, manager), err)] #[tracing::instrument(level = "debug", skip(data, manager), err)]
pub async fn open_workspace_handler( pub async fn open_workspace_handler(
data: AFPluginData<UserWorkspacePB>, data: AFPluginData<UserWorkspaceIdPB>,
manager: AFPluginState<Weak<UserManager>>, manager: AFPluginState<Weak<UserManager>>,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let manager = upgrade_manager(manager)?; let manager = upgrade_manager(manager)?;
let params = data.into_inner(); let params = data.validate()?.into_inner();
manager.open_workspace(&params.id).await?; manager.open_workspace(&params.workspace_id).await?;
Ok(()) Ok(())
} }

View File

@ -29,7 +29,7 @@ pub fn init(user_session: Weak<UserManager>) -> AFPlugin {
.event(UserEvent::SignUp, sign_up) .event(UserEvent::SignUp, sign_up)
.event(UserEvent::InitUser, init_user_handler) .event(UserEvent::InitUser, init_user_handler)
.event(UserEvent::GetUserProfile, get_user_profile_handler) .event(UserEvent::GetUserProfile, get_user_profile_handler)
.event(UserEvent::SignOut, sign_out) .event(UserEvent::SignOut, sign_out_handler)
.event(UserEvent::UpdateUserProfile, update_user_profile_handler) .event(UserEvent::UpdateUserProfile, update_user_profile_handler)
.event(UserEvent::SetAppearanceSetting, set_appearance_setting) .event(UserEvent::SetAppearanceSetting, set_appearance_setting)
.event(UserEvent::GetAppearanceSetting, get_appearance_setting) .event(UserEvent::GetAppearanceSetting, get_appearance_setting)
@ -41,7 +41,7 @@ pub fn init(user_session: Weak<UserManager>) -> AFPlugin {
.event(UserEvent::OauthSignIn, oauth_handler) .event(UserEvent::OauthSignIn, oauth_handler)
.event(UserEvent::GetSignInURL, get_sign_in_url_handler) .event(UserEvent::GetSignInURL, get_sign_in_url_handler)
.event(UserEvent::GetOauthURLWithProvider, sign_in_with_provider_handler) .event(UserEvent::GetOauthURLWithProvider, sign_in_with_provider_handler)
.event(UserEvent::GetAllUserWorkspaces, get_all_user_workspace_handler) .event(UserEvent::GetAllWorkspace, get_all_workspace_handler)
.event(UserEvent::OpenWorkspace, open_workspace_handler) .event(UserEvent::OpenWorkspace, open_workspace_handler)
.event(UserEvent::UpdateNetworkState, update_network_state_handler) .event(UserEvent::UpdateNetworkState, update_network_state_handler)
.event(UserEvent::GetHistoricalUsers, get_historical_users_handler) .event(UserEvent::GetHistoricalUsers, get_historical_users_handler)
@ -60,7 +60,7 @@ pub fn init(user_session: Weak<UserManager>) -> AFPlugin {
.event(UserEvent::AddWorkspaceMember, add_workspace_member_handler) .event(UserEvent::AddWorkspaceMember, add_workspace_member_handler)
.event(UserEvent::RemoveWorkspaceMember, delete_workspace_member_handler) .event(UserEvent::RemoveWorkspaceMember, delete_workspace_member_handler)
.event(UserEvent::GetWorkspaceMember, get_workspace_member_handler) .event(UserEvent::GetWorkspaceMember, get_workspace_member_handler)
.event(UserEvent::UpdateWorkspaceMember, update_workspace_member_handler,) .event(UserEvent::UpdateWorkspaceMember, update_workspace_member_handler)
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
@ -129,10 +129,10 @@ pub enum UserEvent {
CheckEncryptionSign = 16, CheckEncryptionSign = 16,
/// Return the all the workspaces of the user /// Return the all the workspaces of the user
#[event()] #[event(output = "RepeatedUserWorkspacePB")]
GetAllUserWorkspaces = 20, GetAllWorkspace = 17,
#[event(input = "UserWorkspacePB")] #[event(input = "UserWorkspaceIdPB")]
OpenWorkspace = 21, OpenWorkspace = 21,
#[event(input = "NetworkStatePB")] #[event(input = "NetworkStatePB")]

View File

@ -1,4 +1,5 @@
use std::string::ToString; use std::string::ToString;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use collab_user::core::MutexUserAwareness; use collab_user::core::MutexUserAwareness;
@ -55,7 +56,7 @@ impl UserSessionConfig {
} }
pub struct UserManager { pub struct UserManager {
database: UserDB, database: Arc<UserDB>,
session_config: UserSessionConfig, session_config: UserSessionConfig,
pub(crate) cloud_services: Arc<dyn UserCloudServiceProvider>, pub(crate) cloud_services: Arc<dyn UserCloudServiceProvider>,
pub(crate) store_preferences: Arc<StorePreferences>, pub(crate) store_preferences: Arc<StorePreferences>,
@ -64,7 +65,8 @@ pub struct UserManager {
pub(crate) collab_builder: Weak<AppFlowyCollabBuilder>, pub(crate) collab_builder: Weak<AppFlowyCollabBuilder>,
pub(crate) collab_interact: RwLock<Arc<dyn CollabInteract>>, pub(crate) collab_interact: RwLock<Arc<dyn CollabInteract>>,
resumable_sign_up: Mutex<Option<ResumableSignUp>>, resumable_sign_up: Mutex<Option<ResumableSignUp>>,
current_session: parking_lot::RwLock<Option<Session>>, current_session: Arc<parking_lot::RwLock<Option<Session>>>,
refresh_user_profile_since: AtomicI64,
} }
impl UserManager { impl UserManager {
@ -74,10 +76,11 @@ impl UserManager {
store_preferences: Arc<StorePreferences>, store_preferences: Arc<StorePreferences>,
collab_builder: Weak<AppFlowyCollabBuilder>, collab_builder: Weak<AppFlowyCollabBuilder>,
) -> Arc<Self> { ) -> Arc<Self> {
let database = UserDB::new(&session_config.root_dir); let database = Arc::new(UserDB::new(&session_config.root_dir));
let user_status_callback: RwLock<Arc<dyn UserStatusCallback>> = let user_status_callback: RwLock<Arc<dyn UserStatusCallback>> =
RwLock::new(Arc::new(DefaultUserStatusCallback)); RwLock::new(Arc::new(DefaultUserStatusCallback));
let refresh_user_profile_since = AtomicI64::new(0);
let user_manager = Arc::new(Self { let user_manager = Arc::new(Self {
database, database,
session_config, session_config,
@ -89,6 +92,7 @@ impl UserManager {
collab_interact: RwLock::new(Arc::new(DefaultCollabInteract)), collab_interact: RwLock::new(Arc::new(DefaultCollabInteract)),
resumable_sign_up: Default::default(), resumable_sign_up: Default::default(),
current_session: Default::default(), current_session: Default::default(),
refresh_user_profile_since,
}); });
let weak_user_manager = Arc::downgrade(&user_manager); let weak_user_manager = Arc::downgrade(&user_manager);
@ -120,13 +124,28 @@ impl UserManager {
/// a local data migration for the user. After ensuring the user's data is migrated and up-to-date, /// a local data migration for the user. After ensuring the user's data is migrated and up-to-date,
/// the function will set up the collaboration configuration and initialize the user's awareness. Upon successful /// the function will set up the collaboration configuration and initialize the user's awareness. Upon successful
/// completion, a user status callback is invoked to signify that the initialization process is complete. /// completion, a user status callback is invoked to signify that the initialization process is complete.
#[instrument(level = "debug", skip_all, err)]
pub async fn init<C: UserStatusCallback + 'static, I: CollabInteract>( pub async fn init<C: UserStatusCallback + 'static, I: CollabInteract>(
&self, &self,
user_status_callback: C, user_status_callback: C,
collab_interact: I, collab_interact: I,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let user_status_callback = Arc::new(user_status_callback);
*self.user_status_callback.write().await = user_status_callback.clone();
*self.collab_interact.write().await = Arc::new(collab_interact);
if let Ok(session) = self.get_session() { if let Ok(session) = self.get_session() {
let user = self.get_user_profile(session.user_id).await?; let user = self.get_user_profile(session.user_id).await?;
event!(
tracing::Level::INFO,
"init user session: {}:{}",
user.uid,
user.email
);
// Set the token if the current cloud service using token to authenticate
// Currently, only the AppFlowy cloud using token to init the client api.
if let Err(err) = self.cloud_services.set_token(&user.token) { if let Err(err) = self.cloud_services.set_token(&user.token) {
error!("Set token failed: {}", err); error!("Set token failed: {}", err);
} }
@ -134,10 +153,13 @@ impl UserManager {
// Subscribe the token state // Subscribe the token state
let weak_pool = Arc::downgrade(&self.db_pool(user.uid)?); let weak_pool = Arc::downgrade(&self.db_pool(user.uid)?);
if let Some(mut token_state_rx) = self.cloud_services.subscribe_token_state() { if let Some(mut token_state_rx) = self.cloud_services.subscribe_token_state() {
event!(tracing::Level::DEBUG, "Listen token state change");
af_spawn(async move { af_spawn(async move {
while let Some(token_state) = token_state_rx.next().await { while let Some(token_state) = token_state_rx.next().await {
debug!("Token state changed: {:?}", token_state);
match token_state { match token_state {
UserTokenState::Refresh { token } => { UserTokenState::Refresh { token } => {
// Only save the token if the token is different from the current token
if token != user.token { if token != user.token {
if let Some(pool) = weak_pool.upgrade() { if let Some(pool) = weak_pool.upgrade() {
// Save the new token // Save the new token
@ -147,19 +169,14 @@ impl UserManager {
} }
} }
}, },
UserTokenState::Invalid => { UserTokenState::Invalid => {},
send_auth_state_notification(AuthStateChangedPB {
state: AuthStatePB::InvalidAuth,
message: "Token is invalid".to_string(),
})
.send();
},
} }
} }
}); });
} }
// Do the user data migration if needed // Do the user data migration if needed
event!(tracing::Level::INFO, "Prepare user data migration");
match ( match (
self.database.get_collab_db(session.user_id), self.database.get_collab_db(session.user_id),
self.database.get_pool(session.user_id), self.database.get_pool(session.user_id),
@ -202,8 +219,6 @@ impl UserManager {
error!("Failed to call did_init callback: {:?}", e); error!("Failed to call did_init callback: {:?}", e);
} }
} }
*self.user_status_callback.write().await = Arc::new(user_status_callback);
*self.collab_interact.write().await = Arc::new(collab_interact);
Ok(()) Ok(())
} }
@ -380,6 +395,7 @@ impl UserManager {
self self
.save_auth_data(&response, auth_type, &new_session) .save_auth_data(&response, auth_type, &new_session)
.await?; .await?;
self self
.user_status_callback .user_status_callback
.read() .read()
@ -445,14 +461,28 @@ impl UserManager {
pub async fn get_user_profile(&self, uid: i64) -> Result<UserProfile, FlowyError> { pub async fn get_user_profile(&self, uid: i64) -> Result<UserProfile, FlowyError> {
let user: UserProfile = user_table::dsl::user_table let user: UserProfile = user_table::dsl::user_table
.filter(user_table::id.eq(&uid.to_string())) .filter(user_table::id.eq(&uid.to_string()))
.first::<UserTable>(&*(self.db_connection(uid)?))? .first::<UserTable>(&*(self.db_connection(uid)?))
.map_err(|err| {
FlowyError::record_not_found().with_context(format!(
"Can't find the user profile for user id: {}, error: {:?}",
uid, err
))
})?
.into(); .into();
Ok(user) Ok(user)
} }
#[tracing::instrument(level = "info", skip_all)] #[tracing::instrument(level = "info", skip_all, err)]
pub async fn refresh_user_profile(&self, old_user_profile: &UserProfile) -> FlowyResult<()> { pub async fn refresh_user_profile(&self, old_user_profile: &UserProfile) -> FlowyResult<()> {
let now = chrono::Utc::now().timestamp();
// Add debounce to avoid too many requests
if now - self.refresh_user_profile_since.load(Ordering::SeqCst) < 5 {
return Ok(());
}
self.refresh_user_profile_since.store(now, Ordering::SeqCst);
let uid = old_user_profile.uid; let uid = old_user_profile.uid;
let result: Result<UserProfile, FlowyError> = self let result: Result<UserProfile, FlowyError> = self
.cloud_services .cloud_services
@ -494,12 +524,12 @@ impl UserManager {
}, },
Err(err) => { Err(err) => {
// If the user is not found, notify the frontend to logout // If the user is not found, notify the frontend to logout
if err.is_record_not_found() { if err.is_unauthorized() {
event!( event!(
tracing::Level::INFO, tracing::Level::ERROR,
"User is not found on the server when refreshing profile" "User is unauthorized, sign out the user"
); );
self.sign_out().await?;
send_auth_state_notification(AuthStateChangedPB { send_auth_state_notification(AuthStateChangedPB {
state: AuthStatePB::InvalidAuth, state: AuthStatePB::InvalidAuth,
message: "User is not found on the server".to_string(), message: "User is not found on the server".to_string(),
@ -641,6 +671,7 @@ impl UserManager {
Ok(url) Ok(url)
} }
#[instrument(level = "info", skip_all, err)]
async fn save_auth_data( async fn save_auth_data(
&self, &self,
response: &impl UserAuthResponse, response: &impl UserAuthResponse,

View File

@ -5,11 +5,13 @@ 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::Folder; use collab_folder::Folder;
use tracing::{event, instrument};
use collab_integrate::{RocksCollabDB, YrsDocAction}; 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;
use crate::migrations::util::load_collab;
use crate::services::entities::Session; use crate::services::entities::Session;
/// Migrate the first level documents of the workspace by inserting documents /// Migrate the first level documents of the workspace by inserting documents
@ -20,39 +22,42 @@ impl UserDataMigration for HistoricalEmptyDocumentMigration {
"historical_empty_document" "historical_empty_document"
} }
#[instrument(name = "HistoricalEmptyDocumentMigration", skip_all, err)]
fn run(&self, session: &Session, collab_db: &Arc<RocksCollabDB>) -> FlowyResult<()> { fn run(&self, session: &Session, collab_db: &Arc<RocksCollabDB>) -> FlowyResult<()> {
let write_txn = collab_db.write_txn(); let write_txn = collab_db.write_txn();
if let Ok(updates) = write_txn.get_all_updates(session.user_id, &session.user_workspace.id) { let origin = CollabOrigin::Client(CollabClient::new(session.user_id, "phantom"));
let origin = CollabOrigin::Client(CollabClient::new(session.user_id, "phantom")); // Deserialize the folder from the raw data
// Deserialize the folder from the raw data if let Ok(folder_collab) = load_collab(session.user_id, &write_txn, &session.user_workspace.id)
let folder = Folder::from_collab_raw_data( {
session.user_id, let folder = Folder::open(session.user_id, folder_collab, None)?;
origin.clone(),
updates,
&session.user_workspace.id,
vec![],
)?;
// Migration the first level documents of the workspace // Migration the first level documents of the workspace. The first level documents do not have
// any updates. So when calling load_collab, it will return error.
let migration_views = folder.get_workspace_views(&session.user_workspace.id); let migration_views = folder.get_workspace_views(&session.user_workspace.id);
for view in migration_views { for view in migration_views {
// Read all updates of the view if load_collab(session.user_id, &write_txn, &view.id).is_err() {
if let Ok(view_updates) = write_txn.get_all_updates(session.user_id, &view.id) { // Create a document with default data
if Document::from_updates(origin.clone(), view_updates, &view.id, vec![]).is_err() { let document_data = default_document_data();
// Create a document with default data let collab = Arc::new(MutexCollab::new(origin.clone(), &view.id, vec![]));
let document_data = default_document_data(); if let Ok(document) = Document::create_with_data(collab.clone(), document_data) {
let collab = Arc::new(MutexCollab::new(origin.clone(), &view.id, vec![])); // Remove all old updates and then insert the new update
if let Ok(document) = Document::create_with_data(collab.clone(), document_data) { let (doc_state, sv) = document.get_collab().encode_as_update_v1();
// Remove all old updates and then insert the new update if let Err(err) = write_txn.flush_doc_with(session.user_id, &view.id, &doc_state, &sv) {
let (doc_state, sv) = document.get_collab().encode_as_update_v1(); event!(
write_txn tracing::Level::ERROR,
.flush_doc_with(session.user_id, &view.id, &doc_state, &sv) "Failed to migrate document {}, error: {}",
.map_err(internal_error)?; view.id,
err
);
} else {
event!(tracing::Level::INFO, "Did migrate document {}", view.id);
} }
} }
} }
} }
} }
event!(tracing::Level::INFO, "Save all migrated documents");
write_txn.commit_transaction().map_err(internal_error)?; write_txn.commit_transaction().map_err(internal_error)?;
Ok(()) Ok(())
} }

View File

@ -2,7 +2,6 @@ use std::sync::Arc;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::{RunQueryDsl, SqliteConnection}; use diesel::{RunQueryDsl, SqliteConnection};
use tracing::event;
use collab_integrate::RocksCollabDB; use collab_integrate::RocksCollabDB;
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
@ -55,12 +54,6 @@ impl UserLocalDataMigration {
{ {
let migration_name = migration.name().to_string(); let migration_name = migration.name().to_string();
if !duplicated_names.contains(&migration_name) { if !duplicated_names.contains(&migration_name) {
event!(
tracing::Level::INFO,
"Running migration {}",
migration.name()
);
migration.run(&self.session, &self.collab_db)?; migration.run(&self.session, &self.collab_db)?;
applied_migrations.push(migration.name().to_string()); applied_migrations.push(migration.name().to_string());
save_record(&conn, &migration_name); save_record(&conn, &migration_name);

View File

@ -3,4 +3,5 @@ pub use define::*;
mod define; mod define;
pub mod document_empty_content; pub mod document_empty_content;
pub mod migration; pub mod migration;
mod util;
pub mod workspace_and_favorite_v1; pub mod workspace_and_favorite_v1;

View File

@ -0,0 +1,23 @@
use std::sync::Arc;
use collab::core::collab::MutexCollab;
use collab::preclude::Collab;
use collab_integrate::{PersistenceError, YrsDocAction};
use flowy_error::{internal_error, FlowyResult};
pub fn load_collab<'a, R>(
uid: i64,
collab_r_txn: &R,
object_id: &str,
) -> FlowyResult<Arc<MutexCollab>>
where
R: YrsDocAction<'a>,
PersistenceError: From<R::Error>,
{
let collab = Collab::new(uid, object_id, "phantom", vec![]);
collab
.with_origin_transact_mut(|txn| collab_r_txn.load_doc_with_txn(uid, &object_id, txn))
.map_err(internal_error)?;
Ok(Arc::new(MutexCollab::from_collab(collab)))
}

View File

@ -1,12 +1,13 @@
use std::sync::Arc; use std::sync::Arc;
use collab::core::origin::{CollabClient, CollabOrigin};
use collab_folder::Folder; use collab_folder::Folder;
use tracing::instrument;
use collab_integrate::{RocksCollabDB, YrsDocAction}; 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;
use crate::migrations::util::load_collab;
use crate::services::entities::Session; use crate::services::entities::Session;
/// 1. Migrate the workspace: { favorite: [view_id] } to { favorite: { uid: [view_id] } } /// 1. Migrate the workspace: { favorite: [view_id] } to { favorite: { uid: [view_id] } }
@ -19,19 +20,11 @@ impl UserDataMigration for FavoriteV1AndWorkspaceArrayMigration {
"workspace_favorite_v1_and_workspace_array_migration" "workspace_favorite_v1_and_workspace_array_migration"
} }
#[instrument(name = "FavoriteV1AndWorkspaceArrayMigration", skip_all, err)]
fn run(&self, session: &Session, collab_db: &Arc<RocksCollabDB>) -> FlowyResult<()> { fn run(&self, session: &Session, collab_db: &Arc<RocksCollabDB>) -> FlowyResult<()> {
let write_txn = collab_db.write_txn(); let write_txn = collab_db.write_txn();
if let Ok(updates) = write_txn.get_all_updates(session.user_id, &session.user_workspace.id) { if let Ok(collab) = load_collab(session.user_id, &write_txn, &session.user_workspace.id) {
let origin = CollabOrigin::Client(CollabClient::new(session.user_id, "phantom")); let folder = Folder::open(session.user_id, collab, None)?;
// Deserialize the folder from the raw data
let folder = Folder::from_collab_raw_data(
session.user_id,
origin,
updates,
&session.user_workspace.id,
vec![],
)?;
folder.migrate_workspace_to_view(); folder.migrate_workspace_to_view();
let favorite_view_ids = folder let favorite_view_ids = folder
@ -48,8 +41,9 @@ impl UserDataMigration for FavoriteV1AndWorkspaceArrayMigration {
write_txn write_txn
.flush_doc_with(session.user_id, &session.user_workspace.id, &doc_state, &sv) .flush_doc_with(session.user_id, &session.user_workspace.id, &doc_state, &sv)
.map_err(internal_error)?; .map_err(internal_error)?;
write_txn.commit_transaction().map_err(internal_error)?;
} }
write_txn.commit_transaction().map_err(internal_error)?;
Ok(()) Ok(())
} }
} }

View File

@ -2,6 +2,7 @@ use std::convert::TryFrom;
use std::sync::Arc; use std::sync::Arc;
use collab_entity::{CollabObject, CollabType}; use collab_entity::{CollabObject, CollabType};
use tracing::{error, instrument};
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
use flowy_sqlite::schema::user_workspace_table; use flowy_sqlite::schema::user_workspace_table;
@ -15,8 +16,14 @@ use crate::notification::{send_notification, UserNotification};
use crate::services::user_workspace_sql::UserWorkspaceTable; use crate::services::user_workspace_sql::UserWorkspaceTable;
impl UserManager { impl UserManager {
#[instrument(skip(self), err)]
pub async fn open_workspace(&self, workspace_id: &str) -> FlowyResult<()> { pub async fn open_workspace(&self, workspace_id: &str) -> FlowyResult<()> {
let uid = self.user_id()?; let uid = self.user_id()?;
let _ = self
.cloud_services
.get_user_service()?
.open_workspace(workspace_id)
.await;
if let Some(user_workspace) = self.get_user_workspace(uid, workspace_id) { if let Some(user_workspace) = self.get_user_workspace(uid, workspace_id) {
if let Err(err) = self if let Err(err) = self
.user_status_callback .user_status_callback
@ -25,7 +32,7 @@ impl UserManager {
.open_workspace(uid, &user_workspace) .open_workspace(uid, &user_workspace)
.await .await
{ {
tracing::error!("Open workspace failed: {:?}", err); error!("Open workspace failed: {:?}", err);
} }
} }
Ok(()) Ok(())
@ -101,7 +108,7 @@ impl UserManager {
if let Ok(service) = self.cloud_services.get_user_service() { if let Ok(service) = self.cloud_services.get_user_service() {
if let Ok(pool) = self.db_pool(uid) { if let Ok(pool) = self.db_pool(uid) {
af_spawn(async move { af_spawn(async move {
if let Ok(new_user_workspaces) = service.get_all_user_workspaces(uid).await { if let Ok(new_user_workspaces) = service.get_all_workspace(uid).await {
let _ = save_user_workspaces(uid, pool, &new_user_workspaces); let _ = save_user_workspaces(uid, pool, &new_user_workspaces);
let repeated_workspace_pbs = RepeatedUserWorkspacePB::from(new_user_workspaces); let repeated_workspace_pbs = RepeatedUserWorkspacePB::from(new_user_workspaces);
send_notification(&uid.to_string(), UserNotification::DidUpdateUserWorkspaces) send_notification(&uid.to_string(), UserNotification::DidUpdateUserWorkspaces)

View File

@ -7,7 +7,6 @@ edition = "2018"
[dependencies] [dependencies]
tracing-log = { version = "0.2"}
tracing-subscriber = { version = "0.3.17", features = ["registry", "env-filter", "ansi", "json"] } tracing-subscriber = { version = "0.3.17", features = ["registry", "env-filter", "ansi", "json"] }
tracing-bunyan-formatter = "0.3.9" tracing-bunyan-formatter = "0.3.9"
tracing-appender = "0.2.2" tracing-appender = "0.2.2"

View File

@ -1,9 +1,11 @@
use std::sync::RwLock; use std::sync::RwLock;
use chrono::Local;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use tracing::subscriber::set_global_default; use tracing::subscriber::set_global_default;
use tracing_appender::{non_blocking::WorkerGuard, rolling::RollingFileAppender}; use tracing_appender::{non_blocking::WorkerGuard, rolling::RollingFileAppender};
use tracing_bunyan_formatter::JsonStorageLayer; use tracing_bunyan_formatter::JsonStorageLayer;
use tracing_subscriber::fmt::format::Writer;
use tracing_subscriber::{layer::SubscriberExt, EnvFilter}; use tracing_subscriber::{layer::SubscriberExt, EnvFilter};
use crate::layer::FlowyFormattingLayer; use crate::layer::FlowyFormattingLayer;
@ -43,12 +45,12 @@ impl Builder {
let (non_blocking, guard) = tracing_appender::non_blocking(self.file_appender); let (non_blocking, guard) = tracing_appender::non_blocking(self.file_appender);
let subscriber = tracing_subscriber::fmt() let subscriber = tracing_subscriber::fmt()
.with_timer(CustomTime)
.with_ansi(true) .with_ansi(true)
.with_target(true) .with_target(false)
.with_max_level(tracing::Level::TRACE) .with_max_level(tracing::Level::TRACE)
.with_thread_ids(false) .with_thread_ids(false)
.with_file(false) .with_writer(std::io::stdout)
.with_writer(std::io::stderr)
.pretty() .pretty()
.with_env_filter(env_filter) .with_env_filter(env_filter)
.finish() .finish()
@ -56,7 +58,15 @@ impl Builder {
.with(FlowyFormattingLayer::new(non_blocking)); .with(FlowyFormattingLayer::new(non_blocking));
set_global_default(subscriber).map_err(|e| format!("{:?}", e))?; set_global_default(subscriber).map_err(|e| format!("{:?}", e))?;
*LOG_GUARD.write().unwrap() = Some(guard); *LOG_GUARD.write().unwrap() = Some(guard);
Ok(()) Ok(())
} }
} }
struct CustomTime;
impl tracing_subscriber::fmt::time::FormatTime for CustomTime {
fn format_time(&self, w: &mut Writer<'_>) -> std::fmt::Result {
write!(w, "{}", Local::now().format("%Y-%m-%d %H:%M:%S"))
}
}