From 52e5e43d1093965983b873eca6f63bb70960974e Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 7 May 2019 17:41:32 -0500 Subject: [PATCH] Add download feed to folder relationships syncing --- Frameworks/Account/Account.swift | 6 + .../Account/Account.xcodeproj/project.pbxproj | 22 +- .../AccountTests/AccountFeedSyncTest.swift | 1 - .../AccountFolderContentsSyncTest.swift | 68 + .../AccountTests/JSON/taggings_add.json | 1117 +++++++++++++++++ .../AccountTests/JSON/taggings_delete.json | 1097 ++++++++++++++++ .../AccountTests/JSON/taggings_initial.json | 1112 ++++++++++++++++ .../Account/Feedbin/FeedbinAPICaller.swift | 20 + .../Feedbin/FeedbinAccountDelegate.swift | 191 ++- 9 files changed, 3594 insertions(+), 40 deletions(-) create mode 100644 Frameworks/Account/AccountTests/AccountFolderContentsSyncTest.swift create mode 100644 Frameworks/Account/AccountTests/JSON/taggings_add.json create mode 100644 Frameworks/Account/AccountTests/JSON/taggings_delete.json create mode 100644 Frameworks/Account/AccountTests/JSON/taggings_initial.json diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 9d164cf68..6a914f1c3 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -594,6 +594,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, return _flattenedFeeds } + func deleteFeeds(_ feeds: Set) { + topLevelFeeds.subtract(feeds) + structureDidChange() + postChildrenDidChangeNotification() + } + public func deleteFeed(_ feed: Feed) { topLevelFeeds.remove(feed) structureDidChange() diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index d3e2ec323..1650498b1 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -17,6 +17,10 @@ 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 */; }; + 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 */; }; 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 */; }; @@ -105,6 +109,10 @@ 5133230F22810E5700C30F19 /* FeedbinIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinIcon.swift; sourceTree = ""; }; 5144EA48227B497600D19003 /* FeedbinAPICaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAPICaller.swift; sourceTree = ""; }; 5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAccountDelegate.swift; sourceTree = ""; }; + 5165D7112282080C00D9D53D /* AccountFolderContentsSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFolderContentsSyncTest.swift; sourceTree = ""; }; + 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 = ""; }; 51D58754227F53BE00900287 /* FeedbinTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinTag.swift; sourceTree = ""; }; 51D58757227F630B00900287 /* tags_delete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_delete.json; sourceTree = ""; }; 51D58758227F630B00900287 /* tags_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_add.json; sourceTree = ""; }; @@ -173,12 +181,15 @@ 51D58756227F62E300900287 /* JSON */ = { isa = PBXGroup; 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 */, 51D58757227F630B00900287 /* tags_delete.json */, 51D58759227F630B00900287 /* tags_initial.json */, - 513323092281082F00C30F19 /* subscriptions_initial.json */, - 5133230B2281088A00C30F19 /* subscriptions_add.json */, - 5133230D2281089500C30F19 /* icons.json */, ); path = JSON; sourceTree = ""; @@ -280,6 +291,7 @@ 5107A098227DE42E00C7C3C5 /* AccountCredentialsTest.swift */, 51D5875D227F643C00900287 /* AccountFolderSyncTest.swift */, 513323072281070C00C30F19 /* AccountFeedSyncTest.swift */, + 5165D7112282080C00D9D53D /* AccountFolderContentsSyncTest.swift */, 5107A09C227DE77700C7C3C5 /* TestTransport.swift */, 5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */, 51D58756227F62E300900287 /* JSON */, @@ -448,11 +460,14 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5165D71822821C2400D9D53D /* taggings_initial.json in Resources */, 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 */, + 5165D71722821C2400D9D53D /* taggings_add.json in Resources */, + 5165D71622821C2400D9D53D /* taggings_delete.json in Resources */, 5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -493,6 +508,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5165D7122282080C00D9D53D /* AccountFolderContentsSyncTest.swift in Sources */, 51D5875E227F643C00900287 /* AccountFolderSyncTest.swift in Sources */, 5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */, 513323082281070D00C30F19 /* AccountFeedSyncTest.swift in Sources */, diff --git a/Frameworks/Account/AccountTests/AccountFeedSyncTest.swift b/Frameworks/Account/AccountTests/AccountFeedSyncTest.swift index 37c871ea9..07f42ace5 100644 --- a/Frameworks/Account/AccountTests/AccountFeedSyncTest.swift +++ b/Frameworks/Account/AccountTests/AccountFeedSyncTest.swift @@ -43,7 +43,6 @@ class AccountFeedSyncTest: XCTestCase { // 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() diff --git a/Frameworks/Account/AccountTests/AccountFolderContentsSyncTest.swift b/Frameworks/Account/AccountTests/AccountFolderContentsSyncTest.swift new file mode 100644 index 000000000..0be0ee639 --- /dev/null +++ b/Frameworks/Account/AccountTests/AccountFolderContentsSyncTest.swift @@ -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) + + } + +} diff --git a/Frameworks/Account/AccountTests/JSON/taggings_add.json b/Frameworks/Account/AccountTests/JSON/taggings_add.json new file mode 100644 index 000000000..a3553679b --- /dev/null +++ b/Frameworks/Account/AccountTests/JSON/taggings_add.json @@ -0,0 +1,1117 @@ +[ + { + "id": 4225485, + "feed_id": 1486863, + "name": "Developers" + }, + { + "id": 4225484, + "feed_id": 15452, + "name": "Development Orgs" + }, + { + "id": 4225483, + "feed_id": 1578301, + "name": "Developers" + }, + { + "id": 4225482, + "feed_id": 1364385, + "name": "Developers" + }, + { + "id": 4225481, + "feed_id": 1597435, + "name": "Developers" + }, + { + "id": 4225480, + "feed_id": 98472, + "name": "Amusement" + }, + { + "id": 4225479, + "feed_id": 1486842, + "name": "Developers" + }, + { + "id": 4225478, + "feed_id": 1605375, + "name": "Developers" + }, + { + "id": 4225477, + "feed_id": 1388169, + "name": "Developers" + }, + { + "id": 4225476, + "feed_id": 1913, + "name": "Tech Media" + }, + { + "id": 4225475, + "feed_id": 105, + "name": "Tech Media" + }, + { + "id": 4225474, + "feed_id": 1424879, + "name": "Developers" + }, + { + "id": 4225473, + "feed_id": 1176579, + "name": "Developers" + }, + { + "id": 4225472, + "feed_id": 1713, + "name": "Tech Media" + }, + { + "id": 4225471, + "feed_id": 1605374, + "name": "Vanlife" + }, + { + "id": 4225470, + "feed_id": 547888, + "name": "Development Orgs" + }, + { + "id": 4225469, + "feed_id": 1528640, + "name": "Business" + }, + { + "id": 4225468, + "feed_id": 1211804, + "name": "Development Orgs" + }, + { + "id": 4225467, + "feed_id": 1528652, + "name": "Business" + }, + { + "id": 4225466, + "feed_id": 1221017, + "name": "Tech Media" + }, + { + "id": 4225465, + "feed_id": 1486846, + "name": "Development Orgs" + }, + { + "id": 4225464, + "feed_id": 1340283, + "name": "Development Orgs" + }, + { + "id": 4225463, + "feed_id": 1735, + "name": "Tech Media" + }, + { + "id": 4225462, + "feed_id": 143536, + "name": "Development Orgs" + }, + { + "id": 4225461, + "feed_id": 1176732, + "name": "Development Orgs" + }, + { + "id": 4225460, + "feed_id": 1241233, + "name": "Tech Media" + }, + { + "id": 4225459, + "feed_id": 1062583, + "name": "Development Orgs" + }, + { + "id": 4225458, + "feed_id": 387024, + "name": "Development Orgs" + }, + { + "id": 4225457, + "feed_id": 1605285, + "name": "Development Orgs" + }, + { + "id": 4225456, + "feed_id": 1229997, + "name": "Development Orgs" + }, + { + "id": 4225455, + "feed_id": 1528975, + "name": "Development Orgs" + }, + { + "id": 4225454, + "feed_id": 1488022, + "name": "Development Orgs" + }, + { + "id": 4225453, + "feed_id": 1067438, + "name": "Development Orgs" + }, + { + "id": 4225452, + "feed_id": 1538463, + "name": "Development Orgs" + }, + { + "id": 4225451, + "feed_id": 1528649, + "name": "Business" + }, + { + "id": 4225450, + "feed_id": 1528643, + "name": "Business" + }, + { + "id": 4225449, + "feed_id": 1320545, + "name": "Business" + }, + { + "id": 4225448, + "feed_id": 1113164, + "name": "Tech Media" + }, + { + "id": 4225447, + "feed_id": 1528650, + "name": "Business" + }, + { + "id": 4225446, + "feed_id": 1463575, + "name": "Tech Media" + }, + { + "id": 4225445, + "feed_id": 747836, + "name": "Open Web" + }, + { + "id": 4225444, + "feed_id": 1051845, + "name": "Developers" + }, + { + "id": 4225443, + "feed_id": 1211114, + "name": "Tech Media" + }, + { + "id": 4225442, + "feed_id": 1030946, + "name": "Developers" + }, + { + "id": 4225441, + "feed_id": 1388089, + "name": "Developers" + }, + { + "id": 4225440, + "feed_id": 1472912, + "name": "Developers" + }, + { + "id": 4225439, + "feed_id": 169025, + "name": "Amusement" + }, + { + "id": 4225438, + "feed_id": 1099459, + "name": "Developers" + }, + { + "id": 4225437, + "feed_id": 1578297, + "name": "Developers" + }, + { + "id": 4225436, + "feed_id": 1424888, + "name": "Developers" + }, + { + "id": 4225435, + "feed_id": 1177901, + "name": "Developers" + }, + { + "id": 4225434, + "feed_id": 1014335, + "name": "Developers" + }, + { + "id": 4225433, + "feed_id": 1486813, + "name": "Developers" + }, + { + "id": 4225432, + "feed_id": 1436884, + "name": "Developers" + }, + { + "id": 4225431, + "feed_id": 1468269, + "name": "Developers" + }, + { + "id": 4225430, + "feed_id": 1453910, + "name": "Developers" + }, + { + "id": 4225429, + "feed_id": 1064554, + "name": "Developers" + }, + { + "id": 4225428, + "feed_id": 1505016, + "name": "Developers" + }, + { + "id": 4225427, + "feed_id": 1578290, + "name": "Developers" + }, + { + "id": 4225426, + "feed_id": 1545252, + "name": "Developers" + }, + { + "id": 4225425, + "feed_id": 1127707, + "name": "Developers" + }, + { + "id": 4225424, + "feed_id": 1094799, + "name": "Developers" + }, + { + "id": 4225423, + "feed_id": 1525481, + "name": "Developers" + }, + { + "id": 4225422, + "feed_id": 780733, + "name": "Developers" + }, + { + "id": 4225421, + "feed_id": 1486854, + "name": "Developers" + }, + { + "id": 4225420, + "feed_id": 1559907, + "name": "Developers" + }, + { + "id": 4225419, + "feed_id": 1565908, + "name": "Developers" + }, + { + "id": 4225418, + "feed_id": 1486812, + "name": "Developers" + }, + { + "id": 4225417, + "feed_id": 1516541, + "name": "Developers" + }, + { + "id": 4225416, + "feed_id": 230857, + "name": "Developers" + }, + { + "id": 4225415, + "feed_id": 37, + "name": "Developers" + }, + { + "id": 4225414, + "feed_id": 1177914, + "name": "Developers" + }, + { + "id": 4225413, + "feed_id": 1556686, + "name": "Developers" + }, + { + "id": 4225412, + "feed_id": 1559420, + "name": "Developers" + }, + { + "id": 4225411, + "feed_id": 1597437, + "name": "Developers" + }, + { + "id": 4225410, + "feed_id": 1388165, + "name": "Developers" + }, + { + "id": 4225409, + "feed_id": 1344882, + "name": "Developers" + }, + { + "id": 4225408, + "feed_id": 60926, + "name": "Developers" + }, + { + "id": 4225407, + "feed_id": 1553728, + "name": "Developers" + }, + { + "id": 4225406, + "feed_id": 1418311, + "name": "Developers" + }, + { + "id": 4225405, + "feed_id": 1578271, + "name": "Developers" + }, + { + "id": 4225404, + "feed_id": 1221779, + "name": "Developers" + }, + { + "id": 4225403, + "feed_id": 1463706, + "name": "Developers" + }, + { + "id": 4225402, + "feed_id": 1578299, + "name": "Developers" + }, + { + "id": 4225401, + "feed_id": 1078919, + "name": "Developers" + }, + { + "id": 4225400, + "feed_id": 1596199, + "name": "Developers" + }, + { + "id": 4225399, + "feed_id": 1486828, + "name": "Developers" + }, + { + "id": 4225398, + "feed_id": 874888, + "name": "Developers" + }, + { + "id": 4225397, + "feed_id": 1578282, + "name": "Developers" + }, + { + "id": 4225396, + "feed_id": 1377938, + "name": "Developers" + }, + { + "id": 4225395, + "feed_id": 961696, + "name": "Developers" + }, + { + "id": 4225394, + "feed_id": 25184, + "name": "Developers" + }, + { + "id": 4225393, + "feed_id": 1150642, + "name": "Developers" + }, + { + "id": 4225392, + "feed_id": 1449461, + "name": "Developers" + }, + { + "id": 4225391, + "feed_id": 1424885, + "name": "Developers" + }, + { + "id": 4225390, + "feed_id": 989928, + "name": "Developers" + }, + { + "id": 4225389, + "feed_id": 1389178, + "name": "Developers" + }, + { + "id": 4225388, + "feed_id": 1346114, + "name": "Developers" + }, + { + "id": 4225387, + "feed_id": 1481847, + "name": "Developers" + }, + { + "id": 4225386, + "feed_id": 1487018, + "name": "Developers" + }, + { + "id": 4225385, + "feed_id": 1578279, + "name": "Developers" + }, + { + "id": 4225384, + "feed_id": 1424889, + "name": "Developers" + }, + { + "id": 4225383, + "feed_id": 1012582, + "name": "Developers" + }, + { + "id": 4225382, + "feed_id": 1095014, + "name": "Developers" + }, + { + "id": 4225381, + "feed_id": 1578284, + "name": "Developers" + }, + { + "id": 4225380, + "feed_id": 1424878, + "name": "Developers" + }, + { + "id": 4225379, + "feed_id": 1296383, + "name": "Developers" + }, + { + "id": 4225378, + "feed_id": 1377354, + "name": "Developers" + }, + { + "id": 4225377, + "feed_id": 1510180, + "name": "Developers" + }, + { + "id": 4225376, + "feed_id": 1237279, + "name": "Developers" + }, + { + "id": 4225375, + "feed_id": 1388622, + "name": "Developers" + }, + { + "id": 4225374, + "feed_id": 1578292, + "name": "Developers" + }, + { + "id": 4225373, + "feed_id": 1505017, + "name": "Developers" + }, + { + "id": 4225372, + "feed_id": 1578300, + "name": "Developers" + }, + { + "id": 4225371, + "feed_id": 1572820, + "name": "Developers" + }, + { + "id": 4225370, + "feed_id": 1578275, + "name": "Developers" + }, + { + "id": 4225369, + "feed_id": 1511092, + "name": "Developers" + }, + { + "id": 4225368, + "feed_id": 1399906, + "name": "Developers" + }, + { + "id": 4225367, + "feed_id": 1486836, + "name": "Developers" + }, + { + "id": 4225366, + "feed_id": 1578288, + "name": "Developers" + }, + { + "id": 4225365, + "feed_id": 1424887, + "name": "Developers" + }, + { + "id": 4225364, + "feed_id": 861611, + "name": "Developers" + }, + { + "id": 4225363, + "feed_id": 1239214, + "name": "Developers" + }, + { + "id": 4225362, + "feed_id": 1149420, + "name": "Developers" + }, + { + "id": 4225361, + "feed_id": 1506640, + "name": "Developers" + }, + { + "id": 4225360, + "feed_id": 1418314, + "name": "Developers" + }, + { + "id": 4225359, + "feed_id": 26795, + "name": "Developers" + }, + { + "id": 4225358, + "feed_id": 1153329, + "name": "Developers" + }, + { + "id": 4225357, + "feed_id": 1119253, + "name": "Developers" + }, + { + "id": 4225356, + "feed_id": 1367414, + "name": "Developers" + }, + { + "id": 4225355, + "feed_id": 1501797, + "name": "Developers" + }, + { + "id": 4225354, + "feed_id": 913020, + "name": "Developers" + }, + { + "id": 4225353, + "feed_id": 95556, + "name": "Developers" + }, + { + "id": 4225352, + "feed_id": 1192662, + "name": "Developers" + }, + { + "id": 4225351, + "feed_id": 1510178, + "name": "Developers" + }, + { + "id": 4225350, + "feed_id": 1274281, + "name": "Developers" + }, + { + "id": 4225349, + "feed_id": 1365909, + "name": "Developers" + }, + { + "id": 4225348, + "feed_id": 1069647, + "name": "Developers" + }, + { + "id": 4225347, + "feed_id": 1576799, + "name": "Developers" + }, + { + "id": 4225346, + "feed_id": 17463, + "name": "Developers" + }, + { + "id": 4225345, + "feed_id": 1486826, + "name": "Developers" + }, + { + "id": 4225344, + "feed_id": 74, + "name": "Developers" + }, + { + "id": 4225343, + "feed_id": 1376302, + "name": "Developers" + }, + { + "id": 4225342, + "feed_id": 1578277, + "name": "Developers" + }, + { + "id": 4225341, + "feed_id": 35102, + "name": "Developers" + }, + { + "id": 4225340, + "feed_id": 1074742, + "name": "Developers" + }, + { + "id": 4225339, + "feed_id": 1296380, + "name": "Developers" + }, + { + "id": 4225338, + "feed_id": 1578287, + "name": "Developers" + }, + { + "id": 4225337, + "feed_id": 1200837, + "name": "Developers" + }, + { + "id": 4225336, + "feed_id": 1291156, + "name": "Developers" + }, + { + "id": 4225335, + "feed_id": 1338599, + "name": "Developers" + }, + { + "id": 4225334, + "feed_id": 1505579, + "name": "Developers" + }, + { + "id": 4225333, + "feed_id": 1424881, + "name": "Developers" + }, + { + "id": 4225332, + "feed_id": 1303511, + "name": "Developers" + }, + { + "id": 4225331, + "feed_id": 1388351, + "name": "Developers" + }, + { + "id": 4225330, + "feed_id": 1519998, + "name": "Developers" + }, + { + "id": 4225329, + "feed_id": 1578272, + "name": "Developers" + }, + { + "id": 4225328, + "feed_id": 16340, + "name": "Developers" + }, + { + "id": 4225327, + "feed_id": 1486815, + "name": "Developers" + }, + { + "id": 4225326, + "feed_id": 1525506, + "name": "Developers" + }, + { + "id": 4225325, + "feed_id": 1578273, + "name": "Developers" + }, + { + "id": 4225324, + "feed_id": 1359517, + "name": "Developers" + }, + { + "id": 4225323, + "feed_id": 1270217, + "name": "Developers" + }, + { + "id": 4225322, + "feed_id": 1021976, + "name": "Developers" + }, + { + "id": 4225321, + "feed_id": 860220, + "name": "Developers" + }, + { + "id": 4225320, + "feed_id": 1223531, + "name": "Developers" + }, + { + "id": 4225319, + "feed_id": 1017026, + "name": "Developers" + }, + { + "id": 4225318, + "feed_id": 1578296, + "name": "Developers" + }, + { + "id": 4225317, + "feed_id": 1381532, + "name": "Developers" + }, + { + "id": 4225316, + "feed_id": 1407941, + "name": "Developers" + }, + { + "id": 4225315, + "feed_id": 4269, + "name": "Developers" + }, + { + "id": 4225314, + "feed_id": 972279, + "name": "Developers" + }, + { + "id": 4225313, + "feed_id": 1539758, + "name": "Developers" + }, + { + "id": 4225312, + "feed_id": 1076603, + "name": "Developers" + }, + { + "id": 4225311, + "feed_id": 1411746, + "name": "Developers" + }, + { + "id": 4225310, + "feed_id": 694239, + "name": "Developers" + }, + { + "id": 4225309, + "feed_id": 1271644, + "name": "Developers" + }, + { + "id": 4225308, + "feed_id": 1578285, + "name": "Developers" + }, + { + "id": 4225307, + "feed_id": 1260873, + "name": "Developers" + }, + { + "id": 4225306, + "feed_id": 1082983, + "name": "Developers" + }, + { + "id": 4225305, + "feed_id": 1424893, + "name": "Developers" + }, + { + "id": 4225304, + "feed_id": 1023777, + "name": "Developers" + }, + { + "id": 4225303, + "feed_id": 1574913, + "name": "Developers" + }, + { + "id": 4225302, + "feed_id": 1578278, + "name": "Developers" + }, + { + "id": 4225301, + "feed_id": 1388263, + "name": "Developers" + }, + { + "id": 4225300, + "feed_id": 1418558, + "name": "Developers" + }, + { + "id": 4225299, + "feed_id": 1578274, + "name": "Developers" + }, + { + "id": 4225298, + "feed_id": 1418313, + "name": "Developers" + }, + { + "id": 4225297, + "feed_id": 1598316, + "name": "Developers" + }, + { + "id": 4225296, + "feed_id": 1372247, + "name": "Vanlife" + }, + { + "id": 4225295, + "feed_id": 1423653, + "name": "Developers" + }, + { + "id": 4225294, + "feed_id": 1255081, + "name": "Vanlife" + }, + { + "id": 4225293, + "feed_id": 1424883, + "name": "Developers" + }, + { + "id": 4225292, + "feed_id": 1510177, + "name": "Vanlife" + }, + { + "id": 4225291, + "feed_id": 1510185, + "name": "Vanlife" + }, + { + "id": 4225290, + "feed_id": 1510174, + "name": "Vanlife" + }, + { + "id": 4225289, + "feed_id": 1510176, + "name": "Vanlife" + }, + { + "id": 4225288, + "feed_id": 1510173, + "name": "Vanlife" + }, + { + "id": 4225287, + "feed_id": 1510183, + "name": "Vanlife" + }, + { + "id": 4225286, + "feed_id": 1510181, + "name": "Vanlife" + }, + { + "id": 4225285, + "feed_id": 1510186, + "name": "Vanlife" + }, + { + "id": 4225284, + "feed_id": 1356567, + "name": "Vanlife" + }, + { + "id": 4225283, + "feed_id": 1124009, + "name": "Pundits" + }, + { + "id": 4225282, + "feed_id": 1510172, + "name": "Vanlife" + }, + { + "id": 4225281, + "feed_id": 1156884, + "name": "Vanlife" + }, + { + "id": 4225280, + "feed_id": 1510179, + "name": "Vanlife" + }, + { + "id": 4225279, + "feed_id": 1510182, + "name": "Vanlife" + }, + { + "id": 4225278, + "feed_id": 1510189, + "name": "Vanlife" + }, + { + "id": 4225277, + "feed_id": 1261027, + "name": "Vanlife" + }, + { + "id": 4225276, + "feed_id": 1510175, + "name": "Vanlife" + }, + { + "id": 4225275, + "feed_id": 1601, + "name": "Pundits" + }, + { + "id": 4225274, + "feed_id": 1320757, + "name": "Outdoors" + }, + { + "id": 4225273, + "feed_id": 1510190, + "name": "Vanlife" + }, + { + "id": 4225271, + "feed_id": 1470170, + "name": "Outdoors" + }, + { + "id": 4225272, + "feed_id": 1296379, + "name": "Pundits" + }, + { + "id": 4225270, + "feed_id": 1347890, + "name": "Vanlife" + }, + { + "id": 4225269, + "feed_id": 1568536, + "name": "Overlanding" + }, + { + "id": 4225268, + "feed_id": 1243036, + "name": "Pundits" + }, + { + "id": 4225267, + "feed_id": 435047, + "name": "Pundits" + }, + { + "id": 4225266, + "feed_id": 1298812, + "name": "Pundits" + }, + { + "id": 4225265, + "feed_id": 18627, + "name": "Pundits" + }, + { + "id": 4225264, + "feed_id": 1304436, + "name": "Pundits" + }, + { + "id": 4225263, + "feed_id": 117722, + "name": "Pundits" + } +] \ No newline at end of file diff --git a/Frameworks/Account/AccountTests/JSON/taggings_delete.json b/Frameworks/Account/AccountTests/JSON/taggings_delete.json new file mode 100644 index 000000000..0a0844a58 --- /dev/null +++ b/Frameworks/Account/AccountTests/JSON/taggings_delete.json @@ -0,0 +1,1097 @@ +[ + { + "id": 4225484, + "feed_id": 15452, + "name": "Development Orgs" + }, + { + "id": 4225480, + "feed_id": 98472, + "name": "Amusement" + }, + { + "id": 4225479, + "feed_id": 1486842, + "name": "Developers" + }, + { + "id": 4225478, + "feed_id": 1605375, + "name": "Developers" + }, + { + "id": 4225477, + "feed_id": 1388169, + "name": "Developers" + }, + { + "id": 4225476, + "feed_id": 1913, + "name": "Tech Media" + }, + { + "id": 4225475, + "feed_id": 105, + "name": "Tech Media" + }, + { + "id": 4225474, + "feed_id": 1424879, + "name": "Developers" + }, + { + "id": 4225473, + "feed_id": 1176579, + "name": "Developers" + }, + { + "id": 4225472, + "feed_id": 1713, + "name": "Tech Media" + }, + { + "id": 4225471, + "feed_id": 1605374, + "name": "Vanlife" + }, + { + "id": 4225470, + "feed_id": 547888, + "name": "Development Orgs" + }, + { + "id": 4225469, + "feed_id": 1528640, + "name": "Business" + }, + { + "id": 4225468, + "feed_id": 1211804, + "name": "Development Orgs" + }, + { + "id": 4225467, + "feed_id": 1528652, + "name": "Business" + }, + { + "id": 4225466, + "feed_id": 1221017, + "name": "Tech Media" + }, + { + "id": 4225465, + "feed_id": 1486846, + "name": "Development Orgs" + }, + { + "id": 4225464, + "feed_id": 1340283, + "name": "Development Orgs" + }, + { + "id": 4225463, + "feed_id": 1735, + "name": "Tech Media" + }, + { + "id": 4225462, + "feed_id": 143536, + "name": "Development Orgs" + }, + { + "id": 4225461, + "feed_id": 1176732, + "name": "Development Orgs" + }, + { + "id": 4225460, + "feed_id": 1241233, + "name": "Tech Media" + }, + { + "id": 4225459, + "feed_id": 1062583, + "name": "Development Orgs" + }, + { + "id": 4225458, + "feed_id": 387024, + "name": "Development Orgs" + }, + { + "id": 4225457, + "feed_id": 1605285, + "name": "Development Orgs" + }, + { + "id": 4225456, + "feed_id": 1229997, + "name": "Development Orgs" + }, + { + "id": 4225455, + "feed_id": 1528975, + "name": "Development Orgs" + }, + { + "id": 4225454, + "feed_id": 1488022, + "name": "Development Orgs" + }, + { + "id": 4225453, + "feed_id": 1067438, + "name": "Development Orgs" + }, + { + "id": 4225452, + "feed_id": 1538463, + "name": "Development Orgs" + }, + { + "id": 4225451, + "feed_id": 1528649, + "name": "Business" + }, + { + "id": 4225450, + "feed_id": 1528643, + "name": "Business" + }, + { + "id": 4225449, + "feed_id": 1320545, + "name": "Business" + }, + { + "id": 4225448, + "feed_id": 1113164, + "name": "Tech Media" + }, + { + "id": 4225447, + "feed_id": 1528650, + "name": "Business" + }, + { + "id": 4225446, + "feed_id": 1463575, + "name": "Tech Media" + }, + { + "id": 4225445, + "feed_id": 747836, + "name": "Open Web" + }, + { + "id": 4225444, + "feed_id": 1051845, + "name": "Developers" + }, + { + "id": 4225443, + "feed_id": 1211114, + "name": "Tech Media" + }, + { + "id": 4225442, + "feed_id": 1030946, + "name": "Developers" + }, + { + "id": 4225441, + "feed_id": 1388089, + "name": "Developers" + }, + { + "id": 4225440, + "feed_id": 1472912, + "name": "Developers" + }, + { + "id": 4225439, + "feed_id": 169025, + "name": "Amusement" + }, + { + "id": 4225438, + "feed_id": 1099459, + "name": "Developers" + }, + { + "id": 4225437, + "feed_id": 1578297, + "name": "Developers" + }, + { + "id": 4225436, + "feed_id": 1424888, + "name": "Developers" + }, + { + "id": 4225435, + "feed_id": 1177901, + "name": "Developers" + }, + { + "id": 4225434, + "feed_id": 1014335, + "name": "Developers" + }, + { + "id": 4225433, + "feed_id": 1486813, + "name": "Developers" + }, + { + "id": 4225432, + "feed_id": 1436884, + "name": "Developers" + }, + { + "id": 4225431, + "feed_id": 1468269, + "name": "Developers" + }, + { + "id": 4225430, + "feed_id": 1453910, + "name": "Developers" + }, + { + "id": 4225429, + "feed_id": 1064554, + "name": "Developers" + }, + { + "id": 4225428, + "feed_id": 1505016, + "name": "Developers" + }, + { + "id": 4225427, + "feed_id": 1578290, + "name": "Developers" + }, + { + "id": 4225426, + "feed_id": 1545252, + "name": "Developers" + }, + { + "id": 4225425, + "feed_id": 1127707, + "name": "Developers" + }, + { + "id": 4225424, + "feed_id": 1094799, + "name": "Developers" + }, + { + "id": 4225423, + "feed_id": 1525481, + "name": "Developers" + }, + { + "id": 4225422, + "feed_id": 780733, + "name": "Developers" + }, + { + "id": 4225421, + "feed_id": 1486854, + "name": "Developers" + }, + { + "id": 4225420, + "feed_id": 1559907, + "name": "Developers" + }, + { + "id": 4225419, + "feed_id": 1565908, + "name": "Developers" + }, + { + "id": 4225418, + "feed_id": 1486812, + "name": "Developers" + }, + { + "id": 4225417, + "feed_id": 1516541, + "name": "Developers" + }, + { + "id": 4225416, + "feed_id": 230857, + "name": "Developers" + }, + { + "id": 4225415, + "feed_id": 37, + "name": "Developers" + }, + { + "id": 4225414, + "feed_id": 1177914, + "name": "Developers" + }, + { + "id": 4225413, + "feed_id": 1556686, + "name": "Developers" + }, + { + "id": 4225412, + "feed_id": 1559420, + "name": "Developers" + }, + { + "id": 4225411, + "feed_id": 1597437, + "name": "Developers" + }, + { + "id": 4225410, + "feed_id": 1388165, + "name": "Developers" + }, + { + "id": 4225409, + "feed_id": 1344882, + "name": "Developers" + }, + { + "id": 4225408, + "feed_id": 60926, + "name": "Developers" + }, + { + "id": 4225407, + "feed_id": 1553728, + "name": "Developers" + }, + { + "id": 4225406, + "feed_id": 1418311, + "name": "Developers" + }, + { + "id": 4225405, + "feed_id": 1578271, + "name": "Developers" + }, + { + "id": 4225404, + "feed_id": 1221779, + "name": "Developers" + }, + { + "id": 4225403, + "feed_id": 1463706, + "name": "Developers" + }, + { + "id": 4225402, + "feed_id": 1578299, + "name": "Developers" + }, + { + "id": 4225401, + "feed_id": 1078919, + "name": "Developers" + }, + { + "id": 4225400, + "feed_id": 1596199, + "name": "Developers" + }, + { + "id": 4225399, + "feed_id": 1486828, + "name": "Developers" + }, + { + "id": 4225398, + "feed_id": 874888, + "name": "Developers" + }, + { + "id": 4225397, + "feed_id": 1578282, + "name": "Developers" + }, + { + "id": 4225396, + "feed_id": 1377938, + "name": "Developers" + }, + { + "id": 4225395, + "feed_id": 961696, + "name": "Developers" + }, + { + "id": 4225394, + "feed_id": 25184, + "name": "Developers" + }, + { + "id": 4225393, + "feed_id": 1150642, + "name": "Developers" + }, + { + "id": 4225392, + "feed_id": 1449461, + "name": "Developers" + }, + { + "id": 4225391, + "feed_id": 1424885, + "name": "Developers" + }, + { + "id": 4225390, + "feed_id": 989928, + "name": "Developers" + }, + { + "id": 4225389, + "feed_id": 1389178, + "name": "Developers" + }, + { + "id": 4225388, + "feed_id": 1346114, + "name": "Developers" + }, + { + "id": 4225387, + "feed_id": 1481847, + "name": "Developers" + }, + { + "id": 4225386, + "feed_id": 1487018, + "name": "Developers" + }, + { + "id": 4225385, + "feed_id": 1578279, + "name": "Developers" + }, + { + "id": 4225384, + "feed_id": 1424889, + "name": "Developers" + }, + { + "id": 4225383, + "feed_id": 1012582, + "name": "Developers" + }, + { + "id": 4225382, + "feed_id": 1095014, + "name": "Developers" + }, + { + "id": 4225381, + "feed_id": 1578284, + "name": "Developers" + }, + { + "id": 4225380, + "feed_id": 1424878, + "name": "Developers" + }, + { + "id": 4225379, + "feed_id": 1296383, + "name": "Developers" + }, + { + "id": 4225378, + "feed_id": 1377354, + "name": "Developers" + }, + { + "id": 4225377, + "feed_id": 1510180, + "name": "Developers" + }, + { + "id": 4225376, + "feed_id": 1237279, + "name": "Developers" + }, + { + "id": 4225375, + "feed_id": 1388622, + "name": "Developers" + }, + { + "id": 4225374, + "feed_id": 1578292, + "name": "Developers" + }, + { + "id": 4225373, + "feed_id": 1505017, + "name": "Developers" + }, + { + "id": 4225372, + "feed_id": 1578300, + "name": "Developers" + }, + { + "id": 4225371, + "feed_id": 1572820, + "name": "Developers" + }, + { + "id": 4225370, + "feed_id": 1578275, + "name": "Developers" + }, + { + "id": 4225369, + "feed_id": 1511092, + "name": "Developers" + }, + { + "id": 4225368, + "feed_id": 1399906, + "name": "Developers" + }, + { + "id": 4225367, + "feed_id": 1486836, + "name": "Developers" + }, + { + "id": 4225366, + "feed_id": 1578288, + "name": "Developers" + }, + { + "id": 4225365, + "feed_id": 1424887, + "name": "Developers" + }, + { + "id": 4225364, + "feed_id": 861611, + "name": "Developers" + }, + { + "id": 4225363, + "feed_id": 1239214, + "name": "Developers" + }, + { + "id": 4225362, + "feed_id": 1149420, + "name": "Developers" + }, + { + "id": 4225361, + "feed_id": 1506640, + "name": "Developers" + }, + { + "id": 4225360, + "feed_id": 1418314, + "name": "Developers" + }, + { + "id": 4225359, + "feed_id": 26795, + "name": "Developers" + }, + { + "id": 4225358, + "feed_id": 1153329, + "name": "Developers" + }, + { + "id": 4225357, + "feed_id": 1119253, + "name": "Developers" + }, + { + "id": 4225356, + "feed_id": 1367414, + "name": "Developers" + }, + { + "id": 4225355, + "feed_id": 1501797, + "name": "Developers" + }, + { + "id": 4225354, + "feed_id": 913020, + "name": "Developers" + }, + { + "id": 4225353, + "feed_id": 95556, + "name": "Developers" + }, + { + "id": 4225352, + "feed_id": 1192662, + "name": "Developers" + }, + { + "id": 4225351, + "feed_id": 1510178, + "name": "Developers" + }, + { + "id": 4225350, + "feed_id": 1274281, + "name": "Developers" + }, + { + "id": 4225349, + "feed_id": 1365909, + "name": "Developers" + }, + { + "id": 4225348, + "feed_id": 1069647, + "name": "Developers" + }, + { + "id": 4225347, + "feed_id": 1576799, + "name": "Developers" + }, + { + "id": 4225346, + "feed_id": 17463, + "name": "Developers" + }, + { + "id": 4225345, + "feed_id": 1486826, + "name": "Developers" + }, + { + "id": 4225344, + "feed_id": 74, + "name": "Developers" + }, + { + "id": 4225343, + "feed_id": 1376302, + "name": "Developers" + }, + { + "id": 4225342, + "feed_id": 1578277, + "name": "Developers" + }, + { + "id": 4225341, + "feed_id": 35102, + "name": "Developers" + }, + { + "id": 4225340, + "feed_id": 1074742, + "name": "Developers" + }, + { + "id": 4225339, + "feed_id": 1296380, + "name": "Developers" + }, + { + "id": 4225338, + "feed_id": 1578287, + "name": "Developers" + }, + { + "id": 4225337, + "feed_id": 1200837, + "name": "Developers" + }, + { + "id": 4225336, + "feed_id": 1291156, + "name": "Developers" + }, + { + "id": 4225335, + "feed_id": 1338599, + "name": "Developers" + }, + { + "id": 4225334, + "feed_id": 1505579, + "name": "Developers" + }, + { + "id": 4225333, + "feed_id": 1424881, + "name": "Developers" + }, + { + "id": 4225332, + "feed_id": 1303511, + "name": "Developers" + }, + { + "id": 4225331, + "feed_id": 1388351, + "name": "Developers" + }, + { + "id": 4225330, + "feed_id": 1519998, + "name": "Developers" + }, + { + "id": 4225329, + "feed_id": 1578272, + "name": "Developers" + }, + { + "id": 4225328, + "feed_id": 16340, + "name": "Developers" + }, + { + "id": 4225327, + "feed_id": 1486815, + "name": "Developers" + }, + { + "id": 4225326, + "feed_id": 1525506, + "name": "Developers" + }, + { + "id": 4225325, + "feed_id": 1578273, + "name": "Developers" + }, + { + "id": 4225324, + "feed_id": 1359517, + "name": "Developers" + }, + { + "id": 4225323, + "feed_id": 1270217, + "name": "Developers" + }, + { + "id": 4225322, + "feed_id": 1021976, + "name": "Developers" + }, + { + "id": 4225321, + "feed_id": 860220, + "name": "Developers" + }, + { + "id": 4225320, + "feed_id": 1223531, + "name": "Developers" + }, + { + "id": 4225319, + "feed_id": 1017026, + "name": "Developers" + }, + { + "id": 4225318, + "feed_id": 1578296, + "name": "Developers" + }, + { + "id": 4225317, + "feed_id": 1381532, + "name": "Developers" + }, + { + "id": 4225316, + "feed_id": 1407941, + "name": "Developers" + }, + { + "id": 4225315, + "feed_id": 4269, + "name": "Developers" + }, + { + "id": 4225314, + "feed_id": 972279, + "name": "Developers" + }, + { + "id": 4225313, + "feed_id": 1539758, + "name": "Developers" + }, + { + "id": 4225312, + "feed_id": 1076603, + "name": "Developers" + }, + { + "id": 4225311, + "feed_id": 1411746, + "name": "Developers" + }, + { + "id": 4225310, + "feed_id": 694239, + "name": "Developers" + }, + { + "id": 4225309, + "feed_id": 1271644, + "name": "Developers" + }, + { + "id": 4225308, + "feed_id": 1578285, + "name": "Developers" + }, + { + "id": 4225307, + "feed_id": 1260873, + "name": "Developers" + }, + { + "id": 4225306, + "feed_id": 1082983, + "name": "Developers" + }, + { + "id": 4225305, + "feed_id": 1424893, + "name": "Developers" + }, + { + "id": 4225304, + "feed_id": 1023777, + "name": "Developers" + }, + { + "id": 4225303, + "feed_id": 1574913, + "name": "Developers" + }, + { + "id": 4225302, + "feed_id": 1578278, + "name": "Developers" + }, + { + "id": 4225301, + "feed_id": 1388263, + "name": "Developers" + }, + { + "id": 4225300, + "feed_id": 1418558, + "name": "Developers" + }, + { + "id": 4225299, + "feed_id": 1578274, + "name": "Developers" + }, + { + "id": 4225298, + "feed_id": 1418313, + "name": "Developers" + }, + { + "id": 4225297, + "feed_id": 1598316, + "name": "Developers" + }, + { + "id": 4225296, + "feed_id": 1372247, + "name": "Vanlife" + }, + { + "id": 4225295, + "feed_id": 1423653, + "name": "Developers" + }, + { + "id": 4225294, + "feed_id": 1255081, + "name": "Vanlife" + }, + { + "id": 4225293, + "feed_id": 1424883, + "name": "Developers" + }, + { + "id": 4225292, + "feed_id": 1510177, + "name": "Vanlife" + }, + { + "id": 4225291, + "feed_id": 1510185, + "name": "Vanlife" + }, + { + "id": 4225290, + "feed_id": 1510174, + "name": "Vanlife" + }, + { + "id": 4225289, + "feed_id": 1510176, + "name": "Vanlife" + }, + { + "id": 4225288, + "feed_id": 1510173, + "name": "Vanlife" + }, + { + "id": 4225287, + "feed_id": 1510183, + "name": "Vanlife" + }, + { + "id": 4225286, + "feed_id": 1510181, + "name": "Vanlife" + }, + { + "id": 4225285, + "feed_id": 1510186, + "name": "Vanlife" + }, + { + "id": 4225284, + "feed_id": 1356567, + "name": "Vanlife" + }, + { + "id": 4225283, + "feed_id": 1124009, + "name": "Pundits" + }, + { + "id": 4225282, + "feed_id": 1510172, + "name": "Vanlife" + }, + { + "id": 4225281, + "feed_id": 1156884, + "name": "Vanlife" + }, + { + "id": 4225280, + "feed_id": 1510179, + "name": "Vanlife" + }, + { + "id": 4225279, + "feed_id": 1510182, + "name": "Vanlife" + }, + { + "id": 4225278, + "feed_id": 1510189, + "name": "Vanlife" + }, + { + "id": 4225277, + "feed_id": 1261027, + "name": "Vanlife" + }, + { + "id": 4225276, + "feed_id": 1510175, + "name": "Vanlife" + }, + { + "id": 4225275, + "feed_id": 1601, + "name": "Pundits" + }, + { + "id": 4225274, + "feed_id": 1320757, + "name": "Outdoors" + }, + { + "id": 4225273, + "feed_id": 1510190, + "name": "Vanlife" + }, + { + "id": 4225271, + "feed_id": 1470170, + "name": "Outdoors" + }, + { + "id": 4225272, + "feed_id": 1296379, + "name": "Pundits" + }, + { + "id": 4225270, + "feed_id": 1347890, + "name": "Vanlife" + }, + { + "id": 4225269, + "feed_id": 1568536, + "name": "Overlanding" + }, + { + "id": 4225268, + "feed_id": 1243036, + "name": "Pundits" + }, + { + "id": 4225267, + "feed_id": 435047, + "name": "Pundits" + }, + { + "id": 4225266, + "feed_id": 1298812, + "name": "Pundits" + }, + { + "id": 4225265, + "feed_id": 18627, + "name": "Pundits" + }, + { + "id": 4225264, + "feed_id": 1304436, + "name": "Pundits" + }, + { + "id": 4225263, + "feed_id": 117722, + "name": "Pundits" + } +] diff --git a/Frameworks/Account/AccountTests/JSON/taggings_initial.json b/Frameworks/Account/AccountTests/JSON/taggings_initial.json new file mode 100644 index 000000000..3ea143f79 --- /dev/null +++ b/Frameworks/Account/AccountTests/JSON/taggings_initial.json @@ -0,0 +1,1112 @@ +[ + { + "id": 4225484, + "feed_id": 15452, + "name": "Development Orgs" + }, + { + "id": 4225483, + "feed_id": 1578301, + "name": "Developers" + }, + { + "id": 4225482, + "feed_id": 1364385, + "name": "Developers" + }, + { + "id": 4225481, + "feed_id": 1597435, + "name": "Developers" + }, + { + "id": 4225480, + "feed_id": 98472, + "name": "Amusement" + }, + { + "id": 4225479, + "feed_id": 1486842, + "name": "Developers" + }, + { + "id": 4225478, + "feed_id": 1605375, + "name": "Developers" + }, + { + "id": 4225477, + "feed_id": 1388169, + "name": "Developers" + }, + { + "id": 4225476, + "feed_id": 1913, + "name": "Tech Media" + }, + { + "id": 4225475, + "feed_id": 105, + "name": "Tech Media" + }, + { + "id": 4225474, + "feed_id": 1424879, + "name": "Developers" + }, + { + "id": 4225473, + "feed_id": 1176579, + "name": "Developers" + }, + { + "id": 4225472, + "feed_id": 1713, + "name": "Tech Media" + }, + { + "id": 4225471, + "feed_id": 1605374, + "name": "Vanlife" + }, + { + "id": 4225470, + "feed_id": 547888, + "name": "Development Orgs" + }, + { + "id": 4225469, + "feed_id": 1528640, + "name": "Business" + }, + { + "id": 4225468, + "feed_id": 1211804, + "name": "Development Orgs" + }, + { + "id": 4225467, + "feed_id": 1528652, + "name": "Business" + }, + { + "id": 4225466, + "feed_id": 1221017, + "name": "Tech Media" + }, + { + "id": 4225465, + "feed_id": 1486846, + "name": "Development Orgs" + }, + { + "id": 4225464, + "feed_id": 1340283, + "name": "Development Orgs" + }, + { + "id": 4225463, + "feed_id": 1735, + "name": "Tech Media" + }, + { + "id": 4225462, + "feed_id": 143536, + "name": "Development Orgs" + }, + { + "id": 4225461, + "feed_id": 1176732, + "name": "Development Orgs" + }, + { + "id": 4225460, + "feed_id": 1241233, + "name": "Tech Media" + }, + { + "id": 4225459, + "feed_id": 1062583, + "name": "Development Orgs" + }, + { + "id": 4225458, + "feed_id": 387024, + "name": "Development Orgs" + }, + { + "id": 4225457, + "feed_id": 1605285, + "name": "Development Orgs" + }, + { + "id": 4225456, + "feed_id": 1229997, + "name": "Development Orgs" + }, + { + "id": 4225455, + "feed_id": 1528975, + "name": "Development Orgs" + }, + { + "id": 4225454, + "feed_id": 1488022, + "name": "Development Orgs" + }, + { + "id": 4225453, + "feed_id": 1067438, + "name": "Development Orgs" + }, + { + "id": 4225452, + "feed_id": 1538463, + "name": "Development Orgs" + }, + { + "id": 4225451, + "feed_id": 1528649, + "name": "Business" + }, + { + "id": 4225450, + "feed_id": 1528643, + "name": "Business" + }, + { + "id": 4225449, + "feed_id": 1320545, + "name": "Business" + }, + { + "id": 4225448, + "feed_id": 1113164, + "name": "Tech Media" + }, + { + "id": 4225447, + "feed_id": 1528650, + "name": "Business" + }, + { + "id": 4225446, + "feed_id": 1463575, + "name": "Tech Media" + }, + { + "id": 4225445, + "feed_id": 747836, + "name": "Open Web" + }, + { + "id": 4225444, + "feed_id": 1051845, + "name": "Developers" + }, + { + "id": 4225443, + "feed_id": 1211114, + "name": "Tech Media" + }, + { + "id": 4225442, + "feed_id": 1030946, + "name": "Developers" + }, + { + "id": 4225441, + "feed_id": 1388089, + "name": "Developers" + }, + { + "id": 4225440, + "feed_id": 1472912, + "name": "Developers" + }, + { + "id": 4225439, + "feed_id": 169025, + "name": "Amusement" + }, + { + "id": 4225438, + "feed_id": 1099459, + "name": "Developers" + }, + { + "id": 4225437, + "feed_id": 1578297, + "name": "Developers" + }, + { + "id": 4225436, + "feed_id": 1424888, + "name": "Developers" + }, + { + "id": 4225435, + "feed_id": 1177901, + "name": "Developers" + }, + { + "id": 4225434, + "feed_id": 1014335, + "name": "Developers" + }, + { + "id": 4225433, + "feed_id": 1486813, + "name": "Developers" + }, + { + "id": 4225432, + "feed_id": 1436884, + "name": "Developers" + }, + { + "id": 4225431, + "feed_id": 1468269, + "name": "Developers" + }, + { + "id": 4225430, + "feed_id": 1453910, + "name": "Developers" + }, + { + "id": 4225429, + "feed_id": 1064554, + "name": "Developers" + }, + { + "id": 4225428, + "feed_id": 1505016, + "name": "Developers" + }, + { + "id": 4225427, + "feed_id": 1578290, + "name": "Developers" + }, + { + "id": 4225426, + "feed_id": 1545252, + "name": "Developers" + }, + { + "id": 4225425, + "feed_id": 1127707, + "name": "Developers" + }, + { + "id": 4225424, + "feed_id": 1094799, + "name": "Developers" + }, + { + "id": 4225423, + "feed_id": 1525481, + "name": "Developers" + }, + { + "id": 4225422, + "feed_id": 780733, + "name": "Developers" + }, + { + "id": 4225421, + "feed_id": 1486854, + "name": "Developers" + }, + { + "id": 4225420, + "feed_id": 1559907, + "name": "Developers" + }, + { + "id": 4225419, + "feed_id": 1565908, + "name": "Developers" + }, + { + "id": 4225418, + "feed_id": 1486812, + "name": "Developers" + }, + { + "id": 4225417, + "feed_id": 1516541, + "name": "Developers" + }, + { + "id": 4225416, + "feed_id": 230857, + "name": "Developers" + }, + { + "id": 4225415, + "feed_id": 37, + "name": "Developers" + }, + { + "id": 4225414, + "feed_id": 1177914, + "name": "Developers" + }, + { + "id": 4225413, + "feed_id": 1556686, + "name": "Developers" + }, + { + "id": 4225412, + "feed_id": 1559420, + "name": "Developers" + }, + { + "id": 4225411, + "feed_id": 1597437, + "name": "Developers" + }, + { + "id": 4225410, + "feed_id": 1388165, + "name": "Developers" + }, + { + "id": 4225409, + "feed_id": 1344882, + "name": "Developers" + }, + { + "id": 4225408, + "feed_id": 60926, + "name": "Developers" + }, + { + "id": 4225407, + "feed_id": 1553728, + "name": "Developers" + }, + { + "id": 4225406, + "feed_id": 1418311, + "name": "Developers" + }, + { + "id": 4225405, + "feed_id": 1578271, + "name": "Developers" + }, + { + "id": 4225404, + "feed_id": 1221779, + "name": "Developers" + }, + { + "id": 4225403, + "feed_id": 1463706, + "name": "Developers" + }, + { + "id": 4225402, + "feed_id": 1578299, + "name": "Developers" + }, + { + "id": 4225401, + "feed_id": 1078919, + "name": "Developers" + }, + { + "id": 4225400, + "feed_id": 1596199, + "name": "Developers" + }, + { + "id": 4225399, + "feed_id": 1486828, + "name": "Developers" + }, + { + "id": 4225398, + "feed_id": 874888, + "name": "Developers" + }, + { + "id": 4225397, + "feed_id": 1578282, + "name": "Developers" + }, + { + "id": 4225396, + "feed_id": 1377938, + "name": "Developers" + }, + { + "id": 4225395, + "feed_id": 961696, + "name": "Developers" + }, + { + "id": 4225394, + "feed_id": 25184, + "name": "Developers" + }, + { + "id": 4225393, + "feed_id": 1150642, + "name": "Developers" + }, + { + "id": 4225392, + "feed_id": 1449461, + "name": "Developers" + }, + { + "id": 4225391, + "feed_id": 1424885, + "name": "Developers" + }, + { + "id": 4225390, + "feed_id": 989928, + "name": "Developers" + }, + { + "id": 4225389, + "feed_id": 1389178, + "name": "Developers" + }, + { + "id": 4225388, + "feed_id": 1346114, + "name": "Developers" + }, + { + "id": 4225387, + "feed_id": 1481847, + "name": "Developers" + }, + { + "id": 4225386, + "feed_id": 1487018, + "name": "Developers" + }, + { + "id": 4225385, + "feed_id": 1578279, + "name": "Developers" + }, + { + "id": 4225384, + "feed_id": 1424889, + "name": "Developers" + }, + { + "id": 4225383, + "feed_id": 1012582, + "name": "Developers" + }, + { + "id": 4225382, + "feed_id": 1095014, + "name": "Developers" + }, + { + "id": 4225381, + "feed_id": 1578284, + "name": "Developers" + }, + { + "id": 4225380, + "feed_id": 1424878, + "name": "Developers" + }, + { + "id": 4225379, + "feed_id": 1296383, + "name": "Developers" + }, + { + "id": 4225378, + "feed_id": 1377354, + "name": "Developers" + }, + { + "id": 4225377, + "feed_id": 1510180, + "name": "Developers" + }, + { + "id": 4225376, + "feed_id": 1237279, + "name": "Developers" + }, + { + "id": 4225375, + "feed_id": 1388622, + "name": "Developers" + }, + { + "id": 4225374, + "feed_id": 1578292, + "name": "Developers" + }, + { + "id": 4225373, + "feed_id": 1505017, + "name": "Developers" + }, + { + "id": 4225372, + "feed_id": 1578300, + "name": "Developers" + }, + { + "id": 4225371, + "feed_id": 1572820, + "name": "Developers" + }, + { + "id": 4225370, + "feed_id": 1578275, + "name": "Developers" + }, + { + "id": 4225369, + "feed_id": 1511092, + "name": "Developers" + }, + { + "id": 4225368, + "feed_id": 1399906, + "name": "Developers" + }, + { + "id": 4225367, + "feed_id": 1486836, + "name": "Developers" + }, + { + "id": 4225366, + "feed_id": 1578288, + "name": "Developers" + }, + { + "id": 4225365, + "feed_id": 1424887, + "name": "Developers" + }, + { + "id": 4225364, + "feed_id": 861611, + "name": "Developers" + }, + { + "id": 4225363, + "feed_id": 1239214, + "name": "Developers" + }, + { + "id": 4225362, + "feed_id": 1149420, + "name": "Developers" + }, + { + "id": 4225361, + "feed_id": 1506640, + "name": "Developers" + }, + { + "id": 4225360, + "feed_id": 1418314, + "name": "Developers" + }, + { + "id": 4225359, + "feed_id": 26795, + "name": "Developers" + }, + { + "id": 4225358, + "feed_id": 1153329, + "name": "Developers" + }, + { + "id": 4225357, + "feed_id": 1119253, + "name": "Developers" + }, + { + "id": 4225356, + "feed_id": 1367414, + "name": "Developers" + }, + { + "id": 4225355, + "feed_id": 1501797, + "name": "Developers" + }, + { + "id": 4225354, + "feed_id": 913020, + "name": "Developers" + }, + { + "id": 4225353, + "feed_id": 95556, + "name": "Developers" + }, + { + "id": 4225352, + "feed_id": 1192662, + "name": "Developers" + }, + { + "id": 4225351, + "feed_id": 1510178, + "name": "Developers" + }, + { + "id": 4225350, + "feed_id": 1274281, + "name": "Developers" + }, + { + "id": 4225349, + "feed_id": 1365909, + "name": "Developers" + }, + { + "id": 4225348, + "feed_id": 1069647, + "name": "Developers" + }, + { + "id": 4225347, + "feed_id": 1576799, + "name": "Developers" + }, + { + "id": 4225346, + "feed_id": 17463, + "name": "Developers" + }, + { + "id": 4225345, + "feed_id": 1486826, + "name": "Developers" + }, + { + "id": 4225344, + "feed_id": 74, + "name": "Developers" + }, + { + "id": 4225343, + "feed_id": 1376302, + "name": "Developers" + }, + { + "id": 4225342, + "feed_id": 1578277, + "name": "Developers" + }, + { + "id": 4225341, + "feed_id": 35102, + "name": "Developers" + }, + { + "id": 4225340, + "feed_id": 1074742, + "name": "Developers" + }, + { + "id": 4225339, + "feed_id": 1296380, + "name": "Developers" + }, + { + "id": 4225338, + "feed_id": 1578287, + "name": "Developers" + }, + { + "id": 4225337, + "feed_id": 1200837, + "name": "Developers" + }, + { + "id": 4225336, + "feed_id": 1291156, + "name": "Developers" + }, + { + "id": 4225335, + "feed_id": 1338599, + "name": "Developers" + }, + { + "id": 4225334, + "feed_id": 1505579, + "name": "Developers" + }, + { + "id": 4225333, + "feed_id": 1424881, + "name": "Developers" + }, + { + "id": 4225332, + "feed_id": 1303511, + "name": "Developers" + }, + { + "id": 4225331, + "feed_id": 1388351, + "name": "Developers" + }, + { + "id": 4225330, + "feed_id": 1519998, + "name": "Developers" + }, + { + "id": 4225329, + "feed_id": 1578272, + "name": "Developers" + }, + { + "id": 4225328, + "feed_id": 16340, + "name": "Developers" + }, + { + "id": 4225327, + "feed_id": 1486815, + "name": "Developers" + }, + { + "id": 4225326, + "feed_id": 1525506, + "name": "Developers" + }, + { + "id": 4225325, + "feed_id": 1578273, + "name": "Developers" + }, + { + "id": 4225324, + "feed_id": 1359517, + "name": "Developers" + }, + { + "id": 4225323, + "feed_id": 1270217, + "name": "Developers" + }, + { + "id": 4225322, + "feed_id": 1021976, + "name": "Developers" + }, + { + "id": 4225321, + "feed_id": 860220, + "name": "Developers" + }, + { + "id": 4225320, + "feed_id": 1223531, + "name": "Developers" + }, + { + "id": 4225319, + "feed_id": 1017026, + "name": "Developers" + }, + { + "id": 4225318, + "feed_id": 1578296, + "name": "Developers" + }, + { + "id": 4225317, + "feed_id": 1381532, + "name": "Developers" + }, + { + "id": 4225316, + "feed_id": 1407941, + "name": "Developers" + }, + { + "id": 4225315, + "feed_id": 4269, + "name": "Developers" + }, + { + "id": 4225314, + "feed_id": 972279, + "name": "Developers" + }, + { + "id": 4225313, + "feed_id": 1539758, + "name": "Developers" + }, + { + "id": 4225312, + "feed_id": 1076603, + "name": "Developers" + }, + { + "id": 4225311, + "feed_id": 1411746, + "name": "Developers" + }, + { + "id": 4225310, + "feed_id": 694239, + "name": "Developers" + }, + { + "id": 4225309, + "feed_id": 1271644, + "name": "Developers" + }, + { + "id": 4225308, + "feed_id": 1578285, + "name": "Developers" + }, + { + "id": 4225307, + "feed_id": 1260873, + "name": "Developers" + }, + { + "id": 4225306, + "feed_id": 1082983, + "name": "Developers" + }, + { + "id": 4225305, + "feed_id": 1424893, + "name": "Developers" + }, + { + "id": 4225304, + "feed_id": 1023777, + "name": "Developers" + }, + { + "id": 4225303, + "feed_id": 1574913, + "name": "Developers" + }, + { + "id": 4225302, + "feed_id": 1578278, + "name": "Developers" + }, + { + "id": 4225301, + "feed_id": 1388263, + "name": "Developers" + }, + { + "id": 4225300, + "feed_id": 1418558, + "name": "Developers" + }, + { + "id": 4225299, + "feed_id": 1578274, + "name": "Developers" + }, + { + "id": 4225298, + "feed_id": 1418313, + "name": "Developers" + }, + { + "id": 4225297, + "feed_id": 1598316, + "name": "Developers" + }, + { + "id": 4225296, + "feed_id": 1372247, + "name": "Vanlife" + }, + { + "id": 4225295, + "feed_id": 1423653, + "name": "Developers" + }, + { + "id": 4225294, + "feed_id": 1255081, + "name": "Vanlife" + }, + { + "id": 4225293, + "feed_id": 1424883, + "name": "Developers" + }, + { + "id": 4225292, + "feed_id": 1510177, + "name": "Vanlife" + }, + { + "id": 4225291, + "feed_id": 1510185, + "name": "Vanlife" + }, + { + "id": 4225290, + "feed_id": 1510174, + "name": "Vanlife" + }, + { + "id": 4225289, + "feed_id": 1510176, + "name": "Vanlife" + }, + { + "id": 4225288, + "feed_id": 1510173, + "name": "Vanlife" + }, + { + "id": 4225287, + "feed_id": 1510183, + "name": "Vanlife" + }, + { + "id": 4225286, + "feed_id": 1510181, + "name": "Vanlife" + }, + { + "id": 4225285, + "feed_id": 1510186, + "name": "Vanlife" + }, + { + "id": 4225284, + "feed_id": 1356567, + "name": "Vanlife" + }, + { + "id": 4225283, + "feed_id": 1124009, + "name": "Pundits" + }, + { + "id": 4225282, + "feed_id": 1510172, + "name": "Vanlife" + }, + { + "id": 4225281, + "feed_id": 1156884, + "name": "Vanlife" + }, + { + "id": 4225280, + "feed_id": 1510179, + "name": "Vanlife" + }, + { + "id": 4225279, + "feed_id": 1510182, + "name": "Vanlife" + }, + { + "id": 4225278, + "feed_id": 1510189, + "name": "Vanlife" + }, + { + "id": 4225277, + "feed_id": 1261027, + "name": "Vanlife" + }, + { + "id": 4225276, + "feed_id": 1510175, + "name": "Vanlife" + }, + { + "id": 4225275, + "feed_id": 1601, + "name": "Pundits" + }, + { + "id": 4225274, + "feed_id": 1320757, + "name": "Outdoors" + }, + { + "id": 4225273, + "feed_id": 1510190, + "name": "Vanlife" + }, + { + "id": 4225271, + "feed_id": 1470170, + "name": "Outdoors" + }, + { + "id": 4225272, + "feed_id": 1296379, + "name": "Pundits" + }, + { + "id": 4225270, + "feed_id": 1347890, + "name": "Vanlife" + }, + { + "id": 4225269, + "feed_id": 1568536, + "name": "Overlanding" + }, + { + "id": 4225268, + "feed_id": 1243036, + "name": "Pundits" + }, + { + "id": 4225267, + "feed_id": 435047, + "name": "Pundits" + }, + { + "id": 4225266, + "feed_id": 1298812, + "name": "Pundits" + }, + { + "id": 4225265, + "feed_id": 18627, + "name": "Pundits" + }, + { + "id": 4225264, + "feed_id": 1304436, + "name": "Pundits" + }, + { + "id": 4225263, + "feed_id": 117722, + "name": "Pundits" + } +] \ No newline at end of file diff --git a/Frameworks/Account/Feedbin/FeedbinAPICaller.swift b/Frameworks/Account/Feedbin/FeedbinAPICaller.swift index a21ba0984..16344fe80 100644 --- a/Frameworks/Account/Feedbin/FeedbinAPICaller.swift +++ b/Frameworks/Account/Feedbin/FeedbinAPICaller.swift @@ -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) { let callURL = feedbinBaseURL.appendingPathComponent("icons.json") diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index d453f700c..60c294fcd 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -81,16 +81,22 @@ final class FeedbinAccountDelegate: AccountDelegate { 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 { - case .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. + case .success(let taggings): + DispatchQueue.main.sync { + BatchUpdate.shared.perform { + for feed in folder.topLevelFeeds { + account.addFeed(feed, to: nil) + } + account.deleteFolder(folder) + } completion(.success(())) } + self?.syncTaggings(account, taggings) case .failure(let error): DispatchQueue.main.async { completion(.failure(error)) @@ -160,6 +166,9 @@ private extension FeedbinAccountDelegate { folders.forEach { folder in if !tagNames.contains(folder.name ?? "") { DispatchQueue.main.sync { + for feed in folder.topLevelFeeds { + account.addFeed(feed, to: nil) + } account.deleteFolder(folder) } } @@ -186,58 +195,168 @@ private extension FeedbinAccountDelegate { } func refreshFeeds(_ account: Account, completion: @escaping (Result) -> Void) { + caller.retrieveSubscriptions { [weak self] result in switch result { 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): completion(.failure(error)) } + } } func syncFeeds(_ account: Account, _ subscriptions: [FeedbinSubscription]?) { + guard let subscriptions = subscriptions else { return } - BatchUpdate.shared.perform { - subscriptions.forEach { subscription in - syncFeed(account, subscription) + + let subFeedIds = subscriptions.map { String($0.feedID) } + + // 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() + 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() + for feed in account.topLevelFeeds { + if taggedFeedIds.contains(feed.feedID) { + feedsToDelete.insert(feed) + } + } 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) { - - 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)) - } + account.deleteFeeds(feedsToDelete) } } - func syncIcons(_ account: Account, _ icons: [FeedbinIcon]?) { + func syncFavicons(_ account: Account, _ icons: [FeedbinIcon]?) { guard let icons = icons else { return }