diff --git a/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift b/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift index 54da8c74e..88c788388 100644 --- a/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift @@ -212,72 +212,20 @@ final class CloudKitAccountDelegate: AccountDelegate { } func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { - let editedName = name == nil || name!.isEmpty ? nil : name - - guard let url = URL(string: urlString) else { + guard let url = URL(string: urlString), let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { completion(.failure(LocalAccountDelegateError.invalidParameter)) return } - BatchUpdate.shared.start() - refreshProgress.addToNumberOfTasksAndRemaining(3) - FeedFinder.find(url: url) { result in - - self.refreshProgress.completeTask() - switch result { - case .success(let feedSpecifiers): - guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers), let url = URL(string: bestFeedSpecifier.urlString) else { - BatchUpdate.shared.end() - self.refreshProgress.clear() - completion(.failure(AccountError.createErrorNotFound)) - return - } - - if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) { - BatchUpdate.shared.end() - self.refreshProgress.clear() - completion(.failure(AccountError.createErrorAlreadySubscribed)) - return - } - - self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString, editedName: editedName, container: container) { result in + let editedName = name == nil || name!.isEmpty ? nil : name - self.refreshProgress.completeTask() - switch result { - case .success(let externalID): - - let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) - feed.editedName = editedName - feed.externalID = externalID - container.addWebFeed(feed) - - InitialFeedDownloader.download(url) { parsedFeed in - self.refreshProgress.completeTask() - - if let parsedFeed = parsedFeed { - account.update(feed, with: parsedFeed, {_ in - BatchUpdate.shared.end() - completion(.success(feed)) - }) - } - - } - - case .failure(let error): - BatchUpdate.shared.end() - self.refreshProgress.clear() - completion(.failure(error)) - } - } - - case .failure: - BatchUpdate.shared.end() - self.refreshProgress.clear() - completion(.failure(AccountError.createErrorNotFound)) - } - + // Username should be part of the URL on new feed adds + if let feedProvider = FeedProviderManager.shared.best(for: urlComponents, with: nil) { + createProviderWebFeed(for: account, urlComponents: urlComponents, editedName: editedName, container: container, feedProvider: feedProvider, completion: completion) + } else { + createRSSWebFeed(for: account, url: url, editedName: editedName, container: container, completion: completion) } - + } func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { @@ -594,7 +542,7 @@ private extension CloudKitAccountDelegate { self.refreshProgress.completeTask() - self.refresher.refreshFeeds(webFeeds) { + self.refreshWebFeeds(account, webFeeds) { account.metadata.lastArticleFetchEndTime = Date() } @@ -615,6 +563,158 @@ private extension CloudKitAccountDelegate { } } + func refreshWebFeeds(_ account: Account, _ webFeeds: Set, completion: @escaping () -> Void) { + var refresherWebFeeds = Set() + let group = DispatchGroup() + + for webFeed in webFeeds { + if let components = URLComponents(string: webFeed.url), let feedProvider = FeedProviderManager.shared.best(for: components, with: webFeed.username) { + refreshProgress.addToNumberOfTasksAndRemaining(1) + group.enter() + feedProvider.refresh(webFeed) { result in + switch result { + case .success(let parsedItems): + account.update(webFeed.webFeedID, with: parsedItems) { _ in + self.refreshProgress.completeTask() + group.leave() + } + case .failure(let error): + os_log(.error, log: self.log, "Feed Provider refresh error: %@.", error.localizedDescription) + self.refreshProgress.completeTask() + group.leave() + } + } + } else { + refresherWebFeeds.insert(webFeed) + } + } + + refreshProgress.addToNumberOfTasksAndRemaining(refresherWebFeeds.count) + group.enter() + refresher.refreshFeeds(refresherWebFeeds) { + group.leave() + } + + group.notify(queue: DispatchQueue.main) { + completion() + } + } + + func createProviderWebFeed(for account: Account, urlComponents: URLComponents, editedName: String?, container: Container, feedProvider: FeedProvider, completion: @escaping (Result) -> Void) { + refreshProgress.addToNumberOfTasksAndRemaining(3) + + feedProvider.assignName(urlComponents) { result in + self.refreshProgress.completeTask() + switch result { + + case .success(let name): + + // Move the user to the WebFeed and out of the URL + var newURLComponents = urlComponents + newURLComponents.user = nil + guard let newURLString = newURLComponents.url?.absoluteString else { + completion(.failure(AccountError.createErrorNotFound)) + return + } + + self.accountZone.createWebFeed(url: newURLString, editedName: editedName, container: container) { result in + + self.refreshProgress.completeTask() + switch result { + case .success(let externalID): + + let feed = account.createWebFeed(with: name, url: newURLString, webFeedID: newURLString, homePageURL: nil) + feed.editedName = editedName + feed.username = urlComponents.user + feed.externalID = externalID + container.addWebFeed(feed) + + feedProvider.refresh(feed) { result in + self.refreshProgress.completeTask() + switch result { + case .success(let parsedItems): + account.update(newURLString, with: parsedItems) { _ in + completion(.success(feed)) + } + case .failure: + completion(.failure(AccountError.createErrorNotFound)) + } + } + + case .failure(let error): + self.refreshProgress.clear() + completion(.failure(error)) + } + } + + case .failure(let error): + self.refreshProgress.clear() + completion(.failure(error)) + } + } + } + + func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result) -> Void) { + BatchUpdate.shared.start() + refreshProgress.addToNumberOfTasksAndRemaining(3) + FeedFinder.find(url: url) { result in + + self.refreshProgress.completeTask() + switch result { + case .success(let feedSpecifiers): + guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers), let url = URL(string: bestFeedSpecifier.urlString) else { + BatchUpdate.shared.end() + self.refreshProgress.clear() + completion(.failure(AccountError.createErrorNotFound)) + return + } + + if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) { + BatchUpdate.shared.end() + self.refreshProgress.clear() + completion(.failure(AccountError.createErrorAlreadySubscribed)) + return + } + + self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString, editedName: editedName, container: container) { result in + + self.refreshProgress.completeTask() + switch result { + case .success(let externalID): + + let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) + feed.editedName = editedName + feed.externalID = externalID + container.addWebFeed(feed) + + InitialFeedDownloader.download(url) { parsedFeed in + self.refreshProgress.completeTask() + + if let parsedFeed = parsedFeed { + account.update(feed, with: parsedFeed, {_ in + BatchUpdate.shared.end() + completion(.success(feed)) + }) + } + + } + + case .failure(let error): + BatchUpdate.shared.end() + self.refreshProgress.clear() + completion(.failure(error)) + } + } + + case .failure: + BatchUpdate.shared.end() + self.refreshProgress.clear() + completion(.failure(AccountError.createErrorNotFound)) + } + + } + } + func processAccountError(_ account: Account, _ error: Error) { if case CloudKitZoneError.userDeletedZone = error { account.removeFeeds(account.topLevelWebFeeds) diff --git a/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift b/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift index 8415bd5d0..a4361e44c 100644 --- a/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift +++ b/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift @@ -180,28 +180,61 @@ private extension CloudKitAcountZoneDelegate { } func createWebFeedIfNecessary(url: URL, editedName: String?, webFeedExternalID: String, container: Container, completion: @escaping (WebFeed) -> Void) { - guard let account = account else { return } + guard let account = account, let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return } if let webFeed = account.existingWebFeed(withExternalID: webFeedExternalID) { completion(webFeed) return } - let webFeed = account.createWebFeed(with: editedName, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) + let webFeed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) webFeed.editedName = editedName webFeed.externalID = webFeedExternalID - container.addWebFeed(webFeed) - refreshProgress?.addToNumberOfTasksAndRemaining(1) - InitialFeedDownloader.download(url) { parsedFeed in - self.refreshProgress?.completeTask() - if let parsedFeed = parsedFeed { - account.update(webFeed, with: parsedFeed, { _ in + if let feedProvider = FeedProviderManager.shared.best(for: urlComponents, with: nil) { + + refreshProgress?.addToNumberOfTasksAndRemaining(2) + feedProvider.assignName(urlComponents) { result in + self.refreshProgress?.completeTask() + switch result { + case .success(let name): + + webFeed.name = name + container.addWebFeed(webFeed) + + feedProvider.refresh(webFeed) { result in + self.refreshProgress?.completeTask() + switch result { + case .success(let parsedItems): + account.update(url.absoluteString, with: parsedItems) { _ in + completion(webFeed) + } + case .failure: + completion(webFeed) + } + } + + case .failure: completion(webFeed) - }) - } else { - completion(webFeed) + } } + + } else { + + container.addWebFeed(webFeed) + + refreshProgress?.addToNumberOfTasksAndRemaining(1) + InitialFeedDownloader.download(url) { parsedFeed in + self.refreshProgress?.completeTask() + if let parsedFeed = parsedFeed { + account.update(webFeed, with: parsedFeed, { _ in + completion(webFeed) + }) + } else { + completion(webFeed) + } + } + } } diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index c88fa7685..9a12c4801 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -84,6 +84,7 @@ final class LocalAccountDelegate: AccountDelegate { } group.notify(queue: DispatchQueue.main) { + account.metadata.lastArticleFetchEndTime = Date() completion(.success(())) } @@ -147,9 +148,9 @@ final class LocalAccountDelegate: AccountDelegate { // Username should be part of the URL on new feed adds if let feedProvider = FeedProviderManager.shared.best(for: urlComponents, with: nil) { - createProviderWebFeed(for: account, urlComponents: urlComponents, name: name, container: container, feedProvider: feedProvider, completion: completion) + createProviderWebFeed(for: account, urlComponents: urlComponents, editedName: name, container: container, feedProvider: feedProvider, completion: completion) } else { - createRSSWebFeed(for: account, url: url, name: name, container: container, completion: completion) + createRSSWebFeed(for: account, url: url, editedName: name, container: container, completion: completion) } } @@ -244,14 +245,13 @@ extension LocalAccountDelegate: LocalAccountRefresherDelegate { func localAccountRefresherDidFinish(_ refresher: LocalAccountRefresher) { self.refreshProgress.clear() - account?.metadata.lastArticleFetchEndTime = Date() } } private extension LocalAccountDelegate { - func createProviderWebFeed(for account: Account, urlComponents: URLComponents, name: String?, container: Container, feedProvider: FeedProvider, completion: @escaping (Result) -> Void) { + func createProviderWebFeed(for account: Account, urlComponents: URLComponents, editedName: String?, container: Container, feedProvider: FeedProvider, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(2) feedProvider.assignName(urlComponents) { result in @@ -269,7 +269,7 @@ private extension LocalAccountDelegate { } let feed = account.createWebFeed(with: name, url: newURLString, webFeedID: newURLString, homePageURL: nil) - feed.editedName = name + feed.editedName = editedName feed.username = urlComponents.user container.addWebFeed(feed) @@ -291,7 +291,7 @@ private extension LocalAccountDelegate { } } - func createRSSWebFeed(for account: Account, url: URL, name: String?, container: Container, completion: @escaping (Result) -> Void) { + func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result) -> Void) { // We need to use a batch update here because we need to assign add the feed to the // container before the name has been downloaded. This will put it in the sidebar @@ -318,7 +318,7 @@ private extension LocalAccountDelegate { } let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) - feed.editedName = name + feed.editedName = editedName container.addWebFeed(feed) InitialFeedDownloader.download(url) { parsedFeed in