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

This commit is contained in:
appflowy 2021-10-17 22:44:51 +08:00
parent ab36f520de
commit d1b7fd29a6
45 changed files with 810 additions and 838 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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("*")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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