[client]: fix trash notification issue

This commit is contained in:
appflowy 2021-10-16 21:22:59 +08:00
parent 560ee376f9
commit 2513853ea4
14 changed files with 95 additions and 451 deletions

View File

@ -40,7 +40,7 @@ class ApplicationBlocObserver extends BlocObserver {
@override @override
// ignore: unnecessary_overrides // ignore: unnecessary_overrides
void onTransition(Bloc bloc, Transition transition) { void onTransition(Bloc bloc, Transition transition) {
// Log.debug("[current]: ${transition.currentState} \n[next]: ${transition.nextState}"); Log.debug("[current]: ${transition.currentState} \n[next]: ${transition.nextState}");
super.onTransition(bloc, transition); super.onTransition(bloc, transition);
} }

View File

@ -28,7 +28,10 @@ class TrashBloc extends Bloc<TrashEvent, TrashState> {
}, },
putback: (e) async* { putback: (e) async* {
final result = await iTrash.putback(e.trashId); final result = await iTrash.putback(e.trashId);
result.fold((l) {}, (error) {}); yield result.fold(
(l) => state.copyWith(successOrFailure: left(unit)),
(error) => state.copyWith(successOrFailure: right(error)),
);
}, },
delete: (e) async* { delete: (e) async* {
final result = await iTrash.delete(e.trashId); final result = await iTrash.delete(e.trashId);

View File

@ -32,7 +32,7 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
(error) => state.copyWith(successOrFailure: right(error)), (error) => state.copyWith(successOrFailure: right(error)),
); );
}, delete: (e) async* { }, delete: (e) async* {
final result = await iViewImpl.pushIntoTrash(); final result = await iViewImpl.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

@ -1,43 +0,0 @@
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:app_flowy/workspace/domain/i_view.dart';
part 'view_edit_bloc.freezed.dart';
class ViewEditBloc extends Bloc<ViewEditEvent, ViewEditState> {
final IView iViewImpl;
ViewEditBloc({
required this.iViewImpl,
}) : super(ViewEditState.initial());
@override
Stream<ViewEditState> mapEventToState(ViewEditEvent event) async* {
yield* event.map(initial: (_) async* {
yield state;
});
}
}
@freezed
class ViewEditEvent with _$ViewEditEvent {
const factory ViewEditEvent.initial() = Initial;
}
@freezed
class ViewEditState with _$ViewEditState {
const factory ViewEditState({
required bool isLoading,
required Option<View> view,
required Either<Unit, WorkspaceError> successOrFailure,
}) = _ViewState;
factory ViewEditState.initial() => ViewEditState(
isLoading: false,
view: none(),
successOrFailure: left(unit),
);
}

View File

@ -1,332 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'view_edit_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
/// @nodoc
class _$ViewEditEventTearOff {
const _$ViewEditEventTearOff();
Initial initial() {
return const Initial();
}
}
/// @nodoc
const $ViewEditEvent = _$ViewEditEventTearOff();
/// @nodoc
mixin _$ViewEditEvent {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(Initial value) initial,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(Initial value)? initial,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ViewEditEventCopyWith<$Res> {
factory $ViewEditEventCopyWith(
ViewEditEvent value, $Res Function(ViewEditEvent) then) =
_$ViewEditEventCopyWithImpl<$Res>;
}
/// @nodoc
class _$ViewEditEventCopyWithImpl<$Res>
implements $ViewEditEventCopyWith<$Res> {
_$ViewEditEventCopyWithImpl(this._value, this._then);
final ViewEditEvent _value;
// ignore: unused_field
final $Res Function(ViewEditEvent) _then;
}
/// @nodoc
abstract class $InitialCopyWith<$Res> {
factory $InitialCopyWith(Initial value, $Res Function(Initial) then) =
_$InitialCopyWithImpl<$Res>;
}
/// @nodoc
class _$InitialCopyWithImpl<$Res> extends _$ViewEditEventCopyWithImpl<$Res>
implements $InitialCopyWith<$Res> {
_$InitialCopyWithImpl(Initial _value, $Res Function(Initial) _then)
: super(_value, (v) => _then(v as Initial));
@override
Initial get _value => super._value as Initial;
}
/// @nodoc
class _$Initial implements Initial {
const _$Initial();
@override
String toString() {
return 'ViewEditEvent.initial()';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) || (other is Initial);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
}) {
return initial();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
required TResult orElse(),
}) {
if (initial != null) {
return initial();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(Initial value) initial,
}) {
return initial(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(Initial value)? initial,
required TResult orElse(),
}) {
if (initial != null) {
return initial(this);
}
return orElse();
}
}
abstract class Initial implements ViewEditEvent {
const factory Initial() = _$Initial;
}
/// @nodoc
class _$ViewEditStateTearOff {
const _$ViewEditStateTearOff();
_ViewState call(
{required bool isLoading,
required Option<View> view,
required Either<Unit, WorkspaceError> successOrFailure}) {
return _ViewState(
isLoading: isLoading,
view: view,
successOrFailure: successOrFailure,
);
}
}
/// @nodoc
const $ViewEditState = _$ViewEditStateTearOff();
/// @nodoc
mixin _$ViewEditState {
bool get isLoading => throw _privateConstructorUsedError;
Option<View> get view => throw _privateConstructorUsedError;
Either<Unit, WorkspaceError> get successOrFailure =>
throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ViewEditStateCopyWith<ViewEditState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ViewEditStateCopyWith<$Res> {
factory $ViewEditStateCopyWith(
ViewEditState value, $Res Function(ViewEditState) then) =
_$ViewEditStateCopyWithImpl<$Res>;
$Res call(
{bool isLoading,
Option<View> view,
Either<Unit, WorkspaceError> successOrFailure});
}
/// @nodoc
class _$ViewEditStateCopyWithImpl<$Res>
implements $ViewEditStateCopyWith<$Res> {
_$ViewEditStateCopyWithImpl(this._value, this._then);
final ViewEditState _value;
// ignore: unused_field
final $Res Function(ViewEditState) _then;
@override
$Res call({
Object? isLoading = freezed,
Object? view = freezed,
Object? successOrFailure = freezed,
}) {
return _then(_value.copyWith(
isLoading: isLoading == freezed
? _value.isLoading
: isLoading // ignore: cast_nullable_to_non_nullable
as bool,
view: view == freezed
? _value.view
: view // ignore: cast_nullable_to_non_nullable
as Option<View>,
successOrFailure: successOrFailure == freezed
? _value.successOrFailure
: successOrFailure // ignore: cast_nullable_to_non_nullable
as Either<Unit, WorkspaceError>,
));
}
}
/// @nodoc
abstract class _$ViewStateCopyWith<$Res>
implements $ViewEditStateCopyWith<$Res> {
factory _$ViewStateCopyWith(
_ViewState value, $Res Function(_ViewState) then) =
__$ViewStateCopyWithImpl<$Res>;
@override
$Res call(
{bool isLoading,
Option<View> view,
Either<Unit, WorkspaceError> successOrFailure});
}
/// @nodoc
class __$ViewStateCopyWithImpl<$Res> extends _$ViewEditStateCopyWithImpl<$Res>
implements _$ViewStateCopyWith<$Res> {
__$ViewStateCopyWithImpl(_ViewState _value, $Res Function(_ViewState) _then)
: super(_value, (v) => _then(v as _ViewState));
@override
_ViewState get _value => super._value as _ViewState;
@override
$Res call({
Object? isLoading = freezed,
Object? view = freezed,
Object? successOrFailure = freezed,
}) {
return _then(_ViewState(
isLoading: isLoading == freezed
? _value.isLoading
: isLoading // ignore: cast_nullable_to_non_nullable
as bool,
view: view == freezed
? _value.view
: view // ignore: cast_nullable_to_non_nullable
as Option<View>,
successOrFailure: successOrFailure == freezed
? _value.successOrFailure
: successOrFailure // ignore: cast_nullable_to_non_nullable
as Either<Unit, WorkspaceError>,
));
}
}
/// @nodoc
class _$_ViewState implements _ViewState {
const _$_ViewState(
{required this.isLoading,
required this.view,
required this.successOrFailure});
@override
final bool isLoading;
@override
final Option<View> view;
@override
final Either<Unit, WorkspaceError> successOrFailure;
@override
String toString() {
return 'ViewEditState(isLoading: $isLoading, view: $view, successOrFailure: $successOrFailure)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other is _ViewState &&
(identical(other.isLoading, isLoading) ||
const DeepCollectionEquality()
.equals(other.isLoading, isLoading)) &&
(identical(other.view, view) ||
const DeepCollectionEquality().equals(other.view, view)) &&
(identical(other.successOrFailure, successOrFailure) ||
const DeepCollectionEquality()
.equals(other.successOrFailure, successOrFailure)));
}
@override
int get hashCode =>
runtimeType.hashCode ^
const DeepCollectionEquality().hash(isLoading) ^
const DeepCollectionEquality().hash(view) ^
const DeepCollectionEquality().hash(successOrFailure);
@JsonKey(ignore: true)
@override
_$ViewStateCopyWith<_ViewState> get copyWith =>
__$ViewStateCopyWithImpl<_ViewState>(this, _$identity);
}
abstract class _ViewState implements ViewEditState {
const factory _ViewState(
{required bool isLoading,
required Option<View> view,
required Either<Unit, WorkspaceError> successOrFailure}) = _$_ViewState;
@override
bool get isLoading => throw _privateConstructorUsedError;
@override
Option<View> get view => throw _privateConstructorUsedError;
@override
Either<Unit, WorkspaceError> get successOrFailure =>
throw _privateConstructorUsedError;
@override
@JsonKey(ignore: true)
_$ViewStateCopyWith<_ViewState> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -7,7 +7,7 @@ typedef ViewUpdatedCallback = void Function(Either<View, WorkspaceError>);
abstract class IView { abstract class IView {
View get view; View get view;
Future<Either<Unit, WorkspaceError>> pushIntoTrash(); Future<Either<Unit, WorkspaceError>> delete();
Future<Either<View, WorkspaceError>> rename(String newName); Future<Either<View, WorkspaceError>> rename(String newName);
} }

View File

@ -13,8 +13,8 @@ class IViewImpl extends IView {
View get view => repo.view; View get view => repo.view;
@override @override
Future<Either<Unit, WorkspaceError>> pushIntoTrash() { Future<Either<Unit, WorkspaceError>> delete() {
return repo.updateView(isTrash: true).then((result) { return repo.delete().then((result) {
return result.fold( return result.fold(
(_) => left(unit), (_) => left(unit),
(error) => right(error), (error) => right(error),

View File

@ -21,9 +21,7 @@ class AppRepository {
}); });
Future<Either<App, WorkspaceError>> getAppDesc() { Future<Either<App, WorkspaceError>> getAppDesc() {
final request = QueryAppRequest.create() final request = QueryAppRequest.create()..appId = appId;
..appId = appId
..readBelongings = false;
return WorkspaceEventReadApp(request).send(); return WorkspaceEventReadApp(request).send();
} }
@ -39,9 +37,7 @@ class AppRepository {
} }
Future<Either<List<View>, WorkspaceError>> getViews() { Future<Either<List<View>, WorkspaceError>> getViews() {
final request = QueryAppRequest.create() final request = QueryAppRequest.create()..appId = appId;
..appId = appId
..readBelongings = true;
return WorkspaceEventReadApp(request).send().then((result) { return WorkspaceEventReadApp(request).send().then((result) {
return result.fold( return result.fold(

View File

@ -26,7 +26,7 @@ class ViewRepository {
return WorkspaceEventReadView(request).send(); return WorkspaceEventReadView(request).send();
} }
Future<Either<View, WorkspaceError>> updateView({String? name, String? desc, bool? isTrash}) { Future<Either<View, WorkspaceError>> updateView({String? name, String? desc}) {
final request = UpdateViewRequest.create()..viewId = view.id; final request = UpdateViewRequest.create()..viewId = view.id;
if (name != null) { if (name != null) {
@ -37,15 +37,11 @@ class ViewRepository {
request.desc = desc; request.desc = desc;
} }
if (isTrash != null) {
request.isTrash = isTrash;
}
return WorkspaceEventUpdateView(request).send(); return WorkspaceEventUpdateView(request).send();
} }
Future<Either<Unit, WorkspaceError>> delete() { Future<Either<Unit, WorkspaceError>> delete() {
final request = DeleteViewRequest.create()..viewId = view.id; final request = DeleteViewRequest.create()..viewIds.add(view.id);
return WorkspaceEventDeleteView(request).send(); return WorkspaceEventDeleteView(request).send();
} }
} }

View File

@ -149,7 +149,9 @@ class _TrashStackPageState extends State<TrashStackPage> {
height: 42, height: 42,
child: TrashCell( child: TrashCell(
object: object, object: object,
onRestore: () => context.read<TrashBloc>().add(TrashEvent.putback(object.id)), onRestore: () {
context.read<TrashBloc>().add(TrashEvent.putback(object.id));
},
onDelete: () => context.read<TrashBloc>().add(TrashEvent.delete(object.id)), onDelete: () => context.read<TrashBloc>().add(TrashEvent.delete(object.id)),
), ),
); );

View File

@ -1,16 +1,15 @@
use crate::{ use crate::{
entities::{ entities::trash::{RepeatedTrash, TrashIdentifier},
trash::{RepeatedTrash, TrashIdentifier},
},
errors::WorkspaceError, errors::WorkspaceError,
services::TrashCan, services::TrashCan,
}; };
use flowy_dispatch::prelude::{data_result, Data, DataResult, Unit}; use flowy_dispatch::prelude::{data_result, Data, DataResult, Unit};
use std::{sync::Arc}; use std::sync::Arc;
#[tracing::instrument(skip(controller), err)] #[tracing::instrument(skip(controller), err)]
pub(crate) async fn read_trash_handler(controller: Unit<Arc<TrashCan>>) -> DataResult<RepeatedTrash, WorkspaceError> { pub(crate) async fn read_trash_handler(controller: Unit<Arc<TrashCan>>) -> DataResult<RepeatedTrash, WorkspaceError> {
let repeated_trash = controller.read_trash()?; let conn = controller.database.db_connection()?;
let repeated_trash = controller.read_trash(&conn)?;
data_result(repeated_trash) data_result(repeated_trash)
} }

View File

@ -41,7 +41,7 @@ impl TrashEvent {
} }
pub struct TrashCan { pub struct TrashCan {
database: Arc<dyn WorkspaceDatabase>, pub database: Arc<dyn WorkspaceDatabase>,
notify: broadcast::Sender<TrashEvent>, notify: broadcast::Sender<TrashEvent>,
} }
@ -51,16 +51,36 @@ impl TrashCan {
Self { database, notify: tx } Self { database, notify: tx }
} }
pub fn read_trash(&self) -> Result<RepeatedTrash, WorkspaceError> {
let conn = self.database.db_connection()?; pub fn read_trash(&self, conn: &SqliteConnection) -> Result<RepeatedTrash, WorkspaceError> {
let repeated_trash = TrashTableSql::read_all(&*conn)?; let repeated_trash = TrashTableSql::read_all(&*conn)?;
Ok(repeated_trash) Ok(repeated_trash)
} }
pub fn trash_ids(&self, conn: &SqliteConnection) -> Result<Vec<String>, WorkspaceError> {
let ids = TrashTableSql::read_all(&*conn)?
.take_items()
.into_iter()
.map(|item| item.id)
.collect::<Vec<String>>();
Ok(ids)
}
#[tracing::instrument(level = "debug", skip(self), fields(putback) err)] #[tracing::instrument(level = "debug", skip(self), fields(putback) err)]
pub async fn putback(&self, trash_id: &str) -> WorkspaceResult<()> { pub async fn putback(&self, trash_id: &str) -> WorkspaceResult<()> {
let (tx, mut rx) = mpsc::channel::<WorkspaceResult<()>>(1); let (tx, mut rx) = mpsc::channel::<WorkspaceResult<()>>(1);
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 conn = self.database.db_connection()?;
let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
let _ = TrashTableSql::delete_trash(trash_id, &*conn)?;
let _ = self.notify_dart_trash_did_update(&conn)?;
Ok(())
})?;
Ok::<(), WorkspaceError>(())
})
.unwrap()?;
tracing::Span::current().record( tracing::Span::current().record(
"putback", "putback",
&format!("{:?}: {}", &trash_table.ty, trash_table.id).as_str(), &format!("{:?}: {}", &trash_table.ty, trash_table.id).as_str(),
@ -70,12 +90,6 @@ impl TrashCan {
.send(TrashEvent::Putback(trash_table.ty.into(), vec![trash_table.id], tx)); .send(TrashEvent::Putback(trash_table.ty.into(), vec![trash_table.id], tx));
let _ = rx.recv().await.unwrap()?; let _ = rx.recv().await.unwrap()?;
let conn = self.database.db_connection()?;
let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
let _ = TrashTableSql::delete_trash(trash_id, &*conn)?;
let _ = self.notify_dart_trash_did_update(&conn)?;
Ok(())
})?;
Ok(()) Ok(())
} }
@ -122,10 +136,12 @@ impl TrashCan {
if trash_type.as_ref().unwrap() != &t.ty { if trash_type.as_ref().unwrap() != &t.ty {
return Err(WorkspaceError::internal()); return Err(WorkspaceError::internal());
} }
let trash_id = t.id.clone();
ids.push(t.id.clone()); log::debug!("create trash: {:?}", t);
let _ = TrashTableSql::create_trash(t.into(), &*conn)?; let _ = TrashTableSql::create_trash(t.into(), &*conn)?;
ids.push(trash_id);
} }
let _ = self.notify_dart_trash_did_update(&conn)?;
Ok(()) Ok(())
})?; })?;
Ok::<(), WorkspaceError>(()) Ok::<(), WorkspaceError>(())

View File

@ -59,12 +59,12 @@ impl ViewController {
pub(crate) async fn create_view(&self, params: CreateViewParams) -> Result<View, WorkspaceError> { pub(crate) async fn create_view(&self, params: CreateViewParams) -> Result<View, WorkspaceError> {
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();
// TODO: rollback anything created before if failed? // 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))?; 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 = ViewTableSql::read_views(&view.belong_to_id, 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)
.send(); .send();
@ -112,7 +112,7 @@ impl ViewController {
pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result<RepeatedView, WorkspaceError> { pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result<RepeatedView, WorkspaceError> {
// TODO: read from server // TODO: read from server
let conn = self.database.db_connection()?; let conn = self.database.db_connection()?;
let repeated_view = ViewTableSql::read_views(belong_to_id, &*conn)?; let repeated_view = read_belonging_view(belong_to_id, self.trash_can.clone(), &conn)?;
Ok(repeated_view) Ok(repeated_view)
} }
@ -165,22 +165,22 @@ impl ViewController {
Ok(()) Ok(())
} }
#[tracing::instrument(skip(self), err)] // #[tracing::instrument(skip(self), err)]
fn delete_view_on_server(&self, view_ids: Vec<String>) -> Result<(), WorkspaceError> { // fn delete_view_on_server(&self, view_ids: Vec<String>) -> Result<(),
let token = self.user.token()?; // WorkspaceError> { let token = self.user.token()?;
let server = self.server.clone(); // let server = self.server.clone();
let params = DeleteViewParams { view_ids }; // let params = DeleteViewParams { view_ids };
spawn(async move { // spawn(async move {
match server.delete_view(&token, params).await { // match server.delete_view(&token, params).await {
Ok(_) => {}, // Ok(_) => {},
Err(e) => { // Err(e) => {
// TODO: retry? // // TODO: retry?
log::error!("Delete view failed: {:?}", e); // log::error!("Delete view failed: {:?}", e);
}, // },
} // }
}); // });
Ok(()) // Ok(())
} // }
#[tracing::instrument(skip(self), err)] #[tracing::instrument(skip(self), err)]
fn read_view_on_server(&self, params: ViewIdentifier) -> Result<(), WorkspaceError> { fn read_view_on_server(&self, params: ViewIdentifier) -> Result<(), WorkspaceError> {
@ -202,6 +202,7 @@ impl ViewController {
let mut rx = self.trash_can.subscribe(); let mut rx = self.trash_can.subscribe();
let database = self.database.clone(); let database = self.database.clone();
let document = self.document.clone(); let document = self.document.clone();
let trash_can = self.trash_can.clone();
let _ = tokio::spawn(async move { let _ = tokio::spawn(async move {
loop { loop {
let mut stream = Box::pin(rx.recv().into_stream().filter_map(|result| async move { let mut stream = Box::pin(rx.recv().into_stream().filter_map(|result| async move {
@ -212,7 +213,9 @@ impl ViewController {
})); }));
let event: Option<TrashEvent> = stream.next().await; let event: Option<TrashEvent> = stream.next().await;
match event { match event {
Some(event) => handle_trash_event(database.clone(), document.clone(), event), Some(event) => {
handle_trash_event(database.clone(), document.clone(), trash_can.clone(), event).await
},
None => {}, None => {},
} }
} }
@ -220,7 +223,12 @@ impl ViewController {
} }
} }
fn handle_trash_event(database: Arc<dyn WorkspaceDatabase>, document: Arc<FlowyDocument>, event: TrashEvent) { async fn handle_trash_event(
database: Arc<dyn WorkspaceDatabase>,
document: Arc<FlowyDocument>,
trash_can: Arc<TrashCan>,
event: TrashEvent,
) {
let db_result = database.db_connection(); let db_result = database.db_connection();
match event { match event {
@ -229,13 +237,13 @@ fn handle_trash_event(database: Arc<dyn WorkspaceDatabase>, document: Arc<FlowyD
let conn = &*db_result?; let conn = &*db_result?;
let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| { let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
for view_id in view_ids { for view_id in view_ids {
let _ = notify_view_num_did_change(&view_id, conn)?; let _ = notify_view_num_did_change(&view_id, conn, trash_can.clone())?;
} }
Ok(()) Ok(())
})?; })?;
Ok::<(), WorkspaceError>(()) Ok::<(), WorkspaceError>(())
}; };
let _ = ret.send(result()); let _ = ret.send(result()).await;
}, },
TrashEvent::Delete(_, delete_ids, ret) => { TrashEvent::Delete(_, delete_ids, ret) => {
let result = || { let result = || {
@ -244,22 +252,34 @@ fn handle_trash_event(database: Arc<dyn WorkspaceDatabase>, document: Arc<FlowyD
for view_id in delete_ids { for view_id in delete_ids {
let _ = ViewTableSql::delete_view(&view_id, conn)?; let _ = ViewTableSql::delete_view(&view_id, conn)?;
let _ = document.delete(view_id.clone().into())?; let _ = document.delete(view_id.clone().into())?;
let _ = notify_view_num_did_change(&view_id, conn)?; let _ = notify_view_num_did_change(&view_id, conn, trash_can.clone())?;
} }
Ok(()) Ok(())
})?; })?;
Ok::<(), WorkspaceError>(()) Ok::<(), WorkspaceError>(())
}; };
let _ = ret.send(result()); let _ = ret.send(result()).await;
}, },
} }
} }
fn notify_view_num_did_change(view_id: &str, conn: &SqliteConnection) -> WorkspaceResult<()> { #[tracing::instrument(skip(conn, trash_can), err)]
fn notify_view_num_did_change(view_id: &str, conn: &SqliteConnection, trash_can: Arc<TrashCan>) -> WorkspaceResult<()> {
let view_table = ViewTableSql::read_view(view_id, conn)?; let view_table = ViewTableSql::read_view(view_id, conn)?;
let repeated_view = ViewTableSql::read_views(&view_table.belong_to_id, conn)?; let repeated_view = read_belonging_view(&view_table.belong_to_id, trash_can, conn)?;
send_dart_notification(&view_table.belong_to_id, WorkspaceNotification::AppViewsChanged) send_dart_notification(&view_table.belong_to_id, WorkspaceNotification::AppViewsChanged)
.payload(repeated_view) .payload(repeated_view)
.send(); .send();
Ok(()) Ok(())
} }
fn read_belonging_view(
belong_to_id: &str,
trash_can: Arc<TrashCan>,
conn: &SqliteConnection,
) -> WorkspaceResult<RepeatedView> {
let mut repeated_view = ViewTableSql::read_views(belong_to_id, conn)?;
let trash_ids = trash_can.trash_ids(conn)?;
repeated_view.retain(|view| !trash_ids.contains(&view.id));
Ok(repeated_view)
}

