From d80aeefdb3046bacbec0082f3d862f8364cd5839 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 16 Oct 2019 08:30:11 -0400 Subject: [PATCH 1/2] Allow refreshing of Feed Wrangler subscriptions --- .../Account/Account.xcodeproj/project.pbxproj | 8 ++ .../Credentials/URLRequest+RSWeb.swift | 8 +- .../FeedWrangler/FeedWranglerAPICaller.swift | 15 ++++ .../FeedWranglerAccountDelegate.swift | 77 ++++++++++++++++--- .../FeedWranglerSubscription.swift | 18 +++++ .../FeedWranglerSubscriptionsRequest.swift | 17 ++++ 6 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 Frameworks/Account/FeedWrangler/FeedWranglerSubscription.swift create mode 100644 Frameworks/Account/FeedWrangler/FeedWranglerSubscriptionsRequest.swift diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 5608e6ed1..e0c3cc872 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ 3BF610C723571CD4000EF978 /* FeedWranglerAPICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF610C423571CD4000EF978 /* FeedWranglerAPICaller.swift */; }; 3BF610C823571CD4000EF978 /* FeedWranglerConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF610C523571CD4000EF978 /* FeedWranglerConfig.swift */; }; 3BF610C923571CD4000EF978 /* FeedWranglerAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF610C623571CD4000EF978 /* FeedWranglerAccountDelegate.swift */; }; + 3BF6112423572A62000EF978 /* FeedWranglerSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF6112323572A62000EF978 /* FeedWranglerSubscription.swift */; }; + 3BF6112623572E43000EF978 /* FeedWranglerSubscriptionsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF6112523572E43000EF978 /* FeedWranglerSubscriptionsRequest.swift */; }; 5107A099227DE42E00C7C3C5 /* AccountCredentialsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A098227DE42E00C7C3C5 /* AccountCredentialsTest.swift */; }; 5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */; }; 5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; }; @@ -191,6 +193,8 @@ 3BF610C423571CD4000EF978 /* FeedWranglerAPICaller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerAPICaller.swift; sourceTree = ""; }; 3BF610C523571CD4000EF978 /* FeedWranglerConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerConfig.swift; sourceTree = ""; }; 3BF610C623571CD4000EF978 /* FeedWranglerAccountDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerAccountDelegate.swift; sourceTree = ""; }; + 3BF6112323572A62000EF978 /* FeedWranglerSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedWranglerSubscription.swift; sourceTree = ""; }; + 3BF6112523572E43000EF978 /* FeedWranglerSubscriptionsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedWranglerSubscriptionsRequest.swift; sourceTree = ""; }; 5107A098227DE42E00C7C3C5 /* AccountCredentialsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCredentialsTest.swift; sourceTree = ""; }; 5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAccountManager.swift; sourceTree = ""; }; 5107A09C227DE77700C7C3C5 /* TestTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransport.swift; sourceTree = ""; }; @@ -349,6 +353,8 @@ 3BF610C423571CD4000EF978 /* FeedWranglerAPICaller.swift */, 3BF610C523571CD4000EF978 /* FeedWranglerConfig.swift */, 3BF610C623571CD4000EF978 /* FeedWranglerAccountDelegate.swift */, + 3BF6112323572A62000EF978 /* FeedWranglerSubscription.swift */, + 3BF6112523572E43000EF978 /* FeedWranglerSubscriptionsRequest.swift */, ); path = FeedWrangler; sourceTree = ""; @@ -892,6 +898,7 @@ 9ECC9A85234DC16E009B5144 /* FeedlyAccountDelegateError.swift in Sources */, 9EA3133B231E368100268BA0 /* FeedlyAccountDelegate.swift in Sources */, 51E5959B228C781500FCC42B /* FeedbinStarredEntry.swift in Sources */, + 3BF6112423572A62000EF978 /* FeedWranglerSubscription.swift in Sources */, 846E77451F6EF9B900A165E2 /* Container.swift in Sources */, 9E1D15532334304B00F4944C /* FeedlyGetStreamOperation.swift in Sources */, 9E12B0202334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift in Sources */, @@ -950,6 +957,7 @@ 84D09623217418DC00D77525 /* FeedbinTagging.swift in Sources */, 84CAD7161FDF2E22000F0755 /* FeedbinEntry.swift in Sources */, 5165D72A22835F7D00D9D53D /* HTMLFeedFinder.swift in Sources */, + 3BF6112623572E43000EF978 /* FeedWranglerSubscriptionsRequest.swift in Sources */, 841974011F6DD1EC006346C4 /* Folder.swift in Sources */, 510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */, 846E774F1F6EF9C000A165E2 /* LocalAccountDelegate.swift in Sources */, diff --git a/Frameworks/Account/Credentials/URLRequest+RSWeb.swift b/Frameworks/Account/Credentials/URLRequest+RSWeb.swift index da7b04a49..90d215a72 100755 --- a/Frameworks/Account/Credentials/URLRequest+RSWeb.swift +++ b/Frameworks/Account/Credentials/URLRequest+RSWeb.swift @@ -37,7 +37,13 @@ public extension URLRequest { ] self.url = components.url case .feedWranglerToken: - fatalError() // TODO: implement + guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + return + } + components.queryItems = [ + URLQueryItem(name: "access_token", value: credentials.secret), + ] + self.url = components.url case .readerBasic: setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") httpMethod = "POST" diff --git a/Frameworks/Account/FeedWrangler/FeedWranglerAPICaller.swift b/Frameworks/Account/FeedWrangler/FeedWranglerAPICaller.swift index 110ec5d8b..1aeabe525 100644 --- a/Frameworks/Account/FeedWrangler/FeedWranglerAPICaller.swift +++ b/Frameworks/Account/FeedWrangler/FeedWranglerAPICaller.swift @@ -56,4 +56,19 @@ final class FeedWranglerAPICaller: NSObject { } } + func retrieveSubscriptions(completion: @escaping (Result<[FeedWranglerSubscription], Error>) -> Void) { + let url = FeedWranglerConfig.clientURL.appendingPathComponent("subscriptions/list") + let request = URLRequest(url: url, credentials: credentials) + + transport.send(request: request, resultType: FeedWranglerSubscriptionsRequest.self) { result in + switch result { + case .success(let (_, results)): + completion(.success(results?.feeds ?? [])) + + case .failure(let error): + completion(.failure(error)) + } + } + } + } diff --git a/Frameworks/Account/FeedWrangler/FeedWranglerAccountDelegate.swift b/Frameworks/Account/FeedWrangler/FeedWranglerAccountDelegate.swift index 0c125feaa..8eba043ef 100644 --- a/Frameworks/Account/FeedWrangler/FeedWranglerAccountDelegate.swift +++ b/Frameworks/Account/FeedWrangler/FeedWranglerAccountDelegate.swift @@ -19,7 +19,12 @@ final class FeedWranglerAccountDelegate: AccountDelegate { var isOPMLImportInProgress = false var server: String? = FeedWranglerConfig.clientPath - var credentials: Credentials? + var credentials: Credentials? { + didSet { + caller.credentials = credentials + } + } + var accountMetadata: AccountMetadata? var refreshProgress = DownloadProgress(numberOfTasks: 0) @@ -54,13 +59,17 @@ final class FeedWranglerAccountDelegate: AccountDelegate { func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(6) - self.sendArticleStatus(for: account) { - self.refreshArticleStatus(for: account) { - self.refreshArticles(for: account) { - self.refreshMissingArticles(for: account) { - self.refreshProgress.clear() - DispatchQueue.main.async { - completion(.success(())) + self.refreshCredentials(for: account) { + self.refreshSubscriptions(for: account) { _ in + self.sendArticleStatus(for: account) { + self.refreshArticleStatus(for: account) { + self.refreshArticles(for: account) { + self.refreshMissingArticles(for: account) { + self.refreshProgress.clear() + DispatchQueue.main.async { + completion(.success(())) + } + } } } } @@ -68,8 +77,31 @@ final class FeedWranglerAccountDelegate: AccountDelegate { } } + func refreshCredentials(for account: Account, completion: @escaping (() -> Void)) { + os_log(.debug, log: log, "Refreshing credentials...") + // MARK: TODO + credentials = try? account.retrieveCredentials(type: .feedWranglerToken) + completion() + } + + func refreshSubscriptions(for account: Account, completion: @escaping ((Result) -> Void)) { + os_log(.debug, log: log, "Refreshing subscriptions...") + caller.retrieveSubscriptions { result in + switch result { + case .success(let subscriptions): + self.syncFeeds(account, subscriptions) + completion(.success(())) + + case .failure(let error): + completion(.failure(error)) + } + + } + } + func refreshArticles(for account: Account, completion: @escaping (() -> Void)) { - os_log(.debug, log: log, "Refreshing articles...") + os_log(.debug, log: log, "Refreshing articles...") + completion() } func refreshMissingArticles(for account: Account, completion: @escaping (() -> Void)) { @@ -152,5 +184,32 @@ final class FeedWranglerAccountDelegate: AccountDelegate { // MARK: Private private extension FeedWranglerAccountDelegate { + + func syncFeeds(_ account: Account, _ subscriptions: [FeedWranglerSubscription]) { + assert(Thread.isMainThread) + let feedIds = subscriptions.map { String($0.feed_id) } + + let feedsToRemove = account.topLevelFeeds.filter { !feedIds.contains($0.feedID) } + account.removeFeeds(feedsToRemove) + var subscriptionsToAdd = Set() + subscriptions.forEach { subscription in + let subscriptionId = String(subscription.feed_id) + + if let feed = account.existingFeed(withFeedID: subscriptionId) { + feed.name = subscription.title + feed.homePageURL = subscription.site_url + feed.subscriptionID = nil // MARK: TODO What should this be? + } else { + subscriptionsToAdd.insert(subscription) + } + } + + subscriptionsToAdd.forEach { subscription in + let feedId = String(subscription.feed_id) + let feed = account.createFeed(with: subscription.title, url: subscription.feed_url, feedID: feedId, homePageURL: subscription.site_url) + feed.subscriptionID = nil + account.addFeed(feed) + } + } } diff --git a/Frameworks/Account/FeedWrangler/FeedWranglerSubscription.swift b/Frameworks/Account/FeedWrangler/FeedWranglerSubscription.swift new file mode 100644 index 000000000..3a69961dd --- /dev/null +++ b/Frameworks/Account/FeedWrangler/FeedWranglerSubscription.swift @@ -0,0 +1,18 @@ +// +// FeedWranglerSubscription.swift +// Account +// +// Created by Jonathan Bennett on 2019-10-16. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// +import Foundation +import RSCore +import RSParser + +struct FeedWranglerSubscription: Hashable, Codable { + let title: String + let feed_id: Int + let feed_url: String + let site_url: String? + +} diff --git a/Frameworks/Account/FeedWrangler/FeedWranglerSubscriptionsRequest.swift b/Frameworks/Account/FeedWrangler/FeedWranglerSubscriptionsRequest.swift new file mode 100644 index 000000000..66fb6a20b --- /dev/null +++ b/Frameworks/Account/FeedWrangler/FeedWranglerSubscriptionsRequest.swift @@ -0,0 +1,17 @@ +// +// FeedWranglerSubscriptionsRequest.swift +// Account +// +// Created by Jonathan Bennett on 2019-10-16. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct FeedWranglerSubscriptionsRequest: Hashable, Codable { + + let feeds: [FeedWranglerSubscription] + let error: String? + let result: String + +} From 51dc82ffef619060e32a4318e4dd3d8cfe22f9d1 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 16 Oct 2019 09:04:24 -0400 Subject: [PATCH 2/2] use swiftier property names --- .../FeedWrangler/FeedWranglerAccountDelegate.swift | 11 ++++++----- .../FeedWrangler/FeedWranglerSubscription.swift | 14 +++++++++++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Frameworks/Account/FeedWrangler/FeedWranglerAccountDelegate.swift b/Frameworks/Account/FeedWrangler/FeedWranglerAccountDelegate.swift index 8eba043ef..1f5a056da 100644 --- a/Frameworks/Account/FeedWrangler/FeedWranglerAccountDelegate.swift +++ b/Frameworks/Account/FeedWrangler/FeedWranglerAccountDelegate.swift @@ -93,6 +93,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate { completion(.success(())) case .failure(let error): + os_log(.debug, log: self.log, "Failed to refresh subscriptions: %@", error.localizedDescription) completion(.failure(error)) } @@ -187,18 +188,18 @@ private extension FeedWranglerAccountDelegate { func syncFeeds(_ account: Account, _ subscriptions: [FeedWranglerSubscription]) { assert(Thread.isMainThread) - let feedIds = subscriptions.map { String($0.feed_id) } + let feedIds = subscriptions.map { String($0.feedID) } let feedsToRemove = account.topLevelFeeds.filter { !feedIds.contains($0.feedID) } account.removeFeeds(feedsToRemove) var subscriptionsToAdd = Set() subscriptions.forEach { subscription in - let subscriptionId = String(subscription.feed_id) + let subscriptionId = String(subscription.feedID) if let feed = account.existingFeed(withFeedID: subscriptionId) { feed.name = subscription.title - feed.homePageURL = subscription.site_url + feed.homePageURL = subscription.siteURL feed.subscriptionID = nil // MARK: TODO What should this be? } else { subscriptionsToAdd.insert(subscription) @@ -206,8 +207,8 @@ private extension FeedWranglerAccountDelegate { } subscriptionsToAdd.forEach { subscription in - let feedId = String(subscription.feed_id) - let feed = account.createFeed(with: subscription.title, url: subscription.feed_url, feedID: feedId, homePageURL: subscription.site_url) + let feedId = String(subscription.feedID) + let feed = account.createFeed(with: subscription.title, url: subscription.feedURL, feedID: feedId, homePageURL: subscription.siteURL) feed.subscriptionID = nil account.addFeed(feed) } diff --git a/Frameworks/Account/FeedWrangler/FeedWranglerSubscription.swift b/Frameworks/Account/FeedWrangler/FeedWranglerSubscription.swift index 3a69961dd..821dd41ad 100644 --- a/Frameworks/Account/FeedWrangler/FeedWranglerSubscription.swift +++ b/Frameworks/Account/FeedWrangler/FeedWranglerSubscription.swift @@ -10,9 +10,17 @@ import RSCore import RSParser struct FeedWranglerSubscription: Hashable, Codable { + let title: String - let feed_id: Int - let feed_url: String - let site_url: String? + let feedID: Int + let feedURL: String + let siteURL: String? + + enum CodingKeys: String, CodingKey { + case title = "title" + case feedID = "feed_id" + case feedURL = "feed_url" + case siteURL = "site_url" + } }