mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: optimize the UI if fail to open the workspace (#3246)
* chore: async load user profile * chore: enable reset workspace * chore: add confirm dialog
This commit is contained in:
parent
bd30e31f6c
commit
12d6cbd46a
@ -38,4 +38,7 @@ class LoadingState with _$LoadingState {
|
||||
const factory LoadingState.finish(
|
||||
Either<Unit, FlowyError> successOrFail,
|
||||
) = _Finish;
|
||||
|
||||
const LoadingState._();
|
||||
isLoading() => this is _Loading;
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ class RowDocumentBloc extends Bloc<RowDocumentEvent, RowDocumentState> {
|
||||
viewsOrError.fold(
|
||||
(view) => add(RowDocumentEvent.didReceiveRowDocument(view)),
|
||||
(error) async {
|
||||
if (error.code == ErrorCode.RecordNotFound.value) {
|
||||
if (error.code == ErrorCode.RecordNotFound) {
|
||||
// By default, the document of the row is not exist. So creating a
|
||||
// new document for the given document id of the row.
|
||||
final documentView =
|
||||
|
@ -107,7 +107,7 @@ class DateCellCalendarBloc
|
||||
}
|
||||
},
|
||||
(err) {
|
||||
switch (ErrorCode.valueOf(err.code)!) {
|
||||
switch (err.code) {
|
||||
case ErrorCode.InvalidDateTimeFormat:
|
||||
if (isClosed) return;
|
||||
add(
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/auth.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -81,7 +82,7 @@ class AppFlowyAuthService implements AuthService {
|
||||
}) async {
|
||||
return left(
|
||||
FlowyError.create()
|
||||
..code = 0
|
||||
..code = ErrorCode.Internal
|
||||
..msg = "Unsupported sign up action",
|
||||
);
|
||||
}
|
||||
@ -98,7 +99,7 @@ class AppFlowyAuthService implements AuthService {
|
||||
}) async {
|
||||
return left(
|
||||
FlowyError.create()
|
||||
..code = 0
|
||||
..code = ErrorCode.Internal
|
||||
..msg = "Unsupported sign up action",
|
||||
);
|
||||
}
|
||||
|
@ -1,19 +1,20 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
|
||||
class AuthError {
|
||||
static final supabaseSignInError = FlowyError()
|
||||
..msg = 'supabase sign in error'
|
||||
..code = -10001;
|
||||
..msg = 'supabase sign in error -10001'
|
||||
..code = ErrorCode.UserUnauthorized;
|
||||
|
||||
static final supabaseSignUpError = FlowyError()
|
||||
..msg = 'supabase sign up error'
|
||||
..code = -10002;
|
||||
..msg = 'supabase sign up error -10002'
|
||||
..code = ErrorCode.UserUnauthorized;
|
||||
|
||||
static final supabaseSignInWithOauthError = FlowyError()
|
||||
..msg = 'supabase sign in with oauth error'
|
||||
..code = -10003;
|
||||
..msg = 'supabase sign in with oauth error -10003'
|
||||
..code = ErrorCode.UserUnauthorized;
|
||||
|
||||
static final supabaseGetUserError = FlowyError()
|
||||
..msg = 'unable to get user from supabase'
|
||||
..code = -10004;
|
||||
..msg = 'unable to get user from supabase -10004'
|
||||
..code = ErrorCode.UserUnauthorized;
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
|
||||
}
|
||||
|
||||
SignInState stateFromCode(FlowyError error) {
|
||||
switch (ErrorCode.valueOf(error.code)) {
|
||||
switch (error.code) {
|
||||
case ErrorCode.EmailFormatInvalid:
|
||||
return state.copyWith(
|
||||
isSubmitting: false,
|
||||
|
@ -119,7 +119,7 @@ class SignUpBloc extends Bloc<SignUpEvent, SignUpState> {
|
||||
}
|
||||
|
||||
SignUpState stateFromCode(FlowyError error) {
|
||||
switch (ErrorCode.valueOf(error.code)!) {
|
||||
switch (error.code) {
|
||||
case ErrorCode.EmailFormatInvalid:
|
||||
return state.copyWith(
|
||||
isSubmitting: false,
|
||||
|
@ -0,0 +1,98 @@
|
||||
import 'package:appflowy/plugins/database_view/application/defines.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'workspace_error_bloc.freezed.dart';
|
||||
|
||||
class WorkspaceErrorBloc
|
||||
extends Bloc<WorkspaceErrorEvent, WorkspaceErrorState> {
|
||||
final UserFolderPB userFolder;
|
||||
|
||||
WorkspaceErrorBloc({
|
||||
required this.userFolder,
|
||||
required FlowyError error,
|
||||
}) : super(WorkspaceErrorState.initial(error)) {
|
||||
on<WorkspaceErrorEvent>((event, emit) async {
|
||||
await event.when(
|
||||
init: () {
|
||||
// _loadSnapshots();
|
||||
},
|
||||
resetWorkspace: () async {
|
||||
emit(state.copyWith(loadingState: const LoadingState.loading()));
|
||||
final payload = ResetWorkspacePB.create()
|
||||
..workspaceId = userFolder.workspaceId
|
||||
..uid = userFolder.uid;
|
||||
UserEventResetWorkspace(payload).send().then(
|
||||
(result) {
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
add(WorkspaceErrorEvent.didResetWorkspace(result));
|
||||
},
|
||||
);
|
||||
},
|
||||
didResetWorkspace: (result) {
|
||||
result.fold(
|
||||
(_) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
loadingState: LoadingState.finish(result),
|
||||
workspaceState: const WorkspaceState.reset(),
|
||||
),
|
||||
);
|
||||
},
|
||||
(err) {
|
||||
emit(state.copyWith(loadingState: LoadingState.finish(result)));
|
||||
},
|
||||
);
|
||||
},
|
||||
logout: () {
|
||||
emit(
|
||||
state.copyWith(
|
||||
workspaceState: const WorkspaceState.logout(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class WorkspaceErrorEvent with _$WorkspaceErrorEvent {
|
||||
const factory WorkspaceErrorEvent.init() = _Init;
|
||||
const factory WorkspaceErrorEvent.logout() = _DidLogout;
|
||||
const factory WorkspaceErrorEvent.resetWorkspace() = _ResetWorkspace;
|
||||
const factory WorkspaceErrorEvent.didResetWorkspace(
|
||||
Either<Unit, FlowyError> result,
|
||||
) = _DidResetWorkspace;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class WorkspaceErrorState with _$WorkspaceErrorState {
|
||||
const factory WorkspaceErrorState({
|
||||
required FlowyError initialError,
|
||||
LoadingState? loadingState,
|
||||
required WorkspaceState workspaceState,
|
||||
}) = _WorkspaceErrorState;
|
||||
|
||||
factory WorkspaceErrorState.initial(FlowyError error) => WorkspaceErrorState(
|
||||
initialError: error,
|
||||
workspaceState: const WorkspaceState.initial(),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class WorkspaceState with _$WorkspaceState {
|
||||
const factory WorkspaceState.initial() = _Initial;
|
||||
const factory WorkspaceState.logout() = _Logout;
|
||||
const factory WorkspaceState.reset() = _Reset;
|
||||
const factory WorkspaceState.createNewWorkspace() = _NewWorkspace;
|
||||
const factory WorkspaceState.restoreFromSnapshot() = _RestoreFromSnapshot;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class EmptyWorkspaceScreen extends StatelessWidget {
|
||||
const EmptyWorkspaceScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/user/presentation/sign_in_screen.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@ -49,9 +48,8 @@ class _EncryptSecretScreenState extends State<EncryptSecretScreen> {
|
||||
(unit) async {
|
||||
await runAppFlowy();
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
showSnackBarMessage(context, err.msg);
|
||||
(error) {
|
||||
handleOpenWorkspaceError(context, error);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -6,6 +6,7 @@ import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
|
||||
import 'package:appflowy/user/presentation/welcome_screen.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_screen.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_infra/time/duration.dart';
|
||||
import 'package:flowy_infra_ui/widget/route/animation.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||
@ -14,6 +15,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'encrypt_secret_screen.dart';
|
||||
import 'workspace_error_screen.dart';
|
||||
|
||||
const routerNameRoot = '/';
|
||||
const routerNameSignUp = '/signUp';
|
||||
@ -88,6 +90,24 @@ class AuthRouter {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> pushWorkspaceErrorScreen(
|
||||
BuildContext context,
|
||||
UserFolderPB userFolder,
|
||||
FlowyError error,
|
||||
) async {
|
||||
final screen = WorkspaceErrorScreen(
|
||||
userFolder: userFolder,
|
||||
error: error,
|
||||
);
|
||||
await Navigator.of(context).push(
|
||||
PageRoutes.fade(
|
||||
() => screen,
|
||||
const RouteSettings(name: routerNameWelcome),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SplashRoute {
|
||||
|
@ -3,10 +3,14 @@ import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/core/frameless_window.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/historical_user_bloc.dart';
|
||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||
import 'package:appflowy/user/presentation/router.dart';
|
||||
import 'package:appflowy/user/presentation/widgets/background.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
@ -14,7 +18,6 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
@ -62,7 +65,26 @@ class SignInScreen extends StatelessWidget {
|
||||
router.pushHomeScreen(context, user);
|
||||
}
|
||||
},
|
||||
(error) => showSnapBar(context, error.msg),
|
||||
(error) {
|
||||
handleOpenWorkspaceError(context, error);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void handleOpenWorkspaceError(BuildContext context, FlowyError error) {
|
||||
if (error.code == ErrorCode.WorkspaceDataNotSync) {
|
||||
final userFolder = UserFolderPB.fromBuffer(error.payload);
|
||||
getIt<AuthRouter>().pushWorkspaceErrorScreen(context, userFolder, error);
|
||||
} else {
|
||||
Log.error(error);
|
||||
showSnapBar(
|
||||
context,
|
||||
error.msg,
|
||||
onClosed: () {
|
||||
getIt<AuthService>().signOut();
|
||||
runAppFlowy();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/presentation/sign_in_screen.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -80,9 +81,8 @@ class SplashScreen extends StatelessWidget {
|
||||
workspaceSetting,
|
||||
);
|
||||
},
|
||||
(error) async {
|
||||
Log.error(error);
|
||||
getIt<SplashRoute>().pushWelcomeScreen(context, userProfile);
|
||||
(error) {
|
||||
handleOpenWorkspaceError(context, error);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,196 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../application/workspace_error_bloc.dart';
|
||||
|
||||
class WorkspaceErrorScreen extends StatelessWidget {
|
||||
final FlowyError error;
|
||||
final UserFolderPB userFolder;
|
||||
const WorkspaceErrorScreen({
|
||||
required this.userFolder,
|
||||
required this.error,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
extendBody: true,
|
||||
body: BlocProvider(
|
||||
create: (context) => WorkspaceErrorBloc(
|
||||
userFolder: userFolder,
|
||||
error: error,
|
||||
)..add(const WorkspaceErrorEvent.init()),
|
||||
child: MultiBlocListener(
|
||||
listeners: [
|
||||
BlocListener<WorkspaceErrorBloc, WorkspaceErrorState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.workspaceState != current.workspaceState,
|
||||
listener: (context, state) async {
|
||||
await state.workspaceState.when(
|
||||
initial: () {},
|
||||
logout: () async {
|
||||
await getIt<AuthService>().signOut();
|
||||
await runAppFlowy();
|
||||
},
|
||||
reset: () async {
|
||||
await getIt<AuthService>().signOut();
|
||||
await runAppFlowy();
|
||||
},
|
||||
restoreFromSnapshot: () {},
|
||||
createNewWorkspace: () {},
|
||||
);
|
||||
},
|
||||
),
|
||||
BlocListener<WorkspaceErrorBloc, WorkspaceErrorState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.loadingState != current.loadingState,
|
||||
listener: (context, state) async {
|
||||
state.loadingState?.when(
|
||||
loading: () {},
|
||||
finish: (error) {
|
||||
error.fold(
|
||||
(_) {},
|
||||
(err) {
|
||||
showSnapBar(context, err.msg);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<WorkspaceErrorBloc, WorkspaceErrorState>(
|
||||
builder: (context, state) {
|
||||
final List<Widget> children = [
|
||||
WorkspaceErrorDescription(error: error),
|
||||
];
|
||||
|
||||
children.addAll([
|
||||
const VSpace(50),
|
||||
const LogoutButton(),
|
||||
const VSpace(20),
|
||||
const ResetWorkspaceButton(),
|
||||
]);
|
||||
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 500,
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkspaceErrorDescription extends StatelessWidget {
|
||||
final FlowyError error;
|
||||
const WorkspaceErrorDescription({
|
||||
required this.error,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<WorkspaceErrorBloc, WorkspaceErrorState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FlowyText.medium(
|
||||
state.initialError.msg.toString(),
|
||||
fontSize: 14,
|
||||
maxLines: 10,
|
||||
),
|
||||
FlowyText.medium(
|
||||
"Error code: ${state.initialError.code.value.toString()}",
|
||||
fontSize: 12,
|
||||
maxLines: 1,
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LogoutButton extends StatelessWidget {
|
||||
const LogoutButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 40,
|
||||
width: 200,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.settings_menu_logout.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
onTap: () async {
|
||||
context.read<WorkspaceErrorBloc>().add(
|
||||
const WorkspaceErrorEvent.logout(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ResetWorkspaceButton extends StatelessWidget {
|
||||
const ResetWorkspaceButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 200,
|
||||
height: 40,
|
||||
child: BlocBuilder<WorkspaceErrorBloc, WorkspaceErrorState>(
|
||||
builder: (context, state) {
|
||||
final isLoading = state.loadingState?.isLoading() ?? false;
|
||||
final icon = isLoading
|
||||
? const Center(
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
)
|
||||
: null;
|
||||
|
||||
return FlowyButton(
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.workspace_reset.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
onTap: () {
|
||||
NavigatorAlertDialog(
|
||||
title: LocaleKeys.workspace_resetWorkspacePrompt.tr(),
|
||||
confirm: () {
|
||||
context.read<WorkspaceErrorBloc>().add(
|
||||
const WorkspaceErrorEvent.resetWorkspace(),
|
||||
);
|
||||
},
|
||||
).show(context);
|
||||
},
|
||||
rightIcon: icon,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -127,8 +127,8 @@ class _CreateFlowyAlertDialog extends State<NavigatorAlertDialog> {
|
||||
...[
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 300,
|
||||
maxHeight: 100,
|
||||
maxWidth: 400,
|
||||
maxHeight: 260,
|
||||
),
|
||||
child: FlowyText.medium(
|
||||
widget.title,
|
||||
|
@ -1,26 +1,26 @@
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void showSnapBar(BuildContext context, String title, [Color? backgroundColor]) {
|
||||
void showSnapBar(BuildContext context, String title, {VoidCallback? onClosed}) {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
SnackBar(
|
||||
duration: const Duration(milliseconds: 10000),
|
||||
content: WillPopScope(
|
||||
onWillPop: () async {
|
||||
ScaffoldMessenger.of(context).removeCurrentSnackBar();
|
||||
return true;
|
||||
},
|
||||
child: Text(
|
||||
child: FlowyText.medium(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
backgroundColor: backgroundColor,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
)
|
||||
.closed
|
||||
.then((value) => null);
|
||||
.then((value) => onClosed?.call());
|
||||
}
|
||||
|
@ -45,6 +45,8 @@
|
||||
},
|
||||
"workspace": {
|
||||
"create": "Create workspace",
|
||||
"reset": "Reset workspace",
|
||||
"resetWorkspacePrompt": "Resetting the workspace will delete all pages and data within it. Are you sure you want to reset the workspace? Alternatively, you can contact the support team to restore the workspace",
|
||||
"hint": "workspace",
|
||||
"notFoundError": "Workspace not found"
|
||||
},
|
||||
|
@ -29,7 +29,7 @@ pub(crate) async fn get_key_value_handler(
|
||||
data: AFPluginData<KeyPB>,
|
||||
) -> DataResult<KeyValuePB, FlowyError> {
|
||||
match store_preferences.upgrade() {
|
||||
None => Err(FlowyError::internal().context("The store preferences is already drop"))?,
|
||||
None => Err(FlowyError::internal().with_context("The store preferences is already drop"))?,
|
||||
Some(store_preferences) => {
|
||||
let data = data.into_inner();
|
||||
let value = store_preferences.get_str(&data.key);
|
||||
@ -46,7 +46,7 @@ pub(crate) async fn remove_key_value_handler(
|
||||
data: AFPluginData<KeyPB>,
|
||||
) -> FlowyResult<()> {
|
||||
match store_preferences.upgrade() {
|
||||
None => Err(FlowyError::internal().context("The store preferences is already drop"))?,
|
||||
None => Err(FlowyError::internal().with_context("The store preferences is already drop"))?,
|
||||
Some(store_preferences) => {
|
||||
let data = data.into_inner();
|
||||
store_preferences.remove(&data.key);
|
||||
|
@ -35,7 +35,7 @@ impl DatabaseUser for DatabaseUserImpl {
|
||||
self
|
||||
.0
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
|
||||
.ok_or(FlowyError::internal().with_context("Unexpected error: UserSession is None"))?
|
||||
.user_id()
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ impl DatabaseUser for DatabaseUserImpl {
|
||||
self
|
||||
.0
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
|
||||
.ok_or(FlowyError::internal().with_context("Unexpected error: UserSession is None"))?
|
||||
.token()
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ impl DatabaseUser for DatabaseUserImpl {
|
||||
self
|
||||
.0
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
|
||||
.ok_or(FlowyError::internal().with_context("Unexpected error: UserSession is None"))?
|
||||
.get_collab_db(uid)
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ impl DocumentUser for DocumentUserImpl {
|
||||
self
|
||||
.0
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
|
||||
.ok_or(FlowyError::internal().with_context("Unexpected error: UserSession is None"))?
|
||||
.user_id()
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ impl DocumentUser for DocumentUserImpl {
|
||||
self
|
||||
.0
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
|
||||
.ok_or(FlowyError::internal().with_context("Unexpected error: UserSession is None"))?
|
||||
.token()
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ impl DocumentUser for DocumentUserImpl {
|
||||
self
|
||||
.0
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
|
||||
.ok_or(FlowyError::internal().with_context("Unexpected error: UserSession is None"))?
|
||||
.get_collab_db(uid)
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ impl FolderUser for FolderUserImpl {
|
||||
self
|
||||
.0
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
|
||||
.ok_or(FlowyError::internal().with_context("Unexpected error: UserSession is None"))?
|
||||
.user_id()
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ impl FolderUser for FolderUserImpl {
|
||||
self
|
||||
.0
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
|
||||
.ok_or(FlowyError::internal().with_context("Unexpected error: UserSession is None"))?
|
||||
.token()
|
||||
}
|
||||
|
||||
@ -85,7 +85,7 @@ impl FolderUser for FolderUserImpl {
|
||||
self
|
||||
.0
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
|
||||
.ok_or(FlowyError::internal().with_context("Unexpected error: UserSession is None"))?
|
||||
.get_collab_db(uid)
|
||||
}
|
||||
}
|
||||
@ -305,7 +305,7 @@ impl FolderOperationHandler for DatabaseFolderOperation {
|
||||
ViewLayout::Calendar => make_default_calendar(view_id, &name),
|
||||
ViewLayout::Document => {
|
||||
return FutureResult::new(async move {
|
||||
Err(FlowyError::internal().context(format!("Can't handle {:?} layout type", layout)))
|
||||
Err(FlowyError::internal().with_context(format!("Can't handle {:?} layout type", layout)))
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -332,7 +332,8 @@ impl FolderOperationHandler for DatabaseFolderOperation {
|
||||
_ => CSVFormat::Original,
|
||||
};
|
||||
FutureResult::new(async move {
|
||||
let content = String::from_utf8(bytes).map_err(|err| FlowyError::internal().context(err))?;
|
||||
let content =
|
||||
String::from_utf8(bytes).map_err(|err| FlowyError::internal().with_context(err))?;
|
||||
database_manager
|
||||
.import_csv(view_id, content, format)
|
||||
.await?;
|
||||
@ -359,7 +360,7 @@ impl FolderOperationHandler for DatabaseFolderOperation {
|
||||
let database_layout = match new.layout {
|
||||
ViewLayout::Document => {
|
||||
return FutureResult::new(async {
|
||||
Err(FlowyError::internal().context("Can't handle document layout type"))
|
||||
Err(FlowyError::internal().with_context("Can't handle document layout type"))
|
||||
});
|
||||
},
|
||||
ViewLayout::Grid => DatabaseLayoutPB::Grid,
|
||||
|
@ -422,9 +422,9 @@ impl LocalServerDB for LocalServerDBImpl {
|
||||
fn get_collab_updates(&self, uid: i64, object_id: &str) -> Result<Vec<Vec<u8>>, FlowyError> {
|
||||
let collab_db = open_collab_db(&self.storage_path, uid)?;
|
||||
let read_txn = collab_db.read_txn();
|
||||
let updates = read_txn
|
||||
.get_all_updates(uid, object_id)
|
||||
.map_err(|e| FlowyError::internal().context(format!("Failed to open collab db: {:?}", e)))?;
|
||||
let updates = read_txn.get_all_updates(uid, object_id).map_err(|e| {
|
||||
FlowyError::internal().with_context(format!("Failed to open collab db: {:?}", e))
|
||||
})?;
|
||||
|
||||
Ok(updates)
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ fn upgrade_manager(
|
||||
) -> FlowyResult<Arc<DatabaseManager>> {
|
||||
let manager = database_manager
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::internal().context("The database manager is already dropped"))?;
|
||||
.ok_or(FlowyError::internal().with_context("The database manager is already dropped"))?;
|
||||
Ok(manager)
|
||||
}
|
||||
|
||||
@ -459,7 +459,7 @@ pub(crate) async fn create_row_handler(
|
||||
.create_row(&view_id, group_id, params)
|
||||
.await?
|
||||
{
|
||||
None => Err(FlowyError::internal().context("Create row fail")),
|
||||
None => Err(FlowyError::internal().with_context("Create row fail")),
|
||||
Some(row) => data_result_ok(RowMetaPB::from(row.meta)),
|
||||
}
|
||||
}
|
||||
@ -510,9 +510,10 @@ pub(crate) async fn new_select_option_handler(
|
||||
.create_select_option(¶ms.field_id, params.option_name)
|
||||
.await;
|
||||
match result {
|
||||
None => {
|
||||
Err(FlowyError::record_not_found().context("Create select option fail. Can't find the field"))
|
||||
},
|
||||
None => Err(
|
||||
FlowyError::record_not_found()
|
||||
.with_context("Create select option fail. Can't find the field"),
|
||||
),
|
||||
Some(pb) => data_result_ok(pb),
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ impl DatabaseManager {
|
||||
collab_raw_data = updates;
|
||||
},
|
||||
Err(err) => {
|
||||
return Err(FlowyError::record_not_found().context(format!(
|
||||
return Err(FlowyError::record_not_found().with_context(format!(
|
||||
"get workspace database :{} failed: {}",
|
||||
database_storage_id, err,
|
||||
)));
|
||||
@ -156,7 +156,7 @@ impl DatabaseManager {
|
||||
let wdb = self.get_workspace_database().await?;
|
||||
wdb.get_database_id_with_view_id(view_id).ok_or_else(|| {
|
||||
FlowyError::record_not_found()
|
||||
.context(format!("The database for view id: {} not found", view_id))
|
||||
.with_context(format!("The database for view id: {} not found", view_id))
|
||||
})
|
||||
}
|
||||
|
||||
@ -331,7 +331,7 @@ impl DatabaseManager {
|
||||
async fn get_workspace_database(&self) -> FlowyResult<Arc<WorkspaceDatabase>> {
|
||||
let database = self.workspace_database.read().await;
|
||||
match &*database {
|
||||
None => Err(FlowyError::internal().context("Workspace database not initialized")),
|
||||
None => Err(FlowyError::internal().with_context("Workspace database not initialized")),
|
||||
Some(user_database) => Ok(user_database.clone()),
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ impl TypeCellData {
|
||||
pub fn from_json_str(s: &str) -> FlowyResult<Self> {
|
||||
let type_cell_data: TypeCellData = serde_json::from_str(s).map_err(|err| {
|
||||
let msg = format!("Deserialize {} to type cell data failed.{}", s, err);
|
||||
FlowyError::internal().context(msg)
|
||||
FlowyError::internal().with_context(msg)
|
||||
})?;
|
||||
Ok(type_cell_data)
|
||||
}
|
||||
|
@ -673,7 +673,7 @@ impl DatabaseEditor {
|
||||
Some(field) => Ok(field),
|
||||
None => {
|
||||
let msg = format!("Field with id:{} not found", &field_id);
|
||||
Err(FlowyError::internal().context(msg))
|
||||
Err(FlowyError::internal().with_context(msg))
|
||||
},
|
||||
}?;
|
||||
(field, database.get_cell(field_id, &row_id).cell)
|
||||
@ -767,7 +767,8 @@ impl DatabaseEditor {
|
||||
.fields
|
||||
.get_field(field_id)
|
||||
.ok_or_else(|| {
|
||||
FlowyError::record_not_found().context(format!("Field with id:{} not found", &field_id))
|
||||
FlowyError::record_not_found()
|
||||
.with_context(format!("Field with id:{} not found", &field_id))
|
||||
})?;
|
||||
debug_assert!(FieldType::from(field.field_type).is_select_option());
|
||||
|
||||
@ -802,7 +803,7 @@ impl DatabaseEditor {
|
||||
Some(field) => Ok(field),
|
||||
None => {
|
||||
let msg = format!("Field with id:{} not found", &field_id);
|
||||
Err(FlowyError::internal().context(msg))
|
||||
Err(FlowyError::internal().with_context(msg))
|
||||
},
|
||||
}?;
|
||||
let mut type_option = select_type_option_from_field(&field)?;
|
||||
@ -868,7 +869,8 @@ impl DatabaseEditor {
|
||||
.fields
|
||||
.get_field(field_id)
|
||||
.ok_or_else(|| {
|
||||
FlowyError::record_not_found().context(format!("Field with id:{} not found", &field_id))
|
||||
FlowyError::record_not_found()
|
||||
.with_context(format!("Field with id:{} not found", &field_id))
|
||||
})?;
|
||||
debug_assert!(FieldType::from(field.field_type).is_checklist());
|
||||
|
||||
@ -1047,11 +1049,10 @@ impl DatabaseEditor {
|
||||
&self,
|
||||
view_id: &str,
|
||||
) -> FlowyResult<DatabaseViewSettingPB> {
|
||||
let view = self
|
||||
.database
|
||||
.lock()
|
||||
.get_view(view_id)
|
||||
.ok_or_else(|| FlowyError::record_not_found().context("Can't find the database view"))?;
|
||||
let view =
|
||||
self.database.lock().get_view(view_id).ok_or_else(|| {
|
||||
FlowyError::record_not_found().with_context("Can't find the database view")
|
||||
})?;
|
||||
Ok(database_view_setting_pb_from_view(view))
|
||||
}
|
||||
|
||||
|
@ -418,7 +418,7 @@ impl DatabaseViewEditor {
|
||||
.as_ref()
|
||||
.and_then(|group| group.get_group(group_id))
|
||||
{
|
||||
None => Err(FlowyError::record_not_found().context("Can't find the group")),
|
||||
None => Err(FlowyError::record_not_found().with_context("Can't find the group")),
|
||||
Some((_, group)) => Ok(GroupPB::from(group)),
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +125,10 @@ impl CellDataChangeset for RichTextTypeOption {
|
||||
_cell: Option<Cell>,
|
||||
) -> FlowyResult<(Cell, <Self as TypeOption>::CellData)> {
|
||||
if changeset.len() > 10000 {
|
||||
Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000"))
|
||||
Err(
|
||||
FlowyError::text_too_long()
|
||||
.with_context("The len of the text should not be more than 10000"),
|
||||
)
|
||||
} else {
|
||||
let text_cell_data = StrCellData(changeset);
|
||||
Ok((text_cell_data.clone().into(), text_cell_data))
|
||||
|
@ -223,7 +223,9 @@ where
|
||||
})?;
|
||||
Ok(())
|
||||
},
|
||||
_ => Err(FlowyError::record_not_found().context("Moving group failed. Groups are not exist")),
|
||||
_ => Err(
|
||||
FlowyError::record_not_found().with_context("Moving group failed. Groups are not exist"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ impl CSVExport {
|
||||
.collect::<Vec<String>>();
|
||||
wtr
|
||||
.write_record(&field_records)
|
||||
.map_err(|e| FlowyError::internal().context(e))?;
|
||||
.map_err(|e| FlowyError::internal().with_context(e))?;
|
||||
|
||||
// Write rows
|
||||
let mut field_by_field_id = IndexMap::new();
|
||||
@ -63,8 +63,8 @@ impl CSVExport {
|
||||
|
||||
let data = wtr
|
||||
.into_inner()
|
||||
.map_err(|e| FlowyError::internal().context(e))?;
|
||||
let csv = String::from_utf8(data).map_err(|e| FlowyError::internal().context(e))?;
|
||||
.map_err(|e| FlowyError::internal().with_context(e))?;
|
||||
let csv = String::from_utf8(data).map_err(|e| FlowyError::internal().with_context(e))?;
|
||||
Ok(csv)
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
use crate::entities::FieldType;
|
||||
use std::{fs::File, io::prelude::*};
|
||||
|
||||
use crate::services::field::{default_type_option_data_from_type, CELL_DATA};
|
||||
use crate::services::share::csv::CSVFormat;
|
||||
use collab_database::database::{gen_database_id, gen_field_id, gen_row_id};
|
||||
use collab_database::fields::Field;
|
||||
use collab_database::rows::{new_cell_builder, Cell, CreateRowParams};
|
||||
use collab_database::views::{CreateDatabaseParams, DatabaseLayout};
|
||||
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use std::{fs::File, io::prelude::*};
|
||||
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::field::{default_type_option_data_from_type, CELL_DATA};
|
||||
use crate::services::share::csv::CSVFormat;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CSVImporter;
|
||||
@ -41,7 +43,7 @@ impl CSVImporter {
|
||||
fn get_fields_and_rows(&self, content: String) -> Result<FieldsRows, FlowyError> {
|
||||
let mut fields: Vec<String> = vec![];
|
||||
if content.is_empty() {
|
||||
return Err(FlowyError::invalid_data().context("Import content is empty"));
|
||||
return Err(FlowyError::invalid_data().with_context("Import content is empty"));
|
||||
}
|
||||
|
||||
let mut reader = csv::Reader::from_reader(content.as_bytes());
|
||||
@ -50,7 +52,7 @@ impl CSVImporter {
|
||||
fields.push(header.to_string());
|
||||
}
|
||||
} else {
|
||||
return Err(FlowyError::invalid_data().context("Header not found"));
|
||||
return Err(FlowyError::invalid_data().with_context("Header not found"));
|
||||
}
|
||||
|
||||
let rows = reader
|
||||
@ -164,9 +166,10 @@ pub struct ImportResult {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::services::share::csv::{CSVFormat, CSVImporter};
|
||||
use collab_database::database::gen_database_view_id;
|
||||
|
||||
use crate::services::share::csv::{CSVFormat, CSVImporter};
|
||||
|
||||
#[test]
|
||||
fn test_import_csv_from_str() {
|
||||
let s = r#"Name,Tags,Number,Date,Checkbox,URL
|
||||
|
@ -22,7 +22,7 @@ fn upgrade_document(
|
||||
) -> FlowyResult<Arc<DocumentManager>> {
|
||||
let manager = document_manager
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::internal().context("The document manager is already dropped"))?;
|
||||
.ok_or(FlowyError::internal().with_context("The document manager is already dropped"))?;
|
||||
Ok(manager)
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,12 @@ use thiserror::Error;
|
||||
|
||||
use flowy_derive::ProtoBuf_Enum;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize_repr, Deserialize_repr, ProtoBuf_Enum)]
|
||||
#[derive(
|
||||
Debug, Default, Clone, PartialEq, Eq, Error, Serialize_repr, Deserialize_repr, ProtoBuf_Enum,
|
||||
)]
|
||||
#[repr(u8)]
|
||||
pub enum ErrorCode {
|
||||
#[default]
|
||||
#[error("Internal error")]
|
||||
Internal = 0,
|
||||
|
||||
@ -226,6 +229,9 @@ pub enum ErrorCode {
|
||||
|
||||
#[error("It appears that the collaboration object's data has not been fully synchronized")]
|
||||
CollabDataNotSync = 75,
|
||||
|
||||
#[error("It appears that the workspace data has not been fully synchronized")]
|
||||
WorkspaceDataNotSync = 76,
|
||||
}
|
||||
|
||||
impl ErrorCode {
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use anyhow::Result;
|
||||
use protobuf::ProtobufError;
|
||||
use thiserror::Error;
|
||||
|
||||
use flowy_derive::ProtoBuf;
|
||||
@ -13,10 +14,13 @@ pub type FlowyResult<T> = anyhow::Result<T, FlowyError>;
|
||||
#[error("{code:?}: {msg}")]
|
||||
pub struct FlowyError {
|
||||
#[pb(index = 1)]
|
||||
pub code: i32,
|
||||
pub code: ErrorCode,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub msg: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
macro_rules! static_flowy_error {
|
||||
@ -31,17 +35,23 @@ macro_rules! static_flowy_error {
|
||||
impl FlowyError {
|
||||
pub fn new<T: ToString>(code: ErrorCode, msg: T) -> Self {
|
||||
Self {
|
||||
code: code.value(),
|
||||
code,
|
||||
msg: msg.to_string(),
|
||||
payload: vec![],
|
||||
}
|
||||
}
|
||||
pub fn context<T: Debug>(mut self, error: T) -> Self {
|
||||
pub fn with_context<T: Debug>(mut self, error: T) -> Self {
|
||||
self.msg = format!("{:?}", error);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_payload<T: TryInto<Vec<u8>, Error = ProtobufError>>(mut self, payload: T) -> Self {
|
||||
self.payload = payload.try_into().unwrap_or_default();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_record_not_found(&self) -> bool {
|
||||
self.code == ErrorCode::RecordNotFound.value()
|
||||
self.code == ErrorCode::RecordNotFound
|
||||
}
|
||||
|
||||
static_flowy_error!(internal, ErrorCode::Internal);
|
||||
@ -93,9 +103,11 @@ impl FlowyError {
|
||||
|
||||
impl std::convert::From<ErrorCode> for FlowyError {
|
||||
fn from(code: ErrorCode) -> Self {
|
||||
let msg = format!("{}", code);
|
||||
FlowyError {
|
||||
code: code.value(),
|
||||
msg: format!("{}", code),
|
||||
code,
|
||||
msg,
|
||||
payload: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -104,18 +116,18 @@ pub fn internal_error<T>(e: T) -> FlowyError
|
||||
where
|
||||
T: std::fmt::Debug,
|
||||
{
|
||||
FlowyError::internal().context(e)
|
||||
FlowyError::internal().with_context(e)
|
||||
}
|
||||
|
||||
impl std::convert::From<std::io::Error> for FlowyError {
|
||||
fn from(error: std::io::Error) -> Self {
|
||||
FlowyError::internal().context(error)
|
||||
FlowyError::internal().with_context(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<protobuf::ProtobufError> for FlowyError {
|
||||
fn from(e: protobuf::ProtobufError) -> Self {
|
||||
FlowyError::internal().context(e)
|
||||
FlowyError::internal().with_context(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,16 @@
|
||||
use crate::FlowyError;
|
||||
use collab_database::error::DatabaseError;
|
||||
use collab_document::error::DocumentError;
|
||||
|
||||
use crate::FlowyError;
|
||||
|
||||
impl From<DatabaseError> for FlowyError {
|
||||
fn from(error: DatabaseError) -> Self {
|
||||
FlowyError::internal().context(error)
|
||||
FlowyError::internal().with_context(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DocumentError> for FlowyError {
|
||||
fn from(error: DocumentError) -> Self {
|
||||
FlowyError::internal().context(error)
|
||||
FlowyError::internal().with_context(error)
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,12 @@ use crate::FlowyError;
|
||||
|
||||
impl std::convert::From<flowy_sqlite::Error> for FlowyError {
|
||||
fn from(error: flowy_sqlite::Error) -> Self {
|
||||
FlowyError::internal().context(error)
|
||||
FlowyError::internal().with_context(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<::r2d2::Error> for FlowyError {
|
||||
fn from(error: r2d2::Error) -> Self {
|
||||
FlowyError::internal().context(error)
|
||||
FlowyError::internal().with_context(error)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
use crate::FlowyError;
|
||||
use bytes::Bytes;
|
||||
use lib_dispatch::prelude::{AFPluginEventResponse, ResponseBuilder};
|
||||
use std::convert::TryInto;
|
||||
|
||||
use bytes::Bytes;
|
||||
|
||||
use lib_dispatch::prelude::{AFPluginEventResponse, ResponseBuilder};
|
||||
|
||||
use crate::FlowyError;
|
||||
|
||||
impl lib_dispatch::Error for FlowyError {
|
||||
fn as_response(&self) -> AFPluginEventResponse {
|
||||
let bytes: Bytes = self.clone().try_into().unwrap();
|
||||
|
@ -2,6 +2,6 @@ use crate::FlowyError;
|
||||
|
||||
impl std::convert::From<tokio_postgres::Error> for FlowyError {
|
||||
fn from(error: tokio_postgres::Error) -> Self {
|
||||
FlowyError::internal().context(error)
|
||||
FlowyError::internal().with_context(error)
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
use crate::FlowyError;
|
||||
use reqwest::Error;
|
||||
|
||||
use crate::FlowyError;
|
||||
|
||||
impl std::convert::From<reqwest::Error> for FlowyError {
|
||||
fn from(error: Error) -> Self {
|
||||
FlowyError::http().context(error)
|
||||
FlowyError::http().with_context(error)
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,6 @@ use crate::FlowyError;
|
||||
|
||||
impl std::convert::From<serde_json::Error> for FlowyError {
|
||||
fn from(error: serde_json::Error) -> Self {
|
||||
FlowyError::serde().context(error)
|
||||
FlowyError::serde().with_context(error)
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::FlowyError;
|
||||
|
||||
use crate::entities::parser::empty_str::NotEmptyStr;
|
||||
use crate::entities::ViewLayoutPB;
|
||||
use crate::share::{ImportParams, ImportType};
|
||||
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::FlowyError;
|
||||
|
||||
#[derive(Clone, Debug, ProtoBuf_Enum)]
|
||||
pub enum ImportTypePB {
|
||||
HistoryDocument = 0,
|
||||
@ -69,7 +69,7 @@ impl TryInto<ImportParams> for ImportPB {
|
||||
None => None,
|
||||
Some(file_path) => Some(
|
||||
NotEmptyStr::parse(file_path)
|
||||
.map_err(|_| FlowyError::invalid_data().context("The import file path is empty"))?
|
||||
.map_err(|_| FlowyError::invalid_data().with_context("The import file path is empty"))?
|
||||
.0,
|
||||
),
|
||||
};
|
||||
|
@ -1,12 +1,15 @@
|
||||
use std::convert::TryInto;
|
||||
|
||||
use collab::core::collab_state::SyncState;
|
||||
use collab_folder::core::Workspace;
|
||||
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
use crate::{
|
||||
entities::parser::workspace::{WorkspaceDesc, WorkspaceIdentify, WorkspaceName},
|
||||
entities::view::ViewPB,
|
||||
};
|
||||
use collab::core::collab_state::SyncState;
|
||||
use collab_folder::core::Workspace;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Default, Debug, Clone)]
|
||||
pub struct WorkspacePB {
|
||||
@ -197,3 +200,12 @@ impl From<SyncState> for FolderSyncStatePB {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default)]
|
||||
pub struct UserFolderPB {
|
||||
#[pb(index = 1)]
|
||||
pub uid: i64,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub workspace_id: String,
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ fn upgrade_folder(
|
||||
) -> FlowyResult<Arc<FolderManager>> {
|
||||
let folder = folder_manager
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::internal().context("The folder manager is already dropped"))?;
|
||||
.ok_or(FlowyError::internal().with_context("The folder manager is already dropped"))?;
|
||||
Ok(folder)
|
||||
}
|
||||
|
||||
@ -45,10 +45,10 @@ pub(crate) async fn open_workspace_handler(
|
||||
let folder = upgrade_folder(folder)?;
|
||||
let params: WorkspaceIdPB = data.into_inner();
|
||||
match params.value {
|
||||
None => Err(FlowyError::workspace_id().context("workspace id should not be empty")),
|
||||
None => Err(FlowyError::workspace_id().with_context("workspace id should not be empty")),
|
||||
Some(workspace_id) => {
|
||||
if workspace_id.is_empty() {
|
||||
Err(FlowyError::workspace_id().context("workspace id should not be empty"))
|
||||
Err(FlowyError::workspace_id().with_context("workspace id should not be empty"))
|
||||
} else {
|
||||
let workspace = folder.open_workspace(&workspace_id).await?;
|
||||
let views = folder.get_workspace_views(&workspace_id).await?;
|
||||
|
@ -134,7 +134,7 @@ pub enum FolderEvent {
|
||||
#[event(input = "ImportPB")]
|
||||
ImportData = 30,
|
||||
|
||||
#[event()]
|
||||
#[event(input = "WorkspaceIdPB", output = "RepeatedFolderSnapshotPB")]
|
||||
GetFolderSnapshots = 31,
|
||||
/// Moves a nested view to a new location in the hierarchy.
|
||||
///
|
||||
|
@ -10,7 +10,7 @@ use collab_folder::core::{
|
||||
FavoritesInfo, Folder, FolderData, FolderNotify, TrashChange, TrashChangeReceiver, TrashInfo,
|
||||
View, ViewChange, ViewChangeReceiver, ViewLayout, ViewUpdate, Workspace,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use tokio_stream::wrappers::WatchStream;
|
||||
use tokio_stream::StreamExt;
|
||||
use tracing::{event, Level};
|
||||
@ -22,7 +22,8 @@ use crate::entities::icon::UpdateViewIconParams;
|
||||
use crate::entities::{
|
||||
view_pb_with_child_views, view_pb_without_child_views, ChildViewUpdatePB, CreateViewParams,
|
||||
CreateWorkspaceParams, DeletedViewPB, FolderSnapshotPB, FolderSnapshotStatePB, FolderSyncStatePB,
|
||||
RepeatedTrashPB, RepeatedViewPB, RepeatedWorkspacePB, UpdateViewParams, ViewPB, WorkspacePB,
|
||||
RepeatedTrashPB, RepeatedViewPB, RepeatedWorkspacePB, UpdateViewParams, UserFolderPB, ViewPB,
|
||||
WorkspacePB,
|
||||
};
|
||||
use crate::notification::{
|
||||
send_notification, send_workspace_notification, send_workspace_setting_notification,
|
||||
@ -42,6 +43,7 @@ pub trait FolderUser: Send + Sync {
|
||||
}
|
||||
|
||||
pub struct FolderManager {
|
||||
workspace_id: RwLock<Option<String>>,
|
||||
mutex_folder: Arc<MutexFolder>,
|
||||
collab_builder: Arc<AppFlowyCollabBuilder>,
|
||||
user: Arc<dyn FolderUser>,
|
||||
@ -66,6 +68,7 @@ impl FolderManager {
|
||||
collab_builder,
|
||||
operation_handlers,
|
||||
cloud_service,
|
||||
workspace_id: Default::default(),
|
||||
};
|
||||
|
||||
Ok(manager)
|
||||
@ -73,7 +76,14 @@ impl FolderManager {
|
||||
|
||||
pub async fn get_current_workspace(&self) -> FlowyResult<WorkspacePB> {
|
||||
self.with_folder(
|
||||
Err(FlowyError::internal().context("Folder is not initialized".to_string())),
|
||||
|| {
|
||||
let uid = self.user.user_id()?;
|
||||
let workspace_id = self.workspace_id.read().as_ref().cloned().ok_or(
|
||||
FlowyError::from(ErrorCode::WorkspaceIdInvalid)
|
||||
.with_context("Unexpected empty workspace id"),
|
||||
)?;
|
||||
Err(workspace_data_not_sync_error(uid, &workspace_id))
|
||||
},
|
||||
|folder| {
|
||||
let workspace_pb_from_workspace = |workspace: Workspace, folder: &Folder| {
|
||||
let views = get_workspace_view_pbs(&workspace.id, folder);
|
||||
@ -87,7 +97,7 @@ impl FolderManager {
|
||||
// from the folder. Otherwise, return an error.
|
||||
let mut workspaces = folder.workspaces.get_all_workspaces();
|
||||
if workspaces.is_empty() {
|
||||
Err(FlowyError::record_not_found().context("Can not find the workspace"))
|
||||
Err(FlowyError::record_not_found().with_context("Can not find the workspace"))
|
||||
} else {
|
||||
tracing::error!("Can't find the current workspace, use the first workspace");
|
||||
let workspace = workspaces.remove(0);
|
||||
@ -119,9 +129,10 @@ impl FolderManager {
|
||||
}
|
||||
|
||||
pub async fn get_workspace_views(&self, workspace_id: &str) -> FlowyResult<Vec<ViewPB>> {
|
||||
let views = self.with_folder(vec![], |folder| {
|
||||
get_workspace_view_pbs(workspace_id, folder)
|
||||
});
|
||||
let views = self.with_folder(
|
||||
|| vec![],
|
||||
|folder| get_workspace_view_pbs(workspace_id, folder),
|
||||
);
|
||||
|
||||
Ok(views)
|
||||
}
|
||||
@ -134,6 +145,7 @@ impl FolderManager {
|
||||
workspace_id: &str,
|
||||
initial_data: FolderInitializeData,
|
||||
) -> FlowyResult<()> {
|
||||
*self.workspace_id.write() = Some(workspace_id.to_string());
|
||||
let workspace_id = workspace_id.to_string();
|
||||
if let Ok(collab_db) = self.user.collab_db(uid) {
|
||||
let (view_tx, view_rx) = tokio::sync::broadcast::channel(100);
|
||||
@ -157,10 +169,7 @@ impl FolderManager {
|
||||
},
|
||||
FolderInitializeData::Raw(raw_data) => {
|
||||
if raw_data.is_empty() {
|
||||
return Err(FlowyError::new(
|
||||
ErrorCode::CollabDataNotSync,
|
||||
"Can't fetch the workspace from server",
|
||||
));
|
||||
return Err(workspace_data_not_sync_error(uid, &workspace_id));
|
||||
}
|
||||
let collab = self.collab_for_folder(uid, &workspace_id, collab_db, raw_data)?;
|
||||
Folder::open(collab, Some(folder_notifier))
|
||||
@ -299,10 +308,13 @@ impl FolderManager {
|
||||
.create_workspace(self.user.user_id()?, ¶ms.name)
|
||||
.await?;
|
||||
|
||||
self.with_folder((), |folder| {
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
folder.workspaces.create_workspace(workspace.clone());
|
||||
folder.set_current_workspace(&workspace.id);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
let repeated_workspace = RepeatedWorkspacePB {
|
||||
items: vec![workspace.clone().into()],
|
||||
@ -313,20 +325,26 @@ impl FolderManager {
|
||||
|
||||
#[tracing::instrument(level = "info", skip_all, err)]
|
||||
pub async fn open_workspace(&self, workspace_id: &str) -> FlowyResult<Workspace> {
|
||||
self.with_folder(Err(FlowyError::internal()), |folder| {
|
||||
self.with_folder(
|
||||
|| Err(FlowyError::internal()),
|
||||
|folder| {
|
||||
let workspace = folder
|
||||
.workspaces
|
||||
.get_workspace(workspace_id)
|
||||
.ok_or_else(|| {
|
||||
FlowyError::record_not_found().context("Can't open not existing workspace")
|
||||
FlowyError::record_not_found().with_context("Can't open not existing workspace")
|
||||
})?;
|
||||
folder.set_current_workspace(&workspace.id);
|
||||
Ok::<Workspace, FlowyError>(workspace)
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn get_workspace(&self, workspace_id: &str) -> Option<Workspace> {
|
||||
self.with_folder(None, |folder| folder.workspaces.get_workspace(workspace_id))
|
||||
self.with_folder(
|
||||
|| None,
|
||||
|folder| folder.workspaces.get_workspace(workspace_id),
|
||||
)
|
||||
}
|
||||
|
||||
async fn get_current_workspace_id(&self) -> FlowyResult<String> {
|
||||
@ -335,22 +353,30 @@ impl FolderManager {
|
||||
.lock()
|
||||
.as_ref()
|
||||
.and_then(|folder| folder.get_current_workspace_id())
|
||||
.ok_or(FlowyError::internal().context("Unexpected empty workspace id"))
|
||||
.ok_or(FlowyError::internal().with_context("Unexpected empty workspace id"))
|
||||
}
|
||||
|
||||
fn with_folder<F, Output>(&self, default_value: Output, f: F) -> Output
|
||||
/// This function acquires a lock on the `mutex_folder` and checks its state.
|
||||
/// If the folder is `None`, it invokes the `none_callback`, otherwise, it passes the folder to the `f2` callback.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `none_callback`: A callback function that is invoked when `mutex_folder` contains `None`.
|
||||
/// * `f2`: A callback function that is invoked when `mutex_folder` contains a `Some` value. The contained folder is passed as an argument to this callback.
|
||||
fn with_folder<F1, F2, Output>(&self, none_callback: F1, f2: F2) -> Output
|
||||
where
|
||||
F: FnOnce(&Folder) -> Output,
|
||||
F1: FnOnce() -> Output,
|
||||
F2: FnOnce(&Folder) -> Output,
|
||||
{
|
||||
let folder = self.mutex_folder.lock();
|
||||
match &*folder {
|
||||
None => default_value,
|
||||
Some(folder) => f(folder),
|
||||
None => none_callback(),
|
||||
Some(folder) => f2(folder),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_all_workspaces(&self) -> Vec<Workspace> {
|
||||
self.with_folder(vec![], |folder| folder.workspaces.get_all_workspaces())
|
||||
self.with_folder(|| vec![], |folder| folder.workspaces.get_all_workspaces())
|
||||
}
|
||||
|
||||
pub async fn create_view_with_params(&self, params: CreateViewParams) -> FlowyResult<View> {
|
||||
@ -381,9 +407,12 @@ impl FolderManager {
|
||||
|
||||
let index = params.index;
|
||||
let view = create_view(params, view_layout);
|
||||
self.with_folder((), |folder| {
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
folder.insert_view(view.clone(), index);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
Ok(view)
|
||||
}
|
||||
@ -402,15 +431,18 @@ impl FolderManager {
|
||||
.create_built_in_view(user_id, ¶ms.view_id, ¶ms.name, view_layout.clone())
|
||||
.await?;
|
||||
let view = create_view(params, view_layout);
|
||||
self.with_folder((), |folder| {
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
folder.insert_view(view.clone(), None);
|
||||
});
|
||||
},
|
||||
);
|
||||
Ok(view)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||
pub(crate) async fn close_view(&self, view_id: &str) -> Result<(), FlowyError> {
|
||||
if let Some(view) = self.with_folder(None, |folder| folder.views.get_view(view_id)) {
|
||||
if let Some(view) = self.with_folder(|| None, |folder| folder.views.get_view(view_id)) {
|
||||
let handler = self.get_handler(&view.layout)?;
|
||||
handler.close_view(view_id).await?;
|
||||
}
|
||||
@ -455,7 +487,9 @@ impl FolderManager {
|
||||
/// All the favorite views being trashed will be unfavorited first to remove it from favorites list as well. The process of unfavoriting concerned view is handled by `unfavorite_view_and_decendants()`
|
||||
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||
pub async fn move_view_to_trash(&self, view_id: &str) -> FlowyResult<()> {
|
||||
self.with_folder((), |folder| {
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
if let Some(view) = folder.views.get_view(view_id) {
|
||||
self.unfavorite_view_and_decendants(view.clone(), folder);
|
||||
folder.add_trash(vec![view_id.to_string()]);
|
||||
@ -472,7 +506,8 @@ impl FolderManager {
|
||||
ChildViewChangeReason::DidDeleteView,
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -528,9 +563,12 @@ impl FolderManager {
|
||||
) -> FlowyResult<()> {
|
||||
let view = self.get_view(&view_id).await?;
|
||||
let old_parent_id = view.parent_view_id;
|
||||
self.with_folder((), |folder| {
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
folder.move_nested_view(&view_id, &new_parent_id, prev_view_id);
|
||||
});
|
||||
},
|
||||
);
|
||||
notify_parent_view_did_change(
|
||||
self.mutex_folder.clone(),
|
||||
vec![new_parent_id, old_parent_id],
|
||||
@ -574,9 +612,12 @@ impl FolderManager {
|
||||
if let (Some(actual_from_index), Some(actual_to_index)) =
|
||||
(actual_from_index, actual_to_index)
|
||||
{
|
||||
self.with_folder((), |folder| {
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
folder.move_view(view_id, actual_from_index as u32, actual_to_index as u32);
|
||||
});
|
||||
},
|
||||
);
|
||||
notify_parent_view_did_change(self.mutex_folder.clone(), vec![parent_view_id]);
|
||||
}
|
||||
}
|
||||
@ -587,9 +628,10 @@ impl FolderManager {
|
||||
/// Return a list of views that belong to the given parent view id.
|
||||
#[tracing::instrument(level = "debug", skip(self, parent_view_id), err)]
|
||||
pub async fn get_views_belong_to(&self, parent_view_id: &str) -> FlowyResult<Vec<Arc<View>>> {
|
||||
let views = self.with_folder(vec![], |folder| {
|
||||
folder.views.get_views_belong_to(parent_view_id)
|
||||
});
|
||||
let views = self.with_folder(
|
||||
|| vec![],
|
||||
|folder| folder.views.get_views_belong_to(parent_view_id),
|
||||
);
|
||||
Ok(views)
|
||||
}
|
||||
|
||||
@ -625,8 +667,8 @@ impl FolderManager {
|
||||
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||
pub(crate) async fn duplicate_view(&self, view_id: &str) -> Result<(), FlowyError> {
|
||||
let view = self
|
||||
.with_folder(None, |folder| folder.views.get_view(view_id))
|
||||
.ok_or_else(|| FlowyError::record_not_found().context("Can't duplicate the view"))?;
|
||||
.with_folder(|| None, |folder| folder.views.get_view(view_id))
|
||||
.ok_or_else(|| FlowyError::record_not_found().with_context("Can't duplicate the view"))?;
|
||||
|
||||
let handler = self.get_handler(&view.layout)?;
|
||||
let view_data = handler.duplicate_view(&view.id).await?;
|
||||
@ -670,14 +712,16 @@ impl FolderManager {
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub(crate) async fn get_current_view(&self) -> Option<ViewPB> {
|
||||
let view_id = self.with_folder(None, |folder| folder.get_current_view())?;
|
||||
let view_id = self.with_folder(|| None, |folder| folder.get_current_view())?;
|
||||
self.get_view(&view_id).await.ok()
|
||||
}
|
||||
|
||||
/// Toggles the favorite status of a view identified by `view_id`If the view is not a favorite, it will be added to the favorites list; otherwise, it will be removed from the list.
|
||||
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||
pub async fn toggle_favorites(&self, view_id: &str) -> FlowyResult<()> {
|
||||
self.with_folder((), |folder| {
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
if let Some(old_view) = folder.views.get_view(view_id) {
|
||||
if old_view.is_favorite {
|
||||
folder.delete_favorites(vec![view_id.to_string()]);
|
||||
@ -685,7 +729,8 @@ impl FolderManager {
|
||||
folder.add_favorites(vec![view_id.to_string()]);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
self.send_toggle_favorite_notification(view_id).await;
|
||||
Ok(())
|
||||
}
|
||||
@ -712,7 +757,9 @@ impl FolderManager {
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub(crate) async fn get_all_favorites(&self) -> Vec<FavoritesInfo> {
|
||||
self.with_folder(vec![], |folder| {
|
||||
self.with_folder(
|
||||
|| vec![],
|
||||
|folder| {
|
||||
let trash_ids = folder
|
||||
.get_all_trash()
|
||||
.into_iter()
|
||||
@ -722,19 +769,23 @@ impl FolderManager {
|
||||
let mut views = folder.get_all_favorites();
|
||||
views.retain(|view| !trash_ids.contains(&view.id));
|
||||
views
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub(crate) async fn get_all_trash(&self) -> Vec<TrashInfo> {
|
||||
self.with_folder(vec![], |folder| folder.get_all_trash())
|
||||
self.with_folder(|| vec![], |folder| folder.get_all_trash())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub(crate) async fn restore_all_trash(&self) {
|
||||
self.with_folder((), |folder| {
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
folder.remote_all_trash();
|
||||
});
|
||||
},
|
||||
);
|
||||
send_notification("trash", FolderNotification::DidUpdateTrash)
|
||||
.payload(RepeatedTrashPB { items: vec![] })
|
||||
.send();
|
||||
@ -742,15 +793,18 @@ impl FolderManager {
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub(crate) async fn restore_trash(&self, trash_id: &str) {
|
||||
self.with_folder((), |folder| {
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
folder.delete_trash(vec![trash_id.to_string()]);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Delete all the trash permanently.
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub(crate) async fn delete_all_trash(&self) {
|
||||
let deleted_trash = self.with_folder(vec![], |folder| folder.get_all_trash());
|
||||
let deleted_trash = self.with_folder(|| vec![], |folder| folder.get_all_trash());
|
||||
for trash in deleted_trash {
|
||||
let _ = self.delete_trash(&trash.id).await;
|
||||
}
|
||||
@ -764,11 +818,14 @@ impl FolderManager {
|
||||
/// is a database view. Then the database will be deleted as well.
|
||||
#[tracing::instrument(level = "debug", skip(self, view_id), err)]
|
||||
pub async fn delete_trash(&self, view_id: &str) -> FlowyResult<()> {
|
||||
let view = self.with_folder(None, |folder| folder.views.get_view(view_id));
|
||||
self.with_folder((), |folder| {
|
||||
let view = self.with_folder(|| None, |folder| folder.views.get_view(view_id));
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
folder.delete_trash(vec![view_id.to_string()]);
|
||||
folder.views.delete_views(vec![view_id]);
|
||||
});
|
||||
},
|
||||
);
|
||||
if let Some(view) = view {
|
||||
if let Ok(handler) = self.get_handler(&view.layout) {
|
||||
handler.delete_view(view_id).await?;
|
||||
@ -819,9 +876,12 @@ impl FolderManager {
|
||||
};
|
||||
|
||||
let view = create_view(params, import_data.view_layout);
|
||||
self.with_folder((), |folder| {
|
||||
self.with_folder(
|
||||
|| (),
|
||||
|folder| {
|
||||
folder.insert_view(view.clone(), None);
|
||||
});
|
||||
},
|
||||
);
|
||||
notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.parent_view_id.clone()]);
|
||||
Ok(view)
|
||||
}
|
||||
@ -831,12 +891,15 @@ impl FolderManager {
|
||||
where
|
||||
F: FnOnce(ViewUpdate) -> Option<View>,
|
||||
{
|
||||
let value = self.with_folder(None, |folder| {
|
||||
let value = self.with_folder(
|
||||
|| None,
|
||||
|folder| {
|
||||
let old_view = folder.views.get_view(view_id);
|
||||
let new_view = folder.views.update_view(view_id, f);
|
||||
|
||||
Some((old_view, new_view))
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if let Some((Some(old_view), Some(new_view))) = value {
|
||||
if let Ok(handler) = self.get_handler(&old_view.layout) {
|
||||
@ -858,7 +921,7 @@ impl FolderManager {
|
||||
view_layout: &ViewLayout,
|
||||
) -> FlowyResult<Arc<dyn FolderOperationHandler + Send + Sync>> {
|
||||
match self.operation_handlers.get(view_layout) {
|
||||
None => Err(FlowyError::internal().context(format!(
|
||||
None => Err(FlowyError::internal().with_context(format!(
|
||||
"Get data processor failed. Unknown layout type: {:?}",
|
||||
view_layout
|
||||
))),
|
||||
@ -871,7 +934,9 @@ impl FolderManager {
|
||||
/// Otherwise, the parent_view_id is the parent view id of the view. The child_view_ids is the
|
||||
/// child view ids of the view.
|
||||
async fn get_view_relation(&self, view_id: &str) -> Option<(bool, String, Vec<String>)> {
|
||||
self.with_folder(None, |folder| {
|
||||
self.with_folder(
|
||||
|| None,
|
||||
|folder| {
|
||||
let view = folder.views.get_view(view_id)?;
|
||||
match folder.views.get_view(&view.parent_view_id) {
|
||||
None => folder.get_current_workspace().map(|workspace| {
|
||||
@ -898,7 +963,8 @@ impl FolderManager {
|
||||
.collect::<Vec<String>>(),
|
||||
)),
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn get_folder_snapshots(
|
||||
@ -1158,7 +1224,7 @@ fn notify_child_views_changed(view_pb: ViewPB, reason: ChildViewChangeReason) {
|
||||
}
|
||||
|
||||
fn folder_not_init_error() -> FlowyError {
|
||||
FlowyError::internal().context("Folder not initialized")
|
||||
FlowyError::internal().with_context("Folder not initialized")
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
@ -1190,3 +1256,10 @@ fn is_exist_in_local_disk(user: &Arc<dyn FolderUser>, doc_id: &str) -> FlowyResu
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn workspace_data_not_sync_error(uid: i64, workspace_id: &str) -> FlowyError {
|
||||
FlowyError::from(ErrorCode::WorkspaceDataNotSync).with_payload(UserFolderPB {
|
||||
uid,
|
||||
workspace_id: workspace_id.to_string(),
|
||||
})
|
||||
}
|
||||
|
@ -118,6 +118,10 @@ impl UserService for LocalServerUserAuthServiceImpl {
|
||||
FutureResult::new(async { Ok(vec![]) })
|
||||
}
|
||||
|
||||
fn reset_workspace(&self, _collab_object: CollabObject) -> FutureResult<(), Error> {
|
||||
FutureResult::new(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn create_collab_object(
|
||||
&self,
|
||||
_collab_object: &CollabObject,
|
||||
|
@ -159,7 +159,7 @@ impl HttpRequestBuilder {
|
||||
|
||||
fn unexpected_empty_payload(url: &str) -> FlowyError {
|
||||
let msg = format!("Request: {} receives unexpected empty payload", url);
|
||||
FlowyError::payload_none().context(msg)
|
||||
FlowyError::payload_none().with_context(msg)
|
||||
}
|
||||
|
||||
async fn flowy_response_from(original: Response) -> Result<HttpResponse, FlowyError> {
|
||||
@ -178,7 +178,7 @@ async fn get_response_data(original: Response) -> Result<Bytes, FlowyError> {
|
||||
Some(error) => Err(FlowyError::new(error.code, &error.msg)),
|
||||
}
|
||||
} else {
|
||||
Err(FlowyError::http().context(original))
|
||||
Err(FlowyError::http().with_context(original))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,6 +129,11 @@ impl UserService for SelfHostedUserAuthServiceImpl {
|
||||
FutureResult::new(async { Ok(vec![]) })
|
||||
}
|
||||
|
||||
fn reset_workspace(&self, _collab_object: CollabObject) -> FutureResult<(), Error> {
|
||||
// TODO(nathan): implement the RESTful API for this
|
||||
FutureResult::new(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn create_collab_object(
|
||||
&self,
|
||||
_collab_object: &CollabObject,
|
||||
|
@ -161,8 +161,7 @@ where
|
||||
.get_workspace_id()
|
||||
.ok_or(anyhow::anyhow!("Invalid workspace id"))?;
|
||||
|
||||
let update_items =
|
||||
get_updates_from_server(&object.object_id, &object.ty, postgrest.clone()).await?;
|
||||
let update_items = get_updates_from_server(&object.object_id, &object.ty, &postgrest).await?;
|
||||
|
||||
// If the update_items is empty, we can send the init_update directly
|
||||
if update_items.is_empty() {
|
||||
@ -175,14 +174,42 @@ where
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
flush_collab_with_update(object, update_items, &postgrest, init_update, self.secret())
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn subscribe_remote_updates(&self, _object: &CollabObject) -> Option<RemoteUpdateReceiver> {
|
||||
let rx = self.rx.lock().take();
|
||||
if rx.is_none() {
|
||||
tracing::warn!("The receiver is already taken");
|
||||
}
|
||||
rx
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn flush_collab_with_update(
|
||||
object: &CollabObject,
|
||||
update_items: Vec<UpdateItem>,
|
||||
postgrest: &Arc<PostgresWrapper>,
|
||||
update: Vec<u8>,
|
||||
secret: Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
// 2.Merge the updates into one and then delete the merged updates
|
||||
let merge_result = spawn_blocking(move || merge_updates(update_items, init_update)).await??;
|
||||
let merge_result = spawn_blocking(move || merge_updates(update_items, update)).await??;
|
||||
tracing::trace!("Merged updates count: {}", merge_result.merged_keys.len());
|
||||
|
||||
let workspace_id = object
|
||||
.get_workspace_id()
|
||||
.ok_or(anyhow::anyhow!("Invalid workspace id"))?;
|
||||
|
||||
let value_size = merge_result.new_update.len() as i32;
|
||||
let md5 = md5(&merge_result.new_update);
|
||||
|
||||
tracing::trace!("Flush collab id:{} type:{}", object.object_id, object.ty);
|
||||
let (new_update, encrypt) =
|
||||
SupabaseBinaryColumnEncoder::encode(merge_result.new_update, &self.secret())?;
|
||||
SupabaseBinaryColumnEncoder::encode(merge_result.new_update, &secret)?;
|
||||
let params = InsertParamsBuilder::new()
|
||||
.insert("oid", object.object_id.clone())
|
||||
.insert("new_value", new_update)
|
||||
@ -202,19 +229,9 @@ where
|
||||
.await?
|
||||
.success()
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn subscribe_remote_updates(&self, _object: &CollabObject) -> Option<RemoteUpdateReceiver> {
|
||||
let rx = self.rx.lock().take();
|
||||
if rx.is_none() {
|
||||
tracing::warn!("The receiver is already taken");
|
||||
}
|
||||
rx
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn send_update(
|
||||
workspace_id: String,
|
||||
object: &CollabObject,
|
||||
|
@ -72,7 +72,7 @@ where
|
||||
let workspace_id = workspace_id.to_string();
|
||||
FutureResult::new(async move {
|
||||
let postgrest = try_get_postgrest?;
|
||||
let updates = get_updates_from_server(&workspace_id, &CollabType::Folder, postgrest).await?;
|
||||
let updates = get_updates_from_server(&workspace_id, &CollabType::Folder, &postgrest).await?;
|
||||
let updates = updates
|
||||
.into_iter()
|
||||
.map(|item| item.value)
|
||||
|
@ -66,12 +66,14 @@ impl Action for FetchObjectUpdateAction {
|
||||
Box::pin(async move {
|
||||
match weak_postgres.upgrade() {
|
||||
None => Ok(vec![]),
|
||||
Some(postgrest) => match get_updates_from_server(&object_id, &object_ty, postgrest).await {
|
||||
Some(postgrest) => {
|
||||
match get_updates_from_server(&object_id, &object_ty, &postgrest).await {
|
||||
Ok(items) => Ok(items.into_iter().map(|item| item.value).collect()),
|
||||
Err(err) => {
|
||||
tracing::error!("Get {} updates failed with error: {:?}", object_id, err);
|
||||
Err(err)
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
@ -285,7 +287,7 @@ pub async fn batch_get_updates_from_server(
|
||||
pub async fn get_updates_from_server(
|
||||
object_id: &str,
|
||||
object_ty: &CollabType,
|
||||
postgrest: Arc<PostgresWrapper>,
|
||||
postgrest: &Arc<PostgresWrapper>,
|
||||
) -> Result<Vec<UpdateItem>, Error> {
|
||||
let json = postgrest
|
||||
.from(table_name(object_ty))
|
||||
|
@ -2,23 +2,29 @@ use std::str::FromStr;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use anyhow::Error;
|
||||
use collab::core::collab::MutexCollab;
|
||||
use collab::core::origin::CollabOrigin;
|
||||
use collab_plugins::cloud_storage::CollabObject;
|
||||
use parking_lot::RwLock;
|
||||
use serde_json::Value;
|
||||
use tokio::sync::oneshot::channel;
|
||||
use uuid::Uuid;
|
||||
|
||||
use flowy_folder_deps::cloud::{Folder, Workspace};
|
||||
use flowy_user_deps::cloud::*;
|
||||
use flowy_user_deps::entities::*;
|
||||
use flowy_user_deps::DEFAULT_USER_NAME;
|
||||
use lib_infra::box_any::BoxAny;
|
||||
use lib_infra::future::FutureResult;
|
||||
use lib_infra::util::timestamp;
|
||||
|
||||
use crate::supabase::api::request::FetchObjectUpdateAction;
|
||||
use crate::supabase::api::request::{get_updates_from_server, FetchObjectUpdateAction};
|
||||
use crate::supabase::api::util::{
|
||||
ExtendedResponse, InsertParamsBuilder, RealtimeBinaryColumnDecoder, SupabaseBinaryColumnDecoder,
|
||||
};
|
||||
use crate::supabase::api::{send_update, PostgresWrapper, SupabaseServerService};
|
||||
use crate::supabase::api::{
|
||||
flush_collab_with_update, send_update, PostgresWrapper, SupabaseServerService,
|
||||
};
|
||||
use crate::supabase::define::*;
|
||||
use crate::supabase::entities::UserProfileResponse;
|
||||
use crate::supabase::entities::{GetUserProfileParams, RealtimeUserEvent};
|
||||
@ -266,6 +272,39 @@ where
|
||||
self.user_update_tx.as_ref().map(|tx| tx.subscribe())
|
||||
}
|
||||
|
||||
fn reset_workspace(&self, collab_object: CollabObject) -> FutureResult<(), Error> {
|
||||
let collab_object = collab_object.clone();
|
||||
|
||||
let try_get_postgrest = self.server.try_get_weak_postgrest();
|
||||
let (tx, rx) = channel();
|
||||
let init_update = empty_workspace_update(&collab_object);
|
||||
tokio::spawn(async move {
|
||||
tx.send(
|
||||
async move {
|
||||
let postgrest = try_get_postgrest?
|
||||
.upgrade()
|
||||
.ok_or(anyhow::anyhow!("postgrest is not available"))?;
|
||||
|
||||
let updates =
|
||||
get_updates_from_server(&collab_object.object_id, &collab_object.ty, &postgrest)
|
||||
.await?;
|
||||
|
||||
flush_collab_with_update(
|
||||
&collab_object,
|
||||
updates,
|
||||
&postgrest,
|
||||
init_update,
|
||||
postgrest.secret(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
.await,
|
||||
)
|
||||
});
|
||||
FutureResult::new(async { rx.await? })
|
||||
}
|
||||
|
||||
fn create_collab_object(
|
||||
&self,
|
||||
collab_object: &CollabObject,
|
||||
@ -516,3 +555,21 @@ impl RealtimeEventHandler for RealtimeCollabUpdateHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_workspace_update(collab_object: &CollabObject) -> Vec<u8> {
|
||||
let workspace_id = collab_object.object_id.clone();
|
||||
let collab = Arc::new(MutexCollab::new(
|
||||
CollabOrigin::Empty,
|
||||
&collab_object.object_id,
|
||||
vec![],
|
||||
));
|
||||
let folder = Folder::create(collab.clone(), None, None);
|
||||
folder.workspaces.create_workspace(Workspace {
|
||||
id: workspace_id.clone(),
|
||||
name: "My workspace".to_string(),
|
||||
child_views: Default::default(),
|
||||
created_at: timestamp(),
|
||||
});
|
||||
folder.set_current_workspace(&workspace_id);
|
||||
collab.encode_as_update_v1().0
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ async fn delete_view_event_test() {
|
||||
.await
|
||||
.error()
|
||||
.unwrap();
|
||||
assert_eq!(error.code, ErrorCode::RecordNotFound.value());
|
||||
assert_eq!(error.code, ErrorCode::RecordNotFound);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -150,7 +150,7 @@ async fn put_back_trash_event_test() {
|
||||
.await
|
||||
.error()
|
||||
.unwrap();
|
||||
assert_eq!(error.code, ErrorCode::RecordNotFound.value());
|
||||
assert_eq!(error.code, ErrorCode::RecordNotFound);
|
||||
|
||||
let payload = TrashIdPB {
|
||||
id: view.id.clone(),
|
||||
@ -480,7 +480,7 @@ async fn create_parent_view_with_invalid_name() {
|
||||
.error()
|
||||
.unwrap()
|
||||
.code,
|
||||
code.value()
|
||||
code
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ async fn sign_up_with_invalid_email() {
|
||||
.error()
|
||||
.unwrap()
|
||||
.code,
|
||||
ErrorCode::EmailFormatInvalid.value()
|
||||
ErrorCode::EmailFormatInvalid
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -51,7 +51,7 @@ async fn sign_up_with_long_password() {
|
||||
.error()
|
||||
.unwrap()
|
||||
.code,
|
||||
ErrorCode::PasswordTooLong.value()
|
||||
ErrorCode::PasswordTooLong
|
||||
);
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ async fn sign_in_with_invalid_email() {
|
||||
.error()
|
||||
.unwrap()
|
||||
.code,
|
||||
ErrorCode::EmailFormatInvalid.value()
|
||||
ErrorCode::EmailFormatInvalid
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ async fn user_update_with_invalid_email() {
|
||||
.error()
|
||||
.unwrap()
|
||||
.code,
|
||||
ErrorCode::EmailFormatInvalid.value()
|
||||
ErrorCode::EmailFormatInvalid
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ async fn third_party_sign_up_with_duplicated_email() {
|
||||
.await
|
||||
.err()
|
||||
.unwrap();
|
||||
assert_eq!(error.code, ErrorCode::Conflict.value());
|
||||
assert_eq!(error.code, ErrorCode::Conflict);
|
||||
};
|
||||
}
|
||||
|
||||
@ -198,7 +198,7 @@ async fn check_not_exist_user_test() {
|
||||
.check_user_with_uuid(&uuid::Uuid::new_v4().to_string())
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(err.code, ErrorCode::RecordNotFound.value());
|
||||
assert_eq!(err.code, ErrorCode::RecordNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,6 +256,6 @@ async fn update_user_profile_with_existing_email_test() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(error.code, ErrorCode::Conflict.value());
|
||||
assert_eq!(error.code, ErrorCode::Conflict);
|
||||
}
|
||||
}
|
||||
|
@ -110,6 +110,8 @@ pub trait UserService: Send + Sync {
|
||||
None
|
||||
}
|
||||
|
||||
fn reset_workspace(&self, collab_object: CollabObject) -> FutureResult<(), Error>;
|
||||
|
||||
fn create_collab_object(
|
||||
&self,
|
||||
collab_object: &CollabObject,
|
||||
|
@ -48,6 +48,9 @@ pub struct UserProfilePB {
|
||||
|
||||
#[pb(index = 9)]
|
||||
pub encryption_type: EncryptionTypePB,
|
||||
|
||||
#[pb(index = 10)]
|
||||
pub workspace_id: String,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf_Enum, Eq, PartialEq, Debug, Clone)]
|
||||
@ -78,6 +81,7 @@ impl std::convert::From<UserProfile> for UserProfilePB {
|
||||
auth_type: user_profile.auth_type.into(),
|
||||
encryption_sign,
|
||||
encryption_type: encryption_ty,
|
||||
workspace_id: user_profile.workspace_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -274,3 +278,12 @@ impl From<HistoricalUser> for HistoricalUserPB {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default, Clone)]
|
||||
pub struct ResetWorkspacePB {
|
||||
#[pb(index = 1)]
|
||||
pub uid: i64,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub workspace_id: String,
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use std::{convert::TryInto, sync::Arc};
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_sqlite::kv::StorePreferences;
|
||||
use flowy_user_deps::cloud::UserCloudConfig;
|
||||
use flowy_user_deps::entities::*;
|
||||
@ -20,7 +20,7 @@ use crate::services::cloud_config::{
|
||||
fn upgrade_manager(manager: AFPluginState<Weak<UserManager>>) -> FlowyResult<Arc<UserManager>> {
|
||||
let manager = manager
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::internal().context("The user session is already drop"))?;
|
||||
.ok_or(FlowyError::internal().with_context("The user session is already drop"))?;
|
||||
Ok(manager)
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ fn upgrade_store_preferences(
|
||||
) -> FlowyResult<Arc<StorePreferences>> {
|
||||
let store = store
|
||||
.upgrade()
|
||||
.ok_or(FlowyError::internal().context("The store preferences is already drop"))?;
|
||||
.ok_or(FlowyError::internal().with_context("The store preferences is already drop"))?;
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
@ -96,7 +96,15 @@ pub async fn get_user_profile_handler(
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let uid = manager.get_session()?.user_id;
|
||||
let user_profile = manager.get_user_profile(uid).await?;
|
||||
let _ = manager.refresh_user_profile(&user_profile).await;
|
||||
|
||||
let weak_manager = Arc::downgrade(&manager);
|
||||
let cloned_user_profile = user_profile.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Some(manager) = weak_manager.upgrade() {
|
||||
let _ = manager.refresh_user_profile(&cloned_user_profile).await;
|
||||
}
|
||||
});
|
||||
|
||||
data_result_ok(user_profile.into())
|
||||
}
|
||||
|
||||
@ -250,7 +258,7 @@ pub async fn set_cloud_config_handler(
|
||||
let update = data.into_inner();
|
||||
let store_preferences = upgrade_store_preferences(store_preferences)?;
|
||||
let mut config = get_cloud_config(session.user_id, &store_preferences)
|
||||
.ok_or(FlowyError::internal().context("Can't find any cloud config"))?;
|
||||
.ok_or(FlowyError::internal().with_context("Can't find any cloud config"))?;
|
||||
|
||||
if let Some(enable_sync) = update.enable_sync {
|
||||
manager.cloud_services.set_enable_sync(enable_sync);
|
||||
@ -429,3 +437,20 @@ pub async fn get_all_reminder_event_handler(
|
||||
.collect::<Vec<_>>();
|
||||
data_result_ok(reminders.into())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub async fn reset_workspace_handler(
|
||||
data: AFPluginData<ResetWorkspacePB>,
|
||||
manager: AFPluginState<Weak<UserManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let reset_pb = data.into_inner();
|
||||
if reset_pb.workspace_id.is_empty() {
|
||||
return Err(FlowyError::new(
|
||||
ErrorCode::WorkspaceIdInvalid,
|
||||
"The workspace id is empty",
|
||||
));
|
||||
}
|
||||
manager.reset_workspace(reset_pb).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ pub fn init(user_session: Weak<UserManager>) -> AFPlugin {
|
||||
.event(UserEvent::PushRealtimeEvent, push_realtime_event_handler)
|
||||
.event(UserEvent::CreateReminder, create_reminder_event_handler)
|
||||
.event(UserEvent::GetAllReminders, get_all_reminder_event_handler)
|
||||
.event(UserEvent::ResetWorkspace, reset_workspace_handler)
|
||||
}
|
||||
|
||||
pub struct SignUpContext {
|
||||
@ -271,4 +272,7 @@ pub enum UserEvent {
|
||||
|
||||
#[event(output = "RepeatedReminderPB")]
|
||||
GetAllReminders = 29,
|
||||
|
||||
#[event(input = "ResetWorkspacePB")]
|
||||
ResetWorkspace = 30,
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ pub fn open_user_db(root: &str, user_id: i64) -> Result<Arc<ConnectionPool>, Flo
|
||||
let dir = user_db_path_from_uid(root, user_id);
|
||||
tracing::debug!("open sqlite db {} at path: {:?}", user_id, dir);
|
||||
let db = flowy_sqlite::init(&dir)
|
||||
.map_err(|e| FlowyError::internal().context(format!("open user db failed, {:?}", e)))?;
|
||||
.map_err(|e| FlowyError::internal().with_context(format!("open user db failed, {:?}", e)))?;
|
||||
let pool = db.get_pool();
|
||||
write_guard.insert(user_id.to_owned(), db);
|
||||
drop(write_guard);
|
||||
|
@ -1,12 +1,14 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::Arc;
|
||||
|
||||
use appflowy_integrate::{CollabObject, CollabType};
|
||||
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_sqlite::schema::user_workspace_table;
|
||||
use flowy_sqlite::{query_dsl::*, ConnectionPool, ExpressionMethods};
|
||||
use flowy_user_deps::entities::UserWorkspace;
|
||||
|
||||
use crate::entities::RepeatedUserWorkspacePB;
|
||||
use crate::entities::{RepeatedUserWorkspacePB, ResetWorkspacePB};
|
||||
use crate::manager::UserManager;
|
||||
use crate::notification::{send_notification, UserNotification};
|
||||
use crate::services::user_workspace_sql::UserWorkspaceTable;
|
||||
@ -84,6 +86,20 @@ impl UserManager {
|
||||
}
|
||||
Ok(rows.into_iter().map(UserWorkspace::from).collect())
|
||||
}
|
||||
|
||||
/// Reset the remote workspace using local workspace data. This is useful when a user wishes to
|
||||
/// open a workspace on a new device that hasn't fully synchronized with the server.
|
||||
pub async fn reset_workspace(&self, reset: ResetWorkspacePB) -> FlowyResult<()> {
|
||||
let collab_object =
|
||||
CollabObject::new(reset.uid, reset.workspace_id.clone(), CollabType::Folder)
|
||||
.with_workspace_id(reset.workspace_id);
|
||||
self
|
||||
.cloud_services
|
||||
.get_user_service()?
|
||||
.reset_workspace(collab_object)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_user_workspaces(
|
||||
|
@ -21,10 +21,10 @@ impl TryFrom<(i64, &UserWorkspace)> for UserWorkspaceTable {
|
||||
|
||||
fn try_from(value: (i64, &UserWorkspace)) -> Result<Self, Self::Error> {
|
||||
if value.1.id.is_empty() {
|
||||
return Err(FlowyError::invalid_data().context("The id is empty"));
|
||||
return Err(FlowyError::invalid_data().with_context("The id is empty"));
|
||||
}
|
||||
if value.1.database_storage_id.is_empty() {
|
||||
return Err(FlowyError::invalid_data().context("The database storage id is empty"));
|
||||
return Err(FlowyError::invalid_data().with_context("The database storage id is empty"));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
|
@ -28,6 +28,16 @@ pub fn make_se_token_stream(ast_result: &ASTResult, ast: &ASTContainer) -> Optio
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryInto<Vec<u8>> for #struct_ident {
|
||||
type Error = ::protobuf::ProtobufError;
|
||||
fn try_into(self) -> Result<Vec<u8>, Self::Error> {
|
||||
use protobuf::Message;
|
||||
let pb: crate::protobuf::#pb_ty = self.into();
|
||||
let bytes = pb.write_to_bytes()?;
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<#struct_ident> for crate::protobuf::#pb_ty {
|
||||
fn from(mut o: #struct_ident) -> crate::protobuf::#pb_ty {
|
||||
let mut pb = crate::protobuf::#pb_ty::new();
|
||||
|
Loading…
Reference in New Issue
Block a user