Add download feed to folder relationships syncing
This commit is contained in:
parent
ae61d36c7d
commit
52e5e43d10
|
@ -594,6 +594,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||||
return _flattenedFeeds
|
return _flattenedFeeds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deleteFeeds(_ feeds: Set<Feed>) {
|
||||||
|
topLevelFeeds.subtract(feeds)
|
||||||
|
structureDidChange()
|
||||||
|
postChildrenDidChangeNotification()
|
||||||
|
}
|
||||||
|
|
||||||
public func deleteFeed(_ feed: Feed) {
|
public func deleteFeed(_ feed: Feed) {
|
||||||
topLevelFeeds.remove(feed)
|
topLevelFeeds.remove(feed)
|
||||||
structureDidChange()
|
structureDidChange()
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
5133231122810EB200C30F19 /* FeedbinIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133230F22810E5700C30F19 /* FeedbinIcon.swift */; };
|
5133231122810EB200C30F19 /* FeedbinIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5133230F22810E5700C30F19 /* FeedbinIcon.swift */; };
|
||||||
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA48227B497600D19003 /* FeedbinAPICaller.swift */; };
|
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA48227B497600D19003 /* FeedbinAPICaller.swift */; };
|
||||||
5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */; };
|
5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */; };
|
||||||
|
5165D7122282080C00D9D53D /* AccountFolderContentsSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5165D7112282080C00D9D53D /* AccountFolderContentsSyncTest.swift */; };
|
||||||
|
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 */; };
|
||||||
51D58755227F53BE00900287 /* FeedbinTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D58754227F53BE00900287 /* FeedbinTag.swift */; };
|
51D58755227F53BE00900287 /* FeedbinTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D58754227F53BE00900287 /* FeedbinTag.swift */; };
|
||||||
51D5875A227F630B00900287 /* tags_delete.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58757227F630B00900287 /* tags_delete.json */; };
|
51D5875A227F630B00900287 /* tags_delete.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58757227F630B00900287 /* tags_delete.json */; };
|
||||||
51D5875B227F630B00900287 /* tags_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58758227F630B00900287 /* tags_add.json */; };
|
51D5875B227F630B00900287 /* tags_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58758227F630B00900287 /* tags_add.json */; };
|
||||||
|
@ -105,6 +109,10 @@
|
||||||
5133230F22810E5700C30F19 /* FeedbinIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinIcon.swift; 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>"; };
|
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>"; };
|
5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAccountDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
5165D7112282080C00D9D53D /* AccountFolderContentsSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFolderContentsSyncTest.swift; sourceTree = "<group>"; };
|
||||||
|
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>"; };
|
||||||
51D58754227F53BE00900287 /* FeedbinTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinTag.swift; sourceTree = "<group>"; };
|
51D58754227F53BE00900287 /* FeedbinTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinTag.swift; sourceTree = "<group>"; };
|
||||||
51D58757227F630B00900287 /* tags_delete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_delete.json; sourceTree = "<group>"; };
|
51D58757227F630B00900287 /* tags_delete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_delete.json; sourceTree = "<group>"; };
|
||||||
51D58758227F630B00900287 /* tags_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_add.json; sourceTree = "<group>"; };
|
51D58758227F630B00900287 /* tags_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_add.json; sourceTree = "<group>"; };
|
||||||
|
@ -173,12 +181,15 @@
|
||||||
51D58756227F62E300900287 /* JSON */ = {
|
51D58756227F62E300900287 /* JSON */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5133230D2281089500C30F19 /* icons.json */,
|
||||||
|
5133230B2281088A00C30F19 /* subscriptions_add.json */,
|
||||||
|
513323092281082F00C30F19 /* subscriptions_initial.json */,
|
||||||
|
5165D71422821C2400D9D53D /* taggings_add.json */,
|
||||||
|
5165D71322821C2400D9D53D /* taggings_delete.json */,
|
||||||
|
5165D71522821C2400D9D53D /* taggings_initial.json */,
|
||||||
51D58758227F630B00900287 /* tags_add.json */,
|
51D58758227F630B00900287 /* tags_add.json */,
|
||||||
51D58757227F630B00900287 /* tags_delete.json */,
|
51D58757227F630B00900287 /* tags_delete.json */,
|
||||||
51D58759227F630B00900287 /* tags_initial.json */,
|
51D58759227F630B00900287 /* tags_initial.json */,
|
||||||
513323092281082F00C30F19 /* subscriptions_initial.json */,
|
|
||||||
5133230B2281088A00C30F19 /* subscriptions_add.json */,
|
|
||||||
5133230D2281089500C30F19 /* icons.json */,
|
|
||||||
);
|
);
|
||||||
path = JSON;
|
path = JSON;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -280,6 +291,7 @@
|
||||||
5107A098227DE42E00C7C3C5 /* AccountCredentialsTest.swift */,
|
5107A098227DE42E00C7C3C5 /* AccountCredentialsTest.swift */,
|
||||||
51D5875D227F643C00900287 /* AccountFolderSyncTest.swift */,
|
51D5875D227F643C00900287 /* AccountFolderSyncTest.swift */,
|
||||||
513323072281070C00C30F19 /* AccountFeedSyncTest.swift */,
|
513323072281070C00C30F19 /* AccountFeedSyncTest.swift */,
|
||||||
|
5165D7112282080C00D9D53D /* AccountFolderContentsSyncTest.swift */,
|
||||||
5107A09C227DE77700C7C3C5 /* TestTransport.swift */,
|
5107A09C227DE77700C7C3C5 /* TestTransport.swift */,
|
||||||
5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */,
|
5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */,
|
||||||
51D58756227F62E300900287 /* JSON */,
|
51D58756227F62E300900287 /* JSON */,
|
||||||
|
@ -448,11 +460,14 @@
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
5165D71822821C2400D9D53D /* taggings_initial.json in Resources */,
|
||||||
5133230E2281089500C30F19 /* icons.json in Resources */,
|
5133230E2281089500C30F19 /* icons.json in Resources */,
|
||||||
51D5875B227F630B00900287 /* tags_add.json in Resources */,
|
51D5875B227F630B00900287 /* tags_add.json in Resources */,
|
||||||
5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */,
|
5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */,
|
||||||
51D5875C227F630B00900287 /* tags_initial.json in Resources */,
|
51D5875C227F630B00900287 /* tags_initial.json in Resources */,
|
||||||
51D5875A227F630B00900287 /* tags_delete.json in Resources */,
|
51D5875A227F630B00900287 /* tags_delete.json in Resources */,
|
||||||
|
5165D71722821C2400D9D53D /* taggings_add.json in Resources */,
|
||||||
|
5165D71622821C2400D9D53D /* taggings_delete.json in Resources */,
|
||||||
5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */,
|
5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -493,6 +508,7 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
5165D7122282080C00D9D53D /* AccountFolderContentsSyncTest.swift in Sources */,
|
||||||
51D5875E227F643C00900287 /* AccountFolderSyncTest.swift in Sources */,
|
51D5875E227F643C00900287 /* AccountFolderSyncTest.swift in Sources */,
|
||||||
5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */,
|
5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */,
|
||||||
513323082281070D00C30F19 /* AccountFeedSyncTest.swift in Sources */,
|
513323082281070D00C30F19 /* AccountFeedSyncTest.swift in Sources */,
|
||||||
|
|
|
@ -43,7 +43,6 @@ class AccountFeedSyncTest: XCTestCase {
|
||||||
// Test Adding a Feed
|
// Test Adding a Feed
|
||||||
testTransport.testFiles["https://api.feedbin.com/v2/subscriptions.json"] = "subscriptions_add.json"
|
testTransport.testFiles["https://api.feedbin.com/v2/subscriptions.json"] = "subscriptions_add.json"
|
||||||
|
|
||||||
// Test initial folders
|
|
||||||
let addExpection = self.expectation(description: "Add feeds")
|
let addExpection = self.expectation(description: "Add feeds")
|
||||||
account.refreshAll() {
|
account.refreshAll() {
|
||||||
addExpection.fulfill()
|
addExpection.fulfill()
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
//
|
||||||
|
// AccountFolderContentsSyncTest.swift
|
||||||
|
// AccountTests
|
||||||
|
//
|
||||||
|
// Created by Maurice Parker on 5/7/19.
|
||||||
|
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import Account
|
||||||
|
|
||||||
|
class AccountFolderContentsSyncTest: 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/taggings.json"] = "taggings_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 contents")
|
||||||
|
account.refreshAll() {
|
||||||
|
initialExpection.fulfill()
|
||||||
|
}
|
||||||
|
waitForExpectations(timeout: 5, handler: nil)
|
||||||
|
|
||||||
|
let folder = account.folders?.filter { $0.name == "Developers" } .first!
|
||||||
|
XCTAssertEqual(156, folder?.topLevelFeeds.count ?? 0)
|
||||||
|
XCTAssertEqual(2, account.topLevelFeeds.count)
|
||||||
|
|
||||||
|
// Test Adding a Feed to the folder
|
||||||
|
testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "taggings_add.json"
|
||||||
|
|
||||||
|
let addExpection = self.expectation(description: "Add contents")
|
||||||
|
account.refreshAll() {
|
||||||
|
addExpection.fulfill()
|
||||||
|
}
|
||||||
|
waitForExpectations(timeout: 5, handler: nil)
|
||||||
|
|
||||||
|
XCTAssertEqual(157, folder?.topLevelFeeds.count ?? 0)
|
||||||
|
XCTAssertEqual(1, account.topLevelFeeds.count)
|
||||||
|
|
||||||
|
// Test Deleting some Feeds from the folder
|
||||||
|
testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "taggings_delete.json"
|
||||||
|
|
||||||
|
let deleteExpection = self.expectation(description: "Delete contents")
|
||||||
|
account.refreshAll() {
|
||||||
|
deleteExpection.fulfill()
|
||||||
|
}
|
||||||
|
waitForExpectations(timeout: 5, handler: nil)
|
||||||
|
|
||||||
|
XCTAssertEqual(153, folder?.topLevelFeeds.count ?? 0)
|
||||||
|
XCTAssertEqual(5, account.topLevelFeeds.count)
|
||||||
|
|
||||||
|
TestAccountManager.shared.deleteAccount(account)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -113,6 +113,26 @@ final class FeedbinAPICaller: NSObject {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func retrieveTaggings(completionHandler completion: @escaping (Result<[FeedbinTagging]?, Error>) -> Void) {
|
||||||
|
|
||||||
|
let callURL = feedbinBaseURL.appendingPathComponent("taggings.json")
|
||||||
|
let conditionalGet = accountMetadata?.conditionalGetInfo[AccountMetadata.ConditionalGetKeys.taggings]
|
||||||
|
let request = URLRequest(url: callURL, credentials: credentials, conditionalGet: conditionalGet)
|
||||||
|
|
||||||
|
transport.send(request: request, resultType: [FeedbinTagging].self) { [weak self] result in
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let (headers, taggings)):
|
||||||
|
self?.storeConditionalGet(metadata: self?.accountMetadata, key: AccountMetadata.ConditionalGetKeys.taggings, headers: headers)
|
||||||
|
completion(.success(taggings))
|
||||||
|
case .failure(let error):
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func retrieveIcons(completionHandler completion: @escaping (Result<[FeedbinIcon]?, Error>) -> Void) {
|
func retrieveIcons(completionHandler completion: @escaping (Result<[FeedbinIcon]?, Error>) -> Void) {
|
||||||
|
|
||||||
let callURL = feedbinBaseURL.appendingPathComponent("icons.json")
|
let callURL = feedbinBaseURL.appendingPathComponent("icons.json")
|
||||||
|
|
|
@ -81,16 +81,22 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
caller.deleteTag(name: folder.name ?? "") { result in
|
// After we successfully delete at Feedbin, we add all the feeds to the account to save them. We then
|
||||||
|
// delete the folder. We then sync the taggings we received on the delete to remove any feeds from
|
||||||
|
// the account that might be in another folder.
|
||||||
|
caller.deleteTag(name: folder.name ?? "") { [weak self] result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success:
|
case .success(let taggings):
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.sync {
|
||||||
|
BatchUpdate.shared.perform {
|
||||||
account.deleteFolder(folder)
|
for feed in folder.topLevelFeeds {
|
||||||
// TODO: Take the serialized taggings and reestablish the folder to feed relationships. Deleting
|
account.addFeed(feed, to: nil)
|
||||||
// a tag on Feedbin doesn't any feeds.
|
}
|
||||||
|
account.deleteFolder(folder)
|
||||||
|
}
|
||||||
completion(.success(()))
|
completion(.success(()))
|
||||||
}
|
}
|
||||||
|
self?.syncTaggings(account, taggings)
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
completion(.failure(error))
|
completion(.failure(error))
|
||||||
|
@ -160,6 +166,9 @@ private extension FeedbinAccountDelegate {
|
||||||
folders.forEach { folder in
|
folders.forEach { folder in
|
||||||
if !tagNames.contains(folder.name ?? "") {
|
if !tagNames.contains(folder.name ?? "") {
|
||||||
DispatchQueue.main.sync {
|
DispatchQueue.main.sync {
|
||||||
|
for feed in folder.topLevelFeeds {
|
||||||
|
account.addFeed(feed, to: nil)
|
||||||
|
}
|
||||||
account.deleteFolder(folder)
|
account.deleteFolder(folder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,58 +195,168 @@ private extension FeedbinAccountDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshFeeds(_ account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
func refreshFeeds(_ account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
|
|
||||||
caller.retrieveSubscriptions { [weak self] result in
|
caller.retrieveSubscriptions { [weak self] result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let subscriptions):
|
case .success(let subscriptions):
|
||||||
self?.syncFeeds(account, subscriptions)
|
|
||||||
self?.refreshFavicons(account, completion: completion)
|
self?.caller.retrieveTaggings { [weak self] result in
|
||||||
|
switch result {
|
||||||
|
case .success(let taggings):
|
||||||
|
|
||||||
|
self?.caller.retrieveIcons { [weak self] result in
|
||||||
|
switch result {
|
||||||
|
case .success(let icons):
|
||||||
|
|
||||||
|
BatchUpdate.shared.perform {
|
||||||
|
self?.syncFeeds(account, subscriptions)
|
||||||
|
self?.syncTaggings(account, taggings)
|
||||||
|
self?.syncFavicons(account, icons)
|
||||||
|
}
|
||||||
|
|
||||||
|
completion(.success(()))
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
completion(.failure(error))
|
completion(.failure(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncFeeds(_ account: Account, _ subscriptions: [FeedbinSubscription]?) {
|
func syncFeeds(_ account: Account, _ subscriptions: [FeedbinSubscription]?) {
|
||||||
|
|
||||||
guard let subscriptions = subscriptions else { return }
|
guard let subscriptions = subscriptions else { return }
|
||||||
BatchUpdate.shared.perform {
|
|
||||||
subscriptions.forEach { subscription in
|
let subFeedIds = subscriptions.map { String($0.feedID) }
|
||||||
syncFeed(account, subscription)
|
|
||||||
|
// Remove any feeds that are no longer in the subscriptions
|
||||||
|
if let folders = account.folders {
|
||||||
|
for folder in folders {
|
||||||
|
for feed in folder.topLevelFeeds {
|
||||||
|
if !subFeedIds.contains(feed.feedID) {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
folder.deleteFeed(feed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for feed in account.topLevelFeeds {
|
||||||
|
if !subFeedIds.contains(feed.feedID) {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
account.deleteFeed(feed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any feeds we don't have and update any we do
|
||||||
|
subscriptions.forEach { subscription in
|
||||||
|
|
||||||
|
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 syncFeed(_ account: Account, _ subscription: FeedbinSubscription) {
|
func syncTaggings(_ account: Account, _ taggings: [FeedbinTagging]?) {
|
||||||
|
|
||||||
let subFeedId = String(subscription.feedID)
|
guard let taggings = taggings else { return }
|
||||||
|
|
||||||
|
// Set up some structures to make syncing easier
|
||||||
|
let folderDict: [String: Folder] = {
|
||||||
|
if let folders = account.folders {
|
||||||
|
return Dictionary(uniqueKeysWithValues: folders.map { ($0.name ?? "", $0) } )
|
||||||
|
} else {
|
||||||
|
return [String: Folder]()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
let taggingsDict = taggings.reduce([String: [String]]()) { (dict, tagging) in
|
||||||
|
var taggedFeeds = dict
|
||||||
|
if var taggedFeed = taggedFeeds[tagging.name] {
|
||||||
|
taggedFeed.append(String(tagging.feedID))
|
||||||
|
taggedFeeds[tagging.name] = taggedFeed
|
||||||
|
} else {
|
||||||
|
taggedFeeds[tagging.name] = [String(tagging.feedID)]
|
||||||
|
}
|
||||||
|
return taggedFeeds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync the folders
|
||||||
|
for (folderName, feedIDs) in taggingsDict {
|
||||||
|
|
||||||
|
guard let folder = folderDict[folderName] else { return }
|
||||||
|
|
||||||
|
// Move any feeds not in the folder to the account
|
||||||
|
for feed in folder.topLevelFeeds {
|
||||||
|
if !feedIDs.contains(feed.feedID) {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
folder.deleteFeed(feed)
|
||||||
|
account.addFeed(feed, to: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any feeds not in the folder
|
||||||
|
let folderFeedIds = folder.topLevelFeeds.map { $0.feedID }
|
||||||
|
|
||||||
|
var feedsToAdd = Set<Feed>()
|
||||||
|
for feedId in feedIDs {
|
||||||
|
if !folderFeedIds.contains(feedId) {
|
||||||
|
guard let feed = account.idToFeedDictionary[feedId] else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
feedsToAdd.insert(feed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
folder.addFeeds(feedsToAdd)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let taggedFeedIds = Set(taggings.map { String($0.feedID) })
|
||||||
|
|
||||||
|
// Delete all the feeds without a tag
|
||||||
|
var feedsToDelete = Set<Feed>()
|
||||||
|
for feed in account.topLevelFeeds {
|
||||||
|
if taggedFeedIds.contains(feed.feedID) {
|
||||||
|
feedsToDelete.insert(feed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DispatchQueue.main.sync {
|
DispatchQueue.main.sync {
|
||||||
if let feed = account.idToFeedDictionary[subFeedId] {
|
account.deleteFeeds(feedsToDelete)
|
||||||
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]?) {
|
func syncFavicons(_ account: Account, _ icons: [FeedbinIcon]?) {
|
||||||
|
|
||||||
guard let icons = icons else { return }
|
guard let icons = icons else { return }
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue