소스 검색

[client]: crud with trash on server & fix some bugs

appflowy 3 년 전
부모
커밋
d1b7fd29a6
45개의 변경된 파일810개의 추가작업 그리고 838개의 파일을 삭제
  1. 1 1
      app_flowy/lib/workspace/application/trash/trash_bloc.dart
  2. 1 1
      app_flowy/lib/workspace/domain/i_trash.dart
  3. 2 2
      app_flowy/lib/workspace/infrastructure/i_trash_impl.dart
  4. 7 4
      app_flowy/lib/workspace/infrastructure/repos/trash_repo.dart
  5. 29 1
      app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart
  6. 0 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/protobuf.dart
  7. 54 13
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_create.pb.dart
  8. 15 5
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_create.pbjson.dart
  9. 0 99
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pb.dart
  10. 0 7
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pbenum.dart
  11. 0 30
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pbjson.dart
  12. 0 9
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pbserver.dart
  13. 2 2
      backend/src/application.rs
  14. 12 13
      backend/src/service/trash/router.rs
  15. 15 14
      backend/src/service/trash/trash.rs
  16. 4 23
      backend/tests/api/workspace.rs
  17. 17 4
      backend/tests/helper.rs
  18. 1 2
      rust-lib/flowy-derive/src/derive_cache/derive_cache.rs
  19. 1 1
      rust-lib/flowy-infra/Cargo.toml
  20. 22 4
      rust-lib/flowy-infra/src/retry/future.rs
  21. 2 0
      rust-lib/flowy-net/src/config.rs
  22. 1 1
      rust-lib/flowy-workspace/src/entities/app/app_delete.rs
  23. 0 2
      rust-lib/flowy-workspace/src/entities/trash/mod.rs
  24. 22 1
      rust-lib/flowy-workspace/src/entities/trash/trash_create.rs
  25. 0 13
      rust-lib/flowy-workspace/src/entities/trash/trash_delete.rs
  26. 1 1
      rust-lib/flowy-workspace/src/event.rs
  27. 4 4
      rust-lib/flowy-workspace/src/handlers/trash_handler.rs
  28. 1 1
      rust-lib/flowy-workspace/src/module.rs
  29. 0 3
      rust-lib/flowy-workspace/src/protobuf/model/mod.rs
  30. 233 62
      rust-lib/flowy-workspace/src/protobuf/model/trash_create.rs
  31. 0 368
      rust-lib/flowy-workspace/src/protobuf/model/trash_delete.rs
  32. 4 1
      rust-lib/flowy-workspace/src/protobuf/proto/trash_create.proto
  33. 0 8
      rust-lib/flowy-workspace/src/protobuf/proto/trash_delete.proto
  34. 37 18
      rust-lib/flowy-workspace/src/services/app_controller.rs
  35. 1 0
      rust-lib/flowy-workspace/src/services/mod.rs
  36. 8 0
      rust-lib/flowy-workspace/src/services/server/mod.rs
  37. 20 4
      rust-lib/flowy-workspace/src/services/server/server_api.rs
  38. 16 0
      rust-lib/flowy-workspace/src/services/server/server_api_mock.rs
  39. 164 73
      rust-lib/flowy-workspace/src/services/trash_can.rs
  40. 72 0
      rust-lib/flowy-workspace/src/services/util.rs
  41. 30 31
      rust-lib/flowy-workspace/src/services/view_controller.rs
  42. 1 0
      rust-lib/flowy-workspace/src/services/workspace_controller.rs
  43. 4 9
      rust-lib/flowy-workspace/src/sql_tables/app/app_sql.rs
  44. 1 1
      rust-lib/flowy-workspace/src/sql_tables/view/view_sql.rs
  45. 5 1
      rust-lib/flowy-workspace/tests/workspace/view_test.rs

+ 1 - 1
app_flowy/lib/workspace/application/trash/trash_bloc.dart

@@ -34,7 +34,7 @@ class TrashBloc extends Bloc<TrashEvent, TrashState> {
         );
       },
       delete: (e) async* {
-        final result = await iTrash.delete(e.trashId);
+        final result = await iTrash.deleteViews([e.trashId]);
         result.fold((l) {}, (error) {});
       },
       deleteAll: (e) async* {},

+ 1 - 1
app_flowy/lib/workspace/domain/i_trash.dart

@@ -8,7 +8,7 @@ abstract class ITrash {
 
   Future<Either<Unit, WorkspaceError>> putback(String trashId);
 
-  Future<Either<Unit, WorkspaceError>> delete(String trashId);
+  Future<Either<Unit, WorkspaceError>> deleteViews(List<String> trashIds);
 }
 
 typedef TrashUpdatedCallback = void Function(Either<List<Trash>, WorkspaceError> trashOrFailed);

+ 2 - 2
app_flowy/lib/workspace/infrastructure/i_trash_impl.dart

@@ -25,8 +25,8 @@ class ITrashImpl implements ITrash {
   }
 
   @override
-  Future<Either<Unit, WorkspaceError>> delete(String trashId) {
-    return repo.delete(trashId);
+  Future<Either<Unit, WorkspaceError>> deleteViews(List<String> trashIds) {
+    return repo.deleteViews(trashIds);
   }
 }
 

+ 7 - 4
app_flowy/lib/workspace/infrastructure/repos/trash_repo.dart

@@ -8,7 +8,6 @@ import 'package:flowy_sdk/protobuf/flowy-dart-notify/subject.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/observable.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/trash_create.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-workspace/trash_delete.pb.dart';
 import 'package:flowy_sdk/rust_stream.dart';
 
 class TrashRepo {
@@ -22,9 +21,13 @@ class TrashRepo {
     return WorkspaceEventPutbackTrash(id).send();
   }
 
-  Future<Either<Unit, WorkspaceError>> delete(String trashId) {
-    final id = TrashIdentifier.create()..id = trashId;
-    return WorkspaceEventDeleteTrash(id).send();
+  Future<Either<Unit, WorkspaceError>> deleteViews(List<String> viewIds) {
+    final trashIdentifiers = TrashIdentifiers(
+        items: viewIds.map((id) => TrashIdentifier.create()
+          ..id = id
+          ..ty = TrashType.View));
+
+    return WorkspaceEventDeleteTrash(trashIdentifiers).send();
   }
 }
 

+ 29 - 1
app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart

@@ -303,7 +303,7 @@ class WorkspaceEventPutbackTrash {
 }
 
 class WorkspaceEventDeleteTrash {
-     TrashIdentifier request;
+     TrashIdentifiers request;
      WorkspaceEventDeleteTrash(this.request);
 
     Future<Either<Unit, WorkspaceError>> send() {
@@ -319,6 +319,34 @@ class WorkspaceEventDeleteTrash {
     }
 }
 
+class WorkspaceEventRestoreAll {
+    WorkspaceEventRestoreAll();
+
+    Future<Either<Unit, WorkspaceError>> send() {
+     final request = FFIRequest.create()
+        ..event = WorkspaceEvent.RestoreAll.toString();
+
+     return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
+        (bytes) => left(unit),
+        (errBytes) => right(WorkspaceError.fromBuffer(errBytes)),
+      ));
+    }
+}
+
+class WorkspaceEventDeleteAll {
+    WorkspaceEventDeleteAll();
+
+    Future<Either<Unit, WorkspaceError>> send() {
+     final request = FFIRequest.create()
+        ..event = WorkspaceEvent.DeleteAll.toString();
+
+     return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
+        (bytes) => left(unit),
+        (errBytes) => right(WorkspaceError.fromBuffer(errBytes)),
+      ));
+    }
+}
+
 class WorkspaceEventInitWorkspace {
     WorkspaceEventInitWorkspace();
 

+ 0 - 1
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/protobuf.dart

@@ -5,7 +5,6 @@ export './app_query.pb.dart';
 export './workspace_delete.pb.dart';
 export './observable.pb.dart';
 export './errors.pb.dart';
-export './trash_delete.pb.dart';
 export './workspace_update.pb.dart';
 export './app_create.pb.dart';
 export './workspace_query.pb.dart';

+ 54 - 13
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_create.pb.dart

@@ -14,15 +14,56 @@ import 'trash_create.pbenum.dart';
 
 export 'trash_create.pbenum.dart';
 
-class CreateTrashParams extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CreateTrashParams', createEmptyInstance: create)
+class TrashIdentifiers extends $pb.GeneratedMessage {
+  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)
+    ..hasRequiredFields = false
+  ;
+
+  TrashIdentifiers._() : super();
+  factory TrashIdentifiers({
+    $core.Iterable<TrashIdentifier>? items,
+  }) {
+    final _result = create();
+    if (items != null) {
+      _result.items.addAll(items);
+    }
+    return _result;
+  }
+  factory TrashIdentifiers.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory TrashIdentifiers.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  TrashIdentifiers clone() => TrashIdentifiers()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  TrashIdentifiers copyWith(void Function(TrashIdentifiers) updates) => super.copyWith((message) => updates(message as TrashIdentifiers)) as TrashIdentifiers; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static TrashIdentifiers create() => TrashIdentifiers._();
+  TrashIdentifiers createEmptyInstance() => create();
+  static $pb.PbList<TrashIdentifiers> createRepeated() => $pb.PbList<TrashIdentifiers>();
+  @$core.pragma('dart2js:noInline')
+  static TrashIdentifiers getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<TrashIdentifiers>(create);
+  static TrashIdentifiers? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<TrashIdentifier> get items => $_getList(0);
+}
+
+class TrashIdentifier extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'TrashIdentifier', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
     ..e<TrashType>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'ty', $pb.PbFieldType.OE, defaultOrMaker: TrashType.Unknown, valueOf: TrashType.valueOf, enumValues: TrashType.values)
     ..hasRequiredFields = false
   ;
 
