Use best feed specifier code to determine which Feedbin option to use when creating a feed

This commit is contained in:
Maurice Parker 2019-05-10 10:14:24 -05:00
parent a7d1014d5b
commit fe70723eb4
10 changed files with 94 additions and 98 deletions

View File

@ -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<AccountCreateFeedResult, Error>) -> Void) {
public func createFeed(url: String, completion: @escaping (Result<Feed, Error>) -> Void) {
delegate.createFeed(for: self, url: url, completion: completion)
}

View File

@ -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 = "<group>"; };
5165D71422821C2400D9D53D /* taggings_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = taggings_add.json; sourceTree = "<group>"; };
5165D71522821C2400D9D53D /* taggings_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = taggings_initial.json; sourceTree = "<group>"; };
5165D7192283398700D9D53D /* AccountCreateFeedResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCreateFeedResult.swift; sourceTree = "<group>"; };
5165D71C22835E9800D9D53D /* FeedFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedFinder.swift; sourceTree = "<group>"; };
5165D71D22835E9800D9D53D /* FeedSpecifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedSpecifier.swift; sourceTree = "<group>"; };
5165D71E22835E9800D9D53D /* HTMLFeedFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLFeedFinder.swift; sourceTree = "<group>"; };
@ -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 */,

View File

@ -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
}

View File

@ -24,7 +24,7 @@ protocol AccountDelegate {
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
func deleteFolder(for account: Account, with folder: Folder, completion: @escaping (Result<Void, Error>) -> Void)
func createFeed(for account: Account, url: String, completion: @escaping (Result<AccountCreateFeedResult, Error>) -> Void)
func createFeed(for account: Account, url: String, completion: @escaping (Result<Feed, Error>) -> Void)
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
func deleteFeed(for account: Account, with feed: Feed, completion: @escaping (Result<Void, Error>) -> Void)

View File

@ -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))

View File

@ -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<AccountCreateFeedResult, Error>) -> Void) {
func createFeed(for account: Account, url: String, completion: @escaping (Result<Feed, Error>) -> 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):
case .success(let feed):
self?.processRestoredFeed(for: account, feed: feed, editedName: editedName, folder: folder, completion: completion)
default:
DispatchQueue.main.async {
completion(.failure(FeedbinAccountDelegateError.invalidResponse))
}
}
case .failure(let error):
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
@ -662,4 +643,36 @@ private extension FeedbinAccountDelegate {
}
}
func decideBestFeedChoice(account: Account, url: String, choices: [FeedbinSubscriptionChoice], completion: @escaping (Result<Feed, Error>) -> 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<Feed, Error>) -> 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))
}
}
}

View File

@ -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"
}
}

View File

@ -22,7 +22,7 @@ final class LocalAccountDelegate: AccountDelegate {
private weak var account: Account?
private var feedFinder: FeedFinder?
private var createFeedCompletion: ((Result<AccountCreateFeedResult, Error>) -> Void)?
private var createFeedCompletion: ((Result<Feed, Error>) -> 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<AccountCreateFeedResult, Error>) -> Void) {
func createFeed(for account: Account, url urlString: String, completion: @escaping (Result<Feed, Error>) -> 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))
}
}

View File

@ -64,20 +64,18 @@ class AddFeedController: AddFeedWindowControllerDelegate {
self?.endShowingProgress()
switch result {
case .success(let createFeedResult):
switch createFeedResult {
case .created(let feed):
case .success(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 .failure(let error):
switch error {
case AccountError.createErrorAlreadySubscribed:
self?.showAlreadySubscribedError(url.absoluteString)
case AccountError.createErrorNotFound:
self?.showNoFeedsErrorMessage()
default:
NSApplication.shared.presentError(error)
}
}
}

View File

@ -102,10 +102,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
account.createFeed(url: url) { result in
switch result {
case .success(let createFeedResult):
switch createFeedResult {
case .created(let feed):
case .success(let feed):
if let editedName = titleFromArgs {
account.renameFeed(feed, to: editedName) { result in
@ -124,10 +121,6 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
}
}
default:
command.resumeExecution(withResult:nil)
}
case .failure:
command.resumeExecution(withResult:nil)
}