Rewrite folder management for Reader API

This commit is contained in:
Maurice Parker 2020-10-31 12:47:12 -05:00
parent 04b1667293
commit 1760d38777

View File

@ -263,7 +263,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
switch result { switch result {
case .success: case .success:
DispatchQueue.main.async { DispatchQueue.main.async {
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") self.clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
} }
case .failure(let error): case .failure(let error):
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription) os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
@ -410,14 +410,14 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
} }
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) { func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
if let folder = container as? Folder, let feedName = feed.externalID { if let folder = container as? Folder, let feedExternalID = feed.externalID {
refreshProgress.addToNumberOfTasksAndRemaining(1) refreshProgress.addToNumberOfTasksAndRemaining(1)
caller.createTagging(subscriptionID: feedName, tagName: folder.name ?? "") { result in caller.createTagging(subscriptionID: feedExternalID, tagName: folder.name ?? "") { result in
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
switch result { switch result {
case .success: case .success:
DispatchQueue.main.async { DispatchQueue.main.async {
self.saveFolderRelationship(for: feed, withFolderName: folder.name ?? "", id: feed.externalID!) self.saveFolderRelationship(for: feed, folderExternalID: folder.externalID, feedExternalID: feedExternalID)
account.removeWebFeed(feed) account.removeWebFeed(feed)
folder.addWebFeed(feed) folder.addWebFeed(feed)
completion(.success(())) completion(.success(()))
@ -576,35 +576,38 @@ private extension ReaderAPIAccountDelegate {
guard let tags = tags else { return } guard let tags = tags else { return }
assert(Thread.isMainThread) assert(Thread.isMainThread)
os_log(.debug, log: log, "Syncing folders with %ld tags.", tags.count) let folderTags = tags.filter{ $0.type == "folder" }
guard !folderTags.isEmpty else { return }
let readerFolderNames = tags.compactMap { $0.folderName } os_log(.debug, log: log, "Syncing folders with %ld tags.", folderTags.count)
let readerFolderExternalIDs = folderTags.compactMap { $0.tagID }
// Delete any folders not at Reader // Delete any folders not at Reader
if let folders = account.folders { if let folders = account.folders {
folders.forEach { folder in folders.forEach { folder in
if !readerFolderNames.contains(folder.name ?? "") { if !readerFolderExternalIDs.contains(folder.externalID ?? "") {
for feed in folder.topLevelWebFeeds { for feed in folder.topLevelWebFeeds {
account.addWebFeed(feed) account.addWebFeed(feed)
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
} }
account.removeFolder(folder) account.removeFolder(folder)
} }
} }
} }
let folderNames: [String] = { let folderExternalIDs: [String] = {
if let folders = account.folders { if let folders = account.folders {
return folders.map { $0.name ?? "" } return folders.compactMap { $0.externalID }
} else { } else {
return [String]() return [String]()
} }
}() }()
// Make any folders Reader has, but we don't // Make any folders Reader has, but we don't
tags.forEach { tag in folderTags.forEach { tag in
if let tagFolderName = tag.folderName, !folderNames.contains(tagFolderName) { if !folderExternalIDs.contains(tag.tagID) {
let folder = account.ensureFolder(with: tagFolderName) let folder = account.ensureFolder(with: tag.folderName ?? "None")
folder?.externalID = tag.tagID folder?.externalID = tag.tagID
} }
} }
@ -618,7 +621,7 @@ private extension ReaderAPIAccountDelegate {
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
BatchUpdate.shared.perform { BatchUpdate.shared.perform {
self.syncFeeds(account, subscriptions) self.syncFeeds(account, subscriptions)
self.syncTaggings(account, subscriptions) self.syncFeedFolderRelationship(account, subscriptions)
} }
completion(.success(())) completion(.success(()))
case .failure(let error): case .failure(let error):
@ -649,6 +652,7 @@ private extension ReaderAPIAccountDelegate {
for feed in account.topLevelWebFeeds { for feed in account.topLevelWebFeeds {
if !subFeedIds.contains(feed.webFeedID) { if !subFeedIds.contains(feed.webFeedID) {
account.clearWebFeedMetadata(feed)
account.removeWebFeed(feed) account.removeWebFeed(feed)
} }
} }
@ -670,43 +674,38 @@ private extension ReaderAPIAccountDelegate {
} }
func syncTaggings(_ account: Account, _ subscriptions: [ReaderAPISubscription]?) { func syncFeedFolderRelationship(_ account: Account, _ subscriptions: [ReaderAPISubscription]?) {
guard let subscriptions = subscriptions else { return } guard let subscriptions = subscriptions else { return }
assert(Thread.isMainThread) assert(Thread.isMainThread)
os_log(.debug, log: log, "Syncing taggings with %ld subscriptions.", subscriptions.count) os_log(.debug, log: log, "Syncing taggings with %ld subscriptions.", subscriptions.count)
// Set up some structures to make syncing easier // Set up some structures to make syncing easier
let folderDict = nameToFolderDictionary(with: account.folders) let folderDict = externalIDToFolderDictionary(with: account.folders)
let taggingsDict = subscriptions.reduce([String: [ReaderAPISubscription]]()) { (dict, subscription) in let taggingsDict = subscriptions.reduce([String: [ReaderAPISubscription]]()) { (dict, subscription) in
var taggedFeeds = dict var taggedFeeds = dict
// For each category that this feed belongs to, add the feed to that name in the dict
subscription.categories.forEach({ (category) in subscription.categories.forEach({ (category) in
let categoryName = category.categoryLabel.replacingOccurrences(of: "user/-/label/", with: "") if var taggedFeed = taggedFeeds[category.categoryId] {
if var taggedFeed = taggedFeeds[categoryName] {
taggedFeed.append(subscription) taggedFeed.append(subscription)
taggedFeeds[categoryName] = taggedFeed taggedFeeds[category.categoryId] = taggedFeed
} else { } else {
taggedFeeds[categoryName] = [subscription] taggedFeeds[category.categoryId] = [subscription]
} }
}) })
return taggedFeeds return taggedFeeds
} }
var taggedFeedIDs = Set<String>()
// Sync the folders // Sync the folders
for (folderName, groupedTaggings) in taggingsDict { for (folderExternalID, groupedTaggings) in taggingsDict {
guard let folder = folderDict[folderName] else { return } guard let folder = folderDict[folderExternalID] else { return }
let taggingFeedIDs = groupedTaggings.map { $0.feedID } let taggingFeedIDs = groupedTaggings.map { $0.feedID }
// Move any feeds not in the folder to the account // Move any feeds not in the folder to the account
for feed in folder.topLevelWebFeeds { for feed in folder.topLevelWebFeeds {
if !taggingFeedIDs.contains(feed.webFeedID) { if !taggingFeedIDs.contains(feed.webFeedID) {
folder.removeWebFeed(feed) folder.removeWebFeed(feed)
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
account.addWebFeed(feed) account.addWebFeed(feed)
} }
} }
@ -720,14 +719,15 @@ private extension ReaderAPIAccountDelegate {
guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else { guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else {
continue continue
} }
saveFolderRelationship(for: feed, withFolderName: folderName, id: String(subscription.feedID)) saveFolderRelationship(for: feed, folderExternalID: folderExternalID, feedExternalID: subscription.feedID)
folder.addWebFeed(feed) folder.addWebFeed(feed)
taggedFeedIDs.insert(taggingFeedID)
} }
} }
} }
let taggedFeedIDs = Set(subscriptions.map { String($0.feedID) })
// Remove all feeds from the account container that have a tag // Remove all feeds from the account container that have a tag
for feed in account.topLevelWebFeeds { for feed in account.topLevelWebFeeds {
if taggedFeedIDs.contains(feed.webFeedID) { if taggedFeedIDs.contains(feed.webFeedID) {
@ -736,18 +736,19 @@ private extension ReaderAPIAccountDelegate {
} }
} }
func nameToFolderDictionary(with folders: Set<Folder>?) -> [String: Folder] { func externalIDToFolderDictionary(with folders: Set<Folder>?) -> [String: Folder] {
guard let folders = folders else { guard let folders = folders else {
return [String: Folder]() return [String: Folder]()
} }
var d = [String: Folder]() var d = [String: Folder]()
for folder in folders { for folder in folders {
let name = folder.name ?? "" if let externalID = folder.externalID, d[externalID] == nil {
if d[name] == nil { d[externalID] = folder
d[name] = folder
} }
} }
return d return d
} }
@ -784,19 +785,19 @@ private extension ReaderAPIAccountDelegate {
} }
func clearFolderRelationship(for feed: WebFeed, withFolderName folderName: String) { func clearFolderRelationship(for feed: WebFeed, folderExternalID: String?) {
if var folderRelationship = feed.folderRelationship { guard var folderRelationship = feed.folderRelationship, let folderExternalID = folderExternalID else { return }
folderRelationship[folderName] = nil folderRelationship[folderExternalID] = nil
feed.folderRelationship = folderRelationship feed.folderRelationship = folderRelationship
} }
}
func saveFolderRelationship(for feed: WebFeed, withFolderName folderName: String, id: String) { func saveFolderRelationship(for feed: WebFeed, folderExternalID: String?, feedExternalID: String) {
guard let folderExternalID = folderExternalID else { return }
if var folderRelationship = feed.folderRelationship { if var folderRelationship = feed.folderRelationship {
folderRelationship[folderName] = id folderRelationship[folderExternalID] = feedExternalID
feed.folderRelationship = folderRelationship feed.folderRelationship = folderRelationship
} else { } else {
feed.folderRelationship = [folderName: id] feed.folderRelationship = [folderExternalID: feedExternalID]
} }
} }
@ -1052,7 +1053,7 @@ private extension ReaderAPIAccountDelegate {
switch result { switch result {
case .success: case .success:
DispatchQueue.main.async { DispatchQueue.main.async {
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") self.clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
folder.removeWebFeed(feed) folder.removeWebFeed(feed)
account.addFeedIfNotInAnyFolder(feed) account.addFeedIfNotInAnyFolder(feed)
completion(.success(())) completion(.success(()))