rollback trash record if dependency actions fail

This commit is contained in:
appflowy 2021-10-15 13:10:52 +08:00
parent 33b2eae174
commit bcaa942dfc
13 changed files with 161 additions and 86 deletions

View File

@ -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,
// );
// }
// }

View File

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

View File

@ -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 {}

View File

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

View File

@ -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,
];

View File

@ -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=');

View File

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

View File

@ -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(())
}

View File

@ -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
}

View File

@ -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;

View File

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

View File

@ -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. Its 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)?;

View File

@ -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());
},
}
}