[client]: fix restore & delete all bugs

This commit is contained in:
appflowy 2021-10-18 18:40:15 +08:00
parent 4f0f4221fc
commit 3f83e7ee06
30 changed files with 321 additions and 220 deletions

View File

@ -13,8 +13,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
part 'menu_bloc.freezed.dart'; part 'menu_bloc.freezed.dart';
class MenuBloc extends Bloc<MenuEvent, MenuState> { class MenuBloc extends Bloc<MenuEvent, MenuState> {
final IWorkspace workspace; final IWorkspace workspaceManager;
MenuBloc(this.workspace) : super(MenuState.initial()); MenuBloc(this.workspaceManager) : super(MenuState.initial());
@override @override
Stream<MenuState> mapEventToState( Stream<MenuState> mapEventToState(
@ -42,7 +42,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
} }
Stream<MenuState> _performActionOnCreateApp(CreateApp event) async* { Stream<MenuState> _performActionOnCreateApp(CreateApp event) async* {
final result = await workspace.createApp(name: event.name, desc: event.desc); final result = await workspaceManager.createApp(name: event.name, desc: event.desc);
yield result.fold( yield result.fold(
(app) => state.copyWith(apps: some([app])), (app) => state.copyWith(apps: some([app])),
(error) { (error) {
@ -54,7 +54,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
// ignore: unused_element // ignore: unused_element
Stream<MenuState> _fetchApps() async* { Stream<MenuState> _fetchApps() async* {
final appsOrFail = await workspace.getApps(); final appsOrFail = await workspaceManager.getApps();
yield appsOrFail.fold( yield appsOrFail.fold(
(apps) => state.copyWith(apps: some(apps)), (apps) => state.copyWith(apps: some(apps)),
(error) { (error) {

View File

@ -10,10 +10,10 @@ import 'package:dartz/dartz.dart';
part 'menu_user_bloc.freezed.dart'; part 'menu_user_bloc.freezed.dart';
class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> { class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
final IUser iUserImpl; final IUser userManager;
final IUserListener listener; final IUserListener listener;
MenuUserBloc(this.iUserImpl, this.listener) : super(MenuUserState.initial(iUserImpl.user)); MenuUserBloc(this.userManager, this.listener) : super(MenuUserState.initial(userManager.user));
@override @override
Stream<MenuUserState> mapEventToState(MenuUserEvent event) async* { Stream<MenuUserState> mapEventToState(MenuUserEvent event) async* {
@ -36,7 +36,7 @@ class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
} }
Future<void> _initUser() async { Future<void> _initUser() async {
final result = await iUserImpl.initUser(); final result = await userManager.initUser();
result.fold((l) => null, (error) => Log.error(error)); result.fold((l) => null, (error) => Log.error(error));
} }

View File

@ -8,16 +8,16 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'trash_bloc.freezed.dart'; part 'trash_bloc.freezed.dart';
class TrashBloc extends Bloc<TrashEvent, TrashState> { class TrashBloc extends Bloc<TrashEvent, TrashState> {
final ITrash iTrash; final ITrash trasnManager;
final ITrashListener listener; final ITrashListener listener;
TrashBloc({required this.iTrash, required this.listener}) : super(TrashState.init()); TrashBloc({required this.trasnManager, required this.listener}) : super(TrashState.init());
@override @override
Stream<TrashState> mapEventToState(TrashEvent event) async* { Stream<TrashState> mapEventToState(TrashEvent event) async* {
yield* event.map( yield* event.map(
initial: (e) async* { initial: (e) async* {
listener.start(_listenTrashUpdated); listener.start(_listenTrashUpdated);
final result = await iTrash.readTrash(); final result = await trasnManager.readTrash();
yield result.fold( yield result.fold(
(objects) => state.copyWith(objects: objects, successOrFailure: left(unit)), (objects) => state.copyWith(objects: objects, successOrFailure: left(unit)),
(error) => state.copyWith(successOrFailure: right(error)), (error) => state.copyWith(successOrFailure: right(error)),
@ -27,18 +27,21 @@ class TrashBloc extends Bloc<TrashEvent, TrashState> {
yield state.copyWith(objects: e.trash); yield state.copyWith(objects: e.trash);
}, },
putback: (e) async* { putback: (e) async* {
final result = await iTrash.putback(e.trashId); final result = await trasnManager.putback(e.trashId);
yield* _handleResult(result); yield* _handleResult(result);
}, },
delete: (e) async* { delete: (e) async* {
final result = await iTrash.deleteViews([e.trashId]); final result = await trasnManager.deleteViews([e.trashId]);
yield* _handleResult(result); yield* _handleResult(result);
}, },
deleteAll: (e) async* { deleteAll: (e) async* {
final result = await iTrash.deleteAll(); final result = await trasnManager.deleteAll();
yield* _handleResult(result);
},
restoreAll: (e) async* {
final result = await trasnManager.restoreAll();
yield* _handleResult(result); yield* _handleResult(result);
}, },
restoreAll: (e) async* {},
); );
} }

View File

@ -8,13 +8,13 @@ import 'package:app_flowy/workspace/domain/i_view.dart';
part 'view_bloc.freezed.dart'; part 'view_bloc.freezed.dart';
class ViewBloc extends Bloc<ViewEvent, ViewState> { class ViewBloc extends Bloc<ViewEvent, ViewState> {
final IView iViewImpl; final IView viewManager;
final IViewListener listener; final IViewListener listener;
ViewBloc({ ViewBloc({
required this.iViewImpl, required this.viewManager,
required this.listener, required this.listener,
}) : super(ViewState.init(iViewImpl.view)); }) : super(ViewState.init(viewManager.view));
@override @override
Stream<ViewState> mapEventToState(ViewEvent event) async* { Stream<ViewState> mapEventToState(ViewEvent event) async* {
@ -26,13 +26,13 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
}, viewDidUpdate: (e) async* { }, viewDidUpdate: (e) async* {
yield* _handleViewDidUpdate(e.result); yield* _handleViewDidUpdate(e.result);
}, rename: (e) async* { }, rename: (e) async* {
final result = await iViewImpl.rename(e.newName); final result = await viewManager.rename(e.newName);
yield result.fold( yield result.fold(
(l) => state.copyWith(successOrFailure: left(unit)), (l) => state.copyWith(successOrFailure: left(unit)),
(error) => state.copyWith(successOrFailure: right(error)), (error) => state.copyWith(successOrFailure: right(error)),
); );
}, delete: (e) async* { }, delete: (e) async* {
final result = await iViewImpl.delete(); final result = await viewManager.delete();
yield result.fold( yield result.fold(
(l) => state.copyWith(successOrFailure: left(unit)), (l) => state.copyWith(successOrFailure: left(unit)),
(error) => state.copyWith(successOrFailure: right(error)), (error) => state.copyWith(successOrFailure: right(error)),

View File

@ -11,16 +11,16 @@ part 'welcome_bloc.freezed.dart';
class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> { class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
final UserRepo repo; final UserRepo repo;
final IUserListener watch; final IUserListener listener;
WelcomeBloc({required this.repo, required this.watch}) : super(WelcomeState.initial()); WelcomeBloc({required this.repo, required this.listener}) : super(WelcomeState.initial());
@override @override
Stream<WelcomeState> mapEventToState( Stream<WelcomeState> mapEventToState(
WelcomeEvent event, WelcomeEvent event,
) async* { ) async* {
yield* event.map(initial: (e) async* { yield* event.map(initial: (e) async* {
watch.setWorkspacesCallback(_workspacesUpdated); listener.setWorkspacesCallback(_workspacesUpdated);
watch.start(); listener.start();
// //
yield* _fetchWorkspaces(); yield* _fetchWorkspaces();
}, openWorkspace: (e) async* { }, openWorkspace: (e) async* {
@ -37,7 +37,7 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
@override @override
Future<void> close() async { Future<void> close() async {
await watch.stop(); await listener.stop();
super.close(); super.close();
} }
@ -67,7 +67,6 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
final result = await repo.createWorkspace(name, desc); final result = await repo.createWorkspace(name, desc);
yield result.fold( yield result.fold(
(workspace) { (workspace) {
// add(const WelcomeEvent.fetchWorkspaces());
return state.copyWith(successOrFailure: left(unit)); return state.copyWith(successOrFailure: left(unit));
}, },
(error) { (error) {
@ -88,7 +87,6 @@ class WelcomeEvent with _$WelcomeEvent {
// const factory WelcomeEvent.fetchWorkspaces() = FetchWorkspace; // const factory WelcomeEvent.fetchWorkspaces() = FetchWorkspace;
const factory WelcomeEvent.createWorkspace(String name, String desc) = CreateWorkspace; const factory WelcomeEvent.createWorkspace(String name, String desc) = CreateWorkspace;
const factory WelcomeEvent.openWorkspace(Workspace workspace) = OpenWorkspace; const factory WelcomeEvent.openWorkspace(Workspace workspace) = OpenWorkspace;
const factory WelcomeEvent.workspacesReveived(Either<List<Workspace>, WorkspaceError> workspacesOrFail) = const factory WelcomeEvent.workspacesReveived(Either<List<Workspace>, WorkspaceError> workspacesOrFail) =
WorkspacesReceived; WorkspacesReceived;
} }

View File

@ -34,7 +34,7 @@ class HomeDepsResolver {
getIt.registerFactoryParam<WelcomeBloc, UserProfile, void>( getIt.registerFactoryParam<WelcomeBloc, UserProfile, void>(
(user, _) => WelcomeBloc( (user, _) => WelcomeBloc(
repo: UserRepo(user: user), repo: UserRepo(user: user),
watch: getIt<IUserListener>(param1: user), listener: getIt<IUserListener>(param1: user),
), ),
); );

View File

@ -53,21 +53,27 @@ class _TrashStackPageState extends State<TrashStackPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = context.watch<AppTheme>(); final theme = context.watch<AppTheme>();
const horizontalPadding = 80.0; const horizontalPadding = 80.0;
return SizedBox.expand( return BlocProvider(
child: Column( create: (context) => getIt<TrashBloc>()..add(const TrashEvent.initial()),
children: [ child: BlocBuilder<TrashBloc, TrashState>(
_renderTopBar(theme), builder: (context, state) {
const VSpace(32), return SizedBox.expand(
_renderTrashList(context), child: Column(
], children: [
mainAxisAlignment: MainAxisAlignment.start, _renderTopBar(context, theme, state),
).padding(horizontal: horizontalPadding, vertical: 48), const VSpace(32),
_renderTrashList(context, state),
],
mainAxisAlignment: MainAxisAlignment.start,
).padding(horizontal: horizontalPadding, vertical: 48),
);
},
),
); );
} }
Widget _renderTrashList(BuildContext context) { Widget _renderTrashList(BuildContext context, TrashState state) {
const barSize = 6.0; const barSize = 6.0;
return Expanded( return Expanded(
child: ScrollbarListStack( child: ScrollbarListStack(
axis: Axis.vertical, axis: Axis.vertical,
@ -86,8 +92,8 @@ class _TrashStackPageState extends State<TrashStackPage> {
physics: StyledScrollPhysics(), physics: StyledScrollPhysics(),
controller: _scrollController, controller: _scrollController,
slivers: [ slivers: [
_renderListHeader(context), _renderListHeader(context, state),
_renderListBody(context), _renderListBody(context, state),
], ],
), ),
), ),
@ -97,7 +103,7 @@ class _TrashStackPageState extends State<TrashStackPage> {
); );
} }
Widget _renderTopBar(AppTheme theme) { Widget _renderTopBar(BuildContext context, AppTheme theme, TrashState state) {
return SizedBox( return SizedBox(
height: 36, height: 36,
child: Row( child: Row(
@ -128,7 +134,7 @@ class _TrashStackPageState extends State<TrashStackPage> {
); );
} }
Widget _renderListHeader(BuildContext context) { Widget _renderListHeader(BuildContext context, TrashState state) {
return SliverPersistentHeader( return SliverPersistentHeader(
delegate: TrashHeaderDelegate(), delegate: TrashHeaderDelegate(),
floating: true, floating: true,
@ -136,31 +142,24 @@ class _TrashStackPageState extends State<TrashStackPage> {
); );
} }
Widget _renderListBody(BuildContext context) { Widget _renderListBody(BuildContext context, TrashState state) {
return BlocProvider( return SliverList(
create: (context) => getIt<TrashBloc>()..add(const TrashEvent.initial()), delegate: SliverChildBuilderDelegate(
child: BlocBuilder<TrashBloc, TrashState>( (BuildContext context, int index) {
builder: (context, state) { final object = state.objects[index];
return SliverList( return SizedBox(
delegate: SliverChildBuilderDelegate( height: 42,
(BuildContext context, int index) { child: TrashCell(
final object = state.objects[index]; object: object,
return SizedBox( onRestore: () {
height: 42, context.read<TrashBloc>().add(TrashEvent.putback(object.id));
child: TrashCell(
object: object,
onRestore: () {
context.read<TrashBloc>().add(TrashEvent.putback(object.id));
},
onDelete: () => context.read<TrashBloc>().add(TrashEvent.delete(object.id)),
),
);
}, },
childCount: state.objects.length, onDelete: () => context.read<TrashBloc>().add(TrashEvent.delete(object.id)),
addAutomaticKeepAlives: false,
), ),
); );
}, },
childCount: state.objects.length,
addAutomaticKeepAlives: false,
), ),
); );
} }

View File

@ -17,17 +17,22 @@ export 'trash_create.pbenum.dart';
class TrashIdentifiers extends $pb.GeneratedMessage { class TrashIdentifiers extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'TrashIdentifiers', createEmptyInstance: create) static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'TrashIdentifiers', createEmptyInstance: create)
..pc<TrashIdentifier>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: TrashIdentifier.create) ..pc<TrashIdentifier>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: TrashIdentifier.create)
..aOB(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'deleteAll')
..hasRequiredFields = false ..hasRequiredFields = false
; ;
TrashIdentifiers._() : super(); TrashIdentifiers._() : super();
factory TrashIdentifiers({ factory TrashIdentifiers({
$core.Iterable<TrashIdentifier>? items, $core.Iterable<TrashIdentifier>? items,
$core.bool? deleteAll,
}) { }) {
final _result = create(); final _result = create();
if (items != null) { if (items != null) {
_result.items.addAll(items); _result.items.addAll(items);
} }
if (deleteAll != null) {
_result.deleteAll = deleteAll;
}
return _result; return _result;
} }
factory TrashIdentifiers.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); factory TrashIdentifiers.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@ -53,6 +58,15 @@ class TrashIdentifiers extends $pb.GeneratedMessage {
@$pb.TagNumber(1) @$pb.TagNumber(1)
$core.List<TrashIdentifier> get items => $_getList(0); $core.List<TrashIdentifier> get items => $_getList(0);
@$pb.TagNumber(2)
$core.bool get deleteAll => $_getBF(1);
@$pb.TagNumber(2)
set deleteAll($core.bool v) { $_setBool(1, v); }
@$pb.TagNumber(2)
$core.bool hasDeleteAll() => $_has(1);
@$pb.TagNumber(2)
void clearDeleteAll() => clearField(2);
} }
class TrashIdentifier extends $pb.GeneratedMessage { class TrashIdentifier extends $pb.GeneratedMessage {

View File

@ -24,11 +24,12 @@ const TrashIdentifiers$json = const {
'1': 'TrashIdentifiers', '1': 'TrashIdentifiers',
'2': const [ '2': const [
const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.TrashIdentifier', '10': 'items'}, const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.TrashIdentifier', '10': 'items'},
const {'1': 'delete_all', '3': 2, '4': 1, '5': 8, '10': 'deleteAll'},
], ],
}; };
/// Descriptor for `TrashIdentifiers`. Decode as a `google.protobuf.DescriptorProto`. /// Descriptor for `TrashIdentifiers`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List trashIdentifiersDescriptor = $convert.base64Decode('ChBUcmFzaElkZW50aWZpZXJzEiYKBWl0ZW1zGAEgAygLMhAuVHJhc2hJZGVudGlmaWVyUgVpdGVtcw=='); final $typed_data.Uint8List trashIdentifiersDescriptor = $convert.base64Decode('ChBUcmFzaElkZW50aWZpZXJzEiYKBWl0ZW1zGAEgAygLMhAuVHJhc2hJZGVudGlmaWVyUgVpdGVtcxIdCgpkZWxldGVfYWxsGAIgASgIUglkZWxldGVBbGw=');
@$core.Deprecated('Use trashIdentifierDescriptor instead') @$core.Deprecated('Use trashIdentifierDescriptor instead')
const TrashIdentifier$json = const { const TrashIdentifier$json = const {
'1': 'TrashIdentifier', '1': 'TrashIdentifier',

View File

@ -1,5 +1,5 @@
use crate::service::{ use crate::service::{
trash::{create_trash, delete_trash, read_trash}, trash::{create_trash, delete_all_trash, delete_trash, read_trash},
user::LoggedUser, user::LoggedUser,
util::parse_from_payload, util::parse_from_payload,
}; };
@ -42,6 +42,7 @@ pub async fn create_handler(
Ok(FlowyResponse::success().into()) Ok(FlowyResponse::success().into())
} }
#[tracing::instrument(skip(payload, pool, logged_user), fields(delete_trash), err)]
pub async fn delete_handler( pub async fn delete_handler(
payload: Payload, payload: Payload,
pool: Data<PgPool>, pool: Data<PgPool>,
@ -53,7 +54,14 @@ pub async fn delete_handler(
.await .await
.context("Failed to acquire a Postgres connection to delete trash")?; .context("Failed to acquire a Postgres connection to delete trash")?;
let _ = delete_trash(&mut transaction, make_records(params)?, &logged_user).await?; if params.delete_all {
tracing::Span::current().record("delete_trash", &"all");
let _ = delete_all_trash(&mut transaction, &logged_user).await?;
} else {
let records = make_records(params)?;
let _ = delete_trash(&mut transaction, records, &logged_user).await?;
}
transaction transaction
.commit() .commit()
.await .await

View File

@ -33,6 +33,22 @@ pub(crate) async fn create_trash(
Ok(()) Ok(())
} }
pub(crate) async fn delete_all_trash(
transaction: &mut DBTransaction<'_>,
user: &LoggedUser,
) -> Result<(), ServerError> {
let (sql, args) = SqlBuilder::delete(TRASH_TABLE)
.and_where_eq("user_id", &user.user_id)
.build()?;
let result = sqlx::query_with(&sql, args)
.execute(transaction as &mut DBTransaction<'_>)
.await
.map_err(map_sqlx_error)?;
tracing::Span::current().record("affected_row", &result.rows_affected());
Ok(())
}
pub(crate) async fn delete_trash( pub(crate) async fn delete_trash(
transaction: &mut DBTransaction<'_>, transaction: &mut DBTransaction<'_>,
records: Vec<(Uuid, i32)>, records: Vec<(Uuid, i32)>,

View File

@ -45,7 +45,7 @@ impl DocError {
static_doc_error!(id_invalid, ErrorCode::DocIdInvalid); static_doc_error!(id_invalid, ErrorCode::DocIdInvalid);
static_doc_error!(internal, ErrorCode::InternalError); static_doc_error!(internal, ErrorCode::InternalError);
static_doc_error!(not_found, ErrorCode::DocNotfound); static_doc_error!(record_not_found, ErrorCode::DocNotfound);
static_doc_error!(unauthorized, ErrorCode::UserUnauthorized); static_doc_error!(unauthorized, ErrorCode::UserUnauthorized);
static_doc_error!(ws, ErrorCode::WsConnectError); static_doc_error!(ws, ErrorCode::WsConnectError);
static_doc_error!(undo, ErrorCode::UndoFail); static_doc_error!(undo, ErrorCode::UndoFail);
@ -97,7 +97,7 @@ impl std::default::Default for ErrorCode {
impl std::convert::From<flowy_database::Error> for DocError { impl std::convert::From<flowy_database::Error> for DocError {
fn from(error: flowy_database::Error) -> Self { fn from(error: flowy_database::Error) -> Self {
match error { match error {
flowy_database::Error::NotFound => DocError::not_found().context(error), flowy_database::Error::NotFound => DocError::record_not_found().context(error),
_ => DocError::internal().context(error), _ => DocError::internal().context(error),
} }
} }

View File

@ -37,11 +37,6 @@ impl FlowyDocument {
Ok(()) Ok(())
} }
pub fn create(&self, params: CreateDocParams) -> Result<(), DocError> {
let _ = self.doc_ctrl.create(params)?;
Ok(())
}
pub fn delete(&self, params: DocIdentifier) -> Result<(), DocError> { pub fn delete(&self, params: DocIdentifier) -> Result<(), DocError> {
let _ = self.doc_ctrl.delete(params)?; let _ = self.doc_ctrl.delete(params)?;
Ok(()) Ok(())

View File

@ -46,4 +46,4 @@ impl DocCache {
} }
} }
fn doc_not_found() -> DocError { DocError::not_found().context("Doc is close or you should call open first") } fn doc_not_found() -> DocError { DocError::record_not_found().context("Doc is close or you should call open first") }

View File

@ -45,17 +45,6 @@ impl DocController {
Ok(()) Ok(())
} }
#[tracing::instrument(skip(self), err)]
pub(crate) fn create(&self, params: CreateDocParams) -> Result<(), DocError> {
// let _doc = Doc {
// id: params.id,
// data: params.data,
// rev_id: 0,
// };
// let _ = self.doc_sql.create_doc_table(DocTable::new(doc), conn)?;
Ok(())
}
#[tracing::instrument(level = "debug", skip(self, pool), err)] #[tracing::instrument(level = "debug", skip(self, pool), err)]
pub(crate) async fn open( pub(crate) async fn open(
&self, &self,
@ -82,7 +71,6 @@ impl DocController {
let doc_id = &params.doc_id; let doc_id = &params.doc_id;
self.cache.remove(doc_id); self.cache.remove(doc_id);
self.ws_manager.remove_handler(doc_id); self.ws_manager.remove_handler(doc_id);
let _ = self.delete_doc_on_server(params)?;
Ok(()) Ok(())
} }
@ -95,22 +83,6 @@ impl DocController {
} }
impl DocController { impl DocController {
#[tracing::instrument(level = "debug", skip(self), err)]
fn delete_doc_on_server(&self, params: DocIdentifier) -> Result<(), DocError> {
let token = self.user.token()?;
let server = self.server.clone();
tokio::spawn(async move {
match server.delete_doc(&token, params).await {
Ok(_) => {},
Err(e) => {
// TODO: retry?
log::error!("Delete doc failed: {:?}", e);
},
}
});
Ok(())
}
async fn make_edit_context(&self, doc_id: &str, pool: Arc<ConnectionPool>) -> Result<Arc<ClientEditDoc>, DocError> { async fn make_edit_context(&self, doc_id: &str, pool: Arc<ConnectionPool>) -> Result<Arc<ClientEditDoc>, DocError> {
// Opti: require upgradable_read lock and then upgrade to write lock using // Opti: require upgradable_read lock and then upgrade to write lock using
// RwLockUpgradableReadGuard::upgrade(xx) of ws // RwLockUpgradableReadGuard::upgrade(xx) of ws
@ -146,7 +118,7 @@ impl RevisionServer for RevisionServerImpl {
ResultFuture::new(async move { ResultFuture::new(async move {
match server.read_doc(&token, params).await? { match server.read_doc(&token, params).await? {
None => Err(DocError::not_found()), None => Err(DocError::record_not_found().context("Remote doesn't have this document")),
Some(doc) => Ok(doc), Some(doc) => Ok(doc),
} }
}) })

View File

@ -180,7 +180,7 @@ async fn fetch_from_local(doc_id: &str, persistence: Arc<Persistence>) -> DocRes
let conn = &*persistence.pool.get().map_err(internal_error)?; let conn = &*persistence.pool.get().map_err(internal_error)?;
let revisions = persistence.rev_sql.read_rev_tables(&doc_id, None, conn)?; let revisions = persistence.rev_sql.read_rev_tables(&doc_id, None, conn)?;
if revisions.is_empty() { if revisions.is_empty() {
return Err(DocError::not_found()); return Err(DocError::record_not_found().context("Local doesn't have this document"));
} }
let base_rev_id: RevId = revisions.last().unwrap().base_rev_id.into(); let base_rev_id: RevId = revisions.last().unwrap().base_rev_id.into();

View File

@ -20,8 +20,6 @@ pub trait DocumentServerAPI {
fn read_doc(&self, token: &str, params: DocIdentifier) -> ResultFuture<Option<Doc>, DocError>; fn read_doc(&self, token: &str, params: DocIdentifier) -> ResultFuture<Option<Doc>, DocError>;
fn update_doc(&self, token: &str, params: UpdateDocParams) -> ResultFuture<(), DocError>; fn update_doc(&self, token: &str, params: UpdateDocParams) -> ResultFuture<(), DocError>;
fn delete_doc(&self, token: &str, params: DocIdentifier) -> ResultFuture<(), DocError>;
} }
pub(crate) fn construct_doc_server(server_config: &ServerConfig) -> Arc<dyn DocumentServerAPI + Send + Sync> { pub(crate) fn construct_doc_server(server_config: &ServerConfig) -> Arc<dyn DocumentServerAPI + Send + Sync> {

View File

@ -32,12 +32,6 @@ impl DocumentServerAPI for DocServer {
let url = self.config.doc_url(); let url = self.config.doc_url();
ResultFuture::new(async move { update_doc_request(&token, params, &url).await }) ResultFuture::new(async move { update_doc_request(&token, params, &url).await })
} }
fn delete_doc(&self, token: &str, params: DocIdentifier) -> ResultFuture<(), DocError> {
let token = token.to_owned();
let url = self.config.doc_url();
ResultFuture::new(async move { delete_doc_request(&token, params, &url).await })
}
} }
pub(crate) fn request_builder() -> HttpRequestBuilder { pub(crate) fn request_builder() -> HttpRequestBuilder {
@ -74,13 +68,3 @@ pub async fn update_doc_request(token: &str, params: UpdateDocParams, url: &str)
.await?; .await?;
Ok(()) Ok(())
} }
pub async fn delete_doc_request(token: &str, params: DocIdentifier, url: &str) -> Result<(), DocError> {
let _ = request_builder()
.delete(url)
.header(HEADER_TOKEN, token)
.protobuf(params)?
.send()
.await?;
Ok(())
}

View File

@ -18,8 +18,4 @@ impl DocumentServerAPI for DocServerMock {
fn update_doc(&self, _token: &str, _params: UpdateDocParams) -> ResultFuture<(), DocError> { fn update_doc(&self, _token: &str, _params: UpdateDocParams) -> ResultFuture<(), DocError> {
ResultFuture::new(async { Ok(()) }) ResultFuture::new(async { Ok(()) })
} }
fn delete_doc(&self, _token: &str, _params: DocIdentifier) -> ResultFuture<(), DocError> {
ResultFuture::new(async { Ok(()) })
}
} }

View File

@ -133,7 +133,7 @@ pub fn read_workspace(sdk: &FlowyTestSDK, request: QueryWorkspaceRequest) -> Opt
let mut workspaces; let mut workspaces;
if let Some(workspace_id) = &request.workspace_id { if let Some(workspace_id) = &request.workspace_id {
workspaces = repeated_workspace workspaces = repeated_workspace
.take_items() .into_inner()
.into_iter() .into_iter()
.filter(|workspace| &workspace.id == workspace_id) .filter(|workspace| &workspace.id == workspace_id)
.collect::<Vec<Workspace>>(); .collect::<Vec<Workspace>>();

View File

@ -3,9 +3,8 @@ use flowy_test::{builder::UserTest, FlowyTest};
use flowy_user::{errors::ErrorCode, event::UserEvent::*, prelude::*}; use flowy_user::{errors::ErrorCode, event::UserEvent::*, prelude::*};
use serial_test::*; use serial_test::*;
#[test] #[tokio::test]
#[serial] async fn sign_up_with_invalid_email() {
fn sign_up_with_invalid_email() {
for email in invalid_email_test_case() { for email in invalid_email_test_case() {
let test = FlowyTest::setup(); let test = FlowyTest::setup();
let request = SignUpRequest { let request = SignUpRequest {
@ -18,16 +17,16 @@ fn sign_up_with_invalid_email() {
UserTest::new(test.sdk) UserTest::new(test.sdk)
.event(SignUp) .event(SignUp)
.request(request) .request(request)
.sync_send() .async_send()
.await
.error() .error()
.code, .code,
ErrorCode::EmailFormatInvalid ErrorCode::EmailFormatInvalid
); );
} }
} }
#[test] #[tokio::test]
#[serial] async fn sign_up_with_invalid_password() {
fn sign_up_with_invalid_password() {
for password in invalid_password_test_case() { for password in invalid_password_test_case() {
let test = FlowyTest::setup(); let test = FlowyTest::setup();
let request = SignUpRequest { let request = SignUpRequest {
@ -39,7 +38,8 @@ fn sign_up_with_invalid_password() {
UserTest::new(test.sdk) UserTest::new(test.sdk)
.event(SignUp) .event(SignUp)
.request(request) .request(request)
.sync_send() .async_send()
.await
.assert_error(); .assert_error();
} }
} }
@ -58,14 +58,14 @@ async fn sign_in_success() {
let response = UserTest::new(test.sdk()) let response = UserTest::new(test.sdk())
.event(SignIn) .event(SignIn)
.request(request) .request(request)
.sync_send() .async_send()
.await
.parse::<UserProfile>(); .parse::<UserProfile>();
dbg!(&response); dbg!(&response);
} }
#[test] #[tokio::test]
#[serial] async fn sign_in_with_invalid_email() {
fn sign_in_with_invalid_email() {
for email in invalid_email_test_case() { for email in invalid_email_test_case() {
let test = FlowyTest::setup(); let test = FlowyTest::setup();
let request = SignInRequest { let request = SignInRequest {
@ -77,7 +77,8 @@ fn sign_in_with_invalid_email() {
UserTest::new(test.sdk) UserTest::new(test.sdk)
.event(SignIn) .event(SignIn)
.request(request) .request(request)
.sync_send() .async_send()
.await
.error() .error()
.code, .code,
ErrorCode::EmailFormatInvalid ErrorCode::EmailFormatInvalid
@ -85,9 +86,8 @@ fn sign_in_with_invalid_email() {
} }
} }
#[test] #[tokio::test]
#[serial] async fn sign_in_with_invalid_password() {
fn sign_in_with_invalid_password() {
for password in invalid_password_test_case() { for password in invalid_password_test_case() {
let test = FlowyTest::setup(); let test = FlowyTest::setup();
@ -99,7 +99,8 @@ fn sign_in_with_invalid_password() {
UserTest::new(test.sdk) UserTest::new(test.sdk)
.event(SignIn) .event(SignIn)
.request(request) .request(request)
.sync_send() .async_send()
.await
.assert_error(); .assert_error();
} }
} }

View File

@ -4,11 +4,14 @@ use flowy_test::{builder::UserTest, FlowyTest};
use flowy_user::{errors::ErrorCode, event::UserEvent::*, prelude::*}; use flowy_user::{errors::ErrorCode, event::UserEvent::*, prelude::*};
use serial_test::*; use serial_test::*;
#[test] #[tokio::test]
#[serial] async fn user_profile_get_failed() {
fn user_profile_get_failed() {
let test = FlowyTest::setup(); let test = FlowyTest::setup();
let result = UserTest::new(test.sdk).event(GetUserProfile).assert_error().sync_send(); let result = UserTest::new(test.sdk)
.event(GetUserProfile)
.assert_error()
.async_send()
.await;
assert!(result.user_profile().is_none()) assert!(result.user_profile().is_none())
} }

View File

@ -27,10 +27,27 @@ impl std::default::Default for TrashType {
pub struct TrashIdentifiers { pub struct TrashIdentifiers {
#[pb(index = 1)] #[pb(index = 1)]
pub items: Vec<TrashIdentifier>, pub items: Vec<TrashIdentifier>,
#[pb(index = 2)]
pub delete_all: bool,
}
impl TrashIdentifiers {
pub fn all() -> TrashIdentifiers {
TrashIdentifiers {
items: vec![],
delete_all: true,
}
}
} }
impl std::convert::From<Vec<TrashIdentifier>> for TrashIdentifiers { impl std::convert::From<Vec<TrashIdentifier>> for TrashIdentifiers {
fn from(items: Vec<TrashIdentifier>) -> Self { TrashIdentifiers { items } } fn from(items: Vec<TrashIdentifier>) -> Self {
TrashIdentifiers {
items,
delete_all: false,
}
}
} }
impl std::convert::From<Vec<Trash>> for TrashIdentifiers { impl std::convert::From<Vec<Trash>> for TrashIdentifiers {
@ -40,7 +57,10 @@ impl std::convert::From<Vec<Trash>> for TrashIdentifiers {
.map(|t| TrashIdentifier { id: t.id, ty: t.ty }) .map(|t| TrashIdentifier { id: t.id, ty: t.ty })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
TrashIdentifiers { items } TrashIdentifiers {
items,
delete_all: false,
}
} }
} }

View File

@ -33,12 +33,12 @@ pub(crate) async fn delete_trash_handler(
#[tracing::instrument(skip(controller), err)] #[tracing::instrument(skip(controller), err)]
pub(crate) async fn restore_all_handler(controller: Unit<Arc<TrashCan>>) -> Result<(), WorkspaceError> { pub(crate) async fn restore_all_handler(controller: Unit<Arc<TrashCan>>) -> Result<(), WorkspaceError> {
let _ = controller.restore_all()?; let _ = controller.restore_all().await?;
Ok(()) Ok(())
} }
#[tracing::instrument(skip(controller), err)] #[tracing::instrument(skip(controller), err)]
pub(crate) async fn delete_all_handler(controller: Unit<Arc<TrashCan>>) -> Result<(), WorkspaceError> { pub(crate) async fn delete_all_handler(controller: Unit<Arc<TrashCan>>) -> Result<(), WorkspaceError> {
let _ = controller.delete_all()?; let _ = controller.delete_all().await?;
Ok(()) Ok(())
} }

View File

@ -27,6 +27,7 @@
pub struct TrashIdentifiers { pub struct TrashIdentifiers {
// message fields // message fields
pub items: ::protobuf::RepeatedField<TrashIdentifier>, pub items: ::protobuf::RepeatedField<TrashIdentifier>,
pub delete_all: bool,
// special fields // special fields
pub unknown_fields: ::protobuf::UnknownFields, pub unknown_fields: ::protobuf::UnknownFields,
pub cached_size: ::protobuf::CachedSize, pub cached_size: ::protobuf::CachedSize,
@ -67,6 +68,21 @@ impl TrashIdentifiers {
pub fn take_items(&mut self) -> ::protobuf::RepeatedField<TrashIdentifier> { pub fn take_items(&mut self) -> ::protobuf::RepeatedField<TrashIdentifier> {
::std::mem::replace(&mut self.items, ::protobuf::RepeatedField::new()) ::std::mem::replace(&mut self.items, ::protobuf::RepeatedField::new())
} }
// bool delete_all = 2;
pub fn get_delete_all(&self) -> bool {
self.delete_all
}
pub fn clear_delete_all(&mut self) {
self.delete_all = false;
}
// Param is passed by value, moved
pub fn set_delete_all(&mut self, v: bool) {
self.delete_all = v;
}
} }
impl ::protobuf::Message for TrashIdentifiers { impl ::protobuf::Message for TrashIdentifiers {
@ -86,6 +102,13 @@ impl ::protobuf::Message for TrashIdentifiers {
1 => { 1 => {
::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.items)?; ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.items)?;
}, },
2 => {
if wire_type != ::protobuf::wire_format::WireTypeVarint {
return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
}
let tmp = is.read_bool()?;
self.delete_all = tmp;
},
_ => { _ => {
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
}, },
@ -102,6 +125,9 @@ impl ::protobuf::Message for TrashIdentifiers {
let len = value.compute_size(); let len = value.compute_size();
my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len; my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
}; };
if self.delete_all != false {
my_size += 2;
}
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
self.cached_size.set(my_size); self.cached_size.set(my_size);
my_size my_size
@ -113,6 +139,9 @@ impl ::protobuf::Message for TrashIdentifiers {
os.write_raw_varint32(v.get_cached_size())?; os.write_raw_varint32(v.get_cached_size())?;
v.write_to_with_cached_sizes(os)?; v.write_to_with_cached_sizes(os)?;
}; };
if self.delete_all != false {
os.write_bool(2, self.delete_all)?;
}
os.write_unknown_fields(self.get_unknown_fields())?; os.write_unknown_fields(self.get_unknown_fields())?;
::std::result::Result::Ok(()) ::std::result::Result::Ok(())
} }
@ -156,6 +185,11 @@ impl ::protobuf::Message for TrashIdentifiers {
|m: &TrashIdentifiers| { &m.items }, |m: &TrashIdentifiers| { &m.items },
|m: &mut TrashIdentifiers| { &mut m.items }, |m: &mut TrashIdentifiers| { &mut m.items },
)); ));
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBool>(
"delete_all",
|m: &TrashIdentifiers| { &m.delete_all },
|m: &mut TrashIdentifiers| { &mut m.delete_all },
));
::protobuf::reflect::MessageDescriptor::new_pb_name::<TrashIdentifiers>( ::protobuf::reflect::MessageDescriptor::new_pb_name::<TrashIdentifiers>(
"TrashIdentifiers", "TrashIdentifiers",
fields, fields,
@ -173,6 +207,7 @@ impl ::protobuf::Message for TrashIdentifiers {
impl ::protobuf::Clear for TrashIdentifiers { impl ::protobuf::Clear for TrashIdentifiers {
fn clear(&mut self) { fn clear(&mut self) {
self.items.clear(); self.items.clear();
self.delete_all = false;
self.unknown_fields.clear(); self.unknown_fields.clear();
} }
} }
@ -898,52 +933,56 @@ impl ::protobuf::reflect::ProtobufValue for TrashType {
} }
static file_descriptor_proto_data: &'static [u8] = b"\ static file_descriptor_proto_data: &'static [u8] = b"\
\n\x12trash_create.proto\":\n\x10TrashIdentifiers\x12&\n\x05items\x18\ \n\x12trash_create.proto\"Y\n\x10TrashIdentifiers\x12&\n\x05items\x18\
\x01\x20\x03(\x0b2\x10.TrashIdentifierR\x05items\"=\n\x0fTrashIdentifier\ \x01\x20\x03(\x0b2\x10.TrashIdentifierR\x05items\x12\x1d\n\ndelete_all\
\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x1a\n\x02ty\x18\x02\x20\ \x18\x02\x20\x01(\x08R\tdeleteAll\"=\n\x0fTrashIdentifier\x12\x0e\n\x02i\
\x01(\x0e2\n.TrashTypeR\x02ty\"\x8d\x01\n\x05Trash\x12\x0e\n\x02id\x18\ d\x18\x01\x20\x01(\tR\x02id\x12\x1a\n\x02ty\x18\x02\x20\x01(\x0e2\n.Tras\
\x01\x20\x01(\tR\x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\ hTypeR\x02ty\"\x8d\x01\n\x05Trash\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\
#\n\rmodified_time\x18\x03\x20\x01(\x03R\x0cmodifiedTime\x12\x1f\n\x0bcr\ \x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12#\n\rmodified_ti\
eate_time\x18\x04\x20\x01(\x03R\ncreateTime\x12\x1a\n\x02ty\x18\x05\x20\ me\x18\x03\x20\x01(\x03R\x0cmodifiedTime\x12\x1f\n\x0bcreate_time\x18\
\x01(\x0e2\n.TrashTypeR\x02ty\"-\n\rRepeatedTrash\x12\x1c\n\x05items\x18\ \x04\x20\x01(\x03R\ncreateTime\x12\x1a\n\x02ty\x18\x05\x20\x01(\x0e2\n.T\
\x01\x20\x03(\x0b2\x06.TrashR\x05items*\"\n\tTrashType\x12\x0b\n\x07Unkn\ rashTypeR\x02ty\"-\n\rRepeatedTrash\x12\x1c\n\x05items\x18\x01\x20\x03(\
own\x10\0\x12\x08\n\x04View\x10\x01J\xe7\x05\n\x06\x12\x04\0\0\x16\x01\n\ \x0b2\x06.TrashR\x05items*\"\n\tTrashType\x12\x0b\n\x07Unknown\x10\0\x12\
\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x04\x01\n\n\ \x08\n\x04View\x10\x01J\x9e\x06\n\x06\x12\x04\0\0\x17\x01\n\x08\n\x01\
\n\x03\x04\0\x01\x12\x03\x02\x08\x18\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\ \x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\
\x04'\n\x0c\n\x05\x04\0\x02\0\x04\x12\x03\x03\x04\x0c\n\x0c\n\x05\x04\0\ \0\x01\x12\x03\x02\x08\x18\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04'\n\
\x02\0\x06\x12\x03\x03\r\x1c\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x1d\ \x0c\n\x05\x04\0\x02\0\x04\x12\x03\x03\x04\x0c\n\x0c\n\x05\x04\0\x02\0\
\"\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03%&\n\n\n\x02\x04\x01\x12\x04\ \x06\x12\x03\x03\r\x1c\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x1d\"\n\
\x05\0\x08\x01\n\n\n\x03\x04\x01\x01\x12\x03\x05\x08\x17\n\x0b\n\x04\x04\ \x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03%&\n\x0b\n\x04\x04\0\x02\x01\x12\
\x01\x02\0\x12\x03\x06\x04\x12\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x06\ \x03\x04\x04\x18\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\x08\n\x0c\
\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x06\x0b\r\n\x0c\n\x05\x04\ \n\x05\x04\0\x02\x01\x01\x12\x03\x04\t\x13\n\x0c\n\x05\x04\0\x02\x01\x03\
\x01\x02\0\x03\x12\x03\x06\x10\x11\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\ \x12\x03\x04\x16\x17\n\n\n\x02\x04\x01\x12\x04\x06\0\t\x01\n\n\n\x03\x04\
\x07\x04\x15\n\x0c\n\x05\x04\x01\x02\x01\x06\x12\x03\x07\x04\r\n\x0c\n\ \x01\x01\x12\x03\x06\x08\x17\n\x0b\n\x04\x04\x01\x02\0\x12\x03\x07\x04\
\x05\x04\x01\x02\x01\x01\x12\x03\x07\x0e\x10\n\x0c\n\x05\x04\x01\x02\x01\ \x12\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x07\x04\n\n\x0c\n\x05\x04\x01\
\x03\x12\x03\x07\x13\x14\n\n\n\x02\x04\x02\x12\x04\t\0\x0f\x01\n\n\n\x03\ \x02\0\x01\x12\x03\x07\x0b\r\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\x07\
\x04\x02\x01\x12\x03\t\x08\r\n\x0b\n\x04\x04\x02\x02\0\x12\x03\n\x04\x12\ \x10\x11\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\x08\x04\x15\n\x0c\n\x05\x04\
\n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\n\x04\n\n\x0c\n\x05\x04\x02\x02\0\ \x01\x02\x01\x06\x12\x03\x08\x04\r\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\
\x01\x12\x03\n\x0b\r\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\n\x10\x11\n\ \x03\x08\x0e\x10\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\x08\x13\x14\n\n\
\x0b\n\x04\x04\x02\x02\x01\x12\x03\x0b\x04\x14\n\x0c\n\x05\x04\x02\x02\ \n\x02\x04\x02\x12\x04\n\0\x10\x01\n\n\n\x03\x04\x02\x01\x12\x03\n\x08\r\
\x01\x05\x12\x03\x0b\x04\n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\x0b\ \n\x0b\n\x04\x04\x02\x02\0\x12\x03\x0b\x04\x12\n\x0c\n\x05\x04\x02\x02\0\
\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03\x0b\x12\x13\n\x0b\n\x04\ \x05\x12\x03\x0b\x04\n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\x0b\x0b\r\n\
\x04\x02\x02\x02\x12\x03\x0c\x04\x1c\n\x0c\n\x05\x04\x02\x02\x02\x05\x12\ \x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x0b\x10\x11\n\x0b\n\x04\x04\x02\x02\
\x03\x0c\x04\t\n\x0c\n\x05\x04\x02\x02\x02\x01\x12\x03\x0c\n\x17\n\x0c\n\ \x01\x12\x03\x0c\x04\x14\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\x0c\x04\
\x05\x04\x02\x02\x02\x03\x12\x03\x0c\x1a\x1b\n\x0b\n\x04\x04\x02\x02\x03\ \n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\x0c\x0b\x0f\n\x0c\n\x05\x04\
\x12\x03\r\x04\x1a\n\x0c\n\x05\x04\x02\x02\x03\x05\x12\x03\r\x04\t\n\x0c\ \x02\x02\x01\x03\x12\x03\x0c\x12\x13\n\x0b\n\x04\x04\x02\x02\x02\x12\x03\
\n\x05\x04\x02\x02\x03\x01\x12\x03\r\n\x15\n\x0c\n\x05\x04\x02\x02\x03\ \r\x04\x1c\n\x0c\n\x05\x04\x02\x02\x02\x05\x12\x03\r\x04\t\n\x0c\n\x05\
\x03\x12\x03\r\x18\x19\n\x0b\n\x04\x04\x02\x02\x04\x12\x03\x0e\x04\x15\n\ \x04\x02\x02\x02\x01\x12\x03\r\n\x17\n\x0c\n\x05\x04\x02\x02\x02\x03\x12\
\x0c\n\x05\x04\x02\x02\x04\x06\x12\x03\x0e\x04\r\n\x0c\n\x05\x04\x02\x02\ \x03\r\x1a\x1b\n\x0b\n\x04\x04\x02\x02\x03\x12\x03\x0e\x04\x1a\n\x0c\n\
\x04\x01\x12\x03\x0e\x0e\x10\n\x0c\n\x05\x04\x02\x02\x04\x03\x12\x03\x0e\ \x05\x04\x02\x02\x03\x05\x12\x03\x0e\x04\t\n\x0c\n\x05\x04\x02\x02\x03\
\x13\x14\n\n\n\x02\x04\x03\x12\x04\x10\0\x12\x01\n\n\n\x03\x04\x03\x01\ \x01\x12\x03\x0e\n\x15\n\x0c\n\x05\x04\x02\x02\x03\x03\x12\x03\x0e\x18\
\x12\x03\x10\x08\x15\n\x0b\n\x04\x04\x03\x02\0\x12\x03\x11\x04\x1d\n\x0c\ \x19\n\x0b\n\x04\x04\x02\x02\x04\x12\x03\x0f\x04\x15\n\x0c\n\x05\x04\x02\
\n\x05\x04\x03\x02\0\x04\x12\x03\x11\x04\x0c\n\x0c\n\x05\x04\x03\x02\0\ \x02\x04\x06\x12\x03\x0f\x04\r\n\x0c\n\x05\x04\x02\x02\x04\x01\x12\x03\
\x06\x12\x03\x11\r\x12\n\x0c\n\x05\x04\x03\x02\0\x01\x12\x03\x11\x13\x18\ \x0f\x0e\x10\n\x0c\n\x05\x04\x02\x02\x04\x03\x12\x03\x0f\x13\x14\n\n\n\
\n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x11\x1b\x1c\n\n\n\x02\x05\0\x12\ \x02\x04\x03\x12\x04\x11\0\x13\x01\n\n\n\x03\x04\x03\x01\x12\x03\x11\x08\
\x04\x13\0\x16\x01\n\n\n\x03\x05\0\x01\x12\x03\x13\x05\x0e\n\x0b\n\x04\ \x15\n\x0b\n\x04\x04\x03\x02\0\x12\x03\x12\x04\x1d\n\x0c\n\x05\x04\x03\
\x05\0\x02\0\x12\x03\x14\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x14\ \x02\0\x04\x12\x03\x12\x04\x0c\n\x0c\n\x05\x04\x03\x02\0\x06\x12\x03\x12\
\x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x14\x0e\x0f\n\x0b\n\x04\x05\ \r\x12\n\x0c\n\x05\x04\x03\x02\0\x01\x12\x03\x12\x13\x18\n\x0c\n\x05\x04\
\0\x02\x01\x12\x03\x15\x04\r\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x15\ \x03\x02\0\x03\x12\x03\x12\x1b\x1c\n\n\n\x02\x05\0\x12\x04\x14\0\x17\x01\
\x04\x08\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x15\x0b\x0cb\x06proto3\ \n\n\n\x03\x05\0\x01\x12\x03\x14\x05\x0e\n\x0b\n\x04\x05\0\x02\0\x12\x03\
\x15\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x15\x04\x0b\n\x0c\n\x05\
\x05\0\x02\0\x02\x12\x03\x15\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\
\x16\x04\r\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x16\x04\x08\n\x0c\n\x05\
\x05\0\x02\x01\x02\x12\x03\x16\x0b\x0cb\x06proto3\
"; ";
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

View File

@ -2,6 +2,7 @@ syntax = "proto3";
message TrashIdentifiers { message TrashIdentifiers {
repeated TrashIdentifier items = 1; repeated TrashIdentifier items = 1;
bool delete_all = 2;
} }
message TrashIdentifier { message TrashIdentifier {
string id = 1; string id = 1;

View File

@ -55,11 +55,12 @@ impl TrashCan {
let trash_table = TrashTableSql::read(trash_id, &*self.database.db_connection()?)?; let trash_table = TrashTableSql::read(trash_id, &*self.database.db_connection()?)?;
let _ = thread::scope(|_s| { let _ = thread::scope(|_s| {
let conn = self.database.db_connection()?; let conn = self.database.db_connection()?;
let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| { conn.immediate_transaction::<_, WorkspaceError, _>(|| {
let _ = TrashTableSql::delete_trash(trash_id, &*conn)?; let _ = TrashTableSql::delete_trash(trash_id, &*conn)?;
notify_trash_num_changed(TrashTableSql::read_all(&conn)?); notify_trash_num_changed(TrashTableSql::read_all(&conn)?);
Ok(()) Ok(())
})?; })?;
Ok::<(), WorkspaceError>(()) Ok::<(), WorkspaceError>(())
}) })
.unwrap()?; .unwrap()?;
@ -71,6 +72,7 @@ impl TrashCan {
let _ = self.delete_trash_on_server(TrashIdentifiers { let _ = self.delete_trash_on_server(TrashIdentifiers {
items: vec![identifier.clone()], items: vec![identifier.clone()],
delete_all: false,
})?; })?;
tracing::Span::current().record("putback", &format!("{:?}", &identifier).as_str()); tracing::Span::current().record("putback", &format!("{:?}", &identifier).as_str());
@ -80,16 +82,45 @@ impl TrashCan {
} }
#[tracing::instrument(level = "debug", skip(self) err)] #[tracing::instrument(level = "debug", skip(self) err)]
pub fn restore_all(&self) -> WorkspaceResult<()> { Ok(()) } pub async fn restore_all(&self) -> WorkspaceResult<()> {
let repeated_trash = self.delete_all_trash_on_local()?;
let identifiers: TrashIdentifiers = repeated_trash.items.clone().into();
let (tx, mut rx) = mpsc::channel::<WorkspaceResult<()>>(1);
let _ = self.notify.send(TrashEvent::Putback(identifiers, tx));
let _ = rx.recv().await;
notify_trash_num_changed(RepeatedTrash { items: vec![] });
let _ = self.delete_all_trash_on_server().await?;
Ok(())
}
#[tracing::instrument(level = "debug", skip(self) err)] #[tracing::instrument(level = "debug", skip(self) err)]
pub fn delete_all(&self) -> WorkspaceResult<()> { Ok(()) } pub async fn delete_all(&self) -> WorkspaceResult<()> {
let repeated_trash = self.delete_all_trash_on_local()?;
let identifiers: TrashIdentifiers = repeated_trash.items.clone().into();
let (tx, mut rx) = mpsc::channel::<WorkspaceResult<()>>(1);
let _ = self.notify.send(TrashEvent::Delete(identifiers, tx));
let _ = rx.recv().await;
notify_trash_num_changed(RepeatedTrash { items: vec![] });
let _ = self.delete_all_trash_on_server().await?;
Ok(())
}
fn delete_all_trash_on_local(&self) -> WorkspaceResult<RepeatedTrash> {
let conn = self.database.db_connection()?;
conn.immediate_transaction::<_, WorkspaceError, _>(|| {
let repeated_trash = TrashTableSql::read_all(&*conn)?;
let _ = TrashTableSql::delete_all(&*conn)?;
Ok(repeated_trash)
})
}
#[tracing::instrument(level = "debug", skip(self) err)] #[tracing::instrument(level = "debug", skip(self) err)]
pub async fn delete(&self, trash_identifiers: TrashIdentifiers) -> WorkspaceResult<()> { pub async fn delete(&self, trash_identifiers: TrashIdentifiers) -> WorkspaceResult<()> {
let (tx, mut rx) = mpsc::channel::<WorkspaceResult<()>>(1); let (tx, mut rx) = mpsc::channel::<WorkspaceResult<()>>(1);
let _ = self.notify.send(TrashEvent::Delete(trash_identifiers.clone(), tx)); let _ = self.notify.send(TrashEvent::Delete(trash_identifiers.clone(), tx));
let _ = rx.recv().await.unwrap()?; let _ = rx.recv().await;
let conn = self.database.db_connection()?; let conn = self.database.db_connection()?;
conn.immediate_transaction::<_, WorkspaceError, _>(|| { conn.immediate_transaction::<_, WorkspaceError, _>(|| {
@ -99,6 +130,7 @@ impl TrashCan {
Ok(()) Ok(())
})?; })?;
notify_trash_num_changed(TrashTableSql::read_all(&conn)?);
let _ = self.delete_trash_on_server(trash_identifiers)?; let _ = self.delete_trash_on_server(trash_identifiers)?;
Ok(()) Ok(())
@ -122,7 +154,8 @@ impl TrashCan {
let conn = self.database.db_connection()?; let conn = self.database.db_connection()?;
conn.immediate_transaction::<_, WorkspaceError, _>(|| { conn.immediate_transaction::<_, WorkspaceError, _>(|| {
let _ = TrashTableSql::create_trash(repeated_trash.clone(), &*conn)?; let _ = TrashTableSql::create_trash(repeated_trash.clone(), &*conn)?;
self.create_trash_on_server(repeated_trash); let _ = self.create_trash_on_server(repeated_trash);
notify_trash_num_changed(TrashTableSql::read_all(&conn)?); notify_trash_num_changed(TrashTableSql::read_all(&conn)?);
Ok(()) Ok(())
})?; })?;
@ -202,6 +235,13 @@ impl TrashCan {
}); });
Ok(()) Ok(())
} }
#[tracing::instrument(level = "debug", skip(self), err)]
async fn delete_all_trash_on_server(&self) -> WorkspaceResult<()> {
let token = self.user.token()?;
let server = self.server.clone();
server.delete_trash(&token, TrashIdentifiers::all()).await
}
} }
#[tracing::instrument(skip(repeated_trash), fields(trash_count))] #[tracing::instrument(skip(repeated_trash), fields(trash_count))]

View File

@ -23,7 +23,7 @@ use flowy_document::{
use crate::{entities::trash::TrashType, errors::WorkspaceResult}; use crate::{entities::trash::TrashType, errors::WorkspaceResult};
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use std::sync::Arc; use std::{collections::HashSet, sync::Arc};
pub(crate) struct ViewController { pub(crate) struct ViewController {
user: Arc<dyn WorkspaceUser>, user: Arc<dyn WorkspaceUser>,
@ -61,10 +61,9 @@ impl ViewController {
let view = self.create_view_on_server(params.clone()).await?; let view = self.create_view_on_server(params.clone()).await?;
let conn = &*self.database.db_connection()?; let conn = &*self.database.db_connection()?;
let trash_can = self.trash_can.clone(); let trash_can = self.trash_can.clone();
// TODO: rollback anything created before if failed?
conn.immediate_transaction::<_, WorkspaceError, _>(|| { conn.immediate_transaction::<_, WorkspaceError, _>(|| {
let _ = self.save_view(view.clone(), conn)?; let _ = self.save_view(view.clone(), conn)?;
self.document.create(CreateDocParams::new(&view.id, params.data))?;
let repeated_view = read_belonging_view(&view.belong_to_id, trash_can, &conn)?; let repeated_view = read_belonging_view(&view.belong_to_id, trash_can, &conn)?;
send_dart_notification(&view.belong_to_id, WorkspaceNotification::AppViewsChanged) send_dart_notification(&view.belong_to_id, WorkspaceNotification::AppViewsChanged)
.payload(repeated_view) .payload(repeated_view)
@ -241,7 +240,9 @@ async fn handle_trash_event(
let conn = &*db_result?; let conn = &*db_result?;
let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| { let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
for identifier in identifiers.items { for identifier in identifiers.items {
let _ = notify_view_num_changed(&identifier.id, conn, trash_can.clone())?; let view_table = ViewTableSql::read_view(&identifier.id, conn)?;
let repeated_view = read_belonging_view(&view_table.belong_to_id, trash_can.clone(), conn)?;
let _ = notify_view_num_changed(&view_table.belong_to_id, repeated_view)?;
} }
Ok(()) Ok(())
})?; })?;
@ -253,11 +254,19 @@ async fn handle_trash_event(
let result = || { let result = || {
let conn = &*db_result?; let conn = &*db_result?;
let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| { let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
let mut notify_ids = HashSet::new();
for identifier in identifiers.items { for identifier in identifiers.items {
let view_table = ViewTableSql::read_view(&identifier.id, conn)?;
let _ = ViewTableSql::delete_view(&identifier.id, conn)?; let _ = ViewTableSql::delete_view(&identifier.id, conn)?;
let _ = document.delete(identifier.id.clone().into())?; let _ = document.delete(identifier.id.clone().into())?;
let _ = notify_view_num_changed(&identifier.id, conn, trash_can.clone())?; notify_ids.insert(view_table.belong_to_id);
} }
for notify_id in notify_ids {
let repeated_view = read_belonging_view(&notify_id, trash_can.clone(), conn)?;
let _ = notify_view_num_changed(&notify_id, repeated_view)?;
}
Ok(()) Ok(())
})?; })?;
Ok::<(), WorkspaceError>(()) Ok::<(), WorkspaceError>(())
@ -267,12 +276,9 @@ async fn handle_trash_event(
} }
} }
#[tracing::instrument(skip(conn, trash_can), err)] #[tracing::instrument(skip(repeated_view), err)]
fn notify_view_num_changed(view_id: &str, conn: &SqliteConnection, trash_can: Arc<TrashCan>) -> WorkspaceResult<()> { fn notify_view_num_changed(belong_to_id: &str, repeated_view: RepeatedView) -> WorkspaceResult<()> {
let view_table = ViewTableSql::read_view(view_id, conn)?; send_dart_notification(&belong_to_id, WorkspaceNotification::AppViewsChanged)
let repeated_view = read_belonging_view(&view_table.belong_to_id, trash_can, conn)?;
send_dart_notification(&view_table.belong_to_id, WorkspaceNotification::AppViewsChanged)
.payload(repeated_view) .payload(repeated_view)
.send(); .send();
Ok(()) Ok(())

View File

@ -35,6 +35,11 @@ impl TrashTableSql {
Ok(RepeatedTrash { items }) Ok(RepeatedTrash { items })
} }
pub(crate) fn delete_all(conn: &SqliteConnection) -> Result<(), WorkspaceError> {
let _ = diesel::delete(dsl::trash_table).execute(conn)?;
Ok(())
}
pub(crate) fn read(trash_id: &str, conn: &SqliteConnection) -> Result<TrashTable, WorkspaceError> { pub(crate) fn read(trash_id: &str, conn: &SqliteConnection) -> Result<TrashTable, WorkspaceError> {
let trash_table = dsl::trash_table let trash_table = dsl::trash_table
.filter(trash_table::id.eq(trash_id)) .filter(trash_table::id.eq(trash_id))

View File

@ -29,8 +29,8 @@ async fn workspace_create_with_apps() {
assert_eq!(&app, workspace_from_db.apps.first_or_crash()); assert_eq!(&app, workspace_from_db.apps.first_or_crash());
} }
#[test] #[tokio::test]
fn workspace_create_with_invalid_name() { async fn workspace_create_with_invalid_name() {
for name in invalid_workspace_name_test_case() { for name in invalid_workspace_name_test_case() {
let sdk = FlowyTest::setup().sdk; let sdk = FlowyTest::setup().sdk;
let request = CreateWorkspaceRequest { let request = CreateWorkspaceRequest {
@ -41,7 +41,8 @@ fn workspace_create_with_invalid_name() {
FlowyWorkspaceTest::new(sdk) FlowyWorkspaceTest::new(sdk)
.event(CreateWorkspace) .event(CreateWorkspace)
.request(request) .request(request)
.sync_send() .async_send()
.await
.error() .error()
.code, .code,
ErrorCode::WorkspaceNameInvalid ErrorCode::WorkspaceNameInvalid
@ -49,8 +50,8 @@ fn workspace_create_with_invalid_name() {
} }
} }
#[test] #[tokio::test]
fn workspace_update_with_invalid_name() { async fn workspace_update_with_invalid_name() {
let sdk = FlowyTest::setup().sdk; let sdk = FlowyTest::setup().sdk;
for name in invalid_workspace_name_test_case() { for name in invalid_workspace_name_test_case() {
let request = CreateWorkspaceRequest { let request = CreateWorkspaceRequest {
@ -61,7 +62,8 @@ fn workspace_update_with_invalid_name() {
FlowyWorkspaceTest::new(sdk.clone()) FlowyWorkspaceTest::new(sdk.clone())
.event(CreateWorkspace) .event(CreateWorkspace)
.request(request) .request(request)
.sync_send() .async_send()
.await
.error() .error()
.code, .code,
ErrorCode::WorkspaceNameInvalid ErrorCode::WorkspaceNameInvalid