diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift index b32e3153d..dad15cb70 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift @@ -21,7 +21,7 @@ import Secrets enum CloudKitAccountDelegateError: LocalizedError { case invalidParameter case unknown - + var errorDescription: String? { return NSLocalizedString("An unexpected CloudKit error occurred.", comment: "An unexpected CloudKit error occurred.") } @@ -32,42 +32,49 @@ final class CloudKitAccountDelegate: AccountDelegate { private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit") private let database: SyncDatabase - + private let container: CKContainer = { let orgID = Bundle.main.object(forInfoDictionaryKey: "OrganizationIdentifier") as! String return CKContainer(identifier: "iCloud.\(orgID).NetNewsWire") }() - + private let accountZone: CloudKitAccountZone private let articlesZone: CloudKitArticlesZone - - private let mainThreadOperationQueue = MainThreadOperationQueue() - private lazy var refresher: LocalAccountRefresher = { - let refresher = LocalAccountRefresher() - refresher.delegate = self - return refresher - }() + private let mainThreadOperationQueue = MainThreadOperationQueue() + private let refresher: LocalAccountRefresher weak var account: Account? - + let behaviors: AccountBehaviors = [] let isOPMLImportInProgress = false - + let server: String? = nil var credentials: Credentials? var accountMetadata: AccountMetadata? - var refreshProgress = DownloadProgress(numberOfTasks: 0) - - init(dataFolder: String) { - accountZone = CloudKitAccountZone(container: container) - articlesZone = CloudKitArticlesZone(container: container) - - let databaseFilePath = (dataFolder as NSString).appendingPathComponent("Sync.sqlite3") - database = SyncDatabase(databaseFilePath: databaseFilePath) + /// refreshProgress is combined sync progress and feed download progress. + let refreshProgress = DownloadProgress(numberOfTasks: 0) + private let syncProgress = DownloadProgress(numberOfTasks: 0) + private var downloadProgress: DownloadProgress { + refresher.downloadProgress } - + + init(dataFolder: String) { + + self.accountZone = CloudKitAccountZone(container: container) + self.articlesZone = CloudKitArticlesZone(container: container) + + let databaseFilePath = (dataFolder as NSString).appendingPathComponent("Sync.sqlite3") + self.database = SyncDatabase(databaseFilePath: databaseFilePath) + + self.refresher = LocalAccountRefresher() + self.refresher.delegate = self + + NotificationCenter.default.addObserver(self, selector: #selector(downloadProgressDidChange(_:)), name: .DownloadProgressDidChange, object: downloadProgress) + NotificationCenter.default.addObserver(self, selector: #selector(syncProgressDidChange(_:)), name: .DownloadProgressDidChange, object: syncProgress) + } + func receiveRemoteNotification(for account: Account, userInfo: [AnyHashable : Any], completion: @escaping () -> Void) { let op = CloudKitRemoteNotificationOperation(accountZone: accountZone, articlesZone: articlesZone, userInfo: userInfo) op.completionBlock = { mainThreadOperaion in @@ -75,8 +82,9 @@ final class CloudKitAccountDelegate: AccountDelegate { } mainThreadOperationQueue.add(op) } - + func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { + guard refreshProgress.isComplete else { completion(.success(())) return @@ -88,7 +96,7 @@ final class CloudKitAccountDelegate: AccountDelegate { completion(.success(())) return } - + standardRefreshAll(for: account, completion: completion) } @@ -109,11 +117,11 @@ final class CloudKitAccountDelegate: AccountDelegate { } } } - + func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { sendArticleStatus(for: account, showProgress: false, completion: completion) } - + func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { let op = CloudKitReceiveStatusOperation(articlesZone: articlesZone) op.completionBlock = { mainThreadOperaion in @@ -125,37 +133,38 @@ final class CloudKitAccountDelegate: AccountDelegate { } mainThreadOperationQueue.add(op) } - + func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) { + guard refreshProgress.isComplete else { completion(.success(())) return } var fileData: Data? - + do { fileData = try Data(contentsOf: opmlFile) } catch { completion(.failure(error)) return } - + guard let opmlData = fileData else { completion(.success(())) return } - + let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData) var opmlDocument: RSOPMLDocument? - + do { opmlDocument = try RSOPMLParser.parseOPML(with: parserData) } catch { completion(.failure(error)) return } - + guard let loadDocument = opmlDocument else { completion(.success(())) return @@ -167,20 +176,19 @@ final class CloudKitAccountDelegate: AccountDelegate { let normalizedItems = OPMLNormalizer.normalize(opmlItems) - refreshProgress.addToNumberOfTasksAndRemaining(1) + syncProgress.addToNumberOfTasksAndRemaining(1) self.accountZone.importOPML(rootExternalID: rootExternalID, items: normalizedItems) { _ in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() self.standardRefreshAll(for: account, completion: completion) } - } - + func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { guard let url = URL(string: urlString) else { completion(.failure(LocalAccountDelegateError.invalidParameter)) return } - + let editedName = name == nil || name!.isEmpty ? nil : name createRSSWebFeed(for: account, url: url, editedName: editedName, container: container, validateFeed: validateFeed, completion: completion) @@ -188,9 +196,9 @@ final class CloudKitAccountDelegate: AccountDelegate { func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { let editedName = name.isEmpty ? nil : name - refreshProgress.addToNumberOfTasksAndRemaining(1) + syncProgress.addToNumberOfTasksAndRemaining(1) accountZone.renameWebFeed(feed, editedName: editedName) { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() switch result { case .success: feed.editedName = name @@ -221,11 +229,11 @@ final class CloudKitAccountDelegate: AccountDelegate { } } } - + func moveWebFeed(for account: Account, with feed: WebFeed, from fromContainer: Container, to toContainer: Container, completion: @escaping (Result) -> Void) { - refreshProgress.addToNumberOfTasksAndRemaining(1) + syncProgress.addToNumberOfTasksAndRemaining(1) accountZone.moveWebFeed(feed, from: fromContainer, to: toContainer) { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() switch result { case .success: fromContainer.removeWebFeed(feed) @@ -237,11 +245,11 @@ final class CloudKitAccountDelegate: AccountDelegate { } } } - + func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result) -> Void) { - refreshProgress.addToNumberOfTasksAndRemaining(1) + syncProgress.addToNumberOfTasksAndRemaining(1) accountZone.addWebFeed(feed, to: container) { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() switch result { case .success: container.addWebFeed(feed) @@ -252,7 +260,7 @@ final class CloudKitAccountDelegate: AccountDelegate { } } } - + func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) { createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in switch result { @@ -263,11 +271,11 @@ final class CloudKitAccountDelegate: AccountDelegate { } } } - + func createFolder(for account: Account, name: String, completion: @escaping (Result) -> Void) { - refreshProgress.addToNumberOfTasksAndRemaining(1) + syncProgress.addToNumberOfTasksAndRemaining(1) accountZone.createFolder(name: name) { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() switch result { case .success(let externalID): if let folder = account.ensureFolder(with: name) { @@ -282,11 +290,11 @@ final class CloudKitAccountDelegate: AccountDelegate { } } } - + func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) { - refreshProgress.addToNumberOfTasksAndRemaining(1) + syncProgress.addToNumberOfTasksAndRemaining(1) accountZone.renameFolder(folder, to: name) { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() switch result { case .success: folder.name = name @@ -297,19 +305,19 @@ final class CloudKitAccountDelegate: AccountDelegate { } } } - + func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) { - refreshProgress.addToNumberOfTasksAndRemaining(2) + syncProgress.addToNumberOfTasksAndRemaining(2) accountZone.findWebFeedExternalIDs(for: folder) { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() switch result { case .success(let webFeedExternalIDs): let webFeeds = webFeedExternalIDs.compactMap { account.existingWebFeed(withExternalID: $0) } let group = DispatchGroup() var errorOccurred = false - + for webFeed in webFeeds { group.enter() self.removeWebFeedFromCloud(for: account, with: webFeed, from: folder) { result in @@ -320,17 +328,17 @@ final class CloudKitAccountDelegate: AccountDelegate { } } } - + group.notify(queue: DispatchQueue.global(qos: .background)) { DispatchQueue.main.async { guard !errorOccurred else { - self.refreshProgress.completeTask() + self.syncProgress.completeTask() completion(.failure(CloudKitAccountDelegateError.unknown)) return } - + self.accountZone.removeFolder(folder) { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() switch result { case .success: account.removeFolder(folder) @@ -341,41 +349,41 @@ final class CloudKitAccountDelegate: AccountDelegate { } } } - + case .failure(let error): - self.refreshProgress.completeTask() - self.refreshProgress.completeTask() + self.syncProgress.completeTask() + self.syncProgress.completeTask() self.processAccountError(account, error) completion(.failure(error)) } } - + } - + func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result) -> Void) { guard let name = folder.name else { completion(.failure(LocalAccountDelegateError.invalidParameter)) return } - + let feedsToRestore = folder.topLevelWebFeeds - refreshProgress.addToNumberOfTasksAndRemaining(1 + feedsToRestore.count) - + syncProgress.addToNumberOfTasksAndRemaining(1 + feedsToRestore.count) + accountZone.createFolder(name: name) { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() switch result { case .success(let externalID): folder.externalID = externalID account.addFolder(folder) - + let group = DispatchGroup() for feed in feedsToRestore { - + folder.topLevelWebFeeds.remove(feed) group.enter() self.restoreWebFeed(for: account, feed: feed, container: folder) { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() group.leave() switch result { case .success: @@ -384,14 +392,14 @@ final class CloudKitAccountDelegate: AccountDelegate { os_log(.error, log: self.log, "Restore folder feed error: %@.", error.localizedDescription) } } - + } - + group.notify(queue: DispatchQueue.main) { account.addFolder(folder) completion(.success(())) } - + case .failure(let error): self.processAccountError(account, error) completion(.failure(error)) @@ -423,12 +431,12 @@ final class CloudKitAccountDelegate: AccountDelegate { func accountDidInitialize(_ account: Account) { self.account = account - - accountZone.delegate = CloudKitAcountZoneDelegate(account: account, refreshProgress: refreshProgress, articlesZone: articlesZone) + + accountZone.delegate = CloudKitAcountZoneDelegate(account: account, articlesZone: articlesZone) articlesZone.delegate = CloudKitArticlesZoneDelegate(account: account, database: database, articlesZone: articlesZone) - + database.resetAllSelectedForProcessing() - + // Check to see if this is a new account and initialize anything we need if account.externalID == nil { accountZone.findOrCreateAccount() { result in @@ -443,9 +451,9 @@ final class CloudKitAccountDelegate: AccountDelegate { accountZone.subscribeToZoneChanges() articlesZone.subscribeToZoneChanges() } - + } - + func accountWillBeDeleted(_ account: Account) { accountZone.resetChangeToken() articlesZone.resetChangeToken() @@ -455,7 +463,7 @@ final class CloudKitAccountDelegate: AccountDelegate { return completion(.success(nil)) } - // MARK: Suspend and Resume (for iOS) + // MARK: - Suspend and Resume (for iOS) func suspendNetwork() { refresher.suspend() @@ -464,45 +472,68 @@ final class CloudKitAccountDelegate: AccountDelegate { func suspendDatabase() { database.suspend() } - + func resume() { refresher.resume() database.resume() } } +// MARK: - Refresh Progress + private extension CloudKitAccountDelegate { - + + func updateRefreshProgress() { + + refreshProgress.numberOfTasks = downloadProgress.numberOfTasks + syncProgress.numberOfTasks + refreshProgress.numberRemaining = downloadProgress.numberRemaining + syncProgress.numberRemaining + + // Complete? + if refreshProgress.numberOfTasks > 0 && refreshProgress.numberRemaining < 1 { + downloadProgress.numberOfTasks = 0 + syncProgress.numberOfTasks = 0 + } + } + + @objc func downloadProgressDidChange(_ note: Notification) { + + updateRefreshProgress() + } + + @objc func syncProgressDidChange(_ note: Notification) { + + updateRefreshProgress() + } +} + +// MARK: - Private + +private extension CloudKitAccountDelegate { + func initialRefreshAll(for account: Account, completion: @escaping (Result) -> Void) { - + func fail(_ error: Error) { self.processAccountError(account, error) - self.refreshProgress.clear() + self.syncProgress.clear() completion(.failure(error)) } - - refreshProgress.addToNumberOfTasksAndRemaining(3) + + syncProgress.addToNumberOfTasksAndRemaining(3) accountZone.fetchChangesInZone() { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() let webFeeds = account.flattenedWebFeeds() - self.refreshProgress.addToNumberOfTasksAndRemaining(webFeeds.count) switch result { case .success: self.refreshArticleStatus(for: account) { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() switch result { case .success: - - self.combinedRefresh(account, webFeeds) { result in - self.refreshProgress.clear() - switch result { - case .success: - account.metadata.lastArticleFetchEndTime = Date() - case .failure(let error): - fail(error) - } + + self.combinedRefresh(account, webFeeds) { + self.syncProgress.clear() + account.metadata.lastArticleFetchEndTime = Date() } case .failure(let error): @@ -513,41 +544,34 @@ private extension CloudKitAccountDelegate { fail(error) } } - } func standardRefreshAll(for account: Account, completion: @escaping (Result) -> Void) { - let intialWebFeedsCount = account.flattenedWebFeeds().count - refreshProgress.addToNumberOfTasksAndRemaining(3 + intialWebFeedsCount) + syncProgress.addToNumberOfTasksAndRemaining(3) func fail(_ error: Error) { self.processAccountError(account, error) - self.refreshProgress.clear() + self.syncProgress.clear() completion(.failure(error)) } - + accountZone.fetchChangesInZone() { result in switch result { case .success: - self.refreshProgress.completeTask() + self.syncProgress.completeTask() let webFeeds = account.flattenedWebFeeds() - self.refreshProgress.addToNumberOfTasksAndRemaining(webFeeds.count - intialWebFeedsCount) - + self.refreshArticleStatus(for: account) { result in switch result { case .success: - self.refreshProgress.completeTask() - self.combinedRefresh(account, webFeeds) { result in + self.syncProgress.completeTask() + self.combinedRefresh(account, webFeeds) { self.sendArticleStatus(for: account, showProgress: true) { _ in - self.refreshProgress.clear() - if case .failure(let error) = result { - fail(error) - } else { - account.metadata.lastArticleFetchEndTime = Date() - completion(.success(())) - } + self.syncProgress.clear() + account.metadata.lastArticleFetchEndTime = Date() + completion(.success(())) } } case .failure(let error): @@ -559,23 +583,13 @@ private extension CloudKitAccountDelegate { fail(error) } } - } - func combinedRefresh(_ account: Account, _ webFeeds: Set, completion: @escaping (Result) -> Void) { - - let group = DispatchGroup() + func combinedRefresh(_ account: Account, _ webFeeds: Set, completion: @escaping () -> Void) { - group.enter() - refresher.refreshFeeds(webFeeds) { - group.leave() - } - - group.notify(queue: DispatchQueue.main) { - completion(.success(())) - } + refresher.refreshFeeds(webFeeds, completion: completion) } - + func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { func addDeadFeed() { @@ -588,7 +602,7 @@ private extension CloudKitAccountDelegate { homePageURL: nil, container: container) { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() switch result { case .success(let externalID): feed.externalID = externalID @@ -600,48 +614,48 @@ private extension CloudKitAccountDelegate { } } - refreshProgress.addToNumberOfTasksAndRemaining(5) + syncProgress.addToNumberOfTasksAndRemaining(5) FeedFinder.find(url: url) { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() switch result { case .success(let feedSpecifiers): guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers), let url = URL(string: bestFeedSpecifier.urlString) else { - self.refreshProgress.completeTasks(3) + self.syncProgress.completeTasks(3) if validateFeed { - self.refreshProgress.completeTask() + self.syncProgress.completeTask() completion(.failure(AccountError.createErrorNotFound)) } else { addDeadFeed() } return } - + if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) { - self.refreshProgress.completeTasks(4) + self.syncProgress.completeTasks(4) completion(.failure(AccountError.createErrorAlreadySubscribed)) return } - + let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) feed.editedName = editedName container.addWebFeed(feed) InitialFeedDownloader.download(url) { parsedFeed in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() if let parsedFeed = parsedFeed { account.update(feed, with: parsedFeed) { result in switch result { case .success: - + self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString, name: parsedFeed.title, editedName: editedName, homePageURL: parsedFeed.homePageURL, container: container) { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() switch result { case .success(let externalID): feed.externalID = externalID @@ -649,31 +663,30 @@ private extension CloudKitAccountDelegate { completion(.success(feed)) case .failure(let error): container.removeWebFeed(feed) - self.refreshProgress.completeTasks(2) + self.syncProgress.completeTasks(2) completion(.failure(error)) } - } case .failure(let error): container.removeWebFeed(feed) - self.refreshProgress.completeTasks(3) + self.syncProgress.completeTasks(3) completion(.failure(error)) } - + } } else { - self.refreshProgress.completeTasks(3) + self.syncProgress.completeTasks(3) container.removeWebFeed(feed) completion(.failure(AccountError.createErrorNotFound)) } - + } - + case .failure: - self.refreshProgress.completeTasks(3) + self.syncProgress.completeTasks(3) if validateFeed { - self.refreshProgress.completeTask() + self.syncProgress.completeTask() completion(.failure(AccountError.createErrorNotFound)) return } else { @@ -688,7 +701,7 @@ private extension CloudKitAccountDelegate { switch result { case .success(let articles): self.storeArticleChanges(new: articles, updated: Set
(), deleted: Set
()) { - self.refreshProgress.completeTask() + self.syncProgress.completeTask() self.sendArticleStatus(for: account, showProgress: true) { result in switch result { case .success: @@ -703,7 +716,7 @@ private extension CloudKitAccountDelegate { } } } - + func processAccountError(_ account: Account, _ error: Error) { if case CloudKitZoneError.userDeletedZone = error { account.removeFeeds(account.topLevelWebFeeds) @@ -712,7 +725,7 @@ private extension CloudKitAccountDelegate { } } } - + func storeArticleChanges(new: Set
?, updated: Set
?, deleted: Set
?, completion: (() -> Void)?) { // New records with a read status aren't really new, they just didn't have the read article stored let group = DispatchGroup() @@ -728,19 +741,19 @@ private extension CloudKitAccountDelegate { insertSyncStatuses(articles: updated, statusKey: .new, flag: false) { group.leave() } - + group.enter() insertSyncStatuses(articles: deleted, statusKey: .deleted, flag: true) { group.leave() } - + group.notify(queue: DispatchQueue.global(qos: .userInitiated)) { DispatchQueue.main.async { completion?() } } } - + func insertSyncStatuses(articles: Set
?, statusKey: SyncStatus.Key, flag: Bool, completion: @escaping () -> Void) { guard let articles = articles, !articles.isEmpty else { completion() @@ -769,12 +782,12 @@ private extension CloudKitAccountDelegate { } mainThreadOperationQueue.add(op) } - + func removeWebFeedFromCloud(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) { - refreshProgress.addToNumberOfTasksAndRemaining(2) + syncProgress.addToNumberOfTasksAndRemaining(2) accountZone.removeWebFeed(feed, from: container) { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() switch result { case .success: guard let webFeedExternalID = feed.externalID else { @@ -782,25 +795,21 @@ private extension CloudKitAccountDelegate { return } self.articlesZone.deleteArticles(webFeedExternalID) { result in - self.refreshProgress.completeTask() + self.syncProgress.completeTask() completion(result) } case .failure(let error): - self.refreshProgress.completeTask() + self.syncProgress.completeTask() self.processAccountError(account, error) completion(.failure(error)) } } } - + } extension CloudKitAccountDelegate: LocalAccountRefresherDelegate { - func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: URL) { - refreshProgress.completeTask() - } - func localAccountRefresher(_ refresher: LocalAccountRefresher, articleChanges: ArticleChanges) { self.storeArticleChanges(new: articleChanges.newArticles, updated: articleChanges.updatedArticles, diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountZoneDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitAccountZoneDelegate.swift index 4f859f8a7..fe9a0ca09 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountZoneDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountZoneDelegate.swift @@ -22,12 +22,10 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate { private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit") weak var account: Account? - weak var refreshProgress: DownloadProgress? weak var articlesZone: CloudKitArticlesZone? - init(account: Account, refreshProgress: DownloadProgress, articlesZone: CloudKitArticlesZone) { + init(account: Account, articlesZone: CloudKitArticlesZone) { self.account = account - self.refreshProgress = refreshProgress self.articlesZone = articlesZone } diff --git a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift index dc440b4e4..b284fd8f1 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift @@ -216,9 +216,6 @@ final class LocalAccountDelegate: AccountDelegate { extension LocalAccountDelegate: LocalAccountRefresherDelegate { - func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: URL) { - } - func localAccountRefresher(_ refresher: LocalAccountRefresher, articleChanges: ArticleChanges) { } } diff --git a/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift b/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift index 313035c76..467305b00 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift @@ -15,7 +15,6 @@ import ArticlesDatabase import os protocol LocalAccountRefresherDelegate { - func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: URL) func localAccountRefresher(_ refresher: LocalAccountRefresher, articleChanges: ArticleChanges) } @@ -36,8 +35,11 @@ final class LocalAccountRefresher { private var urlToFeedDictionary = [String: WebFeed]() public func refreshFeeds(_ feeds: Set, completion: (() -> Void)? = nil) { + guard !feeds.isEmpty else { - completion?() + Task { @MainActor in + completion?() + } return } @@ -70,10 +72,6 @@ extension LocalAccountRefresher: DownloadSessionDelegate { func downloadSession(_ downloadSession: DownloadSession, downloadDidComplete url: URL, response: URLResponse?, data: Data, error: NSError?) { - defer { - delegate?.localAccountRefresher(self, requestCompletedFor: url) - } - guard let feed = urlToFeedDictionary[url.absoluteString] else { return } @@ -116,8 +114,11 @@ extension LocalAccountRefresher: DownloadSessionDelegate { } func downloadSessionDidComplete(_ downloadSession: DownloadSession) { - completion?() - completion = nil + + Task { @MainActor in + completion?() + completion = nil + } } } diff --git a/RSWeb/Sources/RSWeb/DownloadSession.swift b/RSWeb/Sources/RSWeb/DownloadSession.swift index 74dd873e3..d12e7305c 100755 --- a/RSWeb/Sources/RSWeb/DownloadSession.swift +++ b/RSWeb/Sources/RSWeb/DownloadSession.swift @@ -270,7 +270,6 @@ private extension DownloadSession { if downloadProgress.numberOfTasks > 0 && downloadProgress.numberRemaining < 1 { delegate.downloadSessionDidComplete(self) urlsInSession.removeAll() - downloadProgress.numberOfTasks = 0 } }