From 4bf4113c88f3dc6ebc9eacf2aef549f9fac5b295 Mon Sep 17 00:00:00 2001 From: DatDang Date: Thu, 2 Jan 2025 17:23:18 +0700 Subject: [PATCH] TF-3385 Update cache on set method --- .../email_hive_cache_datasource_impl.dart | 111 +++++++++++++++--- .../repository/email_repository_impl.dart | 103 +++++++++++++--- .../mailbox_cache_datasource_impl.dart | 53 +++++++-- .../repository/mailbox_repository_impl.dart | 41 +++++-- .../presentation/mailbox_bindings.dart | 2 + model/lib/extensions/mailbox_extension.dart | 34 ++++++ 6 files changed, 296 insertions(+), 48 deletions(-) diff --git a/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart b/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart index 9e96e0293..b2caa73b3 100644 --- a/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart @@ -18,11 +18,13 @@ 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/user_name.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:model/account/account_request.dart'; import 'package:model/download/download_task_id.dart'; import 'package:model/email/attachment.dart'; import 'package:model/email/mark_star_action.dart'; import 'package:model/email/read_actions.dart'; +import 'package:model/extensions/email_extension.dart'; import 'package:model/extensions/email_id_extensions.dart'; import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; @@ -79,8 +81,13 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { AccountId accountId, EmailId emailId, {CancelToken? cancelToken} - ) { - throw UnimplementedError(); + ) async { + await _emailCacheManager.update( + accountId, + session.username, + destroyed: [emailId], + ); + return true; } @override @@ -91,8 +98,16 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { Session session, AccountId accountId, List emailIds, - ) { - throw UnimplementedError(); + ) async { + await _emailCacheManager.update( + accountId, + session.username, + destroyed: emailIds, + ); + return ( + emailIdsSuccess: emailIds, + mapErrors: {} + ); } @override @@ -137,8 +152,24 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { AccountId accountId, List emailIds, ReadActions readActions, - ) { - throw UnimplementedError(); + ) async { + final storedEmails = await Future.wait(emailIds.map( + (emailId) => getStoredEmail(session, accountId, emailId), + )); + for (var email in storedEmails) { + if (readActions == ReadActions.markAsUnread) { + email.keywords?.remove(KeyWordIdentifier.emailSeen); + } else { + email.keywords?[KeyWordIdentifier.emailSeen] = true; + } + } + await Future.wait(storedEmails.map( + (email) => storeEmail(session, accountId, email), + )); + return ( + emailIdsSuccess: emailIds, + mapErrors: {} + ); } @override @@ -150,8 +181,24 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { AccountId accountId, List emailIds, MarkStarAction markStarAction, - ) { - throw UnimplementedError(); + ) async { + final storedEmails = await Future.wait(emailIds.map( + (emailId) => getStoredEmail(session, accountId, emailId), + )); + for (var email in storedEmails) { + if (markStarAction == MarkStarAction.unMarkStar) { + email.keywords?.remove(KeyWordIdentifier.emailFlagged); + } else { + email.keywords?[KeyWordIdentifier.emailFlagged] = true; + } + } + await Future.wait(storedEmails.map( + (email) => storeEmail(session, accountId, email), + )); + return ( + emailIdsSuccess: emailIds, + mapErrors: {} + ); } @override @@ -162,8 +209,29 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { Session session, AccountId accountId, MoveToMailboxRequest moveRequest, - ) { - throw UnimplementedError(); + ) async { + final emailIds = moveRequest.currentMailboxes.entries.fold( + {}, + (emailIds, entry) { + emailIds.addAll(entry.value); + return emailIds; + }, + ).toList(); + final storedEmails = await Future.wait(emailIds.map( + (emailId) => getStoredEmail(session, accountId, emailId), + )); + for (int i = 0; i < storedEmails.length; i++) { + storedEmails[i] = storedEmails[i].updatedEmail( + newMailboxIds: {moveRequest.destinationMailboxId: true}, + ); + } + await Future.wait(storedEmails.map( + (email) => storeEmail(session, accountId, email), + )); + return ( + emailIdsSuccess: emailIds, + mapErrors: {} + ); } @override @@ -172,8 +240,13 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { AccountId accountId, EmailId emailId, {CancelToken? cancelToken} - ) { - throw UnimplementedError(); + ) async { + await _emailCacheManager.update( + accountId, + session.username, + destroyed: [emailId], + ); + return true; } @override @@ -182,8 +255,9 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { AccountId accountId, Email email, {CancelToken? cancelToken} - ) { - throw UnimplementedError(); + ) async { + await _emailCacheManager.update(accountId, session.username, created: [email]); + return email; } @override @@ -232,8 +306,13 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { Email newEmail, EmailId oldEmailId, {CancelToken? cancelToken} - ) { - throw UnimplementedError(); + ) async { + await _emailCacheManager.update( + accountId, + session.username, + updated: [newEmail], + ); + return newEmail; } @override diff --git a/lib/features/email/data/repository/email_repository_impl.dart b/lib/features/email/data/repository/email_repository_impl.dart index c849e3b22..89ba6ae11 100644 --- a/lib/features/email/data/repository/email_repository_impl.dart +++ b/lib/features/email/data/repository/email_repository_impl.dart @@ -93,13 +93,22 @@ class EmailRepositoryImpl extends EmailRepository { AccountId accountId, List emailIds, ReadActions readActions, - ) { - return emailDataSource[DataSourceType.network]!.markAsRead( + ) async { + final result = await emailDataSource[DataSourceType.network]!.markAsRead( session, accountId, emailIds, readActions, ); + + await emailDataSource[DataSourceType.hiveCache]!.markAsRead( + session, + accountId, + result.emailIdsSuccess, + readActions, + ); + + return result; } @override @@ -136,12 +145,34 @@ class EmailRepositoryImpl extends EmailRepository { Session session, AccountId accountId, MoveToMailboxRequest moveRequest, - ) { - return emailDataSource[DataSourceType.network]!.moveToMailbox( + ) async { + final result = await emailDataSource[DataSourceType.network] + !.moveToMailbox( session, accountId, moveRequest, ); + final updatedCurrentMailboxes = moveRequest.currentMailboxes.map( + (key, value) => MapEntry( + key, + value.where(result.emailIdsSuccess.contains).toList(), + ), + ); + + await emailDataSource[DataSourceType.hiveCache] + !.moveToMailbox( + session, + accountId, + MoveToMailboxRequest( + updatedCurrentMailboxes, + moveRequest.destinationMailboxId, + moveRequest.moveAction, + moveRequest.emailActionType, + destinationPath: moveRequest.destinationPath, + ), + ); + + return result; } @override @@ -153,13 +184,20 @@ class EmailRepositoryImpl extends EmailRepository { AccountId accountId, List emailIds, MarkStarAction markStarAction - ) { - return emailDataSource[DataSourceType.network]!.markAsStar( + ) async { + final result = await emailDataSource[DataSourceType.network]!.markAsStar( session, accountId, emailIds, markStarAction, ); + await emailDataSource[DataSourceType.hiveCache]!.markAsStar( + session, + accountId, + result.emailIdsSuccess, + markStarAction + ); + return result; } @override @@ -185,13 +223,20 @@ class EmailRepositoryImpl extends EmailRepository { AccountId accountId, Email email, {CancelToken? cancelToken} - ) { - return emailDataSource[DataSourceType.network]!.saveEmailAsDrafts( + ) async { + final result = await emailDataSource[DataSourceType.network]!.saveEmailAsDrafts( session, accountId, email, cancelToken: cancelToken ); + await emailDataSource[DataSourceType.hiveCache]!.saveEmailAsDrafts( + session, + accountId, + result, + cancelToken: cancelToken + ); + return result; } @override @@ -200,13 +245,20 @@ class EmailRepositoryImpl extends EmailRepository { AccountId accountId, EmailId emailId, {CancelToken? cancelToken} - ) { - return emailDataSource[DataSourceType.network]!.removeEmailDrafts( + ) async { + final result = await emailDataSource[DataSourceType.network]!.removeEmailDrafts( session, accountId, emailId, cancelToken: cancelToken ); + await emailDataSource[DataSourceType.hiveCache]!.removeEmailDrafts( + session, + accountId, + emailId, + cancelToken: cancelToken + ); + return result; } @override @@ -216,14 +268,21 @@ class EmailRepositoryImpl extends EmailRepository { Email newEmail, EmailId oldEmailId, {CancelToken? cancelToken} - ) { - return emailDataSource[DataSourceType.network]!.updateEmailDrafts( + ) async { + final result = await emailDataSource[DataSourceType.network]!.updateEmailDrafts( session, accountId, newEmail, oldEmailId, cancelToken: cancelToken ); + await emailDataSource[DataSourceType.hiveCache]!.updateEmailDrafts( + session, + accountId, + result, + oldEmailId, + ); + return result; } @override @@ -254,12 +313,17 @@ class EmailRepositoryImpl extends EmailRepository { Session session, AccountId accountId, List emailIds, - ) { - return emailDataSource[DataSourceType.network]!.deleteMultipleEmailsPermanently( + ) async { + final result = await emailDataSource[DataSourceType.network] + !.deleteMultipleEmailsPermanently( session, accountId, emailIds, ); + await emailDataSource[DataSourceType.hiveCache] + !.deleteMultipleEmailsPermanently(session, accountId, result.emailIdsSuccess); + + return result; } @override @@ -268,13 +332,20 @@ class EmailRepositoryImpl extends EmailRepository { AccountId accountId, EmailId emailId, {CancelToken? cancelToken} - ) { - return emailDataSource[DataSourceType.network]!.deleteEmailPermanently( + ) async { + final result = await emailDataSource[DataSourceType.network]!.deleteEmailPermanently( session, accountId, emailId, cancelToken: cancelToken ); + await emailDataSource[DataSourceType.hiveCache]!.deleteEmailPermanently( + session, + accountId, + emailId, + ); + + return result; } @override diff --git a/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart b/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart index e0c604a39..3c5496f13 100644 --- a/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart +++ b/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart @@ -13,6 +13,8 @@ import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/extensions/list_mailbox_extension.dart'; +import 'package:model/extensions/mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource/mailbox_datasource.dart'; import 'package:tmail_ui_user/features/mailbox/data/local/mailbox_cache_manager.dart'; import 'package:tmail_ui_user/features/mailbox/data/model/mailbox_change_response.dart'; @@ -68,8 +70,26 @@ class MailboxCacheDataSourceImpl extends MailboxDataSource { } @override - Future renameMailbox(Session session, AccountId accountId, RenameMailboxRequest request) { - throw UnimplementedError(); + Future renameMailbox( + Session session, + AccountId accountId, + RenameMailboxRequest request, + ) async { + final cachedMailboxes = await getAllMailboxCache(accountId, session.username); + final updatedMailbox = cachedMailboxes.findMailbox(request.mailboxId); + if (updatedMailbox == null) return false; + + final updatedMailboxIndex = cachedMailboxes.indexOf(updatedMailbox); + if (updatedMailboxIndex == -1) return false; + + cachedMailboxes[updatedMailboxIndex] = updatedMailbox.copyWith(name: request.newName); + await update( + accountId, + session.username, + updated: cachedMailboxes, + ); + + return true; } @override @@ -79,12 +99,29 @@ class MailboxCacheDataSourceImpl extends MailboxDataSource { @override Future> markAsMailboxRead( - Session session, - AccountId accountId, - MailboxId mailboxId, - int totalEmailUnread, - StreamController> onProgressController) { - throw UnimplementedError(); + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmailUnread, + StreamController> onProgressController, + ) async { + final mailboxes = await getAllMailboxCache(accountId, session.username); + final updatedMailbox = mailboxes.findMailbox(mailboxId); + if (updatedMailbox == null) return []; + + final updatedMailboxIndex = mailboxes.indexOf(updatedMailbox); + if (updatedMailboxIndex == -1) return []; + + mailboxes[updatedMailboxIndex] = updatedMailbox.copyWith( + unreadEmails: UnreadEmails(UnsignedInt(totalEmailUnread)), + ); + await _mailboxCacheManager.update( + accountId, + session.username, + updated: mailboxes, + ); + + return []; } @override diff --git a/lib/features/mailbox/data/repository/mailbox_repository_impl.dart b/lib/features/mailbox/data/repository/mailbox_repository_impl.dart index bfc6fcc06..cf4a9c120 100644 --- a/lib/features/mailbox/data/repository/mailbox_repository_impl.dart +++ b/lib/features/mailbox/data/repository/mailbox_repository_impl.dart @@ -15,8 +15,10 @@ import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/email/read_actions.dart'; import 'package:model/extensions/list_mailbox_extension.dart'; import 'package:model/extensions/mailbox_extension.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource/mailbox_datasource.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource/state_datasource.dart'; import 'package:tmail_ui_user/features/mailbox/data/extensions/state_extension.dart'; @@ -36,10 +38,12 @@ class MailboxRepositoryImpl extends MailboxRepository { final Map mapDataSource; final StateDataSource stateDataSource; + final EmailDataSource? emailDataSource; MailboxRepositoryImpl( this.mapDataSource, this.stateDataSource, + [this.emailDataSource,] ); @override @@ -211,23 +215,44 @@ class MailboxRepositoryImpl extends MailboxRepository { } @override - Future renameMailbox(Session session, AccountId accountId, RenameMailboxRequest request) { - return mapDataSource[DataSourceType.network]!.renameMailbox(session, accountId, request); + Future renameMailbox(Session session, AccountId accountId, RenameMailboxRequest request) async { + final result = await mapDataSource[DataSourceType.network] + !.renameMailbox(session, accountId, request); + + await mapDataSource[DataSourceType.local] + !.renameMailbox(session, accountId, request); + + return result; } @override Future> markAsMailboxRead( - Session session, - AccountId accountId, - MailboxId mailboxId, - int totalEmailUnread, - StreamController> onProgressController) async { - return mapDataSource[DataSourceType.network]!.markAsMailboxRead( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmailUnread, + StreamController> onProgressController, + ) async { + final result = await mapDataSource[DataSourceType.network]!.markAsMailboxRead( session, accountId, mailboxId, totalEmailUnread, onProgressController); + await mapDataSource[DataSourceType.local]!.markAsMailboxRead( + session, + accountId, + mailboxId, + totalEmailUnread - result.length, + onProgressController, + ); + await emailDataSource?.markAsRead( + session, + accountId, + result, + ReadActions.markAsRead, + ); + return result; } @override diff --git a/lib/features/mailbox/presentation/mailbox_bindings.dart b/lib/features/mailbox/presentation/mailbox_bindings.dart index 245801892..9fd2fd2a1 100644 --- a/lib/features/mailbox/presentation/mailbox_bindings.dart +++ b/lib/features/mailbox/presentation/mailbox_bindings.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/base_bindings.dart'; import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/email_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart'; import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource/mailbox_datasource.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource/state_datasource.dart'; @@ -122,6 +123,7 @@ class MailboxBindings extends BaseBindings { DataSourceType.local: Get.find() }, Get.find(), + Get.find(), )); } } \ No newline at end of file diff --git a/model/lib/extensions/mailbox_extension.dart b/model/lib/extensions/mailbox_extension.dart index 44dcefa26..6219188be 100644 --- a/model/lib/extensions/mailbox_extension.dart +++ b/model/lib/extensions/mailbox_extension.dart @@ -1,5 +1,7 @@ import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox_rights.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/namespace.dart'; import 'package:model/model.dart'; extension MailboxExtension on Mailbox { @@ -68,4 +70,36 @@ extension MailboxExtension on Mailbox { namespace: namespace, ); } + + Mailbox copyWith({ + MailboxId? id, + MailboxName? name, + MailboxId? parentId, + Role? role, + SortOrder? sortOrder, + TotalEmails? totalEmails, + UnreadEmails? unreadEmails, + TotalThreads? totalThreads, + UnreadThreads? unreadThreads, + MailboxRights? myRights, + IsSubscribed? isSubscribed, + Namespace? namespace, + Map?>? rights, + }) { + return Mailbox( + id: id ?? this.id, + name: name ?? this.name, + parentId: parentId ?? this.parentId, + role: role ?? this.role, + sortOrder: sortOrder ?? this.sortOrder, + totalEmails: totalEmails ?? this.totalEmails, + unreadEmails: unreadEmails ?? this.unreadEmails, + totalThreads: totalThreads ?? this.totalThreads, + unreadThreads: unreadThreads ?? this.unreadThreads, + myRights: myRights ?? this.myRights, + isSubscribed: isSubscribed ?? this.isSubscribed, + namespace: namespace ?? this.namespace, + rights: rights ?? this.rights, + ); + } } \ No newline at end of file