Add download subscriptions and faviconURLs

This commit is contained in:
Maurice Parker 2019-05-07 10:51:41 -05:00
parent a5a066dd49
commit 7f9055fe78
22 changed files with 335 additions and 260 deletions

View File

@ -382,21 +382,24 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
}
public func createFeed(with name: String?, editedName: String?, url: String) -> Feed? {
public func createFeed(with name: String?, editedName: String?, url: String) -> Feed {
// For syncing, this may need to be an async method with a callback,
// since it will likely need to call the server.
let metadata = feedMetadata(feedID: url)
let feed = Feed(account: self, url: url, feedID: url, metadata: metadata)
if let name = name, feed.name == nil {
feed.name = name
}
if let editedName = editedName, feed.editedName == nil {
feed.editedName = editedName
}
return createFeed(with: name, editedName: editedName, url: url, feedId: url, homePageURL: nil)
}
func createFeed(with name: String?, editedName: String?, url: String, feedId: String, homePageURL: String?) -> Feed {
let metadata = feedMetadata(feedID: feedId)
let feed = Feed(account: self, url: url, feedID: feedId, metadata: metadata)
feed.name = name
feed.editedName = editedName
feed.homePageURL = homePageURL
return feed
}
public func canAddFolder(_ folder: Folder, to containingFolder: Folder?) -> Bool {

View File

@ -10,6 +10,11 @@
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 */; };
513323082281070D00C30F19 /* AccountFeedSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513323072281070C00C30F19 /* AccountFeedSyncTest.swift */; };
5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 513323092281082F00C30F19 /* subscriptions_initial.json */; };
5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5133230B2281088A00C30F19 /* subscriptions_add.json */; };
5133230E2281089500C30F19 /* icons.json in Resources */ = {isa = PBXBuildFile; fileRef = 5133230D2281089500C30F19 /* icons.json */; };
5133231122810EB200C30F19 /* FeedbinIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133230F22810E5700C30F19 /* FeedbinIcon.swift */; };
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA48227B497600D19003 /* FeedbinAPICaller.swift */; };
5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */; };
51D58755227F53BE00900287 /* FeedbinTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D58754227F53BE00900287 /* FeedbinTag.swift */; };
@ -23,7 +28,7 @@
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841974241F6DDCE4006346C4 /* AccountDelegate.swift */; };
841D4D702106B40400DD04E6 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 841D4D6F2106B40400DD04E6 /* ArticlesDatabase.framework */; };
841D4D722106B40A00DD04E6 /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 841D4D712106B40A00DD04E6 /* Articles.framework */; };
84245C851FDDD8CB0074AFBB /* FeedbinFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84245C841FDDD8CB0074AFBB /* FeedbinFeed.swift */; };
84245C851FDDD8CB0074AFBB /* FeedbinSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84245C841FDDD8CB0074AFBB /* FeedbinSubscription.swift */; };
844B297D2106C7EC004020B3 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B297C2106C7EC004020B3 /* Feed.swift */; };
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */; };
844B2981210CE3BF004020B3 /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844B2980210CE3BF004020B3 /* RSWeb.framework */; };
@ -37,10 +42,8 @@
84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */; };
84C3654A1F899F3B001EC85C /* CombinedRefreshProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */; };
84C8B3F41F89DE430053CCA6 /* DataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */; };
84CAD7161FDF2E22000F0755 /* FeedbinArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAD7151FDF2E22000F0755 /* FeedbinArticle.swift */; };
84D096212174169100D77525 /* FeedbinArticleIDArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D096202174169100D77525 /* FeedbinArticleIDArray.swift */; };
84CAD7161FDF2E22000F0755 /* FeedbinEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAD7151FDF2E22000F0755 /* FeedbinEntry.swift */; };
84D09623217418DC00D77525 /* FeedbinTagging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D09622217418DC00D77525 /* FeedbinTagging.swift */; };
84D0962521741B8500D77525 /* FeedbinSavedSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D0962421741B8500D77525 /* FeedbinSavedSearch.swift */; };
84EAC4822148CC6300F154AB /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84EAC4812148CC6300F154AB /* RSDatabase.framework */; };
84F1F06E2243524700DA0616 /* AccountMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AF4EA3222CFDD100F6A800 /* AccountMetadata.swift */; };
84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F73CF0202788D80000BCEF /* ArticleFetcher.swift */; };
@ -95,6 +98,11 @@
5107A098227DE42E00C7C3C5 /* AccountCredentialsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCredentialsTest.swift; sourceTree = "<group>"; };
5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAccountManager.swift; sourceTree = "<group>"; };
5107A09C227DE77700C7C3C5 /* TestTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransport.swift; sourceTree = "<group>"; };
513323072281070C00C30F19 /* AccountFeedSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedSyncTest.swift; sourceTree = "<group>"; };
513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = "<group>"; };
5133230B2281088A00C30F19 /* subscriptions_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_add.json; sourceTree = "<group>"; };
5133230D2281089500C30F19 /* icons.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = icons.json; sourceTree = "<group>"; };
5133230F22810E5700C30F19 /* FeedbinIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinIcon.swift; sourceTree = "<group>"; };
5144EA48227B497600D19003 /* FeedbinAPICaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAPICaller.swift; sourceTree = "<group>"; };
5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAccountDelegate.swift; sourceTree = "<group>"; };
51D58754227F53BE00900287 /* FeedbinTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinTag.swift; sourceTree = "<group>"; };
@ -111,7 +119,7 @@
8419742D1F6DDE96006346C4 /* LocalAccountRefresher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAccountRefresher.swift; sourceTree = "<group>"; };
841D4D6F2106B40400DD04E6 /* ArticlesDatabase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ArticlesDatabase.framework; sourceTree = BUILT_PRODUCTS_DIR; };
841D4D712106B40A00DD04E6 /* Articles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Articles.framework; sourceTree = BUILT_PRODUCTS_DIR; };
84245C841FDDD8CB0074AFBB /* FeedbinFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinFeed.swift; sourceTree = "<group>"; };
84245C841FDDD8CB0074AFBB /* FeedbinSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinSubscription.swift; sourceTree = "<group>"; };
844B297C2106C7EC004020B3 /* Feed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = "<group>"; };
844B297E210CE37E004020B3 /* UnreadCountProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadCountProvider.swift; sourceTree = "<group>"; };
844B2980210CE3BF004020B3 /* RSWeb.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSWeb.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -126,10 +134,8 @@
84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerPath.swift; sourceTree = "<group>"; };
84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombinedRefreshProgress.swift; sourceTree = "<group>"; };
84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataExtensions.swift; sourceTree = "<group>"; };
84CAD7151FDF2E22000F0755 /* FeedbinArticle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinArticle.swift; sourceTree = "<group>"; };
84D096202174169100D77525 /* FeedbinArticleIDArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinArticleIDArray.swift; sourceTree = "<group>"; };
84CAD7151FDF2E22000F0755 /* FeedbinEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinEntry.swift; sourceTree = "<group>"; };
84D09622217418DC00D77525 /* FeedbinTagging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinTagging.swift; sourceTree = "<group>"; };
84D0962421741B8500D77525 /* FeedbinSavedSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinSavedSearch.swift; sourceTree = "<group>"; };
84EAC4812148CC6300F154AB /* RSDatabase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSDatabase.framework; sourceTree = BUILT_PRODUCTS_DIR; };
84F73CF0202788D80000BCEF /* ArticleFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleFetcher.swift; sourceTree = "<group>"; };
D511EEB5202422BB00712EC3 /* Account_project_debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_debug.xcconfig; sourceTree = "<group>"; };
@ -170,6 +176,9 @@
51D58758227F630B00900287 /* tags_add.json */,
51D58757227F630B00900287 /* tags_delete.json */,
51D58759227F630B00900287 /* tags_initial.json */,
513323092281082F00C30F19 /* subscriptions_initial.json */,
5133230B2281088A00C30F19 /* subscriptions_add.json */,
5133230D2281089500C30F19 /* icons.json */,
);
path = JSON;
sourceTree = "<group>";
@ -207,10 +216,9 @@
children = (
5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */,
5144EA48227B497600D19003 /* FeedbinAPICaller.swift */,
84CAD7151FDF2E22000F0755 /* FeedbinArticle.swift */,
84D096202174169100D77525 /* FeedbinArticleIDArray.swift */,
84245C841FDDD8CB0074AFBB /* FeedbinFeed.swift */,
84D0962421741B8500D77525 /* FeedbinSavedSearch.swift */,
84CAD7151FDF2E22000F0755 /* FeedbinEntry.swift */,
5133230F22810E5700C30F19 /* FeedbinIcon.swift */,
84245C841FDDD8CB0074AFBB /* FeedbinSubscription.swift */,
51D58754227F53BE00900287 /* FeedbinTag.swift */,
84D09622217418DC00D77525 /* FeedbinTagging.swift */,
);
@ -271,6 +279,7 @@
children = (
5107A098227DE42E00C7C3C5 /* AccountCredentialsTest.swift */,
51D5875D227F643C00900287 /* AccountFolderSyncTest.swift */,
513323072281070C00C30F19 /* AccountFeedSyncTest.swift */,
5107A09C227DE77700C7C3C5 /* TestTransport.swift */,
5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */,
51D58756227F62E300900287 /* JSON */,
@ -439,9 +448,12 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5133230E2281089500C30F19 /* icons.json in Resources */,
51D5875B227F630B00900287 /* tags_add.json in Resources */,
5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */,
51D5875C227F630B00900287 /* tags_initial.json in Resources */,
51D5875A227F630B00900287 /* tags_delete.json in Resources */,
5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -455,7 +467,6 @@
84C8B3F41F89DE430053CCA6 /* DataExtensions.swift in Sources */,
84C3654A1F899F3B001EC85C /* CombinedRefreshProgress.swift in Sources */,
8469F81C1F6DD15E0084783E /* Account.swift in Sources */,
84D096212174169100D77525 /* FeedbinArticleIDArray.swift in Sources */,
5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */,
846E77451F6EF9B900A165E2 /* Container.swift in Sources */,
84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */,
@ -465,16 +476,16 @@
84B2D4D02238CD8A00498ADA /* FeedMetadata.swift in Sources */,
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */,
84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */,
5133231122810EB200C30F19 /* FeedbinIcon.swift in Sources */,
846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */,
51D58755227F53BE00900287 /* FeedbinTag.swift in Sources */,
84D09623217418DC00D77525 /* FeedbinTagging.swift in Sources */,
84CAD7161FDF2E22000F0755 /* FeedbinArticle.swift in Sources */,
84D0962521741B8500D77525 /* FeedbinSavedSearch.swift in Sources */,
84CAD7161FDF2E22000F0755 /* FeedbinEntry.swift in Sources */,
841974011F6DD1EC006346C4 /* Folder.swift in Sources */,
846E774F1F6EF9C000A165E2 /* LocalAccountDelegate.swift in Sources */,
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */,
84F1F06E2243524700DA0616 /* AccountMetadata.swift in Sources */,
84245C851FDDD8CB0074AFBB /* FeedbinFeed.swift in Sources */,
84245C851FDDD8CB0074AFBB /* FeedbinSubscription.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -484,6 +495,7 @@
files = (
51D5875E227F643C00900287 /* AccountFolderSyncTest.swift in Sources */,
5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */,
513323082281070D00C30F19 /* AccountFeedSyncTest.swift in Sources */,
5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */,
5107A099227DE42E00C7C3C5 /* AccountCredentialsTest.swift in Sources */,
);