-  CreateTrashParams._() : super();
-  factory CreateTrashParams({
+  TrashIdentifier._() : super();
+  factory TrashIdentifier({
     $core.String? id,
     TrashType? ty,
   }) {
@@ -35,26 +76,26 @@ class CreateTrashParams extends $pb.GeneratedMessage {
     }
     return _result;
   }
-  factory CreateTrashParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory CreateTrashParams.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  factory TrashIdentifier.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory TrashIdentifier.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
   'Will be removed in next major version')
-  CreateTrashParams clone() => CreateTrashParams()..mergeFromMessage(this);
+  TrashIdentifier clone() => TrashIdentifier()..mergeFromMessage(this);
   @$core.Deprecated(
   'Using this can add significant overhead to your binary. '
   'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
   'Will be removed in next major version')
-  CreateTrashParams copyWith(void Function(CreateTrashParams) updates) => super.copyWith((message) => updates(message as CreateTrashParams)) as CreateTrashParams; // ignore: deprecated_member_use
+  TrashIdentifier copyWith(void Function(TrashIdentifier) updates) => super.copyWith((message) => updates(message as TrashIdentifier)) as TrashIdentifier; // ignore: deprecated_member_use
   $pb.BuilderInfo get info_ => _i;
   @$core.pragma('dart2js:noInline')
-  static CreateTrashParams create() => CreateTrashParams._();
-  CreateTrashParams createEmptyInstance() => create();
-  static $pb.PbList<CreateTrashParams> createRepeated() => $pb.PbList<CreateTrashParams>();
+  static TrashIdentifier create() => TrashIdentifier._();
+  TrashIdentifier createEmptyInstance() => create();
+  static $pb.PbList<TrashIdentifier> createRepeated() => $pb.PbList<TrashIdentifier>();
   @$core.pragma('dart2js:noInline')
-  static CreateTrashParams getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CreateTrashParams>(create);
-  static CreateTrashParams? _defaultInstance;
+  static TrashIdentifier getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<TrashIdentifier>(create);
+  static TrashIdentifier? _defaultInstance;
 
   @$pb.TagNumber(1)
   $core.String get id => $_getSZ(0);

+ 15 - 5
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_create.pbjson.dart

@@ -19,17 +19,27 @@ const TrashType$json = const {
 
 /// Descriptor for `TrashType`. Decode as a `google.protobuf.EnumDescriptorProto`.
 final $typed_data.Uint8List trashTypeDescriptor = $convert.base64Decode('CglUcmFzaFR5cGUSCwoHVW5rbm93bhAAEggKBFZpZXcQAQ==');
-@$core.Deprecated('Use createTrashParamsDescriptor instead')
-const CreateTrashParams$json = const {
-  '1': 'CreateTrashParams',
+@$core.Deprecated('Use trashIdentifiersDescriptor instead')
+const TrashIdentifiers$json = const {
+  '1': 'TrashIdentifiers',
+  '2': const [
+    const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.TrashIdentifier', '10': 'items'},
+  ],
+};
+
+/// Descriptor for `TrashIdentifiers`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List trashIdentifiersDescriptor = $convert.base64Decode('ChBUcmFzaElkZW50aWZpZXJzEiYKBWl0ZW1zGAEgAygLMhAuVHJhc2hJZGVudGlmaWVyUgVpdGVtcw==');
+@$core.Deprecated('Use trashIdentifierDescriptor instead')
+const TrashIdentifier$json = const {
+  '1': 'TrashIdentifier',
   '2': const [
     const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
     const {'1': 'ty', '3': 2, '4': 1, '5': 14, '6': '.TrashType', '10': 'ty'},
   ],
 };
 
-/// Descriptor for `CreateTrashParams`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List createTrashParamsDescriptor = $convert.base64Decode('ChFDcmVhdGVUcmFzaFBhcmFtcxIOCgJpZBgBIAEoCVICaWQSGgoCdHkYAiABKA4yCi5UcmFzaFR5cGVSAnR5');
+/// Descriptor for `TrashIdentifier`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List trashIdentifierDescriptor = $convert.base64Decode('Cg9UcmFzaElkZW50aWZpZXISDgoCaWQYASABKAlSAmlkEhoKAnR5GAIgASgOMgouVHJhc2hUeXBlUgJ0eQ==');
 @$core.Deprecated('Use trashDescriptor instead')
 const Trash$json = const {
   '1': 'Trash',

+ 0 - 99
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pb.dart

@@ -1,99 +0,0 @@
-///
-//  Generated code. Do not modify.
-//  source: trash_delete.proto
-//
-// @dart = 2.12
-// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
-
-import 'dart:core' as $core;
-
-import 'package:protobuf/protobuf.dart' as $pb;
-
-class TrashIdentifier extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'TrashIdentifier', createEmptyInstance: create)
-    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
-    ..hasRequiredFields = false
-  ;
-
-  TrashIdentifier._() : super();
-  factory TrashIdentifier({
-    $core.String? id,
-  }) {
-    final _result = create();
-    if (id != null) {
-      _result.id = id;
-    }
-    return _result;
-  }
-  factory TrashIdentifier.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory TrashIdentifier.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
-  @$core.Deprecated(
-  'Using this can add significant overhead to your binary. '
-  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
-  'Will be removed in next major version')
-  TrashIdentifier clone() => TrashIdentifier()..mergeFromMessage(this);
-  @$core.Deprecated(
-  'Using this can add significant overhead to your binary. '
-  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
-  'Will be removed in next major version')
-  TrashIdentifier copyWith(void Function(TrashIdentifier) updates) => super.copyWith((message) => updates(message as TrashIdentifier)) as TrashIdentifier; // ignore: deprecated_member_use
-  $pb.BuilderInfo get info_ => _i;
-  @$core.pragma('dart2js:noInline')
-  static TrashIdentifier create() => TrashIdentifier._();
-  TrashIdentifier createEmptyInstance() => create();
-  static $pb.PbList<TrashIdentifier> createRepeated() => $pb.PbList<TrashIdentifier>();
-  @$core.pragma('dart2js:noInline')
-  static TrashIdentifier getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<TrashIdentifier>(create);
-  static TrashIdentifier? _defaultInstance;
-
-  @$pb.TagNumber(1)
-  $core.String get id => $_getSZ(0);
-  @$pb.TagNumber(1)
-  set id($core.String v) { $_setString(0, v); }
-  @$pb.TagNumber(1)
-  $core.bool hasId() => $_has(0);
-  @$pb.TagNumber(1)
-  void clearId() => clearField(1);
-}
-
-class TrashIdentifiers extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'TrashIdentifiers', createEmptyInstance: create)
-    ..pPS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'ids')
-    ..hasRequiredFields = false
-  ;
-
-  TrashIdentifiers._() : super();
-  factory TrashIdentifiers({
-    $core.Iterable<$core.String>? ids,
-  }) {
-    final _result = create();
-    if (ids != null) {
-      _result.ids.addAll(ids);
-    }
-    return _result;
-  }
-  factory TrashIdentifiers.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
-  factory TrashIdentifiers.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
-  @$core.Deprecated(
-  'Using this can add significant overhead to your binary. '
-  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
-  'Will be removed in next major version')
-  TrashIdentifiers clone() => TrashIdentifiers()..mergeFromMessage(this);
-  @$core.Deprecated(
-  'Using this can add significant overhead to your binary. '
-  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
-  'Will be removed in next major version')
-  TrashIdentifiers copyWith(void Function(TrashIdentifiers) updates) => super.copyWith((message) => updates(message as TrashIdentifiers)) as TrashIdentifiers; // ignore: deprecated_member_use
-  $pb.BuilderInfo get info_ => _i;
-  @$core.pragma('dart2js:noInline')
-  static TrashIdentifiers create() => TrashIdentifiers._();
-  TrashIdentifiers createEmptyInstance() => create();
-  static $pb.PbList<TrashIdentifiers> createRepeated() => $pb.PbList<TrashIdentifiers>();
-  @$core.pragma('dart2js:noInline')
-  static TrashIdentifiers getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<TrashIdentifiers>(create);
-  static TrashIdentifiers? _defaultInstance;
-
-  @$pb.TagNumber(1)
-  $core.List<$core.String> get ids => $_getList(0);
-}
-

+ 0 - 7
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pbenum.dart

@@ -1,7 +0,0 @@
-///
-//  Generated code. Do not modify.
-//  source: trash_delete.proto
-//
-// @dart = 2.12
-// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
-

+ 0 - 30
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pbjson.dart

@@ -1,30 +0,0 @@
-///
-//  Generated code. Do not modify.
-//  source: trash_delete.proto
-//
-// @dart = 2.12
-// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
-
-import 'dart:core' as $core;
-import 'dart:convert' as $convert;
-import 'dart:typed_data' as $typed_data;
-@$core.Deprecated('Use trashIdentifierDescriptor instead')
-const TrashIdentifier$json = const {
-  '1': 'TrashIdentifier',
-  '2': const [
-    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
-  ],
-};
-
-/// Descriptor for `TrashIdentifier`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List trashIdentifierDescriptor = $convert.base64Decode('Cg9UcmFzaElkZW50aWZpZXISDgoCaWQYASABKAlSAmlk');
-@$core.Deprecated('Use trashIdentifiersDescriptor instead')
-const TrashIdentifiers$json = const {
-  '1': 'TrashIdentifiers',
-  '2': const [
-    const {'1': 'ids', '3': 1, '4': 3, '5': 9, '10': 'ids'},
-  ],
-};
-
-/// Descriptor for `TrashIdentifiers`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List trashIdentifiersDescriptor = $convert.base64Decode('ChBUcmFzaElkZW50aWZpZXJzEhAKA2lkcxgBIAMoCVIDaWRz');

+ 0 - 9
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pbserver.dart

@@ -1,9 +0,0 @@
-///
-//  Generated code. Do not modify.
-//  source: trash_delete.proto
-//
-// @dart = 2.12
-// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
-
-export 'trash_delete.pb.dart';
-

+ 2 - 2
backend/src/application.rs

@@ -2,7 +2,7 @@ use std::{net::TcpListener, time::Duration};
 
 use actix::Actor;
 use actix_identity::{CookieIdentityPolicy, IdentityService};
-use actix_web::{dev::Server, web, web::Data, App, HttpServer, Scope};
+use actix_web::{dev::Server, middleware, web, web::Data, App, HttpServer, Scope};
 use sqlx::{postgres::PgPoolOptions, PgPool};
 use tokio::time::interval;
 
