Преглед на файлове

rollback trash record if dependency actions fail

appflowy преди 3 години
родител
ревизия
bcaa942dfc

+ 22 - 8
app_flowy/lib/workspace/presentation/stack_page/trash/trash_page.dart

@@ -79,14 +79,17 @@ class _TrashStackPageState extends State<TrashStackPage> {
           axis: Axis.horizontal,
           child: SizedBox(
             width: TrashSizes.totalWidth,
-            child: CustomScrollView(
-              shrinkWrap: true,
-              physics: StyledScrollPhysics(),
-              controller: _scrollController,
-              slivers: [
-                _renderListHeader(context),
-                _renderListBody(context),
-              ],
+            child: ScrollConfiguration(
+              behavior: const ScrollBehavior().copyWith(scrollbars: false),
+              child: CustomScrollView(
+                shrinkWrap: true,
+                physics: StyledScrollPhysics(),
+                controller: _scrollController,
+                slivers: [
+                  _renderListHeader(context),
+                  _renderListBody(context),
+                ],
+              ),
             ),
           ),
         ),
@@ -160,3 +163,14 @@ class _TrashStackPageState extends State<TrashStackPage> {
     );
   }
 }
+// class TrashScrollbar extends ScrollBehavior {
+//   @override
+//   Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) {
+//     return ScrollbarListStack(
+//       controller: details.controller,
+//       axis: Axis.vertical,
+//       barSize: 6,
+//       child: child,
+//     );
+//   }
+// }

+ 0 - 1
app_flowy/lib/workspace/presentation/stack_page/trash/widget/trash_cell.dart

@@ -32,7 +32,6 @@ class TrashCell extends StatelessWidget {
           onPressed: onDelete,
           icon: svg("editor/delete"),
         ),
-        const HSpace(20),
       ],
     );
   }

+ 1 - 3
app_flowy/lib/workspace/presentation/widgets/menu/menu.dart