View File

@ -19,6 +19,7 @@ final class AccountMetadata: Codable {
static let subscriptions = "subscriptions"
static let tags = "tags"
static let taggings = "taggings"
static let icons = "icons"
}
enum CodingKeys: String, CodingKey {

View File

@ -0,0 +1,65 @@
//
// AccountFullSyncTest.swift
// AccountTests
//
// Created by Maurice Parker on 5/6/19.
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
//
import XCTest
@testable import Account
class AccountFeedSyncTest: XCTestCase {
override func setUp() {
}
override func tearDown() {
}
func testDownloadSync() {
let testTransport = TestTransport()
testTransport.testFiles["https://api.feedbin.com/v2/tags.json"] = "tags_add.json"
testTransport.testFiles["https://api.feedbin.com/v2/subscriptions.json"] = "subscriptions_initial.json"
testTransport.testFiles["https://api.feedbin.com/v2/icons.json"] = "icons.json"
let account = TestAccountManager.shared.createAccount(type: .feedbin, transport: testTransport)
// Test initial folders
let initialExpection = self.expectation(description: "Initial feeds")
account.refreshAll() {
initialExpection.fulfill()
}
waitForExpectations(timeout: 5, handler: nil)
XCTAssertEqual(224, account.flattenedFeeds().count)
let daringFireball = account.idToFeedDictionary["1296379"]
XCTAssertEqual("Daring Fireball", daringFireball!.name)
XCTAssertEqual("https://daringfireball.net/feeds/json", daringFireball!.url)
XCTAssertEqual("https://daringfireball.net/", daringFireball!.homePageURL)
XCTAssertEqual("https://favicons.feedbinusercontent.com/6ac/6acc098f35ed2bcc0915ca89d50a97e5793eda45.png", daringFireball!.faviconURL)
// Test Adding a Feed
testTransport.testFiles["https://api.feedbin.com/v2/subscriptions.json"] = "subscriptions_add.json"
// Test initial folders
let addExpection = self.expectation(description: "Add feeds")
account.refreshAll() {
addExpection.fulfill()
}
waitForExpectations(timeout: 5, handler: nil)
XCTAssertEqual(225, account.flattenedFeeds().count)
let bPixels = account.idToFeedDictionary["1096623"]
XCTAssertEqual("Beautiful Pixels", bPixels!.name)
XCTAssertEqual("https://feedpress.me/beautifulpixels", bPixels!.url)
XCTAssertEqual("https://beautifulpixels.com/", bPixels!.homePageURL)
XCTAssertEqual("https://favicons.feedbinusercontent.com/ea0/ea010c658d6e356e49ab239b793dc415af707b05.png", bPixels?.faviconURL)
TestAccountManager.shared.deleteAccount(account)
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -24,10 +24,17 @@ final class TestTransport: Transport {
return
}
let testFileName = testFiles[urlString]!
let testFileURL = Bundle(for: TestTransport.self).resourceURL!.appendingPathComponent(testFileName)
let data = try! Data(contentsOf: testFileURL)
completion(.success((HTTPHeaders(), data)))
if let testFileName = testFiles[urlString] {
let testFileURL = Bundle(for: TestTransport.self).resourceURL!.appendingPathComponent(testFileName)
let data = try! Data(contentsOf: testFileURL)
DispatchQueue.global(qos: .background).async {
completion(.success((HTTPHeaders(), data)))
}
} else {
DispatchQueue.global(qos: .background).async {
completion(.success((HTTPHeaders(), nil)))
}
}
}

View File

@ -93,6 +93,46 @@ final class FeedbinAPICaller: NSObject {
}
func retrieveSubscriptions(completionHandler completion: @escaping (Result<[FeedbinSubscription]?, Error>) -> Void) {
let callURL = feedbinBaseURL.appendingPathComponent("subscriptions.json")
let conditionalGet = accountMetadata?.conditionalGetInfo[AccountMetadata.ConditionalGetKeys.subscriptions]
let request = URLRequest(url: callURL, credentials: credentials, conditionalGet: conditionalGet)
transport.send(request: request, resultType: [FeedbinSubscription].self) { [weak self] result in
switch result {
case .success(let (headers, subscriptions)):
self?.storeConditionalGet(metadata: self?.accountMetadata, key: AccountMetadata.ConditionalGetKeys.subscriptions, headers: headers)
completion(.success(subscriptions))
case .failure(let error):
completion(.failure(error))
}
}
}
func retrieveIcons(completionHandler completion: @escaping (Result<[FeedbinIcon]?, Error>) -> Void) {
let callURL = feedbinBaseURL.appendingPathComponent("icons.json")
let conditionalGet = accountMetadata?.conditionalGetInfo[AccountMetadata.ConditionalGetKeys.icons]
let request = URLRequest(url: callURL, credentials: credentials, conditionalGet: conditionalGet)
transport.send(request: request, resultType: [FeedbinIcon].self) { [weak self] result in
switch result {
case .success(let (headers, icons)):
self?.storeConditionalGet(metadata: self?.accountMetadata, key: AccountMetadata.ConditionalGetKeys.icons, headers: headers)
completion(.success(icons))
case .failure(let error):
completion(.failure(error))
}
}
}
}
// MARK: Private

View File

@ -39,12 +39,17 @@ final class FeedbinAccountDelegate: AccountDelegate {
var refreshProgress = DownloadProgress(numberOfTasks: 0)
func refreshAll(for account: Account, completion: (() -> Void)? = nil) {
refreshAll(account) { [weak self] result in
refreshFolders(account) { [weak self] result in
switch result {
case .success():
completion?()
DispatchQueue.main.async {
completion?()
}
case .failure(let error):
self?.handleError(error)
DispatchQueue.main.async {
completion?()
// self?.handleError(error)
}
}
}
}
@ -54,10 +59,14 @@ final class FeedbinAccountDelegate: AccountDelegate {
caller.renameTag(oldName: folder.name ?? "", newName: name) { result in
switch result {
case .success:
folder.name = name
completion(.success(()))
DispatchQueue.main.async {
folder.name = name
completion(.success(()))
}
case .failure(let error):
completion(.failure(error))
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
@ -74,12 +83,17 @@ final class FeedbinAccountDelegate: AccountDelegate {
caller.deleteTag(name: folder.name ?? "") { result in
switch result {
case .success:
account.deleteFolder(folder)
// TODO: Take the serialized taggings and reestablish the folder to feed relationships. Deleting
// a tag on Feedbin doesn't any feeds.
completion(.success(()))
DispatchQueue.main.async {
account.deleteFolder(folder)
// TODO: Take the serialized taggings and reestablish the folder to feed relationships. Deleting
// a tag on Feedbin doesn't any feeds.
completion(.success(()))
}
case .failure(let error):
completion(.failure(error))
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
@ -95,7 +109,9 @@ final class FeedbinAccountDelegate: AccountDelegate {
let caller = FeedbinAPICaller(transport: transport)
caller.credentials = credentials
caller.validateCredentials() { result in
completion(result)
DispatchQueue.main.async {
completion(result)
}
}
}
@ -108,8 +124,7 @@ private extension FeedbinAccountDelegate {
func handleError(_ error: Error) {
// TODO: We should do a better job of error handling here.
// We need to prompt for credentials and provide user friendly
// errors.
// We need to prompt for credentials if they are expired.
#if os(macOS)
NSApplication.shared.presentError(error)
#else
@ -117,13 +132,13 @@ private extension FeedbinAccountDelegate {
#endif
}
func refreshAll(_ account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
func refreshFolders(_ account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
caller.retrieveTags { [weak self] result in
switch result {
case .success(let tags):
self?.syncFolders(account, tags)
completion(.success(()))
self?.refreshFeeds(account, completion: completion)
case .failure(let error):
completion(.failure(error))
}
@ -141,7 +156,9 @@ private extension FeedbinAccountDelegate {
if let folders = account.folders {
folders.forEach { folder in
if !tagNames.contains(folder.name ?? "") {
account.deleteFolder(folder)
DispatchQueue.main.sync {
account.deleteFolder(folder)
}
}
}
}
@ -157,10 +174,81 @@ private extension FeedbinAccountDelegate {
// Make any folders Feedbin has, but we don't
tagNames.forEach { tagName in
if !folderNames.contains(tagName) {
account.ensureFolder(with: tagName)
DispatchQueue.main.sync {
_ = account.ensureFolder(with: tagName)
}
}
}
}
func refreshFeeds(_ account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
caller.retrieveSubscriptions { [weak self] result in
switch result {
case .success(let subscriptions):
self?.syncFeeds(account, subscriptions)
self?.refreshFavicons(account, completion: completion)
case .failure(let error):
completion(.failure(error))
}
}
}
func syncFeeds(_ account: Account, _ subscriptions: [FeedbinSubscription]?) {
guard let subscriptions = subscriptions else { return }
subscriptions.forEach { subscription in
syncFeed(account, subscription)
}
}
func syncFeed(_ account: Account, _ subscription: FeedbinSubscription) {
let subFeedId = String(subscription.feedID)
DispatchQueue.main.sync {
if let feed = account.idToFeedDictionary[subFeedId] {
feed.name = subscription.name
feed.homePageURL = subscription.homePageURL
} else {
let feed = account.createFeed(with: subscription.name, editedName: nil, url: subscription.url, feedId: subFeedId, homePageURL: subscription.homePageURL)
account.addFeed(feed, to: nil)
}
}
}
func refreshFavicons(_ account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
caller.retrieveIcons { [weak self] result in
switch result {
case .success(let icons):
self?.syncIcons(account, icons)
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
}
func syncIcons(_ account: Account, _ icons: [FeedbinIcon]?) {
guard let icons = icons else { return }
let iconDict = Dictionary(uniqueKeysWithValues: icons.map { ($0.host, $0.url) } )
for feed in account.flattenedFeeds() {
for (key, value) in iconDict {
if feed.homePageURL?.contains(key) ?? false {
DispatchQueue.main.sync {
feed.faviconURL = value
}
break
}
}
}
}
}

View File

@ -1,106 +0,0 @@
//
// FeedbinArticle.swift
// Account
//
// Created by Brent Simmons on 12/11/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import RSParser
import RSCore
struct FeedbinArticle {
// https://github.com/feedbin/feedbin-api/blob/master/content/entries.md
// https://github.com/feedbin/feedbin-api/blob/master/content/updated-entries.md
//
// "id": 2077,
// "feed_id": 135,
// "title": "Objective-C Runtime Releases",
// "url": "http:\/\/mjtsai.com\/blog\/2013\/02\/02\/objective-c-runtime-releases\/",
// "author": "Michael Tsai",
// "content": "<p><a href=\"https:\/\/twitter.com\/bavarious\/status\/297851496945577984\">Bavarious<\/a> created a <a href=\"https:\/\/github.com\/bavarious\/objc4\/commits\/master\">GitHub repository<\/a> that shows the differences between versions of <a href=\"http:\/\/www.opensource.apple.com\/source\/objc4\/\">Apple\u2019s Objective-C runtime<\/a> that shipped with different versions of Mac OS X.<\/p>",
// "summary": "Bavarious created a GitHub repository that shows the differences between versions of Apple\u2019s Objective-C runtime that shipped with different versions of Mac OS X.",
// "published": "2013-02-03T01:00:19.000000Z",
// "created_at": "2013-02-04T01:00:19.127893Z"
let articleID: Int
let feedID: Int
let title: String?
let url: String?
let authorName: String?
let contentHTML: String?
let contentDiffHTML: String?
let summary: String?
let datePublished: Date?
let dateArrived: Date?
struct Key {
static let articleID = "id"
static let feedID = "feed_id"
static let title = "title"
static let url = "url"
static let authorName = "author"
static let contentHTML = "content"
static let contentDiffHTML = "content_diff"
static let summary = "summary"
static let datePublished = "published"
static let dateArrived = "created_at"
}
init?(jsonDictionary: JSONDictionary) {
guard let articleID = jsonDictionary[Key.articleID] as? Int else {
return nil
}
guard let feedID = jsonDictionary[Key.feedID] as? Int else {
return nil
}
self.articleID = articleID
self.feedID = feedID
self.title = jsonDictionary[Key.title] as? String
self.url = jsonDictionary[Key.url] as? String
self.authorName = jsonDictionary[Key.authorName] as? String
if let contentHTML = jsonDictionary[Key.contentHTML] as? String, !contentHTML.isEmpty {
self.contentHTML = contentHTML
}
else {
self.contentHTML = nil
}
if let contentDiffHTML = jsonDictionary[Key.contentDiffHTML] as? String, !contentDiffHTML.isEmpty {
self.contentDiffHTML = contentDiffHTML
}
else {
self.contentDiffHTML = nil
}
if let summary = jsonDictionary[Key.summary] as? String, !summary.isEmpty {
self.summary = summary
}
else {
self.summary = nil
}
if let datePublishedString = jsonDictionary[Key.datePublished] as? String {
self.datePublished = RSDateWithString(datePublishedString)
}
else {
self.datePublished = nil
}
if let dateArrivedString = jsonDictionary[Key.dateArrived] as? String {
self.dateArrived = RSDateWithString(dateArrivedString)
}
else {
self.dateArrived = nil
}
}
static func articles(with array: JSONArray) -> [FeedbinArticle]? {
let articlesArray = array.compactMap { FeedbinArticle(jsonDictionary: $0) }
return articlesArray.isEmpty ? nil : articlesArray
}
}

View File

@ -1,22 +0,0 @@
//
// FeedbinArticleIDArray.swift
// Account
//
// Created by Brent Simmons on 10/14/18.
// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
//
import Foundation
struct FeedbinArticleIDArray {
// https://github.com/feedbin/feedbin-api/blob/master/content/unread-entries.md
//
// [4087,4088,4089,4090,4091,4092,4093,4094,4095,4096,4097]
let articleIDs: [Int]
init(jsonArray: [Any]) {
self.articleIDs = jsonArray.compactMap { $0 as? Int }
}
}

View File

@ -0,0 +1,39 @@
//
// FeedbinArticle.swift
// Account
//
// Created by Brent Simmons on 12/11/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import RSParser
import RSCore
struct FeedbinEntry: Codable {
let articleID: Int
let feedID: Int
let title: String?
let url: String?
let authorName: String?
let contentHTML: String?
let contentDiffHTML: String?
let summary: String?
let datePublished: Date?
let dateArrived: Date?
enum CodingKeys: String, CodingKey {
case articleID = "id"
case feedID = "feed_id"
case title = "title"
case url = "url"
case authorName = "author"
case contentHTML = "content"
case contentDiffHTML = "content_diff"
case summary = "summary"
case datePublished = "published"
case dateArrived = "created_at"
}
}

View File

@ -0,0 +1,21 @@
//
// FeedbinIcon.swift
// Account
//
// Created by Maurice Parker on 5/6/19.
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
//
import Foundation
struct FeedbinIcon: Codable {
let host: String
let url: String
enum CodingKeys: String, CodingKey {
case host
case url
}
}

View File

@ -1,57 +0,0 @@
//
// FeedbinSavedSearch.swift
// Account
//
// Created by Brent Simmons on 10/14/18.
// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
//
import Foundation
struct FeedbinSavedSearch: Hashable {
// https://github.com/feedbin/feedbin-api/blob/master/content/saved-searches.md
//
// [
// {
// "id": 1,
// "name": "JavaScript",
// "query": "javascript is:unread"
// }
// ]
let uniqueID: Int
let name: String
let query: String
private struct Key {
static let uniqueID = "id"
static let name = "name"
static let query = "query"
}
init?(jsonDictionary: [String: Any]) {
guard let uniqueID = jsonDictionary[Key.uniqueID] as? Int else {
return nil
}
guard let name = jsonDictionary[Key.name] as? String else {
return nil
}
guard let query = jsonDictionary[Key.query] as? String else {
return nil
}
self.uniqueID = uniqueID
self.name = name
self.query = query
}
static func savedSearches(with jsonArray: [Any]) -> Set<FeedbinSavedSearch> {
let searches = jsonArray.compactMap { (oneSearch) -> FeedbinSavedSearch? in
if let oneSearch = oneSearch as? [String: Any] {
return FeedbinSavedSearch(jsonDictionary: oneSearch)
}
return nil
}
return Set(searches)
}
}

View File

@ -10,20 +10,10 @@ import Foundation
import RSCore
import RSParser
struct FeedbinFeed: Codable {
// https://github.com/feedbin/feedbin-api/blob/master/content/feeds.md
//
// "id": 525,
// "created_at": "2013-03-12T11:30:25.209432Z",
// "feed_id": 47,
// "title": "Daring Fireball",
// "feed_url": "http://daringfireball.net/index.xml",
// "site_url": "http://daringfireball.net/"
struct FeedbinSubscription: Codable {
let subscriptionID: Int
let feedID: Int
let creationDate: Date?
let name: String?
let url: String
let homePageURL: String?
@ -31,7 +21,6 @@ struct FeedbinFeed: Codable {
enum CodingKeys: String, CodingKey {
case subscriptionID = "id"
case feedID = "feed_id"
case creationDate = "created_at"
case name = "title"
case url = "feed_url"
case homePageURL = "site_url"

View File

@ -8,7 +8,7 @@
import Foundation
struct FeedbinTag: Codable, Equatable, Hashable {
struct FeedbinTag: Codable {
let tagID: Int
let name: String

View File

@ -8,7 +8,7 @@
import Foundation
struct FeedbinTagging: Codable, Equatable, Hashable {
struct FeedbinTagging: Codable {
let taggingID: Int
let feedID: Int

View File

@ -170,9 +170,7 @@ private extension AddFeedController {
return
}
guard let feed = account.createFeed(with: titleFromFeed, editedName: userEnteredTitle, url: feedURLString) else {
return
}
let feed = account.createFeed(with: titleFromFeed, editedName: userEnteredTitle, url: feedURLString)
if let parsedFeed = parsedFeed {
account.update(feed, with: parsedFeed, {})

View File

@ -114,10 +114,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
if let parsedFeed = parsedFeedOptional {
let titleFromFeed = parsedFeed.title
guard let feed = account.createFeed(with: titleFromFeed, editedName: titleFromArgs, url: url) else {
command.resumeExecution(withResult:nil)
return
}
let feed = account.createFeed(with: titleFromFeed, editedName: titleFromArgs, url: url)
account.update(feed, with:parsedFeed, {})
// add the feed, putting it in a folder if needed

View File

@ -220,10 +220,7 @@ private extension AddFeedViewController {
return
}
guard let feed = account.createFeed(with: titleFromFeed, editedName: userEnteredTitle, url: feedURLString) else {
delegate?.processingDidEnd()
return
}
let feed = account.createFeed(with: titleFromFeed, editedName: userEnteredTitle, url: feedURLString)
if let parsedFeed = parsedFeed {
account.update(feed, with: parsedFeed, {})

@ -1 +1 @@
Subproject commit d1d5eba957eefec54b9a8c8648024a389c2271f0
Subproject commit 4cf5b71a292573c71ca212997a453f9158e95db2