chore: add custom folder prompt (#3961)

* chore: add custom folder prompt

* chore: zip collab db

* chore: fix test

* chore: add test

* chore: fmt

* chore: fmt

* chore: fmt
This commit is contained in:
Nathan.fooo 2023-11-20 20:54:47 +08:00 committed by GitHub
parent 6f83f41c2d
commit b9ecc7ceb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 1423 additions and 551 deletions

View File

@ -4,21 +4,27 @@ import 'package:json_annotation/json_annotation.dart';
part 'backend_env.g.dart'; part 'backend_env.g.dart';
@JsonSerializable() @JsonSerializable()
class AppFlowyEnv { class AppFlowyConfiguration {
final String custom_app_path;
final String origin_app_path;
final String device_id;
final int cloud_type; final int cloud_type;
final SupabaseConfiguration supabase_config; final SupabaseConfiguration supabase_config;
final AppFlowyCloudConfiguration appflowy_cloud_config; final AppFlowyCloudConfiguration appflowy_cloud_config;
AppFlowyEnv({ AppFlowyConfiguration({
required this.custom_app_path,
required this.origin_app_path,
required this.device_id,
required this.cloud_type, required this.cloud_type,
required this.supabase_config, required this.supabase_config,
required this.appflowy_cloud_config, required this.appflowy_cloud_config,
}); });
factory AppFlowyEnv.fromJson(Map<String, dynamic> json) => factory AppFlowyConfiguration.fromJson(Map<String, dynamic> json) =>
_$AppFlowyEnvFromJson(json); _$AppFlowyConfigurationFromJson(json);
Map<String, dynamic> toJson() => _$AppFlowyEnvToJson(this); Map<String, dynamic> toJson() => _$AppFlowyConfigurationToJson(this);
} }
@JsonSerializable() @JsonSerializable()
@ -36,6 +42,13 @@ class SupabaseConfiguration {
_$SupabaseConfigurationFromJson(json); _$SupabaseConfigurationFromJson(json);
Map<String, dynamic> toJson() => _$SupabaseConfigurationToJson(this); Map<String, dynamic> toJson() => _$SupabaseConfigurationToJson(this);
static SupabaseConfiguration defaultConfig() {
return SupabaseConfiguration(
url: '',
anon_key: '',
);
}
} }
@JsonSerializable() @JsonSerializable()
@ -54,4 +67,12 @@ class AppFlowyCloudConfiguration {
_$AppFlowyCloudConfigurationFromJson(json); _$AppFlowyCloudConfigurationFromJson(json);
Map<String, dynamic> toJson() => _$AppFlowyCloudConfigurationToJson(this); Map<String, dynamic> toJson() => _$AppFlowyCloudConfigurationToJson(this);
static AppFlowyCloudConfiguration defaultConfig() {
return AppFlowyCloudConfiguration(
base_url: '',
ws_base_url: '',
gotrue_url: '',
);
}
} }

View File

@ -64,7 +64,7 @@ class FlowyRunner {
// init the app window // init the app window
const InitAppWindowTask(), const InitAppWindowTask(),
// Init Rust SDK // Init Rust SDK
InitRustSDKTask(directory: applicationDataDirectory), InitRustSDKTask(customApplicationPath: applicationDataDirectory),
// Load Plugins, like document, grid ... // Load Plugins, like document, grid ...
const PluginLoadTask(), const PluginLoadTask(),

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:appflowy/env/backend_env.dart'; import 'package:appflowy/env/backend_env.dart';
import 'package:appflowy/env/env.dart'; import 'package:appflowy/env/env.dart';
import 'package:appflowy/user/application/auth/device_id.dart';
import 'package:appflowy_backend/appflowy_backend.dart'; import 'package:appflowy_backend/appflowy_backend.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;
@ -11,29 +12,39 @@ import '../startup.dart';
class InitRustSDKTask extends LaunchTask { class InitRustSDKTask extends LaunchTask {
const InitRustSDKTask({ const InitRustSDKTask({
this.directory, this.customApplicationPath,
}); });
// Customize the RustSDK initialization path // Customize the RustSDK initialization path
final Directory? directory; final Directory? customApplicationPath;
@override @override
LaunchTaskType get type => LaunchTaskType.dataProcessing; LaunchTaskType get type => LaunchTaskType.dataProcessing;
@override @override
Future<void> initialize(LaunchContext context) async { Future<void> initialize(LaunchContext context) async {
final dir = directory ?? await appFlowyApplicationDataDirectory(); final applicationPath = await appFlowyApplicationDataDirectory();
final dir = customApplicationPath ?? applicationPath;
final deviceId = await getDeviceId();
// Pass the environment variables to the Rust SDK // Pass the environment variables to the Rust SDK
final env = getAppFlowyEnv(); final env = _getAppFlowyConfiguration(
await context.getIt<FlowySDK>().init(dir, jsonEncode(env.toJson())); dir.path,
applicationPath.path,
deviceId,
);
await context.getIt<FlowySDK>().init(jsonEncode(env.toJson()));
} }
@override @override
Future<void> dispose() async {} Future<void> dispose() async {}
} }
AppFlowyEnv getAppFlowyEnv() { AppFlowyConfiguration _getAppFlowyConfiguration(
String customAppPath,
String originAppPath,
String deviceId,
) {
if (isCloudEnabled) { if (isCloudEnabled) {
final supabaseConfig = SupabaseConfiguration( final supabaseConfig = SupabaseConfiguration(
url: Env.supabaseUrl, url: Env.supabaseUrl,
@ -46,21 +57,24 @@ AppFlowyEnv getAppFlowyEnv() {
gotrue_url: Env.afCloudGoTrueUrl, gotrue_url: Env.afCloudGoTrueUrl,
); );
return AppFlowyEnv( return AppFlowyConfiguration(
custom_app_path: customAppPath,
origin_app_path: originAppPath,
device_id: deviceId,
cloud_type: Env.cloudType, cloud_type: Env.cloudType,
supabase_config: supabaseConfig, supabase_config: supabaseConfig,
appflowy_cloud_config: appflowyCloudConfig, appflowy_cloud_config: appflowyCloudConfig,
); );
} else { } else {
// Use the default configuration if the cloud feature is disabled // Use the default configuration if the cloud feature is disabled
final supabaseConfig = SupabaseConfiguration(url: '', anon_key: ''); final supabaseConfig = SupabaseConfiguration.defaultConfig();
final appflowyCloudConfig = AppFlowyCloudConfiguration( final appflowyCloudConfig = AppFlowyCloudConfiguration.defaultConfig();
base_url: '',
ws_base_url: '',
gotrue_url: '',
);
return AppFlowyEnv( return AppFlowyConfiguration(
custom_app_path: customAppPath,
origin_app_path: originAppPath,
device_id: deviceId,
// 0 means the cloud type is local
cloud_type: 0, cloud_type: 0,
supabase_config: supabaseConfig, supabase_config: supabaseConfig,
appflowy_cloud_config: appflowyCloudConfig, appflowy_cloud_config: appflowyCloudConfig,

View File

@ -254,12 +254,44 @@ class _FolderCard extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
FlowyText.regular( Row(
title, crossAxisAlignment: CrossAxisAlignment.center,
fontSize: FontSizes.s14, children: [
fontFamily: GoogleFonts.poppins( FlowyText.regular(
fontWeight: FontWeight.w500, title,
).fontFamily, fontSize: FontSizes.s14,
fontFamily: GoogleFonts.poppins(
fontWeight: FontWeight.w500,
).fontFamily,
),
Tooltip(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(6),
),
preferBelow: false,
richMessage: WidgetSpan(
alignment: PlaceholderAlignment.baseline,
baseline: TextBaseline.alphabetic,
child: Container(
color: Theme.of(context).colorScheme.surface,
padding: const EdgeInsets.all(10),
constraints: const BoxConstraints(maxWidth: 450),
child: FlowyText(
LocaleKeys.settings_menu_customPathPrompt.tr(),
maxLines: null,
),
),
),
child: const FlowyIconButton(
icon: Icon(
Icons.warning_amber_rounded,
size: 20,
color: Colors.orangeAccent,
),
),
),
],
), ),
const VSpace(4), const VSpace(4),
FlowyText.regular( FlowyText.regular(

View File

@ -44,14 +44,28 @@ class SettingsFileLocationCustomizerState
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
didReceivedPath: (path) { didReceivedPath: (path) {
return Row( return Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
// display file paths. Row(
_path(path), mainAxisSize: MainAxisSize.min,
children: [
// display file paths.
_path(path),
// display the icons // display the icons
_buttons(path), _buttons(path),
],
),
const VSpace(10),
IntrinsicHeight(
child: Opacity(
opacity: 0.6,
child: FlowyText.medium(
LocaleKeys.settings_menu_customPathPrompt.tr(),
maxLines: 13,
),
),
),
], ],
); );
}, },

View File

@ -1,5 +1,4 @@
export 'package:async/async.dart'; export 'package:async/async.dart';
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';
@ -27,12 +26,11 @@ class FlowySDK {
void dispose() {} void dispose() {}
Future<void> init(Directory sdkDir, String env) async { Future<void> init(String configuration) async {
final port = RustStreamReceiver.shared.port; final port = RustStreamReceiver.shared.port;
ffi.set_stream_port(port); ffi.set_stream_port(port);
ffi.store_dart_post_cobject(NativeApi.postCObject); ffi.store_dart_post_cobject(NativeApi.postCObject);
ffi.set_env(env.toNativeUtf8()); ffi.init_sdk(configuration.toNativeUtf8());
ffi.init_sdk(sdkDir.path.toNativeUtf8());
} }
} }

View File

@ -20,8 +20,7 @@ DynamicLibrary _open() {
return DynamicLibrary.open('${prefix}/libdart_ffi.so'); return DynamicLibrary.open('${prefix}/libdart_ffi.so');
if (Platform.isMacOS) if (Platform.isMacOS)
return DynamicLibrary.open('${prefix}/libdart_ffi.dylib'); return DynamicLibrary.open('${prefix}/libdart_ffi.dylib');
if (Platform.isIOS) if (Platform.isIOS) return DynamicLibrary.open('${prefix}/libdart_ffi.a');
return DynamicLibrary.open('${prefix}/libdart_ffi.a');
if (Platform.isWindows) if (Platform.isWindows)
return DynamicLibrary.open('${prefix}/dart_ffi.dll'); return DynamicLibrary.open('${prefix}/dart_ffi.dll');
} else { } else {
@ -78,9 +77,9 @@ typedef _invoke_sync_Dart = Pointer<Uint8> Function(
/// C function `init_sdk`. /// C function `init_sdk`.
int init_sdk( int init_sdk(
Pointer<ffi.Utf8> path, Pointer<ffi.Utf8> data,
) { ) {
return _init_sdk(path); return _init_sdk(data);
} }
final _init_sdk_Dart _init_sdk = final _init_sdk_Dart _init_sdk =

View File

@ -3,7 +3,7 @@
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
int64_t init_sdk(char *path); int64_t init_sdk(char *data);
void async_event(int64_t port, const uint8_t *input, uintptr_t len); void async_event(int64_t port, const uint8_t *input, uintptr_t len);

View File

@ -164,6 +164,7 @@ dependencies = [
"tauri-build", "tauri-build",
"tauri-utils", "tauri-utils",
"tracing", "tracing",
"uuid",
] ]
[[package]] [[package]]
@ -369,6 +370,12 @@ version = "0.21.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]] [[package]]
name = "bincode" name = "bincode"
version = "1.3.3" version = "1.3.3"
@ -580,6 +587,16 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "bzip2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
dependencies = [
"bzip2-sys",
"libc",
]
[[package]] [[package]]
name = "bzip2-sys" name = "bzip2-sys"
version = "0.1.11+1.0.8" version = "0.1.11+1.0.8"
@ -863,7 +880,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -883,7 +900,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -913,7 +930,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -925,7 +942,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -945,7 +962,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -959,7 +976,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -1001,7 +1018,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1023,7 +1040,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1050,7 +1067,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -1106,6 +1123,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.4.0" version = "0.4.0"
@ -1813,12 +1836,9 @@ dependencies = [
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "1.9.0" version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
dependencies = [
"instant",
]
[[package]] [[package]]
name = "fdeflate" name = "fdeflate"
@ -2114,7 +2134,7 @@ dependencies = [
"anyhow", "anyhow",
"base64 0.21.5", "base64 0.21.5",
"hmac", "hmac",
"pbkdf2", "pbkdf2 0.12.2",
"rand 0.8.5", "rand 0.8.5",
"sha2", "sha2",
] ]
@ -2251,6 +2271,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"flowy-error", "flowy-error",
"serde", "serde",
"serde_repr",
] ]
[[package]] [[package]]
@ -2964,12 +2985,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]] [[package]]
name = "hex" name = "hex"
version = "0.4.3" version = "0.4.3"
@ -3277,17 +3292,6 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "io-lifetimes"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi 0.3.1",
"libc",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.8.0" version = "2.8.0"
@ -3485,7 +3489,10 @@ dependencies = [
"md5", "md5",
"pin-project", "pin-project",
"rand 0.8.5", "rand 0.8.5",
"tempfile",
"tokio", "tokio",
"walkdir",
"zip",
] ]
[[package]] [[package]]
@ -3590,9 +3597,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.3.8" version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -3975,7 +3982,7 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [ dependencies = [
"hermit-abi 0.2.6", "hermit-abi",
"libc", "libc",
] ]
@ -4207,6 +4214,17 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "password-hash"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
dependencies = [
"base64ct",
"rand_core 0.6.4",
"subtle",
]
[[package]] [[package]]
name = "paste" name = "paste"
version = "1.0.14" version = "1.0.14"
@ -4219,6 +4237,18 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
name = "pbkdf2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
dependencies = [
"digest",
"hmac",
"password-hash",
"sha2",
]
[[package]] [[package]]
name = "pbkdf2" name = "pbkdf2"
version = "0.12.2" version = "0.12.2"
@ -5034,6 +5064,15 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
] ]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.4.3" version = "0.4.3"
@ -5297,13 +5336,12 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.37.20" version = "0.38.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 2.4.0",
"errno", "errno",
"io-lifetimes",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.48.0", "windows-sys 0.48.0",
@ -6420,14 +6458,13 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.6.0" version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
dependencies = [ dependencies = [
"autocfg",
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"redox_syscall 0.3.5", "redox_syscall 0.4.1",
"rustix", "rustix",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -7723,6 +7760,45 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"aes",
"byteorder",
"bzip2",
"constant_time_eq",
"crc32fast",
"crossbeam-utils",
"flate2",
"hmac",
"pbkdf2 0.11.0",
"sha1",
"time",
"zstd",
]
[[package]]
name = "zstd"
version = "0.11.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "5.0.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
dependencies = [
"libc",
"zstd-sys",
]
[[package]] [[package]]
name = "zstd-sys" name = "zstd-sys"
version = "2.0.8+zstd.1.5.5" version = "2.0.8+zstd.1.5.5"

View File

@ -41,6 +41,7 @@ tracing.workspace = true
lib-dispatch = { path = "../../rust-lib/lib-dispatch", features = ["use_serde"] } lib-dispatch = { path = "../../rust-lib/lib-dispatch", features = ["use_serde"] }
flowy-core = { path = "../../rust-lib/flowy-core", features = ["rev-sqlite", "ts"] } flowy-core = { path = "../../rust-lib/flowy-core", features = ["rev-sqlite", "ts"] }
flowy-notification = { path = "../../rust-lib/flowy-notification", features = ["ts"] } flowy-notification = { path = "../../rust-lib/flowy-notification", features = ["ts"] }
uuid = "1.5.0"
[features] [features]
# by default Tauri runs in production mode # by default Tauri runs in production mode
@ -66,14 +67,14 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "b57
# 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 = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }

View File

@ -7,12 +7,22 @@ pub fn init_flowy_core() -> AppFlowyCore {
let mut data_path = tauri::api::path::app_local_data_dir(&config).unwrap(); let mut data_path = tauri::api::path::app_local_data_dir(&config).unwrap();
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
data_path.push("dev"); data_path.push("data_dev");
} else {
data_path.push("data");
} }
data_path.push("data");
let custom_application_path = data_path.to_str().unwrap().to_string();
let application_path = data_path.to_str().unwrap().to_string();
let device_id = uuid::Uuid::new_v4().to_string();
std::env::set_var("RUST_LOG", "trace"); std::env::set_var("RUST_LOG", "trace");
let config = AppFlowyCoreConfig::new(data_path.to_str().unwrap(), DEFAULT_NAME.to_string()) let config = AppFlowyCoreConfig::new(
.log_filter("trace", vec!["appflowy_tauri".to_string()]); custom_application_path,
application_path,
device_id,
DEFAULT_NAME.to_string(),
)
.log_filter("trace", vec!["appflowy_tauri".to_string()]);
AppFlowyCore::new(config) AppFlowyCore::new(config)
} }

