TF-3370 Handle SetError when make email action (Mark as read/star/move/delete)

This commit is contained in:
dab246
2025-01-02 13:58:30 +07:00
committed by Dat H. Pham
parent 47963d162b
commit 0f94e52d35
17 changed files with 800 additions and 181 deletions
@@ -9,6 +9,8 @@ import 'package:dio/dio.dart';
import 'package:email_recovery/email_recovery/email_recovery_action.dart'; import 'package:email_recovery/email_recovery/email_recovery_action.dart';
import 'package:email_recovery/email_recovery/email_recovery_action_id.dart'; import 'package:email_recovery/email_recovery/email_recovery_action_id.dart';
import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/error/set_error.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart';
@@ -43,7 +45,10 @@ abstract class EmailDataSource {
} }
); );
Future<List<EmailId>> markAsRead( Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> markAsRead(
Session session, Session session,
AccountId accountId, AccountId accountId,
List<EmailId> emailIds, List<EmailId> emailIds,
@@ -75,9 +80,19 @@ abstract class EmailDataSource {
{CancelToken? cancelToken} {CancelToken? cancelToken}
); );
Future<List<EmailId>> moveToMailbox(Session session, AccountId accountId, MoveToMailboxRequest moveRequest); Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> moveToMailbox(
Session session,
AccountId accountId,
MoveToMailboxRequest moveRequest,
);
Future<List<EmailId>> markAsStar( Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> markAsStar(
Session session, Session session,
AccountId accountId, AccountId accountId,
List<EmailId> emailIds, List<EmailId> emailIds,
@@ -106,8 +121,14 @@ abstract class EmailDataSource {
{CancelToken? cancelToken} {CancelToken? cancelToken}
); );
Future<List<EmailId>> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List<EmailId> emailIds); Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> deleteMultipleEmailsPermanently(
Session session,
AccountId accountId,
List<EmailId> emailIds,
);
Future<bool> deleteEmailPermanently( Future<bool> deleteEmailPermanently(
Session session, Session session,
AccountId accountId, AccountId accountId,
@@ -10,6 +10,8 @@ import 'package:email_recovery/email_recovery/email_recovery_action.dart';
import 'package:email_recovery/email_recovery/email_recovery_action_id.dart'; import 'package:email_recovery/email_recovery/email_recovery_action_id.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/error/set_error.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart';
@@ -75,7 +77,10 @@ class EmailDataSourceImpl extends EmailDataSource {
} }
@override @override
Future<List<EmailId>> markAsRead( Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> markAsRead(
Session session, Session session,
AccountId accountId, AccountId accountId,
List<EmailId> emailIds, List<EmailId> emailIds,
@@ -112,14 +117,24 @@ class EmailDataSourceImpl extends EmailDataSource {
} }
@override @override
Future<List<EmailId>> moveToMailbox(Session session, AccountId accountId, MoveToMailboxRequest moveRequest) { Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> moveToMailbox(
Session session,
AccountId accountId,
MoveToMailboxRequest moveRequest,
) {
return Future.sync(() async { return Future.sync(() async {
return await emailAPI.moveToMailbox(session, accountId, moveRequest); return await emailAPI.moveToMailbox(session, accountId, moveRequest);
}).catchError(_exceptionThrower.throwException); }).catchError(_exceptionThrower.throwException);
} }
@override @override
Future<List<EmailId>> markAsStar( Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> markAsStar(
Session session, Session session,
AccountId accountId, AccountId accountId,
List<EmailId> emailIds, List<EmailId> emailIds,
@@ -211,9 +226,20 @@ class EmailDataSourceImpl extends EmailDataSource {
} }
@override @override
Future<List<EmailId>> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List<EmailId> emailIds) { Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> deleteMultipleEmailsPermanently(
Session session,
AccountId accountId,
List<EmailId> emailIds,
) {
return Future.sync(() async { return Future.sync(() async {
return await emailAPI.deleteMultipleEmailsPermanently(session, accountId, emailIds); return await emailAPI.deleteMultipleEmailsPermanently(
session,
accountId,
emailIds,
);
}).catchError(_exceptionThrower.throwException); }).catchError(_exceptionThrower.throwException);
} }
@@ -12,6 +12,8 @@ import 'package:dio/dio.dart';
import 'package:email_recovery/email_recovery/email_recovery_action.dart'; import 'package:email_recovery/email_recovery/email_recovery_action.dart';
import 'package:email_recovery/email_recovery/email_recovery_action_id.dart'; import 'package:email_recovery/email_recovery/email_recovery_action_id.dart';
import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/error/set_error.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart';
@@ -82,7 +84,14 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource {
} }
@override @override
Future<List<EmailId>> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List<EmailId> emailIds) { Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> deleteMultipleEmailsPermanently(
Session session,
AccountId accountId,
List<EmailId> emailIds,
) {
throw UnimplementedError(); throw UnimplementedError();
} }
@@ -120,7 +129,10 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource {
} }
@override @override
Future<List<EmailId>> markAsRead( Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> markAsRead(
Session session, Session session,
AccountId accountId, AccountId accountId,
List<EmailId> emailIds, List<EmailId> emailIds,
@@ -130,12 +142,27 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource {
} }
@override @override
Future<List<EmailId>> markAsStar(Session session, AccountId accountId, List<EmailId> emailIds, MarkStarAction markStarAction) { Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> markAsStar(
Session session,
AccountId accountId,
List<EmailId> emailIds,
MarkStarAction markStarAction,
) {
throw UnimplementedError(); throw UnimplementedError();
} }
@override @override
Future<List<EmailId>> moveToMailbox(Session session, AccountId accountId, MoveToMailboxRequest moveRequest) { Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> moveToMailbox(
Session session,
AccountId accountId,
MoveToMailboxRequest moveRequest,
) {
throw UnimplementedError(); throw UnimplementedError();
} }
+124 -89
View File
@@ -18,6 +18,7 @@ import 'package:jmap_dart_client/http/http_client.dart';
import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart';
import 'package:jmap_dart_client/jmap/core/capability/core_capability.dart'; import 'package:jmap_dart_client/jmap/core/capability/core_capability.dart';
import 'package:jmap_dart_client/jmap/core/error/set_error.dart';
import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/patch_object.dart'; import 'package:jmap_dart_client/jmap/core/patch_object.dart';
import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
@@ -44,13 +45,13 @@ import 'package:model/account/account_request.dart';
import 'package:model/account/authentication_type.dart'; import 'package:model/account/authentication_type.dart';
import 'package:model/download/download_task_id.dart'; import 'package:model/download/download_task_id.dart';
import 'package:model/email/attachment.dart'; import 'package:model/email/attachment.dart';
import 'package:model/email/email_property.dart';
import 'package:model/email/mark_star_action.dart'; import 'package:model/email/mark_star_action.dart';
import 'package:model/email/read_actions.dart'; import 'package:model/email/read_actions.dart';
import 'package:model/extensions/email_extension.dart'; import 'package:model/extensions/email_extension.dart';
import 'package:model/extensions/email_id_extensions.dart'; import 'package:model/extensions/email_id_extensions.dart';
import 'package:model/extensions/keyword_identifier_extension.dart'; import 'package:model/extensions/keyword_identifier_extension.dart';
import 'package:model/extensions/list_email_id_extension.dart'; import 'package:model/extensions/list_email_id_extension.dart';
import 'package:model/extensions/list_id_extension.dart';
import 'package:model/extensions/mailbox_id_extension.dart'; import 'package:model/extensions/mailbox_id_extension.dart';
import 'package:model/extensions/session_extension.dart'; import 'package:model/extensions/session_extension.dart';
import 'package:model/oidc/token_oidc.dart'; import 'package:model/oidc/token_oidc.dart';
@@ -237,16 +238,21 @@ class EmailAPI with HandleSetErrorMixin {
} }
} }
Future<List<EmailId>> markAsRead( Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> markAsRead(
Session session, Session session,
AccountId accountId, AccountId accountId,
List<EmailId> emailIds, List<EmailId> emailIds,
ReadActions readActions, ReadActions readActions,
) async { ) async {
final maxBatches = _getMaxObjectsInSetMethod(session, accountId); final maxObjects = _getMaxObjectsInSetMethod(session, accountId);
final totalEmails = emails.length; final totalEmails = emailIds.length;
final maxBatches = min(totalEmails, maxObjects);
final List<Email> updatedEmails = List.empty(growable: true); final List<EmailId> updatedEmailIds = List.empty(growable: true);
final Map<Id, SetError> mapErrors = <Id, SetError>{};
for (int start = 0; start < totalEmails; start += maxBatches) { for (int start = 0; start < totalEmails; start += maxBatches) {
int end = (start + maxBatches < totalEmails) int end = (start + maxBatches < totalEmails)
@@ -254,20 +260,16 @@ class EmailAPI with HandleSetErrorMixin {
: totalEmails; : totalEmails;
log('EmailAPI::markAsRead:emails from ${start + 1} to $end'); log('EmailAPI::markAsRead:emails from ${start + 1} to $end');
final currentListEmails = emails.sublist(start, end); final currentListEmailIds = emailIds.sublist(start, end);
final setEmailMethod = SetEmailMethod(accountId) final setEmailMethod = SetEmailMethod(accountId)
..addUpdates(currentListEmails.listEmailIds.generateMapUpdateObjectMarkAsRead(readActions)); ..addUpdates(
currentListEmailIds.generateMapUpdateObjectMarkAsRead(readActions)
final getEmailMethod = GetEmailMethod(accountId) );
..addIds(emails.listEmailIds.toIds().toSet())
..addProperties(Properties({EmailProperty.keywords}));
final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation());
requestBuilder.invocation(setEmailMethod); final setEmailInvocation = requestBuilder.invocation(setEmailMethod);
final getEmailInvocation = requestBuilder.invocation(getEmailMethod);
final capabilities = setEmailMethod.requiredCapabilities final capabilities = setEmailMethod.requiredCapabilities
.toCapabilitiesSupportTeamMailboxes(session, accountId); .toCapabilitiesSupportTeamMailboxes(session, accountId);
@@ -277,18 +279,19 @@ class EmailAPI with HandleSetErrorMixin {
.build() .build()
.execute(); .execute();
final getEmailResponse = response.parse<GetEmailResponse>( final setEmailResponse = response.parse<SetEmailResponse>(
getEmailInvocation.methodCallId, setEmailInvocation.methodCallId,
GetEmailResponse.deserialize, SetEmailResponse.deserialize,
); );
final listEmails = getEmailResponse?.list ?? []; final listEmailIds = setEmailResponse?.updated?.keys.toEmailIds() ?? [];
if (listEmails.isNotEmpty) { final mapErrors = handleSetResponse([setEmailResponse]);
updatedEmails.addAll(listEmails);
} updatedEmailIds.addAll(listEmailIds);
mapErrors.addAll(mapErrors);
} }
return updatedEmails; return (emailIdsSuccess: updatedEmailIds, mapErrors: mapErrors);
} }
Future<List<DownloadTaskId>> downloadAttachments( Future<List<DownloadTaskId>> downloadAttachments(
@@ -397,47 +400,55 @@ class EmailAPI with HandleSetErrorMixin {
return bytesDownloaded; return bytesDownloaded;
} }
Future<List<EmailId>> moveToMailbox( Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> moveToMailbox(
Session session, Session session,
AccountId accountId, AccountId accountId,
MoveToMailboxRequest moveRequest MoveToMailboxRequest moveRequest
) async { ) async {
final maxBatches = _getMaxObjectsInSetMethod(session, accountId);
final List<EmailId> listEmailIdResult = List.empty(growable: true); final List<EmailId> listEmailIdResult = List.empty(growable: true);
final Map<Id, SetError> mapErrors = <Id, SetError>{};
final listMailboxIds = moveRequest.currentMailboxes.keys.toList(); final listMailboxIds = moveRequest.currentMailboxes.keys.toList();
for (int i = 0; i < listMailboxIds.length; i++) { for (int i = 0; i < listMailboxIds.length; i++) {
final currentMailboxId = listMailboxIds[i]; final currentMailboxId = listMailboxIds[i];
final listEmailIds = moveRequest.currentMailboxes[currentMailboxId]!; final listEmailIds = moveRequest.currentMailboxes[currentMailboxId]!;
log('EmailAPI::moveToMailbox:from mailbox ${currentMailboxId.asString} with ${listEmailIds.length} emails to mailbox ${moveRequest.destinationMailboxId.asString}'); log('EmailAPI::moveToMailbox:from mailbox ${currentMailboxId.asString} with ${listEmailIds.length} emails to mailbox ${moveRequest.destinationMailboxId.asString}');
final movedEmailIds = await _moveEmailsBetweenMailboxes( final resultRecords = await _moveEmailsBetweenMailboxes(
session: session, session: session,
accountId: accountId, accountId: accountId,
listEmailIds: listEmailIds, emailIds: listEmailIds,
currentMailboxId: currentMailboxId, currentMailboxId: currentMailboxId,
maxBatches: maxBatches,
destinationMailboxId: moveRequest.destinationMailboxId, destinationMailboxId: moveRequest.destinationMailboxId,
isMovingToSpam: moveRequest.isMovingToSpam, isMovingToSpam: moveRequest.isMovingToSpam,
); );
listEmailIdResult.addAll(movedEmailIds);
listEmailIdResult.addAll(resultRecords.emailIdsSuccess);
mapErrors.addAll(resultRecords.mapErrors);
} }
return listEmailIdResult;
return (emailIdsSuccess: listEmailIdResult, mapErrors: mapErrors);
} }
Future<List<EmailId>> _moveEmailsBetweenMailboxes({ Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> _moveEmailsBetweenMailboxes({
required Session session, required Session session,
required AccountId accountId, required AccountId accountId,
required List<EmailId> listEmailIds, required List<EmailId> emailIds,
required MailboxId currentMailboxId, required MailboxId currentMailboxId,
required MailboxId destinationMailboxId, required MailboxId destinationMailboxId,
required int maxBatches,
bool isMovingToSpam = false, bool isMovingToSpam = false,
}) async { }) async {
final maxBatches = _getMaxObjectsInSetMethod(session, accountId); final maxObjects = _getMaxObjectsInSetMethod(session, accountId);
final totalEmails = listEmailIds.length; final totalEmails = emailIds.length;
final maxBatches = min(totalEmails, maxObjects);
final List<EmailId> updatedEmailIds = List.empty(growable: true); final List<EmailId> updatedEmailIds = List.empty(growable: true);
final Map<Id, SetError> mapErrors = <Id, SetError>{};
for (int start = 0; start < totalEmails; start += maxBatches) { for (int start = 0; start < totalEmails; start += maxBatches) {
int end = (start + maxBatches < totalEmails) int end = (start + maxBatches < totalEmails)
@@ -445,7 +456,7 @@ class EmailAPI with HandleSetErrorMixin {
: totalEmails; : totalEmails;
log('EmailAPI::_moveEmailsBetweenMailboxes:emails from ${start + 1} to $end'); log('EmailAPI::_moveEmailsBetweenMailboxes:emails from ${start + 1} to $end');
final currentEmailIds = listEmailIds.sublist(start, end); final currentEmailIds = emailIds.sublist(start, end);
final moveProperties = isMovingToSpam final moveProperties = isMovingToSpam
? currentEmailIds.generateMapUpdateObjectMoveToSpam( ? currentEmailIds.generateMapUpdateObjectMoveToSpam(
@@ -477,13 +488,14 @@ class EmailAPI with HandleSetErrorMixin {
SetEmailResponse.deserialize, SetEmailResponse.deserialize,
); );
final listIdsUpdated = setEmailResponse?.updated?.keys ?? []; final listEmailIds = setEmailResponse?.updated?.keys.toEmailIds() ?? [];
if (listIdsUpdated.isNotEmpty == true) { final mapErrors = handleSetResponse([setEmailResponse]);
final listEmailIdsUpdated = listIdsUpdated.map((e) => EmailId(e)).toList();
updatedEmailIds.addAll(listEmailIdsUpdated); updatedEmailIds.addAll(listEmailIds);
} mapErrors.addAll(mapErrors);
} }
return updatedEmailIds;
return (emailIdsSuccess: updatedEmailIds, mapErrors: mapErrors);
} }
int _getMaxObjectsInSetMethod(Session session, AccountId accountId) { int _getMaxObjectsInSetMethod(Session session, AccountId accountId) {
@@ -502,16 +514,21 @@ class EmailAPI with HandleSetErrorMixin {
return minOfMaxObjectsInSetMethod; return minOfMaxObjectsInSetMethod;
} }
Future<List<EmailId>> markAsStar( Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> markAsStar(
Session session, Session session,
AccountId accountId, AccountId accountId,
List<EmailId> emailIds, List<EmailId> emailIds,
MarkStarAction markStarAction MarkStarAction markStarAction
) async { ) async {
final maxBatches = _getMaxObjectsInSetMethod(session, accountId); final maxObjects = _getMaxObjectsInSetMethod(session, accountId);
final totalEmails = emails.length; final totalEmails = emailIds.length;
final maxBatches = min(totalEmails, maxObjects);
final List<Email> updatedEmails = List.empty(growable: true); final List<EmailId> updatedEmailIds = List.empty(growable: true);
final Map<Id, SetError> mapErrors = <Id, SetError>{};
for (int start = 0; start < totalEmails; start += maxBatches) { for (int start = 0; start < totalEmails; start += maxBatches) {
int end = (start + maxBatches < totalEmails) int end = (start + maxBatches < totalEmails)
@@ -519,20 +536,16 @@ class EmailAPI with HandleSetErrorMixin {
: totalEmails; : totalEmails;
log('EmailAPI::markAsStar:emails from ${start + 1} to $end'); log('EmailAPI::markAsStar:emails from ${start + 1} to $end');
final currentListEmails = emails.sublist(start, end); final currentListEmailIds = emailIds.sublist(start, end);
final setEmailMethod = SetEmailMethod(accountId) final setEmailMethod = SetEmailMethod(accountId)
..addUpdates(currentListEmails.listEmailIds.generateMapUpdateObjectMarkAsStar(markStarAction)); ..addUpdates(
currentListEmailIds.generateMapUpdateObjectMarkAsStar(markStarAction),
final getEmailMethod = GetEmailMethod(accountId) );
..addIds(emails.listEmailIds.toIds().toSet())
..addProperties(Properties({EmailProperty.keywords}));
final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation());
requestBuilder.invocation(setEmailMethod); final setEmailInvocation = requestBuilder.invocation(setEmailMethod);
final getEmailInvocation = requestBuilder.invocation(getEmailMethod);
final capabilities = setEmailMethod.requiredCapabilities final capabilities = setEmailMethod.requiredCapabilities
.toCapabilitiesSupportTeamMailboxes(session, accountId); .toCapabilitiesSupportTeamMailboxes(session, accountId);
@@ -542,18 +555,19 @@ class EmailAPI with HandleSetErrorMixin {
.build() .build()
.execute(); .execute();
final getEmailResponse = response.parse<GetEmailResponse>( final setEmailResponse = response.parse<SetEmailResponse>(
getEmailInvocation.methodCallId, setEmailInvocation.methodCallId,
GetEmailResponse.deserialize, SetEmailResponse.deserialize,
); );
final listEmails = getEmailResponse?.list ?? []; final listEmailIds = setEmailResponse?.updated?.keys.toEmailIds() ?? [];
if (listEmails.isNotEmpty) { final mapErrors = handleSetResponse([setEmailResponse]);
updatedEmails.addAll(listEmails);
} updatedEmailIds.addAll(listEmailIds);
mapErrors.addAll(mapErrors);
} }
return updatedEmails; return (emailIdsSuccess: updatedEmailIds, mapErrors: mapErrors);
} }
Future<Email> saveEmailAsDrafts( Future<Email> saveEmailAsDrafts(
@@ -656,36 +670,56 @@ class EmailAPI with HandleSetErrorMixin {
return emailCreated; return emailCreated;
} }
Future<List<EmailId>> deleteMultipleEmailsPermanently( Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> deleteMultipleEmailsPermanently(
Session session, Session session,
AccountId accountId, AccountId accountId,
List<EmailId> emailIds List<EmailId> emailIds
) async { ) async {
final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); final maxObjects = _getMaxObjectsInSetMethod(session, accountId);
final setEmailMethod = SetEmailMethod(accountId) final totalEmails = emailIds.length;
..addDestroy(emailIds.map((emailId) => emailId.id).toSet()); final maxBatches = min(totalEmails, maxObjects);
final setEmailInvocation = requestBuilder.invocation(setEmailMethod); final List<EmailId> destroyedEmailIds = List.empty(growable: true);
final Map<Id, SetError> mapErrors = <Id, SetError>{};
final capabilities = setEmailMethod.requiredCapabilities for (int start = 0; start < totalEmails; start += maxBatches) {
.toCapabilitiesSupportTeamMailboxes(session, accountId); int end = (start + maxBatches < totalEmails)
? start + maxBatches
: totalEmails;
log('EmailAPI::deleteMultipleEmailsPermanently:emails from ${start + 1} to $end');
final response = await (requestBuilder final currentListEmailIds = emailIds.sublist(start, end);
..usings(capabilities))
.build()
.execute();
final setEmailResponse = response.parse<SetEmailResponse>( final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation());
final setEmailMethod = SetEmailMethod(accountId)
..addDestroy(currentListEmailIds.toIds().toSet());
final setEmailInvocation = requestBuilder.invocation(setEmailMethod);
final capabilities = setEmailMethod.requiredCapabilities
.toCapabilitiesSupportTeamMailboxes(session, accountId);
final response = await (requestBuilder
..usings(capabilities))
.build()
.execute();
final setEmailResponse = response.parse<SetEmailResponse>(
setEmailInvocation.methodCallId, setEmailInvocation.methodCallId,
SetEmailResponse.deserialize); SetEmailResponse.deserialize,
);
final listIdResult = setEmailResponse?.destroyed; final listEmailIds = setEmailResponse?.destroyed?.toEmailIds() ?? [];
final mapErrors = handleSetResponse([setEmailResponse]);
if (listIdResult != null) { destroyedEmailIds.addAll(listEmailIds);
return listIdResult.map((id) => EmailId(id)).toList(); mapErrors.addAll(mapErrors);
} }
return List.empty(); return (emailIdsSuccess: destroyedEmailIds, mapErrors: mapErrors);
} }
Future<bool> deleteEmailPermanently( Future<bool> deleteEmailPermanently(
@@ -698,19 +732,20 @@ class EmailAPI with HandleSetErrorMixin {
final setEmailMethod = SetEmailMethod(accountId) final setEmailMethod = SetEmailMethod(accountId)
..addDestroy({emailId.id}); ..addDestroy({emailId.id});
final setEmailInvocation = requestBuilder.invocation(setEmailMethod); final setEmailInvocation = requestBuilder.invocation(setEmailMethod);
final capabilities = setEmailMethod.requiredCapabilities final capabilities = setEmailMethod.requiredCapabilities
.toCapabilitiesSupportTeamMailboxes(session, accountId); .toCapabilitiesSupportTeamMailboxes(session, accountId);
final response = await (requestBuilder final response = await (requestBuilder
..usings(capabilities)) ..usings(capabilities))
.build() .build()
.execute(cancelToken: cancelToken); .execute(cancelToken: cancelToken);
final setEmailResponse = response.parse<SetEmailResponse>( final setEmailResponse = response.parse<SetEmailResponse>(
setEmailInvocation.methodCallId, setEmailInvocation.methodCallId,
SetEmailResponse.deserialize); SetEmailResponse.deserialize,
);
return setEmailResponse?.destroyed?.contains(emailId.id) == true; return setEmailResponse?.destroyed?.contains(emailId.id) == true;
} }
@@ -757,10 +792,10 @@ class EmailAPI with HandleSetErrorMixin {
final capabilities = setEmailMethod.requiredCapabilities.toCapabilitiesSupportTeamMailboxes(session, accountId); final capabilities = setEmailMethod.requiredCapabilities.toCapabilitiesSupportTeamMailboxes(session, accountId);
final response = await (requestBuilder final response = await (requestBuilder
..usings(capabilities)) ..usings(capabilities))
.build() .build()
.execute(); .execute();
final setEmailResponse = response.parse<SetEmailResponse>( final setEmailResponse = response.parse<SetEmailResponse>(
setEmailInvocation.methodCallId, setEmailInvocation.methodCallId,
@@ -11,6 +11,8 @@ import 'package:dio/dio.dart';
import 'package:email_recovery/email_recovery/email_recovery_action.dart'; import 'package:email_recovery/email_recovery/email_recovery_action.dart';
import 'package:email_recovery/email_recovery/email_recovery_action_id.dart'; import 'package:email_recovery/email_recovery/email_recovery_action_id.dart';
import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/error/set_error.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:jmap_dart_client/jmap/core/state.dart' as jmap;
@@ -83,7 +85,10 @@ class EmailRepositoryImpl extends EmailRepository {
} }
@override @override
Future<List<EmailId>> markAsRead( Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> markAsRead(
Session session, Session session,
AccountId accountId, AccountId accountId,
List<EmailId> emailIds, List<EmailId> emailIds,
@@ -124,12 +129,26 @@ class EmailRepositoryImpl extends EmailRepository {
} }
@override @override
Future<List<EmailId>> moveToMailbox(Session session, AccountId accountId, MoveToMailboxRequest moveRequest) { Future<({
return emailDataSource[DataSourceType.network]!.moveToMailbox(session, accountId, moveRequest); List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> moveToMailbox(
Session session,
AccountId accountId,
MoveToMailboxRequest moveRequest,
) {
return emailDataSource[DataSourceType.network]!.moveToMailbox(
session,
accountId,
moveRequest,
);
} }
@override @override
Future<List<EmailId>> markAsStar( Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> markAsStar(
Session session, Session session,
AccountId accountId, AccountId accountId,
List<EmailId> emailIds, List<EmailId> emailIds,
@@ -228,8 +247,19 @@ class EmailRepositoryImpl extends EmailRepository {
} }
@override @override
Future<List<EmailId>> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List<EmailId> emailIds) { Future<({
return emailDataSource[DataSourceType.network]!.deleteMultipleEmailsPermanently(session, accountId, emailIds); List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> deleteMultipleEmailsPermanently(
Session session,
AccountId accountId,
List<EmailId> emailIds,
) {
return emailDataSource[DataSourceType.network]!.deleteMultipleEmailsPermanently(
session,
accountId,
emailIds,
);
} }
@override @override
@@ -10,6 +10,8 @@ import 'package:dio/dio.dart';
import 'package:email_recovery/email_recovery/email_recovery_action.dart'; import 'package:email_recovery/email_recovery/email_recovery_action.dart';
import 'package:email_recovery/email_recovery/email_recovery_action_id.dart'; import 'package:email_recovery/email_recovery/email_recovery_action_id.dart';
import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/error/set_error.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:jmap_dart_client/jmap/core/state.dart' as jmap;
@@ -45,7 +47,10 @@ abstract class EmailRepository {
} }
); );
Future<List<EmailId>> markAsRead( Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> markAsRead(
Session session, Session session,
AccountId accountId, AccountId accountId,
List<EmailId> emailIds, List<EmailId> emailIds,
@@ -77,9 +82,19 @@ abstract class EmailRepository {
{CancelToken? cancelToken} {CancelToken? cancelToken}
); );
Future<List<EmailId>> moveToMailbox(Session session, AccountId accountId, MoveToMailboxRequest moveRequest); Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> moveToMailbox(
Session session,
AccountId accountId,
MoveToMailboxRequest moveRequest,
);
Future<List<EmailId>> markAsStar( Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> markAsStar(
Session session, Session session,
AccountId accountId, AccountId accountId,
List<EmailId> emailIds, List<EmailId> emailIds,
@@ -114,7 +129,14 @@ abstract class EmailRepository {
{CancelToken? cancelToken} {CancelToken? cancelToken}
); );
Future<List<EmailId>> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List<EmailId> emailIds); Future<({
List<EmailId> emailIdsSuccess,
Map<Id, SetError> mapErrors,
})> deleteMultipleEmailsPermanently(
Session session,
AccountId accountId,
List<EmailId> emailIds,
);
Future<bool> deleteEmailPermanently( Future<bool> deleteEmailPermanently(
Session session, Session session,
@@ -16,10 +16,10 @@ class DeleteMultipleEmailsPermanentlyInteractor {
try { try {
yield Right<Failure, Success>(LoadingDeleteMultipleEmailsPermanentlyAll()); yield Right<Failure, Success>(LoadingDeleteMultipleEmailsPermanentlyAll());
final listResult = await _emailRepository.deleteMultipleEmailsPermanently(session, accountId, emailIds); final listResult = await _emailRepository.deleteMultipleEmailsPermanently(session, accountId, emailIds);
if (listResult.length == emailIds.length) { if (listResult.emailIdsSuccess.length == emailIds.length) {
yield Right<Failure, Success>(DeleteMultipleEmailsPermanentlyAllSuccess(listResult)); yield Right<Failure, Success>(DeleteMultipleEmailsPermanentlyAllSuccess(listResult.emailIdsSuccess));
} else if (listResult.isNotEmpty) { } else if (listResult.emailIdsSuccess.isNotEmpty) {
yield Right<Failure, Success>(DeleteMultipleEmailsPermanentlyHasSomeEmailFailure(listResult)); yield Right<Failure, Success>(DeleteMultipleEmailsPermanentlyHasSomeEmailFailure(listResult.emailIdsSuccess));
} else { } else {
yield Left<Failure, Success>(DeleteMultipleEmailsPermanentlyAllFailure()); yield Left<Failure, Success>(DeleteMultipleEmailsPermanentlyAllFailure());
} }
@@ -29,11 +29,15 @@ class MarkAsEmailReadInteractor {
readAction, readAction,
); );
yield Right(MarkAsEmailReadSuccess( if (result.emailIdsSuccess.isEmpty) {
result.first, yield Left(MarkAsEmailReadFailure(readAction));
readAction, } else {
markReadAction, yield Right(MarkAsEmailReadSuccess(
)); result.emailIdsSuccess.first,
readAction,
markReadAction,
));
}
} catch (e) { } catch (e) {
yield Left(MarkAsEmailReadFailure(readAction, exception: e)); yield Left(MarkAsEmailReadFailure(readAction, exception: e));
} }
@@ -16,9 +16,9 @@ class MoveToMailboxInteractor {
try { try {
yield Right(LoadingMoveToMailbox()); yield Right(LoadingMoveToMailbox());
final result = await _emailRepository.moveToMailbox(session, accountId, moveRequest); final result = await _emailRepository.moveToMailbox(session, accountId, moveRequest);
if (result.isNotEmpty) { if (result.emailIdsSuccess.isNotEmpty) {
yield Right(MoveToMailboxSuccess( yield Right(MoveToMailboxSuccess(
result.first, result.emailIdsSuccess.first,
moveRequest.currentMailboxes.keys.first, moveRequest.currentMailboxes.keys.first,
moveRequest.destinationMailboxId, moveRequest.destinationMailboxId,
moveRequest.moveAction, moveRequest.moveAction,
@@ -138,8 +138,8 @@ class MailboxIsolateWorker {
listEmailUnread.listEmailIds, listEmailUnread.listEmailIds,
ReadActions.markAsRead); ReadActions.markAsRead);
log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): MARK_READ: ${result.length}'); log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): MARK_READ: ${result.emailIdsSuccess.length}');
emailIdsCompleted.addAll(result); emailIdsCompleted.addAll(result.emailIdsSuccess);
sendPort.send(emailIdsCompleted); sendPort.send(emailIdsCompleted);
} }
} }
@@ -206,8 +206,8 @@ class MailboxIsolateWorker {
listEmailUnread.listEmailIds, listEmailUnread.listEmailIds,
ReadActions.markAsRead, ReadActions.markAsRead,
); );
log('MailboxIsolateWorker::_handleMarkAsMailboxReadActionOnWeb(): MARK_READ: ${result.length}'); log('MailboxIsolateWorker::_handleMarkAsMailboxReadActionOnWeb(): MARK_READ: ${result.emailIdsSuccess.length}');
emailIdsCompleted.addAll(result); emailIdsCompleted.addAll(result.emailIdsSuccess);
onProgressController.add(Right(UpdatingMarkAsMailboxReadState( onProgressController.add(Right(UpdatingMarkAsMailboxReadState(
mailboxId: mailboxId, mailboxId: mailboxId,
@@ -119,7 +119,7 @@ class ThreadIsolateWorker {
args.session, args.session,
args.accountId, args.accountId,
newEmailList.listEmailIds); newEmailList.listEmailIds);
emailListCompleted.addAll(listEmailIdDeleted); emailListCompleted.addAll(listEmailIdDeleted.emailIdsSuccess);
sendPort.send(emailListCompleted); sendPort.send(emailListCompleted);
} else { } else {
hasEmails = false; hasEmails = false;
@@ -173,7 +173,7 @@ class ThreadIsolateWorker {
session, session,
accountId, accountId,
newEmailList.listEmailIds); newEmailList.listEmailIds);
emailListCompleted.addAll(listEmailIdDeleted); emailListCompleted.addAll(listEmailIdDeleted.emailIdsSuccess);
onProgressController.add(Right<Failure, Success>(EmptyingFolderState( onProgressController.add(Right<Failure, Success>(EmptyingFolderState(
mailboxId, emailListCompleted.length, totalEmails mailboxId, emailListCompleted.length, totalEmails
@@ -29,16 +29,16 @@ class MarkAsMultipleEmailReadInteractor {
readAction, readAction,
); );
if (emailIds.length == result.length) { if (emailIds.length == result.emailIdsSuccess.length) {
yield Right(MarkAsMultipleEmailReadAllSuccess( yield Right(MarkAsMultipleEmailReadAllSuccess(
result.length, result.emailIdsSuccess.length,
readAction, readAction,
)); ));
} else if (result.isEmpty) { } else if (result.emailIdsSuccess.isEmpty) {
yield Left(MarkAsMultipleEmailReadAllFailure(readAction)); yield Left(MarkAsMultipleEmailReadAllFailure(readAction));
} else { } else {
yield Right(MarkAsMultipleEmailReadHasSomeEmailFailure( yield Right(MarkAsMultipleEmailReadHasSomeEmailFailure(
result.length, result.emailIdsSuccess.length,
readAction, readAction,
)); ));
} }
@@ -24,16 +24,16 @@ class MarkAsStarMultipleEmailInteractor {
final result = await _emailRepository.markAsStar(session, accountId, emailIds, markStarAction); final result = await _emailRepository.markAsStar(session, accountId, emailIds, markStarAction);
if (emailIds.length == result.length) { if (emailIds.length == result.emailIdsSuccess.length) {
yield Right(MarkAsStarMultipleEmailAllSuccess( yield Right(MarkAsStarMultipleEmailAllSuccess(
emailIds.length, emailIds.length,
markStarAction, markStarAction,
)); ));
} else if (result.isEmpty) { } else if (result.emailIdsSuccess.isEmpty) {
yield Left(MarkAsStarMultipleEmailAllFailure(markStarAction)); yield Left(MarkAsStarMultipleEmailAllFailure(markStarAction));
} else { } else {
yield Right(MarkAsStarMultipleEmailHasSomeEmailFailure( yield Right(MarkAsStarMultipleEmailHasSomeEmailFailure(
result.length, result.emailIdsSuccess.length,
markStarAction, markStarAction,
)); ));
} }
@@ -22,20 +22,20 @@ class MoveMultipleEmailToMailboxInteractor {
try { try {
yield Right(LoadingMoveMultipleEmailToMailboxAll()); yield Right(LoadingMoveMultipleEmailToMailboxAll());
final result = await _emailRepository.moveToMailbox(session, accountId, moveRequest); final result = await _emailRepository.moveToMailbox(session, accountId, moveRequest);
if (moveRequest.totalEmails == result.length) { if (moveRequest.totalEmails == result.emailIdsSuccess.length) {
yield Right(MoveMultipleEmailToMailboxAllSuccess( yield Right(MoveMultipleEmailToMailboxAllSuccess(
result, result.emailIdsSuccess,
moveRequest.currentMailboxes.keys.first, moveRequest.currentMailboxes.keys.first,
moveRequest.destinationMailboxId, moveRequest.destinationMailboxId,
moveRequest.moveAction, moveRequest.moveAction,
moveRequest.emailActionType, moveRequest.emailActionType,
destinationPath: moveRequest.destinationPath, destinationPath: moveRequest.destinationPath,
)); ));
} else if (result.isEmpty) { } else if (result.emailIdsSuccess.isEmpty) {
yield Left(MoveMultipleEmailToMailboxAllFailure(moveRequest.moveAction, moveRequest.emailActionType)); yield Left(MoveMultipleEmailToMailboxAllFailure(moveRequest.moveAction, moveRequest.emailActionType));
} else { } else {
yield Right(MoveMultipleEmailToMailboxHasSomeEmailFailure( yield Right(MoveMultipleEmailToMailboxHasSomeEmailFailure(
result, result.emailIdsSuccess,
moveRequest.currentMailboxes.keys.first, moveRequest.currentMailboxes.keys.first,
moveRequest.destinationMailboxId, moveRequest.destinationMailboxId,
moveRequest.moveAction, moveRequest.moveAction,
@@ -0,0 +1,6 @@
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
extension ListIdExtension on Iterable<Id> {
Iterable<EmailId> toEmailIds() => map((id) => EmailId(id));
}
+1
View File
@@ -60,6 +60,7 @@ export 'extensions/session_extension.dart';
export 'extensions/username_extension.dart'; export 'extensions/username_extension.dart';
export 'extensions/utc_date_extension.dart'; export 'extensions/utc_date_extension.dart';
export 'extensions/contact_support_capability_extension.dart'; export 'extensions/contact_support_capability_extension.dart';
export 'extensions/list_id_extension.dart';
// Identity // Identity
export 'identity/identity_request_dto.dart'; export 'identity/identity_request_dto.dart';
export 'mailbox/expand_mode.dart'; export 'mailbox/expand_mode.dart';
@@ -1,5 +1,4 @@
import 'dart:math'; import 'package:collection/collection.dart';
import 'package:core/data/network/dio_client.dart'; import 'package:core/data/network/dio_client.dart';
import 'package:core/data/network/download/download_manager.dart'; import 'package:core/data/network/download/download_manager.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@@ -60,7 +59,7 @@ void main() {
const defaultMaxObjectsInSet = 50; const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 50; const maxObjectsInSet = 50;
const totalEmails = 100; const totalEmails = 100;
final maxBatches = min(maxObjectsInSet, defaultMaxObjectsInSet); final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil(); final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet); final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
@@ -102,16 +101,16 @@ void main() {
] ]
}); });
final emails = List.generate( final emailIds = List.generate(
totalEmails, totalEmails,
(index) => Email(id: EmailId(Id('email_$index'))), (index) => EmailId(Id('email_$index')),
); );
// Act // Act
final result = await emailApi.markAsRead( final result = await emailApi.markAsRead(
aliceSession, aliceSession,
AccountFixtures.aliceAccountId, AccountFixtures.aliceAccountId,
emails, emailIds,
ReadActions.markAsRead, ReadActions.markAsRead,
); );
@@ -121,7 +120,8 @@ void main() {
data: anyNamed('data'), data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'), cancelToken: anyNamed('cancelToken'),
)).called(countIterations); )).called(countIterations);
expect(result.length, totalEmails); expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
}); });
test( test(
@@ -133,7 +133,7 @@ void main() {
const defaultMaxObjectsInSet = 50; const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 200; const maxObjectsInSet = 200;
const totalEmails = 100; const totalEmails = 100;
final maxBatches = min(maxObjectsInSet, defaultMaxObjectsInSet); final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil(); final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet); final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
@@ -175,16 +175,16 @@ void main() {
] ]
}); });
final emails = List.generate( final emailIds = List.generate(
totalEmails, totalEmails,
(index) => Email(id: EmailId(Id('email_$index'))), (index) => EmailId(Id('email_$index')),
); );
// Act // Act
final result = await emailApi.markAsRead( final result = await emailApi.markAsRead(
aliceSession, aliceSession,
AccountFixtures.aliceAccountId, AccountFixtures.aliceAccountId,
emails, emailIds,
ReadActions.markAsRead, ReadActions.markAsRead,
); );
@@ -194,7 +194,8 @@ void main() {
data: anyNamed('data'), data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'), cancelToken: anyNamed('cancelToken'),
)).called(countIterations); )).called(countIterations);
expect(result.length, totalEmails); expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
}); });
test( test(
@@ -206,7 +207,7 @@ void main() {
const defaultMaxObjectsInSet = 50; const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 20; const maxObjectsInSet = 20;
const totalEmails = 100; const totalEmails = 100;
final maxBatches = min(maxObjectsInSet, defaultMaxObjectsInSet); final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil(); final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet); final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
@@ -248,16 +249,16 @@ void main() {
] ]
}); });
final emails = List.generate( final emailIds = List.generate(
totalEmails, totalEmails,
(index) => Email(id: EmailId(Id('email_$index'))), (index) => EmailId(Id('email_$index')),
); );
// Act // Act
final result = await emailApi.markAsRead( final result = await emailApi.markAsRead(
aliceSession, aliceSession,
AccountFixtures.aliceAccountId, AccountFixtures.aliceAccountId,
emails, emailIds,
ReadActions.markAsRead, ReadActions.markAsRead,
); );
@@ -267,7 +268,83 @@ void main() {
data: anyNamed('data'), data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'), cancelToken: anyNamed('cancelToken'),
)).called(countIterations); )).called(countIterations);
expect(result.length, totalEmails); expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
});
test(
'SHOULD calls execute the correct number of times\n'
'WHEN totalEmails is less than defaultMaxObjectsInSet\n'
'WHEN maxObjectsInSet equal defaultMaxObjectsInSet\n'
'AND defaultMaxObjectsInSet is 50',
() async {
// Arrange
const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 50;
const totalEmails = 30;
final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
when(httpClient.post(
'',
data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'),
)).thenAnswer((_) async => {
"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
"methodResponses": [
[
"Email/set",
{
"accountId": AccountFixtures.aliceAccountId.asString,
"oldState": "4cb1e760-c11b-11ef-9cfb-ef2eae0e64b1",
"newState": "4cb1e760-c11b-11ef-9cfb-ef2eae0e64b1",
"updated": {
for (var item in List.generate(maxBatches, (index) => {"email_$index": null}))
item.keys.first: item.values.first
}
},
"c0"
],
[
"Email/get",
{
"accountId": AccountFixtures.aliceAccountId.asString,
"state": "b965db40-c11c-11ef-9cfb-ef2eae0e64b1",
"list": List.generate(maxBatches, (index) => {
"id": "email_$index",
"keywords": {
"\$seen": true
}
}),
"notFound": []
},
"c1"
]
]
});
final emailIds = List.generate(
totalEmails,
(index) => EmailId(Id('email_$index')),
);
// Act
final result = await emailApi.markAsRead(
aliceSession,
AccountFixtures.aliceAccountId,
emailIds,
ReadActions.markAsRead,
);
// Assert
verify(httpClient.post(
'',
data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'),
)).called(countIterations);
expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
}); });
}); });
@@ -281,7 +358,7 @@ void main() {
const defaultMaxObjectsInSet = 50; const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 50; const maxObjectsInSet = 50;
const totalEmails = 100; const totalEmails = 100;
final maxBatches = min(maxObjectsInSet, defaultMaxObjectsInSet); final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil(); final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet); final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
@@ -323,16 +400,16 @@ void main() {
] ]
}); });
final emails = List.generate( final emailIds = List.generate(
totalEmails, totalEmails,
(index) => Email(id: EmailId(Id('email_$index'))), (index) => EmailId(Id('email_$index')),
); );
// Act // Act
final result = await emailApi.markAsStar( final result = await emailApi.markAsStar(
aliceSession, aliceSession,
AccountFixtures.aliceAccountId, AccountFixtures.aliceAccountId,
emails, emailIds,
MarkStarAction.markStar, MarkStarAction.markStar,
); );
@@ -342,7 +419,8 @@ void main() {
data: anyNamed('data'), data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'), cancelToken: anyNamed('cancelToken'),
)).called(countIterations); )).called(countIterations);
expect(result.length, totalEmails); expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
}); });
test( test(
@@ -354,7 +432,7 @@ void main() {
const defaultMaxObjectsInSet = 50; const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 200; const maxObjectsInSet = 200;
const totalEmails = 100; const totalEmails = 100;
final maxBatches = min(maxObjectsInSet, defaultMaxObjectsInSet); final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil(); final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet); final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
@@ -396,16 +474,16 @@ void main() {
] ]
}); });
final emails = List.generate( final emailIds = List.generate(
totalEmails, totalEmails,
(index) => Email(id: EmailId(Id('email_$index'))), (index) => EmailId(Id('email_$index')),
); );
// Act // Act
final result = await emailApi.markAsStar( final result = await emailApi.markAsStar(
aliceSession, aliceSession,
AccountFixtures.aliceAccountId, AccountFixtures.aliceAccountId,
emails, emailIds,
MarkStarAction.markStar, MarkStarAction.markStar,
); );
@@ -415,7 +493,8 @@ void main() {
data: anyNamed('data'), data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'), cancelToken: anyNamed('cancelToken'),
)).called(countIterations); )).called(countIterations);
expect(result.length, totalEmails); expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
}); });
test( test(
@@ -427,7 +506,7 @@ void main() {
const defaultMaxObjectsInSet = 50; const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 20; const maxObjectsInSet = 20;
const totalEmails = 100; const totalEmails = 100;
final maxBatches = min(maxObjectsInSet, defaultMaxObjectsInSet); final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil(); final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet); final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
@@ -469,16 +548,16 @@ void main() {
] ]
}); });
final emails = List.generate( final emailIds = List.generate(
totalEmails, totalEmails,
(index) => Email(id: EmailId(Id('email_$index'))), (index) => EmailId(Id('email_$index')),
); );
// Act // Act
final result = await emailApi.markAsStar( final result = await emailApi.markAsStar(
aliceSession, aliceSession,
AccountFixtures.aliceAccountId, AccountFixtures.aliceAccountId,
emails, emailIds,
MarkStarAction.markStar, MarkStarAction.markStar,
); );
@@ -488,7 +567,306 @@ void main() {
data: anyNamed('data'), data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'), cancelToken: anyNamed('cancelToken'),
)).called(countIterations); )).called(countIterations);
expect(result.length, totalEmails); expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
});
test(
'SHOULD calls execute the correct number of times\n'
'WHEN totalEmails is less than defaultMaxObjectsInSet\n'
'WHEN maxObjectsInSet equal defaultMaxObjectsInSet\n'
'AND defaultMaxObjectsInSet is 50',
() async {
// Arrange
const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 50;
const totalEmails = 30;
final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
when(httpClient.post(
'',
data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'),
)).thenAnswer((_) async => {
"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
"methodResponses": [
[
"Email/set",
{
"accountId": AccountFixtures.aliceAccountId.asString,
"oldState": "4cb1e760-c11b-11ef-9cfb-ef2eae0e64b1",
"newState": "4cb1e760-c11b-11ef-9cfb-ef2eae0e64b1",
"updated": {
for (var item in List.generate(maxBatches, (index) => {"email_$index": null}))
item.keys.first: item.values.first
}
},
"c0"
],
[
"Email/get",
{
"accountId": AccountFixtures.aliceAccountId.asString,
"state": "b965db40-c11c-11ef-9cfb-ef2eae0e64b1",
"list": List.generate(maxBatches, (index) => {
"id": "email_$index",
"keywords": {
"\$flagged": true
}
}),
"notFound": []
},
"c1"
]
]
});
final emailIds = List.generate(
totalEmails,
(index) => EmailId(Id('email_$index')),
);
// Act
final result = await emailApi.markAsStar(
aliceSession,
AccountFixtures.aliceAccountId,
emailIds,
MarkStarAction.markStar,
);
// Assert
verify(httpClient.post(
'',
data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'),
)).called(countIterations);
expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
});
});
group('deleteMultipleEmailsPermanently::test', () {
test(
'SHOULD calls execute the correct number of times\n'
'WHEN maxObjectsInSet equal defaultMaxObjectsInSet\n'
'AND defaultMaxObjectsInSet is 50',
() async {
// Arrange
const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 50;
const totalEmails = 100;
final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
when(httpClient.post(
'',
data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'),
)).thenAnswer((_) async => {
"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
"methodResponses": [
[
"Email/set",
{
"accountId": AccountFixtures.aliceAccountId.asString,
"oldState": "4cb1e760-c11b-11ef-9cfb-ef2eae0e64b1",
"newState": "4cb1e760-c11b-11ef-9cfb-ef2eae0e64b1",
"destroyed": List.generate(maxBatches, (index) => "email_$index")
},
"c0"
]
]
});
final emailIds = List.generate(
totalEmails,
(index) => EmailId(Id('email_$index')),
);
// Act
final result = await emailApi.deleteMultipleEmailsPermanently(
aliceSession,
AccountFixtures.aliceAccountId,
emailIds,
);
// Assert
verify(httpClient.post(
'',
data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'),
)).called(countIterations);
expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
});
test(
'SHOULD calls execute the correct number of times\n'
'WHEN maxObjectsInSet is greater than defaultMaxObjectsInSet\n'
'AND defaultMaxObjectsInSet is 50',
() async {
// Arrange
const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 200;
const totalEmails = 100;
final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
when(httpClient.post(
'',
data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'),
)).thenAnswer((_) async => {
"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
"methodResponses": [
[
"Email/set",
{
"accountId": AccountFixtures.aliceAccountId.asString,
"oldState": "4cb1e760-c11b-11ef-9cfb-ef2eae0e64b1",
"newState": "4cb1e760-c11b-11ef-9cfb-ef2eae0e64b1",
"destroyed": List.generate(maxBatches, (index) => "email_$index")
},
"c0"
]
]
});
final emailIds = List.generate(
totalEmails,
(index) => EmailId(Id('email_$index')),
);
// Act
final result = await emailApi.deleteMultipleEmailsPermanently(
aliceSession,
AccountFixtures.aliceAccountId,
emailIds,
);
// Assert
verify(httpClient.post(
'',
data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'),
)).called(countIterations);
expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
});
test(
'SHOULD calls execute the correct number of times\n'
'WHEN maxObjectsInSet is less than defaultMaxObjectsInSet\n'
'AND defaultMaxObjectsInSet is 50',
() async {
// Arrange
const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 20;
const totalEmails = 100;
final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
when(httpClient.post(
'',
data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'),
)).thenAnswer((_) async => {
"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
"methodResponses": [
[
"Email/set",
{
"accountId": AccountFixtures.aliceAccountId.asString,
"oldState": "4cb1e760-c11b-11ef-9cfb-ef2eae0e64b1",
"newState": "4cb1e760-c11b-11ef-9cfb-ef2eae0e64b1",
"destroyed": List.generate(maxBatches, (index) => "email_$index")
},
"c0"
]
]
});
final emailIds = List.generate(
totalEmails,
(index) => EmailId(Id('email_$index')),
);
// Act
final result = await emailApi.deleteMultipleEmailsPermanently(
aliceSession,
AccountFixtures.aliceAccountId,
emailIds,
);
// Assert
verify(httpClient.post(
'',
data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'),
)).called(countIterations);
expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
});
test(
'SHOULD calls execute the correct number of times\n'
'WHEN totalEmails is less than defaultMaxObjectsInSet\n'
'WHEN maxObjectsInSet equal defaultMaxObjectsInSet\n'
'AND defaultMaxObjectsInSet is 50',
() async {
// Arrange
const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 50;
const totalEmails = 30;
final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
when(httpClient.post(
'',
data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'),
)).thenAnswer((_) async => {
"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
"methodResponses": [
[
"Email/set",
{
"accountId": AccountFixtures.aliceAccountId.asString,
"oldState": "4cb1e760-c11b-11ef-9cfb-ef2eae0e64b1",
"newState": "4cb1e760-c11b-11ef-9cfb-ef2eae0e64b1",
"destroyed": List.generate(maxBatches, (index) => "email_$index")
},
"c0"
]
]
});
final emailIds = List.generate(
totalEmails,
(index) => EmailId(Id('email_$index')),
);
// Act
final result = await emailApi.deleteMultipleEmailsPermanently(
aliceSession,
AccountFixtures.aliceAccountId,
emailIds,
);
// Assert
verify(httpClient.post(
'',
data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'),
)).called(countIterations);
expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
}); });
}); });
@@ -502,7 +880,7 @@ void main() {
const defaultMaxObjectsInSet = 50; const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 50; const maxObjectsInSet = 50;
const totalEmails = 100; const totalEmails = 100;
final maxBatches = min(maxObjectsInSet, defaultMaxObjectsInSet); final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil(); final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet); final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
@@ -554,7 +932,8 @@ void main() {
data: anyNamed('data'), data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'), cancelToken: anyNamed('cancelToken'),
)).called(countIterations); )).called(countIterations);
expect(result.length, totalEmails); expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
}); });
test( test(
@@ -566,7 +945,7 @@ void main() {
const defaultMaxObjectsInSet = 50; const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 200; const maxObjectsInSet = 200;
const totalEmails = 100; const totalEmails = 100;
final maxBatches = min(maxObjectsInSet, defaultMaxObjectsInSet); final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil(); final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet); final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
@@ -618,7 +997,8 @@ void main() {
data: anyNamed('data'), data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'), cancelToken: anyNamed('cancelToken'),
)).called(countIterations); )).called(countIterations);
expect(result.length, totalEmails); expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
}); });
test( test(
@@ -630,7 +1010,7 @@ void main() {
const defaultMaxObjectsInSet = 50; const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 20; const maxObjectsInSet = 20;
const totalEmails = 100; const totalEmails = 100;
final maxBatches = min(maxObjectsInSet, defaultMaxObjectsInSet); final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil(); final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet); final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
@@ -682,7 +1062,74 @@ void main() {
data: anyNamed('data'), data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'), cancelToken: anyNamed('cancelToken'),
)).called(countIterations); )).called(countIterations);
expect(result.length, totalEmails); expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
});
test(
'SHOULD calls execute the correct number of times\n'
'WHEN totalEmails is less than defaultMaxObjectsInSet\n'
'WHEN maxObjectsInSet equal defaultMaxObjectsInSet\n'
'AND defaultMaxObjectsInSet is 50',
() async {
// Arrange
const defaultMaxObjectsInSet = 50;
const maxObjectsInSet = 50;
const totalEmails = 30;
final maxBatches = [maxObjectsInSet, defaultMaxObjectsInSet, totalEmails].min;
final countIterations = (totalEmails / maxBatches).ceil();
final aliceSession = SessionFixtures.getAliceSessionWithMaxObjectsInSet(maxObjectsInSet);
when(httpClient.post(
'',
data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'),
)).thenAnswer((_) async => {
"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
"methodResponses": [
[
"Email/set",
{
"accountId": AccountFixtures.aliceAccountId.asString,
"oldState": "4cb1e760-c11b-11ef-9cfb-ef2eae0e64b1",
"newState": "4cb1e760-c11b-11ef-9cfb-ef2eae0e64b1",
"updated": {
for (var item in List.generate(maxBatches, (index) => {"email_$index": null}))
item.keys.first: item.values.first
}
},
"c0"
]
]
});
final emailIds = List.generate(
totalEmails,
(index) => EmailId(Id('email_$index')),
);
// Act
final result = await emailApi.moveToMailbox(
aliceSession,
AccountFixtures.aliceAccountId,
MoveToMailboxRequest(
{
MailboxId(Id('mailboxA')): emailIds,
},
MailboxId(Id('mailboxB')),
MoveAction.moving,
EmailActionType.moveToMailbox,
),
);
// Assert
verify(httpClient.post(
'',
data: anyNamed('data'),
cancelToken: anyNamed('cancelToken'),
)).called(countIterations);
expect(result.emailIdsSuccess.length, totalEmails);
expect(result.mapErrors.isEmpty, isTrue);
}); });
}); });
}); });