@@ -177,7 +177,7 @@ class MenuList extends StatelessWidget {
       data: ExpandableThemeData(useInkWell: true, animationDuration: Durations.medium),
       child: Expanded(
         child: ScrollConfiguration(
-          behavior: const ScrollBehavior(),
+          behavior: const ScrollBehavior().copyWith(scrollbars: false),
           child: ListView.separated(
             itemCount: menuItems.length,
             separatorBuilder: (context, index) {
@@ -197,5 +197,3 @@ class MenuList extends StatelessWidget {
     );
   }
 }
-
-class _NoGlowBehavior extends ScrollBehavior {}

+ 0 - 1
app_flowy/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scroll_bar.dart

@@ -208,7 +208,6 @@ class ScrollbarListStack extends StatelessWidget {
             contentSize: contentSize,
             trackColor: trackColor,
             handleColor: handleColor,
-            showTrack: true,
           ),
         ),
       ],

+ 4 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart

@@ -29,6 +29,8 @@ class WorkspaceEvent extends $pb.ProtobufEnum {
   static const WorkspaceEvent ReadTrash = WorkspaceEvent._(300, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ReadTrash');
   static const WorkspaceEvent PutbackTrash = WorkspaceEvent._(301, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PutbackTrash');
   static const WorkspaceEvent DeleteTrash = WorkspaceEvent._(302, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteTrash');
+  static const WorkspaceEvent RestoreAll = WorkspaceEvent._(303, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RestoreAll');
+  static const WorkspaceEvent DeleteAll = WorkspaceEvent._(304, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteAll');
   static const WorkspaceEvent InitWorkspace = WorkspaceEvent._(1000, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InitWorkspace');
 
   static const $core.List<WorkspaceEvent> values = <WorkspaceEvent> [
@@ -51,6 +53,8 @@ class WorkspaceEvent extends $pb.ProtobufEnum {
     ReadTrash,
     PutbackTrash,
     DeleteTrash,
+    RestoreAll,
+    DeleteAll,
     InitWorkspace,
   ];
 

+ 3 - 1
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart

@@ -31,9 +31,11 @@ const WorkspaceEvent$json = const {
     const {'1': 'ReadTrash', '2': 300},
     const {'1': 'PutbackTrash', '2': 301},
     const {'1': 'DeleteTrash', '2': 302},
+    const {'1': 'RestoreAll', '2': 303},
+    const {'1': 'DeleteAll', '2': 304},
     const {'1': 'InitWorkspace', '2': 1000},
   ],
 };
 
 /// Descriptor for `WorkspaceEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIUChBSZWFkQ3VyV29ya3NwYWNlEAESEgoOUmVhZFdvcmtzcGFjZXMQAhITCg9EZWxldGVXb3Jrc3BhY2UQAxIRCg1PcGVuV29ya3NwYWNlEAQSFQoRUmVhZFdvcmtzcGFjZUFwcHMQBRINCglDcmVhdGVBcHAQZRINCglEZWxldGVBcHAQZhILCgdSZWFkQXBwEGcSDQoJVXBkYXRlQXBwEGgSDwoKQ3JlYXRlVmlldxDJARINCghSZWFkVmlldxDKARIPCgpVcGRhdGVWaWV3EMsBEg8KCkRlbGV0ZVZpZXcQzAESDQoIT3BlblZpZXcQzQESEgoNQXBwbHlEb2NEZWx0YRDOARIOCglSZWFkVHJhc2gQrAISEQoMUHV0YmFja1RyYXNoEK0CEhAKC0RlbGV0ZVRyYXNoEK4CEhIKDUluaXRXb3Jrc3BhY2UQ6Ac=');
+final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIUChBSZWFkQ3VyV29ya3NwYWNlEAESEgoOUmVhZFdvcmtzcGFjZXMQAhITCg9EZWxldGVXb3Jrc3BhY2UQAxIRCg1PcGVuV29ya3NwYWNlEAQSFQoRUmVhZFdvcmtzcGFjZUFwcHMQBRINCglDcmVhdGVBcHAQZRINCglEZWxldGVBcHAQZhILCgdSZWFkQXBwEGcSDQoJVXBkYXRlQXBwEGgSDwoKQ3JlYXRlVmlldxDJARINCghSZWFkVmlldxDKARIPCgpVcGRhdGVWaWV3EMsBEg8KCkRlbGV0ZVZpZXcQzAESDQoIT3BlblZpZXcQzQESEgoNQXBwbHlEb2NEZWx0YRDOARIOCglSZWFkVHJhc2gQrAISEQoMUHV0YmFja1RyYXNoEK0CEhAKC0RlbGV0ZVRyYXNoEK4CEg8KClJlc3RvcmVBbGwQrwISDgoJRGVsZXRlQWxsELACEhIKDUluaXRXb3Jrc3BhY2UQ6Ac=');

+ 6 - 0
rust-lib/flowy-workspace/src/event.rs

@@ -61,6 +61,12 @@ pub enum WorkspaceEvent {
     #[event(input = "TrashIdentifier")]
     DeleteTrash       = 302,
 
+    #[event()]
+    RestoreAll        = 303,
+
+    #[event()]
+    DeleteAll         = 304,
+
     #[event()]
     InitWorkspace     = 1000,
 }

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

@@ -20,7 +20,7 @@ pub(crate) async fn putback_trash_handler(
     identifier: Data<TrashIdentifier>,
     controller: Unit<Arc<TrashCan>>,
 ) -> Result<(), WorkspaceError> {
-    let _ = controller.putback(&identifier.id)?;
+    let _ = controller.putback(&identifier.id).await?;
     Ok(())
 }
 
@@ -29,6 +29,18 @@ pub(crate) async fn delete_trash_handler(
     identifier: Data<TrashIdentifier>,
     controller: Unit<Arc<TrashCan>>,
 ) -> Result<(), WorkspaceError> {
-    let _ = controller.delete_trash(&identifier.id)?;
+    let _ = controller.delete(&identifier.id).await?;
+    Ok(())
+}
+
+#[tracing::instrument(skip(controller), err)]
+pub(crate) async fn restore_all_handler(controller: Unit<Arc<TrashCan>>) -> Result<(), WorkspaceError> {
+    let _ = controller.restore_all()?;
+    Ok(())
+}
+
+#[tracing::instrument(skip(controller), err)]
+pub(crate) async fn delete_all_handler(controller: Unit<Arc<TrashCan>>) -> Result<(), WorkspaceError> {
+    let _ = controller.delete_all()?;
     Ok(())
 }

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

@@ -94,7 +94,9 @@ pub fn create(workspace: Arc<WorkspaceController>) -> Module {
     module = module
         .event(WorkspaceEvent::ReadTrash, read_trash_handler)
         .event(WorkspaceEvent::PutbackTrash, putback_trash_handler)
-        .event(WorkspaceEvent::DeleteTrash, delete_trash_handler);
+        .event(WorkspaceEvent::DeleteTrash, delete_trash_handler)
+        .event(WorkspaceEvent::RestoreAll, restore_all_handler)
+        .event(WorkspaceEvent::DeleteAll, delete_all_handler);
 
     module
 }

+ 18 - 7
rust-lib/flowy-workspace/src/protobuf/model/event.rs

@@ -44,6 +44,8 @@ pub enum WorkspaceEvent {
     ReadTrash = 300,
     PutbackTrash = 301,
     DeleteTrash = 302,
+    RestoreAll = 303,
+    DeleteAll = 304,
     InitWorkspace = 1000,
 }
 
@@ -73,6 +75,8 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
             300 => ::std::option::Option::Some(WorkspaceEvent::ReadTrash),
             301 => ::std::option::Option::Some(WorkspaceEvent::PutbackTrash),
             302 => ::std::option::Option::Some(WorkspaceEvent::DeleteTrash),
+            303 => ::std::option::Option::Some(WorkspaceEvent::RestoreAll),
+            304 => ::std::option::Option::Some(WorkspaceEvent::DeleteAll),
             1000 => ::std::option::Option::Some(WorkspaceEvent::InitWorkspace),
             _ => ::std::option::Option::None
         }
@@ -99,6 +103,8 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
             WorkspaceEvent::ReadTrash,
             WorkspaceEvent::PutbackTrash,
             WorkspaceEvent::DeleteTrash,
+            WorkspaceEvent::RestoreAll,
+            WorkspaceEvent::DeleteAll,
             WorkspaceEvent::InitWorkspace,
         ];
         values
@@ -128,7 +134,7 @@ impl ::protobuf::reflect::ProtobufValue for WorkspaceEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0bevent.proto*\xf6\x02\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorksp\
+    \n\x0bevent.proto*\x97\x03\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorksp\
     ace\x10\0\x12\x14\n\x10ReadCurWorkspace\x10\x01\x12\x12\n\x0eReadWorkspa\
     ces\x10\x02\x12\x13\n\x0fDeleteWorkspace\x10\x03\x12\x11\n\rOpenWorkspac\
     e\x10\x04\x12\x15\n\x11ReadWorkspaceApps\x10\x05\x12\r\n\tCreateApp\x10e\
@@ -137,10 +143,11 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x12\x0f\n\nUpdateView\x10\xcb\x01\x12\x0f\n\nDeleteView\x10\xcc\x01\x12\
     \r\n\x08OpenView\x10\xcd\x01\x12\x12\n\rApplyDocDelta\x10\xce\x01\x12\
     \x0e\n\tReadTrash\x10\xac\x02\x12\x11\n\x0cPutbackTrash\x10\xad\x02\x12\
-    \x10\n\x0bDeleteTrash\x10\xae\x02\x12\x12\n\rInitWorkspace\x10\xe8\x07J\
-    \xde\x06\n\x06\x12\x04\0\0\x17\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\
-    \x02\x05\0\x12\x04\x02\0\x17\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x13\
-    \n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x18\n\x0c\n\x05\x05\0\x02\0\x01\
+    \x10\n\x0bDeleteTrash\x10\xae\x02\x12\x0f\n\nRestoreAll\x10\xaf\x02\x12\
+    \x0e\n\tDeleteAll\x10\xb0\x02\x12\x12\n\rInitWorkspace\x10\xe8\x07J\xb0\
+    \x07\n\x06\x12\x04\0\0\x19\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\
+    \x05\0\x12\x04\x02\0\x19\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x13\n\
+    \x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x18\n\x0c\n\x05\x05\0\x02\0\x01\
     \x12\x03\x03\x04\x13\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x03\x16\x17\n\
     \x0b\n\x04\x05\0\x02\x01\x12\x03\x04\x04\x19\n\x0c\n\x05\x05\0\x02\x01\
     \x01\x12\x03\x04\x04\x14\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x04\x17\
@@ -179,8 +186,12 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x02\x12\x03\x14\x13\x16\n\x0b\n\x04\x05\0\x02\x12\x12\x03\x15\x04\x16\n\
     \x0c\n\x05\x05\0\x02\x12\x01\x12\x03\x15\x04\x0f\n\x0c\n\x05\x05\0\x02\
     \x12\x02\x12\x03\x15\x12\x15\n\x0b\n\x04\x05\0\x02\x13\x12\x03\x16\x04\
-    \x19\n\x0c\n\x05\x05\0\x02\x13\x01\x12\x03\x16\x04\x11\n\x0c\n\x05\x05\0\
-    \x02\x13\x02\x12\x03\x16\x14\x18b\x06proto3\
+    \x15\n\x0c\n\x05\x05\0\x02\x13\x01\x12\x03\x16\x04\x0e\n\x0c\n\x05\x05\0\
+    \x02\x13\x02\x12\x03\x16\x11\x14\n\x0b\n\x04\x05\0\x02\x14\x12\x03\x17\
+    \x04\x14\n\x0c\n\x05\x05\0\x02\x14\x01\x12\x03\x17\x04\r\n\x0c\n\x05\x05\
+    \0\x02\x14\x02\x12\x03\x17\x10\x13\n\x0b\n\x04\x05\0\x02\x15\x12\x03\x18\
+    \x04\x19\n\x0c\n\x05\x05\0\x02\x15\x01\x12\x03\x18\x04\x11\n\x0c\n\x05\
+    \x05\0\x02\x15\x02\x12\x03\x18\x14\x18b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 2 - 0
rust-lib/flowy-workspace/src/protobuf/proto/event.proto

@@ -20,5 +20,7 @@ enum WorkspaceEvent {
     ReadTrash = 300;
     PutbackTrash = 301;
     DeleteTrash = 302;
+    RestoreAll = 303;
+    DeleteAll = 304;
     InitWorkspace = 1000;
 }

+ 40 - 31
rust-lib/flowy-workspace/src/services/trash_can.rs

@@ -8,23 +8,23 @@ use crate::{
 use flowy_database::SqliteConnection;
 use parking_lot::RwLock;
 use std::{collections::HashSet, sync::Arc};
-use tokio::sync::broadcast;
+use tokio::sync::{broadcast, mpsc};
 
-#[derive(Clone, PartialEq, Eq)]
+#[derive(Clone)]
 pub enum TrashEvent {
-    Putback(TrashSource, String),
-    Delete(TrashSource, String),
+    Putback(TrashSource, Vec<String>, mpsc::Sender<WorkspaceResult<()>>),
+    Delete(TrashSource, Vec<String>, mpsc::Sender<WorkspaceResult<()>>),
 }
 
 impl TrashEvent {
     pub fn select(self, s: TrashSource) -> Option<TrashEvent> {
         match &self {
-            TrashEvent::Putback(source, id) => {
+            TrashEvent::Putback(source, _, _) => {
                 if source == &s {
                     return Some(self);
                 }
             },
-            TrashEvent::Delete(source, id) => {
+            TrashEvent::Delete(source, _, _) => {
                 if source == &s {
                     return Some(self);
                 }
@@ -32,13 +32,6 @@ impl TrashEvent {
         }
         None
     }
-
-    fn split(self) -> (TrashSource, String) {
-        match self {
-            TrashEvent::Putback(source, id) => (source, id),
-            TrashEvent::Delete(source, id) => (source, id),
-        }
-    }
 }
 
 pub struct TrashCan {
@@ -59,38 +52,52 @@ impl TrashCan {
     }
 
     #[tracing::instrument(level = "debug", skip(self), fields(putback)  err)]
-    pub fn putback(&self, trash_id: &str) -> WorkspaceResult<()> {
+    pub async fn putback(&self, trash_id: &str) -> WorkspaceResult<()> {
+        let (tx, mut rx) = mpsc::channel::<WorkspaceResult<()>>(1);
+        let trash_table = TrashTableSql::read(trash_id, &*self.database.db_connection()?)?;
+        tracing::Span::current().record(
+            "putback",
+            &format!("{:?}: {}", &trash_table.source, trash_table.id).as_str(),
+        );
+        self.notify
+            .send(TrashEvent::Putback(trash_table.source, vec![trash_table.id], tx));
+
+        let _ = rx.recv().await.unwrap()?;
         let conn = self.database.db_connection()?;
         let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
-            let trash_table = TrashTableSql::read(trash_id, &*conn)?;
             let _ = TrashTableSql::delete_trash(trash_id, &*conn)?;
-            tracing::Span::current().record(
-                "putback",
-                &format!("{:?}: {}", &trash_table.source, trash_table.id).as_str(),
-            );
-
-            self.notify
-                .send(TrashEvent::Putback(trash_table.source, trash_table.id));
-
             let _ = self.notify_dart_trash_did_update(&conn)?;
             Ok(())
         })?;
-
         Ok(())
     }
 
     #[tracing::instrument(level = "debug", skip(self)  err)]
-    pub fn delete_trash(&self, trash_id: &str) -> WorkspaceResult<()> {
-        let conn = self.database.db_connection()?;
-        let trash_table = TrashTableSql::read(trash_id, &*conn)?;
-        let _ = TrashTableSql::delete_trash(trash_id, &*conn)?;
+    pub fn restore_all(&self) -> WorkspaceResult<()> { Ok(()) }
+
+    #[tracing::instrument(level = "debug", skip(self)  err)]
+    pub fn delete_all(&self) -> WorkspaceResult<()> { Ok(()) }
+
+    #[tracing::instrument(level = "debug", skip(self)  err)]
+    pub async fn delete(&self, trash_id: &str) -> 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.source, vec![trash_table.id], tx));
+
+        let _ = rx.recv().await.unwrap()?;
+        let _ = TrashTableSql::delete_trash(trash_id, &*self.database.db_connection()?)?;
 
-        let _ = self.notify.send(TrashEvent::Delete(trash_table.source, trash_table.id));
         Ok(())
     }
 
-    pub fn subscribe(&self) -> broadcast::Receiver<TrashEvent> { self.notify.subscribe() }
-
+    // [[ transaction ]]
+    // https://www.tutlane.com/tutorial/sqlite/sqlite-transactions-begin-commit-rollback
+    // We can use these commands only when we are performing INSERT, UPDATE, and
+    // DELETE operations. It’s not possible for us to use these commands to
+    // CREATE and DROP tables operations because those are auto-commit in the
+    // database.
     #[tracing::instrument(level = "debug", skip(self, trash, source, conn), fields(add_trash)  err)]
     pub fn add<T: Into<Trash>>(
         &self,
@@ -119,6 +126,8 @@ impl TrashCan {
         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)?;

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

@@ -137,22 +137,23 @@ impl ViewController {
         let updated_view = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
             let _ = ViewTableSql::update_view(changeset, conn)?;
             let view: View = ViewTableSql::read_view(&view_id, conn)?.into();
-            match params.is_trash {
-                None => {
-                    send_dart_notification(&view_id, WorkspaceNotification::ViewUpdated)
-                        .payload(view.clone())
-                        .send();
-                },
-                Some(is_trash) => {
-                    if is_trash {
-                        self.trash_can.add(view.clone(), TrashSource::View, conn);
-                    }
-                    let _ = notify_view_num_did_change(&view.belong_to_id, conn)?;
-                },
-            }
             Ok(view)
         })?;
 
+        match params.is_trash {
+            None => {
+                send_dart_notification(&view_id, WorkspaceNotification::ViewUpdated)
+                    .payload(updated_view.clone())
+                    .send();
+            },
+            Some(is_trash) => {
+                if is_trash {
+                    self.trash_can.add(updated_view.clone(), TrashSource::View, conn)?;
+                }
+                let _ = notify_view_num_did_change(&updated_view.belong_to_id, conn)?;
+            },
+        }
+
         let _ = self.update_view_on_server(params);
         Ok(updated_view)
     }
@@ -252,27 +253,43 @@ fn notify_view_num_did_change(belong_to_id: &str, conn: &SqliteConnection) -> Wo
 }
 
 fn handle_trash_event(database: Arc<dyn WorkspaceDatabase>, event: TrashEvent) {
-    let result = || {
-        let conn = &*database.db_connection()?;
-        match event {
-            TrashEvent::Putback(_, pub_back_id) => {
+    let db_result = database.db_connection();
+    match event {
+        TrashEvent::Putback(_, putback_ids, ret) => {
+            let result = || {
+                let conn = &*db_result?;
                 let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
-                    let view_table = ViewTableSql::read_view(&pub_back_id, conn)?;
-                    notify_view_num_did_change(&view_table.belong_to_id, conn)
+                    for putback_id in putback_ids {
+                        match ViewTableSql::read_view(&putback_id, conn) {
+                            Ok(view_table) => {
+                                let _ = notify_view_num_did_change(&view_table.belong_to_id, conn)?;
+                            },
+                            Err(e) => log::error!("Putback view: {} failed: {:?}", putback_id, e),
+                        }
+                    }
+                    Ok(())
                 })?;
-            },
-            TrashEvent::Delete(_, delete_id) => {
+                Ok::<(), WorkspaceError>(())
+            };
+            ret.send(result());
+        },
+        TrashEvent::Delete(_, delete_ids, ret) => {
+            let result = || {
+                let conn = &*db_result?;
                 let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
-                    let view_table = ViewTableSql::delete_view(&delete_id, conn)?;
-                    notify_view_num_did_change(&view_table.belong_to_id, conn)
+                    for delete_id in delete_ids {
+                        match ViewTableSql::delete_view(&delete_id, conn) {
+                            Ok(view_table) => {
+                                let _ = notify_view_num_did_change(&view_table.belong_to_id, conn)?;
+                            },
+                            Err(e) => log::error!("Delete view: {} failed: {:?}", delete_id, e),
+                        }
+                    }
+                    Ok(())
                 })?;
-            },
-        }
-        Ok::<(), WorkspaceError>(())
-    };
-
-    match result() {
-        Ok(_) => {},
-        Err(e) => log::error!("{:?}", e),
+                Ok::<(), WorkspaceError>(())
+            };
+            ret.send(result());
+        },
     }
 }