View File

@ -272,7 +272,8 @@
"inputTextFieldHint": "Your secret", "inputTextFieldHint": "Your secret",
"historicalUserList": "User login history", "historicalUserList": "User login history",
"historicalUserListTooltip": "This list displays your anonymous accounts. You can click on an account to view its details. Anonymous accounts are created by clicking the 'Get Started' button", "historicalUserListTooltip": "This list displays your anonymous accounts. You can click on an account to view its details. Anonymous accounts are created by clicking the 'Get Started' button",
"openHistoricalUser": "Click to open the anonymous account" "openHistoricalUser": "Click to open the anonymous account",
"customPathPrompt": "Storing the AppFlowy data folder in a cloud-synced folder such as Google Drive can pose risks. If the database within this folder is accessed or modified from multiple locations at the same time, it may result in synchronization conflicts and potential data corruption"
}, },
"notifications": { "notifications": {
"enableNotifications": { "enableNotifications": {

View File

@ -730,7 +730,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -750,7 +750,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -780,7 +780,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -792,7 +792,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -812,7 +812,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -826,7 +826,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -868,7 +868,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -890,7 +890,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -917,7 +917,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=b8097fa891bdbb5826d6e480460c50ab66d26881#b8097fa891bdbb5826d6e480460c50ab66d26881" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a4f8a08544f6b113fb7b26a0c953e1c1979cf22c#a4f8a08544f6b113fb7b26a0c953e1c1979cf22c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -1150,7 +1150,7 @@ dependencies = [
"cssparser-macros", "cssparser-macros",
"dtoa-short", "dtoa-short",
"itoa", "itoa",
"phf 0.8.0", "phf 0.11.2",
"smallvec", "smallvec",
] ]
@ -2079,6 +2079,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"flowy-error", "flowy-error",
"serde", "serde",
"serde_repr",
] ]
[[package]] [[package]]
@ -2995,7 +2996,10 @@ dependencies = [
"md5", "md5",
"pin-project", "pin-project",
"rand 0.8.5", "rand 0.8.5",
"tempfile",
"tokio", "tokio",
"walkdir",
"zip",
] ]
[[package]] [[package]]
@ -3091,9 +3095,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.5" version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -3658,7 +3662,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [ dependencies = [
"phf_macros", "phf_macros 0.8.0",
"phf_shared 0.8.0", "phf_shared 0.8.0",
"proc-macro-hack", "proc-macro-hack",
] ]
@ -3678,6 +3682,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [ dependencies = [
"phf_macros 0.11.2",
"phf_shared 0.11.2", "phf_shared 0.11.2",
] ]
@ -3745,6 +3750,19 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "phf_macros"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
dependencies = [
"phf_generator 0.11.2",
"phf_shared 0.11.2",
"proc-macro2",
"quote",
"syn 2.0.31",
]
[[package]] [[package]]
name = "phf_shared" name = "phf_shared"
version = "0.8.0" version = "0.8.0"
@ -3948,7 +3966,7 @@ checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac"
dependencies = [ dependencies = [
"bytes", "bytes",
"heck 0.4.1", "heck 0.4.1",
"itertools 0.10.5", "itertools 0.11.0",
"log", "log",
"multimap", "multimap",
"once_cell", "once_cell",
@ -3969,7 +3987,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.10.5", "itertools 0.11.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.31", "syn 2.0.31",
@ -4348,6 +4366,15 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
] ]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.4.3" version = "0.4.3"
@ -4621,9 +4648,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.11" version = "0.38.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
dependencies = [ dependencies = [
"bitflags 2.4.0", "bitflags 2.4.0",
"errno", "errno",
@ -5322,13 +5349,13 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.8.0" version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"redox_syscall 0.3.5", "redox_syscall 0.4.1",
"rustix", "rustix",
"windows-sys", "windows-sys",
] ]

View File

@ -109,11 +109,11 @@ client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "b57
# 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 = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "b8097fa891bdbb5826d6e480460c50ab66d26881" } collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "a4f8a08544f6b113fb7b26a0c953e1c1979cf22c" }

View File