@@ -51,7 +51,7 @@ pub fn run(listener: TcpListener, app_ctx: AppContext) -> Result<Server, std::io
 
     let server = HttpServer::new(move || {
         App::new()
-            // .wrap(middleware::Logger::default())
+            .wrap(middleware::Logger::default())
             .wrap(identify_service(&domain, &secret))
             .wrap(crate::middleware::default_cors())
             .wrap(crate::middleware::AuthenticationService)

+ 12 - 13
backend/src/service/trash/router.rs

@@ -15,25 +15,24 @@ use flowy_net::{
 };
 use flowy_workspace::{
     entities::trash::parser::{TrashId, TrashTypeParser},
-    protobuf::{CreateTrashParams, TrashIdentifiers},
+    protobuf::TrashIdentifiers,
 };
 use sqlx::PgPool;
 use uuid::Uuid;
 
+#[tracing::instrument(skip(payload, pool, logged_user), err)]
 pub async fn create_handler(
     payload: Payload,
     pool: Data<PgPool>,
     logged_user: LoggedUser,
 ) -> Result<HttpResponse, ServerError> {
-    let params: CreateTrashParams = parse_from_payload(payload).await?;
+    let params: TrashIdentifiers = parse_from_payload(payload).await?;
     let mut transaction = pool
         .begin()
         .await
         .context("Failed to acquire a Postgres connection to create trash")?;
-
-    let trash_id = check_trash_id(params.id)?;
-    let ty = TrashTypeParser::parse(params.ty.value()).map_err(invalid_params)?;
-    let _ = create_trash(&mut transaction, trash_id, ty, logged_user).await?;
+    log::error!("😁create handler: {:?}", params);
+    let _ = create_trash(&mut transaction, make_records(params)?, logged_user).await?;
 
     transaction
         .commit()
@@ -54,8 +53,7 @@ pub async fn delete_handler(
         .await
         .context("Failed to acquire a Postgres connection to delete trash")?;
 
-    let trash_ids = check_trash_ids(params.ids.into_vec())?;
-    let _ = delete_trash(&mut transaction, trash_ids, &logged_user).await?;
+    let _ = delete_trash(&mut transaction, make_records(params)?, &logged_user).await?;
     transaction
         .commit()
         .await
@@ -86,10 +84,11 @@ fn check_trash_id(id: String) -> Result<Uuid, ServerError> {
     Ok(trash_id)
 }
 
-fn check_trash_ids(ids: Vec<String>) -> Result<Vec<Uuid>, ServerError> {
-    let mut trash_ids = vec![];
-    for id in ids {
-        trash_ids.push(check_trash_id(id)?)
+fn make_records(identifiers: TrashIdentifiers) -> Result<Vec<(Uuid, i32)>, ServerError> {
+    let mut records = vec![];
+    for identifier in identifiers.items {
+        let ty = TrashTypeParser::parse(identifier.ty.value()).map_err(invalid_params)?;
+        records.push((check_trash_id(identifier.id.to_owned())?, ty));
     }
-    Ok(trash_ids)
+    Ok(records)
 }

+ 15 - 14
backend/src/service/trash/trash.rs

@@ -8,36 +8,37 @@ use crate::{
 };
 use ::protobuf::ProtobufEnum;
 use flowy_net::errors::ServerError;
-use flowy_workspace::protobuf::{RepeatedTrash, Trash, TrashIdentifiers, TrashType};
+use flowy_workspace::protobuf::{RepeatedTrash, Trash, TrashType};
 use sqlx::{postgres::PgArguments, Postgres};
 use uuid::Uuid;
 
 pub(crate) async fn create_trash(
     transaction: &mut DBTransaction<'_>,
-    trash_id: Uuid,
-    ty: i32,
+    records: Vec<(Uuid, i32)>,
     user: LoggedUser,
 ) -> Result<(), ServerError> {
-    let (sql, args) = SqlBuilder::create(TRASH_TABLE)
-        .add_arg("id", trash_id)
-        .add_arg("user_id", &user.user_id)
-        .add_arg("ty", ty)
-        .build()?;
+    for (trash_id, ty) in records {
+        let (sql, args) = SqlBuilder::create(TRASH_TABLE)
+            .add_arg("id", trash_id)
+            .add_arg("user_id", &user.user_id)
+            .add_arg("ty", ty)
+            .build()?;
 
-    let _ = sqlx::query_with(&sql, args)
-        .execute(transaction)
-        .await
-        .map_err(map_sqlx_error)?;
+        let _ = sqlx::query_with(&sql, args)
+            .execute(transaction as &mut DBTransaction<'_>)
+            .await
+            .map_err(map_sqlx_error)?;
+    }
 
     Ok(())
 }
 
 pub(crate) async fn delete_trash(
     transaction: &mut DBTransaction<'_>,
-    trash_ids: Vec<Uuid>,
+    records: Vec<(Uuid, i32)>,
     _user: &LoggedUser,
 ) -> Result<(), ServerError> {
-    for trash_id in trash_ids {
+    for (trash_id, _) in records {
         // Read the trash_table and delete the original table according to the TrashType
         let (sql, args) = SqlBuilder::select(TRASH_TABLE)
             .add_field("*")

+ 4 - 23
backend/tests/api/workspace.rs

@@ -1,7 +1,6 @@
 use crate::helper::*;
 use flowy_workspace::entities::{
     app::{AppIdentifier, DeleteAppParams, UpdateAppParams},
-    trash::{CreateTrashParams, TrashIdentifiers, TrashType},
     view::{UpdateViewParams, ViewIdentifier},
     workspace::{CreateWorkspaceParams, DeleteWorkspaceParams, QueryWorkspaceParams, UpdateWorkspaceParams},
 };
@@ -98,11 +97,7 @@ async fn app_read_with_belongs_in_trash() {
     let _ = create_test_view(&test.server, &test.app.id).await;
     let view = create_test_view(&test.server, &test.app.id).await;
 
-    let params = CreateTrashParams {
-        id: view.id.clone(),
-        ty: TrashType::View,
-    };
-    test.server.create_trash(params).await;
+    test.server.create_view_trash(&view.id).await;
 
     let read_params = AppIdentifier::new(&test.app.id);
     let app = test.server.read_app(read_params).await.unwrap();
@@ -159,13 +154,7 @@ async fn view_update() {
 #[actix_rt::test]
 async fn view_delete() {
     let test = ViewTest::new().await;
-
-    // delete
-    let params = CreateTrashParams {
-        id: test.view.id.clone(),
-        ty: TrashType::View,
-    };
-    test.server.create_trash(params).await;
+    test.server.create_view_trash(&test.view.id).await;
 
     let trash_ids = test
         .server
@@ -186,16 +175,8 @@ async fn view_delete() {
 #[actix_rt::test]
 async fn view_delete_and_then_delete_the_trash_record() {
     let test = ViewTest::new().await;
-    let params = CreateTrashParams {
-        id: test.view.id.clone(),
-        ty: TrashType::View,
-    };
-    test.server.create_trash(params).await;
-    test.server
-        .delete_trash(TrashIdentifiers {
-            ids: vec![test.view.id.clone()],
-        })
-        .await;
+    test.server.create_view_trash(&test.view.id).await;
+    test.server.delete_view_trash(&test.view.id).await;
 
     assert_eq!(test.server.read_trash().await.is_empty(), true);
 }

+ 17 - 4
backend/tests/helper.rs

@@ -122,14 +122,27 @@ impl TestUserServer {
         delete_view_request(self.user_token(), params, &url).await.unwrap();
     }
 
-    pub async fn create_trash(&self, params: CreateTrashParams) {
+    pub async fn create_view_trash(&self, view_id: &str) {
+        let identifier = TrashIdentifier {
+            id: view_id.to_string(),
+            ty: TrashType::View,
+        };
         let url = format!("{}/api/trash", self.http_addr());
-        create_trash_request(self.user_token(), params, &url).await.unwrap();
+        create_trash_request(self.user_token(), vec![identifier].into(), &url)
+            .await
+            .unwrap();
     }
 
-    pub async fn delete_trash(&self, params: TrashIdentifiers) {
+    pub async fn delete_view_trash(&self, trash_id: &str) {
         let url = format!("{}/api/trash", self.http_addr());
-        delete_trash_request(self.user_token(), params, &url).await.unwrap();
+
+        let identifier = TrashIdentifier {
+            id: trash_id.to_string(),
+            ty: TrashType::View,
+        };
+        delete_trash_request(self.user_token(), vec![identifier].into(), &url)
+            .await
+            .unwrap();
     }
 
     pub async fn read_trash(&self) -> RepeatedTrash {

+ 1 - 2
rust-lib/flowy-derive/src/derive_cache/derive_cache.rs

@@ -38,9 +38,8 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "QueryWorkspaceRequest"
         | "QueryWorkspaceParams"
         | "CurrentWorkspace"
-        | "TrashIdentifier"
         | "TrashIdentifiers"
-        | "CreateTrashParams"
+        | "TrashIdentifier"
         | "Trash"
         | "RepeatedTrash"
         | "UpdateViewRequest"

+ 1 - 1
rust-lib/flowy-infra/Cargo.toml

@@ -19,5 +19,5 @@ chrono = "0.4.19"
 bytes = { version = "1.0" }
 pin-project = "1.0"
 futures-core = { version = "0.3", default-features = false }
-tokio = { version = "1.0", features = ["time"] }
+tokio = { version = "1.0", features = ["time", "rt"] }
 rand = "0.8.3"

+ 22 - 4
rust-lib/flowy-infra/src/retry/future.rs

@@ -1,3 +1,4 @@
+use crate::retry::FixedInterval;
 use pin_project::pin_project;
 use std::{
     future::Future,
@@ -5,7 +6,10 @@ use std::{
     pin::Pin,
     task::{Context, Poll},
 };
-use tokio::time::{sleep_until, Duration, Instant, Sleep};
+use tokio::{
+    task::JoinHandle,
+    time::{sleep_until, Duration, Instant, Sleep},
+};
 
 #[pin_project(project = RetryStateProj)]
 enum RetryState<A>
@@ -171,9 +175,8 @@ pub trait Action: Send + Sync {
 
     fn run(&mut self) -> Self::Future;
 }
-
-// impl<R, E, T: Future<Output = Result<R, E>>, F: FnMut() -> T> Action for F {
-//     type Future = T;
+// impl<R, E, T: Future<Output = Result<R, E>>, F: FnMut() -> T + Send + Sync>
+// Action for F {     type Future = T;
 //     type Item = R;
 //     type Error = E;
 //
@@ -187,3 +190,18 @@ pub trait Condition<E> {
 impl<E, F: FnMut(&E) -> bool> Condition<E> for F {
     fn should_retry(&mut self, error: &E) -> bool { self(error) }
 }
+
+pub fn spawn_retry<A: Action + 'static>(
+    millis: u64,
+    retry_count: usize,
+    action: A,
+) -> JoinHandle<Result<A::Item, A::Error>>
+where
+    A::Item: Send + Sync,
+    A::Error: Send + Sync,
+    <A as Action>::Future: Send + Sync,
+{
+    let strategy = FixedInterval::from_millis(millis).take(retry_count);
+    let retry = Retry::spawn(strategy, action);
+    tokio::spawn(async move { retry.await })
+}

+ 2 - 0
rust-lib/flowy-net/src/config.rs

@@ -47,5 +47,7 @@ impl ServerConfig {
 
     pub fn doc_url(&self) -> String { format!("{}{}/api/doc", self.scheme(), self.host) }
 
+    pub fn trash_url(&self) -> String { format!("{}{}/api/trash", self.scheme(), self.host) }
+
     pub fn ws_addr(&self) -> String { format!("{}://{}/ws", self.ws_schema, self.host) }
 }

+ 1 - 1
rust-lib/flowy-workspace/src/entities/app/app_delete.rs

@@ -8,7 +8,7 @@ pub struct DeleteAppRequest {
     pub app_id: String,
 }
 
-#[derive(Default, ProtoBuf)]
+#[derive(Default, ProtoBuf, Clone)]
 pub struct DeleteAppParams {
     #[pb(index = 1)]
     pub app_id: String,

+ 0 - 2
rust-lib/flowy-workspace/src/entities/trash/mod.rs

@@ -1,6 +1,4 @@
 pub mod parser;
 mod trash_create;
-mod trash_delete;
 
 pub use trash_create::*;
-pub use trash_delete::*;

+ 22 - 1
rust-lib/flowy-workspace/src/entities/trash/trash_create.rs

@@ -24,7 +24,28 @@ impl std::default::Default for TrashType {
 }
 
 #[derive(PartialEq, ProtoBuf, Default, Debug, Clone)]
-pub struct CreateTrashParams {
+pub struct TrashIdentifiers {
+    #[pb(index = 1)]
+    pub items: Vec<TrashIdentifier>,
+}
+
+impl std::convert::From<Vec<TrashIdentifier>> for TrashIdentifiers {
+    fn from(items: Vec<TrashIdentifier>) -> Self { TrashIdentifiers { items } }
+}
+
+impl std::convert::From<Vec<Trash>> for TrashIdentifiers {
+    fn from(trash: Vec<Trash>) -> Self {
+        let items = trash
+            .into_iter()
+            .map(|t| TrashIdentifier { id: t.id, ty: t.ty })
+            .collect::<Vec<_>>();
+
+        TrashIdentifiers { items }
+    }
+}
+
+#[derive(PartialEq, ProtoBuf, Default, Debug, Clone)]
+pub struct TrashIdentifier {
     #[pb(index = 1)]
     pub id: String,
 

+ 0 - 13
rust-lib/flowy-workspace/src/entities/trash/trash_delete.rs

@@ -1,13 +0,0 @@
-use flowy_derive::ProtoBuf;
-
-#[derive(PartialEq, ProtoBuf, Default, Debug, Clone)]
-pub struct TrashIdentifier {
-    #[pb(index = 1)]
-    pub id: String,
-}
-
-#[derive(PartialEq, ProtoBuf, Default, Debug, Clone)]
-pub struct TrashIdentifiers {
-    #[pb(index = 1)]
-    pub ids: Vec<String>,
-}

+ 1 - 1
rust-lib/flowy-workspace/src/event.rs

@@ -58,7 +58,7 @@ pub enum WorkspaceEvent {
     #[event(input = "TrashIdentifier")]
     PutbackTrash      = 301,
 
-    #[event(input = "TrashIdentifier")]
+    #[event(input = "TrashIdentifiers")]
     DeleteTrash       = 302,
 
     #[event()]

+ 4 - 4
rust-lib/flowy-workspace/src/handlers/trash_handler.rs

@@ -1,5 +1,5 @@
 use crate::{
-    entities::trash::{RepeatedTrash, TrashIdentifier},
+    entities::trash::{RepeatedTrash, TrashIdentifier, TrashIdentifiers},
     errors::WorkspaceError,
     services::TrashCan,
 };
@@ -22,12 +22,12 @@ pub(crate) async fn putback_trash_handler(
     Ok(())
 }
 
-#[tracing::instrument(skip(identifier, controller), err)]
+#[tracing::instrument(skip(identifiers, controller), err)]
 pub(crate) async fn delete_trash_handler(
-    identifier: Data<TrashIdentifier>,
+    identifiers: Data<TrashIdentifiers>,
     controller: Unit<Arc<TrashCan>>,
 ) -> Result<(), WorkspaceError> {
-    let _ = controller.delete(&identifier.id).await?;
+    let _ = controller.delete(identifiers.into_inner()).await?;
     Ok(())
 }
 

+ 1 - 1
rust-lib/flowy-workspace/src/module.rs

@@ -38,7 +38,7 @@ pub fn mk_workspace(
 ) -> Arc<WorkspaceController> {
     let server = construct_workspace_server(server_config);
 
-    let trash_can = Arc::new(TrashCan::new(database.clone()));
+    let trash_can = Arc::new(TrashCan::new(database.clone(), server.clone(), user.clone()));
 
     let view_controller = Arc::new(ViewController::new(
         user.clone(),

+ 0 - 3
rust-lib/flowy-workspace/src/protobuf/model/mod.rs

@@ -18,9 +18,6 @@ pub use observable::*;
 mod errors; 
 pub use errors::*; 
 
-mod trash_delete; 
-pub use trash_delete::*; 
-
 mod workspace_update; 
 pub use workspace_update::*; 
 

+ 233 - 62
rust-lib/flowy-workspace/src/protobuf/model/trash_create.rs

@@ -24,7 +24,173 @@
 // const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_22_1;
 
 #[derive(PartialEq,Clone,Default)]
-pub struct CreateTrashParams {
+pub struct TrashIdentifiers {
+    // message fields
+    pub items: ::protobuf::RepeatedField<TrashIdentifier>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a TrashIdentifiers {
+    fn default() -> &'a TrashIdentifiers {
+        <TrashIdentifiers as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl TrashIdentifiers {
+    pub fn new() -> TrashIdentifiers {
+        ::std::default::Default::default()
+    }
+
+    // repeated .TrashIdentifier items = 1;
+
+
+    pub fn get_items(&self) -> &[TrashIdentifier] {
+        &self.items
+    }
+    pub fn clear_items(&mut self) {
+        self.items.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_items(&mut self, v: ::protobuf::RepeatedField<TrashIdentifier>) {
+        self.items = v;
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_items(&mut self) -> &mut ::protobuf::RepeatedField<TrashIdentifier> {
+        &mut self.items
+    }
+
+    // Take field
+    pub fn take_items(&mut self) -> ::protobuf::RepeatedField<TrashIdentifier> {
+        ::std::mem::replace(&mut self.items, ::protobuf::RepeatedField::new())
+    }
+}
+
+impl ::protobuf::Message for TrashIdentifiers {
+    fn is_initialized(&self) -> bool {
+        for v in &self.items {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.items)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        for value in &self.items {
+            let len = value.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        };
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        for v in &self.items {
+            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
+        };
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> TrashIdentifiers {
+        TrashIdentifiers::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<TrashIdentifier>>(
+                "items",
+                |m: &TrashIdentifiers| { &m.items },
+                |m: &mut TrashIdentifiers| { &mut m.items },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<TrashIdentifiers>(
+                "TrashIdentifiers",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static TrashIdentifiers {
+        static instance: ::protobuf::rt::LazyV2<TrashIdentifiers> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(TrashIdentifiers::new)
+    }
+}
+
+impl ::protobuf::Clear for TrashIdentifiers {
+    fn clear(&mut self) {
+        self.items.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for TrashIdentifiers {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for TrashIdentifiers {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct TrashIdentifier {
     // message fields
     pub id: ::std::string::String,
     pub ty: TrashType,
@@ -33,14 +199,14 @@ pub struct CreateTrashParams {
     pub cached_size: ::protobuf::CachedSize,
 }
 
-impl<'a> ::std::default::Default for &'a CreateTrashParams {
-    fn default() -> &'a CreateTrashParams {
-        <CreateTrashParams as ::protobuf::Message>::default_instance()
+impl<'a> ::std::default::Default for &'a TrashIdentifier {
+    fn default() -> &'a TrashIdentifier {
+        <TrashIdentifier as ::protobuf::Message>::default_instance()
     }
 }
 
-impl CreateTrashParams {
-    pub fn new() -> CreateTrashParams {
+impl TrashIdentifier {
+    pub fn new() -> TrashIdentifier {
         ::std::default::Default::default()
     }
 
@@ -86,7 +252,7 @@ impl CreateTrashParams {
     }
 }
 
-impl ::protobuf::Message for CreateTrashParams {
+impl ::protobuf::Message for TrashIdentifier {
     fn is_initialized(&self) -> bool {
         true
     }
@@ -161,8 +327,8 @@ impl ::protobuf::Message for CreateTrashParams {
         Self::descriptor_static()
     }
 
-    fn new() -> CreateTrashParams {
-        CreateTrashParams::new()
+    fn new() -> TrashIdentifier {
+        TrashIdentifier::new()
     }
 
     fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
@@ -171,29 +337,29 @@ impl ::protobuf::Message for CreateTrashParams {
             let mut fields = ::std::vec::Vec::new();
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "id",
-                |m: &CreateTrashParams| { &m.id },
-                |m: &mut CreateTrashParams| { &mut m.id },
+                |m: &TrashIdentifier| { &m.id },
+                |m: &mut TrashIdentifier| { &mut m.id },
             ));
             fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum<TrashType>>(
                 "ty",
-                |m: &CreateTrashParams| { &m.ty },
-                |m: &mut CreateTrashParams| { &mut m.ty },
+                |m: &TrashIdentifier| { &m.ty },
+                |m: &mut TrashIdentifier| { &mut m.ty },
             ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CreateTrashParams>(
-                "CreateTrashParams",
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<TrashIdentifier>(
+                "TrashIdentifier",
                 fields,
                 file_descriptor_proto()
             )
         })
     }
 
-    fn default_instance() -> &'static CreateTrashParams {
-        static instance: ::protobuf::rt::LazyV2<CreateTrashParams> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(CreateTrashParams::new)
+    fn default_instance() -> &'static TrashIdentifier {
+        static instance: ::protobuf::rt::LazyV2<TrashIdentifier> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(TrashIdentifier::new)
     }
 }
 
-impl ::protobuf::Clear for CreateTrashParams {
+impl ::protobuf::Clear for TrashIdentifier {
     fn clear(&mut self) {
         self.id.clear();
         self.ty = TrashType::Unknown;
@@ -201,13 +367,13 @@ impl ::protobuf::Clear for CreateTrashParams {
     }
 }
 
-impl ::std::fmt::Debug for CreateTrashParams {
+impl ::std::fmt::Debug for TrashIdentifier {
     fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
         ::protobuf::text_format::fmt(self, f)
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for CreateTrashParams {
+impl ::protobuf::reflect::ProtobufValue for TrashIdentifier {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Message(self)
     }
@@ -732,47 +898,52 @@ impl ::protobuf::reflect::ProtobufValue for TrashType {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x12trash_create.proto\"?\n\x11CreateTrashParams\x12\x0e\n\x02id\x18\
-    \x01\x20\x01(\tR\x02id\x12\x1a\n\x02ty\x18\x02\x20\x01(\x0e2\n.TrashType\
-    R\x02ty\"\x8d\x01\n\x05Trash\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\
-    \x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12#\n\rmodified_time\x18\
-    \x03\x20\x01(\x03R\x0cmodifiedTime\x12\x1f\n\x0bcreate_time\x18\x04\x20\
-    \x01(\x03R\ncreateTime\x12\x1a\n\x02ty\x18\x05\x20\x01(\x0e2\n.TrashType\
-    R\x02ty\"-\n\rRepeatedTrash\x12\x1c\n\x05items\x18\x01\x20\x03(\x0b2\x06\
-    .TrashR\x05items*\"\n\tTrashType\x12\x0b\n\x07Unknown\x10\0\x12\x08\n\
-    \x04View\x10\x01J\x8a\x05\n\x06\x12\x04\0\0\x13\x01\n\x08\n\x01\x0c\x12\
-    \x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\0\x01\
-    \x12\x03\x02\x08\x19\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x12\n\x0c\n\
-    \x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\
-    \x03\x03\x0b\r\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x10\x11\n\x0b\n\
-    \x04\x04\0\x02\x01\x12\x03\x04\x04\x15\n\x0c\n\x05\x04\0\x02\x01\x06\x12\
-    \x03\x04\x04\r\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0e\x10\n\x0c\n\
-    \x05\x04\0\x02\x01\x03\x12\x03\x04\x13\x14\n\n\n\x02\x04\x01\x12\x04\x06\
-    \0\x0c\x01\n\n\n\x03\x04\x01\x01\x12\x03\x06\x08\r\n\x0b\n\x04\x04\x01\
-    \x02\0\x12\x03\x07\x04\x12\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x07\x04\
-    \n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x07\x0b\r\n\x0c\n\x05\x04\x01\
-    \x02\0\x03\x12\x03\x07\x10\x11\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\x08\
-    \x04\x14\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\x03\x08\x04\n\n\x0c\n\x05\
-    \x04\x01\x02\x01\x01\x12\x03\x08\x0b\x0f\n\x0c\n\x05\x04\x01\x02\x01\x03\
-    \x12\x03\x08\x12\x13\n\x0b\n\x04\x04\x01\x02\x02\x12\x03\t\x04\x1c\n\x0c\
-    \n\x05\x04\x01\x02\x02\x05\x12\x03\t\x04\t\n\x0c\n\x05\x04\x01\x02\x02\
-    \x01\x12\x03\t\n\x17\n\x0c\n\x05\x04\x01\x02\x02\x03\x12\x03\t\x1a\x1b\n\
-    \x0b\n\x04\x04\x01\x02\x03\x12\x03\n\x04\x1a\n\x0c\n\x05\x04\x01\x02\x03\
-    \x05\x12\x03\n\x04\t\n\x0c\n\x05\x04\x01\x02\x03\x01\x12\x03\n\n\x15\n\
-    \x0c\n\x05\x04\x01\x02\x03\x03\x12\x03\n\x18\x19\n\x0b\n\x04\x04\x01\x02\
-    \x04\x12\x03\x0b\x04\x15\n\x0c\n\x05\x04\x01\x02\x04\x06\x12\x03\x0b\x04\
-    \r\n\x0c\n\x05\x04\x01\x02\x04\x01\x12\x03\x0b\x0e\x10\n\x0c\n\x05\x04\
-    \x01\x02\x04\x03\x12\x03\x0b\x13\x14\n\n\n\x02\x04\x02\x12\x04\r\0\x0f\
-    \x01\n\n\n\x03\x04\x02\x01\x12\x03\r\x08\x15\n\x0b\n\x04\x04\x02\x02\0\
-    \x12\x03\x0e\x04\x1d\n\x0c\n\x05\x04\x02\x02\0\x04\x12\x03\x0e\x04\x0c\n\
-    \x0c\n\x05\x04\x02\x02\0\x06\x12\x03\x0e\r\x12\n\x0c\n\x05\x04\x02\x02\0\
-    \x01\x12\x03\x0e\x13\x18\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x0e\x1b\
-    \x1c\n\n\n\x02\x05\0\x12\x04\x10\0\x13\x01\n\n\n\x03\x05\0\x01\x12\x03\
-    \x10\x05\x0e\n\x0b\n\x04\x05\0\x02\0\x12\x03\x11\x04\x10\n\x0c\n\x05\x05\
-    \0\x02\0\x01\x12\x03\x11\x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x11\
-    \x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x12\x04\r\n\x0c\n\x05\x05\0\
-    \x02\x01\x01\x12\x03\x12\x04\x08\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\
-    \x12\x0b\x0cb\x06proto3\
+    \n\x12trash_create.proto\":\n\x10TrashIdentifiers\x12&\n\x05items\x18\
+    \x01\x20\x03(\x0b2\x10.TrashIdentifierR\x05items\"=\n\x0fTrashIdentifier\
+    \x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x1a\n\x02ty\x18\x02\x20\
+    \x01(\x0e2\n.TrashTypeR\x02ty\"\x8d\x01\n\x05Trash\x12\x0e\n\x02id\x18\
+    \x01\x20\x01(\tR\x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\
+    #\n\rmodified_time\x18\x03\x20\x01(\x03R\x0cmodifiedTime\x12\x1f\n\x0bcr\
+    eate_time\x18\x04\x20\x01(\x03R\ncreateTime\x12\x1a\n\x02ty\x18\x05\x20\
+    \x01(\x0e2\n.TrashTypeR\x02ty\"-\n\rRepeatedTrash\x12\x1c\n\x05items\x18\
+    \x01\x20\x03(\x0b2\x06.TrashR\x05items*\"\n\tTrashType\x12\x0b\n\x07Unkn\
+    own\x10\0\x12\x08\n\x04View\x10\x01J\xe7\x05\n\x06\x12\x04\0\0\x16\x01\n\
+    \x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x04\x01\n\n\
+    \n\x03\x04\0\x01\x12\x03\x02\x08\x18\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\
+    \x04'\n\x0c\n\x05\x04\0\x02\0\x04\x12\x03\x03\x04\x0c\n\x0c\n\x05\x04\0\
+    \x02\0\x06\x12\x03\x03\r\x1c\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x1d\
+    \"\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03%&\n\n\n\x02\x04\x01\x12\x04\
+    \x05\0\x08\x01\n\n\n\x03\x04\x01\x01\x12\x03\x05\x08\x17\n\x0b\n\x04\x04\
+    \x01\x02\0\x12\x03\x06\x04\x12\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x06\
+    \x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x06\x0b\r\n\x0c\n\x05\x04\
+    \x01\x02\0\x03\x12\x03\x06\x10\x11\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\
+    \x07\x04\x15\n\x0c\n\x05\x04\x01\x02\x01\x06\x12\x03\x07\x04\r\n\x0c\n\
+    \x05\x04\x01\x02\x01\x01\x12\x03\x07\x0e\x10\n\x0c\n\x05\x04\x01\x02\x01\
+    \x03\x12\x03\x07\x13\x14\n\n\n\x02\x04\x02\x12\x04\t\0\x0f\x01\n\n\n\x03\
+    \x04\x02\x01\x12\x03\t\x08\r\n\x0b\n\x04\x04\x02\x02\0\x12\x03\n\x04\x12\
+    \n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\n\x04\n\n\x0c\n\x05\x04\x02\x02\0\
+    \x01\x12\x03\n\x0b\r\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\n\x10\x11\n\
+    \x0b\n\x04\x04\x02\x02\x01\x12\x03\x0b\x04\x14\n\x0c\n\x05\x04\x02\x02\
+    \x01\x05\x12\x03\x0b\x04\n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\x0b\
+    \x0b\x0f\n\x0c\n\x05\x04\x02\x02\x01\x03\x12\x03\x0b\x12\x13\n\x0b\n\x04\
+    \x04\x02\x02\x02\x12\x03\x0c\x04\x1c\n\x0c\n\x05\x04\x02\x02\x02\x05\x12\
+    \x03\x0c\x04\t\n\x0c\n\x05\x04\x02\x02\x02\x01\x12\x03\x0c\n\x17\n\x0c\n\
+    \x05\x04\x02\x02\x02\x03\x12\x03\x0c\x1a\x1b\n\x0b\n\x04\x04\x02\x02\x03\
+    \x12\x03\r\x04\x1a\n\x0c\n\x05\x04\x02\x02\x03\x05\x12\x03\r\x04\t\n\x0c\
+    \n\x05\x04\x02\x02\x03\x01\x12\x03\r\n\x15\n\x0c\n\x05\x04\x02\x02\x03\
+    \x03\x12\x03\r\x18\x19\n\x0b\n\x04\x04\x02\x02\x04\x12\x03\x0e\x04\x15\n\
+    \x0c\n\x05\x04\x02\x02\x04\x06\x12\x03\x0e\x04\r\n\x0c\n\x05\x04\x02\x02\
+    \x04\x01\x12\x03\x0e\x0e\x10\n\x0c\n\x05\x04\x02\x02\x04\x03\x12\x03\x0e\
+    \x13\x14\n\n\n\x02\x04\x03\x12\x04\x10\0\x12\x01\n\n\n\x03\x04\x03\x01\
+    \x12\x03\x10\x08\x15\n\x0b\n\x04\x04\x03\x02\0\x12\x03\x11\x04\x1d\n\x0c\
+    \n\x05\x04\x03\x02\0\x04\x12\x03\x11\x04\x0c\n\x0c\n\x05\x04\x03\x02\0\
+    \x06\x12\x03\x11\r\x12\n\x0c\n\x05\x04\x03\x02\0\x01\x12\x03\x11\x13\x18\
+    \n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x11\x1b\x1c\n\n\n\x02\x05\0\x12\
+    \x04\x13\0\x16\x01\n\n\n\x03\x05\0\x01\x12\x03\x13\x05\x0e\n\x0b\n\x04\
+    \x05\0\x02\0\x12\x03\x14\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x14\
+    \x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x14\x0e\x0f\n\x0b\n\x04\x05\
+    \0\x02\x01\x12\x03\x15\x04\r\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x15\
+    \x04\x08\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x15\x0b\x0cb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 0 - 368
rust-lib/flowy-workspace/src/protobuf/model/trash_delete.rs

@@ -1,368 +0,0 @@
-// This file is generated by rust-protobuf 2.22.1. Do not edit
-// @generated
-
-// https://github.com/rust-lang/rust-clippy/issues/702
-#![allow(unknown_lints)]
-#![allow(clippy::all)]
-
-#![allow(unused_attributes)]
-#![cfg_attr(rustfmt, rustfmt::skip)]
-
-#![allow(box_pointers)]
-#![allow(dead_code)]
-#![allow(missing_docs)]
-#![allow(non_camel_case_types)]
-#![allow(non_snake_case)]
-#![allow(non_upper_case_globals)]
-#![allow(trivial_casts)]
-#![allow(unused_imports)]
-#![allow(unused_results)]
-//! Generated file from `trash_delete.proto`
-
-/// Generated files are compatible only with the same version
-/// of protobuf runtime.
-// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_22_1;
-
-#[derive(PartialEq,Clone,Default)]
-pub struct TrashIdentifier {
-    // message fields
-    pub id: ::std::string::String,
-    // special fields
-    pub unknown_fields: ::protobuf::UnknownFields,
-    pub cached_size: ::protobuf::CachedSize,
-}
-
-impl<'a> ::std::default::Default for &'a TrashIdentifier {
-    fn default() -> &'a TrashIdentifier {
-        <TrashIdentifier as ::protobuf::Message>::default_instance()
-    }
-}
-
-impl TrashIdentifier {
-    pub fn new() -> TrashIdentifier {
-        ::std::default::Default::default()
-    }
-
-    // string id = 1;
-
-
-    pub fn get_id(&self) -> &str {
-        &self.id
-    }
-    pub fn clear_id(&mut self) {
-        self.id.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_id(&mut self, v: ::std::string::String) {
-        self.id = v;
-    }
-
-    // Mutable pointer to the field.
-    // If field is not initialized, it is initialized with default value first.
-    pub fn mut_id(&mut self) -> &mut ::std::string::String {
-        &mut self.id
-    }
-
-    // Take field
-    pub fn take_id(&mut self) -> ::std::string::String {
-        ::std::mem::replace(&mut self.id, ::std::string::String::new())
-    }
-}
-
-impl ::protobuf::Message for TrashIdentifier {
-    fn is_initialized(&self) -> bool {
-        true
-    }
-
-    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        while !is.eof()? {
-            let (field_number, wire_type) = is.read_tag_unpack()?;
-            match field_number {
-                1 => {
-                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.id)?;
-                },
-                _ => {
-                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
-                },
-            };
-        }
-        ::std::result::Result::Ok(())
-    }
-
-    // Compute sizes of nested messages
-    #[allow(unused_variables)]
-    fn compute_size(&self) -> u32 {
-        let mut my_size = 0;
-        if !self.id.is_empty() {
-            my_size += ::protobuf::rt::string_size(1, &self.id);
-        }
-        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
-        self.cached_size.set(my_size);
-        my_size
-    }
-
-    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        if !self.id.is_empty() {
-            os.write_string(1, &self.id)?;
-        }
-        os.write_unknown_fields(self.get_unknown_fields())?;
-        ::std::result::Result::Ok(())
-    }
-
-    fn get_cached_size(&self) -> u32 {
-        self.cached_size.get()
-    }
-
-    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
-        &self.unknown_fields
-    }
-
-    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
-        &mut self.unknown_fields
-    }
-
-    fn as_any(&self) -> &dyn (::std::any::Any) {
-        self as &dyn (::std::any::Any)
-    }
-    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
-        self as &mut dyn (::std::any::Any)
-    }
-    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
-        self
-    }
-
-    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
-        Self::descriptor_static()
-    }
-
-    fn new() -> TrashIdentifier {
-        TrashIdentifier::new()
-    }
-
-    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
-        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
-        descriptor.get(|| {
-            let mut fields = ::std::vec::Vec::new();
-            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "id",
-                |m: &TrashIdentifier| { &m.id },
-                |m: &mut TrashIdentifier| { &mut m.id },
-            ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<TrashIdentifier>(
-                "TrashIdentifier",
-                fields,
-                file_descriptor_proto()
-            )
-        })
-    }
-
-    fn default_instance() -> &'static TrashIdentifier {
-        static instance: ::protobuf::rt::LazyV2<TrashIdentifier> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(TrashIdentifier::new)
-    }
-}
-
-impl ::protobuf::Clear for TrashIdentifier {
-    fn clear(&mut self) {
-        self.id.clear();
-        self.unknown_fields.clear();
-    }
-}
-
-impl ::std::fmt::Debug for TrashIdentifier {
-    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
-        ::protobuf::text_format::fmt(self, f)
-    }
-}
-
-impl ::protobuf::reflect::ProtobufValue for TrashIdentifier {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Message(self)
-    }
-}
-
-#[derive(PartialEq,Clone,Default)]
-pub struct TrashIdentifiers {
-    // message fields
-    pub ids: ::protobuf::RepeatedField<::std::string::String>,
-    // special fields
-    pub unknown_fields: ::protobuf::UnknownFields,
-    pub cached_size: ::protobuf::CachedSize,
-}
-
-impl<'a> ::std::default::Default for &'a TrashIdentifiers {
-    fn default() -> &'a TrashIdentifiers {
-        <TrashIdentifiers as ::protobuf::Message>::default_instance()
-    }
-}
-
-impl TrashIdentifiers {
-    pub fn new() -> TrashIdentifiers {
-        ::std::default::Default::default()
-    }
-
-    // repeated string ids = 1;
-
-
-    pub fn get_ids(&self) -> &[::std::string::String] {
-        &self.ids
-    }
-    pub fn clear_ids(&mut self) {
-        self.ids.clear();
-    }
-
-    // Param is passed by value, moved
-    pub fn set_ids(&mut self, v: ::protobuf::RepeatedField<::std::string::String>) {
-        self.ids = v;
-    }
-
-    // Mutable pointer to the field.
-    pub fn mut_ids(&mut self) -> &mut ::protobuf::RepeatedField<::std::string::String> {
-        &mut self.ids
-    }
-
-    // Take field
-    pub fn take_ids(&mut self) -> ::protobuf::RepeatedField<::std::string::String> {
-        ::std::mem::replace(&mut self.ids, ::protobuf::RepeatedField::new())
-    }
-}
-
-impl ::protobuf::Message for TrashIdentifiers {
-    fn is_initialized(&self) -> bool {
-        true
-    }
-
-    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        while !is.eof()? {
-            let (field_number, wire_type) = is.read_tag_unpack()?;
-            match field_number {
-                1 => {
-                    ::protobuf::rt::read_repeated_string_into(wire_type, is, &mut self.ids)?;
-                },
-                _ => {
-                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
-                },
-            };
-        }
-        ::std::result::Result::Ok(())
-    }
-
-    // Compute sizes of nested messages
-    #[allow(unused_variables)]
-    fn compute_size(&self) -> u32 {
-        let mut my_size = 0;
-        for value in &self.ids {
-            my_size += ::protobuf::rt::string_size(1, &value);
-        };
-        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
-        self.cached_size.set(my_size);
-        my_size
-    }
-
-    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
-        for v in &self.ids {
-            os.write_string(1, &v)?;
-        };
-        os.write_unknown_fields(self.get_unknown_fields())?;
-        ::std::result::Result::Ok(())
-    }
-
-    fn get_cached_size(&self) -> u32 {
-        self.cached_size.get()
-    }
-
-    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
-        &self.unknown_fields
-    }
-
-    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
-        &mut self.unknown_fields
-    }
-
-    fn as_any(&self) -> &dyn (::std::any::Any) {
-        self as &dyn (::std::any::Any)
-    }
-    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
-        self as &mut dyn (::std::any::Any)
-    }
-    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
-        self
-    }
-
-    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
-        Self::descriptor_static()
-    }
-
-    fn new() -> TrashIdentifiers {
-        TrashIdentifiers::new()
-    }
-
-    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
-        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
-        descriptor.get(|| {
-            let mut fields = ::std::vec::Vec::new();
-            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
-                "ids",
-                |m: &TrashIdentifiers| { &m.ids },
-                |m: &mut TrashIdentifiers| { &mut m.ids },
-            ));
-            ::protobuf::reflect::MessageDescriptor::new_pb_name::<TrashIdentifiers>(
-                "TrashIdentifiers",
-                fields,
-                file_descriptor_proto()
-            )
-        })
-    }
-
-    fn default_instance() -> &'static TrashIdentifiers {
-        static instance: ::protobuf::rt::LazyV2<TrashIdentifiers> = ::protobuf::rt::LazyV2::INIT;
-        instance.get(TrashIdentifiers::new)
-    }
-}
-
-impl ::protobuf::Clear for TrashIdentifiers {
-    fn clear(&mut self) {
-        self.ids.clear();
-        self.unknown_fields.clear();
-    }
-}
-
-impl ::std::fmt::Debug for TrashIdentifiers {
-    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
-        ::protobuf::text_format::fmt(self, f)
-    }
-}
-
-impl ::protobuf::reflect::ProtobufValue for TrashIdentifiers {
-    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
-        ::protobuf::reflect::ReflectValueRef::Message(self)
-    }
-}
-
-static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x12trash_delete.proto\"!\n\x0fTrashIdentifier\x12\x0e\n\x02id\x18\x01\
-    \x20\x01(\tR\x02id\"$\n\x10TrashIdentifiers\x12\x10\n\x03ids\x18\x01\x20\
-    \x03(\tR\x03idsJ\xbe\x01\n\x06\x12\x04\0\0\x07\x01\n\x08\n\x01\x0c\x12\
-    \x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x04\x01\n\n\n\x03\x04\0\x01\
-    \x12\x03\x02\x08\x17\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x12\n\x0c\n\
-    \x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\
-    \x03\x03\x0b\r\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x10\x11\n\n\n\x02\
-    \x04\x01\x12\x04\x05\0\x07\x01\n\n\n\x03\x04\x01\x01\x12\x03\x05\x08\x18\
-    \n\x0b\n\x04\x04\x01\x02\0\x12\x03\x06\x04\x1c\n\x0c\n\x05\x04\x01\x02\0\
-    \x04\x12\x03\x06\x04\x0c\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x06\r\x13\
-    \n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x06\x14\x17\n\x0c\n\x05\x04\x01\
-    \x02\0\x03\x12\x03\x06\x1a\x1bb\x06proto3\
-";
-
-static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
-
-fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
-    ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
-}
-
-pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
-    file_descriptor_proto_lazy.get(|| {
-        parse_descriptor_proto()
-    })
-}

+ 4 - 1
rust-lib/flowy-workspace/src/protobuf/proto/trash_create.proto

@@ -1,6 +1,9 @@
 syntax = "proto3";
 
-message CreateTrashParams {
+message TrashIdentifiers {
+    repeated TrashIdentifier items = 1;
+}
+message TrashIdentifier {
     string id = 1;
     TrashType ty = 2;
 }

+ 0 - 8
rust-lib/flowy-workspace/src/protobuf/proto/trash_delete.proto

@@ -1,8 +0,0 @@
-syntax = "proto3";
-
-message TrashIdentifier {
-    string id = 1;
-}
-message TrashIdentifiers {
-    repeated string ids = 1;
-}

+ 37 - 18
rust-lib/flowy-workspace/src/services/app_controller.rs

@@ -8,24 +8,18 @@ use crate::{
 };
 
 use flowy_database::SqliteConnection;
+
 use std::sync::Arc;
 
 pub(crate) struct AppController {
     user: Arc<dyn WorkspaceUser>,
-    sql: Arc<AppTableSql>,
     database: Arc<dyn WorkspaceDatabase>,
     server: Server,
 }
 
 impl AppController {
     pub(crate) fn new(user: Arc<dyn WorkspaceUser>, database: Arc<dyn WorkspaceDatabase>, server: Server) -> Self {
-        let sql = Arc::new(AppTableSql {});
-        Self {
-            user,
-            sql,
-            database,
-            server,
-        }
+        Self { user, database, server }
     }
 
     #[tracing::instrument(level = "debug", skip(self), err)]
@@ -47,12 +41,12 @@ impl AppController {
 
     pub(crate) fn save_app(&self, app: App, conn: &SqliteConnection) -> Result<(), WorkspaceError> {
         let app_table = AppTable::new(app.clone());
-        let _ = self.sql.create_app(app_table, &*conn)?;
+        let _ = AppTableSql::create_app(app_table, &*conn)?;
         Ok(())
     }
 
     pub(crate) async fn read_app(&self, params: AppIdentifier) -> Result<App, WorkspaceError> {
-        let app_table = self.sql.read_app(&params.app_id, &*self.database.db_connection()?)?;
+        let app_table = AppTableSql::read_app(&params.app_id, &*self.database.db_connection()?)?;
         let _ = self.read_app_on_server(params)?;
         Ok(app_table.into())
     }
@@ -61,7 +55,7 @@ impl AppController {
     pub(crate) async fn delete_app(&self, app_id: &str) -> Result<(), WorkspaceError> {
         let conn = &*self.database.db_connection()?;
         conn.immediate_transaction::<_, WorkspaceError, _>(|| {
-            let app = self.sql.delete_app(app_id, &*conn)?;
+            let app = AppTableSql::delete_app(app_id, &*conn)?;
             let apps = self.read_local_apps(&app.workspace_id, &*conn)?;
             send_dart_notification(&app.workspace_id, WorkspaceNotification::WorkspaceDeleteApp)
                 .payload(apps)
@@ -74,7 +68,7 @@ impl AppController {
     }
 
     fn read_local_apps(&self, workspace_id: &str, conn: &SqliteConnection) -> Result<RepeatedApp, WorkspaceError> {
-        let app_tables = self.sql.read_apps(workspace_id, false, conn)?;
+        let app_tables = AppTableSql::read_apps(workspace_id, false, conn)?;
         let apps = app_tables.into_iter().map(|table| table.into()).collect::<Vec<App>>();
         Ok(RepeatedApp { items: apps })
     }
@@ -84,8 +78,8 @@ impl AppController {
         let app_id = changeset.id.clone();
         let conn = &*self.database.db_connection()?;
         conn.immediate_transaction::<_, WorkspaceError, _>(|| {
-            let _ = self.sql.update_app(changeset, conn)?;
-            let app: App = self.sql.read_app(&app_id, conn)?.into();
+            let _ = AppTableSql::update_app(changeset, conn)?;
+            let app: App = AppTableSql::read_app(&app_id, conn)?.into();
             send_dart_notification(&app_id, WorkspaceNotification::AppUpdated)
                 .payload(app)
                 .send();
@@ -137,6 +131,18 @@ impl AppController {
                 },
             }
         });
+        // let action = RetryAction::new(self.server.clone(), self.user.clone(), move
+        // |token, server| {     let params = params.clone();
+        //     async move {
+        //         match server.delete_app(&token, params).await {
+        //             Ok(_) => {},
+        //             Err(e) => log::error!("Delete app failed: {:?}", e),
+        //         }
+        //         Ok::<(), WorkspaceError>(())
+        //     }
+        // });
+        //
+        // spawn_retry(500, 3, action);
         Ok(())
     }
 
@@ -144,14 +150,27 @@ impl AppController {
     fn read_app_on_server(&self, params: AppIdentifier) -> Result<(), WorkspaceError> {
         let token = self.user.token()?;
         let server = self.server.clone();
+        let pool = self.database.db_pool()?;
         spawn(async move {
             // Opti: retry?
             match server.read_app(&token, params).await {
-                Ok(option) => match option {
-                    None => {},
-                    Some(_app) => {},
+                Ok(Some(app)) => match pool.get() {
+                    Ok(conn) => {
+                        let app_table = AppTable::new(app.clone());
+                        let result = AppTableSql::create_app(app_table, &*conn);
+                        match result {
+                            Ok(_) => {
+                                send_dart_notification(&app.id, WorkspaceNotification::AppUpdated)
+                                    .payload(app)
+                                    .send();
+                            },
+                            Err(e) => log::error!("Save app failed: {:?}", e),
+                        }
+                    },
+                    Err(e) => log::error!("Require db connection failed: {:?}", e),
                 },
-                Err(_) => {},
+                Ok(None) => {},
+                Err(e) => log::error!("Read app failed: {:?}", e),
             }
         });
         Ok(())

+ 1 - 0
rust-lib/flowy-workspace/src/services/mod.rs

@@ -8,5 +8,6 @@ mod database;
 mod helper;
 pub mod server;
 mod trash_can;
+mod util;
 mod view_controller;
 mod workspace_controller;

+ 8 - 0
rust-lib/flowy-workspace/src/services/server/mod.rs

@@ -9,6 +9,7 @@ pub use server_api_mock::*;
 use crate::{
     entities::{
         app::{App, AppIdentifier, CreateAppParams, DeleteAppParams, UpdateAppParams},
+        trash::{RepeatedTrash, TrashIdentifiers},
         view::{CreateViewParams, DeleteViewParams, UpdateViewParams, View, ViewIdentifier},
         workspace::{
             CreateWorkspaceParams,
@@ -58,6 +59,13 @@ pub trait WorkspaceServerAPI {
     fn update_app(&self, token: &str, params: UpdateAppParams) -> ResultFuture<(), WorkspaceError>;
 
     fn delete_app(&self, token: &str, params: DeleteAppParams) -> ResultFuture<(), WorkspaceError>;
+
+    // Trash
+    fn create_trash(&self, token: &str, params: TrashIdentifiers) -> ResultFuture<(), WorkspaceError>;
+
+    fn delete_trash(&self, token: &str, params: TrashIdentifiers) -> ResultFuture<(), WorkspaceError>;
+
+    fn read_trash(&self, token: &str) -> ResultFuture<RepeatedTrash, WorkspaceError>;
 }
 
 pub(crate) fn construct_workspace_server(config: &ServerConfig) -> Arc<dyn WorkspaceServerAPI + Send + Sync> {

+ 20 - 4
rust-lib/flowy-workspace/src/services/server/server_api.rs

@@ -1,7 +1,7 @@
 use crate::{
     entities::{
         app::{App, AppIdentifier, CreateAppParams, DeleteAppParams, UpdateAppParams},
-        trash::CreateTrashParams,
+        trash::{RepeatedTrash, TrashIdentifiers},
         view::{CreateViewParams, DeleteViewParams, UpdateViewParams, View, ViewIdentifier},
         workspace::{
             CreateWorkspaceParams,
@@ -15,8 +15,6 @@ use crate::{
     errors::WorkspaceError,
     services::server::WorkspaceServerAPI,
 };
-
-use crate::entities::trash::{RepeatedTrash, TrashIdentifiers};
 use flowy_infra::future::ResultFuture;
 use flowy_net::{config::*, request::HttpRequestBuilder};
 
@@ -104,6 +102,24 @@ impl WorkspaceServerAPI for WorkspaceServer {
         let url = self.config.app_url();
         ResultFuture::new(async move { delete_app_request(&token, params, &url).await })
     }
+
+    fn create_trash(&self, token: &str, params: TrashIdentifiers) -> ResultFuture<(), WorkspaceError> {
+        let token = token.to_owned();
+        let url = self.config.trash_url();
+        ResultFuture::new(async move { create_trash_request(&token, params, &url).await })
+    }
+
+    fn delete_trash(&self, token: &str, params: TrashIdentifiers) -> ResultFuture<(), WorkspaceError> {
+        let token = token.to_owned();
+        let url = self.config.trash_url();
+        ResultFuture::new(async move { delete_trash_request(&token, params, &url).await })
+    }
+
+    fn read_trash(&self, token: &str) -> ResultFuture<RepeatedTrash, WorkspaceError> {
+        let token = token.to_owned();
+        let url = self.config.trash_url();
+        ResultFuture::new(async move { read_trash_request(&token, &url).await })
+    }
 }
 
 pub(crate) fn request_builder() -> HttpRequestBuilder {
@@ -250,7 +266,7 @@ pub async fn delete_view_request(token: &str, params: DeleteViewParams, url: &st
     Ok(())
 }
 
-pub async fn create_trash_request(token: &str, params: CreateTrashParams, url: &str) -> Result<(), WorkspaceError> {
+pub async fn create_trash_request(token: &str, params: TrashIdentifiers, url: &str) -> Result<(), WorkspaceError> {
     let _ = request_builder()
         .post(&url.to_owned())
         .header(HEADER_TOKEN, token)

+ 16 - 0
rust-lib/flowy-workspace/src/services/server/server_api_mock.rs

@@ -1,6 +1,7 @@
 use crate::{
     entities::{
         app::{App, AppIdentifier, CreateAppParams, DeleteAppParams, RepeatedApp, UpdateAppParams},
+        trash::{RepeatedTrash, TrashIdentifiers},
         view::{CreateViewParams, DeleteViewParams, RepeatedView, UpdateViewParams, View, ViewIdentifier},
         workspace::{
             CreateWorkspaceParams,
@@ -106,4 +107,19 @@ impl WorkspaceServerAPI for WorkspaceServerMock {
     fn delete_app(&self, _token: &str, _params: DeleteAppParams) -> ResultFuture<(), WorkspaceError> {
         ResultFuture::new(async { Ok(()) })
     }
+
+    fn create_trash(&self, _token: &str, _params: TrashIdentifiers) -> ResultFuture<(), WorkspaceError> {
+        ResultFuture::new(async { Ok(()) })
+    }
+
+    fn delete_trash(&self, _token: &str, _params: TrashIdentifiers) -> ResultFuture<(), WorkspaceError> {
+        ResultFuture::new(async { Ok(()) })
+    }
+
+    fn read_trash(&self, _token: &str) -> ResultFuture<RepeatedTrash, WorkspaceError> {
+        ResultFuture::new(async {
+            let repeated_trash = RepeatedTrash { items: vec![] };
+            Ok(repeated_trash)
+        })
+    }
 }

+ 164 - 73
rust-lib/flowy-workspace/src/services/trash_can.rs

@@ -1,59 +1,42 @@
 use crate::{
-    entities::trash::{RepeatedTrash, Trash, TrashType},
+    entities::trash::{RepeatedTrash, Trash, TrashIdentifier, TrashIdentifiers, TrashType},
     errors::{WorkspaceError, WorkspaceResult},
-    module::WorkspaceDatabase,
+    module::{WorkspaceDatabase, WorkspaceUser},
     notify::{send_anonymous_dart_notification, WorkspaceNotification},
+    services::{helper::spawn, server::Server},
     sql_tables::trash::TrashTableSql,
 };
 use crossbeam_utils::thread;
 use flowy_database::SqliteConnection;
+
 use std::sync::Arc;
 use tokio::sync::{broadcast, mpsc};
 
-#[derive(Clone)]
-pub enum TrashEvent {
-    NewTrash(TrashType, Vec<String>, mpsc::Sender<WorkspaceResult<()>>),
-    Putback(TrashType, Vec<String>, mpsc::Sender<WorkspaceResult<()>>),
-    Delete(TrashType, Vec<String>, mpsc::Sender<WorkspaceResult<()>>),
-}
-
-impl TrashEvent {
-    pub fn select(self, s: TrashType) -> Option<TrashEvent> {
-        match &self {
-            TrashEvent::Putback(source, _, _) => {
-                if source == &s {
-                    return Some(self);
-                }
-            },
-            TrashEvent::Delete(source, _, _) => {
-                if source == &s {
-                    return Some(self);
-                }
-            },
-            TrashEvent::NewTrash(source, _, _) => {
-                if source == &s {
-                    return Some(self);
-                }
-            },
-        }
-        None
-    }
-}
-
 pub struct TrashCan {
     pub database: Arc<dyn WorkspaceDatabase>,
     notify: broadcast::Sender<TrashEvent>,
+    server: Server,
+    user: Arc<dyn WorkspaceUser>,
 }
 
 impl TrashCan {
-    pub fn new(database: Arc<dyn WorkspaceDatabase>) -> Self {
+    pub fn new(database: Arc<dyn WorkspaceDatabase>, server: Server, user: Arc<dyn WorkspaceUser>) -> Self {
         let (tx, _) = broadcast::channel(10);
 
-        Self { database, notify: tx }
+        Self {
+            database,
+            notify: tx,
+            server,
+            user,
+        }
     }
 
+    pub(crate) fn init(&self) -> Result<(), WorkspaceError> { Ok(()) }
+
     pub fn read_trash(&self, conn: &SqliteConnection) -> Result<RepeatedTrash, WorkspaceError> {
         let repeated_trash = TrashTableSql::read_all(&*conn)?;
+
+        let _ = self.read_trash_on_server()?;
         Ok(repeated_trash)
     }
 
@@ -73,22 +56,26 @@ impl TrashCan {
         let _ = thread::scope(|_s| {
             let conn = self.database.db_connection()?;
             let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
+                let repeated_trash = TrashTableSql::read_all(&conn)?;
                 let _ = TrashTableSql::delete_trash(trash_id, &*conn)?;
-                let _ = self.notify_dart_trash_did_update(&conn)?;
+                notify_trash_num_changed(repeated_trash);
                 Ok(())
             })?;
             Ok::<(), WorkspaceError>(())
         })
         .unwrap()?;
 
-        tracing::Span::current().record(
-            "putback",
-            &format!("{:?}: {}", &trash_table.ty, trash_table.id).as_str(),
-        );
-        let _ = self
-            .notify
-            .send(TrashEvent::Putback(trash_table.ty.into(), vec![trash_table.id], tx));
+        let identifier = TrashIdentifier {
+            id: trash_table.id,
+            ty: trash_table.ty.into(),
+        };
 
+        let _ = self.delete_trash_on_server(TrashIdentifiers {
+            items: vec![identifier.clone()],
+        })?;
+
+        tracing::Span::current().record("putback", &format!("{:?}", &identifier).as_str());
+        let _ = self.notify.send(TrashEvent::Putback(vec![identifier].into(), tx));
         let _ = rx.recv().await.unwrap()?;
         Ok(())
     }
@@ -100,15 +87,20 @@ impl TrashCan {
     pub fn delete_all(&self) -> WorkspaceResult<()> { Ok(()) }
 
     #[tracing::instrument(level = "debug", skip(self)  err)]
-    pub async fn delete(&self, trash_id: &str) -> WorkspaceResult<()> {
+    pub async fn delete(&self, trash_identifiers: TrashIdentifiers) -> WorkspaceResult<()> {
         let (tx, mut rx) = mpsc::channel::<WorkspaceResult<()>>(1);
-        let trash_table = TrashTableSql::read(trash_id, &*self.database.db_connection()?)?;
-        let _ = self
-            .notify
-            .send(TrashEvent::Delete(trash_table.ty.into(), vec![trash_table.id], tx));
-
+        let _ = self.notify.send(TrashEvent::Delete(trash_identifiers.clone(), tx));
         let _ = rx.recv().await.unwrap()?;
-        let _ = TrashTableSql::delete_trash(trash_id, &*self.database.db_connection()?)?;
+
+        let conn = self.database.db_connection()?;
+        conn.immediate_transaction::<_, WorkspaceError, _>(|| {
+            for trash_identifier in &trash_identifiers.items {
+                let _ = TrashTableSql::delete_trash(&trash_identifier.id, &conn)?;
+            }
+            Ok(())
+        })?;
+
+        let _ = self.delete_trash_on_server(trash_identifiers)?;
 
         Ok(())
     }
@@ -123,48 +115,147 @@ impl TrashCan {
     pub async fn add<T: Into<Trash>>(&self, trash: Vec<T>) -> Result<(), WorkspaceError> {
         let (tx, mut rx) = mpsc::channel::<WorkspaceResult<()>>(1);
         let trash = trash.into_iter().map(|t| t.into()).collect::<Vec<Trash>>();
-        let mut ids = vec![];
-        let mut trash_type = None;
+        let mut items = vec![];
         let _ = thread::scope(|_s| {
             let conn = self.database.db_connection()?;
             conn.immediate_transaction::<_, WorkspaceError, _>(|| {
-                for t in trash {
-                    if trash_type == None {
-                        trash_type = Some(t.ty.clone());
-                    }
-
-                    if trash_type.as_ref().unwrap() != &t.ty {
-                        return Err(WorkspaceError::internal());
-                    }
-                    let trash_id = t.id.clone();
+                for t in &trash {
                     log::debug!("create trash: {:?}", t);
-                    let _ = TrashTableSql::create_trash(t.into(), &*conn)?;
-                    ids.push(trash_id);
+                    items.push(TrashIdentifier {
+                        id: t.id.clone(),
+                        ty: t.ty.clone(),
+                    });
+                    let _ = TrashTableSql::create_trash(t.clone().into(), &*conn)?;
                 }
-                let _ = self.notify_dart_trash_did_update(&conn)?;
+                self.create_trash_on_server(trash);
+                let repeated_trash = TrashTableSql::read_all(&conn)?;
+                notify_trash_num_changed(repeated_trash);
                 Ok(())
             })?;
             Ok::<(), WorkspaceError>(())
         })
         .unwrap()?;
 
-        if let Some(trash_type) = trash_type {
-            let _ = self.notify.send(TrashEvent::NewTrash(trash_type, ids, tx));
-            let _ = rx.recv().await.unwrap()?;
-        }
+        let _ = self.notify.send(TrashEvent::NewTrash(items.into(), tx));
+        let _ = rx.recv().await.unwrap()?;
 
         Ok(())
     }
 
     pub fn subscribe(&self) -> broadcast::Receiver<TrashEvent> { self.notify.subscribe() }
+}
 
-    fn notify_dart_trash_did_update(&self, conn: &SqliteConnection) -> WorkspaceResult<()> {
-        // Opti: only push the changeset
-        let repeated_trash = TrashTableSql::read_all(conn)?;
-        send_anonymous_dart_notification(WorkspaceNotification::TrashUpdated)
-            .payload(repeated_trash)
-            .send();
+impl TrashCan {
+    #[tracing::instrument(level = "debug", skip(self, trash), err)]
+    fn create_trash_on_server<T: Into<TrashIdentifiers>>(&self, trash: T) -> WorkspaceResult<()> {
+        let token = self.user.token()?;
+        let trash_identifiers = trash.into();
+        let server = self.server.clone();
+        // TODO: retry?
+        let _ = tokio::spawn(async move {
+            match server.create_trash(&token, trash_identifiers).await {
+                Ok(_) => {},
+                Err(e) => log::error!("Create trash failed: {:?}", e),
+            }
+        });
+        Ok(())
+    }
 
+    #[tracing::instrument(level = "debug", skip(self, trash), err)]
+    fn delete_trash_on_server<T: Into<TrashIdentifiers>>(&self, trash: T) -> WorkspaceResult<()> {
+        let token = self.user.token()?;
+        let trash_identifiers = trash.into();
+        let server = self.server.clone();
+        let _ = tokio::spawn(async move {
+            match server.delete_trash(&token, trash_identifiers).await {
+                Ok(_) => {},
+                Err(e) => log::error!("Delete trash failed: {:?}", e),
+            }
+        });
         Ok(())
     }
+
+    #[tracing::instrument(level = "debug", skip(self), err)]
+    fn read_trash_on_server(&self) -> WorkspaceResult<()> {
+        let token = self.user.token()?;
+        let server = self.server.clone();
+        let pool = self.database.db_pool()?;
+
+        spawn(async move {
+            match server.read_trash(&token).await {
+                Ok(repeated_trash) => {
+                    match pool.get() {
+                        Ok(conn) => {
+                            let result = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
+                                for trash in &repeated_trash.items {
+                                    let _ = TrashTableSql::create_trash(trash.clone().into(), &*conn)?;
+                                }
+                                Ok(())
+                            });
+
+                            match result {
+                                Ok(_) => {
+                                    // FIXME: User may modify the trash(add/putback) before the flying request comes
+                                    // back that will cause the trash list to be outdated.
+                                    // TODO: impl with operation transform
+                                    notify_trash_num_changed(repeated_trash);
+                                },
+                                Err(e) => log::error!("Save trash failed: {:?}", e),
+                            }
+                        },
+                        Err(e) => log::error!("Require db connection failed: {:?}", e),
+                    }
+                },
+                Err(e) => log::error!("Read trash failed: {:?}", e),
+            }
+        });
+        Ok(())
+    }
+}
+
+#[tracing::instrument(skip(repeated_trash), fields(trash_count))]
+fn notify_trash_num_changed(repeated_trash: RepeatedTrash) {
+    tracing::Span::current().record("trash_count", &repeated_trash.len());
+
+    send_anonymous_dart_notification(WorkspaceNotification::TrashUpdated)
+        .payload(repeated_trash)
+        .send();
+}
+
+#[derive(Clone)]
+pub enum TrashEvent {
+    NewTrash(TrashIdentifiers, mpsc::Sender<WorkspaceResult<()>>),
+    Putback(TrashIdentifiers, mpsc::Sender<WorkspaceResult<()>>),
+    Delete(TrashIdentifiers, mpsc::Sender<WorkspaceResult<()>>),
+}
+
+impl TrashEvent {
+    pub fn select(self, s: TrashType) -> Option<TrashEvent> {
+        match self {
+            TrashEvent::Putback(mut identifiers, sender) => {
+                identifiers.items.retain(|item| item.ty == s);
+                if identifiers.items.is_empty() {
+                    None
+                } else {
+                    Some(TrashEvent::Putback(identifiers, sender))
+                }
+            },
+            TrashEvent::Delete(mut identifiers, sender) => {
+                identifiers.items.retain(|item| item.ty == s);
+                if identifiers.items.is_empty() {
+                    None
+                } else {
+                    Some(TrashEvent::Delete(identifiers, sender))
+                }
+            },
+            TrashEvent::NewTrash(mut identifiers, sender) => {
+                identifiers.items.retain(|item| item.ty == s);
+                if identifiers.items.is_empty() {
+                    None
+                } else {
+                    Some(TrashEvent::NewTrash(identifiers, sender))
+                }
+            },
+        }
+    }
 }

+ 72 - 0
rust-lib/flowy-workspace/src/services/util.rs

@@ -0,0 +1,72 @@
+use crate::{module::WorkspaceUser, services::server::Server};
+use flowy_infra::retry::Action;
+use pin_project::pin_project;
+use std::{
+    future::Future,
+    marker::PhantomData,
+    pin::Pin,
+    sync::Arc,
+    task::{Context, Poll},
+};
+
+pub(crate) type Builder<Fut> = Box<dyn Fn(String, Server) -> Fut + Send + Sync>;
+
+pub(crate) struct RetryAction<Fut, T, E> {
+    token: String,
+    server: Server,
+    user: Arc<dyn WorkspaceUser>,
+    builder: Builder<Fut>,
+    phantom: PhantomData<(T, E)>,
+}
+
+impl<Fut, T, E> RetryAction<Fut, T, E> {
+    pub(crate) fn new<F>(server: Server, user: Arc<dyn WorkspaceUser>, builder: F) -> Self
+    where
+        Fut: Future<Output = Result<T, E>> + Send + Sync + 'static,
+        F: Fn(String, Server) -> Fut + Send + Sync + 'static,
+    {
+        let token = user.token().unwrap_or("".to_owned());
+        Self {
+            token,
+            server,
+            user,
+            builder: Box::new(builder),
+            phantom: PhantomData,
+        }
+    }
+}
+
+impl<Fut, T, E> Action for RetryAction<Fut, T, E>
+where
+    Fut: Future<Output = Result<T, E>> + Send + Sync + 'static,
+    T: Send + Sync + 'static,
+    E: Send + Sync + 'static,
+{
+    type Future = Pin<Box<dyn Future<Output = Result<Self::Item, Self::Error>> + Send + Sync>>;
+    type Item = T;
+    type Error = E;
+
+    fn run(&mut self) -> Self::Future {
+        let fut = (self.builder)(self.token.clone(), self.server.clone());
+        Box::pin(RetryActionFut { fut: Box::pin(fut) })
+    }
+}
+
+#[pin_project]
+struct RetryActionFut<T, E> {
+    #[pin]
+    fut: Pin<Box<dyn Future<Output = Result<T, E>> + Send + Sync>>,
+}
+
+impl<T, E> Future for RetryActionFut<T, E>
+where
+    T: Send + Sync + 'static,
+    E: Send + Sync + 'static,
+{
+    type Output = Result<T, E>;
+
+    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+        let mut this = self.project();
+        this.fut.as_mut().poll(cx)
+    }
+}

+ 30 - 31
rust-lib/flowy-workspace/src/services/view_controller.rs

@@ -8,7 +8,7 @@ use crate::{
 };
 
 use crate::{
-    entities::view::{DeleteViewParams, RepeatedView, ViewIdentifier},
+    entities::view::{RepeatedView, ViewIdentifier},
     errors::internal_error,
     module::WorkspaceUser,
     notify::WorkspaceNotification,
@@ -21,6 +21,7 @@ use flowy_document::{
 };
 
 use crate::{entities::trash::TrashType, errors::WorkspaceResult};
+
 use futures::{FutureExt, StreamExt};
 use std::sync::Arc;
 
@@ -171,34 +172,31 @@ impl ViewController {
         Ok(())
     }
 
-    // #[tracing::instrument(skip(self), err)]
-    // fn delete_view_on_server(&self, view_ids: Vec<String>) -> Result<(),
-    // WorkspaceError> {     let token = self.user.token()?;
-    //     let server = self.server.clone();
-    //     let params = DeleteViewParams { view_ids };
-    //     spawn(async move {
-    //         match server.delete_view(&token, params).await {
-    //             Ok(_) => {},
-    //             Err(e) => {
-    //                 // TODO: retry?
-    //                 log::error!("Delete view failed: {:?}", e);
-    //             },
-    //         }
-    //     });
-    //     Ok(())
-    // }
-
     #[tracing::instrument(skip(self), err)]
     fn read_view_on_server(&self, params: ViewIdentifier) -> Result<(), WorkspaceError> {
         let token = self.user.token()?;
         let server = self.server.clone();
+        let pool = self.database.db_pool()?;
+        // Opti: retry?
         spawn(async move {
             match server.read_view(&token, params).await {
-                Ok(_) => {},
-                Err(e) => {
-                    // TODO: retry?
-                    log::error!("Read view failed: {:?}", e);
+                Ok(Some(view)) => match pool.get() {
+                    Ok(conn) => {
+                        let view_table = ViewTable::new(view.clone());
+                        let result = ViewTableSql::create_view(view_table, &conn);
+                        match result {
+                            Ok(_) => {
+                                send_dart_notification(&view.id, WorkspaceNotification::ViewUpdated)
+                                    .payload(view.clone())
+                                    .send();
+                            },
+                            Err(e) => log::error!("Save view failed: {:?}", e),
+                        }
+                    },
+                    Err(e) => log::error!("Require db connection failed: {:?}", e),
                 },
+                Ok(None) => {},
+                Err(e) => log::error!("Read view failed: {:?}", e),
             }
         });
         Ok(())
@@ -238,12 +236,12 @@ async fn handle_trash_event(
     let db_result = database.db_connection();
 
     match event {
-        TrashEvent::NewTrash(_, view_ids, ret) | TrashEvent::Putback(_, view_ids, ret) => {
+        TrashEvent::NewTrash(identifiers, ret) | TrashEvent::Putback(identifiers, ret) => {
             let result = || {
                 let conn = &*db_result?;
                 let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
-                    for view_id in view_ids {
-                        let _ = notify_view_num_did_change(&view_id, conn, trash_can.clone())?;
+                    for identifier in identifiers.items {
+                        let _ = notify_view_num_changed(&identifier.id, conn, trash_can.clone())?;
                     }
                     Ok(())
                 })?;
@@ -251,14 +249,14 @@ async fn handle_trash_event(
             };
             let _ = ret.send(result()).await;
         },
-        TrashEvent::Delete(_, delete_ids, ret) => {
+        TrashEvent::Delete(identifiers, ret) => {
             let result = || {
                 let conn = &*db_result?;
                 let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
-                    for view_id in delete_ids {
-                        let _ = ViewTableSql::delete_view(&view_id, conn)?;
-                        let _ = document.delete(view_id.clone().into())?;
-                        let _ = notify_view_num_did_change(&view_id, conn, trash_can.clone())?;
+                    for identifier in identifiers.items {
+                        let _ = ViewTableSql::delete_view(&identifier.id, conn)?;
+                        let _ = document.delete(identifier.id.clone().into())?;
+                        let _ = notify_view_num_changed(&identifier.id, conn, trash_can.clone())?;
                     }
                     Ok(())
                 })?;
@@ -270,9 +268,10 @@ async fn handle_trash_event(
 }
 
 #[tracing::instrument(skip(conn, trash_can), err)]
-fn notify_view_num_did_change(view_id: &str, conn: &SqliteConnection, trash_can: Arc<TrashCan>) -> WorkspaceResult<()> {
+fn notify_view_num_changed(view_id: &str, conn: &SqliteConnection, trash_can: Arc<TrashCan>) -> WorkspaceResult<()> {
     let view_table = ViewTableSql::read_view(view_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)
         .payload(repeated_view)
         .send();

+ 1 - 0
rust-lib/flowy-workspace/src/services/workspace_controller.rs

@@ -45,6 +45,7 @@ impl WorkspaceController {
     }
 
     pub fn init(&self) -> Result<(), WorkspaceError> {
+        let _ = self.trash_can.init()?;
         let _ = self.view_controller.init()?;
         Ok(())
     }

+ 4 - 9
rust-lib/flowy-workspace/src/sql_tables/app/app_sql.rs

@@ -11,7 +11,7 @@ use flowy_database::{
 pub struct AppTableSql {}
 
 impl AppTableSql {
-    pub(crate) fn create_app(&self, app_table: AppTable, conn: &SqliteConnection) -> Result<(), WorkspaceError> {
+    pub(crate) fn create_app(app_table: AppTable, conn: &SqliteConnection) -> Result<(), WorkspaceError> {
         match diesel_record_count!(app_table, &app_table.id, conn) {
             0 => diesel_insert_table!(app_table, &app_table, conn),
             _ => {
@@ -22,16 +22,12 @@ impl AppTableSql {
         Ok(())
     }
 
-    pub(crate) fn update_app(
-        &self,
-        changeset: AppTableChangeset,
-        conn: &SqliteConnection,
-    ) -> Result<(), WorkspaceError> {
+    pub(crate) fn update_app(changeset: AppTableChangeset, conn: &SqliteConnection) -> Result<(), WorkspaceError> {
         diesel_update_table!(app_table, changeset, conn);
         Ok(())
     }
 
-    pub(crate) fn read_app(&self, app_id: &str, conn: &SqliteConnection) -> Result<AppTable, WorkspaceError> {
+    pub(crate) fn read_app(app_id: &str, conn: &SqliteConnection) -> Result<AppTable, WorkspaceError> {
         let filter = dsl::app_table.filter(app_table::id.eq(app_id)).into_boxed();
 
         // if let Some(is_trash) = is_trash {
@@ -43,7 +39,6 @@ impl AppTableSql {
     }
 
     pub(crate) fn read_apps(
-        &self,
         workspace_id: &str,
         is_trash: bool,
         conn: &SqliteConnection,
@@ -56,7 +51,7 @@ impl AppTableSql {
         Ok(app_table)
     }
 
-    pub(crate) fn delete_app(&self, app_id: &str, conn: &SqliteConnection) -> Result<AppTable, WorkspaceError> {
+    pub(crate) fn delete_app(app_id: &str, conn: &SqliteConnection) -> Result<AppTable, WorkspaceError> {
         let app_table = dsl::app_table
             .filter(app_table::id.eq(app_id))
             .first::<AppTable>(conn)?;

+ 1 - 1
rust-lib/flowy-workspace/src/sql_tables/view/view_sql.rs

@@ -5,7 +5,7 @@ use crate::{
 };
 use flowy_database::{
     prelude::*,
-    schema::{trash_table, view_table, view_table::dsl},
+    schema::{view_table, view_table::dsl},
     SqliteConnection,
 };
 

+ 5 - 1
rust-lib/flowy-workspace/tests/workspace/view_test.rs

@@ -1,5 +1,8 @@
 use flowy_test::{workspace::*, FlowyTest};
-use flowy_workspace::entities::{trash::TrashIdentifier, view::*};
+use flowy_workspace::entities::{
+    trash::{TrashIdentifier, TrashType},
+    view::*,
+};
 
 #[tokio::test]
 #[should_panic]
@@ -24,6 +27,7 @@ async fn view_delete_and_putback() {
         &test.sdk,
         TrashIdentifier {
             id: test.view.id.clone(),
+            ty: TrashType::View,
         },
     )
     .await;