View File

@ -35,29 +35,16 @@ impl ViewTableSql {
.filter(view_table::id.eq(view_id)) .filter(view_table::id.eq(view_id))
.first::<ViewTable>(conn)?; .first::<ViewTable>(conn)?;
let repeated_trash: Vec<String> = trash_table::dsl::trash_table.select(trash_table::dsl::id).load(conn)?;
if repeated_trash.contains(&view_table.id) {
return Err(WorkspaceError::not_found());
}
Ok(view_table) Ok(view_table)
} }
// belong_to_id will be the app_id or view_id. // belong_to_id will be the app_id or view_id.
pub(crate) fn read_views(belong_to_id: &str, conn: &SqliteConnection) -> Result<RepeatedView, WorkspaceError> { pub(crate) fn read_views(belong_to_id: &str, conn: &SqliteConnection) -> Result<RepeatedView, WorkspaceError> {
let mut view_tables = dsl::view_table let view_tables = dsl::view_table
.filter(view_table::belong_to_id.eq(belong_to_id)) .filter(view_table::belong_to_id.eq(belong_to_id))
.into_boxed() .into_boxed()
.load::<ViewTable>(conn)?; .load::<ViewTable>(conn)?;
let repeated_trash: Vec<String> = trash_table::dsl::trash_table.select(trash_table::dsl::id).load(conn)?;
view_tables = view_tables
.into_iter()
.filter(|table| !repeated_trash.contains(&table.id))
.collect::<Vec<ViewTable>>();
let views = view_tables let views = view_tables
.into_iter() .into_iter()
.map(|view_table| view_table.into()) .map(|view_table| view_table.into())