From bfd5901bf4d15f1f45ab2b2de5214c02782bda37 Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 7 Oct 2021 15:02:07 +0700 Subject: [PATCH] TF-145 Add data layer for getAllEmail in Caching --- lib/features/caching/email_cache_client.dart | 14 ++ .../data/datasource/thread_datasource.dart | 19 ++- .../local_thread_datasource_impl.dart | 63 ++++++++ .../thread_datasource_impl.dart | 41 ++++- .../email_address_hive_cache_extension.dart | 8 + .../extensions/email_cache_extension.dart | 34 ++++ .../data/extensions/email_extension.dart | 4 +- .../list_email_cache_extension.dart | 11 ++ .../extensions/map_mailbox_id_extension.dart | 7 + .../data/local/email_cache_manager.dart | 53 ++++++ .../thread/data/model/email_cache.dart | 7 +- .../thread/data/model/email_cache.g.dart | 7 +- .../data/model/email_change_response.dart | 47 ++++++ .../thread/data/network/thread_api.dart | 75 ++++++++- .../repository/thread_repository_impl.dart | 152 ++++++++++++++++-- lib/main/bindings/local/local_bindings.dart | 4 + 16 files changed, 524 insertions(+), 22 deletions(-) create mode 100644 lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart create mode 100644 lib/features/thread/data/extensions/email_address_hive_cache_extension.dart create mode 100644 lib/features/thread/data/extensions/email_cache_extension.dart create mode 100644 lib/features/thread/data/extensions/list_email_cache_extension.dart create mode 100644 lib/features/thread/data/extensions/map_mailbox_id_extension.dart create mode 100644 lib/features/thread/data/local/email_cache_manager.dart create mode 100644 lib/features/thread/data/model/email_change_response.dart diff --git a/lib/features/caching/email_cache_client.dart b/lib/features/caching/email_cache_client.dart index ce06094c3..03c72664b 100644 --- a/lib/features/caching/email_cache_client.dart +++ b/lib/features/caching/email_cache_client.dart @@ -1,5 +1,6 @@ import 'package:hive/hive.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:tmail_ui_user/features/caching/config/hive_cache_client.dart'; import 'package:tmail_ui_user/features/thread/data/model/email_cache.dart'; @@ -118,4 +119,17 @@ class EmailCacheClient extends HiveCacheClient { throw error; }); } + + Future> getListEmailCacheByMailboxId(MailboxId mailboxId) { + return Future.sync(() async { + final boxEmail = await openBox(); + return boxEmail.values.where((emailCache) { + return emailCache.mailboxIds != null + && emailCache.mailboxIds!.containsKey(mailboxId.id.value) + && emailCache.mailboxIds![mailboxId.id.value] == true; + }).toList(); + }).catchError((error) { + throw error; + }); + } } \ No newline at end of file diff --git a/lib/features/thread/data/datasource/thread_datasource.dart b/lib/features/thread/data/datasource/thread_datasource.dart index e9eccd370..1d9ee8409 100644 --- a/lib/features/thread/data/datasource/thread_datasource.dart +++ b/lib/features/thread/data/datasource/thread_datasource.dart @@ -2,11 +2,15 @@ import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart'; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; abstract class ThreadDataSource { - Future> getAllEmail( + Future getAllEmail( AccountId accountId, { int? position, @@ -16,4 +20,17 @@ abstract class ThreadDataSource { Properties? properties } ); + + Future getChanges( + AccountId accountId, + State sinceState, + { + Properties? propertiesCreated, + Properties? propertiesUpdated + } + ); + + Future> getAllEmailCache({MailboxId? inMailboxId, Set? sort}); + + Future update({List? updated, List? created, List? destroyed}); } \ No newline at end of file diff --git a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart new file mode 100644 index 000000000..91a777f14 --- /dev/null +++ b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart @@ -0,0 +1,63 @@ +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; +import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; +import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart'; +import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/thread/data/datasource/thread_datasource.dart'; +import 'package:tmail_ui_user/features/thread/data/local/email_cache_manager.dart'; +import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; + +class LocalThreadDataSourceImpl extends ThreadDataSource { + + final EmailCacheManager _emailCacheManager; + + LocalThreadDataSourceImpl(this._emailCacheManager); + + @override + Future getAllEmail( + AccountId accountId, + { + int? position, + UnsignedInt? limit, + Set? sort, + Filter? filter, + Properties? properties + } + ) { + throw UnimplementedError(); + } + + @override + Future getChanges( + AccountId accountId, + State sinceState, + { + Properties? propertiesCreated, + Properties? propertiesUpdated + } + ) { + throw UnimplementedError(); + } + + @override + Future> getAllEmailCache({MailboxId? inMailboxId, Set? sort}) { + return Future.sync(() async { + return await _emailCacheManager.getAllEmail(inMailboxId: inMailboxId, sort: sort); + }).catchError((error) { + throw error; + }); + } + + @override + Future update({List? updated, List? created, List? destroyed}) { + return Future.sync(() async { + return await _emailCacheManager.update(updated: updated, created: created, destroyed: destroyed); + }).catchError((error) { + throw error; + }); + } +} \ No newline at end of file diff --git a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart index dc8dfcb31..ab73b8f20 100644 --- a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart @@ -2,9 +2,13 @@ import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart'; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:tmail_ui_user/features/thread/data/datasource/thread_datasource.dart'; +import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; class ThreadDataSourceImpl extends ThreadDataSource { @@ -14,14 +18,17 @@ class ThreadDataSourceImpl extends ThreadDataSource { ThreadDataSourceImpl(this.threadAPI); @override - Future> getAllEmail( + Future getAllEmail( AccountId accountId, { int? position, UnsignedInt? limit, Set? sort, Filter? filter, - Properties? properties + Properties? properties, + Properties? propertiesCreated, + Properties? propertiesUpdated, + MailboxId? inMailboxId } ) { return Future.sync(() async { @@ -36,4 +43,34 @@ class ThreadDataSourceImpl extends ThreadDataSource { throw error; }); } + + @override + Future getChanges( + AccountId accountId, + State sinceState, + { + Properties? propertiesCreated, + Properties? propertiesUpdated + } + ) { + return Future.sync(() async { + return await threadAPI.getChanges( + accountId, + sinceState, + propertiesCreated: propertiesCreated, + propertiesUpdated: propertiesUpdated); + }).catchError((error) { + throw error; + }); + } + + @override + Future> getAllEmailCache({MailboxId? inMailboxId, Set? sort}) { + throw UnimplementedError(); + } + + @override + Future update({List? updated, List? created, List? destroyed}) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/thread/data/extensions/email_address_hive_cache_extension.dart b/lib/features/thread/data/extensions/email_address_hive_cache_extension.dart new file mode 100644 index 000000000..87128b56f --- /dev/null +++ b/lib/features/thread/data/extensions/email_address_hive_cache_extension.dart @@ -0,0 +1,8 @@ + +import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; +import 'package:tmail_ui_user/features/thread/data/model/email_address_hive_cache.dart'; + +extension EmailAddressHiveCacheExtension on EmailAddressHiveCache { + + EmailAddress toEmailAddress() => EmailAddress(name, email); +} \ No newline at end of file diff --git a/lib/features/thread/data/extensions/email_cache_extension.dart b/lib/features/thread/data/extensions/email_cache_extension.dart new file mode 100644 index 000000000..397dd2306 --- /dev/null +++ b/lib/features/thread/data/extensions/email_cache_extension.dart @@ -0,0 +1,34 @@ + +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; +import 'package:jmap_dart_client/jmap/core/utc_date.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/thread/data/model/email_cache.dart'; +import 'package:tmail_ui_user/features/thread/data/extensions/email_address_hive_cache_extension.dart'; + +extension EmailCacheExtension on EmailCache { + Email toEmail() { + return Email( + EmailId(Id(id)), + keywords: keywords != null + ? Map.fromIterables(keywords!.keys.map((value) => KeyWordIdentifier(value)), keywords!.values) + : null, + size: size != null ? UnsignedInt(size!) : null, + receivedAt: receivedAt != null ? UTCDate(receivedAt!) : null, + hasAttachment: hasAttachment, + preview: preview, + subject: subject, + sentAt: sentAt != null ? UTCDate(sentAt!) : null, + from: from?.map((emailAddressCache) => emailAddressCache.toEmailAddress()).toSet(), + to: to?.map((emailAddressCache) => emailAddressCache.toEmailAddress()).toSet(), + cc: cc?.map((emailAddressCache) => emailAddressCache.toEmailAddress()).toSet(), + bcc: bcc?.map((emailAddressCache) => emailAddressCache.toEmailAddress()).toSet(), + replyTo: replyTo?.map((emailAddressCache) => emailAddressCache.toEmailAddress()).toSet(), + mailboxIds: mailboxIds != null + ? Map.fromIterables(mailboxIds!.keys.map((value) => MailboxId(Id(value))), mailboxIds!.values) + : null, + ); + } +} \ No newline at end of file diff --git a/lib/features/thread/data/extensions/email_extension.dart b/lib/features/thread/data/extensions/email_extension.dart index 0ac555cfe..f5a295399 100644 --- a/lib/features/thread/data/extensions/email_extension.dart +++ b/lib/features/thread/data/extensions/email_extension.dart @@ -2,12 +2,13 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:tmail_ui_user/features/thread/data/model/email_cache.dart'; import 'package:tmail_ui_user/features/thread/data/extensions/map_keywords_extension.dart'; import 'package:tmail_ui_user/features/thread/data/extensions/email_address_extension.dart'; +import 'package:tmail_ui_user/features/thread/data/extensions/map_mailbox_id_extension.dart'; extension EmailExtension on Email { EmailCache toEmailCache() { return EmailCache( - id.toString(), + id.id.value, keywords: keywords?.toMapString(), size: size?.value.round(), receivedAt: receivedAt?.value, @@ -20,6 +21,7 @@ extension EmailExtension on Email { cc: cc?.map((emailAddress) => emailAddress.toEmailAddressHiveCache()).toList(), bcc: bcc?.map((emailAddress) => emailAddress.toEmailAddressHiveCache()).toList(), replyTo: replyTo?.map((emailAddress) => emailAddress.toEmailAddressHiveCache()).toList(), + mailboxIds: mailboxIds?.toMapString(), ); } } \ No newline at end of file diff --git a/lib/features/thread/data/extensions/list_email_cache_extension.dart b/lib/features/thread/data/extensions/list_email_cache_extension.dart new file mode 100644 index 000000000..03f19a27d --- /dev/null +++ b/lib/features/thread/data/extensions/list_email_cache_extension.dart @@ -0,0 +1,11 @@ + +import 'package:tmail_ui_user/features/thread/data/model/email_cache.dart'; + +extension ListEmailCacheExtension on List { + Map toMap() { + return Map.fromIterable( + this, + key: (emailCache) => emailCache.id, + value: (emailCache) => emailCache); + } +} \ No newline at end of file diff --git a/lib/features/thread/data/extensions/map_mailbox_id_extension.dart b/lib/features/thread/data/extensions/map_mailbox_id_extension.dart new file mode 100644 index 000000000..9e2e168f7 --- /dev/null +++ b/lib/features/thread/data/extensions/map_mailbox_id_extension.dart @@ -0,0 +1,7 @@ + +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; + +extension MapMailboxIdExtension on Map { + + Map toMapString() => Map.fromIterables(keys.map((mailboxId) => mailboxId.id.value), values); +} \ No newline at end of file diff --git a/lib/features/thread/data/local/email_cache_manager.dart b/lib/features/thread/data/local/email_cache_manager.dart new file mode 100644 index 000000000..804c1a53e --- /dev/null +++ b/lib/features/thread/data/local/email_cache_manager.dart @@ -0,0 +1,53 @@ + +import 'package:model/model.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/caching/email_cache_client.dart'; +import 'package:tmail_ui_user/features/thread/data/extensions/email_cache_extension.dart'; +import 'package:tmail_ui_user/features/thread/data/extensions/email_extension.dart'; +import 'package:tmail_ui_user/features/thread/data/extensions/list_email_cache_extension.dart'; +import 'package:tmail_ui_user/features/thread/data/model/email_cache.dart'; +import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; + +class EmailCacheManager { + + final EmailCacheClient _emailCacheClient; + + EmailCacheManager(this._emailCacheClient); + + Future> getAllEmail({MailboxId? inMailboxId, Set? sort}) async { + final emailCacheList = inMailboxId != null + ? await _emailCacheClient.getListEmailCacheByMailboxId(inMailboxId) + : await _emailCacheClient.getAll(); + final emailList = emailCacheList.map((emailCache) => emailCache.toEmail()).toList(); + if (sort != null) { + sort.forEach((comparator) { + emailList.sortBy(comparator); + }); + } + return emailList; + } + + Future update({List? updated, List? created, List? destroyed}) async { + final emailCacheExist = await _emailCacheClient.isExistTable(); + if (emailCacheExist) { + final updatedCacheEmails = updated + ?.map((email) => email.toEmailCache()).toList() ?? []; + final createdCacheEmails = created + ?.map((email) => email.toEmailCache()).toList() ?? []; + final destroyedCacheEmails = destroyed + ?.map((emailId) => emailId.id.value).toList() ?? []; + await Future.wait([ + _emailCacheClient.updateMultipleItem(updatedCacheEmails.toMap()), + _emailCacheClient.insertMultipleItem(createdCacheEmails.toMap()), + _emailCacheClient.deleteMultipleItem(destroyedCacheEmails) + ]); + } else { + final createdCacheEmails = created + ?.map((email) => email.toEmailCache()).toList() ?? []; + await _emailCacheClient.insertMultipleItem(createdCacheEmails.toMap()); + } + } + + +} \ No newline at end of file diff --git a/lib/features/thread/data/model/email_cache.dart b/lib/features/thread/data/model/email_cache.dart index ed947953f..a407d5125 100644 --- a/lib/features/thread/data/model/email_cache.dart +++ b/lib/features/thread/data/model/email_cache.dart @@ -48,6 +48,9 @@ class EmailCache extends HiveObject with EquatableMixin { @HiveField(12) final List? replyTo; + @HiveField(13) + Map? mailboxIds; + EmailCache( this.id, { @@ -63,6 +66,7 @@ class EmailCache extends HiveObject with EquatableMixin { this.cc, this.bcc, this.replyTo, + this.mailboxIds, } ); @@ -80,6 +84,7 @@ class EmailCache extends HiveObject with EquatableMixin { sentAt, replyTo, preview, - hasAttachment + hasAttachment, + mailboxIds, ]; } \ No newline at end of file diff --git a/lib/features/thread/data/model/email_cache.g.dart b/lib/features/thread/data/model/email_cache.g.dart index 9a214147b..d4b8523a8 100644 --- a/lib/features/thread/data/model/email_cache.g.dart +++ b/lib/features/thread/data/model/email_cache.g.dart @@ -30,13 +30,14 @@ class EmailCacheAdapter extends TypeAdapter { cc: (fields[10] as List?)?.cast(), bcc: (fields[11] as List?)?.cast(), replyTo: (fields[12] as List?)?.cast(), + mailboxIds: (fields[13] as Map?)?.cast(), ); } @override void write(BinaryWriter writer, EmailCache obj) { writer - ..writeByte(13) + ..writeByte(14) ..writeByte(0) ..write(obj.id) ..writeByte(1) @@ -62,7 +63,9 @@ class EmailCacheAdapter extends TypeAdapter { ..writeByte(11) ..write(obj.bcc) ..writeByte(12) - ..write(obj.replyTo); + ..write(obj.replyTo) + ..writeByte(13) + ..write(obj.mailboxIds); } @override diff --git a/lib/features/thread/data/model/email_change_response.dart b/lib/features/thread/data/model/email_change_response.dart new file mode 100644 index 000000000..8e6889b3f --- /dev/null +++ b/lib/features/thread/data/model/email_change_response.dart @@ -0,0 +1,47 @@ + +import 'package:model/model.dart'; +import 'package:equatable/equatable.dart'; +import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; + +class EmailChangeResponse with EquatableMixin { + final List? updated; + final List? created; + final List? destroyed; + final State? newStateEmail; + final State? newStateChanges; + final bool hasMoreChanges; + final Properties? updatedProperties; + + EmailChangeResponse({ + this.updated, + this.created, + this.destroyed, + this.newStateEmail, + this.newStateChanges, + this.hasMoreChanges = false, + this.updatedProperties, + }); + + EmailChangeResponse union(EmailChangeResponse other) => EmailChangeResponse( + updated: updated.union(other.updated), + created: created.union(other.created), + destroyed: destroyed.union(other.destroyed), + newStateEmail: other.newStateEmail, + newStateChanges: other.newStateChanges, + hasMoreChanges: other.hasMoreChanges, + updatedProperties: other.updatedProperties, + ); + + @override + List get props => [ + updated, + created, + destroyed, + newStateEmail, + newStateChanges, + hasMoreChanges, + updatedProperties + ]; +} \ No newline at end of file diff --git a/lib/features/thread/data/network/thread_api.dart b/lib/features/thread/data/network/thread_api.dart index 61f23a0d5..acfe84607 100644 --- a/lib/features/thread/data/network/thread_api.dart +++ b/lib/features/thread/data/network/thread_api.dart @@ -6,12 +6,17 @@ import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/request/reference_path.dart'; import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart'; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/jmap_request.dart'; +import 'package:jmap_dart_client/jmap/mail/email/changes/changes_email_method.dart'; +import 'package:jmap_dart_client/jmap/mail/email/changes/changes_email_response.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/get/get_email_method.dart'; import 'package:jmap_dart_client/jmap/mail/email/get/get_email_response.dart'; import 'package:jmap_dart_client/jmap/mail/email/query/query_email_method.dart'; +import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; class ThreadAPI { @@ -19,7 +24,7 @@ class ThreadAPI { ThreadAPI(this.httpClient); - Future> getAllEmail( + Future getAllEmail( AccountId accountId, { int? position, @@ -69,6 +74,72 @@ class ThreadAPI { }); } - return resultList == null ? [] : resultList.list; + return EmailResponse(emailList: resultList?.list, state: resultList?.state); + } + + Future getChanges( + AccountId accountId, + State sinceState, + { + Properties? propertiesCreated, + Properties? propertiesUpdated + } + ) async { + final processingInvocation = ProcessingInvocation(); + + final jmapRequestBuilder = JmapRequestBuilder(httpClient, processingInvocation); + + final changesEmailMethod = ChangesEmailMethod(accountId, sinceState, maxChanges: UnsignedInt(128)); + + final changesEmailInvocation = jmapRequestBuilder.invocation(changesEmailMethod); + + final getMailboxUpdated = GetEmailMethod(accountId) + ..addReferenceIds(processingInvocation.createResultReference( + changesEmailInvocation.methodCallId, + ReferencePath.updatedPath)); + + if (propertiesUpdated != null) { + getMailboxUpdated..addProperties(propertiesUpdated); + } + + final getEmailCreated = GetEmailMethod(accountId) + ..addReferenceIds(processingInvocation.createResultReference( + changesEmailInvocation.methodCallId, + ReferencePath.createdPath)); + + if (propertiesCreated != null) { + getEmailCreated..addProperties(propertiesCreated); + } + + final getEmailUpdatedInvocation = jmapRequestBuilder.invocation(getMailboxUpdated); + final getEmailCreatedInvocation = jmapRequestBuilder.invocation(getEmailCreated); + + final result = await (jmapRequestBuilder + ..usings(getEmailCreated.requiredCapabilities)) + .build() + .execute(); + + final resultChanges = result.parse( + changesEmailInvocation.methodCallId, + ChangesEmailResponse.deserialize); + + final resultUpdated = result.parse( + getEmailUpdatedInvocation.methodCallId, + GetEmailResponse.deserialize); + + final resultCreated = result.parse( + getEmailCreatedInvocation.methodCallId, + GetEmailResponse.deserialize); + + final listMailboxIdDestroyed = resultChanges?.destroyed.map((id) => EmailId(id)).toList() ?? []; + + return EmailChangeResponse( + updated: resultUpdated?.list, + created: resultCreated?.list, + destroyed: listMailboxIdDestroyed, + newStateEmail: resultUpdated?.state, + newStateChanges: resultChanges?.newState, + hasMoreChanges: resultChanges?.hasMoreChanges ?? false, + updatedProperties: propertiesUpdated); } } \ No newline at end of file diff --git a/lib/features/thread/data/repository/thread_repository_impl.dart b/lib/features/thread/data/repository/thread_repository_impl.dart index 454774225..db15729b5 100644 --- a/lib/features/thread/data/repository/thread_repository_impl.dart +++ b/lib/features/thread/data/repository/thread_repository_impl.dart @@ -1,37 +1,163 @@ +import 'package:core/core.dart'; +import 'package:model/model.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart'; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/mailbox/data/datasource/state_datasource.dart'; +import 'package:tmail_ui_user/features/mailbox/data/model/state_type.dart'; import 'package:tmail_ui_user/features/thread/data/datasource/thread_datasource.dart'; +import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/mailbox/data/extensions/state_extension.dart'; class ThreadRepositoryImpl extends ThreadRepository { - final ThreadDataSource threadDataSource; + final Map mapDataSource; + final StateDataSource stateDataSource; - ThreadRepositoryImpl(this.threadDataSource); + ThreadRepositoryImpl(this.mapDataSource, this.stateDataSource); @override - Future> getAllEmail( + Stream getAllEmail( AccountId accountId, { int? position, UnsignedInt? limit, Set? sort, Filter? filter, - Properties? properties + Properties? propertiesCreated, + Properties? propertiesUpdated, + MailboxId? inMailboxId } - ) { - return threadDataSource.getAllEmail( - accountId, - position: position, - limit: limit, - sort: sort, - filter: filter, - properties: properties - ); + ) async* { + final localEmailResponse = await Future.wait([ + mapDataSource[DataSourceType.local]!.getAllEmailCache(inMailboxId: inMailboxId, sort: sort), + stateDataSource.getState(StateType.email) + ]).then((List response) { + return EmailResponse(emailList: response.first, state: response.last); + }); + + EmailResponse? networkEmailResponse; + + if (!localEmailResponse.hasEmails()) { + networkEmailResponse = await mapDataSource[DataSourceType.network]!.getAllEmail( + accountId, + position: position, + limit: limit, + sort: sort, + filter: filter, + properties: propertiesCreated); + + yield networkEmailResponse; + } else { + yield localEmailResponse; + } + + if (localEmailResponse.hasState()) { + EmailChangeResponse? emailChangeResponse; + bool hasMoreChanges = true; + State? sinceState = localEmailResponse.state!; + + while(hasMoreChanges && sinceState != null) { + final changesResponse = await mapDataSource[DataSourceType.network]!.getChanges( + accountId, + sinceState, + propertiesCreated: propertiesCreated, + propertiesUpdated: propertiesUpdated); + + hasMoreChanges = changesResponse.hasMoreChanges; + sinceState = changesResponse.newStateChanges; + + if (emailChangeResponse != null) { + emailChangeResponse.union(changesResponse); + } else { + emailChangeResponse = changesResponse; + } + } + + final totalEmails = networkEmailResponse != null + ? networkEmailResponse.emailList + : localEmailResponse.emailList; + + final newEmailUpdated = await _combineEmailCache( + emailUpdated: emailChangeResponse?.updated, + updatedProperties: emailChangeResponse?.updatedProperties, + emailCacheList: totalEmails); + + final newEmailCreated = networkEmailResponse != null + ? networkEmailResponse.emailList + : emailChangeResponse?.created; + + await _updateEmailCache( + newCreated: newEmailCreated, + newUpdated: newEmailUpdated, + newDestroyed: emailChangeResponse?.destroyed); + + if (emailChangeResponse != null && emailChangeResponse.newStateEmail != null) { + await _updateState(emailChangeResponse.newStateEmail!); + } + } else { + if (networkEmailResponse != null) { + await _updateEmailCache(newCreated: networkEmailResponse.emailList); + if (networkEmailResponse.state != null) { + await _updateState(networkEmailResponse.state!); + } + } + } + + final newEmailResponse = await Future.wait([ + mapDataSource[DataSourceType.local]!.getAllEmailCache(inMailboxId: inMailboxId, sort: sort), + stateDataSource.getState(StateType.email) + ]).then((List response) { + return EmailResponse(emailList: response.first, state: response.last); + }); + + yield newEmailResponse; + } + + Future?> _combineEmailCache({ + List? emailUpdated, + Properties? updatedProperties, + List? emailCacheList + }) async { + if (emailUpdated != null && emailUpdated.isNotEmpty) { + final newEmailUpdated = emailUpdated.map((email) { + if (updatedProperties == null) { + return email; + } else { + final emailOld = emailCacheList?.findEmail(email.id); + if (emailOld != null) { + return emailOld.combineEmail(email, updatedProperties); + } else { + return email; + } + } + }).toList(); + + return newEmailUpdated; + } + return emailUpdated; + } + + Future _updateEmailCache({ + List? newUpdated, + List? newCreated, + List? newDestroyed + }) async { + await mapDataSource[DataSourceType.local]!.update( + updated: newUpdated, + created: newCreated, + destroyed: newDestroyed); + } + + Future _updateState(State newState) async { + await stateDataSource.saveState(newState.toStateCache(StateType.email)); } } \ No newline at end of file diff --git a/lib/main/bindings/local/local_bindings.dart b/lib/main/bindings/local/local_bindings.dart index 36dd94cfc..72ede4d55 100644 --- a/lib/main/bindings/local/local_bindings.dart +++ b/lib/main/bindings/local/local_bindings.dart @@ -2,9 +2,11 @@ import 'package:core/core.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/caching/caching_manager.dart'; +import 'package:tmail_ui_user/features/caching/email_cache_client.dart'; import 'package:tmail_ui_user/features/caching/mailbox_cache_client.dart'; import 'package:tmail_ui_user/features/caching/state_cache_client.dart'; import 'package:tmail_ui_user/features/mailbox/data/local/mailbox_cache_manager.dart'; +import 'package:tmail_ui_user/features/thread/data/local/email_cache_manager.dart'; class LocalBindings extends Bindings { @@ -25,5 +27,7 @@ class LocalBindings extends Bindings { Get.put(CachingManager( Get.find(), Get.find())); + Get.put(EmailCacheClient()); + Get.put(EmailCacheManager(Get.find())); } } \ No newline at end of file