From 69199b488b7b1bf3ac0b1397ef508dd549b3d76c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Thu, 31 Aug 2023 13:21:44 +0200 Subject: [PATCH 1/3] feat: make sure refreshing folder action is not killed randomely in background --- Mail/Views/Menu Drawer/FolderListView.swift | 2 +- Mail/Views/SplitView.swift | 4 +-- .../MailboxManager+Folders.swift | 14 ++++++-- MailCore/Cache/RefreshActor.swift | 35 ++++++++++++++----- MailTests/MailboxManagerTests.swift | 2 +- 5 files changed, 41 insertions(+), 16 deletions(-) diff --git a/Mail/Views/Menu Drawer/FolderListView.swift b/Mail/Views/Menu Drawer/FolderListView.swift index 9d78eeda2..884e81c42 100644 --- a/Mail/Views/Menu Drawer/FolderListView.swift +++ b/Mail/Views/Menu Drawer/FolderListView.swift @@ -56,7 +56,7 @@ struct NestableFolder: Identifiable { } } -class FolderListViewModel: ObservableObject { +final class FolderListViewModel: ObservableObject { /// Special folders (eg. Inbox) for the current mailbox @Published var roleFolders = [NestableFolder]() /// User created folders for the current mailbox diff --git a/Mail/Views/SplitView.swift b/Mail/Views/SplitView.swift index 7b0cf745d..c3dc0498e 100644 --- a/Mail/Views/SplitView.swift +++ b/Mail/Views/SplitView.swift @@ -107,7 +107,7 @@ struct SplitView: View { .onChange(of: scenePhase) { newScenePhase in guard newScenePhase == .active else { return } Task { - async let _ = try? mailboxManager.folders() + async let _ = try? mailboxManager.refreshAllFolders() async let _ = try? mailboxManager.refreshAllSignatures() } } @@ -202,7 +202,7 @@ struct SplitView: View { private func fetchFolders() async { await tryOrDisplayError { - try await mailboxManager.folders() + try await mailboxManager.refreshAllFolders() } } diff --git a/MailCore/Cache/MailboxManager/MailboxManager+Folders.swift b/MailCore/Cache/MailboxManager/MailboxManager+Folders.swift index 915711c35..af31b0f3d 100644 --- a/MailCore/Cache/MailboxManager/MailboxManager+Folders.swift +++ b/MailCore/Cache/MailboxManager/MailboxManager+Folders.swift @@ -18,16 +18,21 @@ import Foundation import InfomaniakCore +import InfomaniakCoreUI import RealmSwift // MARK: - Folders public extension MailboxManager { - func folders() async throws { - // Get from Realm + /// Get all remote folders in DB + func refreshAllFolders() async throws { + let backgroundTracker = await ApplicationBackgroundTaskTracker(identifier: #function + UUID().uuidString) + + // Network check guard ReachabilityListener.instance.currentStatus != .offline else { return } + // Get from API let folderResult = try await apiFetcher.folders(mailbox: mailbox) let newFolders = getSubFolders(from: folderResult) @@ -37,6 +42,7 @@ public extension MailboxManager { self.keepCacheAttributes(for: folder, using: realm) } + // Get from Realm let cachedFolders = realm.objects(Folder.self) // Update folders in Realm @@ -61,6 +67,8 @@ public extension MailboxManager { realm.delete(toDeleteFolders) } } + + await backgroundTracker.end() } /// Get the folder with the corresponding role in Realm. @@ -107,7 +115,7 @@ public extension MailboxManager { } func refresh(folder: Folder) async { - await refreshActor.refresh(folder: folder) + await refreshActor.refreshFolderContent(folder) } func cancelRefresh() async { diff --git a/MailCore/Cache/RefreshActor.swift b/MailCore/Cache/RefreshActor.swift index ce515bcf5..234df396f 100644 --- a/MailCore/Cache/RefreshActor.swift +++ b/MailCore/Cache/RefreshActor.swift @@ -17,6 +17,7 @@ */ import Foundation +import InfomaniakCoreUI public actor RefreshActor { weak var mailboxManager: MailboxManager? @@ -29,25 +30,41 @@ public actor RefreshActor { public func flushFolder(folder: Folder, mailbox: Mailbox, apiFetcher: MailApiFetcher) async throws -> Bool { let response = try await apiFetcher.flushFolder(mailbox: mailbox, folderId: folder.id) - await refresh(folder: folder) + await refreshFolderContent(folder) return response } public func refreshFolder(from messages: [Message], additionalFolder: Folder?) async throws { - var folders = messages.map(\.folder) - if let additionalFolder { - folders.append(additionalFolder) + let updateFolders = Task { + var folders = messages.map(\.folder) + if let additionalFolder { + folders.append(additionalFolder) + } + + let orderedSet = NSOrderedSet(array: folders as [Any]) + + for folder in orderedSet { + guard !Task.isCancelled else { break } + guard let impactedFolder = folder as? Folder else { continue } + await refreshFolderContent(impactedFolder) + } } - let orderedSet = NSOrderedSet(array: folders as [Any]) + // Track progress in background with a cancelation handler + let backgroundTracker = await ApplicationBackgroundTaskTracker(identifier: #function + UUID().uuidString) { + updateFolders.cancel() - for folder in orderedSet { - guard let impactedFolder = folder as? Folder else { continue } - await refresh(folder: impactedFolder) + Task { + await self.cancelRefresh() + } } + + await updateFolders.finish() + + await backgroundTracker.end() } - public func refresh(folder: Folder) async { + public func refreshFolderContent(_ folder: Folder) async { await cancelRefresh() refreshTask = Task { diff --git a/MailTests/MailboxManagerTests.swift b/MailTests/MailboxManagerTests.swift index 7ed20a28e..43bedeaf6 100644 --- a/MailTests/MailboxManagerTests.swift +++ b/MailTests/MailboxManagerTests.swift @@ -44,7 +44,7 @@ final class MailboxManagerTests: XCTestCase { // MARK: Tests methods func testFolders() async throws { - try await MailboxManagerTests.mailboxManager.folders() + try await MailboxManagerTests.mailboxManager.refreshAllFolders() } func testThreads() async throws { From 9060eb76f6c66c19df5b574c44ce3196ad441339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Thu, 31 Aug 2023 13:52:18 +0200 Subject: [PATCH 2/3] refactor: more explicit function name --- Mail/Views/Thread List/ThreadListViewModel.swift | 2 +- MailCore/Cache/DraftManager.swift | 6 +++--- MailCore/Cache/MailboxManager/MailboxManager+Folders.swift | 2 +- MailNotificationServiceExtension/NotificationService.swift | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Mail/Views/Thread List/ThreadListViewModel.swift b/Mail/Views/Thread List/ThreadListViewModel.swift index 0cafd4344..93a0fcbf3 100644 --- a/Mail/Views/Thread List/ThreadListViewModel.swift +++ b/Mail/Views/Thread List/ThreadListViewModel.swift @@ -201,7 +201,7 @@ final class DateSection: Identifiable, Equatable { isLoadingPage = true } - await mailboxManager.refresh(folder: folder.freezeIfNeeded()) + await mailboxManager.refreshFolderContent(folder.freezeIfNeeded()) withAnimation { isLoadingPage = false diff --git a/MailCore/Cache/DraftManager.swift b/MailCore/Cache/DraftManager.swift index 520d048c1..2a6d6bc14 100644 --- a/MailCore/Cache/DraftManager.swift +++ b/MailCore/Cache/DraftManager.swift @@ -255,7 +255,7 @@ public final class DraftManager { private func refreshDraftFolder(latestSendDate: Date?, mailboxManager: MailboxManager) async throws { if let draftFolder = mailboxManager.getFolder(with: .draft)?.freeze() { - await mailboxManager.refresh(folder: draftFolder) + await mailboxManager.refreshFolderContent(draftFolder) if let latestSendDate { /* @@ -264,7 +264,7 @@ public final class DraftManager { */ let delay = latestSendDate.timeIntervalSinceNow try await Task.sleep(nanoseconds: UInt64(1_000_000_000 * max(Double(delay), 1.5))) - await mailboxManager.refresh(folder: draftFolder) + await mailboxManager.refreshFolderContent(draftFolder) } await mailboxManager.deleteOrphanDrafts() @@ -278,7 +278,7 @@ public final class DraftManager { try await mailboxManager.delete(draft: liveDraft.freeze()) alertDisplayable.show(message: MailResourcesStrings.Localizable.snackbarDraftDeleted) if let draftFolder = mailboxManager.getFolder(with: .draft)?.freeze() { - await mailboxManager.refresh(folder: draftFolder) + await mailboxManager.refreshFolderContent(draftFolder) } } } diff --git a/MailCore/Cache/MailboxManager/MailboxManager+Folders.swift b/MailCore/Cache/MailboxManager/MailboxManager+Folders.swift index af31b0f3d..9fffb4860 100644 --- a/MailCore/Cache/MailboxManager/MailboxManager+Folders.swift +++ b/MailCore/Cache/MailboxManager/MailboxManager+Folders.swift @@ -114,7 +114,7 @@ public extension MailboxManager { try await refreshActor.refreshFolder(from: messages, additionalFolder: additionalFolder) } - func refresh(folder: Folder) async { + func refreshFolderContent(_ folder: Folder) async { await refreshActor.refreshFolderContent(folder) } diff --git a/MailNotificationServiceExtension/NotificationService.swift b/MailNotificationServiceExtension/NotificationService.swift index 9f5620645..575ed8016 100644 --- a/MailNotificationServiceExtension/NotificationService.swift +++ b/MailNotificationServiceExtension/NotificationService.swift @@ -47,7 +47,7 @@ final class NotificationService: UNNotificationServiceExtension { // We do nothing if we don't have an initial cursor return nil } - await mailboxManager.refresh(folder: inboxFolder.freezeIfNeeded()) + await mailboxManager.refreshFolderContent(inboxFolder.freezeIfNeeded()) @ThreadSafe var message = mailboxManager.getRealm().object(ofType: Message.self, forPrimaryKey: uid) From bd89a849b17a2a56b05df9b787345d061453fbc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Thu, 31 Aug 2023 14:45:51 +0200 Subject: [PATCH 3/3] refactor(Folders): Save fresh folders after we performed the cleanup to ensure none could be missing --- MailCore/Cache/MailboxManager/MailboxManager+Folders.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MailCore/Cache/MailboxManager/MailboxManager+Folders.swift b/MailCore/Cache/MailboxManager/MailboxManager+Folders.swift index 9fffb4860..dc6bf86e9 100644 --- a/MailCore/Cache/MailboxManager/MailboxManager+Folders.swift +++ b/MailCore/Cache/MailboxManager/MailboxManager+Folders.swift @@ -48,7 +48,6 @@ public extension MailboxManager { // Update folders in Realm try? realm.safeWrite { // Remove old folders - realm.add(folderResult, update: .modified) let toDeleteFolders = Set(cachedFolders).subtracting(Set(newFolders)).filter { $0.id != Constants.searchFolderId } var toDeleteThreads = [Thread]() @@ -65,6 +64,9 @@ public extension MailboxManager { realm.delete(toDeleteMessages) realm.delete(toDeleteThreads) realm.delete(toDeleteFolders) + + // Insert fresh remote folders + realm.add(folderResult, update: .modified) } }