@ -8,7 +8,7 @@ use collab::preclude::{CollabBuilder, CollabPlugin};
use collab_entity::{CollabObject, CollabType}; use collab_entity::{CollabObject, CollabType};
use collab_persistence::kv::rocks_kv::RocksCollabDB; use collab_persistence::kv::rocks_kv::RocksCollabDB;
use collab_plugins::cloud_storage::network_state::{CollabNetworkReachability, CollabNetworkState}; use collab_plugins::cloud_storage::network_state::{CollabNetworkReachability, CollabNetworkState};
use collab_plugins::local_storage::rocksdb::RocksdbDiskPlugin; use collab_plugins::local_storage::rocksdb::{RocksdbBackup, RocksdbDiskPlugin};
use collab_plugins::local_storage::CollabPersistenceConfig; use collab_plugins::local_storage::CollabPersistenceConfig;
use collab_plugins::snapshot::{CollabSnapshotPlugin, SnapshotPersistence}; use collab_plugins::snapshot::{CollabSnapshotPlugin, SnapshotPersistence};
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
@ -68,17 +68,19 @@ pub struct AppFlowyCollabBuilder {
workspace_id: RwLock<Option<String>>, workspace_id: RwLock<Option<String>>,
cloud_storage: tokio::sync::RwLock<Arc<dyn CollabStorageProvider>>, cloud_storage: tokio::sync::RwLock<Arc<dyn CollabStorageProvider>>,
snapshot_persistence: Mutex<Option<Arc<dyn SnapshotPersistence>>>, snapshot_persistence: Mutex<Option<Arc<dyn SnapshotPersistence>>>,
device_id: Mutex<String>, rocksdb_backup: Mutex<Option<Arc<dyn RocksdbBackup>>>,
device_id: String,
} }
impl AppFlowyCollabBuilder { impl AppFlowyCollabBuilder {
pub fn new<T: CollabStorageProvider>(storage_provider: T) -> Self { pub fn new<T: CollabStorageProvider>(storage_provider: T, device_id: String) -> Self {
Self { Self {
network_reachability: CollabNetworkReachability::new(), network_reachability: CollabNetworkReachability::new(),
workspace_id: Default::default(), workspace_id: Default::default(),
cloud_storage: tokio::sync::RwLock::new(Arc::new(storage_provider)), cloud_storage: tokio::sync::RwLock::new(Arc::new(storage_provider)),
snapshot_persistence: Default::default(), snapshot_persistence: Default::default(),
device_id: Default::default(), rocksdb_backup: Default::default(),
device_id,
} }
} }
@ -86,12 +88,12 @@ impl AppFlowyCollabBuilder {
*self.snapshot_persistence.lock() = Some(snapshot_persistence); *self.snapshot_persistence.lock() = Some(snapshot_persistence);
} }
pub fn initialize(&self, workspace_id: String) { pub fn set_rocksdb_backup(&self, rocksdb_backup: Arc<dyn RocksdbBackup>) {
*self.workspace_id.write() = Some(workspace_id); *self.rocksdb_backup.lock() = Some(rocksdb_backup);
} }
pub fn set_sync_device(&self, device_id: String) { pub fn initialize(&self, workspace_id: String) {
*self.device_id.lock() = device_id; *self.workspace_id.write() = Some(workspace_id);
} }
pub fn update_network(&self, reachable: bool) { pub fn update_network(&self, reachable: bool) {
@ -120,7 +122,7 @@ impl AppFlowyCollabBuilder {
object_id.to_string(), object_id.to_string(),
collab_type, collab_type,
workspace_id, workspace_id,
self.device_id.lock().clone(), self.device_id.clone(),
)) ))
} }
@ -146,15 +148,9 @@ impl AppFlowyCollabBuilder {
raw_data: CollabRawData, raw_data: CollabRawData,
collab_db: Weak<RocksCollabDB>, collab_db: Weak<RocksCollabDB>,
) -> Result<Arc<MutexCollab>, Error> { ) -> Result<Arc<MutexCollab>, Error> {
let config = CollabPersistenceConfig::default();
self self
.build_with_config( .build_with_config(uid, object_id, object_type, collab_db, raw_data, &config)
uid,
object_id,
object_type,
collab_db,
raw_data,
&CollabPersistenceConfig::default(),
)
.await .await
} }
@ -188,8 +184,9 @@ impl AppFlowyCollabBuilder {
uid, uid,
collab_db.clone(), collab_db.clone(),
config.clone(), config.clone(),
self.rocksdb_backup.lock().clone(),
)) ))
.with_device_id(self.device_id.lock().clone()) .with_device_id(self.device_id.clone())
.build()?, .build()?,
); );
{ {

View File

@ -3,7 +3,7 @@
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
int64_t init_sdk(char *path); int64_t init_sdk(char *data);
void async_event(int64_t port, const uint8_t *input, uintptr_t len); void async_event(int64_t port, const uint8_t *input, uintptr_t len);

View File

@ -1,63 +1,37 @@
use serde::Deserialize; use serde::Deserialize;
use serde_repr::Deserialize_repr;
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_server_config::AuthenticatorType;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct AppFlowyEnv { pub struct AppFlowyDartConfiguration {
cloud_type: CloudType, /// This path will be used to store the user data
supabase_config: SupabaseConfiguration, pub custom_app_path: String,
appflowy_cloud_config: AFCloudConfiguration, pub origin_app_path: String,
pub device_id: String,
pub cloud_type: AuthenticatorType,
pub(crate) supabase_config: SupabaseConfiguration,
pub(crate) appflowy_cloud_config: AFCloudConfiguration,
} }
const CLOUT_TYPE_STR: &str = "APPFLOWY_CLOUD_ENV_CLOUD_TYPE"; impl AppFlowyDartConfiguration {
pub fn from_str(s: &str) -> Self {
#[derive(Deserialize_repr, Debug, Clone)] serde_json::from_str::<AppFlowyDartConfiguration>(s).unwrap()
#[repr(u8)]
pub enum CloudType {
Local = 0,
Supabase = 1,
AppFlowyCloud = 2,
}
impl CloudType {
fn write_env(&self) {
let s = self.clone() as u8;
std::env::set_var(CLOUT_TYPE_STR, s.to_string());
} }
#[allow(dead_code)]
fn from_str(s: &str) -> Self {
match s {
"0" => CloudType::Local,
"1" => CloudType::Supabase,
"2" => CloudType::AppFlowyCloud,
_ => CloudType::Local,
}
}
#[allow(dead_code)]
pub fn from_env() -> Self {
let cloud_type_str = std::env::var(CLOUT_TYPE_STR).unwrap_or_default();
CloudType::from_str(&cloud_type_str)
}
}
impl AppFlowyEnv {
/// Parse the environment variable from the frontend application. The frontend will /// Parse the environment variable from the frontend application. The frontend will
/// pass the environment variable as a json string after launching. /// pass the environment variable as a json string after launching.
pub fn write_env_from(env_str: &str) { pub fn write_env_from(env_str: &str) {
if let Ok(env) = serde_json::from_str::<AppFlowyEnv>(env_str) { let configuration = Self::from_str(env_str);
let _ = env.cloud_type.write_env(); configuration.cloud_type.write_env();
let is_valid = env.appflowy_cloud_config.write_env().is_ok(); let is_valid = configuration.appflowy_cloud_config.write_env().is_ok();
// Note on Configuration Priority: // Note on Configuration Priority:
// If both Supabase config and AppFlowy cloud config are provided in the '.env' file, // If both Supabase config and AppFlowy cloud config are provided in the '.env' file,
// the AppFlowy cloud config will be prioritized and the Supabase config ignored. // the AppFlowy cloud config will be prioritized and the Supabase config ignored.
// Ensure only one of these configurations is active at any given time. // Ensure only one of these configurations is active at any given time.
if !is_valid { if !is_valid {
let _ = env.supabase_config.write_env(); let _ = configuration.supabase_config.write_env();
}
} }
} }
} }

View File

@ -13,7 +13,7 @@ use flowy_notification::{register_notification_sender, unregister_all_notificati
use lib_dispatch::prelude::ToBytes; use lib_dispatch::prelude::ToBytes;
use lib_dispatch::prelude::*; use lib_dispatch::prelude::*;
use crate::env_serde::AppFlowyEnv; use crate::env_serde::AppFlowyDartConfiguration;
use crate::notification::DartNotificationSender; use crate::notification::DartNotificationSender;
use crate::{ use crate::{
c::{extend_front_four_bytes_into_bytes, forget_rust}, c::{extend_front_four_bytes_into_bytes, forget_rust},
@ -49,13 +49,29 @@ unsafe impl Sync for MutexAppFlowyCore {}
unsafe impl Send for MutexAppFlowyCore {} unsafe impl Send for MutexAppFlowyCore {}
#[no_mangle] #[no_mangle]
pub extern "C" fn init_sdk(path: *mut c_char) -> i64 { pub extern "C" fn init_sdk(data: *mut c_char) -> i64 {
let c_str: &CStr = unsafe { CStr::from_ptr(path) }; let c_str = unsafe { CStr::from_ptr(data) };
let path: &str = c_str.to_str().unwrap(); let serde_str = c_str.to_str().unwrap();
let configuration = AppFlowyDartConfiguration::from_str(serde_str);
configuration.cloud_type.write_env();
let is_valid = configuration.appflowy_cloud_config.write_env().is_ok();
// Note on Configuration Priority:
// If both Supabase config and AppFlowy cloud config are provided in the '.env' file,
// the AppFlowy cloud config will be prioritized and the Supabase config ignored.
// Ensure only one of these configurations is active at any given time.
if !is_valid {
let _ = configuration.supabase_config.write_env();
}
let log_crates = vec!["flowy-ffi".to_string()]; let log_crates = vec!["flowy-ffi".to_string()];
let config = let config = AppFlowyCoreConfig::new(
AppFlowyCoreConfig::new(path, DEFAULT_NAME.to_string()).log_filter("info", log_crates); configuration.custom_app_path,
configuration.origin_app_path,
configuration.device_id,
DEFAULT_NAME.to_string(),
)
.log_filter("info", log_crates);
*APPFLOWY_CORE.0.lock() = Some(AppFlowyCore::new(config)); *APPFLOWY_CORE.0.lock() = Some(AppFlowyCore::new(config));
0 0
} }
@ -163,5 +179,5 @@ pub extern "C" fn backend_log(level: i64, data: *const c_char) {
pub extern "C" fn set_env(data: *const c_char) { pub extern "C" fn set_env(data: *const c_char) {
let c_str = unsafe { CStr::from_ptr(data) }; let c_str = unsafe { CStr::from_ptr(data) };
let serde_str = c_str.to_str().unwrap(); let serde_str = c_str.to_str().unwrap();
AppFlowyEnv::write_env_from(serde_str); AppFlowyDartConfiguration::write_env_from(serde_str);
} }

View File

@ -34,8 +34,10 @@ impl EventIntegrationTest {
std::fs::create_dir_all(&temp_dir).unwrap(); std::fs::create_dir_all(&temp_dir).unwrap();
Self::new_with_user_data_path(temp_dir, nanoid!(6)).await Self::new_with_user_data_path(temp_dir, nanoid!(6)).await
} }
pub async fn new_with_user_data_path(path: PathBuf, name: String) -> Self { pub async fn new_with_user_data_path(path_buf: PathBuf, name: String) -> Self {
let config = AppFlowyCoreConfig::new(path.to_str().unwrap(), name).log_filter( let path = path_buf.to_str().unwrap().to_string();
let device_id = uuid::Uuid::new_v4().to_string();
let config = AppFlowyCoreConfig::new(path.clone(), path, device_id, name).log_filter(
"trace", "trace",
vec![ vec![
"flowy_test".to_string(), "flowy_test".to_string(),
@ -53,7 +55,7 @@ impl EventIntegrationTest {
inner, inner,
auth_type, auth_type,
notification_sender, notification_sender,
cleaner: Arc::new(Cleaner(path)), cleaner: Arc::new(Cleaner(path_buf)),
} }
} }
} }

View File

@ -787,7 +787,7 @@ async fn hide_group_event_test() {
let groups = test.get_groups(&board_view.id).await; let groups = test.get_groups(&board_view.id).await;
assert_eq!(groups.len(), 4); assert_eq!(groups.len(), 4);
assert_eq!(groups[0].is_visible, false); assert!(!groups[0].is_visible);
} }
// Update the database layout type from grid to board // Update the database layout type from grid to board

View File

@ -0,0 +1,20 @@
use event_integration::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use crate::util::unzip_history_user_db;
#[tokio::test]
async fn migrate_historical_empty_document_test() {
let (cleaner, user_db_path) = unzip_history_user_db(
"./tests/user/migration_test/history_user_db",
"038_collab_db_corrupt_restore",
)
.unwrap();
let test =
EventIntegrationTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string()).await;
let views = test.get_all_workspace_views().await;
assert_eq!(views.len(), 1);
drop(cleaner);
}

View File

@ -1,2 +1,4 @@
mod document_test; mod document_test;
mod version_test; mod version_test;
mod collab_db_restore;

View File

@ -7,16 +7,22 @@ use tracing::{error, info};
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_user::manager::URL_SAFE_ENGINE; use flowy_user::manager::URL_SAFE_ENGINE;
use lib_infra::file_util::copy_dir_recursive;
use crate::integrate::log::create_log_filter; use crate::integrate::log::create_log_filter;
use crate::integrate::util::copy_dir_recursive;
#[derive(Clone)] #[derive(Clone)]
pub struct AppFlowyCoreConfig { pub struct AppFlowyCoreConfig {
/// Different `AppFlowyCoreConfig` instance should have different name /// Different `AppFlowyCoreConfig` instance should have different name
pub(crate) name: String, pub(crate) name: String,
/// Panics if the `root` path is not existing pub(crate) device_id: String,
/// Used to store the user data
pub storage_path: String, pub storage_path: String,
/// Origin application path is the path of the application binary. By default, the
/// storage_path is the same as the origin_application_path. However, when the user
/// choose a custom path for the user data, the storage_path will be different from
/// the origin_application_path.
pub application_path: String,
pub(crate) log_filter: String, pub(crate) log_filter: String,
cloud_config: Option<AFCloudConfiguration>, cloud_config: Option<AFCloudConfiguration>,
} }
@ -25,6 +31,7 @@ impl fmt::Debug for AppFlowyCoreConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_struct("AppFlowy Configuration"); let mut debug = f.debug_struct("AppFlowy Configuration");
debug.field("storage_path", &self.storage_path); debug.field("storage_path", &self.storage_path);
debug.field("application_path", &self.application_path);
if let Some(config) = &self.cloud_config { if let Some(config) = &self.cloud_config {
debug.field("base_url", &config.base_url); debug.field("base_url", &config.base_url);
debug.field("ws_url", &config.ws_base_url); debug.field("ws_url", &config.ws_base_url);
@ -36,7 +43,7 @@ impl fmt::Debug for AppFlowyCoreConfig {
fn migrate_local_version_data_folder(root: &str, url: &str) -> String { fn migrate_local_version_data_folder(root: &str, url: &str) -> String {
// Isolate the user data folder by using the base url of AppFlowy cloud. This is to avoid // Isolate the user data folder by using the base url of AppFlowy cloud. This is to avoid
// the user data folder being shared by different AppFlowy cloud. // the user data folder being shared by different AppFlowy cloud.
let server_base64 = URL_SAFE_ENGINE.encode(&url); let server_base64 = URL_SAFE_ENGINE.encode(url);
let storage_path = format!("{}_{}", root, server_base64); let storage_path = format!("{}_{}", root, server_base64);
// Copy the user data folder from the root path to the isolated path // Copy the user data folder from the root path to the isolated path
@ -44,7 +51,7 @@ fn migrate_local_version_data_folder(root: &str, url: &str) -> String {
if !Path::new(&storage_path).exists() && Path::new(root).exists() { if !Path::new(&storage_path).exists() && Path::new(root).exists() {
info!("Copy dir from {} to {}", root, storage_path); info!("Copy dir from {} to {}", root, storage_path);
let src = Path::new(root); let src = Path::new(root);
match copy_dir_recursive(&src, Path::new(&storage_path)) { match copy_dir_recursive(src, Path::new(&storage_path)) {
Ok(_) => storage_path, Ok(_) => storage_path,
Err(err) => { Err(err) => {
// when the copy dir failed, use the root path as the storage path // when the copy dir failed, use the root path as the storage path
@ -58,22 +65,29 @@ fn migrate_local_version_data_folder(root: &str, url: &str) -> String {
} }
impl AppFlowyCoreConfig { impl AppFlowyCoreConfig {
pub fn new(root: &str, name: String) -> Self { pub fn new(
custom_application_path: String,
application_path: String,
device_id: String,
name: String,
) -> Self {
let cloud_config = AFCloudConfiguration::from_env().ok(); let cloud_config = AFCloudConfiguration::from_env().ok();
let storage_path = match &cloud_config { let storage_path = match &cloud_config {
None => { None => {
let supabase_config = SupabaseConfiguration::from_env().ok(); let supabase_config = SupabaseConfiguration::from_env().ok();
match &supabase_config { match &supabase_config {
None => root.to_string(), None => custom_application_path,
Some(config) => migrate_local_version_data_folder(root, &config.url), Some(config) => migrate_local_version_data_folder(&custom_application_path, &config.url),
} }
}, },
Some(config) => migrate_local_version_data_folder(root, &config.base_url), Some(config) => migrate_local_version_data_folder(&custom_application_path, &config.base_url),
}; };
AppFlowyCoreConfig { AppFlowyCoreConfig {
name, name,
storage_path, storage_path,
application_path,
device_id,
log_filter: create_log_filter("info".to_owned(), vec![]), log_filter: create_log_filter("info".to_owned(), vec![]),
cloud_config, cloud_config,
} }

View File

@ -0,0 +1,84 @@
use std::sync::{Arc, Weak};
use collab::core::collab_plugin::EncodedCollabV1;
use collab_plugins::local_storage::rocksdb::RocksdbBackup;
use diesel::SqliteConnection;
use flowy_error::FlowyError;
use flowy_sqlite::{prelude::*, schema::rocksdb_backup, schema::rocksdb_backup::dsl};
use flowy_user::manager::UserManager;
use lib_infra::util::timestamp;
pub struct RocksdbBackupImpl(pub Weak<UserManager>);
impl RocksdbBackupImpl {
fn get_pool(&self, uid: i64) -> Result<Arc<ConnectionPool>, FlowyError> {
self
.0
.upgrade()
.ok_or(FlowyError::internal().with_context("Unexpected error: UserSession is None"))?
.db_pool(uid)
}
}
impl RocksdbBackup for RocksdbBackupImpl {
fn save_doc(
&self,
uid: i64,
object_id: &str,
data: EncodedCollabV1,
) -> Result<(), anyhow::Error> {
let row = RocksdbBackupRow {
object_id: object_id.to_string(),
timestamp: timestamp(),
data: data.encode_to_bytes().unwrap_or_default().to_vec(),
};
self
.get_pool(uid)
.map(|pool| RocksdbBackupTableSql::create(row, &*pool.get()?))??;
Ok(())
}
fn get_doc(&self, uid: i64, object_id: &str) -> Result<EncodedCollabV1, anyhow::Error> {
let sql = dsl::rocksdb_backup
.filter(dsl::object_id.eq(object_id))
.into_boxed();
let pool = self.get_pool(uid)?;
let row = pool
.get()
.map(|conn| sql.first::<RocksdbBackupRow>(&*conn))??;
Ok(EncodedCollabV1::decode_from_bytes(&row.data)?)
}
}
#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
#[table_name = "rocksdb_backup"]
#[primary_key(object_id)]
struct RocksdbBackupRow {
object_id: String,
timestamp: i64,
data: Vec<u8>,
}
struct RocksdbBackupTableSql;
impl RocksdbBackupTableSql {
fn create(row: RocksdbBackupRow, conn: &SqliteConnection) -> Result<(), FlowyError> {
let _ = replace_into(dsl::rocksdb_backup)
.values(&row)
.execute(conn)?;
Ok(())
}
#[allow(dead_code)]
fn get_row(object_id: &str, conn: &SqliteConnection) -> Result<RocksdbBackupRow, FlowyError> {
let sql = dsl::rocksdb_backup
.filter(dsl::object_id.eq(object_id))
.into_boxed();
let row = sql.first::<RocksdbBackupRow>(conn)?;
Ok(row)
}
}

View File

@ -2,9 +2,7 @@ use std::sync::Weak;
use diesel::SqliteConnection; use diesel::SqliteConnection;
use collab_integrate::{ use collab_integrate::{CollabSnapshot, PersistenceError, SnapshotPersistence};
calculate_snapshot_diff, CollabSnapshot, PersistenceError, SnapshotPersistence,
};
use flowy_error::FlowyError; use flowy_error::FlowyError;
use flowy_sqlite::{ use flowy_sqlite::{
insert_or_ignore_into, insert_or_ignore_into,
@ -49,21 +47,13 @@ impl SnapshotPersistence for SnapshotDBImpl {
.get() .get()
.map_err(|e| PersistenceError::Internal(e.into()))?; .map_err(|e| PersistenceError::Internal(e.into()))?;
let desc = match CollabSnapshotTableSql::get_latest_snapshot(&object_id, &conn) {
None => Ok("".to_string()),
Some(old_snapshot) => {
calculate_snapshot_diff(uid, &object_id, &old_snapshot.data, &snapshot_data)
},
}
.map_err(|e| PersistenceError::InvalidData(format!("{:?}", e)))?;
// Save the snapshot data to disk // Save the snapshot data to disk
let result = CollabSnapshotTableSql::create( let result = CollabSnapshotTableSql::create(
CollabSnapshotRow { CollabSnapshotRow {
id: uuid::Uuid::new_v4().to_string(), id: uuid::Uuid::new_v4().to_string(),
object_id: object_id.clone(), object_id: object_id.clone(),
title, title,
desc, desc: "".to_string(),
collab_type: "".to_string(), collab_type: "".to_string(),
timestamp: timestamp(), timestamp: timestamp(),
data: snapshot_data, data: snapshot_data,
@ -137,6 +127,7 @@ impl CollabSnapshotTableSql {
Ok(rows) Ok(rows)
} }
#[allow(dead_code)]
fn get_latest_snapshot(object_id: &str, conn: &SqliteConnection) -> Option<CollabSnapshotRow> { fn get_latest_snapshot(object_id: &str, conn: &SqliteConnection) -> Option<CollabSnapshotRow> {
let sql = dsl::collab_snapshot let sql = dsl::collab_snapshot
.filter(dsl::object_id.eq(object_id)) .filter(dsl::object_id.eq(object_id))

View File

@ -128,7 +128,7 @@ impl FolderOperationHandler for DocumentFolderOperation {
let manager = self.0.clone(); let manager = self.0.clone();
let view_id = view_id.to_string(); let view_id = view_id.to_string();
FutureResult::new(async move { FutureResult::new(async move {
manager.close_document(&view_id)?; manager.close_document(&view_id).await?;
Ok(()) Ok(())
}) })
} }

View File

@ -8,5 +8,6 @@ mod document_deps;
mod folder_deps; mod folder_deps;
mod util; mod util;
pub mod collab_backup;
mod database_deps; mod database_deps;
mod user_deps; mod user_deps;

View File

@ -3,4 +3,3 @@ pub(crate) mod log;
pub(crate) mod server; pub(crate) mod server;
mod trait_impls; mod trait_impls;
pub(crate) mod user; pub(crate) mod user;
pub(crate) mod util;

View File

@ -6,7 +6,7 @@ use parking_lot::RwLock;
use serde_repr::*; use serde_repr::*;
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{FlowyError, FlowyResult};
use flowy_server::af_cloud::AFCloudServer; use flowy_server::af_cloud::AppFlowyCloudServer;
use flowy_server::local_server::{LocalServer, LocalServerDB}; use flowy_server::local_server::{LocalServer, LocalServerDB};
use flowy_server::supabase::SupabaseServer; use flowy_server::supabase::SupabaseServer;
use flowy_server::{AppFlowyEncryption, AppFlowyServer, EncryptionImpl}; use flowy_server::{AppFlowyEncryption, AppFlowyServer, EncryptionImpl};
@ -58,7 +58,6 @@ pub struct ServerProvider {
pub(crate) store_preferences: Weak<StorePreferences>, pub(crate) store_preferences: Weak<StorePreferences>,
pub(crate) cache_user_service: RwLock<HashMap<ServerType, Arc<dyn UserCloudService>>>, pub(crate) cache_user_service: RwLock<HashMap<ServerType, Arc<dyn UserCloudService>>>,
pub(crate) device_id: Arc<RwLock<String>>,
pub(crate) enable_sync: RwLock<bool>, pub(crate) enable_sync: RwLock<bool>,
pub(crate) uid: Arc<RwLock<Option<i64>>>, pub(crate) uid: Arc<RwLock<Option<i64>>>,
} }
@ -73,7 +72,6 @@ impl ServerProvider {
Self { Self {
config, config,
server_type: RwLock::new(server_type), server_type: RwLock::new(server_type),
device_id: Arc::new(RwLock::new(uuid::Uuid::new_v4().to_string())),
providers: RwLock::new(HashMap::new()), providers: RwLock::new(HashMap::new()),
enable_sync: RwLock::new(true), enable_sync: RwLock::new(true),
encryption: RwLock::new(Arc::new(encryption)), encryption: RwLock::new(Arc::new(encryption)),
@ -115,10 +113,10 @@ impl ServerProvider {
}, },
ServerType::AFCloud => { ServerType::AFCloud => {
let config = AFCloudConfiguration::from_env()?; let config = AFCloudConfiguration::from_env()?;
let server = Arc::new(AFCloudServer::new( let server = Arc::new(AppFlowyCloudServer::new(
config, config,
*self.enable_sync.read(), *self.enable_sync.read(),
self.device_id.clone(), self.config.device_id.clone(),
)); ));
Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server) Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
@ -132,7 +130,7 @@ impl ServerProvider {
uid, uid,
config, config,
*self.enable_sync.read(), *self.enable_sync.read(),
self.device_id.clone(), self.config.device_id.clone(),
encryption, encryption,
))) )))
}, },

View File

@ -110,15 +110,6 @@ impl UserCloudServiceProvider for ServerProvider {
Authenticator::from(server_type) Authenticator::from(server_type)
} }
fn set_device_id(&self, device_id: &str) {
if device_id.is_empty() {
tracing::error!("🔴Device id is empty");
return;
}
*self.device_id.write() = device_id.to_string();
}
/// Returns the [UserCloudService] base on the current [ServerType]. /// Returns the [UserCloudService] base on the current [ServerType].
/// Creates a new [AppFlowyServer] if it doesn't exist. /// Creates a new [AppFlowyServer] if it doesn't exist.
fn get_user_service(&self) -> Result<Arc<dyn UserCloudService>, FlowyError> { fn get_user_service(&self) -> Result<Arc<dyn UserCloudService>, FlowyError> {

View File

@ -1,20 +0,0 @@
use std::fs::{self};
use std::io;
use std::path::Path;
use walkdir::WalkDir;
pub fn copy_dir_recursive(src: &Path, dst: &Path) -> io::Result<()> {
for entry in WalkDir::new(src).into_iter().filter_map(|e| e.ok()) {
let path = entry.path();
let relative_path = path.strip_prefix(src).unwrap();
let target_path = dst.join(relative_path);
if path.is_dir() {
fs::create_dir_all(&target_path)?;
} else {
fs::copy(path, target_path)?;
}
}
Ok(())
}

View File

@ -15,13 +15,14 @@ use flowy_sqlite::kv::StorePreferences;
use flowy_storage::FileStorageService; use flowy_storage::FileStorageService;
use flowy_task::{TaskDispatcher, TaskRunner}; use flowy_task::{TaskDispatcher, TaskRunner};
use flowy_user::event_map::UserCloudServiceProvider; use flowy_user::event_map::UserCloudServiceProvider;
use flowy_user::manager::{UserManager, UserSessionConfig}; use flowy_user::manager::{UserConfig, UserManager};
use lib_dispatch::prelude::*; use lib_dispatch::prelude::*;
use lib_dispatch::runtime::AFPluginRuntime; use lib_dispatch::runtime::AFPluginRuntime;
use module::make_plugins; use module::make_plugins;
pub use module::*; pub use module::*;
use crate::config::AppFlowyCoreConfig; use crate::config::AppFlowyCoreConfig;
use crate::deps_resolve::collab_backup::RocksdbBackupImpl;
use crate::deps_resolve::*; use crate::deps_resolve::*;
use crate::integrate::collab_interact::CollabInteractImpl; use crate::integrate::collab_interact::CollabInteractImpl;
use crate::integrate::log::init_log; use crate::integrate::log::init_log;
@ -108,7 +109,10 @@ impl AppFlowyCore {
) = async { ) = async {
/// The shared collab builder is used to build the [Collab] instance. The plugins will be loaded /// The shared collab builder is used to build the [Collab] instance. The plugins will be loaded
/// on demand based on the [CollabPluginConfig]. /// on demand based on the [CollabPluginConfig].
let collab_builder = Arc::new(AppFlowyCollabBuilder::new(server_provider.clone())); let collab_builder = Arc::new(AppFlowyCollabBuilder::new(
server_provider.clone(),
config.device_id.clone(),
));
let user_manager = init_user_manager( let user_manager = init_user_manager(
&config, &config,
&store_preference, &store_preference,
@ -119,6 +123,8 @@ impl AppFlowyCore {
collab_builder collab_builder
.set_snapshot_persistence(Arc::new(SnapshotDBImpl(Arc::downgrade(&user_manager)))); .set_snapshot_persistence(Arc::new(SnapshotDBImpl(Arc::downgrade(&user_manager))));
collab_builder.set_rocksdb_backup(Arc::new(RocksdbBackupImpl(Arc::downgrade(&user_manager))));
let database_manager = DatabaseDepsResolver::resolve( let database_manager = DatabaseDepsResolver::resolve(
Arc::downgrade(&user_manager), Arc::downgrade(&user_manager),
task_dispatcher.clone(), task_dispatcher.clone(),
@ -169,9 +175,9 @@ impl AppFlowyCore {
document_manager: Arc::downgrade(&document_manager), document_manager: Arc::downgrade(&document_manager),
}; };
let cloned_user_session = Arc::downgrade(&user_manager); let cloned_user_manager = Arc::downgrade(&user_manager);
if let Some(user_session) = cloned_user_session.upgrade() { if let Some(user_manager) = cloned_user_manager.upgrade() {
if let Err(err) = user_session if let Err(err) = user_manager
.init(user_status_callback, collab_interact_impl) .init(user_status_callback, collab_interact_impl)
.await .await
{ {
@ -212,7 +218,12 @@ fn init_user_manager(
user_cloud_service_provider: Arc<dyn UserCloudServiceProvider>, user_cloud_service_provider: Arc<dyn UserCloudServiceProvider>,
collab_builder: Weak<AppFlowyCollabBuilder>, collab_builder: Weak<AppFlowyCollabBuilder>,
) -> Arc<UserManager> { ) -> Arc<UserManager> {
let user_config = UserSessionConfig::new(&config.name, &config.storage_path); let user_config = UserConfig::new(
&config.name,
&config.storage_path,
&config.application_path,
&config.device_id,
);
UserManager::new( UserManager::new(
user_config, user_config,
user_cloud_service_provider, user_cloud_service_provider,

View File

@ -117,6 +117,10 @@ impl DatabaseEditor {
/// ///
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub async fn close_view_editor(&self, view_id: &str) -> bool { pub async fn close_view_editor(&self, view_id: &str) -> bool {
if let Some(database) = self.database.try_lock() {
let _ = database.flush();
}
self.database_views.close_view(view_id).await self.database_views.close_view(view_id).await
} }

View File

@ -18,12 +18,11 @@ use lib_dispatch::prelude::{
use crate::entities::*; use crate::entities::*;
use crate::parser::document_data_parser::DocumentDataParser; use crate::parser::document_data_parser::DocumentDataParser;
use crate::parser::external::parser::ExternalDataToNestedJSONParser;
use crate::parser::parser_entities::{ use crate::parser::parser_entities::{
ConvertDataToJsonParams, ConvertDataToJsonPayloadPB, ConvertDataToJsonResponsePB, ConvertDataToJsonParams, ConvertDataToJsonPayloadPB, ConvertDataToJsonResponsePB,
ConvertDocumentParams, ConvertDocumentPayloadPB, ConvertDocumentResponsePB, ConvertDocumentParams, ConvertDocumentPayloadPB, ConvertDocumentResponsePB,
}; };
use crate::parser::external::parser::ExternalDataToNestedJSONParser;
use crate::{manager::DocumentManager, parser::json::parser::JsonToDocumentParser}; use crate::{manager::DocumentManager, parser::json::parser::JsonToDocumentParser};
fn upgrade_document( fn upgrade_document(
@ -69,7 +68,7 @@ pub(crate) async fn close_document_handler(
let manager = upgrade_document(manager)?; let manager = upgrade_document(manager)?;
let params: CloseDocumentParams = data.into_inner().try_into()?; let params: CloseDocumentParams = data.into_inner().try_into()?;
let doc_id = params.document_id; let doc_id = params.document_id;
manager.close_document(&doc_id)?; manager.close_document(&doc_id).await?;
Ok(()) Ok(())
} }

View File

@ -165,9 +165,14 @@ impl DocumentManager {
} }
#[instrument(level = "debug", skip(self), err)] #[instrument(level = "debug", skip(self), err)]
pub fn close_document(&self, doc_id: &str) -> FlowyResult<()> { pub async fn close_document(&self, doc_id: &str) -> FlowyResult<()> {
// TODO(nathan): remove the document from lru cache. Currently, we don't remove it from the cache.
// The lru will pop the least recently used document when the cache is full. // The lru will pop the least recently used document when the cache is full.
if let Ok(doc) = self.get_document(doc_id).await {
if let Some(doc) = doc.try_lock() {
let _ = doc.flush();
}
}
Ok(()) Ok(())
} }

View File

@ -29,7 +29,7 @@ async fn restore_document() {
.get_document_data() .get_document_data()
.unwrap(); .unwrap();
// close a document // close a document
_ = test.close_document(&doc_id); _ = test.close_document(&doc_id).await;
assert_eq!(data_b, data); assert_eq!(data_b, data);
// restore // restore
@ -43,7 +43,7 @@ async fn restore_document() {
.get_document_data() .get_document_data()
.unwrap(); .unwrap();
// close a document // close a document
_ = test.close_document(&doc_id); _ = test.close_document(&doc_id).await;
assert_eq!(data_b, data); assert_eq!(data_b, data);
} }
@ -85,7 +85,7 @@ async fn document_apply_insert_action() {
document.lock().apply_action(vec![insert_text_action]); document.lock().apply_action(vec![insert_text_action]);
let data_a = document.lock().get_document_data().unwrap(); let data_a = document.lock().get_document_data().unwrap();
// close the original document // close the original document
_ = test.close_document(&doc_id); _ = test.close_document(&doc_id).await;
// re-open the document // re-open the document
let data_b = test let data_b = test
@ -96,7 +96,7 @@ async fn document_apply_insert_action() {
.get_document_data() .get_document_data()
.unwrap(); .unwrap();
// close a document // close a document
_ = test.close_document(&doc_id); _ = test.close_document(&doc_id).await;
assert_eq!(data_b, data_a); assert_eq!(data_b, data_a);
} }
@ -135,7 +135,7 @@ async fn document_apply_update_page_action() {
tracing::trace!("{:?}", &actions); tracing::trace!("{:?}", &actions);
document.lock().apply_action(actions); document.lock().apply_action(actions);
let page_block_old = document.lock().get_block(&data.page_id).unwrap(); let page_block_old = document.lock().get_block(&data.page_id).unwrap();
_ = test.close_document(&doc_id); _ = test.close_document(&doc_id).await;
// re-open the document // re-open the document
let document = test.get_document(&doc_id).await.unwrap(); let document = test.get_document(&doc_id).await.unwrap();
@ -206,12 +206,12 @@ async fn document_apply_update_action() {
}; };
document.lock().apply_action(vec![update_text_action]); document.lock().apply_action(vec![update_text_action]);
// close the original document // close the original document
_ = test.close_document(&doc_id); _ = test.close_document(&doc_id).await;
// re-open the document // re-open the document
let document = test.get_document(&doc_id).await.unwrap(); let document = test.get_document(&doc_id).await.unwrap();
let block = document.lock().get_block(&text_block_id).unwrap(); let block = document.lock().get_block(&text_block_id).unwrap();
assert_eq!(block.data, updated_text_block_data); assert_eq!(block.data, updated_text_block_data);
// close a document // close a document
_ = test.close_document(&doc_id); _ = test.close_document(&doc_id).await;
} }

View File

@ -92,8 +92,8 @@ pub fn db() -> Arc<RocksCollabDB> {
} }
pub fn default_collab_builder() -> Arc<AppFlowyCollabBuilder> { pub fn default_collab_builder() -> Arc<AppFlowyCollabBuilder> {
let builder = AppFlowyCollabBuilder::new(DefaultCollabStorageProvider()); let builder =
builder.set_sync_device(uuid::Uuid::new_v4().to_string()); AppFlowyCollabBuilder::new(DefaultCollabStorageProvider(), "fake_device_id".to_string());
builder.initialize(uuid::Uuid::new_v4().to_string()); builder.initialize(uuid::Uuid::new_v4().to_string());
Arc::new(builder) Arc::new(builder)
} }

View File

@ -197,8 +197,8 @@ pub enum ErrorCode {
#[error("Missing auth field")] #[error("Missing auth field")]
MissingAuthField = 65, MissingAuthField = 65,
#[error("Only one application can access the database")] #[error("Rocksdb IO error")]
MultipleDBInstance = 66, RocksdbIOError = 66,
#[error("Document id is empty")] #[error("Document id is empty")]
DocumentIdIsEmpty = 67, DocumentIdIsEmpty = 67,
@ -256,6 +256,12 @@ pub enum ErrorCode {
#[error("Not support yet")] #[error("Not support yet")]
NotSupportYet = 85, NotSupportYet = 85,
#[error("rocksdb corruption")]
RocksdbCorruption = 86,
#[error("rocksdb internal error")]
RocksdbInternal = 87,
} }
impl ErrorCode { impl ErrorCode {

View File

@ -7,4 +7,5 @@ edition = "2021"
[dependencies] [dependencies]
flowy-error = { workspace = true } flowy-error = { workspace = true }
serde.workspace = true serde.workspace = true
serde_repr.workspace = true

View File

@ -1,2 +1,37 @@
use serde_repr::Deserialize_repr;
pub mod af_cloud_config; pub mod af_cloud_config;
pub mod supabase_config; pub mod supabase_config;
pub const CLOUT_TYPE_STR: &str = "APPFLOWY_CLOUD_ENV_CLOUD_TYPE";
#[derive(Deserialize_repr, Debug, Clone)]
#[repr(u8)]
pub enum AuthenticatorType {
Local = 0,
Supabase = 1,
AppFlowyCloud = 2,
}
impl AuthenticatorType {
pub fn write_env(&self) {
let s = self.clone() as u8;
std::env::set_var(CLOUT_TYPE_STR, s.to_string());
}
#[allow(dead_code)]
fn from_str(s: &str) -> Self {
match s {
"0" => AuthenticatorType::Local,
"1" => AuthenticatorType::Supabase,
"2" => AuthenticatorType::AppFlowyCloud,
_ => AuthenticatorType::Local,
}
}
#[allow(dead_code)]
pub fn from_env() -> Self {
let cloud_type_str = std::env::var(CLOUT_TYPE_STR).unwrap_or_default();
AuthenticatorType::from_str(&cloud_type_str)
}
}

View File

@ -18,7 +18,7 @@ use crate::af_cloud::impls::user::dto::{
}; };
use crate::af_cloud::impls::user::util::encryption_type_from_profile; use crate::af_cloud::impls::user::util::encryption_type_from_profile;
use crate::af_cloud::{AFCloudClient, AFServer}; use crate::af_cloud::{AFCloudClient, AFServer};
use crate::supabase::define::{USER_DEVICE_ID, USER_SIGN_IN_URL}; use crate::supabase::define::USER_SIGN_IN_URL;
pub(crate) struct AFCloudUserAuthServiceImpl<T> { pub(crate) struct AFCloudUserAuthServiceImpl<T> {
server: T, server: T,
@ -276,7 +276,6 @@ pub async fn user_sign_in_with_url(
user_workspaces, user_workspaces,
email: user_profile.email, email: user_profile.email,
token: Some(client.get_token()?), token: Some(client.get_token()?),
device_id: params.device_id,
encryption_type, encryption_type,
is_new_user, is_new_user,
updated_at: user_profile.updated_at, updated_at: user_profile.updated_at,
@ -307,9 +306,7 @@ fn oauth_params_from_box_any(any: BoxAny) -> Result<AFCloudOAuthParams, Error> {
.get(USER_SIGN_IN_URL) .get(USER_SIGN_IN_URL)
.ok_or_else(|| FlowyError::new(ErrorCode::MissingAuthField, "Missing token field"))? .ok_or_else(|| FlowyError::new(ErrorCode::MissingAuthField, "Missing token field"))?
.as_str(); .as_str();
let device_id = map.get(USER_DEVICE_ID).cloned().unwrap_or_default();
Ok(AFCloudOAuthParams { Ok(AFCloudOAuthParams {
sign_in_url: sign_in_url.to_string(), sign_in_url: sign_in_url.to_string(),
device_id,
}) })
} }

View File

@ -30,24 +30,20 @@ use crate::af_cloud::impls::{
}; };
use crate::AppFlowyServer; use crate::AppFlowyServer;
pub(crate) type AFCloudClient = client_api::Client; pub(crate) type AFCloudClient = Client;
pub struct AFCloudServer { pub struct AppFlowyCloudServer {
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) config: AFCloudConfiguration, pub(crate) config: AFCloudConfiguration,
pub(crate) client: Arc<AFCloudClient>, pub(crate) client: Arc<AFCloudClient>,
enable_sync: Arc<AtomicBool>, enable_sync: Arc<AtomicBool>,
#[allow(dead_code)] #[allow(dead_code)]
device_id: Arc<parking_lot::RwLock<String>>, device_id: String,
ws_client: Arc<WSClient>, ws_client: Arc<WSClient>,
} }
impl AFCloudServer { impl AppFlowyCloudServer {
pub fn new( pub fn new(config: AFCloudConfiguration, enable_sync: bool, device_id: String) -> Self {
config: AFCloudConfiguration,
enable_sync: bool,
device_id: Arc<parking_lot::RwLock<String>>,
) -> Self {
let api_client = AFCloudClient::new(&config.base_url, &config.ws_base_url, &config.gotrue_url); let api_client = AFCloudClient::new(&config.base_url, &config.ws_base_url, &config.gotrue_url);
let token_state_rx = api_client.subscribe_token_state(); let token_state_rx = api_client.subscribe_token_state();
let enable_sync = Arc::new(AtomicBool::new(enable_sync)); let enable_sync = Arc::new(AtomicBool::new(enable_sync));
@ -81,7 +77,7 @@ impl AFCloudServer {
} }
} }
impl AppFlowyServer for AFCloudServer { impl AppFlowyServer for AppFlowyCloudServer {
fn set_token(&self, token: &str) -> Result<(), Error> { fn set_token(&self, token: &str) -> Result<(), Error> {
self self
.client .client
@ -167,7 +163,7 @@ impl AppFlowyServer for AFCloudServer {
WSConnectStateReceiver, WSConnectStateReceiver,
bool, bool,
)>, )>,
anyhow::Error, 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();
@ -198,13 +194,13 @@ impl AppFlowyServer for AFCloudServer {
/// This function listens to the `token_state_rx` channel for token state updates. Depending on the /// This function listens to the `token_state_rx` channel for token state updates. Depending on the
/// received state, it either refreshes the WebSocket connection or disconnects from it. /// received state, it either refreshes the WebSocket connection or disconnects from it.
fn spawn_ws_conn( fn spawn_ws_conn(
device_id: &Arc<parking_lot::RwLock<String>>, device_id: &String,
mut token_state_rx: TokenStateReceiver, mut token_state_rx: TokenStateReceiver,
ws_client: &Arc<WSClient>, ws_client: &Arc<WSClient>,
api_client: &Arc<Client>, api_client: &Arc<Client>,
enable_sync: &Arc<AtomicBool>, enable_sync: &Arc<AtomicBool>,
) { ) {
let weak_device_id = Arc::downgrade(device_id); let cloned_device_id = device_id.to_owned();
let weak_ws_client = Arc::downgrade(ws_client); let weak_ws_client = Arc::downgrade(ws_client);
let weak_api_client = Arc::downgrade(api_client); let weak_api_client = Arc::downgrade(api_client);
let enable_sync = enable_sync.clone(); let enable_sync = enable_sync.clone();
@ -217,15 +213,12 @@ fn spawn_ws_conn(
match state { match state {
ConnectState::PingTimeout | ConnectState::Closed => { ConnectState::PingTimeout | ConnectState::Closed => {
// 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) = weak_api_client.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(); match api_client.ws_url(&cloned_device_id) {
match api_client.ws_url(&device_id) {
Ok(ws_addr) => { Ok(ws_addr) => {
event!(tracing::Level::INFO, "🟢reconnecting websocket"); event!(tracing::Level::INFO, "🟢reconnecting websocket");
let _ = ws_client.connect(ws_addr, &device_id).await; let _ = ws_client.connect(ws_addr, &cloned_device_id).await;
}, },
Err(err) => error!("Failed to get ws url: {}", err), Err(err) => error!("Failed to get ws url: {}", err),
} }
@ -245,19 +238,16 @@ fn spawn_ws_conn(
} }
}); });
let weak_device_id = Arc::downgrade(device_id); let device_id = device_id.to_owned();
let weak_ws_client = Arc::downgrade(ws_client); let weak_ws_client = Arc::downgrade(ws_client);
let weak_api_client = Arc::downgrade(api_client); let weak_api_client = Arc::downgrade(api_client);
af_spawn(async move { af_spawn(async move {
while let Ok(token_state) = token_state_rx.recv().await { while let Ok(token_state) = token_state_rx.recv().await {
match token_state { match token_state {
TokenState::Refresh => { TokenState::Refresh => {
if let (Some(api_client), Some(ws_client), Some(device_id)) = ( if let (Some(api_client), Some(ws_client)) =
weak_api_client.upgrade(), (weak_api_client.upgrade(), weak_ws_client.upgrade())
weak_ws_client.upgrade(), {
weak_device_id.upgrade(),
) {
let device_id = device_id.read().clone();
match api_client.ws_url(&device_id) { match api_client.ws_url(&device_id) {
Ok(ws_addr) => { Ok(ws_addr) => {
info!("🟢token state: {:?}, reconnecting websocket", token_state); info!("🟢token state: {:?}, reconnecting websocket", token_state);

View File

@ -45,7 +45,6 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
is_new_user: true, is_new_user: true,
email: Some(params.email), email: Some(params.email),
token: None, token: None,
device_id: params.device_id,
encryption_type: EncryptionType::NoEncryption, encryption_type: EncryptionType::NoEncryption,
updated_at: timestamp(), updated_at: timestamp(),
metadata: None, metadata: None,
@ -70,7 +69,6 @@ impl UserCloudService for LocalServerUserAuthServiceImpl {
is_new_user: false, is_new_user: false,
email: Some(params.email), email: Some(params.email),
token: None, token: None,
device_id: params.device_id,
encryption_type: EncryptionType::NoEncryption, encryption_type: EncryptionType::NoEncryption,
updated_at: timestamp(), updated_at: timestamp(),
metadata: None, metadata: None,

View File

@ -97,11 +97,7 @@ where
} }
// Query the user profile and workspaces // Query the user profile and workspaces
tracing::debug!( tracing::debug!("user uuid: {}", params.uuid,);
"user uuid: {}, device_id: {}",
params.uuid,
params.device_id
);
let user_profile = let user_profile =
get_user_profile(postgrest.clone(), GetUserProfileParams::Uuid(params.uuid)) get_user_profile(postgrest.clone(), GetUserProfileParams::Uuid(params.uuid))
.await? .await?
@ -126,7 +122,6 @@ where
is_new_user, is_new_user,
email: Some(user_profile.email), email: Some(user_profile.email),
token: None, token: None,
device_id: params.device_id,
encryption_type: EncryptionType::from_sign(&user_profile.encryption_sign), encryption_type: EncryptionType::from_sign(&user_profile.encryption_sign),
updated_at: user_profile.updated_at.timestamp(), updated_at: user_profile.updated_at.timestamp(),
metadata: None, metadata: None,
@ -157,7 +152,6 @@ where
is_new_user: false, is_new_user: false,
email: None, email: None,
token: None, token: None,
device_id: params.device_id,
encryption_type: EncryptionType::from_sign(&response.encryption_sign), encryption_type: EncryptionType::from_sign(&response.encryption_sign),
updated_at: response.updated_at.timestamp(), updated_at: response.updated_at.timestamp(),
metadata: None, metadata: None,
@ -529,11 +523,16 @@ impl RealtimeEventHandler for RealtimeUserHandler {
fn handler_event(&self, event: &RealtimeEvent) { fn handler_event(&self, event: &RealtimeEvent) {
if let Ok(user_event) = serde_json::from_value::<RealtimeUserEvent>(event.new.clone()) { if let Ok(user_event) = serde_json::from_value::<RealtimeUserEvent>(event.new.clone()) {
let _ = self.0.send(UserUpdate { let sender = self.0.clone();
uid: user_event.uid, tokio::spawn(async move {
name: Some(user_event.name), let _ = sender
email: Some(user_event.email), .send(UserUpdate {
encryption_sign: user_event.encryption_sign, uid: user_event.uid,
name: Some(user_event.name),
email: Some(user_event.email),
encryption_sign: user_event.encryption_sign,
})
.await;
}); });
} }
} }
@ -541,14 +540,14 @@ impl RealtimeEventHandler for RealtimeUserHandler {
pub struct RealtimeCollabUpdateHandler { pub struct RealtimeCollabUpdateHandler {
sender_by_oid: Weak<CollabUpdateSenderByOid>, sender_by_oid: Weak<CollabUpdateSenderByOid>,
device_id: Arc<RwLock<String>>, device_id: String,
encryption: Weak<dyn AppFlowyEncryption>, encryption: Weak<dyn AppFlowyEncryption>,
} }
impl RealtimeCollabUpdateHandler { impl RealtimeCollabUpdateHandler {
pub fn new( pub fn new(
sender_by_oid: Weak<CollabUpdateSenderByOid>, sender_by_oid: Weak<CollabUpdateSenderByOid>,
device_id: Arc<RwLock<String>>, device_id: String,
encryption: Weak<dyn AppFlowyEncryption>, encryption: Weak<dyn AppFlowyEncryption>,
) -> Self { ) -> Self {
Self { Self {
@ -571,10 +570,10 @@ impl RealtimeEventHandler for RealtimeCollabUpdateHandler {
if let Some(sender) = sender_by_oid.read().get(collab_update.oid.as_str()) { if let Some(sender) = sender_by_oid.read().get(collab_update.oid.as_str()) {
tracing::trace!( tracing::trace!(
"current device: {}, event device: {}", "current device: {}, event device: {}",
self.device_id.read(), self.device_id,
collab_update.did.as_str() collab_update.did.as_str()
); );
if *self.device_id.read() != collab_update.did.as_str() { if self.device_id != collab_update.did {
let encryption_secret = self let encryption_secret = self
.encryption .encryption
.upgrade() .upgrade()
@ -623,10 +622,5 @@ fn oauth_params_from_box_any(any: BoxAny) -> Result<SupabaseOAuthParams, Error>
let map: HashMap<String, String> = any.unbox_or_error()?; let map: HashMap<String, String> = any.unbox_or_error()?;
let uuid = uuid_from_map(&map)?; let uuid = uuid_from_map(&map)?;
let email = map.get("email").cloned().unwrap_or_default(); let email = map.get("email").cloned().unwrap_or_default();
let device_id = map.get("device_id").cloned().unwrap_or_default(); Ok(SupabaseOAuthParams { uuid, email })
Ok(SupabaseOAuthParams {
uuid,
email,
device_id,
})
} }

View File

@ -61,8 +61,7 @@ pub type CollabUpdateSenderByOid = RwLock<HashMap<String, RemoteUpdateSender>>;
pub struct SupabaseServer { pub struct SupabaseServer {
#[allow(dead_code)] #[allow(dead_code)]
config: SupabaseConfiguration, config: SupabaseConfiguration,
/// did represents as the device id is used to identify the device that is currently using the app. device_id: String,
device_id: Arc<RwLock<String>>,
uid: Arc<RwLock<Option<i64>>>, uid: Arc<RwLock<Option<i64>>>,
collab_update_sender: Arc<CollabUpdateSenderByOid>, collab_update_sender: Arc<CollabUpdateSenderByOid>,
restful_postgres: Arc<RwLock<Option<Arc<RESTfulPostgresServer>>>>, restful_postgres: Arc<RwLock<Option<Arc<RESTfulPostgresServer>>>>,
@ -75,7 +74,7 @@ impl SupabaseServer {
uid: Arc<RwLock<Option<i64>>>, uid: Arc<RwLock<Option<i64>>>,
config: SupabaseConfiguration, config: SupabaseConfiguration,
enable_sync: bool, enable_sync: bool,
device_id: Arc<RwLock<String>>, device_id: String,
encryption: Weak<dyn AppFlowyEncryption>, encryption: Weak<dyn AppFlowyEncryption>,
) -> Self { ) -> Self {
let collab_update_sender = Default::default(); let collab_update_sender = Default::default();
@ -146,7 +145,7 @@ impl AppFlowyServer for SupabaseServer {
)); ));
// handle the realtime user event. // handle the realtime user event.
let user_handler = Box::new(RealtimeUserHandler(user_update_tx.clone())); let user_handler = Box::new(RealtimeUserHandler(user_update_tx));
let handlers: Vec<Box<dyn RealtimeEventHandler>> = vec![collab_update_handler, user_handler]; let handlers: Vec<Box<dyn RealtimeEventHandler>> = vec![collab_update_handler, user_handler];
Arc::new(SupabaseUserServiceImpl::new( Arc::new(SupabaseUserServiceImpl::new(

View File

@ -1,10 +1,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use parking_lot::RwLock;
use uuid::Uuid; use uuid::Uuid;
use flowy_server::af_cloud::AFCloudServer; use flowy_server::af_cloud::AppFlowyCloudServer;
use flowy_server::supabase::define::{USER_DEVICE_ID, USER_SIGN_IN_URL}; use flowy_server::supabase::define::{USER_DEVICE_ID, USER_SIGN_IN_URL};
use flowy_server_config::af_cloud_config::AFCloudConfiguration; use flowy_server_config::af_cloud_config::AFCloudConfiguration;
@ -24,10 +23,9 @@ pub fn get_af_cloud_config() -> Option<AFCloudConfiguration> {
AFCloudConfiguration::from_env().ok() AFCloudConfiguration::from_env().ok()
} }
pub fn af_cloud_server(config: AFCloudConfiguration) -> Arc<AFCloudServer> { pub fn af_cloud_server(config: AFCloudConfiguration) -> Arc<AppFlowyCloudServer> {
let fake_device_id = uuid::Uuid::new_v4().to_string(); let fake_device_id = uuid::Uuid::new_v4().to_string();
let device_id = Arc::new(RwLock::new(fake_device_id)); Arc::new(AppFlowyCloudServer::new(config, true, fake_device_id))
Arc::new(AFCloudServer::new(config, true, device_id))
} }
pub async fn generate_sign_in_url(user_email: &str, config: &AFCloudConfiguration) -> String { pub async fn generate_sign_in_url(user_email: &str, config: &AFCloudConfiguration) -> String {
@ -42,7 +40,7 @@ pub async fn generate_sign_in_url(user_email: &str, config: &AFCloudConfiguratio
.unwrap(); .unwrap();
let action_link = admin_client let action_link = admin_client
.generate_sign_in_action_link(&user_email) .generate_sign_in_action_link(user_email)
.await .await
.unwrap(); .unwrap();
client.extract_sign_in_url(&action_link).await.unwrap() client.extract_sign_in_url(&action_link).await.unwrap()

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE rocksdb_backup;

View File

@ -0,0 +1,6 @@
-- Your SQL goes here
CREATE TABLE rocksdb_backup (
object_id TEXT NOT NULL PRIMARY KEY,
timestamp BIGINT NOT NULL DEFAULT 0,
data BLOB NOT NULL DEFAULT (x'')
);

View File

@ -12,6 +12,14 @@ diesel::table! {
} }
} }
diesel::table! {
rocksdb_backup (object_id) {
object_id -> Text,
timestamp -> BigInt,
data -> Binary,
}
}
diesel::table! { diesel::table! {
user_data_migration_records (id) { user_data_migration_records (id) {
id -> Integer, id -> Integer,
@ -48,6 +56,7 @@ diesel::table! {
diesel::allow_tables_to_appear_in_same_query!( diesel::allow_tables_to_appear_in_same_query!(
collab_snapshot, collab_snapshot,
rocksdb_backup,
user_data_migration_records, user_data_migration_records,
user_table, user_table,
user_workspace_table, user_workspace_table,

View File

@ -16,7 +16,6 @@ pub trait UserAuthResponse {
fn user_name(&self) -> &str; fn user_name(&self) -> &str;
fn latest_workspace(&self) -> &UserWorkspace; fn latest_workspace(&self) -> &UserWorkspace;
fn user_workspaces(&self) -> &[UserWorkspace]; fn user_workspaces(&self) -> &[UserWorkspace];
fn device_id(&self) -> &str;
fn user_token(&self) -> Option<String>; fn user_token(&self) -> Option<String>;
fn user_email(&self) -> Option<String>; fn user_email(&self) -> Option<String>;
fn encryption_type(&self) -> EncryptionType; fn encryption_type(&self) -> EncryptionType;
@ -30,7 +29,6 @@ pub struct SignInParams {
pub password: String, pub password: String,
pub name: String, pub name: String,
pub auth_type: Authenticator, pub auth_type: Authenticator,
pub device_id: String,
} }
#[derive(Serialize, Deserialize, Default, Debug)] #[derive(Serialize, Deserialize, Default, Debug)]
@ -51,7 +49,6 @@ pub struct AuthResponse {
pub is_new_user: bool, pub is_new_user: bool,
pub email: Option<String>, pub email: Option<String>,
pub token: Option<String>, pub token: Option<String>,
pub device_id: String,
pub encryption_type: EncryptionType, pub encryption_type: EncryptionType,
pub updated_at: i64, pub updated_at: i64,
pub metadata: Option<serde_json::Value>, pub metadata: Option<serde_json::Value>,
@ -74,10 +71,6 @@ impl UserAuthResponse for AuthResponse {
&self.user_workspaces &self.user_workspaces
} }
fn device_id(&self) -> &str {
&self.device_id
}
fn user_token(&self) -> Option<String> { fn user_token(&self) -> Option<String> {
self.token.clone() self.token.clone()
} }
@ -364,12 +357,10 @@ impl From<i32> for Authenticator {
pub struct SupabaseOAuthParams { pub struct SupabaseOAuthParams {
pub uuid: Uuid, pub uuid: Uuid,
pub email: String, pub email: String,
pub device_id: String,
} }
pub struct AFCloudOAuthParams { pub struct AFCloudOAuthParams {
pub sign_in_url: String, pub sign_in_url: String,
pub device_id: String,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View File

@ -37,7 +37,6 @@ impl TryInto<SignInParams> for SignInPayloadPB {
password: password.0, password: password.0,
name: self.name, name: self.name,
auth_type: self.auth_type.into(), auth_type: self.auth_type.into(),
device_id: self.device_id,
}) })
} }
} }

View File

@ -482,7 +482,7 @@ pub async fn open_historical_users_handler(
let manager = upgrade_manager(manager)?; let manager = upgrade_manager(manager)?;
let auth_type = Authenticator::from(user.auth_type); let auth_type = Authenticator::from(user.auth_type);
manager manager
.open_historical_user(user.user_id, user.device_id, auth_type) .open_historical_user(user.user_id, auth_type)
.await?; .await?;
Ok(()) Ok(())
} }
@ -541,8 +541,8 @@ pub async fn reset_workspace_handler(
"The workspace id is empty", "The workspace id is empty",
)); ));
} }
let session = manager.get_session()?; let _session = manager.get_session()?;
manager.reset_workspace(reset_pb, session.device_id).await?; manager.reset_workspace(reset_pb).await?;
Ok(()) Ok(())
} }

View File

@ -246,7 +246,6 @@ pub trait UserCloudServiceProvider: Send + Sync + 'static {
fn set_encrypt_secret(&self, secret: String); fn set_encrypt_secret(&self, secret: String);
fn set_authenticator(&self, authenticator: Authenticator); fn set_authenticator(&self, authenticator: Authenticator);
fn get_authenticator(&self) -> Authenticator; fn get_authenticator(&self) -> Authenticator;
fn set_device_id(&self, device_id: &str);
fn get_user_service(&self) -> Result<Arc<dyn UserCloudService>, FlowyError>; fn get_user_service(&self) -> Result<Arc<dyn UserCloudService>, FlowyError>;
fn service_url(&self) -> String; fn service_url(&self) -> String;
} }
@ -275,10 +274,6 @@ where
(**self).get_authenticator() (**self).get_authenticator()
} }
fn set_device_id(&self, device_id: &str) {
(**self).set_device_id(device_id)
}
fn get_user_service(&self) -> Result<Arc<dyn UserCloudService>, FlowyError> { fn get_user_service(&self) -> Result<Arc<dyn UserCloudService>, FlowyError> {
(**self).get_user_service() (**self).get_user_service()
} }

View File

@ -1,3 +1,4 @@
use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::string::ToString; use std::string::ToString;
use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::atomic::{AtomicI64, Ordering};
@ -41,28 +42,42 @@ use crate::services::user_workspace::save_user_workspaces;
use crate::{errors::FlowyError, notification::*}; use crate::{errors::FlowyError, notification::*};
pub const URL_SAFE_ENGINE: GeneralPurpose = GeneralPurpose::new(&URL_SAFE, PAD); pub const URL_SAFE_ENGINE: GeneralPurpose = GeneralPurpose::new(&URL_SAFE, PAD);
pub struct UserSessionConfig { pub struct UserConfig {
root_dir: String, /// Used to store the user data
storage_path: String,
/// application_path is the path of the application binary. By default, the
/// storage_path is the same as the application_path. However, when the user
/// choose a custom path for the user data, the storage_path will be different from
/// the application_path.
application_path: String,
pub device_id: String,
/// Used as the key of `Session` when saving session information to KV. /// Used as the key of `Session` when saving session information to KV.
session_cache_key: String, session_cache_key: String,
} }
impl UserSessionConfig { impl UserConfig {
/// The `root_dir` represents as the root of the user folders. It must be unique for each /// The `root_dir` represents as the root of the user folders. It must be unique for each
/// users. /// users.
pub fn new(name: &str, root_dir: &str) -> Self { pub fn new(name: &str, storage_path: &str, application_path: &str, device_id: &str) -> Self {
let session_cache_key = format!("{}_session_cache", name); let session_cache_key = format!("{}_session_cache", name);
Self { Self {
root_dir: root_dir.to_owned(), storage_path: storage_path.to_owned(),
application_path: application_path.to_owned(),
session_cache_key, session_cache_key,
device_id: device_id.to_owned(),
} }
} }
/// Returns bool whether the user choose a custom path for the user data.
pub fn is_custom_storage_path(&self) -> bool {
self.storage_path != self.application_path
}
} }
pub struct UserManager { pub struct UserManager {
database: Arc<UserDB>, database: Arc<UserDB>,
user_paths: UserPaths, user_paths: UserPaths,
session_config: UserSessionConfig, pub(crate) user_config: UserConfig,
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>,
pub(crate) user_awareness: Arc<Mutex<Option<MutexUserAwareness>>>, pub(crate) user_awareness: Arc<Mutex<Option<MutexUserAwareness>>>,
@ -76,14 +91,12 @@ pub struct UserManager {
impl UserManager { impl UserManager {
pub fn new( pub fn new(
session_config: UserSessionConfig, user_config: UserConfig,
cloud_services: Arc<dyn UserCloudServiceProvider>, cloud_services: Arc<dyn UserCloudServiceProvider>,
store_preferences: Arc<StorePreferences>, store_preferences: Arc<StorePreferences>,
collab_builder: Weak<AppFlowyCollabBuilder>, collab_builder: Weak<AppFlowyCollabBuilder>,
) -> Arc<Self> { ) -> Arc<Self> {
let user_paths = UserPaths { let user_paths = UserPaths::new(user_config.storage_path.clone());
root: session_config.root_dir.clone(),
};
let database = Arc::new(UserDB::new(user_paths.clone())); let database = Arc::new(UserDB::new(user_paths.clone()));
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));
@ -92,7 +105,7 @@ impl UserManager {
let user_manager = Arc::new(Self { let user_manager = Arc::new(Self {
database, database,
user_paths, user_paths,
session_config, user_config,
cloud_services, cloud_services,
store_preferences, store_preferences,
user_awareness: Arc::new(Default::default()), user_awareness: Arc::new(Default::default()),
@ -183,11 +196,14 @@ impl UserManager {
} }
}); });
} }
self.prepare_user(&session).await;
// Do the user data migration if needed // Do the user data migration if needed
event!(tracing::Level::INFO, "Prepare user data migration"); 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.user_config.device_id),
self.database.get_pool(session.user_id), self.database.get_pool(session.user_id),
) { ) {
(Ok(collab_db), Ok(sqlite_pool)) => { (Ok(collab_db), Ok(sqlite_pool)) => {
@ -209,7 +225,6 @@ impl UserManager {
}, },
_ => error!("Failed to get collab db or sqlite pool"), _ => error!("Failed to get collab db or sqlite pool"),
} }
self.set_collab_config(&session);
// Init the user awareness // Init the user awareness
self self
.initialize_user_awareness(&session, UserAwarenessDataSource::Local) .initialize_user_awareness(&session, UserAwarenessDataSource::Local)
@ -221,7 +236,7 @@ impl UserManager {
session.user_id, session.user_id,
&cloud_config, &cloud_config,
&session.user_workspace, &session.user_workspace,
&session.device_id, &self.user_config.device_id,
) )
.await .await
{ {
@ -242,7 +257,7 @@ impl UserManager {
pub fn get_collab_db(&self, uid: i64) -> Result<Weak<RocksCollabDB>, FlowyError> { pub fn get_collab_db(&self, uid: i64) -> Result<Weak<RocksCollabDB>, FlowyError> {
self self
.database .database
.get_collab_db(uid) .get_collab_db(uid, "")
.map(|collab_db| Arc::downgrade(&collab_db)) .map(|collab_db| Arc::downgrade(&collab_db))
} }
@ -267,13 +282,14 @@ impl UserManager {
.sign_in(params) .sign_in(params)
.await?; .await?;
let session = Session::from(&response); let session = Session::from(&response);
self.set_collab_config(&session); self.prepare_user(&session).await;
let latest_workspace = response.latest_workspace.clone(); let latest_workspace = response.latest_workspace.clone();
let user_profile = UserProfile::from((&response, &authenticator)); let user_profile = UserProfile::from((&response, &authenticator));
self self
.save_auth_data(&response, &authenticator, &session) .save_auth_data(&response, &authenticator, &session)
.await?; .await?;
let _ = self let _ = self
.initialize_user_awareness(&session, UserAwarenessDataSource::Remote) .initialize_user_awareness(&session, UserAwarenessDataSource::Remote)
.await; .await;
@ -282,7 +298,11 @@ impl UserManager {
.user_status_callback .user_status_callback
.read() .read()
.await .await
.did_sign_in(user_profile.uid, &latest_workspace, &session.device_id) .did_sign_in(
user_profile.uid,
&latest_workspace,
&self.user_config.device_id,
)
.await .await
{ {
error!("Failed to call did_sign_in callback: {:?}", e); error!("Failed to call did_sign_in callback: {:?}", e);
@ -373,7 +393,7 @@ impl UserManager {
authenticator: &Authenticator, authenticator: &Authenticator,
) -> FlowyResult<()> { ) -> FlowyResult<()> {
let new_session = Session::from(&response); let new_session = Session::from(&response);
self.set_collab_config(&new_session); self.prepare_user(&new_session).await;
let user_awareness_source = if response.is_new_user { let user_awareness_source = if response.is_new_user {
UserAwarenessDataSource::Local UserAwarenessDataSource::Local
@ -415,7 +435,7 @@ impl UserManager {
response.is_new_user, response.is_new_user,
user_profile, user_profile,
&new_session.user_workspace, &new_session.user_workspace,
&new_session.device_id, &self.user_config.device_id,
) )
.await?; .await?;
@ -468,6 +488,24 @@ impl UserManager {
Ok(()) Ok(())
} }
pub async fn prepare_user(&self, session: &Session) {
self.set_collab_config(session);
// Ensure to backup user data if a cloud drive is used for storage. While using a cloud drive
// for storing user data is not advised due to potential data corruption risks, in scenarios where
// users opt for cloud storage, the application should automatically create a backup of the user
// data. This backup should be in the form of a zip file and stored locally on the user's disk
// for safety and data integrity purposes
if self.user_config.is_custom_storage_path() {
self
.database
.backup_or_restore(session.user_id, &session.user_workspace.id);
} else {
self
.database
.restore_if_need(session.user_id, &session.user_workspace.id);
}
}
/// Fetches the user profile for the given user ID. /// Fetches the user profile for the given user ID.
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
@ -623,7 +661,7 @@ impl UserManager {
match self match self
.store_preferences .store_preferences
.get_object::<Session>(&self.session_config.session_cache_key) .get_object::<Session>(&self.user_config.session_cache_key)
{ {
None => Err(FlowyError::new( None => Err(FlowyError::new(
ErrorCode::RecordNotFound, ErrorCode::RecordNotFound,
@ -643,13 +681,13 @@ impl UserManager {
self.current_session.write().take(); self.current_session.write().take();
self self
.store_preferences .store_preferences
.remove(&self.session_config.session_cache_key) .remove(&self.user_config.session_cache_key)
}, },
Some(session) => { Some(session) => {
self.current_session.write().replace(session.clone()); self.current_session.write().replace(session.clone());
self self
.store_preferences .store_preferences
.set_object(&self.session_config.session_cache_key, session.clone()) .set_object(&self.user_config.session_cache_key, session.clone())
.map_err(internal_error)?; .map_err(internal_error)?;
}, },
} }
@ -695,7 +733,7 @@ impl UserManager {
event!(tracing::Level::DEBUG, "Save new history user: {:?}", uid); event!(tracing::Level::DEBUG, "Save new history user: {:?}", uid);
self.add_historical_user( self.add_historical_user(
uid, uid,
response.device_id(), &self.user_config.device_id,
response.user_name().to_string(), response.user_name().to_string(),
authenticator, authenticator,
self.user_dir(uid), self.user_dir(uid),
@ -712,9 +750,7 @@ impl UserManager {
fn set_collab_config(&self, session: &Session) { fn set_collab_config(&self, session: &Session) {
let collab_builder = self.collab_builder.upgrade().unwrap(); let collab_builder = self.collab_builder.upgrade().unwrap();
collab_builder.set_sync_device(session.device_id.clone());
collab_builder.initialize(session.user_workspace.id.clone()); collab_builder.initialize(session.user_workspace.id.clone());
self.cloud_services.set_device_id(&session.device_id);
} }
async fn handler_user_update(&self, user_update: UserUpdate) -> FlowyResult<()> { async fn handler_user_update(&self, user_update: UserUpdate) -> FlowyResult<()> {
@ -742,13 +778,17 @@ impl UserManager {
old_user: &MigrationUser, old_user: &MigrationUser,
new_user: &MigrationUser, new_user: &MigrationUser,
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let old_collab_db = self.database.get_collab_db(old_user.session.user_id)?; let old_collab_db = self
let new_collab_db = self.database.get_collab_db(new_user.session.user_id)?; .database
.get_collab_db(old_user.session.user_id, &self.user_config.device_id)?;
let new_collab_db = self
.database
.get_collab_db(new_user.session.user_id, &self.user_config.device_id)?;
migration_anon_user_on_sign_up(old_user, &old_collab_db, new_user, &new_collab_db)?; migration_anon_user_on_sign_up(old_user, &old_collab_db, new_user, &new_collab_db)?;
if let Err(err) = sync_user_data_to_cloud( if let Err(err) = sync_user_data_to_cloud(
self.cloud_services.get_user_service()?, self.cloud_services.get_user_service()?,
"", &self.user_config.device_id,
new_user, new_user,
&new_collab_db, &new_collab_db,
) )
@ -816,6 +856,9 @@ struct UserPaths {
} }
impl UserPaths { impl UserPaths {
fn new(root: String) -> Self {
Self { root }
}
fn user_dir(&self, uid: i64) -> String { fn user_dir(&self, uid: i64) -> String {
format!("{}/{}", self.root, uid) format!("{}/{}", self.root, uid)
} }
@ -831,4 +874,12 @@ impl UserDBPath for UserPaths {
path.push("collab_db"); path.push("collab_db");
path path
} }
fn collab_db_history(&self, uid: i64, create_if_not_exist: bool) -> std::io::Result<PathBuf> {
let path = PathBuf::from(self.user_dir(uid)).join("collab_db_history");
if !path.exists() && create_if_not_exist {
fs::create_dir_all(&path)?;
}
Ok(path)
}
} }

View File

@ -1,10 +1,12 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{collections::HashMap, sync::Arc, time::Duration}; use std::{collections::HashMap, fs, io, sync::Arc, time::Duration};
use chrono::Local;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use parking_lot::RwLock; use parking_lot::RwLock;
use tracing::{error, event, info, instrument};
use collab_integrate::RocksCollabDB; use collab_integrate::{PersistenceError, RocksCollabDB, YrsDocAction};
use flowy_error::{ErrorCode, FlowyError}; use flowy_error::{ErrorCode, FlowyError};
use flowy_sqlite::schema::user_workspace_table; use flowy_sqlite::schema::user_workspace_table;
use flowy_sqlite::ConnectionPool; use flowy_sqlite::ConnectionPool;
@ -14,6 +16,8 @@ use flowy_sqlite::{
DBConnection, Database, ExpressionMethods, DBConnection, Database, ExpressionMethods,
}; };
use flowy_user_deps::entities::{UserProfile, UserWorkspace}; use flowy_user_deps::entities::{UserProfile, UserWorkspace};
use lib_dispatch::prelude::af_spawn;
use lib_infra::file_util::{unzip_and_replace, zip_folder};
use crate::services::user_sql::UserTable; use crate::services::user_sql::UserTable;
use crate::services::user_workspace_sql::UserWorkspaceTable; use crate::services::user_workspace_sql::UserWorkspaceTable;
@ -21,6 +25,7 @@ use crate::services::user_workspace_sql::UserWorkspaceTable;
pub trait UserDBPath: Send + Sync + 'static { pub trait UserDBPath: Send + Sync + 'static {
fn user_db_path(&self, uid: i64) -> PathBuf; fn user_db_path(&self, uid: i64) -> PathBuf;
fn collab_db_path(&self, uid: i64) -> PathBuf; fn collab_db_path(&self, uid: i64) -> PathBuf;
fn collab_db_history(&self, uid: i64, create_if_not_exist: bool) -> std::io::Result<PathBuf>;
} }
pub struct UserDB { pub struct UserDB {
@ -34,6 +39,58 @@ impl UserDB {
} }
} }
/// Performs a conditional backup or restoration of the collaboration database (CollabDB) for a specific user.
///
/// This function takes a user ID and conducts the following operations:
///
/// **Backup or Restoration**:
/// - If the CollabDB exists, it tries to open the database:
/// - **Successful Open**: If the database opens successfully, it attempts to back it up.
/// - **Failed Open**: If the database cannot be opened, it indicates a potential issue, and the function
/// attempts to restore the database from the latest backup.
/// - If the CollabDB does not exist, it immediately attempts to restore from the latest backup.
///
pub fn backup_or_restore(&self, uid: i64, workspace_id: &str) {
let collab_db_path = self.paths.collab_db_path(uid);
if let Ok(history_folder) = self.paths.collab_db_history(uid, true) {
if collab_db_path.exists() {
let is_ok = validate_collab_db(&collab_db_path, uid, workspace_id);
let zip_backup = CollabDBZipBackup::new(collab_db_path, history_folder);
if is_ok {
// If the database opens successfully, it attempts to back it up in the background.
af_spawn(async move {
let _ = tokio::task::spawn_blocking(move || {
if let Err(err) = zip_backup.backup() {
error!("backup collab db failed, {:?}", err);
}
})
.await;
});
} else if let Err(err) = zip_backup.restore_latest_backup() {
error!("restore collab db failed, {:?}", err);
}
} else {
let zip_backup = CollabDBZipBackup::new(collab_db_path, history_folder);
if let Err(err) = zip_backup.restore_latest_backup() {
error!("restore collab db failed, {:?}", err);
}
}
}
}
pub fn restore_if_need(&self, uid: i64, workspace_id: &str) {
if let Ok(history_folder) = self.paths.collab_db_history(uid, false) {
let collab_db_path = self.paths.collab_db_path(uid);
let is_ok = validate_collab_db(&collab_db_path, uid, workspace_id);
let zip_backup = CollabDBZipBackup::new(collab_db_path, history_folder);
if !is_ok {
if let Err(err) = zip_backup.restore_latest_backup() {
error!("restore collab db failed, {:?}", err);
}
}
}
}
/// Close the database connection for the user. /// Close the database connection for the user.
pub(crate) fn close(&self, user_id: i64) -> Result<(), FlowyError> { pub(crate) fn close(&self, user_id: i64) -> Result<(), FlowyError> {
if let Some(mut sqlite_dbs) = DB_MAP.try_write_for(Duration::from_millis(300)) { if let Some(mut sqlite_dbs) = DB_MAP.try_write_for(Duration::from_millis(300)) {
@ -44,6 +101,7 @@ impl UserDB {
if let Some(mut collab_dbs) = COLLAB_DB_MAP.try_write_for(Duration::from_millis(300)) { if let Some(mut collab_dbs) = COLLAB_DB_MAP.try_write_for(Duration::from_millis(300)) {
if let Some(db) = collab_dbs.remove(&user_id) { if let Some(db) = collab_dbs.remove(&user_id) {
tracing::trace!("close collab db for user {}", user_id); tracing::trace!("close collab db for user {}", user_id);
let _ = db.flush();
drop(db); drop(db);
} }
} }
@ -60,8 +118,17 @@ impl UserDB {
Ok(pool) Ok(pool)
} }
pub(crate) fn get_collab_db(&self, user_id: i64) -> Result<Arc<RocksCollabDB>, FlowyError> { pub(crate) fn get_collab_db(
let collab_db = open_collab_db(self.paths.collab_db_path(user_id), user_id)?; &self,
user_id: i64,
_device_id: &str,
) -> Result<Arc<RocksCollabDB>, FlowyError> {
let collab_db = open_collab_db(
self.paths.user_db_path(user_id),
self.paths.collab_db_path(user_id),
self.paths.collab_db_history(user_id, false).ok(),
user_id,
)?;
Ok(collab_db) Ok(collab_db)
} }
} }
@ -107,18 +174,32 @@ pub fn get_user_workspace(
/// Open a collab db for the user. If the db is already opened, return the opened db. /// Open a collab db for the user. If the db is already opened, return the opened db.
/// ///
fn open_collab_db(db_path: impl AsRef<Path>, uid: i64) -> Result<Arc<RocksCollabDB>, FlowyError> { fn open_collab_db(
_collab_backup_db_path: impl AsRef<Path>,
collab_db_path: impl AsRef<Path>,
_collab_db_history: Option<PathBuf>,
uid: i64,
) -> Result<Arc<RocksCollabDB>, FlowyError> {
if let Some(collab_db) = COLLAB_DB_MAP.read().get(&uid) { if let Some(collab_db) = COLLAB_DB_MAP.read().get(&uid) {
return Ok(collab_db.clone()); return Ok(collab_db.clone());
} }
let mut write_guard = COLLAB_DB_MAP.write(); let mut write_guard = COLLAB_DB_MAP.write();
tracing::trace!("open collab db {} at path: {:?}", uid, db_path.as_ref()); let db = match RocksCollabDB::open(&collab_db_path) {
let db = match RocksCollabDB::open(db_path) {
Ok(db) => Ok(db), Ok(db) => Ok(db),
Err(err) => { Err(err) => {
tracing::error!("open collab db failed, {:?}", err); tracing::error!("open collab db error, {:?}", err);
Err(FlowyError::new(ErrorCode::MultipleDBInstance, err)) match err {
PersistenceError::RocksdbCorruption(_) => {
// try restore from the backup db
Err(FlowyError::new(ErrorCode::RocksdbCorruption, err))
},
PersistenceError::RocksdbIOError(_) => {
//
Err(FlowyError::new(ErrorCode::RocksdbIOError, err))
},
_ => Err(FlowyError::new(ErrorCode::RocksdbInternal, err)),
}
}, },
}?; }?;
@ -132,3 +213,140 @@ lazy_static! {
static ref DB_MAP: RwLock<HashMap<i64, Database>> = RwLock::new(HashMap::new()); static ref DB_MAP: RwLock<HashMap<i64, Database>> = RwLock::new(HashMap::new());
static ref COLLAB_DB_MAP: RwLock<HashMap<i64, Arc<RocksCollabDB>>> = RwLock::new(HashMap::new()); static ref COLLAB_DB_MAP: RwLock<HashMap<i64, Arc<RocksCollabDB>>> = RwLock::new(HashMap::new());
} }
pub struct CollabDBZipBackup {
collab_db_path: PathBuf,
history_folder: PathBuf,
}
impl CollabDBZipBackup {
fn new(collab_db_path: PathBuf, history_folder: PathBuf) -> Self {
Self {
collab_db_path,
history_folder,
}
}
#[instrument(name = "backup_collab_db", skip_all, err)]
pub fn backup(&self) -> io::Result<()> {
let today_zip_file = self
.history_folder
.join(format!("collab_db_{}.zip", today_zip_timestamp()));
// Remove today's existing zip file if it exists
if !today_zip_file.exists() {
// Create a backup for today
event!(
tracing::Level::INFO,
"Backup collab db to {:?}",
today_zip_file
);
zip_folder(&self.collab_db_path, &today_zip_file)?;
}
// Clean up old backups
self.clean_old_backups()?;
Ok(())
}
#[instrument(skip_all, err)]
pub fn restore_latest_backup(&self) -> io::Result<()> {
let mut latest_zip: Option<(String, PathBuf)> = None;
for entry in fs::read_dir(&self.history_folder)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("zip") {
if let Some(file_name) = path.file_stem().and_then(|s| s.to_str()) {
if let Some(timestamp_str) = file_name.strip_prefix("collab_db_") {
match latest_zip {
Some((latest_timestamp, _)) if timestamp_str > latest_timestamp.as_str() => {
latest_zip = Some((timestamp_str.to_string(), path));
},
None => latest_zip = Some((timestamp_str.to_string(), path)),
_ => {},
}
}
}
}
}
let restore_path = latest_zip
.map(|(_, path)| path)
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "No backup folder found"))?;
unzip_and_replace(&restore_path, &self.collab_db_path)?;
info!("Restore collab db from {:?}", restore_path);
Ok(())
}
fn clean_old_backups(&self) -> io::Result<()> {
let mut backups = Vec::new();
let threshold_date = Local::now() - chrono::Duration::days(10);
// Collect all backup files
for entry in fs::read_dir(&self.history_folder)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("zip") {
let filename = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or_default();
let date_str = filename.split('_').last().unwrap_or("");
backups.push((date_str.to_string(), path));
}
}
// Sort backups by date (oldest first)
backups.sort_by(|a, b| a.0.cmp(&b.0));
// Remove backups older than 10 days
let threshold_str = threshold_date.format(zip_time_format()).to_string();
// If there are more than 10 backups, remove the oldest ones
while backups.len() > 10 {
if let Some((date_str, path)) = backups.first() {
if date_str < &threshold_str {
info!("Remove old backup file: {:?}", path);
fs::remove_file(path)?;
backups.remove(0);
} else {
break;
}
}
}
Ok(())
}
}
fn today_zip_timestamp() -> String {
Local::now().format(zip_time_format()).to_string()
}
fn zip_time_format() -> &'static str {
"%Y%m%d"
}
pub(crate) fn validate_collab_db(
collab_db_path: impl AsRef<Path>,
uid: i64,
workspace_id: &str,
) -> bool {
// Attempt to open the collaboration database using the workspace_id. The workspace_id must already
// exist in the collab database. If it does not, it may be indicative of corruption in the collab database
// due to other factors.
let result = RocksCollabDB::open(&collab_db_path).map(|db| {
let read_txn = db.read_txn();
read_txn.is_exist(uid, workspace_id)
});
match result {
Ok(is_ok) => is_ok,
// return false if the error is not related to corruption
Err(err) => !matches!(
err,
PersistenceError::RocksdbCorruption(_) | PersistenceError::RocksdbRepairFail(_)
),
}
}

View File

@ -16,7 +16,6 @@ use crate::migrations::MigrationUser;
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct Session { pub struct Session {
pub user_id: i64, pub user_id: i64,
pub device_id: String,
pub user_workspace: UserWorkspace, pub user_workspace: UserWorkspace,
} }
@ -36,7 +35,6 @@ impl<'de> Visitor<'de> for SessionVisitor {
// For historical reasons, the session used to contain a workspace_id field. // For historical reasons, the session used to contain a workspace_id field.
// This field is no longer used, and is replaced by user_workspace. // This field is no longer used, and is replaced by user_workspace.
let mut workspace_id = None; let mut workspace_id = None;
let mut device_id = "phantom".to_string();
let mut user_workspace = None; let mut user_workspace = None;
while let Some(key) = map.next_key::<String>()? { while let Some(key) = map.next_key::<String>()? {
@ -47,9 +45,6 @@ impl<'de> Visitor<'de> for SessionVisitor {
"workspace_id" => { "workspace_id" => {
workspace_id = Some(map.next_value()?); workspace_id = Some(map.next_value()?);
}, },
"device_id" => {
device_id = map.next_value()?;
},
"user_workspace" => { "user_workspace" => {
user_workspace = Some(map.next_value()?); user_workspace = Some(map.next_value()?);
}, },
@ -73,7 +68,6 @@ impl<'de> Visitor<'de> for SessionVisitor {
let session = Session { let session = Session {
user_id, user_id,
device_id,
user_workspace: user_workspace.ok_or(serde::de::Error::missing_field("user_workspace"))?, user_workspace: user_workspace.ok_or(serde::de::Error::missing_field("user_workspace"))?,
}; };
@ -97,7 +91,6 @@ where
fn from(value: &T) -> Self { fn from(value: &T) -> Self {
Self { Self {
user_id: value.user_id(), user_id: value.user_id(),
device_id: value.device_id().to_string(),
user_workspace: value.latest_workspace().clone(), user_workspace: value.latest_workspace().clone(),
} }
} }

View File

@ -82,12 +82,7 @@ impl UserManager {
/// This function facilitates the re-opening of a user's session from historical tracking. /// This function facilitates the re-opening of a user's session from historical tracking.
/// It retrieves the user's workspace and establishes a new session for the user. /// It retrieves the user's workspace and establishes a new session for the user.
/// ///
pub async fn open_historical_user( pub async fn open_historical_user(&self, uid: i64, auth_type: Authenticator) -> FlowyResult<()> {
&self,
uid: i64,
device_id: String,
auth_type: Authenticator,
) -> FlowyResult<()> {
debug_assert!(auth_type.is_local()); debug_assert!(auth_type.is_local());
self.update_authenticator(&auth_type).await; self.update_authenticator(&auth_type).await;
let conn = self.db_connection(uid)?; let conn = self.db_connection(uid)?;
@ -97,7 +92,6 @@ impl UserManager {
let user_workspace = UserWorkspace::from(row); let user_workspace = UserWorkspace::from(row);
let session = Session { let session = Session {
user_id: uid, user_id: uid,
device_id,
user_workspace, user_workspace,
}; };
self.set_session(Some(session))?; self.set_session(Some(session))?;

View File

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

View File

@ -28,7 +28,7 @@ impl Builder {
Builder { Builder {
name: name.to_owned(), name: name.to_owned(),
env_filter: "Info".to_owned(), env_filter: "Info".to_owned(),
file_appender: tracing_appender::rolling::daily(directory, format!("{}", name)), file_appender: tracing_appender::rolling::daily(directory, name),
} }
} }

382
shared-lib/Cargo.lock generated
View File

@ -17,6 +17,17 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aes"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.18" version = "0.7.18"
@ -88,6 +99,12 @@ dependencies = [
"rustc-demangle", "rustc-demangle",
] ]
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]] [[package]]
name = "basic-toml" name = "basic-toml"
version = "0.1.2" version = "0.1.2"
@ -118,6 +135,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@ -143,17 +166,47 @@ version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.5.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
name = "bzip2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
dependencies = [
"bzip2-sys",
"libc",
]
[[package]]
name = "bzip2-sys"
version = "0.1.11+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.79" version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
dependencies = [
"jobserver",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -170,7 +223,7 @@ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
"num-traits", "num-traits",
"windows-targets 0.48.0", "windows-targets",
] ]
[[package]] [[package]]
@ -195,6 +248,16 @@ dependencies = [
"phf_codegen", "phf_codegen",
] ]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]] [[package]]
name = "cmd_lib" name = "cmd_lib"
version = "1.3.0" version = "1.3.0"
@ -245,6 +308,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]] [[package]]
name = "core-foundation-sys" name = "core-foundation-sys"
version = "0.8.3" version = "0.8.3"
@ -260,6 +329,24 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -325,6 +412,15 @@ dependencies = [
"parking_lot", "parking_lot",
] ]
[[package]]
name = "deranged"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
dependencies = [
"powerfmt",
]
[[package]] [[package]]
name = "deunicode" name = "deunicode"
version = "0.4.3" version = "0.4.3"
@ -339,6 +435,7 @@ checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [ dependencies = [
"block-buffer", "block-buffer",
"crypto-common", "crypto-common",
"subtle",
] ]
[[package]] [[package]]
@ -380,7 +477,7 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ae66425802d6a903e268ae1a08b8c38ba143520f227a205edf4e9c7e3e26d5" checksum = "59ae66425802d6a903e268ae1a08b8c38ba143520f227a205edf4e9c7e3e26d5"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"libc", "libc",
"winapi", "winapi",
] ]
@ -397,11 +494,18 @@ dependencies = [
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "1.9.0" version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "flate2"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
dependencies = [ dependencies = [
"instant", "crc32fast",
"miniz_oxide",
] ]
[[package]] [[package]]
@ -529,7 +633,7 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"ignore", "ignore",
"walkdir", "walkdir",
] ]
@ -559,10 +663,13 @@ dependencies = [
] ]
[[package]] [[package]]
name = "hermit-abi" name = "hmac"
version = "0.3.1" version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]] [[package]]
name = "humansize" name = "humansize"
@ -632,23 +739,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497f036ac2fae75c34224648a77802e5dd4e9cfb56f4713ab6b12b7160a0523b" checksum = "497f036ac2fae75c34224648a77802e5dd4e9cfb56f4713ab6b12b7160a0523b"
[[package]] [[package]]
name = "instant" name = "inout"
version = "0.1.12" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [ dependencies = [
"cfg-if", "generic-array",
]
[[package]]
name = "io-lifetimes"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
dependencies = [
"hermit-abi 0.3.1",
"libc",
"windows-sys 0.48.0",
] ]
[[package]] [[package]]
@ -666,6 +762,15 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "jobserver"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.61" version = "0.3.61"
@ -693,7 +798,10 @@ dependencies = [
"md5", "md5",
"pin-project", "pin-project",
"rand 0.8.5", "rand 0.8.5",
"tempfile",
"tokio", "tokio",
"walkdir",
"zip",
] ]
[[package]] [[package]]
@ -735,9 +843,9 @@ dependencies = [
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.3.7" version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -804,7 +912,7 @@ version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [ dependencies = [
"hermit-abi 0.1.19", "hermit-abi",
"libc", "libc",
] ]
@ -865,6 +973,29 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "password-hash"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
dependencies = [
"base64ct",
"rand_core 0.6.4",
"subtle",
]
[[package]]
name = "pbkdf2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
dependencies = [
"digest",
"hmac",
"password-hash",
"sha2",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.2.0" version = "2.2.0"
@ -1024,6 +1155,18 @@ version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.15" version = "0.2.15"
@ -1252,16 +1395,16 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
] ]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.3.5" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
] ]
[[package]] [[package]]
@ -1289,16 +1432,15 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.37.3" version = "0.38.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b24138615de35e32031d041a09032ef3487a616d901ca4db224e7d557efae2" checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.4.1",
"errno", "errno",
"io-lifetimes",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.45.0", "windows-sys 0.48.0",
] ]
[[package]] [[package]]
@ -1359,6 +1501,17 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "sha1"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.10.6" version = "0.10.6"
@ -1434,6 +1587,12 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.109" version = "1.0.109"
@ -1458,15 +1617,15 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.5.0" version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"redox_syscall 0.3.5", "redox_syscall 0.4.1",
"rustix", "rustix",
"windows-sys 0.45.0", "windows-sys 0.48.0",
] ]
[[package]] [[package]]
@ -1540,6 +1699,24 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "time"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
dependencies = [
"deranged",
"powerfmt",
"serde",
"time-core",
]
[[package]]
name = "time-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.34.0" version = "1.34.0"
@ -1722,9 +1899,9 @@ checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.3.3" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
dependencies = [ dependencies = [
"same-file", "same-file",
"winapi-util", "winapi-util",
@ -1857,37 +2034,13 @@ dependencies = [
"windows_x86_64_msvc 0.36.1", "windows_x86_64_msvc 0.36.1",
] ]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.1",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [ dependencies = [
"windows-targets 0.48.0", "windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
dependencies = [
"windows_aarch64_gnullvm 0.42.1",
"windows_aarch64_msvc 0.42.1",
"windows_i686_gnu 0.42.1",
"windows_i686_msvc 0.42.1",
"windows_x86_64_gnu 0.42.1",
"windows_x86_64_gnullvm 0.42.1",
"windows_x86_64_msvc 0.42.1",
] ]
[[package]] [[package]]
@ -1896,21 +2049,15 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm 0.48.0", "windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.48.0", "windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0", "windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0", "windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0", "windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0", "windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.48.0", "windows_x86_64_msvc 0.48.0",
] ]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.48.0" version = "0.48.0"
@ -1923,12 +2070,6 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.48.0" version = "0.48.0"
@ -1941,12 +2082,6 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]]
name = "windows_i686_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.48.0" version = "0.48.0"
@ -1959,12 +2094,6 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]]
name = "windows_i686_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.48.0" version = "0.48.0"
@ -1977,24 +2106,12 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.48.0" version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.48.0" version = "0.48.0"
@ -2007,14 +2124,57 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.48.0" version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"aes",
"byteorder",
"bzip2",
"constant_time_eq",
"crc32fast",
"crossbeam-utils",
"flate2",
"hmac",
"pbkdf2",
"sha1",
"time",
"zstd",
]
[[package]]
name = "zstd"
version = "0.11.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "5.0.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
dependencies = [
"libc",
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.9+zstd.1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656"
dependencies = [
"cc",
"pkg-config",
]

View File

@ -15,3 +15,6 @@ rand = "0.8.5"
async-trait.workspace = true async-trait.workspace = true
md5 = "0.7.0" md5 = "0.7.0"
anyhow.workspace = true anyhow.workspace = true
walkdir = "2.4.0"
zip = "0.6.6"
tempfile = "3.8.1"

View File

@ -0,0 +1,150 @@
use std::cmp::Ordering;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use std::{fs, io};
use tempfile::tempdir;
use walkdir::WalkDir;
use zip::write::FileOptions;
use zip::ZipArchive;
use zip::ZipWriter;
pub fn copy_dir_recursive(src: &Path, dst: &Path) -> io::Result<()> {
for entry in WalkDir::new(src).into_iter().filter_map(|e| e.ok()) {
let path = entry.path();
let relative_path = path.strip_prefix(src).unwrap();
let target_path = dst.join(relative_path);
if path.is_dir() {
fs::create_dir_all(&target_path)?;
} else {
fs::copy(path, target_path)?;
}
}
Ok(())
}
pub fn find_and_sort_folders_at<P>(path: &str, pat: P, order: Ordering) -> Vec<PathBuf>
where
P: Fn(&str) -> bool,
{
let mut folders = Vec::new();
for entry in WalkDir::new(path)
.min_depth(1)
.max_depth(1)
.into_iter()
.filter_map(|e| e.ok())
{
let entry_path = entry.path().to_path_buf();
if entry_path.is_dir()
&& entry_path
.file_name()
.unwrap_or_default()
.to_str()
.map(&pat)
.unwrap_or(false)
{
let metadata = fs::metadata(&entry_path).ok();
let modified_time = metadata
.and_then(|m| m.modified().ok())
.unwrap_or(SystemTime::UNIX_EPOCH);
folders.push((entry_path, modified_time));
}
}
// Sort folders based on the specified order
folders.sort_by(|a, b| match order {
Ordering::Less => a.1.cmp(&b.1),
Ordering::Greater => b.1.cmp(&a.1),
_ => a.1.cmp(&b.1), // Default case
});
// Extract just the PathBufs, discarding the modification times
folders.into_iter().map(|(path, _)| path).collect()
}
pub fn zip_folder(src_path: impl AsRef<Path>, dest_path: &Path) -> io::Result<()> {
if !src_path.as_ref().exists() {
return Err(io::ErrorKind::NotFound.into());
}
if src_path.as_ref() == dest_path {
return Err(io::ErrorKind::InvalidInput.into());
}
let file = File::create(dest_path)?;
let mut zip = ZipWriter::new(file);
let options = FileOptions::default().compression_method(zip::CompressionMethod::Stored);
for entry in WalkDir::new(&src_path) {
let entry = entry?;
let path = entry.path();
let name = match path.strip_prefix(&src_path) {
Ok(n) => n,
Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "Invalid path")),
};
if path.is_file() {
zip.start_file(
name
.to_str()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid file name"))?,
options,
)?;
let mut f = File::open(path)?;
io::copy(&mut f, &mut zip)?;
} else if !name.as_os_str().is_empty() {
zip.add_directory(
name
.to_str()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid directory name"))?,
options,
)?;
}
}
zip.finish()?;
Ok(())
}
pub fn unzip_and_replace(zip_path: impl AsRef<Path>, target_folder: &Path) -> io::Result<()> {
// Create a temporary directory for unzipping
let temp_dir = tempdir()?;
// Unzip the file
let file = File::open(zip_path.as_ref())?;
let mut archive = ZipArchive::new(file)?;
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
let outpath = temp_dir.path().join(file.mangled_name());
if file.name().ends_with('/') {
fs::create_dir_all(&outpath)?;
} else {
if let Some(p) = outpath.parent() {
if !p.exists() {
fs::create_dir_all(p)?;
}
}
let mut outfile = File::create(&outpath)?;
io::copy(&mut file, &mut outfile)?;
}
}
// Replace the contents of the target folder
if target_folder.exists() {
fs::remove_dir_all(target_folder)?;
}
fs::create_dir_all(target_folder)?;
for entry in fs::read_dir(temp_dir.path())? {
let entry = entry?;
fs::rename(entry.path(), target_folder.join(entry.file_name()))?;
}
Ok(())
}

View File

@ -1,6 +1,7 @@
pub use async_trait; pub use async_trait;
pub mod box_any; pub mod box_any;
pub mod file_util;
pub mod future; pub mod future;
pub mod ref_map; pub mod ref_map;
pub mod util; pub mod util;

View File

@ -1,10 +1,11 @@
use super::cursor::*; use std::ops::{Deref, DerefMut};
use crate::core::delta::operation::{DeltaOperation, OperationAttributes}; use crate::core::delta::operation::{DeltaOperation, OperationAttributes};
use crate::core::delta::{DeltaOperations, NEW_LINE}; use crate::core::delta::{DeltaOperations, NEW_LINE};
use crate::core::interval::Interval; use crate::core::interval::Interval;
use crate::core::AttributeHashMap; use crate::core::AttributeHashMap;
use std::ops::{Deref, DerefMut}; use super::cursor::*;
pub(crate) const MAX_IV_LEN: usize = i32::MAX as usize; pub(crate) const MAX_IV_LEN: usize = i32::MAX as usize;
@ -93,7 +94,7 @@ where
pub fn seek<M: Metric>(&mut self, index: usize) { pub fn seek<M: Metric>(&mut self, index: usize) {
match M::seek(&mut self.cursor, index) { match M::seek(&mut self.cursor, index) {
Ok(_) => {}, Ok(_) => {},
Err(e) => log::error!("Seek fail: {:?}", e), Err(e) => tracing::error!("Seek fail: {:?}", e),
} }
} }

View File

@ -1,7 +1,3 @@
use crate::core::interval::Interval;
use crate::core::ot_str::OTString;
use crate::errors::OTError;
use serde::{Deserialize, Serialize, __private::Formatter};
use std::fmt::Display; use std::fmt::Display;
use std::{ use std::{
cmp::min, cmp::min,
@ -10,6 +6,12 @@ use std::{
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
}; };
use serde::{Deserialize, Serialize, __private::Formatter};
use crate::core::interval::Interval;
use crate::core::ot_str::OTString;
use crate::errors::OTError;
pub trait OperationTransform { pub trait OperationTransform {
/// Merges the operation with `other` into one operation while preserving /// Merges the operation with `other` into one operation while preserving
/// the changes of both. /// the changes of both.
@ -156,7 +158,7 @@ where
pub fn set_attributes(&mut self, attributes: T) { pub fn set_attributes(&mut self, attributes: T) {
match self { match self {
DeltaOperation::Delete(_) => log::error!("Delete should not contains attributes"), DeltaOperation::Delete(_) => tracing::error!("Delete should not contains attributes"),
DeltaOperation::Retain(retain) => retain.attributes = attributes, DeltaOperation::Retain(retain) => retain.attributes = attributes,
DeltaOperation::Insert(insert) => insert.attributes = attributes, DeltaOperation::Insert(insert) => insert.attributes = attributes,
} }

View File

@ -1,3 +1,14 @@
use std::{
cmp::{min, Ordering},
fmt,
iter::FromIterator,
str,
str::FromStr,
};
use bytes::Bytes;
use serde::de::DeserializeOwned;
use crate::core::delta::operation::{ use crate::core::delta::operation::{
DeltaOperation, EmptyAttributes, OperationAttributes, OperationTransform, DeltaOperation, EmptyAttributes, OperationAttributes, OperationTransform,
}; };
@ -6,15 +17,6 @@ use crate::core::interval::Interval;
use crate::core::ot_str::OTString; use crate::core::ot_str::OTString;
use crate::core::DeltaOperationBuilder; use crate::core::DeltaOperationBuilder;
use crate::errors::{ErrorBuilder, OTError, OTErrorCode}; use crate::errors::{ErrorBuilder, OTError, OTErrorCode};
use bytes::Bytes;
use serde::de::DeserializeOwned;
use std::{
cmp::{min, Ordering},
fmt,
iter::FromIterator,
str,
str::FromStr,
};
pub type DeltaBuilder = DeltaOperationBuilder<EmptyAttributes>; pub type DeltaBuilder = DeltaOperationBuilder<EmptyAttributes>;
@ -564,7 +566,7 @@ fn invert_other<T: OperationAttributes>(
base.retain(other_op.len(), inverted_attrs); base.retain(other_op.len(), inverted_attrs);
}, },
DeltaOperation::Insert(_) => { DeltaOperation::Insert(_) => {
log::error!("Impossible to here. Insert operation should be treated as delete") tracing::error!("Impossible to here. Insert operation should be treated as delete")
}, },
}); });
} }