From 050c47c41d8fb0afbb106326e94f59dea75b7c38 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Fri, 1 Nov 2024 21:34:08 -0700 Subject: [PATCH] Rename Feed protocol to SidebarItem. Rename FeedIdentifier to SidebarItemIdentifier. Rename WebFeed to Feed. --- Account/Sources/Account/Account.swift | 88 ++--- Account/Sources/Account/AccountDelegate.swift | 12 +- Account/Sources/Account/AccountManager.swift | 2 +- Account/Sources/Account/ArticleFetcher.swift | 2 +- .../CloudKit/CloudKitAccountDelegate.swift | 22 +- .../CloudKit/CloudKitAccountZone.swift | 8 +- .../CloudKitAccountZoneDelegate.swift | 8 +- Account/Sources/Account/Container.swift | 30 +- Account/Sources/Account/DataExtensions.swift | 8 +- Account/Sources/Account/Feed.swift | 329 ++++++++++++++++-- .../FeedWrangler/FeedWranglerAPICaller.swift | 2 +- .../FeedWranglerAccountDelegate.swift | 16 +- .../Feedbin/FeedbinAccountDelegate.swift | 26 +- .../Feedly/FeedlyAccountDelegate.swift | 12 +- .../Feedly/FeedlyAccountDelegateError.swift | 4 +- .../FeedlyAddNewFeedOperation.swift | 2 +- ...teFeedsForCollectionFoldersOperation.swift | 4 +- Account/Sources/Account/Folder.swift | 22 +- .../LocalAccount/LocalAccountDelegate.swift | 16 +- .../LocalAccount/LocalAccountRefresher.swift | 16 +- .../NewsBlurAccountDelegate+Internal.swift | 12 +- .../NewsBlur/NewsBlurAccountDelegate.swift | 14 +- .../ReaderAPI/ReaderAPIAccountDelegate.swift | 20 +- Account/Sources/Account/SidebarItem.swift | 39 +++ ...fier.swift => SidebarItemIdentifier.swift} | 14 +- Account/Sources/Account/WebFeed.swift | 322 ----------------- Mac/AppDelegate.swift | 4 +- .../WebFeedInspectorViewController.swift | 6 +- Mac/MainWindow/MainWindowController.swift | 4 +- .../Sidebar/PasteboardWebFeed.swift | 6 +- .../Sidebar/SidebarDeleteItemsAlert.swift | 2 +- .../Sidebar/SidebarOutlineDataSource.swift | 14 +- ...idebarViewController+ContextualMenus.swift | 16 +- .../Sidebar/SidebarViewController.swift | 48 +-- .../TimelineContainerViewController.swift | 4 +- ...melineViewController+ContextualMenus.swift | 8 +- .../Timeline/TimelineViewController.swift | 26 +- .../NSApplication+Scriptability.swift | 6 +- Mac/Scriptability/WebFeed+Scriptability.swift | 6 +- Shared/Activity/ActivityManager.swift | 28 +- Shared/Commands/DeleteCommand.swift | 8 +- Shared/Extensions/ArticleUtilities.swift | 4 +- Shared/Extensions/SmallIconProvider.swift | 2 +- Shared/Favicons/FaviconDownloader.swift | 8 +- Shared/Favicons/FaviconGenerator.swift | 2 +- Shared/IconImageCache.swift | 30 +- Shared/Images/WebFeedIconDownloader.swift | 18 +- Shared/SmartFeeds/PseudoFeed.swift | 4 +- Shared/SmartFeeds/SearchFeedDelegate.swift | 4 +- .../SearchTimelineFeedDelegate.swift | 4 +- Shared/SmartFeeds/SmartFeed.swift | 4 +- Shared/SmartFeeds/SmartFeedDelegate.swift | 2 +- Shared/SmartFeeds/SmartFeedsController.swift | 4 +- Shared/SmartFeeds/StarredFeedDelegate.swift | 4 +- Shared/SmartFeeds/TodayFeedDelegate.swift | 4 +- Shared/SmartFeeds/UnreadFeed.swift | 4 +- Shared/Timeline/FetchRequestOperation.swift | 6 +- .../Tree/WebFeedTreeControllerDelegate.swift | 14 +- .../UserNotificationManager.swift | 4 +- .../WebFeedInspectorViewController.swift | 2 +- .../MasterFeedViewController+Drag.swift | 2 +- .../MasterFeedViewController+Drop.swift | 10 +- iOS/MasterFeed/MasterFeedViewController.swift | 48 +-- .../MasterTimelineViewController.swift | 4 +- iOS/SceneCoordinator.swift | 82 ++--- 65 files changed, 753 insertions(+), 753 deletions(-) create mode 100644 Account/Sources/Account/SidebarItem.swift rename Account/Sources/Account/{FeedIdentifier.swift => SidebarItemIdentifier.swift} (83%) delete mode 100644 Account/Sources/Account/WebFeed.swift diff --git a/Account/Sources/Account/Account.swift b/Account/Sources/Account/Account.swift index 489182245..cd1f578c4 100644 --- a/Account/Sources/Account/Account.swift +++ b/Account/Sources/Account/Account.swift @@ -57,7 +57,7 @@ public enum FetchType { case unread(_: Int? = nil) case today(_: Int? = nil) case folder(Folder, Bool) - case webFeed(WebFeed) + case webFeed(Feed) case articleIDs(Set) case search(String) case searchWithArticleIDs(String, Set) @@ -143,7 +143,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } } - public var topLevelWebFeeds = Set() + public var topLevelWebFeeds = Set() public var folders: Set? = Set() public var externalID: String? { @@ -163,15 +163,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } private var webFeedDictionariesNeedUpdate = true - private var _idToWebFeedDictionary = [String: WebFeed]() - var idToWebFeedDictionary: [String: WebFeed] { + private var _idToWebFeedDictionary = [String: Feed]() + var idToWebFeedDictionary: [String: Feed] { if webFeedDictionariesNeedUpdate { rebuildWebFeedDictionaries() } return _idToWebFeedDictionary } - private var _externalIDToWebFeedDictionary = [String: WebFeed]() - var externalIDToWebFeedDictionary: [String: WebFeed] { + private var _externalIDToWebFeedDictionary = [String: Feed]() + var externalIDToWebFeedDictionary: [String: Feed] { if webFeedDictionariesNeedUpdate { rebuildWebFeedDictionaries() } @@ -214,7 +214,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, private var unreadCounts = [String: Int]() // [feedID: Int] - private var _flattenedWebFeeds = Set() + private var _flattenedWebFeeds = Set() private var flattenedWebFeedsNeedUpdate = true private lazy var opmlFile = OPMLFile(filename: (dataFolder as NSString).appendingPathComponent("Subscriptions.opml"), account: self) @@ -535,7 +535,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, return existingFolder(withExternalID: externalID) } - func existingContainers(withWebFeed webFeed: WebFeed) -> [Container] { + func existingContainers(withWebFeed webFeed: Feed) -> [Container] { var containers = [Container]() if topLevelWebFeeds.contains(webFeed) { containers.append(self) @@ -586,10 +586,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, return folders?.first(where: { $0.externalID == externalID }) } - func newWebFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> WebFeed { + func newWebFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> Feed { let feedURL = opmlFeedSpecifier.feedURL let metadata = webFeedMetadata(feedURL: feedURL, webFeedID: feedURL) - let feed = WebFeed(account: self, url: opmlFeedSpecifier.feedURL, metadata: metadata) + let feed = Feed(account: self, url: opmlFeedSpecifier.feedURL, metadata: metadata) if let feedTitle = opmlFeedSpecifier.title { if feed.name == nil { feed.name = feedTitle @@ -598,35 +598,35 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, return feed } - public func addWebFeed(_ feed: WebFeed, to container: Container, completion: @escaping (Result) -> Void) { + public func addWebFeed(_ feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { delegate.addWebFeed(for: self, with: feed, to: container, completion: completion) } - public func createWebFeed(url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { + public func createWebFeed(url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { delegate.createWebFeed(for: self, url: url, name: name, container: container, validateFeed: validateFeed, completion: completion) } - func createWebFeed(with name: String?, url: String, webFeedID: String, homePageURL: String?) -> WebFeed { + func createWebFeed(with name: String?, url: String, webFeedID: String, homePageURL: String?) -> Feed { let metadata = webFeedMetadata(feedURL: url, webFeedID: webFeedID) - let feed = WebFeed(account: self, url: url, metadata: metadata) + let feed = Feed(account: self, url: url, metadata: metadata) feed.name = name feed.homePageURL = homePageURL return feed } - public func removeWebFeed(_ feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) { + public func removeWebFeed(_ feed: Feed, from container: Container, completion: @escaping (Result) -> Void) { delegate.removeWebFeed(for: self, with: feed, from: container, completion: completion) } - public func moveWebFeed(_ feed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> Void) { + public func moveWebFeed(_ feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { delegate.moveWebFeed(for: self, with: feed, from: from, to: to, completion: completion) } - public func renameWebFeed(_ feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { + public func renameWebFeed(_ feed: Feed, to name: String, completion: @escaping (Result) -> Void) { delegate.renameWebFeed(for: self, with: feed, to: name, completion: completion) } - public func restoreWebFeed(_ feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) { + public func restoreWebFeed(_ feed: Feed, container: Container, completion: @escaping (Result) -> Void) { delegate.restoreWebFeed(for: self, feed: feed, container: container, completion: completion) } @@ -646,7 +646,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, delegate.restoreFolder(for: self, folder: folder, completion: completion) } - func clearWebFeedMetadata(_ feed: WebFeed) { + func clearWebFeedMetadata(_ feed: Feed) { webFeedMetadata[feed.url] = nil } @@ -656,7 +656,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, structureDidChange() } - public func updateUnreadCounts(for webFeeds: Set, completion: VoidCompletionBlock? = nil) { + public func updateUnreadCounts(for webFeeds: Set, completion: VoidCompletionBlock? = nil) { fetchUnreadCounts(for: webFeeds, completion: completion) } @@ -735,11 +735,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, database.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate(completion) } - public func unreadCount(for webFeed: WebFeed) -> Int { + public func unreadCount(for webFeed: Feed) -> Int { return unreadCounts[webFeed.webFeedID] ?? 0 } - public func setUnreadCount(_ unreadCount: Int, for webFeed: WebFeed) { + public func setUnreadCount(_ unreadCount: Int, for webFeed: Feed) { unreadCounts[webFeed.webFeedID] = unreadCount } @@ -751,7 +751,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, webFeedDictionariesNeedUpdate = true } - func update(_ webFeed: WebFeed, with parsedFeed: ParsedFeed, _ completion: @escaping UpdateArticlesCompletionBlock) { + func update(_ webFeed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping UpdateArticlesCompletionBlock) { // Used only by an On My Mac or iCloud account. precondition(Thread.isMainThread) precondition(type == .onMyMac || type == .cloudKit) @@ -899,7 +899,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, // MARK: - Container - public func flattenedWebFeeds() -> Set { + public func flattenedWebFeeds() -> Set { assert(Thread.isMainThread) if flattenedWebFeedsNeedUpdate { updateFlattenedWebFeeds() @@ -907,13 +907,13 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, return _flattenedWebFeeds } - public func removeWebFeed(_ webFeed: WebFeed) { + public func removeWebFeed(_ webFeed: Feed) { topLevelWebFeeds.remove(webFeed) structureDidChange() postChildrenDidChangeNotification() } - public func removeFeeds(_ webFeeds: Set) { + public func removeFeeds(_ webFeeds: Set) { guard !webFeeds.isEmpty else { return } @@ -922,13 +922,13 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, postChildrenDidChangeNotification() } - public func addWebFeed(_ webFeed: WebFeed) { + public func addWebFeed(_ webFeed: Feed) { topLevelWebFeeds.insert(webFeed) structureDidChange() postChildrenDidChangeNotification() } - func addFeedIfNotInAnyFolder(_ webFeed: WebFeed) { + func addFeedIfNotInAnyFolder(_ webFeed: Feed) { if !flattenedWebFeeds().contains(webFeed) { addWebFeed(webFeed) } @@ -970,7 +970,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } @objc func unreadCountDidChange(_ note: Notification) { - if let feed = note.object as? WebFeed, feed.account === self { + if let feed = note.object as? Feed, feed.account === self { updateUnreadCount() } } @@ -1078,13 +1078,13 @@ private extension Account { fetchUnreadArticlesAsync(forContainer: folder, limit: nil, completion) } - func fetchArticles(webFeed: WebFeed) throws -> Set
{ + func fetchArticles(webFeed: Feed) throws -> Set
{ let articles = try database.fetchArticles(webFeed.webFeedID) validateUnreadCount(webFeed, articles) return articles } - func fetchArticlesAsync(webFeed: WebFeed, _ completion: @escaping ArticleSetResultBlock) { + func fetchArticlesAsync(webFeed: Feed, _ completion: @escaping ArticleSetResultBlock) { database.fetchArticlesAsync(webFeed.webFeedID) { [weak self] articleSetResult in switch articleSetResult { case .success(let articles): @@ -1120,7 +1120,7 @@ private extension Account { return database.fetchArticlesAsync(articleIDs: articleIDs, completion) } - func fetchUnreadArticles(webFeed: WebFeed) throws -> Set
{ + func fetchUnreadArticles(webFeed: Feed) throws -> Set
{ let articles = try database.fetchUnreadArticles(Set([webFeed.webFeedID]), nil) validateUnreadCount(webFeed, articles) return articles @@ -1178,7 +1178,7 @@ private extension Account { } } - func validateUnreadCountsAfterFetchingUnreadArticles(_ webFeeds: Set, _ articles: Set
) { + func validateUnreadCountsAfterFetchingUnreadArticles(_ webFeeds: Set, _ articles: Set
) { // Validate unread counts. This was the site of a performance slowdown: // it was calling going through the entire list of articles once per feed: // feeds.forEach { validateUnreadCount($0, articles) } @@ -1194,7 +1194,7 @@ private extension Account { } } - func validateUnreadCount(_ webFeed: WebFeed, _ articles: Set
) { + func validateUnreadCount(_ webFeed: Feed, _ articles: Set
) { // articles must contain all the unread articles for the feed. // The unread number should match the feed’s unread count. @@ -1225,7 +1225,7 @@ private extension Account { } func updateFlattenedWebFeeds() { - var feeds = Set() + var feeds = Set() feeds.formUnion(topLevelWebFeeds) for folder in folders! { feeds.formUnion(folder.flattenedWebFeeds()) @@ -1236,8 +1236,8 @@ private extension Account { } func rebuildWebFeedDictionaries() { - var idDictionary = [String: WebFeed]() - var externalIDDictionary = [String: WebFeed]() + var idDictionary = [String: Feed]() + var externalIDDictionary = [String: Feed]() flattenedWebFeeds().forEach { (feed) in idDictionary[feed.webFeedID] = feed @@ -1287,7 +1287,7 @@ private extension Account { /// Fetch unread counts for zero or more feeds. /// /// Uses the most efficient method based on how many feeds were passed in. - func fetchUnreadCounts(for feeds: Set, completion: VoidCompletionBlock?) { + func fetchUnreadCounts(for feeds: Set, completion: VoidCompletionBlock?) { if feeds.isEmpty { completion?() return @@ -1303,7 +1303,7 @@ private extension Account { } } - func fetchUnreadCount(_ feed: WebFeed, _ completion: VoidCompletionBlock?) { + func fetchUnreadCount(_ feed: Feed, _ completion: VoidCompletionBlock?) { database.fetchUnreadCount(feed.webFeedID) { result in if let unreadCount = try? result.get() { feed.unreadCount = unreadCount @@ -1312,7 +1312,7 @@ private extension Account { } } - func fetchUnreadCounts(_ feeds: Set, _ completion: VoidCompletionBlock?) { + func fetchUnreadCounts(_ feeds: Set, _ completion: VoidCompletionBlock?) { let webFeedIDs = Set(feeds.map { $0.webFeedID }) database.fetchUnreadCounts(for: webFeedIDs) { result in if let unreadCountDictionary = try? result.get() { @@ -1342,7 +1342,7 @@ private extension Account { } } - func processUnreadCounts(unreadCountDictionary: UnreadCountDictionary, feeds: Set) { + func processUnreadCounts(unreadCountDictionary: UnreadCountDictionary, feeds: Set) { for feed in feeds { // When the unread count is zero, it won’t appear in unreadCountDictionary. let unreadCount = unreadCountDictionary[feed.webFeedID] ?? 0 @@ -1351,7 +1351,7 @@ private extension Account { } func sendNotificationAbout(_ articleChanges: ArticleChanges) { - var webFeeds = Set() + var webFeeds = Set() if let newArticles = articleChanges.newArticles { webFeeds.formUnion(Set(newArticles.compactMap { $0.webFeed })) @@ -1394,11 +1394,11 @@ private extension Account { extension Account { - public func existingWebFeed(withWebFeedID webFeedID: String) -> WebFeed? { + public func existingWebFeed(withWebFeedID webFeedID: String) -> Feed? { return idToWebFeedDictionary[webFeedID] } - public func existingWebFeed(withExternalID externalID: String) -> WebFeed? { + public func existingWebFeed(withExternalID externalID: String) -> Feed? { return externalIDToWebFeedDictionary[externalID] } diff --git a/Account/Sources/Account/AccountDelegate.swift b/Account/Sources/Account/AccountDelegate.swift index 93a998561..eb66b0374 100644 --- a/Account/Sources/Account/AccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegate.swift @@ -36,13 +36,13 @@ protocol AccountDelegate { func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) - func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) - func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) - func addWebFeed(for account: Account, with: WebFeed, to container: Container, completion: @escaping (Result) -> Void) - func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) - func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> Void) + func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) + func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) + func addWebFeed(for account: Account, with: Feed, to container: Container, completion: @escaping (Result) -> Void) + func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> Void) + func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) - func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) + func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result) -> Void) func markArticles(for account: Account, articles: Set
, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping (Result) -> Void) diff --git a/Account/Sources/Account/AccountManager.swift b/Account/Sources/Account/AccountManager.swift index f088f3146..53a271881 100644 --- a/Account/Sources/Account/AccountManager.swift +++ b/Account/Sources/Account/AccountManager.swift @@ -197,7 +197,7 @@ public final class AccountManager: UnreadCountProvider { return nil } - public func existingFeed(with feedID: FeedIdentifier) -> Feed? { + public func existingFeed(with feedID: SidebarItemIdentifier) -> SidebarItem? { switch feedID { case .folder(let accountID, let folderName): if let account = existingAccount(with: accountID) { diff --git a/Account/Sources/Account/ArticleFetcher.swift b/Account/Sources/Account/ArticleFetcher.swift index 308cb155e..39828b4ed 100644 --- a/Account/Sources/Account/ArticleFetcher.swift +++ b/Account/Sources/Account/ArticleFetcher.swift @@ -18,7 +18,7 @@ public protocol ArticleFetcher { func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) } -extension WebFeed: ArticleFetcher { +extension Feed: ArticleFetcher { public func fetchArticles() throws -> Set
{ return try account?.fetchArticles(.webFeed(self)) ?? Set
() diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift index 04c84723c..44ef69fc9 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift @@ -175,7 +175,7 @@ final class CloudKitAccountDelegate: AccountDelegate { } - func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { + func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { guard let url = URL(string: urlString) else { completion(.failure(LocalAccountDelegateError.invalidParameter)) return @@ -186,7 +186,7 @@ final class CloudKitAccountDelegate: AccountDelegate { createRSSWebFeed(for: account, url: url, editedName: editedName, container: container, validateFeed: validateFeed, completion: completion) } - func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { + func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { let editedName = name.isEmpty ? nil : name refreshProgress.addToNumberOfTasksAndRemaining(1) accountZone.renameWebFeed(feed, editedName: editedName) { result in @@ -202,7 +202,7 @@ final class CloudKitAccountDelegate: AccountDelegate { } } - func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) { + func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> Void) { removeWebFeedFromCloud(for: account, with: feed, from: container) { result in switch result { case .success: @@ -222,7 +222,7 @@ final class CloudKitAccountDelegate: AccountDelegate { } } - func moveWebFeed(for account: Account, with feed: WebFeed, from fromContainer: Container, to toContainer: Container, completion: @escaping (Result) -> Void) { + func moveWebFeed(for account: Account, with feed: Feed, from fromContainer: Container, to toContainer: Container, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(1) accountZone.moveWebFeed(feed, from: fromContainer, to: toContainer) { result in self.refreshProgress.completeTask() @@ -238,7 +238,7 @@ final class CloudKitAccountDelegate: AccountDelegate { } } - func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result) -> Void) { + func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(1) accountZone.addWebFeed(feed, to: container) { result in self.refreshProgress.completeTask() @@ -253,7 +253,7 @@ final class CloudKitAccountDelegate: AccountDelegate { } } - func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) { + func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) { createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in switch result { case .success: @@ -562,7 +562,7 @@ private extension CloudKitAccountDelegate { } - func combinedRefresh(_ account: Account, _ webFeeds: Set, completion: @escaping (Result) -> Void) { + func combinedRefresh(_ account: Account, _ webFeeds: Set, completion: @escaping (Result) -> Void) { let group = DispatchGroup() @@ -576,7 +576,7 @@ private extension CloudKitAccountDelegate { } } - func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { + func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { func addDeadFeed() { let feed = account.createWebFeed(with: editedName, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) @@ -683,7 +683,7 @@ private extension CloudKitAccountDelegate { } } - func sendNewArticlesToTheCloud(_ account: Account, _ feed: WebFeed) { + func sendNewArticlesToTheCloud(_ account: Account, _ feed: Feed) { account.fetchArticlesAsync(.webFeed(feed)) { result in switch result { case .success(let articles): @@ -771,7 +771,7 @@ private extension CloudKitAccountDelegate { } - func removeWebFeedFromCloud(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) { + func removeWebFeedFromCloud(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(2) accountZone.removeWebFeed(feed, from: container) { result in self.refreshProgress.completeTask() @@ -798,7 +798,7 @@ private extension CloudKitAccountDelegate { extension CloudKitAccountDelegate: LocalAccountRefresherDelegate { - func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: WebFeed) { + func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: Feed) { refreshProgress.completeTask() } diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountZone.swift b/Account/Sources/Account/CloudKit/CloudKitAccountZone.swift index e5f971c55..eb935dea5 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountZone.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountZone.swift @@ -119,7 +119,7 @@ final class CloudKitAccountZone: CloudKitZone { } /// Rename the given web feed - func renameWebFeed(_ webFeed: WebFeed, editedName: String?, completion: @escaping (Result) -> Void) { + func renameWebFeed(_ webFeed: Feed, editedName: String?, completion: @escaping (Result) -> Void) { guard let externalID = webFeed.externalID else { completion(.failure(CloudKitZoneError.corruptAccount)) return @@ -140,7 +140,7 @@ final class CloudKitAccountZone: CloudKitZone { } /// Removes a web feed from a container and optionally deletes it, calling the completion with true if deleted - func removeWebFeed(_ webFeed: WebFeed, from: Container, completion: @escaping (Result) -> Void) { + func removeWebFeed(_ webFeed: Feed, from: Container, completion: @escaping (Result) -> Void) { guard let fromContainerExternalID = from.externalID else { completion(.failure(CloudKitZoneError.corruptAccount)) return @@ -189,7 +189,7 @@ final class CloudKitAccountZone: CloudKitZone { } } - func moveWebFeed(_ webFeed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> Void) { + func moveWebFeed(_ webFeed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { guard let fromContainerExternalID = from.externalID, let toContainerExternalID = to.externalID else { completion(.failure(CloudKitZoneError.corruptAccount)) return @@ -211,7 +211,7 @@ final class CloudKitAccountZone: CloudKitZone { } } - func addWebFeed(_ webFeed: WebFeed, to: Container, completion: @escaping (Result) -> Void) { + func addWebFeed(_ webFeed: Feed, to: Container, completion: @escaping (Result) -> Void) { guard let toContainerExternalID = to.externalID else { completion(.failure(CloudKitZoneError.corruptAccount)) return diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountZoneDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitAccountZoneDelegate.swift index bc1224836..2686d4d7f 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountZoneDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountZoneDelegate.swift @@ -17,7 +17,7 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate { private typealias UnclaimedWebFeed = (url: URL, name: String?, editedName: String?, homePageURL: String?, webFeedExternalID: String) private var newUnclaimedWebFeeds = [String: [UnclaimedWebFeed]]() - private var existingUnclaimedWebFeeds = [String: [WebFeed]]() + private var existingUnclaimedWebFeeds = [String: [Feed]]() private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit") @@ -140,7 +140,7 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate { private extension CloudKitAcountZoneDelegate { - func updateWebFeed(_ webFeed: WebFeed, name: String?, editedName: String?, homePageURL: String?, containerExternalIDs: [String]) { + func updateWebFeed(_ webFeed: Feed, name: String?, editedName: String?, homePageURL: String?, containerExternalIDs: [String]) { guard let account = account else { return } webFeed.name = name @@ -192,12 +192,12 @@ private extension CloudKitAcountZoneDelegate { } } - func addExistingUnclaimedWebFeed(_ webFeed: WebFeed, containerExternalID: String) { + func addExistingUnclaimedWebFeed(_ webFeed: Feed, containerExternalID: String) { if var unclaimedWebFeeds = self.existingUnclaimedWebFeeds[containerExternalID] { unclaimedWebFeeds.append(webFeed) self.existingUnclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds } else { - var unclaimedWebFeeds = [WebFeed]() + var unclaimedWebFeeds = [Feed]() unclaimedWebFeeds.append(webFeed) self.existingUnclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds } diff --git a/Account/Sources/Account/Container.swift b/Account/Sources/Account/Container.swift index 1a17158ee..fdd6d5f26 100644 --- a/Account/Sources/Account/Container.swift +++ b/Account/Sources/Account/Container.swift @@ -19,7 +19,7 @@ extension Notification.Name { public protocol Container: AnyObject, ContainerIdentifiable { var account: Account? { get } - var topLevelWebFeeds: Set { get set } + var topLevelWebFeeds: Set { get set } var folders: Set? { get set } var externalID: String? { get set } @@ -29,17 +29,17 @@ public protocol Container: AnyObject, ContainerIdentifiable { func hasChildFolder(with: String) -> Bool func childFolder(with: String) -> Folder? - func removeWebFeed(_ webFeed: WebFeed) - func addWebFeed(_ webFeed: WebFeed) + func removeWebFeed(_ webFeed: Feed) + func addWebFeed(_ webFeed: Feed) //Recursive — checks subfolders - func flattenedWebFeeds() -> Set - func has(_ webFeed: WebFeed) -> Bool + func flattenedWebFeeds() -> Set + func has(_ webFeed: Feed) -> Bool func hasWebFeed(with webFeedID: String) -> Bool func hasWebFeed(withURL url: String) -> Bool - func existingWebFeed(withWebFeedID: String) -> WebFeed? - func existingWebFeed(withURL url: String) -> WebFeed? - func existingWebFeed(withExternalID externalID: String) -> WebFeed? + func existingWebFeed(withWebFeedID: String) -> Feed? + func existingWebFeed(withURL url: String) -> Feed? + func existingWebFeed(withExternalID externalID: String) -> Feed? func existingFolder(with name: String) -> Folder? func existingFolder(withID: Int) -> Folder? @@ -69,7 +69,7 @@ public extension Container { } func objectIsChild(_ object: AnyObject) -> Bool { - if let feed = object as? WebFeed { + if let feed = object as? Feed { return topLevelWebFeeds.contains(feed) } if let folder = object as? Folder { @@ -78,8 +78,8 @@ public extension Container { return false } - func flattenedWebFeeds() -> Set { - var feeds = Set() + func flattenedWebFeeds() -> Set { + var feeds = Set() feeds.formUnion(topLevelWebFeeds) if let folders = folders { for folder in folders { @@ -97,11 +97,11 @@ public extension Container { return existingWebFeed(withURL: url) != nil } - func has(_ webFeed: WebFeed) -> Bool { + func has(_ webFeed: Feed) -> Bool { return flattenedWebFeeds().contains(webFeed) } - func existingWebFeed(withWebFeedID webFeedID: String) -> WebFeed? { + func existingWebFeed(withWebFeedID webFeedID: String) -> Feed? { for feed in flattenedWebFeeds() { if feed.webFeedID == webFeedID { return feed @@ -110,7 +110,7 @@ public extension Container { return nil } - func existingWebFeed(withURL url: String) -> WebFeed? { + func existingWebFeed(withURL url: String) -> Feed? { for feed in flattenedWebFeeds() { if feed.url == url { return feed @@ -119,7 +119,7 @@ public extension Container { return nil } - func existingWebFeed(withExternalID externalID: String) -> WebFeed? { + func existingWebFeed(withExternalID externalID: String) -> Feed? { for feed in flattenedWebFeeds() { if feed.externalID == externalID { return feed diff --git a/Account/Sources/Account/DataExtensions.swift b/Account/Sources/Account/DataExtensions.swift index 61ddc93cd..bcdebd100 100644 --- a/Account/Sources/Account/DataExtensions.swift +++ b/Account/Sources/Account/DataExtensions.swift @@ -14,7 +14,7 @@ public extension Notification.Name { static let WebFeedSettingDidChange = Notification.Name(rawValue: "FeedSettingDidChangeNotification") } -public extension WebFeed { +public extension Feed { static let WebFeedSettingUserInfoKey = "feedSetting" @@ -30,7 +30,7 @@ public extension WebFeed { } } -extension WebFeed { +extension Feed { func takeSettings(from parsedFeed: ParsedFeed) { iconURL = parsedFeed.iconURL @@ -41,7 +41,7 @@ extension WebFeed { } func postFeedSettingDidChangeNotification(_ codingKey: WebFeedMetadata.CodingKeys) { - let userInfo = [WebFeed.WebFeedSettingUserInfoKey: codingKey.stringValue] + let userInfo = [Feed.WebFeedSettingUserInfoKey: codingKey.stringValue] NotificationCenter.default.post(name: .WebFeedSettingDidChange, object: self, userInfo: userInfo) } } @@ -56,7 +56,7 @@ public extension Article { return manager.existingAccount(with: accountID) } - var webFeed: WebFeed? { + var webFeed: Feed? { return account?.existingWebFeed(withWebFeedID: webFeedID) } } diff --git a/Account/Sources/Account/Feed.swift b/Account/Sources/Account/Feed.swift index 23d842f0a..df100a5e8 100644 --- a/Account/Sources/Account/Feed.swift +++ b/Account/Sources/Account/Feed.swift @@ -1,39 +1,322 @@ // -// Feed.swift -// Account +// WebFeed.swift +// NetNewsWire // -// Created by Maurice Parker on 11/15/19. -// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// Created by Brent Simmons on 7/1/17. +// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // import Foundation import RSCore +import RSWeb +import Articles -public enum ReadFilterType { - case read - case none - case alwaysRead +public final class Feed: SidebarItem, Renamable, Hashable { + + public var defaultReadFilterType: ReadFilterType { + return .none + } + + public var sidebarItemID: SidebarItemIdentifier? { + guard let accountID = account?.accountID else { + assertionFailure("Expected feed.account, but got nil.") + return nil + } + return SidebarItemIdentifier.webFeed(accountID, webFeedID) + } + + public weak var account: Account? + public let url: String + + public var webFeedID: String { + get { + return metadata.webFeedID + } + set { + metadata.webFeedID = newValue + } + } + + public var homePageURL: String? { + get { + return metadata.homePageURL + } + set { + if let url = newValue, !url.isEmpty { + metadata.homePageURL = url.normalizedURL + } + else { + metadata.homePageURL = nil + } + } + } + + // Note: this is available only if the icon URL was available in the feed. + // The icon URL is a JSON-Feed-only feature. + // Otherwise we find an icon URL via other means, but we don’t store it + // as part of feed metadata. + public var iconURL: String? { + get { + return metadata.iconURL + } + set { + metadata.iconURL = newValue + } + } + + // Note: this is available only if the favicon URL was available in the feed. + // The favicon URL is a JSON-Feed-only feature. + // Otherwise we find a favicon URL via other means, but we don’t store it + // as part of feed metadata. + public var faviconURL: String? { + get { + return metadata.faviconURL + } + set { + metadata.faviconURL = newValue + } + } + + public var name: String? { + didSet { + if name != oldValue { + postDisplayNameDidChangeNotification() + } + } + } + + public var authors: Set? { + get { + if let authorsArray = metadata.authors { + return Set(authorsArray) + } + return nil + } + set { + if let authorsSet = newValue { + metadata.authors = Array(authorsSet) + } + else { + metadata.authors = nil + } + } + } + + public var editedName: String? { + // Don’t let editedName == "" + get { + guard let s = metadata.editedName, !s.isEmpty else { + return nil + } + return s + } + set { + if newValue != editedName { + if let valueToSet = newValue, !valueToSet.isEmpty { + metadata.editedName = valueToSet + } + else { + metadata.editedName = nil + } + postDisplayNameDidChangeNotification() + } + } + } + + public var conditionalGetInfo: HTTPConditionalGetInfo? { + get { + return metadata.conditionalGetInfo + } + set { + metadata.conditionalGetInfo = newValue + } + } + + public var contentHash: String? { + get { + return metadata.contentHash + } + set { + metadata.contentHash = newValue + } + } + + public var isNotifyAboutNewArticles: Bool? { + get { + return metadata.isNotifyAboutNewArticles + } + set { + metadata.isNotifyAboutNewArticles = newValue + } + } + + public var isArticleExtractorAlwaysOn: Bool? { + get { + metadata.isArticleExtractorAlwaysOn + } + set { + metadata.isArticleExtractorAlwaysOn = newValue + } + } + + public var sinceToken: String? { + get { + return metadata.sinceToken + } + set { + metadata.sinceToken = newValue + } + } + + public var externalID: String? { + get { + return metadata.externalID + } + set { + metadata.externalID = newValue + } + } + + // Folder Name: Sync Service Relationship ID + public var folderRelationship: [String: String]? { + get { + return metadata.folderRelationship + } + set { + metadata.folderRelationship = newValue + } + } + + // MARK: - DisplayNameProvider + + public var nameForDisplay: String { + if let s = editedName, !s.isEmpty { + return s + } + if let s = name, !s.isEmpty { + return s + } + return NSLocalizedString("Untitled", comment: "Feed name") + } + + // MARK: - Renamable + + public func rename(to newName: String, completion: @escaping (Result) -> Void) { + guard let account = account else { return } + account.renameWebFeed(self, to: newName, completion: completion) + } + + // MARK: - UnreadCountProvider + + public var unreadCount: Int { + get { + return account?.unreadCount(for: self) ?? 0 + } + set { + if unreadCount == newValue { + return + } + account?.setUnreadCount(newValue, for: self) + postUnreadCountDidChangeNotification() + } + } + + // MARK: - NotificationDisplayName + public var notificationDisplayName: String { + #if os(macOS) + if self.url.contains("www.reddit.com") { + return NSLocalizedString("Show notifications for new posts", comment: "notifyNameDisplay / Reddit") + } else { + return NSLocalizedString("Show notifications for new articles", comment: "notifyNameDisplay / Default") + } + #else + if self.url.contains("www.reddit.com") { + return NSLocalizedString("Notify about new posts", comment: "notifyNameDisplay / Reddit") + } else { + return NSLocalizedString("Notify about new articles", comment: "notifyNameDisplay / Default") + } + #endif + } + + var metadata: WebFeedMetadata + + // MARK: - Private + + private let accountID: String // Used for hashing and equality; account may turn nil + + // MARK: - Init + + init(account: Account, url: String, metadata: WebFeedMetadata) { + self.account = account + self.accountID = account.accountID + self.url = url + self.metadata = metadata + } + + // MARK: - API + + public func dropConditionalGetInfo() { + conditionalGetInfo = nil + contentHash = nil + sinceToken = nil + } + + // MARK: - Hashable + + public func hash(into hasher: inout Hasher) { + hasher.combine(webFeedID) + } + + // MARK: - Equatable + + public class func ==(lhs: Feed, rhs: Feed) -> Bool { + return lhs.webFeedID == rhs.webFeedID && lhs.accountID == rhs.accountID + } } -public protocol Feed: FeedIdentifiable, ArticleFetcher, DisplayNameProvider, UnreadCountProvider { +// MARK: - OPMLRepresentable - var account: Account? { get } - var defaultReadFilterType: ReadFilterType { get } - +extension Feed: OPMLRepresentable { + + public func OPMLString(indentLevel: Int, allowCustomAttributes: Bool) -> String { + // https://github.com/brentsimmons/NetNewsWire/issues/527 + // Don’t use nameForDisplay because that can result in a feed name "Untitled" written to disk, + // which NetNewsWire may take later to be the actual name. + var nameToUse = editedName + if nameToUse == nil { + nameToUse = name + } + if nameToUse == nil { + nameToUse = "" + } + let escapedName = nameToUse!.escapingSpecialXMLCharacters + + var escapedHomePageURL = "" + if let homePageURL = homePageURL { + escapedHomePageURL = homePageURL.escapingSpecialXMLCharacters + } + let escapedFeedURL = url.escapingSpecialXMLCharacters + + var s = "\n" + s = s.prepending(tabCount: indentLevel) + + return s + } } -public extension Feed { - - func readFiltered(readFilterEnabledTable: [FeedIdentifier: Bool]) -> Bool { - guard defaultReadFilterType != .alwaysRead else { - return true - } - if let feedID = feedID, let readFilterEnabled = readFilterEnabledTable[feedID] { - return readFilterEnabled - } else { - return defaultReadFilterType == .read - } +extension Set where Element == Feed { + func webFeedIDs() -> Set { + return Set(map { $0.webFeedID }) + } + + func sorted() -> Array { + return sorted(by: { (webFeed1, webFeed2) -> Bool in + if webFeed1.nameForDisplay.localizedStandardCompare(webFeed2.nameForDisplay) == .orderedSame { + return webFeed1.url < webFeed2.url + } + return webFeed1.nameForDisplay.localizedStandardCompare(webFeed2.nameForDisplay) == .orderedAscending + }) } } diff --git a/Account/Sources/Account/FeedWrangler/FeedWranglerAPICaller.swift b/Account/Sources/Account/FeedWrangler/FeedWranglerAPICaller.swift index 06e880298..008e21742 100644 --- a/Account/Sources/Account/FeedWrangler/FeedWranglerAPICaller.swift +++ b/Account/Sources/Account/FeedWrangler/FeedWranglerAPICaller.swift @@ -169,7 +169,7 @@ final class FeedWranglerAPICaller: NSObject { } - func retrieveFeedItems(page: Int = 0, feed: WebFeed? = nil, completion: @escaping (Result<[FeedWranglerFeedItem], Error>) -> Void) { + func retrieveFeedItems(page: Int = 0, feed: Feed? = nil, completion: @escaping (Result<[FeedWranglerFeedItem], Error>) -> Void) { let queryItems = [ URLQueryItem(name: "read", value: "false"), URLQueryItem(name: "offset", value: String(page * FeedWranglerConfig.pageSize)), diff --git a/Account/Sources/Account/FeedWrangler/FeedWranglerAccountDelegate.swift b/Account/Sources/Account/FeedWrangler/FeedWranglerAccountDelegate.swift index 1be0ec832..5086fad8f 100644 --- a/Account/Sources/Account/FeedWrangler/FeedWranglerAccountDelegate.swift +++ b/Account/Sources/Account/FeedWrangler/FeedWranglerAccountDelegate.swift @@ -315,7 +315,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate { fatalError() } - func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { + func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(2) self.refreshCredentials(for: account) { @@ -336,7 +336,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate { } } - private func addFeedWranglerSubscription(account: Account, subscription sub: FeedWranglerSubscription, name: String?, container: Container, completion: @escaping (Result) -> Void) { + private func addFeedWranglerSubscription(account: Account, subscription sub: FeedWranglerSubscription, name: String?, container: Container, completion: @escaping (Result) -> Void) { DispatchQueue.main.async { let feed = account.createWebFeed(with: sub.title, url: sub.feedURL, webFeedID: String(sub.feedID), homePageURL: sub.siteURL) @@ -364,7 +364,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate { } } - private func initialFeedDownload(account: Account, feed: WebFeed, completion: @escaping (Result) -> Void) { + private func initialFeedDownload(account: Account, feed: Feed, completion: @escaping (Result) -> Void) { self.caller.retrieveFeedItems(page: 0, feed: feed) { results in switch results { @@ -383,7 +383,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate { } } - func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { + func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(2) self.refreshCredentials(for: account) { @@ -408,7 +408,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate { } } - func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result) -> Void) { + func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { // just add to account, folders are not supported DispatchQueue.main.async { account.addFeedIfNotInAnyFolder(feed) @@ -416,7 +416,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate { } } - func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) { + func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(2) self.refreshCredentials(for: account) { @@ -442,11 +442,11 @@ final class FeedWranglerAccountDelegate: AccountDelegate { } } - func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> Void) { + func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { fatalError() } - func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) { + func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) { if let existingFeed = account.existingWebFeed(withURL: feed.url) { account.addWebFeed(existingFeed, to: container) { result in switch result { diff --git a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift index 418c1c81a..da4dca147 100644 --- a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift @@ -388,7 +388,7 @@ final class FeedbinAccountDelegate: AccountDelegate { } - func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { + func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(1) caller.createSubscription(url: url) { result in @@ -420,7 +420,7 @@ final class FeedbinAccountDelegate: AccountDelegate { } - func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { + func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { // This error should never happen guard let subscriptionID = feed.externalID else { @@ -447,7 +447,7 @@ final class FeedbinAccountDelegate: AccountDelegate { } - func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) { + func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> Void) { if feed.folderRelationship?.count ?? 0 > 1 { deleteTagging(for: account, with: feed, from: container, completion: completion) } else { @@ -455,7 +455,7 @@ final class FeedbinAccountDelegate: AccountDelegate { } } - func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> Void) { + func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { if from is Account { addWebFeed(for: account, with: feed, to: to, completion: completion) } else { @@ -470,7 +470,7 @@ final class FeedbinAccountDelegate: AccountDelegate { } } - func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result) -> Void) { + func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { if let folder = container as? Folder, let webFeedID = Int(feed.webFeedID) { refreshProgress.addToNumberOfTasksAndRemaining(1) @@ -502,7 +502,7 @@ final class FeedbinAccountDelegate: AccountDelegate { } - func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) { + func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) { if let existingFeed = account.existingWebFeed(withURL: feed.url) { account.addWebFeed(existingFeed, to: container) { result in @@ -990,14 +990,14 @@ private extension FeedbinAccountDelegate { } } - func clearFolderRelationship(for feed: WebFeed, withFolderName folderName: String) { + func clearFolderRelationship(for feed: Feed, withFolderName folderName: String) { if var folderRelationship = feed.folderRelationship { folderRelationship[folderName] = nil feed.folderRelationship = folderRelationship } } - func saveFolderRelationship(for feed: WebFeed, withFolderName folderName: String, id: String) { + func saveFolderRelationship(for feed: Feed, withFolderName folderName: String, id: String) { if var folderRelationship = feed.folderRelationship { folderRelationship[folderName] = id feed.folderRelationship = folderRelationship @@ -1006,7 +1006,7 @@ private extension FeedbinAccountDelegate { } } - func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [FeedbinSubscriptionChoice], completion: @escaping (Result) -> Void) { + func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [FeedbinSubscriptionChoice], completion: @escaping (Result) -> Void) { var orderFound = 0 let feedSpecifiers: [FeedSpecifier] = choices.map { choice in @@ -1025,7 +1025,7 @@ private extension FeedbinAccountDelegate { } } - func createFeed( account: Account, subscription sub: FeedbinSubscription, name: String?, container: Container, completion: @escaping (Result) -> Void) { + func createFeed( account: Account, subscription sub: FeedbinSubscription, name: String?, container: Container, completion: @escaping (Result) -> Void) { DispatchQueue.main.async { @@ -1058,7 +1058,7 @@ private extension FeedbinAccountDelegate { } - func initialFeedDownload( account: Account, feed: WebFeed, completion: @escaping (Result) -> Void) { + func initialFeedDownload( account: Account, feed: Feed, completion: @escaping (Result) -> Void) { // refreshArticles is being reused and will clear one of the tasks for us refreshProgress.addToNumberOfTasksAndRemaining(4) @@ -1371,7 +1371,7 @@ private extension FeedbinAccountDelegate { } - func deleteTagging(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result) -> Void) { + func deleteTagging(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result) -> Void) { if let folder = container as? Folder, let feedTaggingID = feed.folderRelationship?[folder.name ?? ""] { refreshProgress.addToNumberOfTasksAndRemaining(1) @@ -1401,7 +1401,7 @@ private extension FeedbinAccountDelegate { } - func deleteSubscription(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result) -> Void) { + func deleteSubscription(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result) -> Void) { // This error should never happen guard let subscriptionID = feed.externalID else { diff --git a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift index efa7436ae..abd8585cd 100644 --- a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift @@ -314,7 +314,7 @@ final class FeedlyAccountDelegate: AccountDelegate { } } - func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { + func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { do { guard let credentials = credentials else { @@ -347,7 +347,7 @@ final class FeedlyAccountDelegate: AccountDelegate { } } - func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { + func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { let folderCollectionIds = account.folders?.filter { $0.has(feed) }.compactMap { $0.externalID } guard let collectionIds = folderCollectionIds, let collectionId = collectionIds.first else { completion(.failure(FeedlyAccountDelegateError.unableToRenameFeed(feed.nameForDisplay, name))) @@ -374,7 +374,7 @@ final class FeedlyAccountDelegate: AccountDelegate { feed.editedName = name } - func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result) -> Void) { + func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { do { guard let credentials = credentials else { @@ -405,7 +405,7 @@ final class FeedlyAccountDelegate: AccountDelegate { } } - func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) { + func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> Void) { guard let folder = container as? Folder, let collectionId = folder.externalID else { return DispatchQueue.main.async { completion(.failure(FeedlyAccountDelegateError.unableToRemoveFeed(feed))) @@ -425,7 +425,7 @@ final class FeedlyAccountDelegate: AccountDelegate { folder.removeWebFeed(feed) } - func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> Void) { + func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { guard let from = from as? Folder, let to = to as? Folder else { return DispatchQueue.main.async { completion(.failure(FeedlyAccountDelegateError.addFeedChooseFolder)) @@ -458,7 +458,7 @@ final class FeedlyAccountDelegate: AccountDelegate { to.addWebFeed(feed) } - func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) { + func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) { if let existingFeed = account.existingWebFeed(withURL: feed.url) { account.addWebFeed(existingFeed, to: container) { result in switch result { diff --git a/Account/Sources/Account/Feedly/FeedlyAccountDelegateError.swift b/Account/Sources/Account/Feedly/FeedlyAccountDelegateError.swift index 3c7a42ef9..0ed5cac99 100644 --- a/Account/Sources/Account/Feedly/FeedlyAccountDelegateError.swift +++ b/Account/Sources/Account/Feedly/FeedlyAccountDelegateError.swift @@ -14,11 +14,11 @@ enum FeedlyAccountDelegateError: LocalizedError { case unableToAddFolder(String) case unableToRenameFolder(String, String) case unableToRemoveFolder(String) - case unableToMoveFeedBetweenFolders(WebFeed, Folder, Folder) + case unableToMoveFeedBetweenFolders(Feed, Folder, Folder) case addFeedChooseFolder case addFeedInvalidFolder(Folder) case unableToRenameFeed(String, String) - case unableToRemoveFeed(WebFeed) + case unableToRemoveFeed(Feed) var errorDescription: String? { switch self { diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift index b1a549607..44156cc3f 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift @@ -28,7 +28,7 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl private let getStreamContentsService: FeedlyGetStreamContentsService private let log: OSLog private var feedResourceId: FeedlyFeedResourceId? - var addCompletionHandler: ((Result) -> ())? + var addCompletionHandler: ((Result) -> ())? init(account: Account, credentials: Credentials, url: String, feedName: String?, searchService: FeedlySearchService, addToCollectionService: FeedlyAddFeedToCollectionService, syncUnreadIdsService: FeedlyGetStreamIdsService, getStreamContentsService: FeedlyGetStreamContentsService, database: SyncDatabase, container: Container, progress: DownloadProgress, log: OSLog) throws { diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift index b5424b384..965a8f9b0 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift @@ -46,7 +46,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation { } // Pair each Feed with its Folder. - var feedsAdded = Set() + var feedsAdded = Set() let feedsAndFolders = pairs .map({ (collectionFeeds, folder) -> [(FeedlyFeed, Folder)] in @@ -55,7 +55,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation { } }) .flatMap { $0 } - .compactMap { (collectionFeed, folder) -> (WebFeed, Folder) in + .compactMap { (collectionFeed, folder) -> (Feed, Folder) in // find an existing feed previously added to the account if let feed = account.existingWebFeed(withWebFeedID: collectionFeed.id) { diff --git a/Account/Sources/Account/Folder.swift b/Account/Sources/Account/Folder.swift index 1cff74267..3a4c6aa17 100644 --- a/Account/Sources/Account/Folder.swift +++ b/Account/Sources/Account/Folder.swift @@ -10,7 +10,7 @@ import Foundation import Articles import RSCore -public final class Folder: Feed, Renamable, Container, Hashable { +public final class Folder: SidebarItem, Renamable, Container, Hashable { public var defaultReadFilterType: ReadFilterType { return .read @@ -24,16 +24,16 @@ public final class Folder: Feed, Renamable, Container, Hashable { return ContainerIdentifier.folder(accountID, nameForDisplay) } - public var feedID: FeedIdentifier? { + public var sidebarItemID: SidebarItemIdentifier? { guard let accountID = account?.accountID else { assertionFailure("Expected feed.account, but got nil.") return nil } - return FeedIdentifier.folder(accountID, nameForDisplay) + return SidebarItemIdentifier.folder(accountID, nameForDisplay) } public weak var account: Account? - public var topLevelWebFeeds: Set = Set() + public var topLevelWebFeeds: Set = Set() public var folders: Set? = nil // subfolders are not supported, so this is always nil public var name: String? { @@ -100,25 +100,25 @@ public final class Folder: Feed, Renamable, Container, Hashable { // MARK: Container - public func flattenedWebFeeds() -> Set { + public func flattenedWebFeeds() -> Set { // Since sub-folders are not supported, it’s always the top-level feeds. return topLevelWebFeeds } public func objectIsChild(_ object: AnyObject) -> Bool { // Folders contain Feed objects only, at least for now. - guard let feed = object as? WebFeed else { + guard let feed = object as? Feed else { return false } return topLevelWebFeeds.contains(feed) } - public func addWebFeed(_ feed: WebFeed) { + public func addWebFeed(_ feed: Feed) { topLevelWebFeeds.insert(feed) postChildrenDidChangeNotification() } - public func addFeeds(_ feeds: Set) { + public func addFeeds(_ feeds: Set) { guard !feeds.isEmpty else { return } @@ -126,12 +126,12 @@ public final class Folder: Feed, Renamable, Container, Hashable { postChildrenDidChangeNotification() } - public func removeWebFeed(_ feed: WebFeed) { + public func removeWebFeed(_ feed: Feed) { topLevelWebFeeds.remove(feed) postChildrenDidChangeNotification() } - public func removeFeeds(_ feeds: Set) { + public func removeFeeds(_ feeds: Set) { guard !feeds.isEmpty else { return } @@ -164,7 +164,7 @@ private extension Folder { unreadCount = updatedUnreadCount } - func childrenContain(_ feed: WebFeed) -> Bool { + func childrenContain(_ feed: Feed) -> Bool { return topLevelWebFeeds.contains(feed) } } diff --git a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift index 74b2fd6f8..12caea46a 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift @@ -122,7 +122,7 @@ final class LocalAccountDelegate: AccountDelegate { } - func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { + func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { guard let url = URL(string: urlString) else { completion(.failure(LocalAccountDelegateError.invalidParameter)) return @@ -131,28 +131,28 @@ final class LocalAccountDelegate: AccountDelegate { createRSSWebFeed(for: account, url: url, editedName: name, container: container, completion: completion) } - func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { + func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { feed.editedName = name completion(.success(())) } - func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) { + func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> Void) { container.removeWebFeed(feed) completion(.success(())) } - func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> Void) { + func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { from.removeWebFeed(feed) to.addWebFeed(feed) completion(.success(())) } - func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result) -> Void) { + func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { container.addWebFeed(feed) completion(.success(())) } - func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) { + func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) { container.addWebFeed(feed) completion(.success(())) } @@ -219,7 +219,7 @@ final class LocalAccountDelegate: AccountDelegate { extension LocalAccountDelegate: LocalAccountRefresherDelegate { - func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: WebFeed) { + func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: Feed) { refreshProgress.completeTask() } @@ -231,7 +231,7 @@ extension LocalAccountDelegate: LocalAccountRefresherDelegate { private extension LocalAccountDelegate { - func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result) -> Void) { + func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result) -> Void) { // We need to use a batch update here because we need to assign add the feed to the // container before the name has been downloaded. This will put it in the sidebar diff --git a/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift b/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift index 3fb364410..20af6aee4 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift @@ -14,7 +14,7 @@ import Articles import ArticlesDatabase protocol LocalAccountRefresherDelegate { - func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: WebFeed) + func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: Feed) func localAccountRefresher(_ refresher: LocalAccountRefresher, articleChanges: ArticleChanges, completion: @escaping () -> Void) } @@ -28,7 +28,7 @@ final class LocalAccountRefresher { return DownloadSession(delegate: self) }() - public func refreshFeeds(_ feeds: Set, completion: (() -> Void)? = nil) { + public func refreshFeeds(_ feeds: Set, completion: (() -> Void)? = nil) { guard !feeds.isEmpty else { completion?() return @@ -53,7 +53,7 @@ final class LocalAccountRefresher { extension LocalAccountRefresher: DownloadSessionDelegate { func downloadSession(_ downloadSession: DownloadSession, requestForRepresentedObject representedObject: AnyObject) -> URLRequest? { - guard let feed = representedObject as? WebFeed else { + guard let feed = representedObject as? Feed else { return nil } guard let url = URL(string: feed.url) else { @@ -69,7 +69,7 @@ extension LocalAccountRefresher: DownloadSessionDelegate { } func downloadSession(_ downloadSession: DownloadSession, downloadDidCompleteForRepresentedObject representedObject: AnyObject, response: URLResponse?, data: Data, error: NSError?, completion: @escaping () -> Void) { - let feed = representedObject as! WebFeed + let feed = representedObject as! Feed guard !data.isEmpty, !isSuspended else { completion() @@ -120,7 +120,7 @@ extension LocalAccountRefresher: DownloadSessionDelegate { } func downloadSession(_ downloadSession: DownloadSession, shouldContinueAfterReceivingData data: Data, representedObject: AnyObject) -> Bool { - let feed = representedObject as! WebFeed + let feed = representedObject as! Feed guard !isSuspended else { delegate?.localAccountRefresher(self, requestCompletedFor: feed) return false @@ -139,17 +139,17 @@ extension LocalAccountRefresher: DownloadSessionDelegate { } func downloadSession(_ downloadSession: DownloadSession, didReceiveUnexpectedResponse response: URLResponse, representedObject: AnyObject) { - let feed = representedObject as! WebFeed + let feed = representedObject as! Feed delegate?.localAccountRefresher(self, requestCompletedFor: feed) } func downloadSession(_ downloadSession: DownloadSession, didReceiveNotModifiedResponse: URLResponse, representedObject: AnyObject) { - let feed = representedObject as! WebFeed + let feed = representedObject as! Feed delegate?.localAccountRefresher(self, requestCompletedFor: feed) } func downloadSession(_ downloadSession: DownloadSession, didDiscardDuplicateRepresentedObject representedObject: AnyObject) { - let feed = representedObject as! WebFeed + let feed = representedObject as! Feed delegate?.localAccountRefresher(self, requestCompletedFor: feed) } diff --git a/Account/Sources/Account/NewsBlur/Internals/NewsBlurAccountDelegate+Internal.swift b/Account/Sources/Account/NewsBlur/Internals/NewsBlurAccountDelegate+Internal.swift index f9ac2ad8b..f4c22bbed 100644 --- a/Account/Sources/Account/NewsBlur/Internals/NewsBlurAccountDelegate+Internal.swift +++ b/Account/Sources/Account/NewsBlur/Internals/NewsBlurAccountDelegate+Internal.swift @@ -195,14 +195,14 @@ extension NewsBlurAccountDelegate { } - func clearFolderRelationship(for feed: WebFeed, withFolderName folderName: String) { + func clearFolderRelationship(for feed: Feed, withFolderName folderName: String) { if var folderRelationship = feed.folderRelationship { folderRelationship[folderName] = nil feed.folderRelationship = folderRelationship } } - func saveFolderRelationship(for feed: WebFeed, withFolderName folderName: String, id: String) { + func saveFolderRelationship(for feed: Feed, withFolderName folderName: String, id: String) { if var folderRelationship = feed.folderRelationship { folderRelationship[folderName] = id feed.folderRelationship = folderRelationship @@ -412,7 +412,7 @@ extension NewsBlurAccountDelegate { } } - func createFeed(account: Account, feed: NewsBlurFeed?, name: String?, container: Container, completion: @escaping (Result) -> Void) { + func createFeed(account: Account, feed: NewsBlurFeed?, name: String?, container: Container, completion: @escaping (Result) -> Void) { guard let feed = feed else { completion(.failure(NewsBlurError.invalidParameter)) return @@ -445,7 +445,7 @@ extension NewsBlurAccountDelegate { } } - func downloadFeed(account: Account, feed: WebFeed, page: Int, completion: @escaping (Result) -> Void) { + func downloadFeed(account: Account, feed: Feed, page: Int, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(1) caller.retrieveStories(feedID: feed.webFeedID, page: page) { result in @@ -484,7 +484,7 @@ extension NewsBlurAccountDelegate { } } - func initialFeedDownload(account: Account, feed: WebFeed, completion: @escaping (Result) -> Void) { + func initialFeedDownload(account: Account, feed: Feed, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(1) // Download the initial articles @@ -513,7 +513,7 @@ extension NewsBlurAccountDelegate { } } - func deleteFeed(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result) -> Void) { + func deleteFeed(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result) -> Void) { // This error should never happen guard let feedID = feed.externalID else { completion(.failure(NewsBlurError.invalidParameter)) diff --git a/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift b/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift index b415d03d6..bb5faf7cf 100644 --- a/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift +++ b/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift @@ -423,7 +423,7 @@ final class NewsBlurAccountDelegate: AccountDelegate { } } - func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> ()) { + func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> ()) { refreshProgress.addToNumberOfTasksAndRemaining(1) let folderName = (container as? Folder)?.name @@ -442,7 +442,7 @@ final class NewsBlurAccountDelegate: AccountDelegate { } } - func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> ()) { + func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> ()) { guard let feedID = feed.externalID else { completion(.failure(NewsBlurError.invalidParameter)) return @@ -469,7 +469,7 @@ final class NewsBlurAccountDelegate: AccountDelegate { } } - func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result) -> ()) { + func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result) -> ()) { guard let folder = container as? Folder else { DispatchQueue.main.async { if let account = container as? Account { @@ -488,11 +488,11 @@ final class NewsBlurAccountDelegate: AccountDelegate { completion(.success(())) } - func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> ()) { + func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> ()) { deleteFeed(for: account, with: feed, from: container, completion: completion) } - func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> ()) { + func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> ()) { guard let feedID = feed.externalID else { completion(.failure(NewsBlurError.invalidParameter)) return @@ -519,7 +519,7 @@ final class NewsBlurAccountDelegate: AccountDelegate { } } - func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> ()) { + func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> ()) { if let existingFeed = account.existingWebFeed(withURL: feed.url) { account.addWebFeed(existingFeed, to: container) { result in switch result { @@ -547,7 +547,7 @@ final class NewsBlurAccountDelegate: AccountDelegate { return } - var feedsToRestore: [WebFeed] = [] + var feedsToRestore: [Feed] = [] for feed in folder.topLevelWebFeeds { feedsToRestore.append(feed) folder.topLevelWebFeeds.remove(feed) diff --git a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index e93ffb282..c271e02ec 100644 --- a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -390,7 +390,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } - func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { + func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { guard let url = URL(string: url) else { completion(.failure(ReaderAPIAccountDelegateError.invalidParameter)) return @@ -439,7 +439,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } - func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { + func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { // This error should never happen guard let subscriptionID = feed.externalID else { @@ -466,7 +466,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } - func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) { + func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> Void) { guard let subscriptionID = feed.externalID else { completion(.failure(ReaderAPIAccountDelegateError.invalidParameter)) return @@ -496,7 +496,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } } - func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> Void) { + func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) { if from is Account { addWebFeed(for: account, with: feed, to: to, completion: completion) } else { @@ -524,7 +524,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } } - func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result) -> Void) { + func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result) -> Void) { if let folder = container as? Folder, let feedExternalID = feed.externalID { refreshProgress.addToNumberOfTasksAndRemaining(1) caller.createTagging(subscriptionID: feedExternalID, tagName: folder.name ?? "") { result in @@ -554,7 +554,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } } - func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) { + func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result) -> Void) { if let existingFeed = account.existingWebFeed(withURL: feed.url) { account.addWebFeed(existingFeed, to: container) { result in @@ -901,13 +901,13 @@ private extension ReaderAPIAccountDelegate { } - func clearFolderRelationship(for feed: WebFeed, folderExternalID: String?) { + func clearFolderRelationship(for feed: Feed, folderExternalID: String?) { guard var folderRelationship = feed.folderRelationship, let folderExternalID = folderExternalID else { return } folderRelationship[folderExternalID] = nil feed.folderRelationship = folderRelationship } - func saveFolderRelationship(for feed: WebFeed, folderExternalID: String?, feedExternalID: String) { + func saveFolderRelationship(for feed: Feed, folderExternalID: String?, feedExternalID: String) { guard let folderExternalID = folderExternalID else { return } if var folderRelationship = feed.folderRelationship { folderRelationship[folderExternalID] = feedExternalID @@ -917,7 +917,7 @@ private extension ReaderAPIAccountDelegate { } } - func createFeed( account: Account, subscription sub: ReaderAPISubscription, name: String?, container: Container, completion: @escaping (Result) -> Void) { + func createFeed( account: Account, subscription sub: ReaderAPISubscription, name: String?, container: Container, completion: @escaping (Result) -> Void) { DispatchQueue.main.async { @@ -948,7 +948,7 @@ private extension ReaderAPIAccountDelegate { } - func initialFeedDownload( account: Account, feed: WebFeed, completion: @escaping (Result) -> Void) { + func initialFeedDownload( account: Account, feed: Feed, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(5) // Download the initial articles diff --git a/Account/Sources/Account/SidebarItem.swift b/Account/Sources/Account/SidebarItem.swift new file mode 100644 index 000000000..fab4b18da --- /dev/null +++ b/Account/Sources/Account/SidebarItem.swift @@ -0,0 +1,39 @@ +// +// SidebarItem.swift +// Account +// +// Created by Maurice Parker on 11/15/19. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// + +import Foundation +import RSCore + +public enum ReadFilterType { + case read + case none + case alwaysRead +} + +public protocol SidebarItem: SidebarItemIdentifiable, ArticleFetcher, DisplayNameProvider, UnreadCountProvider { + + var account: Account? { get } + var defaultReadFilterType: ReadFilterType { get } + +} + +public extension SidebarItem { + + func readFiltered(readFilterEnabledTable: [SidebarItemIdentifier: Bool]) -> Bool { + guard defaultReadFilterType != .alwaysRead else { + return true + } + if let feedID = sidebarItemID, let readFilterEnabled = readFilterEnabledTable[feedID] { + return readFilterEnabled + } else { + return defaultReadFilterType == .read + } + + } + +} diff --git a/Account/Sources/Account/FeedIdentifier.swift b/Account/Sources/Account/SidebarItemIdentifier.swift similarity index 83% rename from Account/Sources/Account/FeedIdentifier.swift rename to Account/Sources/Account/SidebarItemIdentifier.swift index 05d3d7f23..2af20201e 100644 --- a/Account/Sources/Account/FeedIdentifier.swift +++ b/Account/Sources/Account/SidebarItemIdentifier.swift @@ -8,11 +8,11 @@ import Foundation -public protocol FeedIdentifiable { - var feedID: FeedIdentifier? { get } +public protocol SidebarItemIdentifiable { + var sidebarItemID: SidebarItemIdentifier? { get } } -public enum FeedIdentifier: CustomStringConvertible, Hashable, Equatable { +public enum SidebarItemIdentifier: CustomStringConvertible, Hashable, Equatable { case smartFeed(String) // String is a unique identifier case script(String) // String is a unique identifier @@ -65,16 +65,16 @@ public enum FeedIdentifier: CustomStringConvertible, Hashable, Equatable { switch type { case "smartFeed": guard let id = userInfo["id"] as? String else { return nil } - self = FeedIdentifier.smartFeed(id) + self = SidebarItemIdentifier.smartFeed(id) case "script": guard let id = userInfo["id"] as? String else { return nil } - self = FeedIdentifier.script(id) + self = SidebarItemIdentifier.script(id) case "feed": guard let accountID = userInfo["accountID"] as? String, let webFeedID = userInfo["webFeedID"] as? String else { return nil } - self = FeedIdentifier.webFeed(accountID, webFeedID) + self = SidebarItemIdentifier.webFeed(accountID, webFeedID) case "folder": guard let accountID = userInfo["accountID"] as? String, let folderName = userInfo["folderName"] as? String else { return nil } - self = FeedIdentifier.folder(accountID, folderName) + self = SidebarItemIdentifier.folder(accountID, folderName) default: return nil } diff --git a/Account/Sources/Account/WebFeed.swift b/Account/Sources/Account/WebFeed.swift deleted file mode 100644 index 119010291..000000000 --- a/Account/Sources/Account/WebFeed.swift +++ /dev/null @@ -1,322 +0,0 @@ -// -// WebFeed.swift -// NetNewsWire -// -// Created by Brent Simmons on 7/1/17. -// Copyright © 2017 Ranchero Software, LLC. All rights reserved. -// - -import Foundation -import RSCore -import RSWeb -import Articles - -public final class WebFeed: Feed, Renamable, Hashable { - - public var defaultReadFilterType: ReadFilterType { - return .none - } - - public var feedID: FeedIdentifier? { - guard let accountID = account?.accountID else { - assertionFailure("Expected feed.account, but got nil.") - return nil - } - return FeedIdentifier.webFeed(accountID, webFeedID) - } - - public weak var account: Account? - public let url: String - - public var webFeedID: String { - get { - return metadata.webFeedID - } - set { - metadata.webFeedID = newValue - } - } - - public var homePageURL: String? { - get { - return metadata.homePageURL - } - set { - if let url = newValue, !url.isEmpty { - metadata.homePageURL = url.normalizedURL - } - else { - metadata.homePageURL = nil - } - } - } - - // Note: this is available only if the icon URL was available in the feed. - // The icon URL is a JSON-Feed-only feature. - // Otherwise we find an icon URL via other means, but we don’t store it - // as part of feed metadata. - public var iconURL: String? { - get { - return metadata.iconURL - } - set { - metadata.iconURL = newValue - } - } - - // Note: this is available only if the favicon URL was available in the feed. - // The favicon URL is a JSON-Feed-only feature. - // Otherwise we find a favicon URL via other means, but we don’t store it - // as part of feed metadata. - public var faviconURL: String? { - get { - return metadata.faviconURL - } - set { - metadata.faviconURL = newValue - } - } - - public var name: String? { - didSet { - if name != oldValue { - postDisplayNameDidChangeNotification() - } - } - } - - public var authors: Set? { - get { - if let authorsArray = metadata.authors { - return Set(authorsArray) - } - return nil - } - set { - if let authorsSet = newValue { - metadata.authors = Array(authorsSet) - } - else { - metadata.authors = nil - } - } - } - - public var editedName: String? { - // Don’t let editedName == "" - get { - guard let s = metadata.editedName, !s.isEmpty else { - return nil - } - return s - } - set { - if newValue != editedName { - if let valueToSet = newValue, !valueToSet.isEmpty { - metadata.editedName = valueToSet - } - else { - metadata.editedName = nil - } - postDisplayNameDidChangeNotification() - } - } - } - - public var conditionalGetInfo: HTTPConditionalGetInfo? { - get { - return metadata.conditionalGetInfo - } - set { - metadata.conditionalGetInfo = newValue - } - } - - public var contentHash: String? { - get { - return metadata.contentHash - } - set { - metadata.contentHash = newValue - } - } - - public var isNotifyAboutNewArticles: Bool? { - get { - return metadata.isNotifyAboutNewArticles - } - set { - metadata.isNotifyAboutNewArticles = newValue - } - } - - public var isArticleExtractorAlwaysOn: Bool? { - get { - metadata.isArticleExtractorAlwaysOn - } - set { - metadata.isArticleExtractorAlwaysOn = newValue - } - } - - public var sinceToken: String? { - get { - return metadata.sinceToken - } - set { - metadata.sinceToken = newValue - } - } - - public var externalID: String? { - get { - return metadata.externalID - } - set { - metadata.externalID = newValue - } - } - - // Folder Name: Sync Service Relationship ID - public var folderRelationship: [String: String]? { - get { - return metadata.folderRelationship - } - set { - metadata.folderRelationship = newValue - } - } - - // MARK: - DisplayNameProvider - - public var nameForDisplay: String { - if let s = editedName, !s.isEmpty { - return s - } - if let s = name, !s.isEmpty { - return s - } - return NSLocalizedString("Untitled", comment: "Feed name") - } - - // MARK: - Renamable - - public func rename(to newName: String, completion: @escaping (Result) -> Void) { - guard let account = account else { return } - account.renameWebFeed(self, to: newName, completion: completion) - } - - // MARK: - UnreadCountProvider - - public var unreadCount: Int { - get { - return account?.unreadCount(for: self) ?? 0 - } - set { - if unreadCount == newValue { - return - } - account?.setUnreadCount(newValue, for: self) - postUnreadCountDidChangeNotification() - } - } - - // MARK: - NotificationDisplayName - public var notificationDisplayName: String { - #if os(macOS) - if self.url.contains("www.reddit.com") { - return NSLocalizedString("Show notifications for new posts", comment: "notifyNameDisplay / Reddit") - } else { - return NSLocalizedString("Show notifications for new articles", comment: "notifyNameDisplay / Default") - } - #else - if self.url.contains("www.reddit.com") { - return NSLocalizedString("Notify about new posts", comment: "notifyNameDisplay / Reddit") - } else { - return NSLocalizedString("Notify about new articles", comment: "notifyNameDisplay / Default") - } - #endif - } - - var metadata: WebFeedMetadata - - // MARK: - Private - - private let accountID: String // Used for hashing and equality; account may turn nil - - // MARK: - Init - - init(account: Account, url: String, metadata: WebFeedMetadata) { - self.account = account - self.accountID = account.accountID - self.url = url - self.metadata = metadata - } - - // MARK: - API - - public func dropConditionalGetInfo() { - conditionalGetInfo = nil - contentHash = nil - sinceToken = nil - } - - // MARK: - Hashable - - public func hash(into hasher: inout Hasher) { - hasher.combine(webFeedID) - } - - // MARK: - Equatable - - public class func ==(lhs: WebFeed, rhs: WebFeed) -> Bool { - return lhs.webFeedID == rhs.webFeedID && lhs.accountID == rhs.accountID - } -} - -// MARK: - OPMLRepresentable - -extension WebFeed: OPMLRepresentable { - - public func OPMLString(indentLevel: Int, allowCustomAttributes: Bool) -> String { - // https://github.com/brentsimmons/NetNewsWire/issues/527 - // Don’t use nameForDisplay because that can result in a feed name "Untitled" written to disk, - // which NetNewsWire may take later to be the actual name. - var nameToUse = editedName - if nameToUse == nil { - nameToUse = name - } - if nameToUse == nil { - nameToUse = "" - } - let escapedName = nameToUse!.escapingSpecialXMLCharacters - - var escapedHomePageURL = "" - if let homePageURL = homePageURL { - escapedHomePageURL = homePageURL.escapingSpecialXMLCharacters - } - let escapedFeedURL = url.escapingSpecialXMLCharacters - - var s = "\n" - s = s.prepending(tabCount: indentLevel) - - return s - } -} - -extension Set where Element == WebFeed { - - func webFeedIDs() -> Set { - return Set(map { $0.webFeedID }) - } - - func sorted() -> Array { - return sorted(by: { (webFeed1, webFeed2) -> Bool in - if webFeed1.nameForDisplay.localizedStandardCompare(webFeed2.nameForDisplay) == .orderedSame { - return webFeed1.url < webFeed2.url - } - return webFeed1.nameForDisplay.localizedStandardCompare(webFeed2.nameForDisplay) == .orderedAscending - }) - } - -} diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 7feee8182..f461fc891 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -350,10 +350,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } @objc func webFeedSettingDidChange(_ note: Notification) { - guard let feed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else { + guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.WebFeedSettingUserInfoKey] as? String else { return } - if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL { + if key == Feed.WebFeedSettingKey.homePageURL || key == Feed.WebFeedSettingKey.faviconURL { let _ = faviconDownloader.favicon(for: feed) } } diff --git a/Mac/Inspector/WebFeedInspectorViewController.swift b/Mac/Inspector/WebFeedInspectorViewController.swift index 39d82a9dc..6c7408a1a 100644 --- a/Mac/Inspector/WebFeedInspectorViewController.swift +++ b/Mac/Inspector/WebFeedInspectorViewController.swift @@ -20,7 +20,7 @@ final class WebFeedInspectorViewController: NSViewController, Inspector { @IBOutlet weak var isNotifyAboutNewArticlesCheckBox: NSButton! @IBOutlet weak var isReaderViewAlwaysOnCheckBox: NSButton? - private var feed: WebFeed? { + private var feed: Feed? { didSet { if feed != oldValue { updateUI() @@ -42,7 +42,7 @@ final class WebFeedInspectorViewController: NSViewController, Inspector { var windowTitle: String = NSLocalizedString("Feed Inspector", comment: "Feed Inspector window title") func canInspect(_ objects: [Any]) -> Bool { - return objects.count == 1 && objects.first is WebFeed + return objects.count == 1 && objects.first is Feed } // MARK: NSViewController @@ -123,7 +123,7 @@ extension WebFeedInspectorViewController: NSTextFieldDelegate { private extension WebFeedInspectorViewController { func updateFeed() { - guard let objects = objects, objects.count == 1, let singleFeed = objects.first as? WebFeed else { + guard let objects = objects, objects.count == 1, let singleFeed = objects.first as? Feed else { feed = nil return } diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index 1d40899bc..24cc17a07 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -176,7 +176,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { } } - if let feed = currentFeedOrFolder as? WebFeed, let noteObject = noteObject as? WebFeed { + if let feed = currentFeedOrFolder as? Feed, let noteObject = noteObject as? Feed { if feed == noteObject { updateWindowTitle() return @@ -633,7 +633,7 @@ extension MainWindowController: TimelineContainerViewControllerDelegate { detailViewController?.setState(detailState, mode: mode) } - func timelineRequestedWebFeedSelection(_: TimelineContainerViewController, webFeed: WebFeed) { + func timelineRequestedWebFeedSelection(_: TimelineContainerViewController, webFeed: Feed) { sidebarViewController?.selectFeed(webFeed) } diff --git a/Mac/MainWindow/Sidebar/PasteboardWebFeed.swift b/Mac/MainWindow/Sidebar/PasteboardWebFeed.swift index f0c838c47..ddc3a5126 100644 --- a/Mac/MainWindow/Sidebar/PasteboardWebFeed.swift +++ b/Mac/MainWindow/Sidebar/PasteboardWebFeed.swift @@ -146,7 +146,7 @@ struct PasteboardWebFeed: Hashable { } } -extension WebFeed: @retroactive PasteboardWriterOwner { +extension Feed: @retroactive PasteboardWriterOwner { public var pasteboardWriter: NSPasteboardWriting { return WebFeedPasteboardWriter(webFeed: self) @@ -155,14 +155,14 @@ extension WebFeed: @retroactive PasteboardWriterOwner { @objc final class WebFeedPasteboardWriter: NSObject, NSPasteboardWriting { - private let webFeed: WebFeed + private let webFeed: Feed static let webFeedUTI = "com.ranchero.webFeed" static let webFeedUTIType = NSPasteboard.PasteboardType(rawValue: webFeedUTI) static let webFeedUTIInternal = "com.ranchero.NetNewsWire-Evergreen.internal.webFeed" static let webFeedUTIInternalType = NSPasteboard.PasteboardType(rawValue: webFeedUTIInternal) - init(webFeed: WebFeed) { + init(webFeed: Feed) { self.webFeed = webFeed } diff --git a/Mac/MainWindow/Sidebar/SidebarDeleteItemsAlert.swift b/Mac/MainWindow/Sidebar/SidebarDeleteItemsAlert.swift index 899c29cf1..96d325044 100644 --- a/Mac/MainWindow/Sidebar/SidebarDeleteItemsAlert.swift +++ b/Mac/MainWindow/Sidebar/SidebarDeleteItemsAlert.swift @@ -22,7 +22,7 @@ enum SidebarDeleteItemsAlert { alert.messageText = NSLocalizedString("Delete Folder", comment: "Delete Folder") let localizedInformativeText = NSLocalizedString("Are you sure you want to delete the “%@” folder?", comment: "Folder delete text") alert.informativeText = NSString.localizedStringWithFormat(localizedInformativeText as NSString, folder.nameForDisplay) as String - } else if let feed = nodes.first?.representedObject as? Feed { + } else if let feed = nodes.first?.representedObject as? SidebarItem { alert.messageText = NSLocalizedString("Delete Feed", comment: "Delete Feed") let localizedInformativeText = NSLocalizedString("Are you sure you want to delete the “%@” feed?", comment: "Feed delete text") alert.informativeText = NSString.localizedStringWithFormat(localizedInformativeText as NSString, feed.nameForDisplay) as String diff --git a/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift b/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift index 59afa8dfd..43fad3aeb 100644 --- a/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift +++ b/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift @@ -136,7 +136,7 @@ private extension SidebarOutlineDataSource { // Don’t allow PseudoFeed to be dragged. // This will have to be revisited later. For instance, // user-created smart feeds should be draggable, maybe. - return node.representedObject is Folder || node.representedObject is WebFeed + return node.representedObject is Folder || node.representedObject is Feed } // MARK: - Drag and Drop @@ -249,7 +249,7 @@ private extension SidebarOutlineDataSource { if let folder = node.representedObject as? Folder { return folder.account } - if let feed = node.representedObject as? WebFeed { + if let feed = node.representedObject as? Feed { return feed.account } return nil @@ -309,7 +309,7 @@ private extension SidebarOutlineDataSource { } func copyWebFeedInAccount(node: Node, to parentNode: Node) { - guard let feed = node.representedObject as? WebFeed, let destination = parentNode.representedObject as? Container else { + guard let feed = node.representedObject as? Feed, let destination = parentNode.representedObject as? Container else { return } @@ -324,7 +324,7 @@ private extension SidebarOutlineDataSource { } func moveWebFeedInAccount(node: Node, to parentNode: Node) { - guard let feed = node.representedObject as? WebFeed, + guard let feed = node.representedObject as? Feed, let source = node.parent?.representedObject as? Container, let destination = parentNode.representedObject as? Container else { return @@ -343,7 +343,7 @@ private extension SidebarOutlineDataSource { } func copyWebFeedBetweenAccounts(node: Node, to parentNode: Node) { - guard let feed = node.representedObject as? WebFeed, + guard let feed = node.representedObject as? Feed, let destinationAccount = nodeAccount(parentNode), let destinationContainer = parentNode.representedObject as? Container else { return @@ -495,7 +495,7 @@ private extension SidebarOutlineDataSource { } func nodeRepresentsAnyDraggedFeed(_ node: Node, _ draggedFeeds: Set) -> Bool { - guard let feed = node.representedObject as? WebFeed else { + guard let feed = node.representedObject as? Feed else { return false } for draggedFeed in draggedFeeds { @@ -520,7 +520,7 @@ private extension SidebarOutlineDataSource { return account } else if let folder = node.representedObject as? Folder { return folder.account - } else if let webFeed = node.representedObject as? WebFeed { + } else if let webFeed = node.representedObject as? Feed { return webFeed.account } else { return nil diff --git a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift index 3d9d4256b..80e7017c5 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift @@ -31,8 +31,8 @@ extension SidebarViewController { let object = objects.first! switch object { - case is WebFeed: - return menuForWebFeed(object as! WebFeed) + case is Feed: + return menuForWebFeed(object as! Feed) case is Folder: return menuForFolder(object as! Folder) case is PseudoFeed: @@ -93,7 +93,7 @@ extension SidebarViewController { @objc func renameFromContextualMenu(_ sender: Any?) { - guard let window = view.window, let menuItem = sender as? NSMenuItem, let object = menuItem.representedObject as? DisplayNameProvider, object is WebFeed || object is Folder else { + guard let window = view.window, let menuItem = sender as? NSMenuItem, let object = menuItem.representedObject as? DisplayNameProvider, object is Feed || object is Folder else { return } @@ -106,7 +106,7 @@ extension SidebarViewController { @objc func toggleNotificationsFromContextMenu(_ sender: Any?) { guard let item = sender as? NSMenuItem, - let feed = item.representedObject as? WebFeed else { + let feed = item.representedObject as? Feed else { return } UNUserNotificationCenter.current().getNotificationSettings { (settings) in @@ -137,7 +137,7 @@ extension SidebarViewController { @objc func toggleArticleExtractorFromContextMenu(_ sender: Any?) { guard let item = sender as? NSMenuItem, - let feed = item.representedObject as? WebFeed else { + let feed = item.representedObject as? Feed else { return } if feed.isArticleExtractorAlwaysOn == nil { feed.isArticleExtractorAlwaysOn = false } @@ -170,7 +170,7 @@ extension SidebarViewController: RenameWindowControllerDelegate { func renameWindowController(_ windowController: RenameWindowController, didRenameObject object: Any, withNewName name: String) { - if let feed = object as? WebFeed { + if let feed = object as? Feed { feed.rename(to: name) { result in switch result { case .success: @@ -206,7 +206,7 @@ private extension SidebarViewController { return menu } - func menuForWebFeed(_ webFeed: WebFeed) -> NSMenu? { + func menuForWebFeed(_ webFeed: Feed) -> NSMenu? { let menu = NSMenu(title: "") @@ -338,7 +338,7 @@ private extension SidebarViewController { func objectIsFeedOrFolder(_ object: Any) -> Bool { - return object is WebFeed || object is Folder + return object is Feed || object is Folder } func menuItem(_ title: String, _ action: Selector, _ representedObject: Any) -> NSMenuItem { diff --git a/Mac/MainWindow/Sidebar/SidebarViewController.swift b/Mac/MainWindow/Sidebar/SidebarViewController.swift index 99b33cb90..3e1fe45e8 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController.swift @@ -97,7 +97,7 @@ protocol SidebarDelegate: AnyObject { func saveState(to state: inout [AnyHashable : Any]) { state[UserInfoKey.readFeedsFilterState] = isReadFiltered state[UserInfoKey.containerExpandedWindowState] = expandedTable.map { $0.userInfo } - state[UserInfoKey.selectedFeedsState] = selectedFeeds.compactMap { $0.feedID?.userInfo } + state[UserInfoKey.selectedFeedsState] = selectedFeeds.compactMap { $0.sidebarItemID?.userInfo } } func restoreState(from state: [AnyHashable : Any]) { @@ -111,7 +111,7 @@ protocol SidebarDelegate: AnyObject { return } - let selectedFeedIdentifers = Set(selectedFeedsState.compactMap( { FeedIdentifier(userInfo: $0) })) + let selectedFeedIdentifers = Set(selectedFeedsState.compactMap( { SidebarItemIdentifier(userInfo: $0) })) selectedFeedIdentifers.forEach { treeControllerDelegate.addFilterException($0) } rebuildTreeAndReloadDataIfNeeded() @@ -119,7 +119,7 @@ protocol SidebarDelegate: AnyObject { var selectIndexes = IndexSet() func selectFeedsVisitor(node: Node) { - if let feedID = (node.representedObject as? FeedIdentifiable)?.feedID { + if let feedID = (node.representedObject as? SidebarItemIdentifiable)?.sidebarItemID { if selectedFeedIdentifers.contains(feedID) { selectIndexes.insert(outlineView.row(forItem: node) ) } @@ -194,15 +194,15 @@ protocol SidebarDelegate: AnyObject { } @objc func webFeedIconDidBecomeAvailable(_ note: Notification) { - guard let webFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed else { return } + guard let webFeed = note.userInfo?[UserInfoKey.webFeed] as? Feed else { return } configureCellsForRepresentedObject(webFeed) } @objc func webFeedSettingDidChange(_ note: Notification) { - guard let webFeed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else { + guard let webFeed = note.object as? Feed, let key = note.userInfo?[Feed.WebFeedSettingUserInfoKey] as? String else { return } - if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL { + if key == Feed.WebFeedSettingKey.homePageURL || key == Feed.WebFeedSettingKey.faviconURL { configureCellsForRepresentedObject(webFeed) } } @@ -444,13 +444,13 @@ protocol SidebarDelegate: AnyObject { // MARK: - API - func selectFeed(_ feed: Feed) { - if isReadFiltered, let feedID = feed.feedID { + func selectFeed(_ feed: SidebarItem) { + if isReadFiltered, let feedID = feed.sidebarItemID { self.treeControllerDelegate.addFilterException(feedID) - if let webFeed = feed as? WebFeed, let account = webFeed.account { + if let webFeed = feed as? Feed, let account = webFeed.account { let parentFolder = account.sortedFolders?.first(where: { $0.objectIsChild(webFeed) }) - if let parentFolderFeedID = parentFolder?.feedID { + if let parentFolderFeedID = parentFolder?.sidebarItemID { self.treeControllerDelegate.addFilterException(parentFolderFeedID) } } @@ -465,7 +465,7 @@ protocol SidebarDelegate: AnyObject { func deepLinkRevealAndSelect(for userInfo: [AnyHashable : Any]) { guard let accountNode = findAccountNode(userInfo), let feedNode = findFeedNode(userInfo, beginningAt: accountNode), - let feed = feedNode.representedObject as? Feed else { + let feed = feedNode.representedObject as? SidebarItem else { return } selectFeed(feed) @@ -510,8 +510,8 @@ private extension SidebarViewController { return [Node]() } - var selectedFeeds: [Feed] { - selectedNodes.compactMap { $0.representedObject as? Feed } + var selectedFeeds: [SidebarItem] { + selectedNodes.compactMap { $0.representedObject as? SidebarItem } } var singleSelectedNode: Node? { @@ -521,26 +521,26 @@ private extension SidebarViewController { return selectedNodes.first! } - var singleSelectedWebFeed: WebFeed? { + var singleSelectedWebFeed: Feed? { guard let node = singleSelectedNode else { return nil } - return node.representedObject as? WebFeed + return node.representedObject as? Feed } func addAllSelectedToFilterExceptions() { selectedFeeds.forEach { addToFilterExeptionsIfNecessary($0) } } - func addToFilterExeptionsIfNecessary(_ feed: Feed?) { - if isReadFiltered, let feedID = feed?.feedID { + func addToFilterExeptionsIfNecessary(_ feed: SidebarItem?) { + if isReadFiltered, let feedID = feed?.sidebarItemID { if feed is PseudoFeed { treeControllerDelegate.addFilterException(feedID) } else if let folderFeed = feed as? Folder { if folderFeed.account?.existingFolder(withID: folderFeed.folderID) != nil { treeControllerDelegate.addFilterException(feedID) } - } else if let webFeed = feed as? WebFeed { + } else if let webFeed = feed as? Feed { if webFeed.account?.existingWebFeed(withWebFeedID: webFeed.webFeedID) != nil { treeControllerDelegate.addFilterException(feedID) addParentFolderToFilterExceptions(webFeed) @@ -549,10 +549,10 @@ private extension SidebarViewController { } } - func addParentFolderToFilterExceptions(_ feed: Feed) { + func addParentFolderToFilterExceptions(_ feed: SidebarItem) { guard let node = treeController.rootNode.descendantNodeRepresentingObject(feed as AnyObject), let folder = node.parent?.representedObject as? Folder, - let folderFeedID = folder.feedID else { + let folderFeedID = folder.sidebarItemID else { return } @@ -610,7 +610,7 @@ private extension SidebarViewController { } func addTreeControllerToFilterExceptionsVisitor(node: Node) { - if let feed = node.representedObject as? Feed, let feedID = feed.feedID { + if let feed = node.representedObject as? SidebarItem, let feedID = feed.sidebarItemID { treeControllerDelegate.addFilterException(feedID) } } @@ -741,7 +741,7 @@ private extension SidebarViewController { guard let webFeedID = userInfo?[ArticlePathKey.webFeedID] as? String else { return nil } - if let node = startingNode.descendantNode(where: { ($0.representedObject as? WebFeed)?.webFeedID == webFeedID }) { + if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.webFeedID == webFeedID }) { return node } return nil @@ -768,7 +768,7 @@ private extension SidebarViewController { } func imageFor(_ node: Node) -> IconImage? { - if let feed = node.representedObject as? WebFeed, let feedIcon = IconImageCache.shared.imageForFeed(feed) { + if let feed = node.representedObject as? Feed, let feedIcon = IconImageCache.shared.imageForFeed(feed) { return feedIcon } if let smallIconProvider = node.representedObject as? SmallIconProvider { @@ -858,7 +858,7 @@ private extension Node { if representedObject === object { return true } - if let feed1 = object as? WebFeed, let feed2 = representedObject as? WebFeed { + if let feed1 = object as? Feed, let feed2 = representedObject as? Feed { return feed1 == feed2 } return false diff --git a/Mac/MainWindow/Timeline/TimelineContainerViewController.swift b/Mac/MainWindow/Timeline/TimelineContainerViewController.swift index c697a1a0c..a6d785fc3 100644 --- a/Mac/MainWindow/Timeline/TimelineContainerViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineContainerViewController.swift @@ -12,7 +12,7 @@ import Articles protocol TimelineContainerViewControllerDelegate: AnyObject { func timelineSelectionDidChange(_: TimelineContainerViewController, articles: [Article]?, mode: TimelineSourceMode) - func timelineRequestedWebFeedSelection(_: TimelineContainerViewController, webFeed: WebFeed) + func timelineRequestedWebFeedSelection(_: TimelineContainerViewController, webFeed: Feed) func timelineInvalidatedRestorationState(_: TimelineContainerViewController) } @@ -141,7 +141,7 @@ extension TimelineContainerViewController: TimelineDelegate { delegate?.timelineSelectionDidChange(self, articles: selectedArticles, mode: mode(for: timelineViewController)) } - func timelineRequestedWebFeedSelection(_: TimelineViewController, webFeed: WebFeed) { + func timelineRequestedWebFeedSelection(_: TimelineViewController, webFeed: Feed) { delegate?.timelineRequestedWebFeedSelection(self, webFeed: webFeed) } diff --git a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift index dd83afdcb..2b4a7a78c 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift @@ -65,7 +65,7 @@ extension TimelineViewController { } @objc func selectFeedInSidebarFromContextualMenu(_ sender: Any?) { - guard let menuItem = sender as? NSMenuItem, let webFeed = menuItem.representedObject as? WebFeed else { + guard let menuItem = sender as? NSMenuItem, let webFeed = menuItem.representedObject as? Feed else { return } delegate?.timelineRequestedWebFeedSelection(self, webFeed: webFeed) @@ -164,7 +164,7 @@ private extension TimelineViewController { menu.addSeparatorIfNeeded() if articles.count == 1, let feed = articles.first!.webFeed { - if !(representedObjects?.contains(where: { $0 as? WebFeed == feed }) ?? false) { + if !(representedObjects?.contains(where: { $0 as? Feed == feed }) ?? false) { menu.addItem(selectFeedInSidebarMenuItem(feed)) } if let markAllMenuItem = markAllAsReadMenuItem(feed) { @@ -248,13 +248,13 @@ private extension TimelineViewController { return menuItem(NSLocalizedString("Mark Below as Read", comment: "Command"), #selector(markBelowArticlesReadFromContextualMenu(_:)), articles) } - func selectFeedInSidebarMenuItem(_ feed: WebFeed) -> NSMenuItem { + func selectFeedInSidebarMenuItem(_ feed: Feed) -> NSMenuItem { let localizedMenuText = NSLocalizedString("Select “%@” in Sidebar", comment: "Command") let formattedMenuText = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) return menuItem(formattedMenuText as String, #selector(selectFeedInSidebarFromContextualMenu(_:)), feed) } - func markAllAsReadMenuItem(_ feed: WebFeed) -> NSMenuItem? { + func markAllAsReadMenuItem(_ feed: Feed) -> NSMenuItem? { guard let articlesSet = try? feed.fetchArticles() else { return nil } diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index 1467649e4..a3a4199d8 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -14,7 +14,7 @@ import os.log protocol TimelineDelegate: AnyObject { func timelineSelectionDidChange(_: TimelineViewController, selectedArticles: [Article]?) - func timelineRequestedWebFeedSelection(_: TimelineViewController, webFeed: WebFeed) + func timelineRequestedWebFeedSelection(_: TimelineViewController, webFeed: Feed) func timelineInvalidatedRestorationState(_: TimelineViewController) } @@ -28,15 +28,15 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr @IBOutlet var tableView: TimelineTableView! - private var readFilterEnabledTable = [FeedIdentifier: Bool]() + private var readFilterEnabledTable = [SidebarItemIdentifier: Bool]() var isReadFiltered: Bool? { - guard representedObjects?.count == 1, let timelineFeed = representedObjects?.first as? Feed else { + guard representedObjects?.count == 1, let timelineFeed = representedObjects?.first as? SidebarItem else { return nil } guard timelineFeed.defaultReadFilterType != .alwaysRead else { return nil } - if let feedID = timelineFeed.feedID, let readFilterEnabled = readFilterEnabledTable[feedID] { + if let feedID = timelineFeed.sidebarItemID, let readFilterEnabled = readFilterEnabledTable[feedID] { return readFilterEnabled } else { return timelineFeed.defaultReadFilterType == .read @@ -46,7 +46,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr var isCleanUpAvailable: Bool { let isEligibleForCleanUp: Bool? - if representedObjects?.count == 1, let timelineFeed = representedObjects?.first as? Feed, timelineFeed.defaultReadFilterType == .alwaysRead { + if representedObjects?.count == 1, let timelineFeed = representedObjects?.first as? SidebarItem, timelineFeed.defaultReadFilterType == .alwaysRead { isEligibleForCleanUp = true } else { isEligibleForCleanUp = isReadFiltered @@ -111,7 +111,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr return } - if let representedObjects = representedObjects, representedObjects.count == 1 && representedObjects.first is WebFeed { + if let representedObjects = representedObjects, representedObjects.count == 1 && representedObjects.first is Feed { showFeedNames = { for article in articles { if !article.byline().isEmpty { @@ -263,7 +263,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } func toggleReadFilter() { - guard let filter = isReadFiltered, let feedID = (representedObjects?.first as? Feed)?.feedID else { return } + guard let filter = isReadFiltered, let feedID = (representedObjects?.first as? SidebarItem)?.sidebarItemID else { return } readFilterEnabledTable[feedID] = !filter delegate?.timelineInvalidatedRestorationState(self) fetchAndReplacePreservingSelection() @@ -287,7 +287,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } for i in 0.. Bool in @@ -636,7 +636,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @objc func accountDidDownloadArticles(_ note: Notification) { - guard let feeds = note.userInfo?[Account.UserInfoKey.webFeeds] as? Set else { + guard let feeds = note.userInfo?[Account.UserInfoKey.webFeeds] as? Set else { return } @@ -1151,7 +1151,7 @@ private extension TimelineViewController { var fetchedArticles = Set
() for fetchers in fetchers { - if (fetchers as? Feed)?.readFiltered(readFilterEnabledTable: readFilterEnabledTable) ?? true { + if (fetchers as? SidebarItem)?.readFiltered(readFilterEnabledTable: readFilterEnabledTable) ?? true { if let articles = try? fetchers.fetchUnreadArticles() { fetchedArticles.formUnion(articles) } @@ -1226,14 +1226,14 @@ private extension TimelineViewController { return representedObjects?.contains(where: { $0 is Folder }) ?? false } - func representedObjectsContainsAnyWebFeed(_ webFeeds: Set) -> Bool { + func representedObjectsContainsAnyWebFeed(_ webFeeds: Set) -> Bool { // Return true if there’s a match or if a folder contains (recursively) one of feeds guard let representedObjects = representedObjects else { return false } for representedObject in representedObjects { - if let feed = representedObject as? WebFeed { + if let feed = representedObject as? Feed { for oneFeed in webFeeds { if feed.webFeedID == oneFeed.webFeedID || feed.url == oneFeed.url { return true diff --git a/Mac/Scriptability/NSApplication+Scriptability.swift b/Mac/Scriptability/NSApplication+Scriptability.swift index b99095259..63b0c69af 100644 --- a/Mac/Scriptability/NSApplication+Scriptability.swift +++ b/Mac/Scriptability/NSApplication+Scriptability.swift @@ -73,10 +73,10 @@ extension NSApplication : ScriptingObjectContainer { for 'articles of feed "The Shape of Everything" of account "On My Mac"' */ - func allWebFeeds() -> [WebFeed] { + func allWebFeeds() -> [Feed] { let accounts = AccountManager.shared.activeAccounts - let emptyFeeds:[WebFeed] = [] - return accounts.reduce(emptyFeeds) { (result, nthAccount) -> [WebFeed] in + let emptyFeeds:[Feed] = [] + return accounts.reduce(emptyFeeds) { (result, nthAccount) -> [Feed] in let accountFeeds = Array(nthAccount.topLevelWebFeeds) return result + accountFeeds } diff --git a/Mac/Scriptability/WebFeed+Scriptability.swift b/Mac/Scriptability/WebFeed+Scriptability.swift index e6b22e0dc..e7dae29cc 100644 --- a/Mac/Scriptability/WebFeed+Scriptability.swift +++ b/Mac/Scriptability/WebFeed+Scriptability.swift @@ -14,10 +14,10 @@ import Articles @objc(ScriptableWebFeed) class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer { - let webFeed:WebFeed + let webFeed:Feed let container:ScriptingObjectContainer - init (_ webFeed:WebFeed, container:ScriptingObjectContainer) { + init (_ webFeed:Feed, container:ScriptingObjectContainer) { self.webFeed = webFeed self.container = container } @@ -71,7 +71,7 @@ class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta return url } - class func scriptableFeed(_ feed:WebFeed, account:Account, folder:Folder?) -> ScriptableWebFeed { + class func scriptableFeed(_ feed:Feed, account:Account, folder:Folder?) -> ScriptableWebFeed { let scriptableAccount = ScriptableAccount(account) if let folder = folder { let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount) diff --git a/Shared/Activity/ActivityManager.swift b/Shared/Activity/ActivityManager.swift index 624bc4a30..f1d18fe1e 100644 --- a/Shared/Activity/ActivityManager.swift +++ b/Shared/Activity/ActivityManager.swift @@ -48,12 +48,12 @@ class ActivityManager { invalidateNextUnread() } - func selecting(feed: Feed) { + func selecting(feed: SidebarItem) { invalidateCurrentActivities() selectingActivity = makeSelectFeedActivity(feed: feed) - if let webFeed = feed as? WebFeed { + if let webFeed = feed as? Feed { updateSelectingActivityFeedSearchAttributes(with: webFeed) } @@ -86,7 +86,7 @@ class ActivityManager { nextUnreadActivity = nil } - func reading(feed: Feed?, article: Article?) { + func reading(feed: SidebarItem?, article: Article?) { invalidateReading() invalidateNextUnread() @@ -134,13 +134,13 @@ class ActivityManager { CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids) } - static func cleanUp(_ webFeed: WebFeed) { + static func cleanUp(_ webFeed: Feed) { CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: identifiers(for: webFeed)) } #endif @objc func webFeedIconDidBecomeAvailable(_ note: Notification) { - guard let webFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed, let activityFeedId = selectingActivity?.userInfo?[ArticlePathKey.webFeedID] as? String else { + guard let webFeed = note.userInfo?[UserInfoKey.webFeed] as? Feed, let activityFeedId = selectingActivity?.userInfo?[ArticlePathKey.webFeedID] as? String else { return } @@ -161,7 +161,7 @@ class ActivityManager { private extension ActivityManager { - func makeSelectFeedActivity(feed: Feed) -> NSUserActivity { + func makeSelectFeedActivity(feed: SidebarItem) -> NSUserActivity { let activity = NSUserActivity(activityType: ActivityType.selectFeed.rawValue) let localizedText = NSLocalizedString("See articles in “%@”", comment: "See articles in Folder") @@ -171,27 +171,27 @@ private extension ActivityManager { activity.keywords = Set(makeKeywords(title)) activity.isEligibleForSearch = true - let articleFetcherIdentifierUserInfo = feed.feedID?.userInfo ?? [AnyHashable: Any]() + let articleFetcherIdentifierUserInfo = feed.sidebarItemID?.userInfo ?? [AnyHashable: Any]() activity.userInfo = [UserInfoKey.feedIdentifier: articleFetcherIdentifierUserInfo] activity.requiredUserInfoKeys = Set(activity.userInfo!.keys.map { $0 as! String }) - activity.persistentIdentifier = feed.feedID?.description ?? "" + activity.persistentIdentifier = feed.sidebarItemID?.description ?? "" #if os(iOS) activity.suggestedInvocationPhrase = title activity.isEligibleForPrediction = true - activity.contentAttributeSet?.relatedUniqueIdentifier = feed.feedID?.description ?? "" + activity.contentAttributeSet?.relatedUniqueIdentifier = feed.sidebarItemID?.description ?? "" #endif return activity } - func makeReadArticleActivity(feed: Feed?, article: Article) -> NSUserActivity { + func makeReadArticleActivity(feed: SidebarItem?, article: Article) -> NSUserActivity { let activity = NSUserActivity(activityType: ActivityType.readArticle.rawValue) activity.title = ArticleStringFormatter.truncatedTitle(article) if let feed = feed { - let articleFetcherIdentifierUserInfo = feed.feedID?.userInfo ?? [AnyHashable: Any]() + let articleFetcherIdentifierUserInfo = feed.sidebarItemID?.userInfo ?? [AnyHashable: Any]() let articlePathUserInfo = article.pathUserInfo activity.userInfo = [UserInfoKey.feedIdentifier: articleFetcherIdentifierUserInfo, UserInfoKey.articlePath: articlePathUserInfo] } else { @@ -244,7 +244,7 @@ private extension ActivityManager { return value?.components(separatedBy: " ").filter { $0.count > 2 } ?? [] } - func updateSelectingActivityFeedSearchAttributes(with feed: WebFeed) { + func updateSelectingActivityFeedSearchAttributes(with feed: Feed) { let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String) attributeSet.title = feed.nameForDisplay @@ -277,7 +277,7 @@ private extension ActivityManager { return "account_\(folder.account!.accountID)_folder_\(folder.nameForDisplay)" } - static func identifier(for feed: WebFeed) -> String { + static func identifier(for feed: Feed) -> String { return "account_\(feed.account!.accountID)_feed_\(feed.webFeedID)" } @@ -285,7 +285,7 @@ private extension ActivityManager { return "account_\(article.accountID)_feed_\(article.webFeedID)_article_\(article.articleID)" } - static func identifiers(for feed: WebFeed) -> [String] { + static func identifiers(for feed: Feed) -> [String] { var ids = [String]() ids.append(identifier(for: feed)) if let articles = try? feed.fetchArticles() { diff --git a/Shared/Commands/DeleteCommand.swift b/Shared/Commands/DeleteCommand.swift index 991b7b430..7c16c9e65 100644 --- a/Shared/Commands/DeleteCommand.swift +++ b/Shared/Commands/DeleteCommand.swift @@ -77,7 +77,7 @@ final class DeleteCommand: UndoableCommand { } for node in nodes { - if let _ = node.representedObject as? WebFeed { + if let _ = node.representedObject as? Feed { continue } if let _ = node.representedObject as? Folder { @@ -98,7 +98,7 @@ private struct SidebarItemSpecifier { private weak var account: Account? private let parentFolder: Folder? private let folder: Folder? - private let webFeed: WebFeed? + private let webFeed: Feed? private let path: ContainerPath private let errorHandler: (Error) -> () @@ -118,7 +118,7 @@ private struct SidebarItemSpecifier { self.parentFolder = node.parentFolder() - if let webFeed = node.representedObject as? WebFeed { + if let webFeed = node.representedObject as? Feed { self.webFeed = webFeed self.folder = nil account = webFeed.account @@ -271,7 +271,7 @@ private struct DeleteActionName { var numberOfFolders = 0 for node in nodes { - if let _ = node.representedObject as? WebFeed { + if let _ = node.representedObject as? Feed { numberOfFeeds += 1 } else if let _ = node.representedObject as? Folder { diff --git a/Shared/Extensions/ArticleUtilities.swift b/Shared/Extensions/ArticleUtilities.swift index 0b52c15d8..09560b3e2 100644 --- a/Shared/Extensions/ArticleUtilities.swift +++ b/Shared/Extensions/ArticleUtilities.swift @@ -42,7 +42,7 @@ private func accountAndArticlesDictionary(_ articles: Set
) -> [String: extension Article { - var webFeed: WebFeed? { + var webFeed: Feed? { return account?.existingWebFeed(withWebFeedID: webFeedID) } @@ -121,7 +121,7 @@ extension Article { return IconImageCache.shared.imageForArticle(self) } - func iconImageUrl(webFeed: WebFeed) -> URL? { + func iconImageUrl(webFeed: Feed) -> URL? { if let image = iconImage() { let fm = FileManager.default var path = fm.urls(for: .cachesDirectory, in: .userDomainMask)[0] diff --git a/Shared/Extensions/SmallIconProvider.swift b/Shared/Extensions/SmallIconProvider.swift index a26b83fb6..195650024 100644 --- a/Shared/Extensions/SmallIconProvider.swift +++ b/Shared/Extensions/SmallIconProvider.swift @@ -25,7 +25,7 @@ extension Account: SmallIconProvider { } } -extension WebFeed: SmallIconProvider { +extension Feed: SmallIconProvider { var smallIcon: IconImage? { if let iconImage = appDelegate.faviconDownloader.favicon(for: self) { diff --git a/Shared/Favicons/FaviconDownloader.swift b/Shared/Favicons/FaviconDownloader.swift index 3f8b85d30..773a0d1c9 100644 --- a/Shared/Favicons/FaviconDownloader.swift +++ b/Shared/Favicons/FaviconDownloader.swift @@ -44,7 +44,7 @@ final class FaviconDownloader { } private let queue: DispatchQueue - private var cache = [WebFeed: IconImage]() // faviconURL: RSImage + private var cache = [Feed: IconImage]() // faviconURL: RSImage struct UserInfoKey { static let faviconURL = "faviconURL" @@ -69,10 +69,10 @@ final class FaviconDownloader { // MARK: - API func resetCache() { - cache = [WebFeed: IconImage]() + cache = [Feed: IconImage]() } - func favicon(for webFeed: WebFeed) -> IconImage? { + func favicon(for webFeed: Feed) -> IconImage? { assert(Thread.isMainThread) @@ -94,7 +94,7 @@ final class FaviconDownloader { return nil } - func faviconAsIcon(for webFeed: WebFeed) -> IconImage? { + func faviconAsIcon(for webFeed: Feed) -> IconImage? { if let image = cache[webFeed] { return image diff --git a/Shared/Favicons/FaviconGenerator.swift b/Shared/Favicons/FaviconGenerator.swift index bb4d1b054..dfb632581 100644 --- a/Shared/Favicons/FaviconGenerator.swift +++ b/Shared/Favicons/FaviconGenerator.swift @@ -14,7 +14,7 @@ final class FaviconGenerator { private static var faviconGeneratorCache = [String: IconImage]() // feedURL: RSImage - static func favicon(_ webFeed: WebFeed) -> IconImage { + static func favicon(_ webFeed: Feed) -> IconImage { if let favicon = FaviconGenerator.faviconGeneratorCache[webFeed.url] { return favicon diff --git a/Shared/IconImageCache.swift b/Shared/IconImageCache.swift index 1a7bc1826..6bb016406 100644 --- a/Shared/IconImageCache.swift +++ b/Shared/IconImageCache.swift @@ -14,13 +14,13 @@ class IconImageCache { static var shared = IconImageCache() - private var smartFeedIconImageCache = [FeedIdentifier: IconImage]() - private var webFeedIconImageCache = [FeedIdentifier: IconImage]() - private var faviconImageCache = [FeedIdentifier: IconImage]() - private var smallIconImageCache = [FeedIdentifier: IconImage]() + private var smartFeedIconImageCache = [SidebarItemIdentifier: IconImage]() + private var webFeedIconImageCache = [SidebarItemIdentifier: IconImage]() + private var faviconImageCache = [SidebarItemIdentifier: IconImage]() + private var smallIconImageCache = [SidebarItemIdentifier: IconImage]() private var authorIconImageCache = [Author: IconImage]() - func imageFor(_ feedID: FeedIdentifier) -> IconImage? { + func imageFor(_ feedID: SidebarItemIdentifier) -> IconImage? { if let smartFeed = SmartFeedsController.shared.find(by: feedID) { return imageForFeed(smartFeed) } @@ -30,15 +30,15 @@ class IconImageCache { return nil } - func imageForFeed(_ feed: Feed) -> IconImage? { - guard let feedID = feed.feedID else { + func imageForFeed(_ feed: SidebarItem) -> IconImage? { + guard let feedID = feed.sidebarItemID else { return nil } if let smartFeed = feed as? PseudoFeed { return imageForSmartFeed(smartFeed, feedID) } - if let webFeed = feed as? WebFeed, let iconImage = imageForWebFeed(webFeed, feedID) { + if let webFeed = feed as? Feed, let iconImage = imageForWebFeed(webFeed, feedID) { return iconImage } if let smallIconProvider = feed as? SmallIconProvider { @@ -59,17 +59,17 @@ class IconImageCache { } func emptyCache() { - smartFeedIconImageCache = [FeedIdentifier: IconImage]() - webFeedIconImageCache = [FeedIdentifier: IconImage]() - faviconImageCache = [FeedIdentifier: IconImage]() - smallIconImageCache = [FeedIdentifier: IconImage]() + smartFeedIconImageCache = [SidebarItemIdentifier: IconImage]() + webFeedIconImageCache = [SidebarItemIdentifier: IconImage]() + faviconImageCache = [SidebarItemIdentifier: IconImage]() + smallIconImageCache = [SidebarItemIdentifier: IconImage]() authorIconImageCache = [Author: IconImage]() } } private extension IconImageCache { - func imageForSmartFeed(_ smartFeed: PseudoFeed, _ feedID: FeedIdentifier) -> IconImage? { + func imageForSmartFeed(_ smartFeed: PseudoFeed, _ feedID: SidebarItemIdentifier) -> IconImage? { if let iconImage = smartFeedIconImageCache[feedID] { return iconImage } @@ -80,7 +80,7 @@ private extension IconImageCache { return nil } - func imageForWebFeed(_ webFeed: WebFeed, _ feedID: FeedIdentifier) -> IconImage? { + func imageForWebFeed(_ webFeed: Feed, _ feedID: SidebarItemIdentifier) -> IconImage? { if let iconImage = webFeedIconImageCache[feedID] { return iconImage } @@ -98,7 +98,7 @@ private extension IconImageCache { return nil } - func imageForSmallIconProvider(_ provider: SmallIconProvider, _ feedID: FeedIdentifier) -> IconImage? { + func imageForSmallIconProvider(_ provider: SmallIconProvider, _ feedID: SidebarItemIdentifier) -> IconImage? { if let iconImage = smallIconImageCache[feedID] { return iconImage } diff --git a/Shared/Images/WebFeedIconDownloader.swift b/Shared/Images/WebFeedIconDownloader.swift index 7790b6292..1a7551d33 100644 --- a/Shared/Images/WebFeedIconDownloader.swift +++ b/Shared/Images/WebFeedIconDownloader.swift @@ -53,8 +53,8 @@ public final class WebFeedIconDownloader { }() private var urlsInProgress = Set() - private var cache = [WebFeed: IconImage]() - private var waitingForFeedURLs = [String: WebFeed]() + private var cache = [Feed: IconImage]() + private var waitingForFeedURLs = [String: Feed]() init(imageDownloader: ImageDownloader, folder: String) { self.imageDownloader = imageDownloader @@ -68,10 +68,10 @@ public final class WebFeedIconDownloader { } func resetCache() { - cache = [WebFeed: IconImage]() + cache = [Feed: IconImage]() } - func icon(for feed: WebFeed) -> IconImage? { + func icon(for feed: Feed) -> IconImage? { if let cachedImage = cache[feed] { return cachedImage @@ -153,7 +153,7 @@ public final class WebFeedIconDownloader { private extension WebFeedIconDownloader { - func icon(forHomePageURL homePageURL: String, feed: WebFeed, _ imageResultBlock: @escaping (RSImage?) -> Void) { + func icon(forHomePageURL homePageURL: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) { if homePagesWithNoIconURLCache.contains(homePageURL) || homePagesWithUglyIcons.contains(homePageURL) { imageResultBlock(nil) @@ -168,7 +168,7 @@ private extension WebFeedIconDownloader { findIconURLForHomePageURL(homePageURL, feed: feed) } - func icon(forURL url: String, feed: WebFeed, _ imageResultBlock: @escaping (RSImage?) -> Void) { + func icon(forURL url: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) { waitingForFeedURLs[url] = feed guard let imageData = imageDownloader.image(for: url) else { imageResultBlock(nil) @@ -177,7 +177,7 @@ private extension WebFeedIconDownloader { RSImage.scaledForIcon(imageData, imageResultBlock: imageResultBlock) } - func postFeedIconDidBecomeAvailableNotification(_ feed: WebFeed) { + func postFeedIconDidBecomeAvailableNotification(_ feed: Feed) { DispatchQueue.main.async { let userInfo: [AnyHashable: Any] = [UserInfoKey.webFeed: feed] @@ -197,7 +197,7 @@ private extension WebFeedIconDownloader { homePageToIconURLCacheDirty = true } - func findIconURLForHomePageURL(_ homePageURL: String, feed: WebFeed) { + func findIconURLForHomePageURL(_ homePageURL: String, feed: Feed) { guard !urlsInProgress.contains(homePageURL) else { return @@ -214,7 +214,7 @@ private extension WebFeedIconDownloader { } } - func pullIconURL(from metadata: RSHTMLMetadata, homePageURL: String, feed: WebFeed) { + func pullIconURL(from metadata: RSHTMLMetadata, homePageURL: String, feed: Feed) { if let url = metadata.bestWebsiteIconURL() { cacheIconURL(for: homePageURL, url) diff --git a/Shared/SmartFeeds/PseudoFeed.swift b/Shared/SmartFeeds/PseudoFeed.swift index 2d49603f2..94436c66b 100644 --- a/Shared/SmartFeeds/PseudoFeed.swift +++ b/Shared/SmartFeeds/PseudoFeed.swift @@ -13,7 +13,7 @@ import Articles import Account import RSCore -protocol PseudoFeed: AnyObject, Feed, SmallIconProvider, PasteboardWriterOwner { +protocol PseudoFeed: AnyObject, SidebarItem, SmallIconProvider, PasteboardWriterOwner { } @@ -24,7 +24,7 @@ import Articles import Account import RSCore -protocol PseudoFeed: AnyObject, Feed, SmallIconProvider { +protocol PseudoFeed: AnyObject, SidebarItem, SmallIconProvider { } diff --git a/Shared/SmartFeeds/SearchFeedDelegate.swift b/Shared/SmartFeeds/SearchFeedDelegate.swift index 246ccca75..cc8060725 100644 --- a/Shared/SmartFeeds/SearchFeedDelegate.swift +++ b/Shared/SmartFeeds/SearchFeedDelegate.swift @@ -14,8 +14,8 @@ import ArticlesDatabase struct SearchFeedDelegate: SmartFeedDelegate { - var feedID: FeedIdentifier? { - return FeedIdentifier.smartFeed(String(describing: SearchFeedDelegate.self)) + var sidebarItemID: SidebarItemIdentifier? { + return SidebarItemIdentifier.smartFeed(String(describing: SearchFeedDelegate.self)) } var nameForDisplay: String { diff --git a/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift b/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift index c186fa838..53acc7a50 100644 --- a/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift +++ b/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift @@ -14,8 +14,8 @@ import ArticlesDatabase struct SearchTimelineFeedDelegate: SmartFeedDelegate { - var feedID: FeedIdentifier? { - return FeedIdentifier.smartFeed(String(describing: SearchTimelineFeedDelegate.self)) + var sidebarItemID: SidebarItemIdentifier? { + return SidebarItemIdentifier.smartFeed(String(describing: SearchTimelineFeedDelegate.self)) } var nameForDisplay: String { diff --git a/Shared/SmartFeeds/SmartFeed.swift b/Shared/SmartFeeds/SmartFeed.swift index e8245a0b9..a31473f19 100644 --- a/Shared/SmartFeeds/SmartFeed.swift +++ b/Shared/SmartFeeds/SmartFeed.swift @@ -20,8 +20,8 @@ final class SmartFeed: PseudoFeed { return .none } - var feedID: FeedIdentifier? { - delegate.feedID + var sidebarItemID: SidebarItemIdentifier? { + delegate.sidebarItemID } var nameForDisplay: String { diff --git a/Shared/SmartFeeds/SmartFeedDelegate.swift b/Shared/SmartFeeds/SmartFeedDelegate.swift index a8bb89e96..7f37a2ae6 100644 --- a/Shared/SmartFeeds/SmartFeedDelegate.swift +++ b/Shared/SmartFeeds/SmartFeedDelegate.swift @@ -12,7 +12,7 @@ import Articles import ArticlesDatabase import RSCore -protocol SmartFeedDelegate: FeedIdentifiable, DisplayNameProvider, ArticleFetcher, SmallIconProvider { +protocol SmartFeedDelegate: SidebarItemIdentifiable, DisplayNameProvider, ArticleFetcher, SmallIconProvider { var fetchType: FetchType { get } func fetchUnreadCount(for: Account, completion: @escaping SingleUnreadCountCompletionBlock) } diff --git a/Shared/SmartFeeds/SmartFeedsController.swift b/Shared/SmartFeeds/SmartFeedsController.swift index 75288e496..8ff75f510 100644 --- a/Shared/SmartFeeds/SmartFeedsController.swift +++ b/Shared/SmartFeeds/SmartFeedsController.swift @@ -19,7 +19,7 @@ final class SmartFeedsController: DisplayNameProvider, ContainerIdentifiable { public static let shared = SmartFeedsController() let nameForDisplay = NSLocalizedString("Smart Feeds", comment: "Smart Feeds group title") - var smartFeeds = [Feed]() + var smartFeeds = [SidebarItem]() let todayFeed = SmartFeed(delegate: TodayFeedDelegate()) let unreadFeed = UnreadFeed() let starredFeed = SmartFeed(delegate: StarredFeedDelegate()) @@ -28,7 +28,7 @@ final class SmartFeedsController: DisplayNameProvider, ContainerIdentifiable { self.smartFeeds = [todayFeed, unreadFeed, starredFeed] } - func find(by identifier: FeedIdentifier) -> PseudoFeed? { + func find(by identifier: SidebarItemIdentifier) -> PseudoFeed? { switch identifier { case .smartFeed(let stringIdentifer): switch stringIdentifer { diff --git a/Shared/SmartFeeds/StarredFeedDelegate.swift b/Shared/SmartFeeds/StarredFeedDelegate.swift index 55770fe36..1eae46c08 100644 --- a/Shared/SmartFeeds/StarredFeedDelegate.swift +++ b/Shared/SmartFeeds/StarredFeedDelegate.swift @@ -16,8 +16,8 @@ import Account struct StarredFeedDelegate: SmartFeedDelegate { - var feedID: FeedIdentifier? { - return FeedIdentifier.smartFeed(String(describing: StarredFeedDelegate.self)) + var sidebarItemID: SidebarItemIdentifier? { + return SidebarItemIdentifier.smartFeed(String(describing: StarredFeedDelegate.self)) } let nameForDisplay = NSLocalizedString("Starred", comment: "Starred pseudo-feed title") diff --git a/Shared/SmartFeeds/TodayFeedDelegate.swift b/Shared/SmartFeeds/TodayFeedDelegate.swift index ad6e47977..06f215ef5 100644 --- a/Shared/SmartFeeds/TodayFeedDelegate.swift +++ b/Shared/SmartFeeds/TodayFeedDelegate.swift @@ -14,8 +14,8 @@ import Account struct TodayFeedDelegate: SmartFeedDelegate { - var feedID: FeedIdentifier? { - return FeedIdentifier.smartFeed(String(describing: TodayFeedDelegate.self)) + var sidebarItemID: SidebarItemIdentifier? { + return SidebarItemIdentifier.smartFeed(String(describing: TodayFeedDelegate.self)) } let nameForDisplay = NSLocalizedString("Today", comment: "Today pseudo-feed title") diff --git a/Shared/SmartFeeds/UnreadFeed.swift b/Shared/SmartFeeds/UnreadFeed.swift index eb9f4fb9c..540eb9620 100644 --- a/Shared/SmartFeeds/UnreadFeed.swift +++ b/Shared/SmartFeeds/UnreadFeed.swift @@ -26,8 +26,8 @@ final class UnreadFeed: PseudoFeed { return .alwaysRead } - var feedID: FeedIdentifier? { - return FeedIdentifier.smartFeed(String(describing: UnreadFeed.self)) + var sidebarItemID: SidebarItemIdentifier? { + return SidebarItemIdentifier.smartFeed(String(describing: UnreadFeed.self)) } let nameForDisplay = NSLocalizedString("All Unread", comment: "All Unread pseudo-feed title") diff --git a/Shared/Timeline/FetchRequestOperation.swift b/Shared/Timeline/FetchRequestOperation.swift index 74ba5d098..bfaab1439 100644 --- a/Shared/Timeline/FetchRequestOperation.swift +++ b/Shared/Timeline/FetchRequestOperation.swift @@ -19,13 +19,13 @@ typealias FetchRequestOperationResultBlock = (Set
, FetchRequestOperatio final class FetchRequestOperation { let id: Int - let readFilterEnabledTable: [FeedIdentifier: Bool] + let readFilterEnabledTable: [SidebarItemIdentifier: Bool] let resultBlock: FetchRequestOperationResultBlock var isCanceled = false var isFinished = false private let fetchers: [ArticleFetcher] - init(id: Int, readFilterEnabledTable: [FeedIdentifier: Bool], fetchers: [ArticleFetcher], resultBlock: @escaping FetchRequestOperationResultBlock) { + init(id: Int, readFilterEnabledTable: [SidebarItemIdentifier: Bool], fetchers: [ArticleFetcher], resultBlock: @escaping FetchRequestOperationResultBlock) { precondition(Thread.isMainThread) self.id = id self.readFilterEnabledTable = readFilterEnabledTable @@ -81,7 +81,7 @@ final class FetchRequestOperation { } for fetcher in fetchers { - if (fetcher as? Feed)?.readFiltered(readFilterEnabledTable: readFilterEnabledTable) ?? true { + if (fetcher as? SidebarItem)?.readFiltered(readFilterEnabledTable: readFilterEnabledTable) ?? true { fetcher.fetchUnreadArticlesAsync { articleSetResult in let articles = (try? articleSetResult.get()) ?? Set
() process(articles) diff --git a/Shared/Tree/WebFeedTreeControllerDelegate.swift b/Shared/Tree/WebFeedTreeControllerDelegate.swift index 60eceef26..d7c35fc24 100644 --- a/Shared/Tree/WebFeedTreeControllerDelegate.swift +++ b/Shared/Tree/WebFeedTreeControllerDelegate.swift @@ -13,15 +13,15 @@ import Account final class WebFeedTreeControllerDelegate: TreeControllerDelegate { - private var filterExceptions = Set() + private var filterExceptions = Set() var isReadFiltered = false - func addFilterException(_ feedID: FeedIdentifier) { + func addFilterException(_ feedID: SidebarItemIdentifier) { filterExceptions.insert(feedID) } func resetFilterExceptions() { - filterExceptions = Set() + filterExceptions = Set() } func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? { @@ -67,14 +67,14 @@ private extension WebFeedTreeControllerDelegate { var children = [AnyObject]() for webFeed in container.topLevelWebFeeds { - if let feedID = webFeed.feedID, !(!filterExceptions.contains(feedID) && isReadFiltered && webFeed.unreadCount == 0) { + if let feedID = webFeed.sidebarItemID, !(!filterExceptions.contains(feedID) && isReadFiltered && webFeed.unreadCount == 0) { children.append(webFeed) } } if let folders = container.folders { for folder in folders { - if let feedID = folder.feedID, !(!filterExceptions.contains(feedID) && isReadFiltered && folder.unreadCount == 0) { + if let feedID = folder.sidebarItemID, !(!filterExceptions.contains(feedID) && isReadFiltered && folder.unreadCount == 0) { children.append(folder) } } @@ -100,7 +100,7 @@ private extension WebFeedTreeControllerDelegate { } func createNode(representedObject: Any, parent: Node) -> Node? { - if let webFeed = representedObject as? WebFeed { + if let webFeed = representedObject as? Feed { return createNode(webFeed: webFeed, parent: parent) } @@ -115,7 +115,7 @@ private extension WebFeedTreeControllerDelegate { return nil } - func createNode(webFeed: WebFeed, parent: Node) -> Node { + func createNode(webFeed: Feed, parent: Node) -> Node { return parent.createChildNode(webFeed) } diff --git a/Shared/UserNotifications/UserNotificationManager.swift b/Shared/UserNotifications/UserNotificationManager.swift index e41165405..9e050c293 100644 --- a/Shared/UserNotifications/UserNotificationManager.swift +++ b/Shared/UserNotifications/UserNotificationManager.swift @@ -53,7 +53,7 @@ final class UserNotificationManager: NSObject { private extension UserNotificationManager { - func sendNotification(webFeed: WebFeed, article: Article) { + func sendNotification(webFeed: Feed, article: Article) { let content = UNMutableNotificationContent() content.title = webFeed.nameForDisplay @@ -81,7 +81,7 @@ private extension UserNotificationManager { /// - webFeed: `WebFeed` /// - Returns: A `UNNotifcationAttachment` if an icon is available. Otherwise nil. /// - Warning: In certain scenarios, this will return the `faviconTemplateImage`. - func thumbnailAttachment(for article: Article, webFeed: WebFeed) -> UNNotificationAttachment? { + func thumbnailAttachment(for article: Article, webFeed: Feed) -> UNNotificationAttachment? { if let imageURL = article.iconImageUrl(webFeed: webFeed) { let thumbnail = try? UNNotificationAttachment(identifier: webFeed.webFeedID, url: imageURL, options: nil) return thumbnail diff --git a/iOS/Inspector/WebFeedInspectorViewController.swift b/iOS/Inspector/WebFeedInspectorViewController.swift index 33ef8ecad..1658985b1 100644 --- a/iOS/Inspector/WebFeedInspectorViewController.swift +++ b/iOS/Inspector/WebFeedInspectorViewController.swift @@ -15,7 +15,7 @@ class WebFeedInspectorViewController: UITableViewController { static let preferredContentSizeForFormSheetDisplay = CGSize(width: 460.0, height: 500.0) - var webFeed: WebFeed! + var webFeed: Feed! @IBOutlet weak var nameTextField: UITextField! @IBOutlet weak var notifyAboutNewArticlesSwitch: UISwitch! @IBOutlet weak var alwaysShowReaderViewSwitch: UISwitch! diff --git a/iOS/MasterFeed/MasterFeedViewController+Drag.swift b/iOS/MasterFeed/MasterFeedViewController+Drag.swift index 9bf7a5010..abc4b18f9 100644 --- a/iOS/MasterFeed/MasterFeedViewController+Drag.swift +++ b/iOS/MasterFeed/MasterFeedViewController+Drag.swift @@ -13,7 +13,7 @@ import Account extension MasterFeedViewController: UITableViewDragDelegate { func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - guard let node = coordinator.nodeFor(indexPath), let webFeed = node.representedObject as? WebFeed else { + guard let node = coordinator.nodeFor(indexPath), let webFeed = node.representedObject as? Feed else { return [UIDragItem]() } diff --git a/iOS/MasterFeed/MasterFeedViewController+Drop.swift b/iOS/MasterFeed/MasterFeedViewController+Drop.swift index c7f925c07..b242d7da5 100644 --- a/iOS/MasterFeed/MasterFeedViewController+Drop.swift +++ b/iOS/MasterFeed/MasterFeedViewController+Drop.swift @@ -22,7 +22,7 @@ extension MasterFeedViewController: UITableViewDropDelegate { return UITableViewDropProposal(operation: .forbidden) } - guard let destFeed = coordinator.nodeFor(destIndexPath)?.representedObject as? Feed, + guard let destFeed = coordinator.nodeFor(destIndexPath)?.representedObject as? SidebarItem, let destAccount = destFeed.account, let destCell = tableView.cellForRow(at: destIndexPath) else { return UITableViewDropProposal(operation: .forbidden) @@ -31,7 +31,7 @@ extension MasterFeedViewController: UITableViewDropDelegate { // Validate account specific behaviors... if destAccount.behaviors.contains(.disallowFeedInMultipleFolders), let sourceNode = session.localDragSession?.items.first?.localObject as? Node, - let sourceWebFeed = sourceNode.representedObject as? WebFeed, + let sourceWebFeed = sourceNode.representedObject as? Feed, sourceWebFeed.account?.accountID != destAccount.accountID && destAccount.hasWebFeed(withURL: sourceWebFeed.url) { return UITableViewDropProposal(operation: .forbidden) } @@ -91,7 +91,7 @@ extension MasterFeedViewController: UITableViewDropDelegate { } }() - guard let destination = destinationContainer, let webFeed = dragNode.representedObject as? WebFeed else { return } + guard let destination = destinationContainer, let webFeed = dragNode.representedObject as? Feed else { return } if source.account == destination.account { moveWebFeedInAccount(feed: webFeed, sourceContainer: source, destinationContainer: destination) @@ -100,7 +100,7 @@ extension MasterFeedViewController: UITableViewDropDelegate { } } - func moveWebFeedInAccount(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) { + func moveWebFeedInAccount(feed: Feed, sourceContainer: Container, destinationContainer: Container) { guard sourceContainer !== destinationContainer else { return } BatchUpdate.shared.start() @@ -115,7 +115,7 @@ extension MasterFeedViewController: UITableViewDropDelegate { } } - func moveWebFeedBetweenAccounts(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) { + func moveWebFeedBetweenAccounts(feed: Feed, sourceContainer: Container, destinationContainer: Container) { if let existingFeed = destinationContainer.account?.existingWebFeed(withURL: feed.url) { diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 94985bd76..a44aec610 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -130,17 +130,17 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } @objc func webFeedIconDidBecomeAvailable(_ note: Notification) { - guard let webFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed else { + guard let webFeed = note.userInfo?[UserInfoKey.webFeed] as? Feed else { return } applyToCellsForRepresentedObject(webFeed, configureIcon(_:_:)) } @objc func webFeedSettingDidChange(_ note: Notification) { - guard let webFeed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else { + guard let webFeed = note.object as? Feed, let key = note.userInfo?[Feed.WebFeedSettingUserInfoKey] as? String else { return } - if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL { + if key == Feed.WebFeedSettingKey.homePageURL || key == Feed.WebFeedSettingKey.faviconURL { configureCellsForRepresentedObject(webFeed) } } @@ -268,7 +268,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { renameAction.backgroundColor = UIColor.systemOrange actions.append(renameAction) - if let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? WebFeed { + if let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed { let moreTitle = NSLocalizedString("More", comment: "More") let moreAction = UIContextualAction(style: .normal, title: moreTitle) { [weak self] (action, view, completion) in @@ -320,10 +320,10 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed else { + guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? SidebarItem else { return nil } - if feed is WebFeed { + if feed is Feed { return makeWebFeedContextMenu(indexPath: indexPath, includeDeleteRename: true) } else if feed is Folder { return makeFolderContextMenu(indexPath: indexPath) @@ -795,7 +795,7 @@ private extension MasterFeedViewController { cell.isDisclosureAvailable = false } - if let feed = node.representedObject as? Feed { + if let feed = node.representedObject as? SidebarItem { cell.name = feed.nameForDisplay cell.unreadCount = feed.unreadCount } @@ -812,7 +812,7 @@ private extension MasterFeedViewController { } func configureIcon(_ cell: MasterFeedTableViewCell, _ indexPath: IndexPath) { - guard let node = coordinator.nodeFor(indexPath), let feed = node.representedObject as? Feed, let feedID = feed.feedID else { + guard let node = coordinator.nodeFor(indexPath), let feed = node.representedObject as? SidebarItem, let feedID = feed.sidebarItemID else { return } cell.iconImage = IconImageCache.shared.imageFor(feedID) @@ -832,9 +832,9 @@ private extension MasterFeedViewController { func applyToCellsForRepresentedObject(_ representedObject: AnyObject, _ completion: (MasterFeedTableViewCell, IndexPath) -> Void) { applyToAvailableCells { (cell, indexPath) in if let node = coordinator.nodeFor(indexPath), - let representedFeed = representedObject as? Feed, - let candidate = node.representedObject as? Feed, - representedFeed.feedID == candidate.feedID { + let representedFeed = representedObject as? SidebarItem, + let candidate = node.representedObject as? SidebarItem, + representedFeed.sidebarItemID == candidate.sidebarItemID { completion(cell, indexPath) } } @@ -862,7 +862,7 @@ private extension MasterFeedViewController { if let folder = node.representedObject as? Folder { return folder.account } - if let feed = node.representedObject as? WebFeed { + if let feed = node.representedObject as? Feed { return feed.account } return nil @@ -1000,7 +1000,7 @@ private extension MasterFeedViewController { } func copyFeedPageAction(indexPath: IndexPath) -> UIAction? { - guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? WebFeed, + guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed, let url = URL(string: webFeed.url) else { return nil } @@ -1013,7 +1013,7 @@ private extension MasterFeedViewController { } func copyFeedPageAlertAction(indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? { - guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? WebFeed, + guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed, let url = URL(string: webFeed.url) else { return nil } @@ -1027,7 +1027,7 @@ private extension MasterFeedViewController { } func copyHomePageAction(indexPath: IndexPath) -> UIAction? { - guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? WebFeed, + guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed, let homePageURL = webFeed.homePageURL, let url = URL(string: homePageURL) else { return nil @@ -1041,7 +1041,7 @@ private extension MasterFeedViewController { } func copyHomePageAlertAction(indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? { - guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? WebFeed, + guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed, let homePageURL = webFeed.homePageURL, let url = URL(string: homePageURL) else { return nil @@ -1056,7 +1056,7 @@ private extension MasterFeedViewController { } func markAllAsReadAlertAction(indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? { - guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? WebFeed, + guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed, webFeed.unreadCount > 0, let articles = try? webFeed.fetchArticles(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else { return nil @@ -1096,7 +1096,7 @@ private extension MasterFeedViewController { } func getInfoAction(indexPath: IndexPath) -> UIAction? { - guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? WebFeed else { + guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed else { return nil } @@ -1124,7 +1124,7 @@ private extension MasterFeedViewController { } func getInfoAlertAction(indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? { - guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? WebFeed else { + guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed else { return nil } @@ -1137,7 +1137,7 @@ private extension MasterFeedViewController { } func markAllAsReadAction(indexPath: IndexPath) -> UIAction? { - guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed, + guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? SidebarItem, let contentView = self.tableView.cellForRow(at: indexPath)?.contentView, feed.unreadCount > 0 else { return nil @@ -1179,7 +1179,7 @@ private extension MasterFeedViewController { func rename(indexPath: IndexPath) { - guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed else { return } + guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? SidebarItem else { return } let formatString = NSLocalizedString("Rename “%@”", comment: "Rename feed") let title = NSString.localizedStringWithFormat(formatString as NSString, feed.nameForDisplay) as String @@ -1196,7 +1196,7 @@ private extension MasterFeedViewController { return } - if let webFeed = feed as? WebFeed { + if let webFeed = feed as? Feed { webFeed.rename(to: name) { result in switch result { case .success: @@ -1233,7 +1233,7 @@ private extension MasterFeedViewController { } func delete(indexPath: IndexPath) { - guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed else { return } + guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? SidebarItem else { return } let title: String let message: String @@ -1271,7 +1271,7 @@ private extension MasterFeedViewController { if let folder = deleteNode.representedObject as? Folder { ActivityManager.cleanUp(folder) - } else if let feed = deleteNode.representedObject as? WebFeed { + } else if let feed = deleteNode.representedObject as? Feed { ActivityManager.cleanUp(feed) } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 94825c03d..778b6654f 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -453,7 +453,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner titleView.iconView.iconImage = coordinator.timelineIconImage } - guard let feed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed else { + guard let feed = note.userInfo?[UserInfoKey.webFeed] as? Feed else { return } tableView.indexPathsForVisibleRows?.forEach { indexPath in @@ -631,7 +631,7 @@ private extension MasterTimelineViewController { titleView.label.text = coordinator.timelineFeed?.nameForDisplay updateTitleUnreadCount() - if coordinator.timelineFeed is WebFeed { + if coordinator.timelineFeed is Feed { titleView.buttonize() titleView.addGestureRecognizer(feedTapGestureRecognizer) } else { diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 830c2fa9b..8899eb948 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -33,11 +33,11 @@ enum ShowFeedName { struct FeedNode: Hashable { var node: Node - var feedID: FeedIdentifier + var feedID: SidebarItemIdentifier init(_ node: Node) { self.node = node - self.feedID = (node.representedObject as! Feed).feedID! + self.feedID = (node.representedObject as! SidebarItem).sidebarItemID! } func hash(into hasher: inout Hasher) { @@ -94,12 +94,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { private var lastExpandedTable = Set() // Which Feeds have the Read Articles Filter enabled - private var readFilterEnabledTable = [FeedIdentifier: Bool]() + private var readFilterEnabledTable = [SidebarItemIdentifier: Bool]() // Flattened tree structure for the Sidebar private var shadowTable = [(sectionID: String, feedNodes: [FeedNode])]() - private(set) var preSearchTimelineFeed: Feed? + private(set) var preSearchTimelineFeed: SidebarItem? private var lastSearchString = "" private var lastSearchScope: SearchScope? = nil private var isSearching: Bool = false @@ -157,7 +157,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { } var isReadArticlesFiltered: Bool { - if let feedID = timelineFeed?.feedID, let readFilterEnabled = readFilterEnabledTable[feedID] { + if let feedID = timelineFeed?.sidebarItemID, let readFilterEnabled = readFilterEnabledTable[feedID] { return readFilterEnabled } else { return timelineDefaultReadFilterType != .none @@ -183,7 +183,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { } private var exceptionArticleFetcher: ArticleFetcher? - private(set) var timelineFeed: Feed? + private(set) var timelineFeed: SidebarItem? var timelineMiddleIndexPath: IndexPath? @@ -363,7 +363,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { if let readArticlesFilterState = windowState[UserInfoKey.readArticlesFilterState] as? [[AnyHashable: AnyHashable]: Bool] { for key in readArticlesFilterState.keys { - if let feedIdentifier = FeedIdentifier(userInfo: key) { + if let feedIdentifier = SidebarItemIdentifier(userInfo: key) { readFilterEnabledTable[feedIdentifier] = readArticlesFilterState[key] } } @@ -545,7 +545,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { } @objc func userDidAddFeed(_ notification: Notification) { - guard let webFeed = notification.userInfo?[UserInfoKey.webFeed] as? WebFeed else { + guard let webFeed = notification.userInfo?[UserInfoKey.webFeed] as? Feed else { return } discloseWebFeed(webFeed, animations: [.scroll, .navigation]) @@ -557,7 +557,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { } @objc func accountDidDownloadArticles(_ note: Notification) { - guard let feeds = note.userInfo?[Account.UserInfoKey.webFeeds] as? Set else { + guard let feeds = note.userInfo?[Account.UserInfoKey.webFeeds] as? Set else { return } @@ -625,7 +625,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { } func toggleReadArticlesFilter() { - guard let feedID = timelineFeed?.feedID else { + guard let feedID = timelineFeed?.sidebarItemID else { return } @@ -638,10 +638,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { refreshTimeline(resetScroll: false) } - func nodeFor(feedID: FeedIdentifier) -> Node? { + func nodeFor(feedID: SidebarItemIdentifier) -> Node? { return treeController.rootNode.descendantNode(where: { node in - if let feed = node.representedObject as? Feed { - return feed.feedID == feedID + if let feed = node.representedObject as? SidebarItem { + return feed.sidebarItemID == feedID } else { return false } @@ -783,7 +783,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { return indexPathFor(node) } - func selectFeed(_ feed: Feed?, animations: Animations = [], deselectArticle: Bool = true, completion: (() -> Void)? = nil) { + func selectFeed(_ feed: SidebarItem?, animations: Animations = [], deselectArticle: Bool = true, completion: (() -> Void)? = nil) { let indexPath: IndexPath? = { if let feed = feed, let indexPath = indexPathFor(feed as AnyObject) { return indexPath @@ -807,7 +807,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { selectArticle(nil) } - if let ip = indexPath, let node = nodeFor(ip), let feed = node.representedObject as? Feed { + if let ip = indexPath, let node = nodeFor(ip), let feed = node.representedObject as? SidebarItem { self.activityManager.selecting(feed: feed) self.installTimelineControllerIfNecessary(animated: animations.contains(.navigation)) @@ -1120,15 +1120,15 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { markArticlesWithUndo([article], statusKey: .starred, flag: !article.status.starred) } - func timelineFeedIsEqualTo(_ feed: WebFeed) -> Bool { - guard let timelineFeed = timelineFeed as? WebFeed else { + func timelineFeedIsEqualTo(_ feed: Feed) -> Bool { + guard let timelineFeed = timelineFeed as? Feed else { return false } return timelineFeed == feed } - func discloseWebFeed(_ webFeed: WebFeed, initialLoad: Bool = false, animations: Animations = [], completion: (() -> Void)? = nil) { + func discloseWebFeed(_ webFeed: Feed, initialLoad: Bool = false, animations: Animations = [], completion: (() -> Void)? = nil) { if isSearching { masterTimelineViewController?.hideSearch() } @@ -1145,10 +1145,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { markExpanded(parentFolder) } - if let webFeedFeedID = webFeed.feedID { + if let webFeedFeedID = webFeed.sidebarItemID { self.treeControllerDelegate.addFilterException(webFeedFeedID) } - if let parentFolderFeedID = parentFolder?.feedID { + if let parentFolderFeedID = parentFolder?.sidebarItemID { self.treeControllerDelegate.addFilterException(parentFolderFeedID) } @@ -1196,7 +1196,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { } func showFeedInspector() { - let timelineWebFeed = timelineFeed as? WebFeed + let timelineWebFeed = timelineFeed as? Feed let articleFeed = currentArticle?.webFeed guard let feed = timelineWebFeed ?? articleFeed else { return @@ -1204,7 +1204,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { showFeedInspector(for: feed) } - func showFeedInspector(for feed: WebFeed) { + func showFeedInspector(for feed: Feed) { let feedInspectorNavController = UIStoryboard.inspector.instantiateViewController(identifier: "FeedInspectorNavigationViewController") as! UINavigationController let feedInspectorController = feedInspectorNavController.topViewController as! WebFeedInspectorViewController @@ -1248,7 +1248,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { func homePageURLForFeed(_ indexPath: IndexPath) -> URL? { guard let node = nodeFor(indexPath), - let feed = node.representedObject as? WebFeed, + let feed = node.representedObject as? Feed, let homePageURL = feed.homePageURL, let url = URL(string: homePageURL) else { return nil @@ -1458,7 +1458,7 @@ private extension SceneCoordinator { articleDictionaryNeedsUpdate = false } - func ensureFeedIsAvailableToSelect(_ feed: Feed, completion: @escaping () -> Void) { + func ensureFeedIsAvailableToSelect(_ feed: SidebarItem, completion: @escaping () -> Void) { addToFilterExeptionsIfNecessary(feed) addShadowTableToFilterExceptions() @@ -1468,15 +1468,15 @@ private extension SceneCoordinator { }) } - func addToFilterExeptionsIfNecessary(_ feed: Feed?) { - if isReadFeedsFiltered, let feedID = feed?.feedID { + func addToFilterExeptionsIfNecessary(_ feed: SidebarItem?) { + if isReadFeedsFiltered, let feedID = feed?.sidebarItemID { if feed is SmartFeed { treeControllerDelegate.addFilterException(feedID) } else if let folderFeed = feed as? Folder { if folderFeed.account?.existingFolder(withID: folderFeed.folderID) != nil { treeControllerDelegate.addFilterException(feedID) } - } else if let webFeed = feed as? WebFeed { + } else if let webFeed = feed as? Feed { if webFeed.account?.existingWebFeed(withWebFeedID: webFeed.webFeedID) != nil { treeControllerDelegate.addFilterException(feedID) addParentFolderToFilterExceptions(webFeed) @@ -1485,10 +1485,10 @@ private extension SceneCoordinator { } } - func addParentFolderToFilterExceptions(_ feed: Feed) { + func addParentFolderToFilterExceptions(_ feed: SidebarItem) { guard let node = treeController.rootNode.descendantNodeRepresentingObject(feed as AnyObject), let folder = node.parent?.representedObject as? Folder, - let folderFeedID = folder.feedID else { + let folderFeedID = folder.sidebarItemID else { return } @@ -1498,7 +1498,7 @@ private extension SceneCoordinator { func addShadowTableToFilterExceptions() { for section in shadowTable { for feedNode in section.feedNodes { - if let feed = feedNode.node.representedObject as? Feed, let feedID = feed.feedID { + if let feed = feedNode.node.representedObject as? SidebarItem, let feedID = feed.sidebarItemID { treeControllerDelegate.addFilterException(feedID) } } @@ -1628,10 +1628,10 @@ private extension SceneCoordinator { return ShadowTableChanges(deletes: deletes, inserts: inserts, moves: moves, rowChanges: changes) } - func shadowTableContains(_ feed: Feed) -> Bool { + func shadowTableContains(_ feed: SidebarItem) -> Bool { for section in shadowTable { for feedNode in section.feedNodes { - if let nodeFeed = feedNode.node.representedObject as? Feed, nodeFeed.feedID == feed.feedID { + if let nodeFeed = feedNode.node.representedObject as? SidebarItem, nodeFeed.sidebarItemID == feed.sidebarItemID { return true } } @@ -1652,7 +1652,7 @@ private extension SceneCoordinator { return indexPathFor(node) } - func setTimelineFeed(_ feed: Feed?, animated: Bool, completion: (() -> Void)? = nil) { + func setTimelineFeed(_ feed: SidebarItem?, animated: Bool, completion: (() -> Void)? = nil) { timelineFeed = feed fetchAndReplaceArticlesAsync(animated: animated) { @@ -1663,7 +1663,7 @@ private extension SceneCoordinator { func updateShowNamesAndIcons() { - if timelineFeed is WebFeed { + if timelineFeed is Feed { showFeedNames = { for article in articles { if !article.byline().isEmpty { @@ -2074,11 +2074,11 @@ private extension SceneCoordinator { return false } - func timelineFetcherContainsAnyFeed(_ feeds: Set) -> Bool { + func timelineFetcherContainsAnyFeed(_ feeds: Set) -> Bool { // Return true if there’s a match or if a folder contains (recursively) one of feeds - if let feed = timelineFeed as? WebFeed { + if let feed = timelineFeed as? Feed { for oneFeed in feeds { if feed.webFeedID == oneFeed.webFeedID || feed.url == oneFeed.url { return true @@ -2242,7 +2242,7 @@ private extension SceneCoordinator { func handleSelectFeed(_ userInfo: [AnyHashable : Any]?) { guard let userInfo = userInfo, let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : AnyHashable], - let feedIdentifier = FeedIdentifier(userInfo: feedIdentifierUserInfo) else { + let feedIdentifier = SidebarItemIdentifier(userInfo: feedIdentifierUserInfo) else { return } @@ -2327,7 +2327,7 @@ private extension SceneCoordinator { func restoreFeedSelection(_ userInfo: [AnyHashable : Any], accountID: String, webFeedID: String, articleID: String) -> Bool { guard let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : AnyHashable], - let feedIdentifier = FeedIdentifier(userInfo: feedIdentifierUserInfo), + let feedIdentifier = SidebarItemIdentifier(userInfo: feedIdentifierUserInfo), let isShowingExtractedArticle = userInfo[UserInfoKey.isShowingExtractedArticle] as? Bool, let articleWindowScrollY = userInfo[UserInfoKey.articleWindowScrollY] as? Int else { return false @@ -2349,7 +2349,7 @@ private extension SceneCoordinator { let found = selectFeedAndArticle(feedIdentifier: feedIdentifier, articleID: articleID, isShowingExtractedArticle: isShowingExtractedArticle, articleWindowScrollY: articleWindowScrollY) if found { treeControllerDelegate.addFilterException(feedIdentifier) - if let webFeedNode = nodeFor(feedID: feedIdentifier), let folder = webFeedNode.parent?.representedObject as? Folder, let folderFeedID = folder.feedID { + if let webFeedNode = nodeFor(feedID: feedIdentifier), let folder = webFeedNode.parent?.representedObject as? Folder, let folderFeedID = folder.sidebarItemID { treeControllerDelegate.addFilterException(folderFeedID) } } @@ -2379,13 +2379,13 @@ private extension SceneCoordinator { } func findWebFeedNode(webFeedID: String, beginningAt startingNode: Node) -> Node? { - if let node = startingNode.descendantNode(where: { ($0.representedObject as? WebFeed)?.webFeedID == webFeedID }) { + if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.webFeedID == webFeedID }) { return node } return nil } - func selectFeedAndArticle(feedIdentifier: FeedIdentifier, articleID: String, isShowingExtractedArticle: Bool, articleWindowScrollY: Int) -> Bool { + func selectFeedAndArticle(feedIdentifier: SidebarItemIdentifier, articleID: String, isShowingExtractedArticle: Bool, articleWindowScrollY: Int) -> Bool { guard let feedNode = nodeFor(feedID: feedIdentifier), let feedIndexPath = indexPathFor(feedNode) else { return false } selectFeed(indexPath: feedIndexPath) {