diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 82cc5cd0c..7915563f5 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -36,6 +36,11 @@ public enum AccountType: Int { // TODO: more } +public enum AccountError: Error { + case createErrorNotFound + case createErrorAlreadySubscribed +} + public final class Account: DisplayNameProvider, UnreadCountProvider, Container, Hashable { public struct UserInfoKey { @@ -362,7 +367,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, delegate.removeFeed(for: self, from: container, with: feed, completion: completion) } - public func createFeed(url: String, completion: @escaping (Result) -> Void) { + public func createFeed(url: String, completion: @escaping (Result) -> Void) { delegate.createFeed(for: self, url: url, completion: completion) } diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 73c2c8c8d..569db18c7 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -21,7 +21,6 @@ 5165D71622821C2400D9D53D /* taggings_delete.json in Resources */ = {isa = PBXBuildFile; fileRef = 5165D71322821C2400D9D53D /* taggings_delete.json */; }; 5165D71722821C2400D9D53D /* taggings_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5165D71422821C2400D9D53D /* taggings_add.json */; }; 5165D71822821C2400D9D53D /* taggings_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 5165D71522821C2400D9D53D /* taggings_initial.json */; }; - 5165D71B22833A7500D9D53D /* AccountCreateFeedResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5165D7192283398700D9D53D /* AccountCreateFeedResult.swift */; }; 5165D72822835F7800D9D53D /* FeedFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5165D71C22835E9800D9D53D /* FeedFinder.swift */; }; 5165D72922835F7A00D9D53D /* FeedSpecifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5165D71D22835E9800D9D53D /* FeedSpecifier.swift */; }; 5165D72A22835F7D00D9D53D /* HTMLFeedFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5165D71E22835E9800D9D53D /* HTMLFeedFinder.swift */; }; @@ -118,7 +117,6 @@ 5165D71322821C2400D9D53D /* taggings_delete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = taggings_delete.json; sourceTree = ""; }; 5165D71422821C2400D9D53D /* taggings_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = taggings_add.json; sourceTree = ""; }; 5165D71522821C2400D9D53D /* taggings_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = taggings_initial.json; sourceTree = ""; }; - 5165D7192283398700D9D53D /* AccountCreateFeedResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCreateFeedResult.swift; sourceTree = ""; }; 5165D71C22835E9800D9D53D /* FeedFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedFinder.swift; sourceTree = ""; }; 5165D71D22835E9800D9D53D /* FeedSpecifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedSpecifier.swift; sourceTree = ""; }; 5165D71E22835E9800D9D53D /* HTMLFeedFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLFeedFinder.swift; sourceTree = ""; }; @@ -274,7 +272,6 @@ isa = PBXGroup; children = ( 848935101F62486800CEBD24 /* Account.swift */, - 5165D7192283398700D9D53D /* AccountCreateFeedResult.swift */, 841974241F6DDCE4006346C4 /* AccountDelegate.swift */, 846E77531F6F00E300A165E2 /* AccountManager.swift */, 84AF4EA3222CFDD100F6A800 /* AccountMetadata.swift */, @@ -508,7 +505,6 @@ 5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */, 846E77451F6EF9B900A165E2 /* Container.swift in Sources */, 84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */, - 5165D71B22833A7500D9D53D /* AccountCreateFeedResult.swift in Sources */, 841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */, 5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */, 846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */, diff --git a/Frameworks/Account/AccountCreateFeedResult.swift b/Frameworks/Account/AccountCreateFeedResult.swift deleted file mode 100644 index e4f521846..000000000 --- a/Frameworks/Account/AccountCreateFeedResult.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// AccountCreateFeedResult.swift -// AccountTests -// -// Created by Maurice Parker on 5/8/19. -// Copyright © 2019 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -public enum AccountCreateFeedResult { - case created(Feed) - case multipleChoice([AccountCreateFeedChoice]) - case alreadySubscribed - case notFound -} - -public struct AccountCreateFeedChoice { - let name: String - let url: String -} diff --git a/Frameworks/Account/AccountDelegate.swift b/Frameworks/Account/AccountDelegate.swift index fed819eb0..144b7ff95 100644 --- a/Frameworks/Account/AccountDelegate.swift +++ b/Frameworks/Account/AccountDelegate.swift @@ -24,7 +24,7 @@ protocol AccountDelegate { func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) func deleteFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) - func createFeed(for account: Account, url: String, completion: @escaping (Result) -> Void) + func createFeed(for account: Account, url: String, completion: @escaping (Result) -> Void) func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) func deleteFeed(for account: Account, with feed: Feed, completion: @escaping (Result) -> Void) diff --git a/Frameworks/Account/Feedbin/FeedbinAPICaller.swift b/Frameworks/Account/Feedbin/FeedbinAPICaller.swift index b25758a0b..ef7636140 100644 --- a/Frameworks/Account/Feedbin/FeedbinAPICaller.swift +++ b/Frameworks/Account/Feedbin/FeedbinAPICaller.swift @@ -11,7 +11,7 @@ import RSWeb enum CreateSubscriptionResult { case created(FeedbinSubscription) - case multipleChoice([FeedbinSubscription]) + case multipleChoice([FeedbinSubscriptionChoice]) case alreadySubscribed case notFound } @@ -157,7 +157,7 @@ final class FeedbinAPICaller: NSObject { break } do { - let subscriptions = try JSONDecoder().decode([FeedbinSubscription].self, from: subData) + let subscriptions = try JSONDecoder().decode([FeedbinSubscriptionChoice].self, from: subData) completion(.success(.multipleChoice(subscriptions))) } catch { completion(.failure(error)) diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index 1a06b10f6..25fa97483 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -18,7 +18,6 @@ import os.log public enum FeedbinAccountDelegateError: String, Error { case invalidParameter = "There was an invalid parameter passed." - case invalidResponse = "An invalid response was received from the service." } final class FeedbinAccountDelegate: AccountDelegate { @@ -115,30 +114,23 @@ final class FeedbinAccountDelegate: AccountDelegate { } - func createFeed(for account: Account, url: String, completion: @escaping (Result) -> Void) { + func createFeed(for account: Account, url: String, completion: @escaping (Result) -> Void) { - caller.createSubscription(url: url) { result in + caller.createSubscription(url: url) { [weak self] result in switch result { case .success(let subResult): switch subResult { - case .created(let sub): - DispatchQueue.main.async { - let feed = account.createFeed(with: sub.name, url: sub.url, feedID: String(sub.feedID), homePageURL: sub.homePageURL) - feed.subscriptionID = String(sub.subscriptionID) - completion(.success(.created(feed))) - } - case .multipleChoice(let subs): - let resultSubs = subs.map { sub in return AccountCreateFeedChoice(name: sub.name ?? "", url: sub.url) } - DispatchQueue.main.async { - completion(.success(.multipleChoice(resultSubs))) - } + case .created(let subscription): + self?.createFeed(account: account, subscription: subscription, completion: completion) + case .multipleChoice(let choices): + self?.decideBestFeedChoice(account: account, url: url, choices: choices, completion: completion) case .alreadySubscribed: DispatchQueue.main.async { - completion(.success(.alreadySubscribed)) + completion(.failure(AccountError.createErrorAlreadySubscribed)) } case .notFound: DispatchQueue.main.async { - completion(.success(.notFound)) + completion(.failure(AccountError.createErrorNotFound)) } } case .failure(let error): @@ -262,25 +254,14 @@ final class FeedbinAccountDelegate: AccountDelegate { let editedName = feed.editedName createFeed(for: account, url: feed.url) { [weak self] result in - switch result { - case .success(let createResult): - - switch createResult { - case .created(let feed): - self?.processRestoredFeed(for: account, feed: feed, editedName: editedName, folder: folder, completion: completion) - default: - DispatchQueue.main.async { - completion(.failure(FeedbinAccountDelegateError.invalidResponse)) - } - } - + case .success(let feed): + self?.processRestoredFeed(for: account, feed: feed, editedName: editedName, folder: folder, completion: completion) case .failure(let error): DispatchQueue.main.async { completion(.failure(error)) } } - } } @@ -661,5 +642,37 @@ private extension FeedbinAccountDelegate { feed.folderRelationship = [folderName: id] } } + + func decideBestFeedChoice(account: Account, url: String, choices: [FeedbinSubscriptionChoice], completion: @escaping (Result) -> Void) { + + let feedSpecifiers: [FeedSpecifier] = choices.map { choice in + let source = url == choice.url ? FeedSpecifier.Source.UserEntered : FeedSpecifier.Source.HTMLLink + let specifier = FeedSpecifier(title: choice.name, urlString: choice.url, source: source) + return specifier + } + + if let bestSpecifier = FeedSpecifier.bestFeed(in: Set(feedSpecifiers)) { + if let bestSubscription = choices.filter({ bestSpecifier.urlString == $0.url }).first { + createFeed(for: account, url: bestSubscription.url, completion: completion) + } else { + DispatchQueue.main.async { + completion(.failure(FeedbinAccountDelegateError.invalidParameter)) + } + } + } else { + DispatchQueue.main.async { + completion(.failure(FeedbinAccountDelegateError.invalidParameter)) + } + } + + } + + func createFeed( account: Account, subscription sub: FeedbinSubscription, completion: @escaping (Result) -> Void) { + DispatchQueue.main.async { + let feed = account.createFeed(with: sub.name, url: sub.url, feedID: String(sub.feedID), homePageURL: sub.homePageURL) + feed.subscriptionID = String(sub.subscriptionID) + completion(.success(feed)) + } + } } diff --git a/Frameworks/Account/Feedbin/FeedbinSubscription.swift b/Frameworks/Account/Feedbin/FeedbinSubscription.swift index d16328995..8d3ca1109 100644 --- a/Frameworks/Account/Feedbin/FeedbinSubscription.swift +++ b/Frameworks/Account/Feedbin/FeedbinSubscription.swift @@ -41,3 +41,15 @@ struct FeedbinUpdateSubscription: Codable { case title } } + +struct FeedbinSubscriptionChoice: Codable { + + let name: String? + let url: String + + enum CodingKeys: String, CodingKey { + case name = "title" + case url = "feed_url" + } + +} diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index fc55e0d4b..7df678b80 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -22,7 +22,7 @@ final class LocalAccountDelegate: AccountDelegate { private weak var account: Account? private var feedFinder: FeedFinder? - private var createFeedCompletion: ((Result) -> Void)? + private var createFeedCompletion: ((Result) -> Void)? private let refresher = LocalAccountRefresher() @@ -46,7 +46,7 @@ final class LocalAccountDelegate: AccountDelegate { completion(.success(())) } - func createFeed(for account: Account, url urlString: String, completion: @escaping (Result) -> Void) { + func createFeed(for account: Account, url urlString: String, completion: @escaping (Result) -> Void) { guard let url = URL(string: urlString) else { completion(.failure(LocalAccountDelegateError.invalidParameter)) @@ -138,7 +138,7 @@ extension LocalAccountDelegate: FeedFinderDelegate { if let error = feedFinder.initialDownloadError { if feedFinder.initialDownloadStatusCode == 404 { - createFeedCompletion!(.success(.notFound)) + createFeedCompletion!(.failure(AccountError.createErrorNotFound)) } else { createFeedCompletion!(.failure(error)) } @@ -148,7 +148,7 @@ extension LocalAccountDelegate: FeedFinderDelegate { guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers), let url = URL(string: bestFeedSpecifier.urlString), let account = account else { - createFeedCompletion!(.success(.notFound)) + createFeedCompletion!(.failure(AccountError.createErrorNotFound)) return } @@ -157,7 +157,7 @@ extension LocalAccountDelegate: FeedFinderDelegate { if let parsedFeed = parsedFeed { account.update(feed, with: parsedFeed, {}) } - self?.createFeedCompletion!(.success(.created(feed))) + self?.createFeedCompletion!(.success(feed)) } } diff --git a/Mac/MainWindow/AddFeed/AddFeedController.swift b/Mac/MainWindow/AddFeed/AddFeedController.swift index 45a32f939..b067b6ae9 100644 --- a/Mac/MainWindow/AddFeed/AddFeedController.swift +++ b/Mac/MainWindow/AddFeed/AddFeedController.swift @@ -64,19 +64,17 @@ class AddFeedController: AddFeedWindowControllerDelegate { self?.endShowingProgress() switch result { - case .success(let createFeedResult): - switch createFeedResult { - case .created(let feed): - self?.processFeed(feed, account: account, folder: folder, url: url, title: title) - case .multipleChoice(let feedChoices): - print() - case .alreadySubscribed: - self?.showAlreadySubscribedError(url.absoluteString) - case .notFound: - self?.showNoFeedsErrorMessage() - } + case .success(let feed): + self?.processFeed(feed, account: account, folder: folder, url: url, title: title) case .failure(let error): - NSApplication.shared.presentError(error) + switch error { + case AccountError.createErrorAlreadySubscribed: + self?.showAlreadySubscribedError(url.absoluteString) + case AccountError.createErrorNotFound: + self?.showNoFeedsErrorMessage() + default: + NSApplication.shared.presentError(error) + } } } diff --git a/Mac/Scriptability/Feed+Scriptability.swift b/Mac/Scriptability/Feed+Scriptability.swift index 9a9151a9d..db385b312 100644 --- a/Mac/Scriptability/Feed+Scriptability.swift +++ b/Mac/Scriptability/Feed+Scriptability.swift @@ -102,32 +102,25 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine account.createFeed(url: url) { result in switch result { - case .success(let createFeedResult): + case .success(let feed): - switch createFeedResult { - case .created(let feed): - - if let editedName = titleFromArgs { - account.renameFeed(feed, to: editedName) { result in - } + if let editedName = titleFromArgs { + account.renameFeed(feed, to: editedName) { result in } - - // add the feed, putting it in a folder if needed - account.addFeed(feed) { result in - switch result { - case .success: - NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed]) - let scriptableFeed = self.scriptableFeed(feed, account:account, folder:folder) - command.resumeExecution(withResult:scriptableFeed.objectSpecifier) - case .failure: - command.resumeExecution(withResult:nil) - } - } - - default: - command.resumeExecution(withResult:nil) } - + + // add the feed, putting it in a folder if needed + account.addFeed(feed) { result in + switch result { + case .success: + NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed]) + let scriptableFeed = self.scriptableFeed(feed, account:account, folder:folder) + command.resumeExecution(withResult:scriptableFeed.objectSpecifier) + case .failure: + command.resumeExecution(withResult:nil) + } + } + case .failure: command.resumeExecution(withResult:nil) }