diff --git a/core/lib/utils/file_utils.dart b/core/lib/utils/file_utils.dart index c092334ef..a4a736b0a 100644 --- a/core/lib/utils/file_utils.dart +++ b/core/lib/utils/file_utils.dart @@ -90,28 +90,27 @@ class FileUtils { return emailContent; } - Future isFileExisted({ - required String nameFile, - String? folderPath, - String? extensionFile - }) async { - final internalStorageDirPath = await _getInternalStorageDirPath( - nameFile: nameFile, - folderPath: folderPath, - extensionFile: extensionFile - ); - - return File(internalStorageDirPath).exists(); + Future getFolder(String folderName) async { + try { + String folderPath = (await getApplicationDocumentsDirectory()).absolute.path; + folderPath = '$folderPath/$folderName'; + log('FileUtils::getFolder(): $folderPath'); + return Directory(folderPath); + } catch (e) { + logError('FileUtils::getFolder():EXCEPTION: $e'); + return null; + } } Future removeFolder(String folderName) async { try { - String folderPath = (await getApplicationDocumentsDirectory()).absolute.path; - folderPath = '$folderPath/$folderName'; - log('FileUtils::removeFolder():folderPath: $folderPath'); - final dir = Directory(folderPath); + final dir = await getFolder(folderName); + if (dir == null) return; + if (await dir.exists()) { await dir.delete(recursive: true); + await dir.create(); // Re-create folder + log('FileUtils::removeFolder: Remove ${dir.path} success'); } } catch (e) { logError('FileUtils::removeFolder():EXCEPTION: $e'); diff --git a/core/test/sanitize_autolink_filter_test.dart b/core/test/sanitize_autolink_filter_test.dart index 98fe740de..46916d11c 100644 --- a/core/test/sanitize_autolink_filter_test.dart +++ b/core/test/sanitize_autolink_filter_test.dart @@ -14,7 +14,7 @@ void main() { final htmlValidate = sanitizeAutolinkFilter.process('See https://linagora.com at Hanoi'); expect( htmlValidate, - equals('See linagora.com at Hanoi') + equals('See linagora.com at Hanoi') ); } ); @@ -25,7 +25,7 @@ void main() { final htmlValidate = sanitizeAutolinkFilter.process('See http://linagora.com at Hanoi'); expect( htmlValidate, - equals('See linagora.com at Hanoi') + equals('See linagora.com at Hanoi') ); } ); @@ -36,7 +36,7 @@ void main() { final htmlValidate = sanitizeAutolinkFilter.process('See www.linagora.com at Hanoi'); expect( htmlValidate, - equals('See linagora.com at Hanoi') + equals('See linagora.com at Hanoi') ); } ); @@ -47,7 +47,7 @@ void main() { final htmlValidate = sanitizeAutolinkFilter.process('See tdvu@linagora.com at Hanoi'); expect( htmlValidate, - equals('See tdvu@linagora.com at Hanoi') + equals('See tdvu@linagora.com at Hanoi') ); } ); diff --git a/core/test/utils/file_utils_test.dart b/core/test/utils/file_utils_test.dart index 995c19a91..0a2040eda 100644 --- a/core/test/utils/file_utils_test.dart +++ b/core/test/utils/file_utils_test.dart @@ -11,13 +11,15 @@ void main() { const fileContent = 'Hello, World!'; group('fileUtils test', () { + final fileUtils = FileUtils(); + setUp(() async { PathProviderPlatform.instance = FakePathProviderPlatform(); }); test('Store private HTLM String to File', () async { - final file = await FileUtils().saveToFile(nameFile: fileName, content: fileContent); + final file = await fileUtils.saveToFile(nameFile: fileName, content: fileContent); expect(await file.exists(), equals(true)); @@ -29,9 +31,9 @@ void main() { test('Get HTML String from File Private', () async { /// Create a temporary file that will be deleted after `getFromFile` is done - final file = await FileUtils().saveToFile(nameFile: fileName, content: fileContent); + final file = await fileUtils.saveToFile(nameFile: fileName, content: fileContent); - final htmlString = await FileUtils().getContentFromFile(nameFile: fileName); + final htmlString = await fileUtils.getContentFromFile(nameFile: fileName); expect(htmlString.isNotEmpty, equals(true)); @@ -47,10 +49,10 @@ void main() { 'and Platform is mobile', () async { // arrange - final file = await FileUtils().saveToFile(nameFile: fileName, content: fileContent); + final file = await fileUtils.saveToFile(nameFile: fileName, content: fileContent); // act - await FileUtils().deleteCompressedFileOnMobile(file.path, pathContains: fileName); + await fileUtils.deleteCompressedFileOnMobile(file.path, pathContains: fileName); // assert expect(await file.exists(), equals(false)); @@ -64,11 +66,11 @@ void main() { () async { // arrange debugDefaultTargetPlatformOverride = TargetPlatform.macOS; - final file = await FileUtils().saveToFile(nameFile: fileName, content: fileContent); + final file = await fileUtils.saveToFile(nameFile: fileName, content: fileContent); expect(await file.exists(), equals(true)); // act - await FileUtils().deleteCompressedFileOnMobile(file.path, pathContains: fileName); + await fileUtils.deleteCompressedFileOnMobile(file.path, pathContains: fileName); // assert expect(await file.exists(), equals(true)); diff --git a/core/test/utils/string_convert_test.dart b/core/test/utils/string_convert_test.dart index 8ebe6fa0d..2ac3f115c 100644 --- a/core/test/utils/string_convert_test.dart +++ b/core/test/utils/string_convert_test.dart @@ -373,6 +373,14 @@ void main() { expect(StringConvert.isTextTable(table), isTrue); }); + test('should return true for separator-only Markdown', () { + const text = """ + |----------|----------| + |----------|----------| + """; + expect(StringConvert.isTextTable(text), isTrue); + }); + // --------------------------- // Test cases cho ASCII art tables // --------------------------- @@ -437,14 +445,6 @@ void main() { expect(StringConvert.isTextTable(' \n \n '), isFalse); }); - test('should return true for separator-only Markdown', () { - const text = """ - |----------|----------| - |----------|----------| - """; - expect(StringConvert.isTextTable(text), isTrue); - }); - test('should return false for single-line Markdown', () { const text = '| Header 1 | Header 2 |'; expect(StringConvert.isTextTable(text), isFalse); diff --git a/lib/features/base/upgradeable/upgrade_hive_database_steps_v11.dart b/lib/features/base/upgradeable/upgrade_hive_database_steps_v11.dart index 0f7de94c9..8003a0d22 100644 --- a/lib/features/base/upgradeable/upgrade_hive_database_steps_v11.dart +++ b/lib/features/base/upgradeable/upgrade_hive_database_steps_v11.dart @@ -11,7 +11,7 @@ class UpgradeHiveDatabaseStepsV11 extends UpgradeDatabaseSteps { @override Future onUpgrade(int oldVersion, int newVersion) async { if (oldVersion > 0 && oldVersion < newVersion && newVersion == 11) { - await _cachingManager.clearEmailCacheAndAllStateCache(); + await _cachingManager.clearAllEmailAndStateCache(); } } } \ No newline at end of file diff --git a/lib/features/base/upgradeable/upgrade_hive_database_steps_v12.dart b/lib/features/base/upgradeable/upgrade_hive_database_steps_v12.dart index 603eec632..a909e0d1d 100644 --- a/lib/features/base/upgradeable/upgrade_hive_database_steps_v12.dart +++ b/lib/features/base/upgradeable/upgrade_hive_database_steps_v12.dart @@ -11,7 +11,7 @@ class UpgradeHiveDatabaseStepsV12 extends UpgradeDatabaseSteps { @override Future onUpgrade(int oldVersion, int newVersion) async { if (oldVersion > 0 && oldVersion < newVersion && newVersion == 12) { - await _cachingManager.clearEmailCacheAndAllStateCache(); + await _cachingManager.clearAllEmailAndStateCache(); } } } \ No newline at end of file diff --git a/lib/features/base/upgradeable/upgrade_hive_database_steps_v14.dart b/lib/features/base/upgradeable/upgrade_hive_database_steps_v14.dart new file mode 100644 index 000000000..ae215cff4 --- /dev/null +++ b/lib/features/base/upgradeable/upgrade_hive_database_steps_v14.dart @@ -0,0 +1,17 @@ + +import 'package:tmail_ui_user/features/base/upgradeable/upgrade_database_steps.dart'; +import 'package:tmail_ui_user/features/caching/caching_manager.dart'; + +class UpgradeHiveDatabaseStepsV14 extends UpgradeDatabaseSteps { + + final CachingManager _cachingManager; + + UpgradeHiveDatabaseStepsV14(this._cachingManager); + + @override + Future onUpgrade(int oldVersion, int newVersion) async { + if (oldVersion > 0 && oldVersion < newVersion && newVersion == 14) { + await _cachingManager.clearAllEmailAndStateCache(); + } + } +} \ No newline at end of file diff --git a/lib/features/caching/caching_manager.dart b/lib/features/caching/caching_manager.dart index fb22bc36c..8d4885a4b 100644 --- a/lib/features/caching/caching_manager.dart +++ b/lib/features/caching/caching_manager.dart @@ -1,8 +1,10 @@ import 'package:core/utils/app_logger.dart'; import 'package:core/utils/file_utils.dart'; import 'package:core/utils/platform_info.dart'; +import 'package:fcm/model/type_name.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; -import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:model/extensions/account_id_extensions.dart'; import 'package:tmail_ui_user/features/caching/clients/account_cache_client.dart'; import 'package:tmail_ui_user/features/caching/clients/email_cache_client.dart'; import 'package:tmail_ui_user/features/caching/clients/fcm_cache_client.dart'; @@ -17,11 +19,13 @@ import 'package:tmail_ui_user/features/caching/clients/recent_search_cache_clien import 'package:tmail_ui_user/features/caching/clients/session_hive_cache_client.dart'; import 'package:tmail_ui_user/features/caching/clients/state_cache_client.dart'; import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart'; +import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart'; import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart'; import 'package:tmail_ui_user/features/mailbox/data/model/state_type.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/local/local_spam_report_manager.dart'; import 'package:tmail_ui_user/features/offline_mode/manager/sending_email_cache_manager.dart'; import 'package:tmail_ui_user/features/push_notification/data/keychain/keychain_sharing_manager.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/extensions/type_name_extension.dart'; class CachingManager { final MailboxCacheClient _mailboxCacheClient; @@ -102,24 +106,58 @@ class CachingManager { ], eagerError: true); } - Future clearEmailCacheAndStateCacheByTupleKey(AccountId accountId, Session session) { - return Future.wait([ - _stateCacheClient.deleteItem(StateType.email.getTupleKeyStored(accountId, session.username)), - _emailCacheClient.clearAllData(), - if (PlatformInfo.isMobile) clearAllFileInStorage(), - ], eagerError: true); + Future clearEmailAndStateCache({AccountId? accountId, UserName? userName}) { + log('CachingManager::clearEmailAndStateCache:userName = $userName'); + if (accountId != null && userName != null) { + final emailKey = TupleKey(accountId.asString, userName.value).encodeKey; + final stateKey = StateType.email.getTupleKeyStored(accountId, userName); + + return Future.wait([ + _emailCacheClient.clearAllDataContainKey(emailKey), + _stateCacheClient.deleteItem(stateKey), + if (PlatformInfo.isMobile) + clearFCMEmailStateCache(accountId: accountId, userName: userName), + ]); + } else { + final stateKey = StateType.email.getTupleKeyStoredWithoutAccount(); + return Future.wait([ + _emailCacheClient.clearAllData(), + _stateCacheClient.deleteItem(stateKey), + if (PlatformInfo.isMobile) clearFCMEmailStateCache(), + ]); + } } - Future clearEmailCacheAndAllStateCache() { + Future clearDetailedEmailCache({AccountId? accountId, UserName? userName}) { + log('CachingManager::clearDetailedEmailCache:userName = $userName'); + if (accountId != null && userName != null) { + final emailKey = TupleKey(accountId.asString, userName.value).encodeKey; + return Future.wait([ + _newEmailHiveCacheClient.clearAllDataContainKey(emailKey), + _openedEmailHiveCacheClient.clearAllDataContainKey(emailKey), + clearAllFileInStorage(), + ]); + } else { + return Future.wait([ + _newEmailHiveCacheClient.clearAllData(), + _openedEmailHiveCacheClient.clearAllData(), + clearAllFileInStorage(), + ]); + } + } + + Future clearAllEmailAndStateCache({AccountId? accountId, UserName? userName}) { + log('CachingManager::clearAllEmailAndStateCache:userName = $userName'); return Future.wait([ - _stateCacheClient.clearAllData(), - _emailCacheClient.clearAllData(), - if (PlatformInfo.isMobile) clearAllFileInStorage(), - ], eagerError: true); + clearEmailAndStateCache(accountId: accountId, userName: userName), + if (PlatformInfo.isMobile) + clearDetailedEmailCache(accountId: accountId, userName: userName), + ]); } Future clearMailboxCache() { return Future.wait([ + _stateCacheClient.deleteItem(StateType.mailbox.getTupleKeyStoredWithoutAccount()), _mailboxCacheClient.clearAllData(), ], eagerError: true); } @@ -138,10 +176,22 @@ class CachingManager { } Future clearAllFileInStorage() async { - await Future.wait([ - _fileUtils.removeFolder(CachingConstants.newEmailsContentFolderName), - _fileUtils.removeFolder(CachingConstants.openedEmailContentFolderName), - ]); + await _fileUtils.removeFolder(CachingConstants.openedEmailContentFolderName); + await _fileUtils.removeFolder(CachingConstants.newEmailsContentFolderName); + } + + Future clearFCMEmailStateCache({AccountId? accountId, UserName? userName}) async { + if (accountId != null && userName != null) { + await _fcmCacheClient.deleteItem( + TypeName.emailDelivery.getTupleKeyStored(accountId, userName)); + await _fcmCacheClient.deleteItem( + TypeName.emailType.getTupleKeyStored(accountId, userName)); + } else { + await _fcmCacheClient.deleteItem( + TypeName.emailDelivery.getTupleKeyStoredWithoutAccount()); + await _fcmCacheClient.deleteItem( + TypeName.emailType.getTupleKeyStoredWithoutAccount()); + } } Future clearLoginRecentData() async { diff --git a/lib/features/caching/config/cache_version.dart b/lib/features/caching/config/cache_version.dart index 7b70a1be9..6bd859e26 100644 --- a/lib/features/caching/config/cache_version.dart +++ b/lib/features/caching/config/cache_version.dart @@ -1,4 +1,4 @@ class CacheVersion { - static const int hiveDBVersion = 12; + static const int hiveDBVersion = 14; } \ No newline at end of file diff --git a/lib/features/caching/config/hive_cache_config.dart b/lib/features/caching/config/hive_cache_config.dart index ea8df229a..8def8152e 100644 --- a/lib/features/caching/config/hive_cache_config.dart +++ b/lib/features/caching/config/hive_cache_config.dart @@ -10,6 +10,7 @@ import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_st import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v11.dart'; import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v12.dart'; import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v13.dart'; +import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v14.dart'; import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v7.dart'; import 'package:tmail_ui_user/features/caching/caching_manager.dart'; import 'package:tmail_ui_user/features/caching/config/cache_version.dart'; @@ -72,6 +73,7 @@ class HiveCacheConfig { await UpgradeHiveDatabaseStepsV11(cachingManager).onUpgrade(oldVersion, newVersion); await UpgradeHiveDatabaseStepsV12(cachingManager).onUpgrade(oldVersion, newVersion); await UpgradeHiveDatabaseStepsV13(cachingManager).onUpgrade(oldVersion, newVersion); + await UpgradeHiveDatabaseStepsV14(cachingManager).onUpgrade(oldVersion, newVersion); if (oldVersion != newVersion) { await cachingManager.storeCacheVersion(newVersion); diff --git a/lib/features/mailbox/data/model/state_type.dart b/lib/features/mailbox/data/model/state_type.dart index b7786c0d2..c182774df 100644 --- a/lib/features/mailbox/data/model/state_type.dart +++ b/lib/features/mailbox/data/model/state_type.dart @@ -20,4 +20,8 @@ enum StateType { String getTupleKeyStored(AccountId accountId, UserName userName) { return TupleKey(name, accountId.asString, userName.value).encodeKey; } + + String getTupleKeyStoredWithoutAccount() { + return TupleKey(name).encodeKey; + } } \ No newline at end of file diff --git a/lib/features/push_notification/presentation/extensions/type_name_extension.dart b/lib/features/push_notification/presentation/extensions/type_name_extension.dart new file mode 100644 index 000000000..4880d142a --- /dev/null +++ b/lib/features/push_notification/presentation/extensions/type_name_extension.dart @@ -0,0 +1,16 @@ + +import 'package:fcm/model/type_name.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:model/extensions/account_id_extensions.dart'; +import 'package:tmail_ui_user/features/caching/utils/cache_utils.dart'; + +extension TypeNameExtension on TypeName { + String getTupleKeyStored(AccountId accountId, UserName userName) { + return TupleKey(value, accountId.asString, userName.value).encodeKey; + } + + String getTupleKeyStoredWithoutAccount() { + return TupleKey(value).encodeKey; + } +} \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 04463a857..1f2d70951 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -457,11 +457,10 @@ class ThreadController extends BaseController with EmailActionController { void _handleErrorGetAllOrRefreshChangesEmail(Object error, StackTrace stackTrace) async { logError('ThreadController::_handleErrorGetAllOrRefreshChangesEmail():Error: $error'); if (error is CannotCalculateChangesMethodResponseException) { - if (_accountId != null && _session != null) { - await cachingManager.clearEmailCacheAndStateCacheByTupleKey(_accountId!, _session!); - } else { - await cachingManager.clearEmailCacheAndAllStateCache(); - } + await cachingManager.clearAllEmailAndStateCache( + accountId: _accountId, + userName: _session?.username, + ); _getAllEmailAction(); } else if (error is MethodLevelErrors) { if (currentOverlayContext != null && error.message != null) { diff --git a/test/features/email/presentation/controller/single_email_controller_test.dart b/test/features/email/presentation/controller/single_email_controller_test.dart index 73ea634e5..22d5c9efb 100644 --- a/test/features/email/presentation/controller/single_email_controller_test.dart +++ b/test/features/email/presentation/controller/single_email_controller_test.dart @@ -346,9 +346,9 @@ void main() { const eventDescription = '\nhttps://example1.com\nhttps://example2.com'; const expectedEventDescription = '' '
' - 'example1.com' + 'example1.com' '
' - 'example2.com' + 'example2.com' ''; final blobId = Id('abc123'); final calendarEvent = CalendarEvent( @@ -402,9 +402,9 @@ void main() { '\nhref xss'; const expectedEventDescription = '' '
' - 'example1.com' + 'example1.com' '
' - 'example2.com' + 'example2.com' '
' '
' 'href xss'