diff --git a/lib/features/caching/config/hive_cache_client.dart b/lib/features/caching/config/hive_cache_client.dart index 89ca70241..2a455f5c1 100644 --- a/lib/features/caching/config/hive_cache_client.dart +++ b/lib/features/caching/config/hive_cache_client.dart @@ -2,6 +2,7 @@ import 'dart:typed_data'; import 'package:core/presentation/extensions/map_extensions.dart'; +import 'package:core/utils/app_logger.dart'; import 'package:hive/hive.dart'; import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart'; import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart'; @@ -80,13 +81,15 @@ abstract class HiveCacheClient { }); } - Future> getListByTupleKey(String accountId, String userName) { + Future> getListByNestedKey(String nestedKey) { return Future.sync(() async { final boxItem = encryption ? await openBoxEncryption() : await openBox(); - return boxItem.toMap() - .where((key, value) => _matchedKey(key, accountId, userName)) + final listItem = boxItem.toMap() + .where((key, value) => _matchedNestedKey(key, nestedKey)) .values .toList(); + log('HiveCacheClient::getListByNestedKey:listItem: ${listItem.length}'); + return listItem; }).catchError((error) { throw error; }); @@ -104,10 +107,10 @@ abstract class HiveCacheClient { }); } - bool _matchedKey(String key, String accountId, String userName) { - final keyDecoded = CacheUtils.decodeKey(key); - final tupleKey = TupleKey.fromString(keyDecoded); - return tupleKey.parts.length >= 3 && tupleKey.parts[1] == accountId && tupleKey.parts[2] == userName; + bool _matchedNestedKey(String key, String nestedKey) { + final decodedKey = CacheUtils.decodeKey(key); + final decodedNestedKey = CacheUtils.decodeKey(nestedKey); + return decodedKey.contains(decodedNestedKey); } Future updateItem(String key, T newObject) { @@ -182,6 +185,17 @@ abstract class HiveCacheClient { }); } + Future clearAllDataContainKey(String nestedKey) { + return Future.sync(() async { + final boxItem = encryption ? await openBoxEncryption() : await openBox(); + final mapItemNotContainNestedKey = boxItem.toMap().where((key, value) => !_matchedNestedKey(key, nestedKey)); + log('HiveCacheClient::clearAllDataContainKey:mapItemNotContainNestedKey: ${mapItemNotContainNestedKey.length}'); + return boxItem.putAll(mapItemNotContainNestedKey); + }).catchError((error) { + throw error; + }); + } + Future closeBox() async { if (Hive.isBoxOpen(tableName)) { await Hive.box(tableName).close(); diff --git a/lib/features/mailbox/data/datasource/mailbox_datasource.dart b/lib/features/mailbox/data/datasource/mailbox_datasource.dart index d18fe6f9a..35a2113e3 100644 --- a/lib/features/mailbox/data/datasource/mailbox_datasource.dart +++ b/lib/features/mailbox/data/datasource/mailbox_datasource.dart @@ -11,18 +11,18 @@ import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/state.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/get/get_mailbox_response.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:tmail_ui_user/features/mailbox/data/model/mailbox_change_response.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/get_mailbox_by_role_response.dart'; -import 'package:tmail_ui_user/features/mailbox/domain/model/mailbox_response.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/move_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/rename_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/subscribe_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/subscribe_multiple_mailbox_request.dart'; abstract class MailboxDataSource { - Future getAllMailbox(Session session, AccountId accountId, {Properties? properties}); + Future getAllMailbox(Session session, AccountId accountId, {Properties? properties}); Future> getAllMailboxCache(AccountId accountId, UserName userName); @@ -54,4 +54,6 @@ abstract class MailboxDataSource { Future setRoleDefaultMailbox(Session session, AccountId accountId, List listMailbox); Future getMailboxByRole(Session session, AccountId accountId, Role role); + + Future clearAllMailboxCache(AccountId accountId, UserName userName); } \ No newline at end of file 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 6e4096c14..869270f9b 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 @@ -12,13 +12,13 @@ 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/core/user_name.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/get/get_mailbox_response.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.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'; import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/get_mailbox_by_role_response.dart'; -import 'package:tmail_ui_user/features/mailbox/domain/model/mailbox_response.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/move_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/rename_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/subscribe_mailbox_request.dart'; @@ -33,7 +33,7 @@ class MailboxCacheDataSourceImpl extends MailboxDataSource { MailboxCacheDataSourceImpl(this._mailboxCacheManager, this._exceptionThrower); @override - Future getAllMailbox(Session session, AccountId accountId, {Properties? properties}) { + Future getAllMailbox(Session session, AccountId accountId, {Properties? properties}) { throw UnimplementedError(); } @@ -111,4 +111,11 @@ class MailboxCacheDataSourceImpl extends MailboxDataSource { Future getMailboxByRole(Session session, AccountId accountId, Role role, {UnsignedInt? limit}) { throw UnimplementedError(); } + + @override + Future clearAllMailboxCache(AccountId accountId, UserName userName) { + return Future.sync(() async { + return await _mailboxCacheManager.clearAll(accountId, userName); + }).catchError(_exceptionThrower.throwException); + } } \ No newline at end of file diff --git a/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart b/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart index 0adc888fe..3151e2778 100644 --- a/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart +++ b/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart @@ -12,6 +12,7 @@ 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/core/user_name.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/get/get_mailbox_response.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:tmail_ui_user/features/mailbox/data/datasource/mailbox_datasource.dart'; import 'package:tmail_ui_user/features/mailbox/data/model/mailbox_change_response.dart'; @@ -19,7 +20,6 @@ import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_api.dart'; import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_isolate_worker.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/get_mailbox_by_role_response.dart'; -import 'package:tmail_ui_user/features/mailbox/domain/model/mailbox_response.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/move_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/rename_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/subscribe_mailbox_request.dart'; @@ -35,7 +35,7 @@ class MailboxDataSourceImpl extends MailboxDataSource { MailboxDataSourceImpl(this.mailboxAPI, this._mailboxIsolateWorker, this._exceptionThrower); @override - Future getAllMailbox(Session session, AccountId accountId, {Properties? properties}) { + Future getAllMailbox(Session session, AccountId accountId, {Properties? properties}) { return Future.sync(() async { return await mailboxAPI.getAllMailbox(session, accountId, properties: properties); }).catchError(_exceptionThrower.throwException); @@ -141,4 +141,9 @@ class MailboxDataSourceImpl extends MailboxDataSource { return await mailboxAPI.getMailboxByRole(session, accountId, role); }).catchError(_exceptionThrower.throwException); } + + @override + Future clearAllMailboxCache(AccountId accountId, UserName userName) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/mailbox/data/local/mailbox_cache_manager.dart b/lib/features/mailbox/data/local/mailbox_cache_manager.dart index f41c87e5a..975b24b76 100644 --- a/lib/features/mailbox/data/local/mailbox_cache_manager.dart +++ b/lib/features/mailbox/data/local/mailbox_cache_manager.dart @@ -1,10 +1,12 @@ +import 'package:collection/collection.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/extensions/account_id_extensions.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/caching/clients/mailbox_cache_client.dart'; +import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart'; import 'package:tmail_ui_user/features/mailbox/data/extensions/list_mailbox_cache_extension.dart'; import 'package:tmail_ui_user/features/mailbox/data/extensions/list_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox/data/extensions/list_mailbox_id_extension.dart'; @@ -17,7 +19,8 @@ class MailboxCacheManager { MailboxCacheManager(this._mailboxCacheClient); Future> getAllMailbox(AccountId accountId, UserName userName) async { - final mailboxCacheList = await _mailboxCacheClient.getListByTupleKey(accountId.asString, userName.value); + final nestedKey = TupleKey(accountId.asString, userName.value).encodeKey; + final mailboxCacheList = await _mailboxCacheClient.getListByNestedKey(nestedKey); return mailboxCacheList.toMailboxList(); } @@ -28,35 +31,35 @@ class MailboxCacheManager { List? created, List? destroyed }) async { - final mailboxCacheExist = await _mailboxCacheClient.isExistTable(); - if (mailboxCacheExist) { - final updatedCacheMailboxes = updated?.toMapCache(accountId, userName) ?? {}; - final createdCacheMailboxes = created?.toMapCache(accountId, userName) ?? {}; - final destroyedCacheMailboxes = destroyed?.toCacheKeyList(accountId, userName) ?? []; - - await Future.wait([ - _mailboxCacheClient.updateMultipleItem(updatedCacheMailboxes), - _mailboxCacheClient.insertMultipleItem(createdCacheMailboxes), - _mailboxCacheClient.deleteMultipleItem(destroyedCacheMailboxes) - ]); - } else { - final createdCacheMailboxes = created?.toMapCache(accountId, userName) ?? {}; + if (created?.isNotEmpty == true) { + final createdCacheMailboxes = created!.toMapCache(accountId, userName); await _mailboxCacheClient.insertMultipleItem(createdCacheMailboxes); } - return Future.value(); + + if (updated?.isNotEmpty == true) { + final updatedCacheMailboxes = updated!.toMapCache(accountId, userName); + await _mailboxCacheClient.updateMultipleItem(updatedCacheMailboxes); + } + + final mailboxCacheExist = await _mailboxCacheClient.isExistTable(); + if (destroyed?.isNotEmpty == true && mailboxCacheExist) { + final destroyedCacheMailboxes = destroyed!.toCacheKeyList(accountId, userName); + await _mailboxCacheClient.deleteMultipleItem(destroyedCacheMailboxes); + } } Future getSpamMailbox(AccountId accountId, UserName userName) async { - final mailboxCachedList = await _mailboxCacheClient.getListByTupleKey(accountId.asString, userName.value); - final listSpamMailboxCached = mailboxCachedList - .toMailboxList() - .where((mailbox) => mailbox.role == PresentationMailbox.roleSpam) - .toList(); - - if (listSpamMailboxCached.isNotEmpty) { - return listSpamMailboxCached.first; + final mailboxList = await getAllMailbox(accountId, userName); + final spamMailbox = mailboxList.firstWhereOrNull((mailbox) => mailbox.role == PresentationMailbox.roleSpam); + if (spamMailbox != null) { + return spamMailbox; } else { throw NotFoundSpamMailboxCachedException(); } } + + Future clearAll(AccountId accountId, UserName userName) async { + final nestedKey = TupleKey(accountId.asString, userName.value).encodeKey; + await _mailboxCacheClient.clearAllDataContainKey(nestedKey); + } } \ No newline at end of file diff --git a/lib/features/mailbox/data/network/mailbox_api.dart b/lib/features/mailbox/data/network/mailbox_api.dart index e51569d40..1b824fe1f 100644 --- a/lib/features/mailbox/data/network/mailbox_api.dart +++ b/lib/features/mailbox/data/network/mailbox_api.dart @@ -30,12 +30,12 @@ import 'package:model/model.dart'; import 'package:tmail_ui_user/features/base/mixin/handle_error_mixin.dart'; import 'package:tmail_ui_user/features/composer/domain/exceptions/set_method_exception.dart'; import 'package:tmail_ui_user/features/mailbox/data/model/mailbox_change_response.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/exceptions/mailbox_exception.dart'; import 'package:tmail_ui_user/features/mailbox/domain/exceptions/set_mailbox_method_exception.dart'; import 'package:tmail_ui_user/features/mailbox/domain/extensions/list_mailbox_id_extension.dart'; import 'package:tmail_ui_user/features/mailbox/domain/extensions/role_extension.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/get_mailbox_by_role_response.dart'; -import 'package:tmail_ui_user/features/mailbox/domain/model/mailbox_response.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/mailbox_subscribe_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/move_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/rename_mailbox_request.dart'; @@ -51,7 +51,7 @@ class MailboxAPI with HandleSetErrorMixin { MailboxAPI(this.httpClient, this._uuid); - Future getAllMailbox(Session session, AccountId accountId, {Properties? properties}) async { + Future getAllMailbox(Session session, AccountId accountId, {Properties? properties}) async { final processingInvocation = ProcessingInvocation(); final jmapRequestBuilder = JmapRequestBuilder(httpClient, processingInvocation); @@ -70,11 +70,15 @@ class MailboxAPI with HandleSetErrorMixin { .build() .execute(); - final resultCreated = result.parse( + final getMailboxResponse = result.parse( queryInvocation.methodCallId, GetMailboxResponse.deserialize); - return MailboxResponse(mailboxes: resultCreated?.list, state: resultCreated?.state); + if (getMailboxResponse == null) { + throw NotFoundGetMailboxResponseException(); + } else { + return getMailboxResponse; + } } Future getChanges(Session session, AccountId accountId, State sinceState, {Properties? properties}) async { diff --git a/lib/features/mailbox/data/repository/mailbox_repository_impl.dart b/lib/features/mailbox/data/repository/mailbox_repository_impl.dart index 24148e294..6986a4e6d 100644 --- a/lib/features/mailbox/data/repository/mailbox_repository_impl.dart +++ b/lib/features/mailbox/data/repository/mailbox_repository_impl.dart @@ -12,6 +12,7 @@ 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/state.dart'; 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'; @@ -20,10 +21,13 @@ import 'package:tmail_ui_user/features/mailbox/data/datasource/mailbox_datasourc import 'package:tmail_ui_user/features/mailbox/data/datasource/state_datasource.dart'; import 'package:tmail_ui_user/features/mailbox/data/extensions/state_extension.dart'; import 'package:tmail_ui_user/features/mailbox/data/model/state_type.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/model/cache_mailbox_response.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/model/full_mailbox_response.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/get_mailbox_by_role_response.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/mailbox_response.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/move_mailbox_request.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/model/partial_mailbox_response.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/rename_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/subscribe_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/subscribe_multiple_mailbox_request.dart'; @@ -41,59 +45,151 @@ class MailboxRepositoryImpl extends MailboxRepository { @override Stream getAllMailbox(Session session, AccountId accountId, {Properties? properties}) async* { - final localMailboxResponse = await Future.wait([ - mapDataSource[DataSourceType.local]!.getAllMailboxCache(accountId, session.username), - stateDataSource.getState(accountId, session.username, StateType.mailbox) - ]).then((List response) { - return MailboxResponse(mailboxes: response.first, state: response.last); - }); - - yield localMailboxResponse; - - if (localMailboxResponse.hasData()) { - bool hasMoreChanges = true; - State? sinceState = localMailboxResponse.state!; - - while(hasMoreChanges && sinceState != null) { - final changesResponse = await mapDataSource[DataSourceType.network]!.getChanges(session, accountId, sinceState, properties: properties); - - hasMoreChanges = changesResponse.hasMoreChanges; - sinceState = changesResponse.newStateChanges; - - final newMailboxUpdated = await _combineMailboxCache( - mailboxUpdatedList: changesResponse.updated, - updatedProperties: changesResponse.updatedProperties, - mailboxCacheList: localMailboxResponse.mailboxes!); - - await Future.wait([ - mapDataSource[DataSourceType.local]!.update( - accountId, - session.username, - updated: newMailboxUpdated, - created: changesResponse.created, - destroyed: changesResponse.destroyed), - if (changesResponse.newStateMailbox != null) - stateDataSource.saveState(accountId, session.username, changesResponse.newStateMailbox!.toStateCache(StateType.mailbox)), - ]); - } - } else { - final mailboxResponse = await mapDataSource[DataSourceType.network]!.getAllMailbox(session, accountId); - - await Future.wait([ - mapDataSource[DataSourceType.local]!.update(accountId, session.username, created: mailboxResponse.mailboxes), - if (mailboxResponse.state != null) - stateDataSource.saveState(accountId, session.username, mailboxResponse.state!.toStateCache(StateType.mailbox)), - ]); + CacheMailboxResponse? cacheMailboxResponse = await _getAllMailboxFromCache(accountId, session.username); + if (cacheMailboxResponse != null) { + yield cacheMailboxResponse; } - final newMailboxResponse = await Future.wait([ - mapDataSource[DataSourceType.local]!.getAllMailboxCache(accountId, session.username), - stateDataSource.getState(accountId, session.username, StateType.mailbox) - ]).then((List response) { - return MailboxResponse(mailboxes: response.first, state: response.last); - }); + final newMailboxResponse = await _getAllMailboxFromJMAP(accountId, session); + if (newMailboxResponse == null) { + return; + } - yield newMailboxResponse; + if (newMailboxResponse is FullMailboxResponse) { + await _syncNewInCache( + accountId, + session.username, + newMailboxResponse.mailboxes, + newMailboxResponse.state!); + + yield newMailboxResponse; + } else if (newMailboxResponse is PartialMailboxResponse) { + await _syncUpdateInCache( + accountId, + session.username, + newMailboxResponse.mailboxes, + newMailboxResponse.state!); + + cacheMailboxResponse = await _getAllMailboxFromCache(accountId, session.username); + if (cacheMailboxResponse != null) { + yield cacheMailboxResponse; + } else { + yield newMailboxResponse; + } + } + } + + Future _getAllMailboxFromCache(AccountId accountId, UserName userName) async { + try { + final cacheMailboxResponse = await Future.wait([ + mapDataSource[DataSourceType.local]!.getAllMailboxCache(accountId, userName), + stateDataSource.getState(accountId, userName, StateType.mailbox) + ], eagerError: true).then((List response) => CacheMailboxResponse(mailboxes: response.first, state: response.last)); + log('MailboxRepositoryImpl::_getAllMailboxFromCache: MAILBOX_CACHED = ${cacheMailboxResponse.mailboxes.length} | STATE_CACHED = ${cacheMailboxResponse.state?.value}'); + return cacheMailboxResponse; + } catch (e) { + logError('MailboxRepositoryImpl::_getAllMailboxFromCache: Exception: $e'); + return null; + } + } + + Future _getAllMailboxFromJMAP( + AccountId accountId, + Session session, + {Properties? properties} + ) async { + try { + final getMailboxResponse = await mapDataSource[DataSourceType.network]!.getAllMailbox( + session, + accountId, + properties: properties); + log('MailboxRepositoryImpl::_getAllMailboxFromJMAP: MAILBOX_NETWORK = ${getMailboxResponse.list.length} | STATE_NETWORK = ${getMailboxResponse.state.value}'); + if (getMailboxResponse.notFound?.isNotEmpty == true) { + return PartialMailboxResponse( + mailboxNotFound: getMailboxResponse.notFound!, + mailboxes: getMailboxResponse.list, + state: getMailboxResponse.state); + } else { + return FullMailboxResponse( + mailboxes: getMailboxResponse.list, + state: getMailboxResponse.state); + } + } catch (e) { + logError('MailboxRepositoryImpl::_getAllMailboxFromJMAP: Exception: $e'); + return null; + } + } + + Future _syncNewInCache( + AccountId accountId, + UserName userName, + List newMailboxes, + State newState, + ) async { + await Future.wait([ + _syncNewMailboxesInCache(accountId, userName, newMailboxes), + _syncNewMailboxStateInCache(accountId, userName, newState) + ]); + } + + Future _syncNewMailboxesInCache( + AccountId accountId, + UserName userName, + List newMailboxes + ) async { + try { + await mapDataSource[DataSourceType.local]!.clearAllMailboxCache( + accountId, + userName); + await mapDataSource[DataSourceType.local]!.update( + accountId, + userName, + created: newMailboxes); + } catch (e) { + logError('MailboxRepositoryImpl::_syncNewMailboxesInCache: Exception = $e'); + } + } + + Future _syncNewMailboxStateInCache( + AccountId accountId, + UserName userName, + State newState + ) async { + try { + await stateDataSource.saveState( + accountId, + userName, + newState.toStateCache(StateType.mailbox)); + } catch (e) { + logError('MailboxRepositoryImpl::_syncNewMailboxStateInCache: Exception = $e'); + } + } + + Future _syncUpdateInCache( + AccountId accountId, + UserName userName, + List newMailboxes, + State newState, + ) async { + await Future.wait([ + _syncUpdateMailboxesInCache(accountId, userName, newMailboxes), + _syncNewMailboxStateInCache(accountId, userName, newState) + ]); + } + + Future _syncUpdateMailboxesInCache( + AccountId accountId, + UserName userName, + List newMailboxes + ) async { + try { + await mapDataSource[DataSourceType.local]!.update( + accountId, + userName, + updated: newMailboxes); + } catch (e) { + logError('MailboxRepositoryImpl::_syncUpdateMailboxesInCache: Exception = $e'); + } } Future?> _combineMailboxCache({ @@ -156,7 +252,7 @@ class MailboxRepositoryImpl extends MailboxRepository { mapDataSource[DataSourceType.local]!.getAllMailboxCache(accountId, session.username), stateDataSource.getState(accountId, session.username, StateType.mailbox) ]).then((List response) { - return MailboxResponse(mailboxes: response.first, state: response.last); + return CacheMailboxResponse(mailboxes: response.first, state: response.last); }); yield newMailboxResponse; diff --git a/lib/features/mailbox/domain/exceptions/mailbox_exception.dart b/lib/features/mailbox/domain/exceptions/mailbox_exception.dart new file mode 100644 index 000000000..015859d57 --- /dev/null +++ b/lib/features/mailbox/domain/exceptions/mailbox_exception.dart @@ -0,0 +1 @@ +class NotFoundGetMailboxResponseException implements Exception {} \ No newline at end of file diff --git a/lib/features/mailbox/domain/model/cache_mailbox_response.dart b/lib/features/mailbox/domain/model/cache_mailbox_response.dart new file mode 100644 index 000000000..a4abf6352 --- /dev/null +++ b/lib/features/mailbox/domain/model/cache_mailbox_response.dart @@ -0,0 +1,6 @@ +import 'package:tmail_ui_user/features/mailbox/domain/model/mailbox_response.dart'; + +class CacheMailboxResponse extends MailboxResponse { + + CacheMailboxResponse({required super.mailboxes, super.state}); +} \ No newline at end of file diff --git a/lib/features/mailbox/domain/model/full_mailbox_response.dart b/lib/features/mailbox/domain/model/full_mailbox_response.dart new file mode 100644 index 000000000..b7d9729bc --- /dev/null +++ b/lib/features/mailbox/domain/model/full_mailbox_response.dart @@ -0,0 +1,6 @@ +import 'package:tmail_ui_user/features/mailbox/domain/model/mailbox_response.dart'; + +class FullMailboxResponse extends MailboxResponse { + + FullMailboxResponse({required super.mailboxes, super.state}); +} \ No newline at end of file diff --git a/lib/features/mailbox/domain/model/mailbox_response.dart b/lib/features/mailbox/domain/model/mailbox_response.dart index 8b07a0e5e..16fb99184 100644 --- a/lib/features/mailbox/domain/model/mailbox_response.dart +++ b/lib/features/mailbox/domain/model/mailbox_response.dart @@ -3,21 +3,15 @@ import 'package:equatable/equatable.dart'; import 'package:jmap_dart_client/jmap/core/state.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; -class MailboxResponse with EquatableMixin { - final List? mailboxes; +abstract class MailboxResponse with EquatableMixin { + final List mailboxes; final State? state; MailboxResponse({ - this.mailboxes, + required this.mailboxes, this.state }); - bool hasData() { - return mailboxes != null - && mailboxes!.isNotEmpty - && state != null; - } - @override List get props => [mailboxes, state]; } \ No newline at end of file diff --git a/lib/features/mailbox/domain/model/partial_mailbox_response.dart b/lib/features/mailbox/domain/model/partial_mailbox_response.dart new file mode 100644 index 000000000..c9c9d4866 --- /dev/null +++ b/lib/features/mailbox/domain/model/partial_mailbox_response.dart @@ -0,0 +1,16 @@ +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/model/mailbox_response.dart'; + +class PartialMailboxResponse extends MailboxResponse { + + final List mailboxNotFound; + + PartialMailboxResponse({ + required this.mailboxNotFound, + required super.mailboxes, + super.state, + }); + + @override + List get props => [mailboxNotFound, ...super.props]; +} \ No newline at end of file diff --git a/lib/features/mailbox/domain/usecases/get_all_mailbox_interactor.dart b/lib/features/mailbox/domain/usecases/get_all_mailbox_interactor.dart index 9bedcc054..367848876 100644 --- a/lib/features/mailbox/domain/usecases/get_all_mailbox_interactor.dart +++ b/lib/features/mailbox/domain/usecases/get_all_mailbox_interactor.dart @@ -1,9 +1,10 @@ -import 'package:core/core.dart'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; import 'package:dartz/dartz.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; -import 'package:model/model.dart'; +import 'package:model/extensions/mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/mailbox_response.dart'; import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/get_all_mailboxes_state.dart'; @@ -30,8 +31,8 @@ class GetAllMailboxInteractor { Either _toGetMailboxState(MailboxResponse mailboxResponse) { final mailboxList = mailboxResponse.mailboxes - ?.map((mailbox) => mailbox.toPresentationMailbox()).toList() - ?? List.empty(); + .map((mailbox) => mailbox.toPresentationMailbox()) + .toList(); return Right(GetAllMailboxSuccess( mailboxList: mailboxList, diff --git a/lib/features/mailbox/domain/usecases/refresh_all_mailbox_interactor.dart b/lib/features/mailbox/domain/usecases/refresh_all_mailbox_interactor.dart index d8985ae9c..d59883003 100644 --- a/lib/features/mailbox/domain/usecases/refresh_all_mailbox_interactor.dart +++ b/lib/features/mailbox/domain/usecases/refresh_all_mailbox_interactor.dart @@ -7,7 +7,6 @@ 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/state.dart' as jmap_state; import 'package:model/extensions/mailbox_extension.dart'; -import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/mailbox_response.dart'; import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/refresh_changes_all_mailboxes_state.dart'; @@ -36,8 +35,8 @@ class RefreshAllMailboxInteractor { Either _toGetMailboxState(MailboxResponse mailboxResponse) { final mailboxList = mailboxResponse.mailboxes - ?.map((mailbox) => mailbox.toPresentationMailbox()).toList() - ?? List.empty(); + .map((mailbox) => mailbox.toPresentationMailbox()) + .toList(); return Right(RefreshChangesAllMailboxSuccess( mailboxList: mailboxList, diff --git a/lib/features/offline_mode/manager/new_email_cache_manager.dart b/lib/features/offline_mode/manager/new_email_cache_manager.dart index 83bf38b53..9040bbe90 100644 --- a/lib/features/offline_mode/manager/new_email_cache_manager.dart +++ b/lib/features/offline_mode/manager/new_email_cache_manager.dart @@ -60,7 +60,8 @@ class NewEmailCacheManager { } Future> getAllDetailedEmails(AccountId accountId, UserName userName) async { - final detailedEmailCacheList = await _cacheClient.getListByTupleKey(accountId.asString, userName.value); + final nestedKey = TupleKey(accountId.asString, userName.value).encodeKey; + final detailedEmailCacheList = await _cacheClient.getListByNestedKey(nestedKey); detailedEmailCacheList.sortByLatestTime(); return detailedEmailCacheList; } diff --git a/lib/features/offline_mode/manager/opened_email_cache_manager.dart b/lib/features/offline_mode/manager/opened_email_cache_manager.dart index b6f791ab1..a523ffe7a 100644 --- a/lib/features/offline_mode/manager/opened_email_cache_manager.dart +++ b/lib/features/offline_mode/manager/opened_email_cache_manager.dart @@ -40,7 +40,8 @@ class OpenedEmailCacheManager { } Future> getAllDetailedEmails(AccountId accountId, UserName userName) async { - final detailedEmailCacheList = await _cacheClient.getListByTupleKey(accountId.asString, userName.value); + final nestedKey = TupleKey(accountId.asString, userName.value).encodeKey; + final detailedEmailCacheList = await _cacheClient.getListByNestedKey(nestedKey); detailedEmailCacheList.sortByLatestTime(); log('OpenedEmailCacheManager::getAllDetailedEmails():SIZE: ${detailedEmailCacheList.length}'); return detailedEmailCacheList; diff --git a/lib/features/offline_mode/manager/sending_email_cache_manager.dart b/lib/features/offline_mode/manager/sending_email_cache_manager.dart index 34c6fecb5..fa7ea3a0b 100644 --- a/lib/features/offline_mode/manager/sending_email_cache_manager.dart +++ b/lib/features/offline_mode/manager/sending_email_cache_manager.dart @@ -30,7 +30,8 @@ class SendingEmailCacheManager { } Future> getAllSendingEmailsByTupleKey(AccountId accountId, UserName userName) async { - final sendingEmailsCache = await _hiveCacheClient.getListByTupleKey(accountId.asString, userName.value); + final nestedKey = TupleKey(accountId.asString, userName.value).encodeKey; + final sendingEmailsCache = await _hiveCacheClient.getListByNestedKey(nestedKey); sendingEmailsCache.sortByLatestTime(); return sendingEmailsCache; } diff --git a/lib/features/push_notification/data/repository/fcm_repository_impl.dart b/lib/features/push_notification/data/repository/fcm_repository_impl.dart index 8bf31af42..3abd9b15a 100644 --- a/lib/features/push_notification/data/repository/fcm_repository_impl.dart +++ b/lib/features/push_notification/data/repository/fcm_repository_impl.dart @@ -7,6 +7,7 @@ import 'package:fcm/model/type_name.dart'; import 'package:jmap_dart_client/jmap/account_id.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/state.dart' as jmap; 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'; @@ -24,7 +25,6 @@ import 'package:tmail_ui_user/features/push_notification/domain/repository/fcm_r import 'package:tmail_ui_user/features/push_notification/domain/utils/fcm_constants.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:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; class FCMRepositoryImpl extends FCMRepository { @@ -131,7 +131,7 @@ class FCMRepositoryImpl extends FCMRepository { return mailboxesCacheNotPutNotifications; } else { final mailboxResponse = await _mapMailboxDataSource[DataSourceType.network]!.getAllMailbox(session, accountId); - final mailboxes = mailboxResponse.mailboxes ?? []; + final mailboxes = mailboxResponse.list; final mailboxesNotPutNotifications = mailboxes .map((mailbox) => mailbox.toPresentationMailbox()) .where((presentationMailbox) => presentationMailbox.pushNotificationDeactivated) diff --git a/lib/features/thread/data/local/email_cache_manager.dart b/lib/features/thread/data/local/email_cache_manager.dart index 0e37fe9d5..43ee5bb25 100644 --- a/lib/features/thread/data/local/email_cache_manager.dart +++ b/lib/features/thread/data/local/email_cache_manager.dart @@ -1,9 +1,10 @@ import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; -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:model/model.dart'; import 'package:tmail_ui_user/features/caching/clients/email_cache_client.dart'; import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart'; import 'package:tmail_ui_user/features/cleanup/domain/model/email_cleanup_rule.dart'; @@ -13,7 +14,6 @@ import 'package:tmail_ui_user/features/thread/data/extensions/email_extension.da import 'package:tmail_ui_user/features/thread/data/extensions/list_email_cache_extension.dart'; import 'package:tmail_ui_user/features/thread/data/extensions/list_email_extension.dart'; import 'package:tmail_ui_user/features/thread/data/extensions/list_email_id_extension.dart'; -import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; import 'package:tmail_ui_user/features/thread/data/model/email_cache.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; @@ -31,7 +31,8 @@ class EmailCacheManager { UnsignedInt? limit, FilterMessageOption filterOption = FilterMessageOption.all }) async { - final emailCacheList = await _emailCacheClient.getListByTupleKey(accountId.asString, userName.value); + final nestedKey = TupleKey(accountId.asString, userName.value).encodeKey; + final emailCacheList = await _emailCacheClient.getListByNestedKey(nestedKey); final emailList = emailCacheList .toEmailList() .where((email) => _filterEmailByMailbox(email, filterOption, inMailboxId))