Renamed Feed and related classes/instances to WebFeed

This commit is contained in:
Maurice Parker 2019-11-14 20:11:41 -06:00
parent f5cd5d7067
commit 06bd5b3a6f
85 changed files with 1106 additions and 1106 deletions

View File

@ -29,7 +29,7 @@ public extension Notification.Name {
static let AccountDidDownloadArticles = Notification.Name(rawValue: "AccountDidDownloadArticles")
static let AccountStateDidChange = Notification.Name(rawValue: "AccountStateDidChange")
static let StatusesDidChange = Notification.Name(rawValue: "StatusesDidChange")
static let FeedMetadataDidChange = Notification.Name(rawValue: "FeedMetadataDidChange")
static let WebFeedMetadataDidChange = Notification.Name(rawValue: "WebFeedMetadataDidChange")
}
public enum AccountType: Int {
@ -48,7 +48,7 @@ public enum FetchType {
case unread
case today
case unreadForFolder(Folder)
case feed(Feed)
case webFeed(WebFeed)
case articleIDs(Set<String>)
case search(String)
case searchWithArticleIDs(String, Set<String>)
@ -62,7 +62,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
public static let updatedArticles = "updatedArticles" // AccountDidDownloadArticles
public static let statuses = "statuses" // StatusesDidChange
public static let articles = "articles" // StatusesDidChange
public static let feeds = "feeds" // AccountDidDownloadArticles, StatusesDidChange
public static let webFeeds = "webFeeds" // AccountDidDownloadArticles, StatusesDidChange
}
public static let defaultLocalAccountName: String = {
@ -126,15 +126,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
}
public var topLevelFeeds = Set<Feed>()
public var topLevelWebFeeds = Set<WebFeed>()
public var folders: Set<Folder>? = Set<Folder>()
private var feedDictionaryNeedsUpdate = true
private var _idToFeedDictionary = [String: Feed]()
var idToFeedDictionary: [String: Feed] {
if feedDictionaryNeedsUpdate {
rebuildFeedDictionaries()
private var webFeedDictionaryNeedsUpdate = true
private var _idToWebFeedDictionary = [String: WebFeed]()
var idToWebFeedDictionary: [String: WebFeed] {
if webFeedDictionaryNeedsUpdate {
rebuildWebFeedDictionaries()
}
return _idToFeedDictionary
return _idToWebFeedDictionary
}
var username: String? {
@ -169,8 +169,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
private var unreadCounts = [String: Int]() // [feedID: Int]
private var _flattenedFeeds = Set<Feed>()
private var flattenedFeedsNeedUpdate = true
private var _flattenedWebFeeds = Set<WebFeed>()
private var flattenedWebFeedsNeedUpdate = true
private lazy var opmlFile = OPMLFile(filename: (dataFolder as NSString).appendingPathComponent("Subscriptions.opml"), account: self)
private lazy var metadataFile = AccountMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("Settings.plist"), account: self)
@ -180,9 +180,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
}
private lazy var feedMetadataFile = FeedMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("FeedMetadata.plist"), account: self)
typealias FeedMetadataDictionary = [String: FeedMetadata]
var feedMetadata = FeedMetadataDictionary()
private lazy var webFeedMetadataFile = WebFeedMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("FeedMetadata.plist"), account: self)
typealias WebFeedMetadataDictionary = [String: WebFeedMetadata]
var webFeedMetadata = WebFeedMetadataDictionary()
var startingUp = true
@ -259,11 +259,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
NotificationCenter.default.addObserver(self, selector: #selector(childrenDidChange(_:)), name: .ChildrenDidChange, object: nil)
metadataFile.load()
feedMetadataFile.load()
webFeedMetadataFile.load()
opmlFile.load()
DispatchQueue.main.async {
self.database.cleanupDatabaseAtStartup(subscribedToFeedIDs: self.flattenedFeeds().feedIDs())
self.database.cleanupDatabaseAtStartup(subscribedToWebFeedIDs: self.flattenedWebFeeds().webFeedIDs())
self.fetchAllUnreadCounts()
}
@ -395,17 +395,17 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
public func save() {
metadataFile.save()
feedMetadataFile.save()
webFeedMetadataFile.save()
opmlFile.save()
}
func loadOPMLItems(_ items: [RSOPMLItem], parentFolder: Folder?) {
var feedsToAdd = Set<Feed>()
var feedsToAdd = Set<WebFeed>()
items.forEach { (item) in
if let feedSpecifier = item.feedSpecifier {
let feed = newFeed(with: feedSpecifier)
let feed = newWebFeed(with: feedSpecifier)
feedsToAdd.insert(feed)
return
}
@ -428,22 +428,22 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
if let parentFolder = parentFolder {
for feed in feedsToAdd {
parentFolder.addFeed(feed)
parentFolder.addWebFeed(feed)
}
} else {
for feed in feedsToAdd {
addFeed(feed)
addWebFeed(feed)
}
}
}
public func resetFeedMetadataAndUnreadCounts() {
for feed in flattenedFeeds() {
feed.metadata = feedMetadata(feedURL: feed.url, feedID: feed.feedID)
public func resetWebFeedMetadataAndUnreadCounts() {
for feed in flattenedWebFeeds() {
feed.metadata = webFeedMetadata(feedURL: feed.url, webFeedID: feed.webFeedID)
}
fetchAllUnreadCounts()
NotificationCenter.default.post(name: .FeedMetadataDidChange, object: self, userInfo: nil)
NotificationCenter.default.post(name: .WebFeedMetadataDidChange, object: self, userInfo: nil)
}
public func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? {
@ -484,10 +484,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return folders?.first(where: { $0.nameForDisplay == displayName })
}
func newFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> Feed {
func newWebFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> WebFeed {
let feedURL = opmlFeedSpecifier.feedURL
let metadata = feedMetadata(feedURL: feedURL, feedID: feedURL)
let feed = Feed(account: self, url: opmlFeedSpecifier.feedURL, metadata: metadata)
let metadata = webFeedMetadata(feedURL: feedURL, webFeedID: feedURL)
let feed = WebFeed(account: self, url: opmlFeedSpecifier.feedURL, metadata: metadata)
if let feedTitle = opmlFeedSpecifier.title {
if feed.name == nil {
feed.name = feedTitle
@ -496,37 +496,37 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return feed
}
public func addFeed(_ feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.addFeed(for: self, with: feed, to: container, completion: completion)
public func addWebFeed(_ feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.addWebFeed(for: self, with: feed, to: container, completion: completion)
}
public func createFeed(url: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
delegate.createFeed(for: self, url: url, name: name, container: container, completion: completion)
public func createWebFeed(url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
delegate.createWebFeed(for: self, url: url, name: name, container: container, completion: completion)
}
func createFeed(with name: String?, url: String, feedID: String, homePageURL: String?) -> Feed {
let metadata = feedMetadata(feedURL: url, feedID: feedID)
let feed = Feed(account: self, url: url, metadata: metadata)
func createWebFeed(with name: String?, url: String, webFeedID: String, homePageURL: String?) -> WebFeed {
let metadata = webFeedMetadata(feedURL: url, webFeedID: webFeedID)
let feed = WebFeed(account: self, url: url, metadata: metadata)
feed.name = name
feed.homePageURL = homePageURL
return feed
}
public func removeFeed(_ feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.removeFeed(for: self, with: feed, from: container, completion: completion)
public func removeWebFeed(_ feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.removeWebFeed(for: self, with: feed, from: container, completion: completion)
}
public func moveFeed(_ feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.moveFeed(for: self, with: feed, from: from, to: to, completion: completion)
public func moveWebFeed(_ feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.moveWebFeed(for: self, with: feed, from: from, to: to, completion: completion)
}
public func renameFeed(_ feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.renameFeed(for: self, with: feed, to: name, completion: completion)
public func renameWebFeed(_ feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.renameWebFeed(for: self, with: feed, to: name, completion: completion)
}
public func restoreFeed(_ feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.restoreFeed(for: self, feed: feed, container: container, completion: completion)
public func restoreWebFeed(_ feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.restoreWebFeed(for: self, feed: feed, container: container, completion: completion)
}
public func addFolder(_ name: String, completion: @escaping (Result<Folder, Error>) -> Void) {
@ -545,8 +545,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
delegate.restoreFolder(for: self, folder: folder, completion: completion)
}
func clearFeedMetadata(_ feed: Feed) {
feedMetadata[feed.url] = nil
func clearWebFeedMetadata(_ feed: WebFeed) {
webFeedMetadata[feed.url] = nil
}
func addFolder(_ folder: Folder) {
@ -555,15 +555,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
structureDidChange()
}
public func updateUnreadCounts(for feeds: Set<Feed>) {
if feeds.isEmpty {
public func updateUnreadCounts(for webFeeds: Set<WebFeed>) {
if webFeeds.isEmpty {
return
}
database.fetchUnreadCounts(for: feeds.feedIDs()) { (unreadCountDictionary) in
for feed in feeds {
if let unreadCount = unreadCountDictionary[feed.feedID] {
feed.unreadCount = unreadCount
database.fetchUnreadCounts(for: webFeeds.webFeedIDs()) { (unreadCountDictionary) in
for webFeed in webFeeds {
if let unreadCount = unreadCountDictionary[webFeed.webFeedID] {
webFeed.unreadCount = unreadCount
}
}
}
@ -579,8 +579,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return fetchTodayArticles()
case .unreadForFolder(let folder):
return fetchArticles(folder: folder)
case .feed(let feed):
return fetchArticles(feed: feed)
case .webFeed(let webFeed):
return fetchArticles(webFeed: webFeed)
case .articleIDs(let articleIDs):
return fetchArticles(articleIDs: articleIDs)
case .search(let searchString):
@ -600,8 +600,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
fetchTodayArticlesAsync(callback)
case .unreadForFolder(let folder):
fetchArticlesAsync(folder: folder, callback)
case .feed(let feed):
fetchArticlesAsync(feed: feed, callback)
case .webFeed(let webFeed):
fetchArticlesAsync(webFeed: webFeed, callback)
case .articleIDs(let articleIDs):
fetchArticlesAsync(articleIDs: articleIDs, callback)
case .search(let searchString):
@ -612,11 +612,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
public func fetchUnreadCountForToday(_ callback: @escaping (Int) -> Void) {
database.fetchUnreadCountForToday(for: flattenedFeeds().feedIDs(), callback: callback)
database.fetchUnreadCountForToday(for: flattenedWebFeeds().webFeedIDs(), callback: callback)
}
public func fetchUnreadCountForStarredArticles(_ callback: @escaping (Int) -> Void) {
database.fetchStarredAndUnreadCount(for: flattenedFeeds().feedIDs(), callback: callback)
database.fetchStarredAndUnreadCount(for: flattenedWebFeeds().webFeedIDs(), callback: callback)
}
public func fetchUnreadArticleIDs() -> Set<String> {
@ -631,12 +631,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return database.fetchArticleIDsForStatusesWithoutArticles()
}
public func unreadCount(for feed: Feed) -> Int {
return unreadCounts[feed.feedID] ?? 0
public func unreadCount(for webFeed: WebFeed) -> Int {
return unreadCounts[webFeed.webFeedID] ?? 0
}
public func setUnreadCount(_ unreadCount: Int, for feed: Feed) {
unreadCounts[feed.feedID] = unreadCount
public func setUnreadCount(_ unreadCount: Int, for webFeed: WebFeed) {
unreadCounts[webFeed.webFeedID] = unreadCount
}
public func structureDidChange() {
@ -645,36 +645,36 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
if !startingUp {
opmlFile.markAsDirty()
}
flattenedFeedsNeedUpdate = true
feedDictionaryNeedsUpdate = true
flattenedWebFeedsNeedUpdate = true
webFeedDictionaryNeedsUpdate = true
}
func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping (() -> Void)) {
func update(_ webFeed: WebFeed, with parsedFeed: ParsedFeed, _ completion: @escaping (() -> Void)) {
// Used only by an On My Mac account.
feed.takeSettings(from: parsedFeed)
let feedIDsAndItems = [feed.feedID: parsedFeed.items]
update(feedIDsAndItems: feedIDsAndItems, defaultRead: false, completion: completion)
webFeed.takeSettings(from: parsedFeed)
let webFeedIDsAndItems = [webFeed.webFeedID: parsedFeed.items]
update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: false, completion: completion)
}
func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping (() -> Void)) {
func update(webFeedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping (() -> Void)) {
assert(Thread.isMainThread)
guard !feedIDsAndItems.isEmpty else {
guard !webFeedIDsAndItems.isEmpty else {
completion()
return
}
database.update(feedIDsAndItems: feedIDsAndItems, defaultRead: defaultRead) { (newArticles, updatedArticles) in
database.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: defaultRead) { (newArticles, updatedArticles) in
var userInfo = [String: Any]()
let feeds = Set(feedIDsAndItems.compactMap { (key, _) -> Feed? in
self.existingFeed(withFeedID: key)
let webFeeds = Set(webFeedIDsAndItems.compactMap { (key, _) -> WebFeed? in
self.existingWebFeed(withWebFeedID: key)
})
if let newArticles = newArticles, !newArticles.isEmpty {
self.updateUnreadCounts(for: feeds)
self.updateUnreadCounts(for: webFeeds)
userInfo[UserInfoKey.newArticles] = newArticles
}
if let updatedArticles = updatedArticles, !updatedArticles.isEmpty {
userInfo[UserInfoKey.updatedArticles] = updatedArticles
}
userInfo[UserInfoKey.feeds] = feeds
userInfo[UserInfoKey.webFeeds] = webFeeds
completion()
@ -711,38 +711,38 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
// MARK: - Container
public func flattenedFeeds() -> Set<Feed> {
public func flattenedWebFeeds() -> Set<WebFeed> {
assert(Thread.isMainThread)
if flattenedFeedsNeedUpdate {
updateFlattenedFeeds()
if flattenedWebFeedsNeedUpdate {
updateFlattenedWebFeeds()
}
return _flattenedFeeds
return _flattenedWebFeeds
}
public func removeFeed(_ feed: Feed) {
topLevelFeeds.remove(feed)
public func removeWebFeed(_ webFeed: WebFeed) {
topLevelWebFeeds.remove(webFeed)
structureDidChange()
postChildrenDidChangeNotification()
}
public func removeFeeds(_ feeds: Set<Feed>) {
guard !feeds.isEmpty else {
public func removeFeeds(_ webFeeds: Set<WebFeed>) {
guard !webFeeds.isEmpty else {
return
}
topLevelFeeds.subtract(feeds)
topLevelWebFeeds.subtract(webFeeds)
structureDidChange()
postChildrenDidChangeNotification()
}
public func addFeed(_ feed: Feed) {
topLevelFeeds.insert(feed)
public func addWebFeed(_ webFeed: WebFeed) {
topLevelWebFeeds.insert(webFeed)
structureDidChange()
postChildrenDidChangeNotification()
}
func addFeedIfNotInAnyFolder(_ feed: Feed) {
if !flattenedFeeds().contains(feed) {
addFeed(feed)
func addFeedIfNotInAnyFolder(_ webFeed: WebFeed) {
if !flattenedWebFeeds().contains(webFeed) {
addWebFeed(webFeed)
}
}
@ -756,7 +756,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
public func debugDropConditionalGetInfo() {
#if DEBUG
flattenedFeeds().forEach{ $0.debugDropConditionalGetInfo() }
flattenedWebFeeds().forEach{ $0.debugDropConditionalGetInfo() }
#endif
}
@ -782,14 +782,14 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
@objc func unreadCountDidChange(_ note: Notification) {
if let feed = note.object as? Feed, feed.account === self {
if let feed = note.object as? WebFeed, feed.account === self {
updateUnreadCount()
}
}
@objc func batchUpdateDidPerform(_ note: Notification) {
flattenedFeedsNeedUpdate = true
rebuildFeedDictionaries()
flattenedWebFeedsNeedUpdate = true
rebuildWebFeedDictionaries()
updateUnreadCount()
}
@ -835,11 +835,11 @@ extension Account: AccountMetadataDelegate {
// MARK: - FeedMetadataDelegate
extension Account: FeedMetadataDelegate {
extension Account: WebFeedMetadataDelegate {
func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys) {
feedMetadataFile.markAsDirty()
guard let feed = existingFeed(withFeedID: feedMetadata.feedID) else {
func valueDidChange(_ feedMetadata: WebFeedMetadata, key: WebFeedMetadata.CodingKeys) {
webFeedMetadataFile.markAsDirty()
guard let feed = existingWebFeed(withWebFeedID: feedMetadata.webFeedID) else {
return
}
feed.postFeedSettingDidChangeNotification(key)
@ -851,11 +851,11 @@ extension Account: FeedMetadataDelegate {
private extension Account {
func fetchStarredArticles() -> Set<Article> {
return database.fetchStarredArticles(flattenedFeeds().feedIDs())
return database.fetchStarredArticles(flattenedWebFeeds().webFeedIDs())
}
func fetchStarredArticlesAsync(_ callback: @escaping ArticleSetBlock) {
database.fetchedStarredArticlesAsync(flattenedFeeds().feedIDs(), callback)
database.fetchedStarredArticlesAsync(flattenedWebFeeds().webFeedIDs(), callback)
}
func fetchUnreadArticles() -> Set<Article> {
@ -867,11 +867,11 @@ private extension Account {
}
func fetchTodayArticles() -> Set<Article> {
return database.fetchTodayArticles(flattenedFeeds().feedIDs())
return database.fetchTodayArticles(flattenedWebFeeds().webFeedIDs())
}
func fetchTodayArticlesAsync(_ callback: @escaping ArticleSetBlock) {
database.fetchTodayArticlesAsync(flattenedFeeds().feedIDs(), callback)
database.fetchTodayArticlesAsync(flattenedWebFeeds().webFeedIDs(), callback)
}
func fetchArticles(folder: Folder) -> Set<Article> {
@ -882,21 +882,21 @@ private extension Account {
fetchUnreadArticlesAsync(forContainer: folder, callback)
}
func fetchArticles(feed: Feed) -> Set<Article> {
let articles = database.fetchArticles(feed.feedID)
validateUnreadCount(feed, articles)
func fetchArticles(webFeed: WebFeed) -> Set<Article> {
let articles = database.fetchArticles(webFeed.webFeedID)
validateUnreadCount(webFeed, articles)
return articles
}
func fetchArticlesAsync(feed: Feed, _ callback: @escaping ArticleSetBlock) {
database.fetchArticlesAsync(feed.feedID) { [weak self] (articles) in
self?.validateUnreadCount(feed, articles)
func fetchArticlesAsync(webFeed: WebFeed, _ callback: @escaping ArticleSetBlock) {
database.fetchArticlesAsync(webFeed.webFeedID) { [weak self] (articles) in
self?.validateUnreadCount(webFeed, articles)
callback(articles)
}
}
func fetchArticlesMatching(_ searchString: String) -> Set<Article> {
return database.fetchArticlesMatching(searchString, flattenedFeeds().feedIDs())
return database.fetchArticlesMatching(searchString, flattenedWebFeeds().webFeedIDs())
}
func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>) -> Set<Article> {
@ -904,7 +904,7 @@ private extension Account {
}
func fetchArticlesMatchingAsync(_ searchString: String, _ callback: @escaping ArticleSetBlock) {
database.fetchArticlesMatchingAsync(searchString, flattenedFeeds().feedIDs(), callback)
database.fetchArticlesMatchingAsync(searchString, flattenedWebFeeds().webFeedIDs(), callback)
}
func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
@ -919,13 +919,13 @@ private extension Account {
return database.fetchArticlesAsync(articleIDs: articleIDs, callback)
}
func fetchUnreadArticles(feed: Feed) -> Set<Article> {
let articles = database.fetchUnreadArticles(Set([feed.feedID]))
validateUnreadCount(feed, articles)
func fetchUnreadArticles(webFeed: WebFeed) -> Set<Article> {
let articles = database.fetchUnreadArticles(Set([webFeed.webFeedID]))
validateUnreadCount(webFeed, articles)
return articles
}
func fetchUnreadArticlesAsync(for feed: Feed, callback: @escaping (Set<Article>) -> Void) {
func fetchUnreadArticlesAsync(for webFeed: WebFeed, callback: @escaping (Set<Article>) -> Void) {
// database.fetchUnreadArticlesAsync(for: Set([feed.feedID])) { [weak self] (articles) in
// self?.validateUnreadCount(feed, articles)
// callback(articles)
@ -934,48 +934,48 @@ private extension Account {
func fetchUnreadArticles(forContainer container: Container) -> Set<Article> {
let feeds = container.flattenedFeeds()
let articles = database.fetchUnreadArticles(feeds.feedIDs())
let feeds = container.flattenedWebFeeds()
let articles = database.fetchUnreadArticles(feeds.webFeedIDs())
validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
return articles
}
func fetchUnreadArticlesAsync(forContainer container: Container, _ callback: @escaping ArticleSetBlock) {
let feeds = container.flattenedFeeds()
database.fetchUnreadArticlesAsync(feeds.feedIDs()) { [weak self] (articles) in
self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
let webFeeds = container.flattenedWebFeeds()
database.fetchUnreadArticlesAsync(webFeeds.webFeedIDs()) { [weak self] (articles) in
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)
callback(articles)
}
}
func validateUnreadCountsAfterFetchingUnreadArticles(_ feeds: Set<Feed>, _ articles: Set<Article>) {
func validateUnreadCountsAfterFetchingUnreadArticles(_ webFeeds: Set<WebFeed>, _ articles: Set<Article>) {
// 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) }
// Now we loop through articles exactly once. This makes a huge difference.
var unreadCountStorage = [String: Int]() // [FeedID: Int]
var unreadCountStorage = [String: Int]() // [WebFeedID: Int]
for article in articles where !article.status.read {
unreadCountStorage[article.feedID, default: 0] += 1
unreadCountStorage[article.webFeedID, default: 0] += 1
}
feeds.forEach { (feed) in
let unreadCount = unreadCountStorage[feed.feedID, default: 0]
feed.unreadCount = unreadCount
webFeeds.forEach { (webFeed) in
let unreadCount = unreadCountStorage[webFeed.webFeedID, default: 0]
webFeed.unreadCount = unreadCount
}
}
func validateUnreadCount(_ feed: Feed, _ articles: Set<Article>) {
func validateUnreadCount(_ webFeed: WebFeed, _ articles: Set<Article>) {
// articles must contain all the unread articles for the feed.
// The unread number should match the feeds unread count.
let feedUnreadCount = articles.reduce(0) { (result, article) -> Int in
if article.feed == feed && !article.status.read {
if article.webFeed == webFeed && !article.status.read {
return result + 1
}
return result
}
feed.unreadCount = feedUnreadCount
webFeed.unreadCount = feedUnreadCount
}
}
@ -983,37 +983,37 @@ private extension Account {
private extension Account {
func feedMetadata(feedURL: String, feedID: String) -> FeedMetadata {
if let d = feedMetadata[feedURL] {
func webFeedMetadata(feedURL: String, webFeedID: String) -> WebFeedMetadata {
if let d = webFeedMetadata[feedURL] {
assert(d.delegate === self)
return d
}
let d = FeedMetadata(feedID: feedID)
let d = WebFeedMetadata(webFeedID: webFeedID)
d.delegate = self
feedMetadata[feedURL] = d
webFeedMetadata[feedURL] = d
return d
}
func updateFlattenedFeeds() {
var feeds = Set<Feed>()
feeds.formUnion(topLevelFeeds)
func updateFlattenedWebFeeds() {
var feeds = Set<WebFeed>()
feeds.formUnion(topLevelWebFeeds)
for folder in folders! {
feeds.formUnion(folder.flattenedFeeds())
feeds.formUnion(folder.flattenedWebFeeds())
}
_flattenedFeeds = feeds
flattenedFeedsNeedUpdate = false
_flattenedWebFeeds = feeds
flattenedWebFeedsNeedUpdate = false
}
func rebuildFeedDictionaries() {
var idDictionary = [String: Feed]()
func rebuildWebFeedDictionaries() {
var idDictionary = [String: WebFeed]()
flattenedFeeds().forEach { (feed) in
idDictionary[feed.feedID] = feed
flattenedWebFeeds().forEach { (feed) in
idDictionary[feed.webFeedID] = feed
}
_idToFeedDictionary = idDictionary
feedDictionaryNeedsUpdate = false
_idToWebFeedDictionary = idDictionary
webFeedDictionaryNeedsUpdate = false
}
func updateUnreadCount() {
@ -1021,21 +1021,21 @@ private extension Account {
return
}
var updatedUnreadCount = 0
for feed in flattenedFeeds() {
for feed in flattenedWebFeeds() {
updatedUnreadCount += feed.unreadCount
}
unreadCount = updatedUnreadCount
}
func noteStatusesForArticlesDidChange(_ articles: Set<Article>) {
let feeds = Set(articles.compactMap { $0.feed })
let feeds = Set(articles.compactMap { $0.webFeed })
let statuses = Set(articles.map { $0.status })
// .UnreadCountDidChange notification will get sent to Folder and Account objects,
// which will update their own unread counts.
updateUnreadCounts(for: feeds)
NotificationCenter.default.post(name: .StatusesDidChange, object: self, userInfo: [UserInfoKey.statuses: statuses, UserInfoKey.articles: articles, UserInfoKey.feeds: feeds])
NotificationCenter.default.post(name: .StatusesDidChange, object: self, userInfo: [UserInfoKey.statuses: statuses, UserInfoKey.articles: articles, UserInfoKey.webFeeds: feeds])
}
func fetchAllUnreadCounts() {
@ -1049,10 +1049,10 @@ private extension Account {
return
}
self.flattenedFeeds().forEach{ (feed) in
self.flattenedWebFeeds().forEach{ (feed) in
// When the unread count is zero, it wont appear in unreadCountDictionary.
if let unreadCount = unreadCountDictionary[feed.feedID] {
if let unreadCount = unreadCountDictionary[feed.webFeedID] {
feed.unreadCount = unreadCount
}
else {
@ -1070,8 +1070,8 @@ private extension Account {
extension Account {
public func existingFeed(withFeedID feedID: String) -> Feed? {
return idToFeedDictionary[feedID]
public func existingWebFeed(withWebFeedID webFeedID: String) -> WebFeed? {
return idToWebFeedDictionary[webFeedID]
}
}
@ -1081,7 +1081,7 @@ extension Account: OPMLRepresentable {
public func OPMLString(indentLevel: Int, strictConformance: Bool) -> String {
var s = ""
for feed in topLevelFeeds {
for feed in topLevelWebFeeds {
s += feed.OPMLString(indentLevel: indentLevel + 1, strictConformance: strictConformance)
}
for folder in folders! {

View File

@ -11,7 +11,7 @@
5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */; };
5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; };
510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD110232C3801002692E4 /* AccountMetadataFile.swift */; };
510BD113232C3E9D002692E4 /* FeedMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD112232C3E9D002692E4 /* FeedMetadataFile.swift */; };
510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */; };
511B9804237CD4270028BCAA /* ArticleFetcherType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9803237CD4270028BCAA /* ArticleFetcherType.swift */; };
513323082281070D00C30F19 /* AccountFeedbinSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */; };
5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 513323092281082F00C30F19 /* subscriptions_initial.json */; };
@ -56,7 +56,7 @@
841D4D702106B40400DD04E6 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 841D4D6F2106B40400DD04E6 /* ArticlesDatabase.framework */; };
841D4D722106B40A00DD04E6 /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 841D4D712106B40A00DD04E6 /* Articles.framework */; };
84245C851FDDD8CB0074AFBB /* FeedbinSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84245C841FDDD8CB0074AFBB /* FeedbinSubscription.swift */; };
844B297D2106C7EC004020B3 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B297C2106C7EC004020B3 /* Feed.swift */; };
844B297D2106C7EC004020B3 /* WebFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B297C2106C7EC004020B3 /* WebFeed.swift */; };
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */; };
844B2981210CE3BF004020B3 /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844B2980210CE3BF004020B3 /* RSWeb.framework */; };
8469F81C1F6DD15E0084783E /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848935101F62486800CEBD24 /* Account.swift */; };
@ -65,7 +65,7 @@
846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419742D1F6DDE96006346C4 /* LocalAccountRefresher.swift */; };
846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E77531F6F00E300A165E2 /* AccountManager.swift */; };
848935001F62484F00CEBD24 /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 848934F61F62484F00CEBD24 /* Account.framework */; };
84B2D4D02238CD8A00498ADA /* FeedMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B2D4CE2238C13D00498ADA /* FeedMetadata.swift */; };
84B2D4D02238CD8A00498ADA /* WebFeedMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B2D4CE2238C13D00498ADA /* WebFeedMetadata.swift */; };
84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */; };
84C3654A1F899F3B001EC85C /* CombinedRefreshProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */; };
84C8B3F41F89DE430053CCA6 /* DataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */; };
@ -209,7 +209,7 @@
5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAccountManager.swift; sourceTree = "<group>"; };
5107A09C227DE77700C7C3C5 /* TestTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransport.swift; sourceTree = "<group>"; };
510BD110232C3801002692E4 /* AccountMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMetadataFile.swift; sourceTree = "<group>"; };
510BD112232C3E9D002692E4 /* FeedMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedMetadataFile.swift; sourceTree = "<group>"; };
510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedMetadataFile.swift; sourceTree = "<group>"; };
511B9803237CD4270028BCAA /* ArticleFetcherType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleFetcherType.swift; sourceTree = "<group>"; };
513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedbinSyncTest.swift; sourceTree = "<group>"; };
513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = "<group>"; };
@ -258,7 +258,7 @@
841D4D6F2106B40400DD04E6 /* ArticlesDatabase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ArticlesDatabase.framework; sourceTree = BUILT_PRODUCTS_DIR; };
841D4D712106B40A00DD04E6 /* Articles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Articles.framework; sourceTree = BUILT_PRODUCTS_DIR; };
84245C841FDDD8CB0074AFBB /* FeedbinSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinSubscription.swift; sourceTree = "<group>"; };
844B297C2106C7EC004020B3 /* Feed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = "<group>"; };
844B297C2106C7EC004020B3 /* WebFeed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebFeed.swift; sourceTree = "<group>"; };
844B297E210CE37E004020B3 /* UnreadCountProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadCountProvider.swift; sourceTree = "<group>"; };
844B2980210CE3BF004020B3 /* RSWeb.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSWeb.framework; sourceTree = BUILT_PRODUCTS_DIR; };
846E77531F6F00E300A165E2 /* AccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = "<group>"; };
@ -268,7 +268,7 @@
848935061F62485000CEBD24 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
848935101F62486800CEBD24 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; };
84AF4EA3222CFDD100F6A800 /* AccountMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMetadata.swift; sourceTree = "<group>"; };
84B2D4CE2238C13D00498ADA /* FeedMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedMetadata.swift; sourceTree = "<group>"; };
84B2D4CE2238C13D00498ADA /* WebFeedMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedMetadata.swift; sourceTree = "<group>"; };
84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerPath.swift; sourceTree = "<group>"; };
84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombinedRefreshProgress.swift; sourceTree = "<group>"; };
84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataExtensions.swift; sourceTree = "<group>"; };
@ -532,9 +532,9 @@
8419740D1F6DD25F006346C4 /* Container.swift */,
84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */,
84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */,
844B297C2106C7EC004020B3 /* Feed.swift */,
84B2D4CE2238C13D00498ADA /* FeedMetadata.swift */,
510BD112232C3E9D002692E4 /* FeedMetadataFile.swift */,
844B297C2106C7EC004020B3 /* WebFeed.swift */,
84B2D4CE2238C13D00498ADA /* WebFeedMetadata.swift */,
510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */,
841974001F6DD1EC006346C4 /* Folder.swift */,
844B297E210CE37E004020B3 /* UnreadCountProvider.swift */,
5165D71F22835E9800D9D53D /* FeedFinder */,
@ -964,7 +964,7 @@
84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */,
9E713653233AD63E00765C84 /* FeedlySetUnreadArticlesOperation.swift in Sources */,
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
510BD113232C3E9D002692E4 /* FeedMetadataFile.swift in Sources */,
510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */,
5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */,
9EEEF71F23545CB4009E9D80 /* FeedlySendArticleStatusesOperation.swift in Sources */,
846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */,
@ -976,7 +976,7 @@
9E84DC492359A73600D6E809 /* FeedlyCheckpointOperation.swift in Sources */,
9E85C8EB236700E600D0F1F7 /* FeedlyGetEntriesOperation.swift in Sources */,
9E1D154D233370D800F4944C /* FeedlySyncAllOperation.swift in Sources */,
844B297D2106C7EC004020B3 /* Feed.swift in Sources */,
844B297D2106C7EC004020B3 /* WebFeed.swift in Sources */,
9E964EBA23754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift in Sources */,
9E1D15572334355900F4944C /* FeedlyRequestStreamsOperation.swift in Sources */,
9E1D15512334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift in Sources */,
@ -987,7 +987,7 @@
9EEAE075235D01C400E3FEE4 /* FeedlyMarkArticlesService.swift in Sources */,
9EF1B10323584B4C000A486A /* FeedlySyncStreamContentsOperation.swift in Sources */,
5154367B228EEB28005E1CDF /* FeedbinImportResult.swift in Sources */,
84B2D4D02238CD8A00498ADA /* FeedMetadata.swift in Sources */,
84B2D4D02238CD8A00498ADA /* WebFeedMetadata.swift in Sources */,
9E84DC472359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift in Sources */,
9EAEC624233315F60085D7C9 /* FeedlyEntry.swift in Sources */,
9EEAE073235D01AE00E3FEE4 /* FeedlyGetStreamIdsService.swift in Sources */,

View File

@ -33,13 +33,13 @@ protocol AccountDelegate {
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result<Void, Error>) -> Void)
func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void)
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
func addFeed(for account: Account, with: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void)
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void)
func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void)
func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void)
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
func addWebFeed(for account: Account, with: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void)
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void)
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void)
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void)
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void)
func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void)
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>?

View File

@ -207,7 +207,7 @@ public final class AccountManager: UnreadCountProvider {
public func anyAccountHasAtLeastOneFeed() -> Bool {
for account in activeAccounts {
if account.hasAtLeastOneFeed() {
if account.hasAtLeastOneWebFeed() {
return true
}
}
@ -217,7 +217,7 @@ public final class AccountManager: UnreadCountProvider {
public func anyAccountHasFeedWithURL(_ urlString: String) -> Bool {
for account in activeAccounts {
if let _ = account.existingFeed(withURL: urlString) {
if let _ = account.existingWebFeed(withURL: urlString) {
return true
}
}

View File

@ -33,8 +33,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase {
waitForExpectations(timeout: 5, handler: nil)
let folder = account.folders?.filter { $0.name == "Developers" } .first!
XCTAssertEqual(156, folder?.topLevelFeeds.count ?? 0)
XCTAssertEqual(2, account.topLevelFeeds.count)
XCTAssertEqual(156, folder?.topLevelWebFeeds.count ?? 0)
XCTAssertEqual(2, account.topLevelWebFeeds.count)
// Test Adding a Feed to the folder
testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "taggings_add.json"
@ -45,8 +45,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase {
}
waitForExpectations(timeout: 5, handler: nil)
XCTAssertEqual(157, folder?.topLevelFeeds.count ?? 0)
XCTAssertEqual(1, account.topLevelFeeds.count)
XCTAssertEqual(157, folder?.topLevelWebFeeds.count ?? 0)
XCTAssertEqual(1, account.topLevelWebFeeds.count)
// Test Deleting some Feeds from the folder
testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "taggings_delete.json"
@ -57,8 +57,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase {
}
waitForExpectations(timeout: 5, handler: nil)
XCTAssertEqual(153, folder?.topLevelFeeds.count ?? 0)
XCTAssertEqual(5, account.topLevelFeeds.count)
XCTAssertEqual(153, folder?.topLevelWebFeeds.count ?? 0)
XCTAssertEqual(5, account.topLevelWebFeeds.count)
TestAccountManager.shared.deleteAccount(account)

View File

@ -36,9 +36,9 @@ class AccountFeedbinSyncTest: XCTestCase {
}
waitForExpectations(timeout: 5, handler: nil)
XCTAssertEqual(224, account.flattenedFeeds().count)
XCTAssertEqual(224, account.flattenedWebFeeds().count)
let daringFireball = account.idToFeedDictionary["1296379"]
let daringFireball = account.idToWebFeedDictionary["1296379"]
XCTAssertEqual("Daring Fireball", daringFireball!.name)
XCTAssertEqual("https://daringfireball.net/feeds/json", daringFireball!.url)
XCTAssertEqual("https://daringfireball.net/", daringFireball!.homePageURL)
@ -57,9 +57,9 @@ class AccountFeedbinSyncTest: XCTestCase {
}
waitForExpectations(timeout: 5, handler: nil)
XCTAssertEqual(225, account.flattenedFeeds().count)
XCTAssertEqual(225, account.flattenedWebFeeds().count)
let bPixels = account.idToFeedDictionary["1096623"]
let bPixels = account.idToWebFeedDictionary["1096623"]
XCTAssertEqual("Beautiful Pixels", bPixels?.name)
XCTAssertEqual("https://feedpress.me/beautifulpixels", bPixels?.url)
XCTAssertEqual("https://beautifulpixels.com/", bPixels?.homePageURL)

View File

@ -58,7 +58,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
completionExpectation.fulfill()
}
XCTAssertTrue(account.flattenedFeeds().isEmpty, "Expected empty account.")
XCTAssertTrue(account.flattenedWebFeeds().isEmpty, "Expected empty account.")
OperationQueue.main.addOperation(createFeeds)
@ -72,8 +72,8 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
.flatMap { $0 }
.map { $0.title })
let accountFeeds = account.flattenedFeeds()
let ingestedIds = Set(accountFeeds.map { $0.feedID })
let accountFeeds = account.flattenedWebFeeds()
let ingestedIds = Set(accountFeeds.map { $0.webFeedID })
let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay })
let missingIds = feedIds.subtracting(ingestedIds)
@ -91,7 +91,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
let ingestedFolderAndFeedIds = (account.folders ?? Set())
.sorted { $0.externalID! < $1.externalID! }
.compactMap { folder -> [String: [String]]? in
return [folder.externalID!: folder.topLevelFeeds.map { $0.feedID }.sorted(by: <)]
return [folder.externalID!: folder.topLevelWebFeeds.map { $0.webFeedID }.sorted(by: <)]
}
XCTAssertEqual(expectedFolderAndFeedIds, ingestedFolderAndFeedIds, "Did not ingest feeds in their corresponding folders.")
@ -129,7 +129,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
completionExpectation.fulfill()
}
XCTAssertTrue(account.flattenedFeeds().isEmpty, "Expected empty account.")
XCTAssertTrue(account.flattenedWebFeeds().isEmpty, "Expected empty account.")
OperationQueue.main.addOperation(createFeeds)
@ -165,8 +165,8 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
.flatMap { $0 }
.map { $0.title })
let accountFeeds = account.flattenedFeeds()
let ingestedIds = Set(accountFeeds.map { $0.feedID })
let accountFeeds = account.flattenedWebFeeds()
let ingestedIds = Set(accountFeeds.map { $0.webFeedID })
let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay })
XCTAssertEqual(ingestedIds.count, feedIds.count)
@ -187,7 +187,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
let ingestedFolderAndFeedIds = (account.folders ?? Set())
.sorted { $0.externalID! < $1.externalID! }
.compactMap { folder -> [String: [String]]? in
return [folder.externalID!: folder.topLevelFeeds.map { $0.feedID }.sorted(by: <)]
return [folder.externalID!: folder.topLevelWebFeeds.map { $0.webFeedID }.sorted(by: <)]
}
XCTAssertEqual(expectedFolderAndFeedIds, ingestedFolderAndFeedIds, "Did not ingest feeds to their corresponding folders.")

View File

@ -183,7 +183,7 @@ class FeedlyMirrorCollectionsAsFoldersOperationTests: XCTestCase {
waitForExpectations(timeout: 2)
XCTAssertFalse(account.flattenedFeeds().isEmpty, "Expected account to have feeds.")
XCTAssertFalse(account.flattenedWebFeeds().isEmpty, "Expected account to have feeds.")
}
// Now that the folders are added, remove them all.
@ -200,7 +200,7 @@ class FeedlyMirrorCollectionsAsFoldersOperationTests: XCTestCase {
waitForExpectations(timeout: 2)
let feeds = account.flattenedFeeds()
let feeds = account.flattenedWebFeeds()
XCTAssertTrue(feeds.isEmpty)
}

View File

@ -129,12 +129,12 @@ class FeedlyTestSupport {
return
}
let collectionFeeds = collection["feeds"] as! [[String: Any]]
let folderFeeds = folder.topLevelFeeds
let folderFeeds = folder.topLevelWebFeeds
XCTAssertEqual(collectionFeeds.count, folderFeeds.count)
let collectionFeedIds = Set(collectionFeeds.map { $0["id"] as! String })
let folderFeedIds = Set(folderFeeds.map { $0.feedID })
let folderFeedIds = Set(folderFeeds.map { $0.webFeedID })
let missingFeedIds = collectionFeedIds.subtracting(folderFeedIds)
XCTAssertTrue(missingFeedIds.isEmpty, "Feeds with these ids were not found in the \"\(label)\" folder.")
@ -205,7 +205,7 @@ class FeedlyTestSupport {
for item in articleItems where item.id == article.articleID {
XCTAssertEqual(article.uniqueID, item.id)
XCTAssertEqual(article.contentHTML, item.content)
XCTAssertEqual(article.feedID, item.feedId)
XCTAssertEqual(article.webFeedID, item.feedId)
XCTAssertEqual(article.externalURL, item.externalUrl)
}
}

View File

@ -19,18 +19,18 @@ public protocol ArticleFetcher {
func fetchUnreadArticlesAsync(_ callback: @escaping ArticleSetBlock)
}
extension Feed: ArticleFetcher {
extension WebFeed: ArticleFetcher {
public var articleFetcherType: ArticleFetcherType? {
guard let accountID = account?.accountID else {
assertionFailure("Expected feed.account, but got nil.")
return nil
}
return ArticleFetcherType.feed(accountID, feedID)
return ArticleFetcherType.webFeed(accountID, webFeedID)
}
public func fetchArticles() -> Set<Article> {
return account?.fetchArticles(.feed(self)) ?? Set<Article>()
return account?.fetchArticles(.webFeed(self)) ?? Set<Article>()
}
public func fetchArticlesAsync(_ callback: @escaping ArticleSetBlock) {
@ -39,7 +39,7 @@ extension Feed: ArticleFetcher {
callback(Set<Article>())
return
}
account.fetchArticlesAsync(.feed(self), callback)
account.fetchArticlesAsync(.webFeed(self), callback)
}
public func fetchUnreadArticles() -> Set<Article> {
@ -52,7 +52,7 @@ extension Feed: ArticleFetcher {
callback(Set<Article>())
return
}
account.fetchArticlesAsync(.feed(self)) { callback($0.unreadArticles()) }
account.fetchArticlesAsync(.webFeed(self)) { callback($0.unreadArticles()) }
}
}

View File

@ -12,7 +12,7 @@ public enum ArticleFetcherType: CustomStringConvertible {
case smartFeed(String) // String is a unique identifier
case script(String) // String is a unique identifier
case feed(String, String) // accountID, feedID
case webFeed(String, String) // accountID, webFeedID
case folder(String, String) // accountID, folderName
public var description: String {
@ -21,8 +21,8 @@ public enum ArticleFetcherType: CustomStringConvertible {
return "smartFeed: \(id)"
case .script(let id):
return "script: \(id)"
case .feed(let accountID, let feedID):
return "feed: \(accountID)_\(feedID)"
case .webFeed(let accountID, let webFeedID):
return "feed: \(accountID)_\(webFeedID)"
case .folder(let accountID, let folderName):
return "folder: \(accountID)_\(folderName)"
}
@ -40,11 +40,11 @@ public enum ArticleFetcherType: CustomStringConvertible {
"type": "script",
"id": id
]
case .feed(let accountID, let feedID):
case .webFeed(let accountID, let webFeedID):
return [
"type": "feed",
"accountID": accountID,
"feedID": feedID
"webFeedID": webFeedID
]
case .folder(let accountID, let folderName):
return [
@ -66,8 +66,8 @@ public enum ArticleFetcherType: CustomStringConvertible {
guard let id = userInfo["id"] as? String else { return nil }
self = ArticleFetcherType.script(id)
case "feed":
guard let accountID = userInfo["accountID"] as? String, let feedID = userInfo["feedID"] as? String else { return nil }
self = ArticleFetcherType.feed(accountID, feedID)
guard let accountID = userInfo["accountID"] as? String, let webFeedID = userInfo["webFeedID"] as? String else { return nil }
self = ArticleFetcherType.webFeed(accountID, webFeedID)
case "folder":
guard let accountID = userInfo["accountID"] as? String, let folderName = userInfo["folderName"] as? String else { return nil }
self = ArticleFetcherType.folder(accountID, folderName)

View File

@ -19,25 +19,25 @@ extension Notification.Name {
public protocol Container: class {
var account: Account? { get }
var topLevelFeeds: Set<Feed> { get set }
var topLevelWebFeeds: Set<WebFeed> { get set }
var folders: Set<Folder>? { get set }
func hasAtLeastOneFeed() -> Bool
func hasAtLeastOneWebFeed() -> Bool
func objectIsChild(_ object: AnyObject) -> Bool
func hasChildFolder(with: String) -> Bool
func childFolder(with: String) -> Folder?
func removeFeed(_ feed: Feed)
func addFeed(_ feed: Feed)
func removeWebFeed(_ webFeed: WebFeed)
func addWebFeed(_ webFeed: WebFeed)
//Recursive  checks subfolders
func flattenedFeeds() -> Set<Feed>
func has(_ feed: Feed) -> Bool
func hasFeed(with feedID: String) -> Bool
func hasFeed(withURL url: String) -> Bool
func existingFeed(withFeedID: String) -> Feed?
func existingFeed(withURL url: String) -> Feed?
func flattenedWebFeeds() -> Set<WebFeed>
func has(_ webFeed: WebFeed) -> 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 existingFolder(with name: String) -> Folder?
func existingFolder(withID: Int) -> Folder?
@ -46,8 +46,8 @@ public protocol Container: class {
public extension Container {
func hasAtLeastOneFeed() -> Bool {
return topLevelFeeds.count > 0
func hasAtLeastOneWebFeed() -> Bool {
return topLevelWebFeeds.count > 0
}
func hasChildFolder(with name: String) -> Bool {
@ -67,8 +67,8 @@ public extension Container {
}
func objectIsChild(_ object: AnyObject) -> Bool {
if let feed = object as? Feed {
return topLevelFeeds.contains(feed)
if let feed = object as? WebFeed {
return topLevelWebFeeds.contains(feed)
}
if let folder = object as? Folder {
return folders?.contains(folder) ?? false
@ -76,40 +76,40 @@ public extension Container {
return false
}
func flattenedFeeds() -> Set<Feed> {
var feeds = Set<Feed>()
feeds.formUnion(topLevelFeeds)
func flattenedWebFeeds() -> Set<WebFeed> {
var feeds = Set<WebFeed>()
feeds.formUnion(topLevelWebFeeds)
if let folders = folders {
for folder in folders {
feeds.formUnion(folder.flattenedFeeds())
feeds.formUnion(folder.flattenedWebFeeds())
}
}
return feeds
}
func hasFeed(with feedID: String) -> Bool {
return existingFeed(withFeedID: feedID) != nil
func hasWebFeed(with webFeedID: String) -> Bool {
return existingWebFeed(withWebFeedID: webFeedID) != nil
}
func hasFeed(withURL url: String) -> Bool {
return existingFeed(withURL: url) != nil
func hasWebFeed(withURL url: String) -> Bool {
return existingWebFeed(withURL: url) != nil
}
func has(_ feed: Feed) -> Bool {
return flattenedFeeds().contains(feed)
func has(_ webFeed: WebFeed) -> Bool {
return flattenedWebFeeds().contains(webFeed)
}
func existingFeed(withFeedID feedID: String) -> Feed? {
for feed in flattenedFeeds() {
if feed.feedID == feedID {
func existingWebFeed(withWebFeedID webFeedID: String) -> WebFeed? {
for feed in flattenedWebFeeds() {
if feed.webFeedID == webFeedID {
return feed
}
}
return nil
}
func existingFeed(withURL url: String) -> Feed? {
for feed in flattenedFeeds() {
func existingWebFeed(withURL url: String) -> WebFeed? {
for feed in flattenedWebFeeds() {
if feed.url == url {
return feed
}

View File

@ -11,14 +11,14 @@ import Articles
import RSParser
public extension Notification.Name {
static let FeedSettingDidChange = Notification.Name(rawValue: "FeedSettingDidChangeNotification")
static let WebFeedSettingDidChange = Notification.Name(rawValue: "FeedSettingDidChangeNotification")
}
public extension Feed {
public extension WebFeed {
static let FeedSettingUserInfoKey = "feedSetting"
static let WebFeedSettingUserInfoKey = "feedSetting"
struct FeedSettingKey {
struct WebFeedSettingKey {
public static let homePageURL = "homePageURL"
public static let iconURL = "iconURL"
public static let faviconURL = "faviconURL"
@ -30,7 +30,7 @@ public extension Feed {
}
}
extension Feed {
extension WebFeed {
func takeSettings(from parsedFeed: ParsedFeed) {
iconURL = parsedFeed.iconURL
@ -40,9 +40,9 @@ extension Feed {
authors = Author.authorsWithParsedAuthors(parsedFeed.authors)
}
func postFeedSettingDidChangeNotification(_ codingKey: FeedMetadata.CodingKeys) {
let userInfo = [Feed.FeedSettingUserInfoKey: codingKey.stringValue]
NotificationCenter.default.post(name: .FeedSettingDidChange, object: self, userInfo: userInfo)
func postFeedSettingDidChangeNotification(_ codingKey: WebFeedMetadata.CodingKeys) {
let userInfo = [WebFeed.WebFeedSettingUserInfoKey: codingKey.stringValue]
NotificationCenter.default.post(name: .WebFeedSettingDidChange, object: self, userInfo: userInfo)
}
}
@ -56,8 +56,8 @@ public extension Article {
return manager.existingAccount(with: accountID)
}
var feed: Feed? {
return account?.existingFeed(withFeedID: feedID)
var webFeed: WebFeed? {
return account?.existingWebFeed(withWebFeedID: webFeedID)
}
}

View File

@ -275,7 +275,7 @@ final class FeedbinAPICaller: NSObject {
}
func createTagging(feedID: Int, name: String, completion: @escaping (Result<Int, Error>) -> Void) {
func createTagging(webFeedID: Int, name: String, completion: @escaping (Result<Int, Error>) -> Void) {
let callURL = feedbinBaseURL.appendingPathComponent("taggings.json")
var request = URLRequest(url: callURL, credentials: credentials)
@ -283,7 +283,7 @@ final class FeedbinAPICaller: NSObject {
let payload: Data
do {
payload = try JSONEncoder().encode(FeedbinCreateTagging(feedID: feedID, name: name))
payload = try JSONEncoder().encode(FeedbinCreateTagging(feedID: webFeedID, name: name))
} catch {
completion(.failure(error))
return

View File

@ -268,7 +268,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
guard folder.hasAtLeastOneFeed() else {
guard folder.hasAtLeastOneWebFeed() else {
folder.name = name
return
}
@ -296,7 +296,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
// Feedbin uses tags and if at least one feed isn't tagged, then the folder doesn't exist on their system
guard folder.hasAtLeastOneFeed() else {
guard folder.hasAtLeastOneWebFeed() else {
account.removeFolder(folder)
completion(.success(()))
return
@ -304,7 +304,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
let group = DispatchGroup()
for feed in folder.topLevelFeeds {
for feed in folder.topLevelWebFeeds {
if feed.folderRelationship?.count ?? 0 > 1 {
@ -336,7 +336,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
switch result {
case .success:
DispatchQueue.main.async {
account.clearFeedMetadata(feed)
account.clearWebFeedMetadata(feed)
}
case .failure(let error):
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
@ -356,7 +356,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
refreshProgress.addToNumberOfTasksAndRemaining(1)
caller.createSubscription(url: url) { result in
@ -388,7 +388,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
// This error should never happen
guard let subscriptionID = feed.subscriptionID else {
@ -415,7 +415,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
if feed.folderRelationship?.count ?? 0 > 1 {
deleteTagging(for: account, with: feed, from: container, completion: completion)
} else {
@ -423,14 +423,14 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
}
func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
if from is Account {
addFeed(for: account, with: feed, to: to, completion: completion)
addWebFeed(for: account, with: feed, to: to, completion: completion)
} else {
deleteTagging(for: account, with: feed, from: from) { result in
switch result {
case .success:
self.addFeed(for: account, with: feed, to: to, completion: completion)
self.addWebFeed(for: account, with: feed, to: to, completion: completion)
case .failure(let error):
completion(.failure(error))
}
@ -438,18 +438,18 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
}
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
if let folder = container as? Folder, let feedID = Int(feed.feedID) {
if let folder = container as? Folder, let webFeedID = Int(feed.webFeedID) {
refreshProgress.addToNumberOfTasksAndRemaining(1)
caller.createTagging(feedID: feedID, name: folder.name ?? "") { result in
caller.createTagging(webFeedID: webFeedID, name: folder.name ?? "") { result in
self.refreshProgress.completeTask()
switch result {
case .success(let taggingID):
DispatchQueue.main.async {
self.saveFolderRelationship(for: feed, withFolderName: folder.name ?? "", id: String(taggingID))
account.removeFeed(feed)
folder.addFeed(feed)
account.removeWebFeed(feed)
folder.addWebFeed(feed)
completion(.success(()))
}
case .failure(let error):
@ -470,10 +470,10 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
if let existingFeed = account.existingFeed(withURL: feed.url) {
account.addFeed(existingFeed, to: container) { result in
if let existingFeed = account.existingWebFeed(withURL: feed.url) {
account.addWebFeed(existingFeed, to: container) { result in
switch result {
case .success:
completion(.success(()))
@ -482,7 +482,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
}
} else {
createFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
switch result {
case .success:
completion(.success(()))
@ -498,12 +498,12 @@ final class FeedbinAccountDelegate: AccountDelegate {
let group = DispatchGroup()
for feed in folder.topLevelFeeds {
for feed in folder.topLevelWebFeeds {
folder.topLevelFeeds.remove(feed)
folder.topLevelWebFeeds.remove(feed)
group.enter()
restoreFeed(for: account, feed: feed, container: folder) { result in
restoreWebFeed(for: account, feed: feed, container: folder) { result in
group.leave()
switch result {
case .success:
@ -721,8 +721,8 @@ private extension FeedbinAccountDelegate {
if let folders = account.folders {
folders.forEach { folder in
if !tagNames.contains(folder.name ?? "") {
for feed in folder.topLevelFeeds {
account.addFeed(feed)
for feed in folder.topLevelWebFeeds {
account.addWebFeed(feed)
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
}
account.removeFolder(folder)
@ -759,17 +759,17 @@ private extension FeedbinAccountDelegate {
// Remove any feeds that are no longer in the subscriptions
if let folders = account.folders {
for folder in folders {
for feed in folder.topLevelFeeds {
if !subFeedIds.contains(feed.feedID) {
folder.removeFeed(feed)
for feed in folder.topLevelWebFeeds {
if !subFeedIds.contains(feed.webFeedID) {
folder.removeWebFeed(feed)
}
}
}
}
for feed in account.topLevelFeeds {
if !subFeedIds.contains(feed.feedID) {
account.removeFeed(feed)
for feed in account.topLevelWebFeeds {
if !subFeedIds.contains(feed.webFeedID) {
account.removeWebFeed(feed)
}
}
@ -779,7 +779,7 @@ private extension FeedbinAccountDelegate {
let subFeedId = String(subscription.feedID)
if let feed = account.existingFeed(withFeedID: subFeedId) {
if let feed = account.existingWebFeed(withWebFeedID: subFeedId) {
feed.name = subscription.name
// If the name has been changed on the server remove the locally edited name
feed.editedName = nil
@ -795,9 +795,9 @@ private extension FeedbinAccountDelegate {
// Actually add subscriptions all in one go, so we dont trigger various rebuilding things that Account does.
subscriptionsToAdd.forEach { subscription in
let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: String(subscription.feedID), homePageURL: subscription.homePageURL)
let feed = account.createWebFeed(with: subscription.name, url: subscription.url, webFeedID: String(subscription.feedID), homePageURL: subscription.homePageURL)
feed.subscriptionID = String(subscription.subscriptionID)
account.addFeed(feed)
account.addWebFeed(feed)
}
}
@ -836,25 +836,25 @@ private extension FeedbinAccountDelegate {
let taggingFeedIDs = groupedTaggings.map { String($0.feedID) }
// Move any feeds not in the folder to the account
for feed in folder.topLevelFeeds {
if !taggingFeedIDs.contains(feed.feedID) {
folder.removeFeed(feed)
for feed in folder.topLevelWebFeeds {
if !taggingFeedIDs.contains(feed.webFeedID) {
folder.removeWebFeed(feed)
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
account.addFeed(feed)
account.addWebFeed(feed)
}
}
// Add any feeds not in the folder
let folderFeedIds = folder.topLevelFeeds.map { $0.feedID }
let folderFeedIds = folder.topLevelWebFeeds.map { $0.webFeedID }
for tagging in groupedTaggings {
let taggingFeedID = String(tagging.feedID)
if !folderFeedIds.contains(taggingFeedID) {
guard let feed = account.existingFeed(withFeedID: taggingFeedID) else {
guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else {
continue
}
saveFolderRelationship(for: feed, withFolderName: folderName, id: String(tagging.taggingID))
folder.addFeed(feed)
folder.addWebFeed(feed)
}
}
@ -863,9 +863,9 @@ private extension FeedbinAccountDelegate {
let taggedFeedIDs = Set(taggings.map { String($0.feedID) })
// Remove all feeds from the account container that have a tag
for feed in account.topLevelFeeds {
if taggedFeedIDs.contains(feed.feedID) {
account.removeFeed(feed)
for feed in account.topLevelWebFeeds {
if taggedFeedIDs.contains(feed.webFeedID) {
account.removeWebFeed(feed)
}
}
}
@ -913,7 +913,7 @@ private extension FeedbinAccountDelegate {
}
func renameFolderRelationship(for account: Account, fromName: String, toName: String) {
for feed in account.flattenedFeeds() {
for feed in account.flattenedWebFeeds() {
if var folderRelationship = feed.folderRelationship {
let relationship = folderRelationship[fromName]
folderRelationship[fromName] = nil
@ -923,14 +923,14 @@ private extension FeedbinAccountDelegate {
}
}
func clearFolderRelationship(for feed: Feed, withFolderName folderName: String) {
func clearFolderRelationship(for feed: WebFeed, withFolderName folderName: String) {
if var folderRelationship = feed.folderRelationship {
folderRelationship[folderName] = nil
feed.folderRelationship = folderRelationship
}
}
func saveFolderRelationship(for feed: Feed, withFolderName folderName: String, id: String) {
func saveFolderRelationship(for feed: WebFeed, withFolderName folderName: String, id: String) {
if var folderRelationship = feed.folderRelationship {
folderRelationship[folderName] = id
feed.folderRelationship = folderRelationship
@ -939,7 +939,7 @@ private extension FeedbinAccountDelegate {
}
}
func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [FeedbinSubscriptionChoice], completion: @escaping (Result<Feed, Error>) -> Void) {
func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [FeedbinSubscriptionChoice], completion: @escaping (Result<WebFeed, Error>) -> Void) {
let feedSpecifiers: [FeedSpecifier] = choices.map { choice in
let source = url == choice.url ? FeedSpecifier.Source.UserEntered : FeedSpecifier.Source.HTMLLink
@ -949,7 +949,7 @@ private extension FeedbinAccountDelegate {
if let bestSpecifier = FeedSpecifier.bestFeed(in: Set(feedSpecifiers)) {
if let bestSubscription = choices.filter({ bestSpecifier.urlString == $0.url }).first {
createFeed(for: account, url: bestSubscription.url, name: name, container: container, completion: completion)
createWebFeed(for: account, url: bestSubscription.url, name: name, container: container, completion: completion)
} else {
DispatchQueue.main.async {
completion(.failure(FeedbinAccountDelegateError.invalidParameter))
@ -963,20 +963,20 @@ private extension FeedbinAccountDelegate {
}
func createFeed( account: Account, subscription sub: FeedbinSubscription, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
func createFeed( account: Account, subscription sub: FeedbinSubscription, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
DispatchQueue.main.async {
let feed = account.createFeed(with: sub.name, url: sub.url, feedID: String(sub.feedID), homePageURL: sub.homePageURL)
let feed = account.createWebFeed(with: sub.name, url: sub.url, webFeedID: String(sub.feedID), homePageURL: sub.homePageURL)
feed.subscriptionID = String(sub.subscriptionID)
feed.iconURL = sub.jsonFeed?.icon
feed.faviconURL = sub.jsonFeed?.favicon
account.addFeed(feed, to: container) { result in
account.addWebFeed(feed, to: container) { result in
switch result {
case .success:
if let name = name {
account.renameFeed(feed, to: name) { result in
account.renameWebFeed(feed, to: name) { result in
switch result {
case .success:
self.initialFeedDownload(account: account, feed: feed, completion: completion)
@ -996,13 +996,13 @@ private extension FeedbinAccountDelegate {
}
func initialFeedDownload( account: Account, feed: Feed, completion: @escaping (Result<Feed, Error>) -> Void) {
func initialFeedDownload( account: Account, feed: WebFeed, completion: @escaping (Result<WebFeed, Error>) -> Void) {
// refreshArticles is being reused and will clear one of the tasks for us
refreshProgress.addToNumberOfTasksAndRemaining(4)
// Download the initial articles
self.caller.retrieveEntries(feedID: feed.feedID) { result in
self.caller.retrieveEntries(feedID: feed.webFeedID) { result in
self.refreshProgress.completeTask()
switch result {
@ -1155,8 +1155,8 @@ private extension FeedbinAccountDelegate {
func processEntries(account: Account, entries: [FeedbinEntry]?, completion: @escaping (() -> Void)) {
let parsedItems = mapEntriesToParsedItems(entries: entries)
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true, completion: completion)
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true, completion: completion)
}
func mapEntriesToParsedItems(entries: [FeedbinEntry]?) -> Set<ParsedItem> {
@ -1232,7 +1232,7 @@ private extension FeedbinAccountDelegate {
account.ensureStatuses(missingUnstarredArticleIDs, true, .starred, false)
}
func deleteTagging(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
func deleteTagging(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
if let folder = container as? Folder, let feedTaggingID = feed.folderRelationship?[folder.name ?? ""] {
refreshProgress.addToNumberOfTasksAndRemaining(1)
@ -1242,7 +1242,7 @@ private extension FeedbinAccountDelegate {
case .success:
DispatchQueue.main.async {
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
folder.removeFeed(feed)
folder.removeWebFeed(feed)
account.addFeedIfNotInAnyFolder(feed)
completion(.success(()))
}
@ -1255,14 +1255,14 @@ private extension FeedbinAccountDelegate {
}
} else {
if let account = container as? Account {
account.removeFeed(feed)
account.removeWebFeed(feed)
}
completion(.success(()))
}
}
func deleteSubscription(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
func deleteSubscription(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
// This error should never happen
guard let subscriptionID = feed.subscriptionID else {
@ -1276,11 +1276,11 @@ private extension FeedbinAccountDelegate {
switch result {
case .success:
DispatchQueue.main.async {
account.clearFeedMetadata(feed)
account.removeFeed(feed)
account.clearWebFeedMetadata(feed)
account.removeWebFeed(feed)
if let folders = account.folders {
for folder in folders {
folder.removeFeed(feed)
folder.removeWebFeed(feed)
}
}
completion(.success(()))

View File

@ -294,7 +294,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
var createFeedRequest: FeedlyAddFeedRequest?
func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
let progress = refreshProgress
progress.addToNumberOfTasksAndRemaining(1)
@ -310,14 +310,14 @@ final class FeedlyAccountDelegate: AccountDelegate {
}
}
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> 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)))
return
}
let feedId = FeedlyFeedResourceId(id: feed.feedID)
let feedId = FeedlyFeedResourceId(id: feed.webFeedID)
let editedNameBefore = feed.editedName
// Adding an existing feed updates it.
@ -339,7 +339,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
var addFeedRequest: FeedlyAddFeedRequest?
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
let progress = refreshProgress
progress.addToNumberOfTasksAndRemaining(1)
@ -362,62 +362,62 @@ final class FeedlyAccountDelegate: AccountDelegate {
}
}
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
guard let folder = container as? Folder, let collectionId = folder.externalID else {
return DispatchQueue.main.async {
completion(.failure(FeedlyAccountDelegateError.unableToRemoveFeed(feed)))
}
}
caller.removeFeed(feed.feedID, fromCollectionWith: collectionId) { result in
caller.removeFeed(feed.webFeedID, fromCollectionWith: collectionId) { result in
switch result {
case .success:
completion(.success(()))
case .failure(let error):
folder.addFeed(feed)
folder.addWebFeed(feed)
completion(.failure(error))
}
}
folder.removeFeed(feed)
folder.removeWebFeed(feed)
}
func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
guard let from = from as? Folder, let to = to as? Folder else {
return DispatchQueue.main.async {
completion(.failure(FeedlyAccountDelegateError.addFeedChooseFolder))
}
}
addFeed(for: account, with: feed, to: to) { [weak self] addResult in
addWebFeed(for: account, with: feed, to: to) { [weak self] addResult in
switch addResult {
// now that we have added the feed, remove it from the other collection
case .success:
self?.removeFeed(for: account, with: feed, from: from) { removeResult in
self?.removeWebFeed(for: account, with: feed, from: from) { removeResult in
switch removeResult {
case .success:
completion(.success(()))
case .failure:
from.addFeed(feed)
from.addWebFeed(feed)
completion(.failure(FeedlyAccountDelegateError.unableToMoveFeedBetweenFolders(feed, from, to)))
}
}
case .failure(let error):
from.addFeed(feed)
to.removeFeed(feed)
from.addWebFeed(feed)
to.removeWebFeed(feed)
completion(.failure(error))
}
}
// optimistically move the feed, undoing as appropriate to the failure
from.removeFeed(feed)
to.addFeed(feed)
from.removeWebFeed(feed)
to.addWebFeed(feed)
}
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
if let existingFeed = account.existingFeed(withURL: feed.url) {
account.addFeed(existingFeed, to: container) { result in
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
if let existingFeed = account.existingWebFeed(withURL: feed.url) {
account.addWebFeed(existingFeed, to: container) { result in
switch result {
case .success:
completion(.success(()))
@ -426,7 +426,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
}
}
} else {
createFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
switch result {
case .success:
completion(.success(()))
@ -440,12 +440,12 @@ final class FeedlyAccountDelegate: AccountDelegate {
func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
let group = DispatchGroup()
for feed in folder.topLevelFeeds {
for feed in folder.topLevelWebFeeds {
folder.topLevelFeeds.remove(feed)
folder.topLevelWebFeeds.remove(feed)
group.enter()
restoreFeed(for: account, feed: feed, container: folder) { result in
restoreWebFeed(for: account, feed: feed, container: folder) { result in
group.leave()
switch result {
case .success:

View File

@ -13,11 +13,11 @@ enum FeedlyAccountDelegateError: LocalizedError {
case unableToAddFolder(String)
case unableToRenameFolder(String, String)
case unableToRemoveFolder(String)
case unableToMoveFeedBetweenFolders(Feed, Folder, Folder)
case unableToMoveFeedBetweenFolders(WebFeed, Folder, Folder)
case addFeedChooseFolder
case addFeedInvalidFolder(Folder)
case unableToRenameFeed(String, String)
case unableToRemoveFeed(Feed)
case unableToRemoveFeed(WebFeed)
var errorDescription: String? {
switch self {

View File

@ -29,7 +29,7 @@ final class FeedlyAddFeedRequest {
self.resourceProvider = resourceProvider
}
var completionHandler: ((Result<Feed, Error>) -> ())?
var completionHandler: ((Result<WebFeed, Error>) -> ())?
var error: Error?
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {
@ -37,17 +37,17 @@ final class FeedlyAddFeedRequest {
}
}
func addNewFeed(at url: String, name: String? = nil, completion: @escaping (Result<Feed, Error>) -> Void) {
func addNewFeed(at url: String, name: String? = nil, completion: @escaping (Result<WebFeed, Error>) -> Void) {
let resource = FeedlyFeedResourceId(url: url)
self.start(resource: resource, name: name, refreshes: true, completion: completion)
}
func add(existing feed: Feed, name: String? = nil, completion: @escaping (Result<Feed, Error>) -> Void) {
let resource = FeedlyFeedResourceId(id: feed.feedID)
func add(existing feed: WebFeed, name: String? = nil, completion: @escaping (Result<WebFeed, Error>) -> Void) {
let resource = FeedlyFeedResourceId(id: feed.webFeedID)
self.start(resource: resource, name: name, refreshes: false, completion: completion)
}
private func start(resource: FeedlyFeedResourceId, name: String?, refreshes: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
private func start(resource: FeedlyFeedResourceId, name: String?, refreshes: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
let (folder, collectionId): (Folder, String)
do {
@ -115,7 +115,7 @@ final class FeedlyAddFeedRequest {
if let error = delegate.error {
handler(.failure(error))
} else if let feed = folder.existingFeed(withFeedID: resource.id) {
} else if let feed = folder.existingWebFeed(withWebFeedID: resource.id) {
handler(.success(feed))
} else {

View File

@ -31,13 +31,13 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
let feedsBefore = Set(pairs
.map { $0.1 }
.flatMap { $0.topLevelFeeds })
.flatMap { $0.topLevelWebFeeds })
// Remove feeds in a folder which are not in the corresponding collection.
for (collectionFeeds, folder) in pairs {
let feedsInFolder = folder.topLevelFeeds
let feedsInFolder = folder.topLevelWebFeeds
let feedsInCollection = Set(collectionFeeds.map { $0.id })
let feedsToRemove = feedsInFolder.filter { !feedsInCollection.contains($0.feedID) }
let feedsToRemove = feedsInFolder.filter { !feedsInCollection.contains($0.webFeedID) }
if !feedsToRemove.isEmpty {
folder.removeFeeds(feedsToRemove)
// os_log(.debug, log: log, "\"%@\" - removed: %@", collection.label, feedsToRemove.map { $0.feedID }, feedsInCollection)
@ -46,7 +46,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
}
// Pair each Feed with its Folder.
var feedsAdded = Set<Feed>()
var feedsAdded = Set<WebFeed>()
let feedsAndFolders = pairs
.map({ (collectionFeeds, folder) -> [(FeedlyFeed, Folder)] in
@ -55,14 +55,14 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
}
})
.flatMap { $0 }
.compactMap { (collectionFeed, folder) -> (Feed, Folder) in
.compactMap { (collectionFeed, folder) -> (WebFeed, Folder) in
// find an existing feed previously added to the account
if let feed = account.existingFeed(withFeedID: collectionFeed.id) {
if let feed = account.existingWebFeed(withWebFeedID: collectionFeed.id) {
return (feed, folder)
} else {
// find an existing feed we created below in an earlier value
for feed in feedsAdded where feed.feedID == collectionFeed.id {
for feed in feedsAdded where feed.webFeedID == collectionFeed.id {
return (feed, folder)
}
}
@ -70,7 +70,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
// no exsiting feed, create a new one
let id = collectionFeed.id
let url = FeedlyFeedResourceId(id: id).url
let feed = account.createFeed(with: collectionFeed.title, url: url, feedID: id, homePageURL: collectionFeed.website)
let feed = account.createWebFeed(with: collectionFeed.title, url: url, webFeedID: id, homePageURL: collectionFeed.website)
// So the same feed isn't created more than once.
feedsAdded.insert(feed)
@ -81,7 +81,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
os_log(.debug, log: log, "Processing %i feeds.", feedsAndFolders.count)
feedsAndFolders.forEach { (feed, folder) in
if !folder.has(feed) {
folder.addFeed(feed)
folder.addWebFeed(feed)
}
}

View File

@ -29,10 +29,10 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
return
}
let feedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId
let webFeedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) {
os_log(.debug, log: self.log, "Updated %i feeds for \"%@\"", feedIDsAndItems.count, self.organisedItemsProvider.providerName)
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) {
os_log(.debug, log: self.log, "Updated %i feeds for \"%@\"", webFeedIDsAndItems.count, self.organisedItemsProvider.providerName)
self.didFinish()
}
}

View File

@ -13,7 +13,7 @@ import RSCore
public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCountProvider, Hashable {
public weak var account: Account?
public var topLevelFeeds: Set<Feed> = Set<Feed>()
public var topLevelWebFeeds: Set<WebFeed> = Set<WebFeed>()
public var folders: Set<Folder>? = nil // subfolders are not supported, so this is always nil
public var name: String? {
@ -80,42 +80,42 @@ public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCoun
// MARK: Container
public func flattenedFeeds() -> Set<Feed> {
public func flattenedWebFeeds() -> Set<WebFeed> {
// Since sub-folders are not supported, its always the top-level feeds.
return topLevelFeeds
return topLevelWebFeeds
}
public func objectIsChild(_ object: AnyObject) -> Bool {
// Folders contain Feed objects only, at least for now.
guard let feed = object as? Feed else {
guard let feed = object as? WebFeed else {
return false
}
return topLevelFeeds.contains(feed)
return topLevelWebFeeds.contains(feed)
}
public func addFeed(_ feed: Feed) {
topLevelFeeds.insert(feed)
public func addWebFeed(_ feed: WebFeed) {
topLevelWebFeeds.insert(feed)
postChildrenDidChangeNotification()
}
public func addFeeds(_ feeds: Set<Feed>) {
public func addFeeds(_ feeds: Set<WebFeed>) {
guard !feeds.isEmpty else {
return
}
topLevelFeeds.formUnion(feeds)
topLevelWebFeeds.formUnion(feeds)
postChildrenDidChangeNotification()
}
public func removeFeed(_ feed: Feed) {
topLevelFeeds.remove(feed)
public func removeWebFeed(_ feed: WebFeed) {
topLevelWebFeeds.remove(feed)
postChildrenDidChangeNotification()
}
public func removeFeeds(_ feeds: Set<Feed>) {
public func removeFeeds(_ feeds: Set<WebFeed>) {
guard !feeds.isEmpty else {
return
}
topLevelFeeds.subtract(feeds)
topLevelWebFeeds.subtract(feeds)
postChildrenDidChangeNotification()
}
@ -138,14 +138,14 @@ private extension Folder {
func updateUnreadCount() {
var updatedUnreadCount = 0
for feed in topLevelFeeds {
for feed in topLevelWebFeeds {
updatedUnreadCount += feed.unreadCount
}
unreadCount = updatedUnreadCount
}
func childrenContain(_ feed: Feed) -> Bool {
return topLevelFeeds.contains(feed)
func childrenContain(_ feed: WebFeed) -> Bool {
return topLevelWebFeeds.contains(feed)
}
}
@ -169,7 +169,7 @@ extension Folder: OPMLRepresentable {
var hasAtLeastOneChild = false
for feed in topLevelFeeds {
for feed in topLevelWebFeeds {
s += feed.OPMLString(indentLevel: indentLevel + 1, strictConformance: strictConformance)
hasAtLeastOneChild = true
}

View File

@ -36,7 +36,7 @@ final class LocalAccountDelegate: AccountDelegate {
}
func refreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
refresher.refreshFeeds(account.flattenedFeeds()) {
refresher.refreshFeeds(account.flattenedWebFeeds()) {
completion(.success(()))
}
}
@ -91,7 +91,7 @@ final class LocalAccountDelegate: AccountDelegate {
}
func createFeed(for account: Account, url urlString: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
guard let url = URL(string: urlString) else {
completion(.failure(LocalAccountDelegateError.invalidParameter))
return
@ -109,13 +109,13 @@ final class LocalAccountDelegate: AccountDelegate {
return
}
if account.hasFeed(withURL: bestFeedSpecifier.urlString) {
if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) {
self.refreshProgress.completeTask()
completion(.failure(AccountError.createErrorAlreadySubscribed))
return
}
let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
InitialFeedDownloader.download(url) { parsedFeed in
self.refreshProgress.completeTask()
@ -126,7 +126,7 @@ final class LocalAccountDelegate: AccountDelegate {
feed.editedName = name
container.addFeed(feed)
container.addWebFeed(feed)
completion(.success(feed))
}
@ -140,29 +140,29 @@ final class LocalAccountDelegate: AccountDelegate {
}
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
feed.editedName = name
completion(.success(()))
}
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
container.removeFeed(feed)
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
container.removeWebFeed(feed)
completion(.success(()))
}
func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
from.removeFeed(feed)
to.addFeed(feed)
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
from.removeWebFeed(feed)
to.addWebFeed(feed)
completion(.success(()))
}
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
container.addFeed(feed)
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
container.addWebFeed(feed)
completion(.success(()))
}
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
container.addFeed(feed)
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
container.addWebFeed(feed)
completion(.success(()))
}

View File

@ -24,7 +24,7 @@ final class LocalAccountRefresher {
return downloadSession.progress
}
public func refreshFeeds(_ feeds: Set<Feed>, completion: @escaping () -> Void) {
public func refreshFeeds(_ feeds: Set<WebFeed>, completion: @escaping () -> Void) {
self.completion = completion
downloadSession.downloadObjects(feeds as NSSet)
}
@ -40,7 +40,7 @@ final class LocalAccountRefresher {
extension LocalAccountRefresher: DownloadSessionDelegate {
func downloadSession(_ downloadSession: DownloadSession, requestForRepresentedObject representedObject: AnyObject) -> URLRequest? {
guard let feed = representedObject as? Feed else {
guard let feed = representedObject as? WebFeed else {
return nil
}
guard let url = URL(string: feed.url) else {
@ -56,7 +56,7 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
}
func downloadSession(_ downloadSession: DownloadSession, downloadDidCompleteForRepresentedObject representedObject: AnyObject, response: URLResponse?, data: Data, error: NSError?) {
guard let feed = representedObject as? Feed, !data.isEmpty else {
guard let feed = representedObject as? WebFeed, !data.isEmpty else {
return
}
@ -86,7 +86,7 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
}
func downloadSession(_ downloadSession: DownloadSession, shouldContinueAfterReceivingData data: Data, representedObject: AnyObject) -> Bool {
guard let feed = representedObject as? Feed else {
guard let feed = representedObject as? WebFeed else {
return false
}

View File

@ -43,7 +43,7 @@ private extension OPMLFile {
func loadCallback() {
guard let opmlItems = parsedOPMLItems() else { return }
BatchUpdate.shared.perform {
account.topLevelFeeds.removeAll()
account.topLevelWebFeeds.removeAll()
account.loadOPMLItems(opmlItems, parentFolder: nil)
}
}

View File

@ -227,9 +227,9 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
let group = DispatchGroup()
for feed in folder.topLevelFeeds {
for feed in folder.topLevelWebFeeds {
group.enter()
removeFeed(for: account, with: feed, from: folder) { result in
removeWebFeed(for: account, with: feed, from: folder) { result in
group.leave()
switch result {
case .success:
@ -256,7 +256,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
caller.createSubscription(url: url) { result in
switch result {
@ -284,7 +284,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
// This error should never happen
guard let subscriptionID = feed.subscriptionID else {
@ -309,23 +309,23 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
if feed.folderRelationship?.count ?? 0 > 1 {
deleteTagging(for: account, with: feed, from: container, completion: completion)
} else {
account.clearFeedMetadata(feed)
account.clearWebFeedMetadata(feed)
deleteSubscription(for: account, with: feed, from: container, completion: completion)
}
}
func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
if from is Account {
addFeed(for: account, with: feed, to: to, completion: completion)
addWebFeed(for: account, with: feed, to: to, completion: completion)
} else {
deleteTagging(for: account, with: feed, from: from) { result in
switch result {
case .success:
self.addFeed(for: account, with: feed, to: to, completion: completion)
self.addWebFeed(for: account, with: feed, to: to, completion: completion)
case .failure(let error):
completion(.failure(error))
}
@ -333,7 +333,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
}
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
if let folder = container as? Folder, let feedName = feed.subscriptionID {
caller.createTagging(subscriptionID: feedName, tagName: folder.name ?? "") { result in
@ -341,8 +341,8 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
case .success:
DispatchQueue.main.async {
self.saveFolderRelationship(for: feed, withFolderName: folder.name ?? "", id: feed.subscriptionID!)
account.removeFeed(feed)
folder.addFeed(feed)
account.removeWebFeed(feed)
folder.addWebFeed(feed)
completion(.success(()))
}
case .failure(let error):
@ -363,9 +363,9 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
createFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
switch result {
case .success:
completion(.success(()))
@ -381,12 +381,12 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
account.addFolder(folder)
let group = DispatchGroup()
for feed in folder.topLevelFeeds {
for feed in folder.topLevelWebFeeds {
group.enter()
addFeed(for: account, with: feed, to: folder) { result in
if account.topLevelFeeds.contains(feed) {
account.removeFeed(feed)
addWebFeed(for: account, with: feed, to: folder) { result in
if account.topLevelWebFeeds.contains(feed) {
account.removeWebFeed(feed)
}
group.leave()
}
@ -469,8 +469,8 @@ private extension ReaderAPIAccountDelegate {
if let folders = account.folders {
folders.forEach { folder in
if !tagNames.contains(folder.name ?? "") {
for feed in folder.topLevelFeeds {
account.addFeed(feed)
for feed in folder.topLevelWebFeeds {
account.addWebFeed(feed)
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
}
account.removeFolder(folder)
@ -532,17 +532,17 @@ private extension ReaderAPIAccountDelegate {
// Remove any feeds that are no longer in the subscriptions
if let folders = account.folders {
for folder in folders {
for feed in folder.topLevelFeeds {
if !subFeedIds.contains(feed.feedID) {
folder.removeFeed(feed)
for feed in folder.topLevelWebFeeds {
if !subFeedIds.contains(feed.webFeedID) {
folder.removeWebFeed(feed)
}
}
}
}
for feed in account.topLevelFeeds {
if !subFeedIds.contains(feed.feedID) {
account.removeFeed(feed)
for feed in account.topLevelWebFeeds {
if !subFeedIds.contains(feed.webFeedID) {
account.removeWebFeed(feed)
}
}
@ -550,14 +550,14 @@ private extension ReaderAPIAccountDelegate {
subscriptions.forEach { subscription in
let subFeedId = String(subscription.feedID)
if let feed = account.existingFeed(withFeedID: subFeedId) {
if let feed = account.existingWebFeed(withWebFeedID: subFeedId) {
feed.name = subscription.name
feed.homePageURL = subscription.homePageURL
} else {
let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: subFeedId, homePageURL: subscription.homePageURL)
let feed = account.createWebFeed(with: subscription.name, url: subscription.url, webFeedID: subFeedId, homePageURL: subscription.homePageURL)
feed.iconURL = subscription.iconURL
feed.subscriptionID = String(subscription.feedID)
account.addFeed(feed)
account.addWebFeed(feed)
}
}
@ -606,25 +606,25 @@ private extension ReaderAPIAccountDelegate {
let taggingFeedIDs = groupedTaggings.map { String($0.feedID) }
// Move any feeds not in the folder to the account
for feed in folder.topLevelFeeds {
if !taggingFeedIDs.contains(feed.feedID) {
folder.removeFeed(feed)
for feed in folder.topLevelWebFeeds {
if !taggingFeedIDs.contains(feed.webFeedID) {
folder.removeWebFeed(feed)
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
account.addFeed(feed)
account.addWebFeed(feed)
}
}
// Add any feeds not in the folder
let folderFeedIds = folder.topLevelFeeds.map { $0.feedID }
let folderFeedIds = folder.topLevelWebFeeds.map { $0.webFeedID }
for subscription in groupedTaggings {
let taggingFeedID = String(subscription.feedID)
if !folderFeedIds.contains(taggingFeedID) {
guard let feed = account.existingFeed(withFeedID: taggingFeedID) else {
guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else {
continue
}
saveFolderRelationship(for: feed, withFolderName: folderName, id: String(subscription.feedID))
folder.addFeed(feed)
folder.addWebFeed(feed)
}
}
@ -633,9 +633,9 @@ private extension ReaderAPIAccountDelegate {
let taggedFeedIDs = Set(subscriptions.map { String($0.feedID) })
// Remove all feeds from the account container that have a tag
for feed in account.topLevelFeeds {
if taggedFeedIDs.contains(feed.feedID) {
account.removeFeed(feed)
for feed in account.topLevelWebFeeds {
if taggedFeedIDs.contains(feed.webFeedID) {
account.removeWebFeed(feed)
}
}
@ -679,14 +679,14 @@ private extension ReaderAPIAccountDelegate {
func clearFolderRelationship(for feed: Feed, withFolderName folderName: String) {
func clearFolderRelationship(for feed: WebFeed, withFolderName folderName: String) {
if var folderRelationship = feed.folderRelationship {
folderRelationship[folderName] = nil
feed.folderRelationship = folderRelationship
}
}
func saveFolderRelationship(for feed: Feed, withFolderName folderName: String, id: String) {
func saveFolderRelationship(for feed: WebFeed, withFolderName folderName: String, id: String) {
if var folderRelationship = feed.folderRelationship {
folderRelationship[folderName] = id
feed.folderRelationship = folderRelationship
@ -695,7 +695,7 @@ private extension ReaderAPIAccountDelegate {
}
}
func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [ReaderAPISubscriptionChoice], completion: @escaping (Result<Feed, Error>) -> Void) {
func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [ReaderAPISubscriptionChoice], completion: @escaping (Result<WebFeed, Error>) -> Void) {
let feedSpecifiers: [FeedSpecifier] = choices.map { choice in
let source = url == choice.url ? FeedSpecifier.Source.UserEntered : FeedSpecifier.Source.HTMLLink
@ -705,7 +705,7 @@ private extension ReaderAPIAccountDelegate {
if let bestSpecifier = FeedSpecifier.bestFeed(in: Set(feedSpecifiers)) {
if let bestSubscription = choices.filter({ bestSpecifier.urlString == $0.url }).first {
createFeed(for: account, url: bestSubscription.url, name: name, container: container, completion: completion)
createWebFeed(for: account, url: bestSubscription.url, name: name, container: container, completion: completion)
} else {
DispatchQueue.main.async {
completion(.failure(ReaderAPIAccountDelegateError.invalidParameter))
@ -719,18 +719,18 @@ private extension ReaderAPIAccountDelegate {
}
func createFeed( account: Account, subscription sub: ReaderAPISubscription, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
func createFeed( account: Account, subscription sub: ReaderAPISubscription, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
DispatchQueue.main.async {
let feed = account.createFeed(with: sub.name, url: sub.url, feedID: String(sub.feedID), homePageURL: sub.homePageURL)
let feed = account.createWebFeed(with: sub.name, url: sub.url, webFeedID: String(sub.feedID), homePageURL: sub.homePageURL)
feed.subscriptionID = String(sub.feedID)
account.addFeed(feed, to: container) { result in
account.addWebFeed(feed, to: container) { result in
switch result {
case .success:
if let name = name {
account.renameFeed(feed, to: name) { result in
account.renameWebFeed(feed, to: name) { result in
switch result {
case .success:
self.initialFeedDownload(account: account, feed: feed, completion: completion)
@ -750,10 +750,10 @@ private extension ReaderAPIAccountDelegate {
}
func initialFeedDownload( account: Account, feed: Feed, completion: @escaping (Result<Feed, Error>) -> Void) {
func initialFeedDownload( account: Account, feed: WebFeed, completion: @escaping (Result<WebFeed, Error>) -> Void) {
// Download the initial articles
self.caller.retrieveEntries(feedID: feed.feedID) { result in
self.caller.retrieveEntries(webFeedID: feed.webFeedID) { result in
switch result {
case .success(let (entries, page)):
@ -871,8 +871,8 @@ private extension ReaderAPIAccountDelegate {
func processEntries(account: Account, entries: [ReaderAPIEntry]?, completion: @escaping (() -> Void)) {
let parsedItems = mapEntriesToParsedItems(account: account, entries: entries)
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true, completion: completion)
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true, completion: completion)
}
func mapEntriesToParsedItems(account: Account, entries: [ReaderAPIEntry]?) -> Set<ParsedItem> {
@ -953,7 +953,7 @@ private extension ReaderAPIAccountDelegate {
func deleteTagging(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
func deleteTagging(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
if let folder = container as? Folder, let feedName = feed.subscriptionID {
caller.deleteTagging(subscriptionID: feedName, tagName: folder.name ?? "") { result in
@ -961,7 +961,7 @@ private extension ReaderAPIAccountDelegate {
case .success:
DispatchQueue.main.async {
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
folder.removeFeed(feed)
folder.removeWebFeed(feed)
account.addFeedIfNotInAnyFolder(feed)
completion(.success(()))
}
@ -974,14 +974,14 @@ private extension ReaderAPIAccountDelegate {
}
} else {
if let account = container as? Account {
account.removeFeed(feed)
account.removeWebFeed(feed)
}
completion(.success(()))
}
}
func deleteSubscription(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
func deleteSubscription(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
// This error should never happen
guard let subscriptionID = feed.subscriptionID else {
@ -993,10 +993,10 @@ private extension ReaderAPIAccountDelegate {
switch result {
case .success:
DispatchQueue.main.async {
account.removeFeed(feed)
account.removeWebFeed(feed)
if let folders = account.folders {
for folder in folders {
folder.removeFeed(feed)
folder.removeWebFeed(feed)
}
}
completion(.success(()))

View File

@ -594,7 +594,7 @@ final class ReaderAPICaller: NSObject {
}
func retrieveEntries(feedID: String, completion: @escaping (Result<([ReaderAPIEntry]?, String?), Error>) -> Void) {
func retrieveEntries(webFeedID: String, completion: @escaping (Result<([ReaderAPIEntry]?, String?), Error>) -> Void) {
let since = Calendar.current.date(byAdding: .month, value: -3, to: Date()) ?? Date()
@ -606,7 +606,7 @@ final class ReaderAPICaller: NSObject {
let url = baseURL
.appendingPathComponent(ReaderAPIEndpoints.itemIds.rawValue)
.appendingQueryItems([
URLQueryItem(name: "s", value: feedID),
URLQueryItem(name: "s", value: webFeedID),
URLQueryItem(name: "ot", value: String(since.timeIntervalSince1970)),
URLQueryItem(name: "output", value: "json")
])

View File

@ -1,5 +1,5 @@
//
// Feed.swift
// WebFeed.swift
// NetNewsWire
//
// Created by Brent Simmons on 7/1/17.
@ -11,17 +11,17 @@ import RSCore
import RSWeb
import Articles
public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Hashable {
public final class WebFeed: DisplayNameProvider, Renamable, UnreadCountProvider, Hashable {
public weak var account: Account?
public let url: String
public var feedID: String {
public var webFeedID: String {
get {
return metadata.feedID
return metadata.webFeedID
}
set {
metadata.feedID = newValue
metadata.webFeedID = newValue
}
}
@ -176,7 +176,7 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
public func rename(to newName: String, completion: @escaping (Result<Void, Error>) -> Void) {
guard let account = account else { return }
account.renameFeed(self, to: newName, completion: completion)
account.renameWebFeed(self, to: newName, completion: completion)
}
// MARK: - UnreadCountProvider
@ -194,7 +194,7 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
}
}
var metadata: FeedMetadata
var metadata: WebFeedMetadata
// MARK: - Private
@ -202,7 +202,7 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
// MARK: - Init
init(account: Account, url: String, metadata: FeedMetadata) {
init(account: Account, url: String, metadata: WebFeedMetadata) {
self.account = account
self.accountID = account.accountID
self.url = url
@ -219,20 +219,20 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
// MARK: - Hashable
public func hash(into hasher: inout Hasher) {
hasher.combine(feedID)
hasher.combine(webFeedID)
hasher.combine(accountID)
}
// MARK: - Equatable
public class func ==(lhs: Feed, rhs: Feed) -> Bool {
return lhs.feedID == rhs.feedID && lhs.accountID == rhs.accountID
public class func ==(lhs: WebFeed, rhs: WebFeed) -> Bool {
return lhs.webFeedID == rhs.webFeedID && lhs.accountID == rhs.accountID
}
}
// MARK: - OPMLRepresentable
extension Feed: OPMLRepresentable {
extension WebFeed: OPMLRepresentable {
public func OPMLString(indentLevel: Int, strictConformance: Bool) -> String {
// https://github.com/brentsimmons/NetNewsWire/issues/527
@ -260,9 +260,9 @@ extension Feed: OPMLRepresentable {
}
}
extension Set where Element == Feed {
extension Set where Element == WebFeed {
func feedIDs() -> Set<String> {
return Set<String>(map { $0.feedID })
func webFeedIDs() -> Set<String> {
return Set<String>(map { $0.webFeedID })
}
}

View File

@ -1,5 +1,5 @@
//
// FeedMetadata.swift
// WebFeedMetadata.swift
// NetNewsWire
//
// Created by Brent Simmons on 3/12/19.
@ -10,14 +10,14 @@ import Foundation
import RSWeb
import Articles
protocol FeedMetadataDelegate: class {
func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys)
protocol WebFeedMetadataDelegate: class {
func valueDidChange(_ feedMetadata: WebFeedMetadata, key: WebFeedMetadata.CodingKeys)
}
final class FeedMetadata: Codable {
final class WebFeedMetadata: Codable {
enum CodingKeys: String, CodingKey {
case feedID
case webFeedID = "feedID"
case homePageURL
case iconURL
case faviconURL
@ -31,10 +31,10 @@ final class FeedMetadata: Codable {
case folderRelationship
}
var feedID: String {
var webFeedID: String {
didSet {
if feedID != oldValue {
valueDidChange(.feedID)
if webFeedID != oldValue {
valueDidChange(.webFeedID)
}
}
}
@ -128,10 +128,10 @@ final class FeedMetadata: Codable {
}
}
weak var delegate: FeedMetadataDelegate?
weak var delegate: WebFeedMetadataDelegate?
init(feedID: String) {
self.feedID = feedID
init(webFeedID: String) {
self.webFeedID = webFeedID
}
func valueDidChange(_ key: CodingKeys) {

View File

@ -1,5 +1,5 @@
//
// FeedMetadataFile.swift
// WebFeedMetadataFile.swift
// Account
//
// Created by Maurice Parker on 9/13/19.
@ -10,9 +10,9 @@ import Foundation
import os.log
import RSCore
final class FeedMetadataFile {
final class WebFeedMetadataFile {
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "feedMetadataFile")
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "webFeedMetadataFile")
private let fileURL: URL
private let account: Account
@ -37,7 +37,7 @@ final class FeedMetadataFile {
}
private extension FeedMetadataFile {
private extension WebFeedMetadataFile {
func loadCallback() {
@ -47,11 +47,11 @@ private extension FeedMetadataFile {
fileCoordinator.coordinate(readingItemAt: fileURL, options: [], error: errorPointer, byAccessor: { readURL in
if let fileData = try? Data(contentsOf: readURL) {
let decoder = PropertyListDecoder()
account.feedMetadata = (try? decoder.decode(Account.FeedMetadataDictionary.self, from: fileData)) ?? Account.FeedMetadataDictionary()
account.webFeedMetadata = (try? decoder.decode(Account.WebFeedMetadataDictionary.self, from: fileData)) ?? Account.WebFeedMetadataDictionary()
}
account.feedMetadata.values.forEach { $0.delegate = account }
account.webFeedMetadata.values.forEach { $0.delegate = account }
if !account.startingUp {
account.resetFeedMetadataAndUnreadCounts()
account.resetWebFeedMetadataAndUnreadCounts()
}
})
@ -87,10 +87,10 @@ private extension FeedMetadataFile {
}
}
private func metadataForOnlySubscribedToFeeds() -> Account.FeedMetadataDictionary {
let feedIDs = account.idToFeedDictionary.keys
return account.feedMetadata.filter { (feedID: String, metadata: FeedMetadata) -> Bool in
return feedIDs.contains(metadata.feedID)
private func metadataForOnlySubscribedToFeeds() -> Account.WebFeedMetadataDictionary {
let webFeedIDs = account.idToWebFeedDictionary.keys
return account.webFeedMetadata.filter { (feedID: String, metadata: WebFeedMetadata) -> Bool in
return webFeedIDs.contains(metadata.webFeedID)
}
}

View File

@ -14,7 +14,7 @@ public struct Article: Hashable {
public let articleID: String // Unique database ID (possibly sync service ID)
public let accountID: String
public let feedID: String // Likely a URL, but not necessarily
public let webFeedID: String // Likely a URL, but not necessarily
public let uniqueID: String // Unique per feed (RSS guid, for example)
public let title: String?
public let contentHTML: String?
@ -30,10 +30,10 @@ public struct Article: Hashable {
public let attachments: Set<Attachment>?
public let status: ArticleStatus
public init(accountID: String, articleID: String?, feedID: String, uniqueID: String, title: String?, contentHTML: String?, contentText: String?, url: String?, externalURL: String?, summary: String?, imageURL: String?, bannerImageURL: String?, datePublished: Date?, dateModified: Date?, authors: Set<Author>?, attachments: Set<Attachment>?, status: ArticleStatus) {
public init(accountID: String, articleID: String?, webFeedID: String, uniqueID: String, title: String?, contentHTML: String?, contentText: String?, url: String?, externalURL: String?, summary: String?, imageURL: String?, bannerImageURL: String?, datePublished: Date?, dateModified: Date?, authors: Set<Author>?, attachments: Set<Attachment>?, status: ArticleStatus) {
self.accountID = accountID
self.feedID = feedID
self.webFeedID = webFeedID
self.uniqueID = uniqueID
self.title = title
self.contentHTML = contentHTML
@ -53,12 +53,12 @@ public struct Article: Hashable {
self.articleID = articleID
}
else {
self.articleID = Article.calculatedArticleID(feedID: feedID, uniqueID: uniqueID)
self.articleID = Article.calculatedArticleID(webFeedID: webFeedID, uniqueID: uniqueID)
}
}
public static func calculatedArticleID(feedID: String, uniqueID: String) -> String {
return databaseIDWithString("\(feedID) \(uniqueID)")
public static func calculatedArticleID(webFeedID: String, uniqueID: String) -> String {
return databaseIDWithString("\(webFeedID) \(uniqueID)")
}
// MARK: - Hashable

View File

@ -16,7 +16,7 @@ import Articles
// Main thread only.
public typealias UnreadCountDictionary = [String: Int] // feedID: unreadCount
public typealias UnreadCountDictionary = [String: Int] // webFeedID: unreadCount
public typealias UnreadCountCompletionBlock = (UnreadCountDictionary) -> Void
public typealias UpdateArticlesCompletionBlock = (Set<Article>?, Set<Article>?) -> Void //newArticles, updatedArticles
@ -44,28 +44,28 @@ public final class ArticlesDatabase {
// MARK: - Fetching Articles
public func fetchArticles(_ feedID: String) -> Set<Article> {
return articlesTable.fetchArticles(feedID)
public func fetchArticles(_ webFeedID: String) -> Set<Article> {
return articlesTable.fetchArticles(webFeedID)
}
public func fetchArticles(articleIDs: Set<String>) -> Set<Article> {
return articlesTable.fetchArticles(articleIDs: articleIDs)
}
public func fetchUnreadArticles(_ feedIDs: Set<String>) -> Set<Article> {
return articlesTable.fetchUnreadArticles(feedIDs)
public func fetchUnreadArticles(_ webFeedID: Set<String>) -> Set<Article> {
return articlesTable.fetchUnreadArticles(webFeedID)
}
public func fetchTodayArticles(_ feedIDs: Set<String>) -> Set<Article> {
return articlesTable.fetchArticlesSince(feedIDs, todayCutoffDate())
public func fetchTodayArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
return articlesTable.fetchArticlesSince(webFeedIDs, todayCutoffDate())
}
public func fetchStarredArticles(_ feedIDs: Set<String>) -> Set<Article> {
return articlesTable.fetchStarredArticles(feedIDs)
public func fetchStarredArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
return articlesTable.fetchStarredArticles(webFeedIDs)
}
public func fetchArticlesMatching(_ searchString: String, _ feedIDs: Set<String>) -> Set<Article> {
return articlesTable.fetchArticlesMatching(searchString, feedIDs)
public func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) -> Set<Article> {
return articlesTable.fetchArticlesMatching(searchString, webFeedIDs)
}
public func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>) -> Set<Article> {
@ -74,28 +74,28 @@ public final class ArticlesDatabase {
// MARK: - Fetching Articles Async
public func fetchArticlesAsync(_ feedID: String, _ callback: @escaping ArticleSetBlock) {
articlesTable.fetchArticlesAsync(feedID, callback)
public func fetchArticlesAsync(_ webFeedID: String, _ callback: @escaping ArticleSetBlock) {
articlesTable.fetchArticlesAsync(webFeedID, callback)
}
public func fetchArticlesAsync(articleIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
articlesTable.fetchArticlesAsync(articleIDs: articleIDs, callback)
}
public func fetchUnreadArticlesAsync(_ feedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
articlesTable.fetchUnreadArticlesAsync(feedIDs, callback)
public func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
articlesTable.fetchUnreadArticlesAsync(webFeedIDs, callback)
}
public func fetchTodayArticlesAsync(_ feedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
articlesTable.fetchArticlesSinceAsync(feedIDs, todayCutoffDate(), callback)
public func fetchTodayArticlesAsync(_ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
articlesTable.fetchArticlesSinceAsync(webFeedIDs, todayCutoffDate(), callback)
}
public func fetchedStarredArticlesAsync(_ feedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
articlesTable.fetchStarredArticlesAsync(feedIDs, callback)
public func fetchedStarredArticlesAsync(_ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
articlesTable.fetchStarredArticlesAsync(webFeedIDs, callback)
}
public func fetchArticlesMatchingAsync(_ searchString: String, _ feedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
articlesTable.fetchArticlesMatchingAsync(searchString, feedIDs, callback)
public func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
articlesTable.fetchArticlesMatchingAsync(searchString, webFeedIDs, callback)
}
public func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
@ -104,20 +104,20 @@ public final class ArticlesDatabase {
// MARK: - Unread Counts
public func fetchUnreadCounts(for feedIDs: Set<String>, _ callback: @escaping UnreadCountCompletionBlock) {
articlesTable.fetchUnreadCounts(feedIDs, callback)
public func fetchUnreadCounts(for webFeedIDs: Set<String>, _ callback: @escaping UnreadCountCompletionBlock) {
articlesTable.fetchUnreadCounts(webFeedIDs, callback)
}
public func fetchUnreadCountForToday(for feedIDs: Set<String>, callback: @escaping (Int) -> Void) {
fetchUnreadCount(for: feedIDs, since: todayCutoffDate(), callback: callback)
public func fetchUnreadCountForToday(for webFeedIDs: Set<String>, callback: @escaping (Int) -> Void) {
fetchUnreadCount(for: webFeedIDs, since: todayCutoffDate(), callback: callback)
}
public func fetchUnreadCount(for feedIDs: Set<String>, since: Date, callback: @escaping (Int) -> Void) {
articlesTable.fetchUnreadCount(feedIDs, since, callback)
public func fetchUnreadCount(for webFeedIDs: Set<String>, since: Date, callback: @escaping (Int) -> Void) {
articlesTable.fetchUnreadCount(webFeedIDs, since, callback)
}
public func fetchStarredAndUnreadCount(for feedIDs: Set<String>, callback: @escaping (Int) -> Void) {
articlesTable.fetchStarredAndUnreadCount(feedIDs, callback)
public func fetchStarredAndUnreadCount(for webFeedIDs: Set<String>, callback: @escaping (Int) -> Void) {
articlesTable.fetchStarredAndUnreadCount(webFeedIDs, callback)
}
public func fetchAllNonZeroUnreadCounts(_ callback: @escaping UnreadCountCompletionBlock) {
@ -126,9 +126,9 @@ public final class ArticlesDatabase {
// MARK: - Saving and Updating Articles
/// Update articles and save new ones. The key for feedIDsAndItems is feedID.
public func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
articlesTable.update(feedIDsAndItems, defaultRead, completion)
/// Update articles and save new ones. The key for ewbFeedIDsAndItems is webFeedID.
public func update(webFeedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
articlesTable.update(webFeedIDsAndItems, defaultRead, completion)
}
public func ensureStatuses(_ articleIDs: Set<String>, _ defaultRead: Bool, _ statusKey: ArticleStatus.Key, _ flag: Bool, completionHandler: (() -> ())? = nil) {
@ -166,8 +166,8 @@ public final class ArticlesDatabase {
// These are to be used only at startup. These are to prevent the database from growing forever.
/// Calls the various clean-up functions.
public func cleanupDatabaseAtStartup(subscribedToFeedIDs: Set<String>) {
articlesTable.deleteArticlesNotInSubscribedToFeedIDs(subscribedToFeedIDs)
public func cleanupDatabaseAtStartup(subscribedToWebFeedIDs: Set<String>) {
articlesTable.deleteArticlesNotInSubscribedToFeedIDs(subscribedToWebFeedIDs)
}
}

View File

@ -48,16 +48,16 @@ final class ArticlesTable: DatabaseTable {
// MARK: - Fetching Articles for Feed
func fetchArticles(_ feedID: String) -> Set<Article> {
return fetchArticles{ self.fetchArticlesForFeedID(feedID, withLimits: true, $0) }
func fetchArticles(_ webFeedID: String) -> Set<Article> {
return fetchArticles{ self.fetchArticlesForFeedID(webFeedID, withLimits: true, $0) }
}
func fetchArticlesAsync(_ feedID: String, _ callback: @escaping ArticleSetBlock) {
fetchArticlesAsync({ self.fetchArticlesForFeedID(feedID, withLimits: true, $0) }, callback)
func fetchArticlesAsync(_ webFeedID: String, _ callback: @escaping ArticleSetBlock) {
fetchArticlesAsync({ self.fetchArticlesForFeedID(webFeedID, withLimits: true, $0) }, callback)
}
private func fetchArticlesForFeedID(_ feedID: String, withLimits: Bool, _ database: FMDatabase) -> Set<Article> {
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [feedID as AnyObject], withLimits: withLimits)
private func fetchArticlesForFeedID(_ webFeedID: String, withLimits: Bool, _ database: FMDatabase) -> Set<Article> {
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [webFeedID as AnyObject], withLimits: withLimits)
}
// MARK: - Fetching Articles by articleID
@ -82,65 +82,65 @@ final class ArticlesTable: DatabaseTable {
// MARK: - Fetching Unread Articles
func fetchUnreadArticles(_ feedIDs: Set<String>) -> Set<Article> {
return fetchArticles{ self.fetchUnreadArticles(feedIDs, $0) }
func fetchUnreadArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
return fetchArticles{ self.fetchUnreadArticles(webFeedIDs, $0) }
}
func fetchUnreadArticlesAsync(_ feedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
fetchArticlesAsync({ self.fetchUnreadArticles(feedIDs, $0) }, callback)
func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
fetchArticlesAsync({ self.fetchUnreadArticles(webFeedIDs, $0) }, callback)
}
private func fetchUnreadArticles(_ feedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
private func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0
if feedIDs.isEmpty {
if webFeedIDs.isEmpty {
return Set<Article>()
}
let parameters = feedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
let parameters = webFeedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let whereClause = "feedID in \(placeholders) and read=0"
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: true)
}
// MARK: - Fetching Today Articles
func fetchArticlesSince(_ feedIDs: Set<String>, _ cutoffDate: Date) -> Set<Article> {
return fetchArticles{ self.fetchArticlesSince(feedIDs, cutoffDate, $0) }
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date) -> Set<Article> {
return fetchArticles{ self.fetchArticlesSince(webFeedIDs, cutoffDate, $0) }
}
func fetchArticlesSinceAsync(_ feedIDs: Set<String>, _ cutoffDate: Date, _ callback: @escaping ArticleSetBlock) {
fetchArticlesAsync({ self.fetchArticlesSince(feedIDs, cutoffDate, $0) }, callback)
func fetchArticlesSinceAsync(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ callback: @escaping ArticleSetBlock) {
fetchArticlesAsync({ self.fetchArticlesSince(webFeedIDs, cutoffDate, $0) }, callback)
}
private func fetchArticlesSince(_ feedIDs: Set<String>, _ cutoffDate: Date, _ database: FMDatabase) -> Set<Article> {
private func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ database: FMDatabase) -> Set<Article> {
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and (datePublished > ? || (datePublished is null and dateArrived > ?)
//
// datePublished may be nil, so we fall back to dateArrived.
if feedIDs.isEmpty {
if webFeedIDs.isEmpty {
return Set<Article>()
}
let parameters = feedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject]
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
let parameters = webFeedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject]
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let whereClause = "feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?)) and userDeleted = 0"
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false)
}
// MARK: - Fetching Starred Articles
func fetchStarredArticles(_ feedIDs: Set<String>) -> Set<Article> {
return fetchArticles{ self.fetchStarredArticles(feedIDs, $0) }
func fetchStarredArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
return fetchArticles{ self.fetchStarredArticles(webFeedIDs, $0) }
}
func fetchStarredArticlesAsync(_ feedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
fetchArticlesAsync({ self.fetchStarredArticles(feedIDs, $0) }, callback)
func fetchStarredArticlesAsync(_ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
fetchArticlesAsync({ self.fetchStarredArticles(webFeedIDs, $0) }, callback)
}
private func fetchStarredArticles(_ feedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
private func fetchStarredArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred = 1 and userDeleted = 0;
if feedIDs.isEmpty {
if webFeedIDs.isEmpty {
return Set<Article>()
}
let parameters = feedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
let parameters = webFeedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let whereClause = "feedID in \(placeholders) and starred = 1 and userDeleted = 0"
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false)
}
@ -155,9 +155,9 @@ final class ArticlesTable: DatabaseTable {
return articles
}
func fetchArticlesMatching(_ searchString: String, _ feedIDs: Set<String>) -> Set<Article> {
func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) -> Set<Article> {
var articles = fetchArticlesMatching(searchString)
articles = articles.filter{ feedIDs.contains($0.feedID) }
articles = articles.filter{ webFeedIDs.contains($0.webFeedID) }
return articles
}
@ -167,18 +167,18 @@ final class ArticlesTable: DatabaseTable {
return articles
}
func fetchArticlesMatchingAsync(_ searchString: String, _ feedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
fetchArticlesAsync({ self.fetchArticlesMatching(searchString, feedIDs, $0) }, callback)
func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
fetchArticlesAsync({ self.fetchArticlesMatching(searchString, webFeedIDs, $0) }, callback)
}
func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
fetchArticlesAsync({ self.fetchArticlesMatchingWithArticleIDs(searchString, articleIDs, $0) }, callback)
}
private func fetchArticlesMatching(_ searchString: String, _ feedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
private func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
let articles = fetchArticlesMatching(searchString, database)
// TODO: include the feedIDs in the SQL rather than filtering here.
return articles.filter{ feedIDs.contains($0.feedID) }
return articles.filter{ webFeedIDs.contains($0.webFeedID) }
}
private func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
@ -216,8 +216,8 @@ final class ArticlesTable: DatabaseTable {
// MARK: - Updating
func update(_ feedIDsAndItems: [String: Set<ParsedItem>], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
if feedIDsAndItems.isEmpty {
func update(_ webFeedIDsAndItems: [String: Set<ParsedItem>], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
if webFeedIDsAndItems.isEmpty {
completion(nil, nil)
return
}
@ -232,7 +232,7 @@ final class ArticlesTable: DatabaseTable {
// 8. Update search index.
var articleIDs = Set<String>()
for (_, parsedItems) in feedIDsAndItems {
for (_, parsedItems) in webFeedIDsAndItems {
articleIDs.formUnion(parsedItems.articleIDs())
}
@ -240,7 +240,7 @@ final class ArticlesTable: DatabaseTable {
let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1
assert(statusesDictionary.count == articleIDs.count)
let allIncomingArticles = Article.articlesWithFeedIDsAndItems(feedIDsAndItems, self.accountID, statusesDictionary) //2
let allIncomingArticles = Article.articlesWithWebFeedIDsAndItems(webFeedIDsAndItems, self.accountID, statusesDictionary) //2
if allIncomingArticles.isEmpty {
self.callUpdateArticlesCompletionBlock(nil, nil, completion)
return
@ -292,8 +292,8 @@ final class ArticlesTable: DatabaseTable {
// MARK: - Unread Counts
func fetchUnreadCounts(_ feedIDs: Set<String>, _ completion: @escaping UnreadCountCompletionBlock) {
if feedIDs.isEmpty {
func fetchUnreadCounts(_ webFeedIDs: Set<String>, _ completion: @escaping UnreadCountCompletionBlock) {
if webFeedIDs.isEmpty {
completion(UnreadCountDictionary())
return
}
@ -301,8 +301,8 @@ final class ArticlesTable: DatabaseTable {
var unreadCountDictionary = UnreadCountDictionary()
queue.fetch { (database) in
for feedID in feedIDs {
unreadCountDictionary[feedID] = self.fetchUnreadCount(feedID, database)
for webFeedID in webFeedIDs {
unreadCountDictionary[webFeedID] = self.fetchUnreadCount(webFeedID, database)
}
DispatchQueue.main.async {
@ -311,20 +311,20 @@ final class ArticlesTable: DatabaseTable {
}
}
func fetchUnreadCount(_ feedIDs: Set<String>, _ since: Date, _ callback: @escaping (Int) -> Void) {
func fetchUnreadCount(_ webFeedIDs: Set<String>, _ since: Date, _ callback: @escaping (Int) -> Void) {
// Get unread count for today, for instance.
if feedIDs.isEmpty {
if webFeedIDs.isEmpty {
callback(0)
return
}
queue.fetch { (database) in
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let sql = "select count(*) from articles natural join statuses where feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?)) and read=0 and userDeleted=0;"
var parameters = [Any]()
parameters += Array(feedIDs) as [Any]
parameters += Array(webFeedIDs) as [Any]
parameters += [since] as [Any]
parameters += [since] as [Any]
@ -354,8 +354,8 @@ final class ArticlesTable: DatabaseTable {
var d = UnreadCountDictionary()
while resultSet.next() {
let unreadCount = resultSet.long(forColumnIndex: 1)
if let feedID = resultSet.string(forColumnIndex: 0) {
d[feedID] = unreadCount
if let webFeedID = resultSet.string(forColumnIndex: 0) {
d[webFeedID] = unreadCount
}
}
@ -365,16 +365,16 @@ final class ArticlesTable: DatabaseTable {
}
}
func fetchStarredAndUnreadCount(_ feedIDs: Set<String>, _ callback: @escaping (Int) -> Void) {
if feedIDs.isEmpty {
func fetchStarredAndUnreadCount(_ webFeedIDs: Set<String>, _ callback: @escaping (Int) -> Void) {
if webFeedIDs.isEmpty {
callback(0)
return
}
queue.fetch { (database) in
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let sql = "select count(*) from articles natural join statuses where feedID in \(placeholders) and read=0 and starred=1 and userDeleted=0;"
let parameters = Array(feedIDs) as [Any]
let parameters = Array(webFeedIDs) as [Any]
let unreadCount = self.numberWithSQLAndParameters(sql, parameters, in: database)
@ -439,14 +439,14 @@ final class ArticlesTable: DatabaseTable {
/// Delete articles from feeds that are no longer in the current set of subscribed-to feeds.
/// This deletes from the articles and articleStatuses tables,
/// and, via a trigger, it also deletes from the search index.
func deleteArticlesNotInSubscribedToFeedIDs(_ feedIDs: Set<String>) {
if feedIDs.isEmpty {
func deleteArticlesNotInSubscribedToFeedIDs(_ webFeedIDs: Set<String>) {
if webFeedIDs.isEmpty {
return
}
queue.run { (database) in
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let sql = "select articleID from articles where feedID not in \(placeholders);"
let parameters = Array(feedIDs) as [Any]
let parameters = Array(webFeedIDs) as [Any]
guard let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) else {
return
}
@ -540,7 +540,7 @@ private extension ArticlesTable {
assertionFailure("Expected status.")
return nil
}
guard let feedID = row.string(forColumn: DatabaseKey.feedID) else {
guard let webFeedID = row.string(forColumn: DatabaseKey.feedID) else {
assertionFailure("Expected feedID.")
return nil
}
@ -560,7 +560,7 @@ private extension ArticlesTable {
let datePublished = row.date(forColumn: DatabaseKey.datePublished)
let dateModified = row.date(forColumn: DatabaseKey.dateModified)
let databaseArticle = DatabaseArticle(articleID: articleID, feedID: feedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, status: status)
let databaseArticle = DatabaseArticle(articleID: articleID, webFeedID: webFeedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, status: status)
databaseArticlesCache[articleID] = databaseArticle
return databaseArticle
}
@ -583,14 +583,14 @@ private extension ArticlesTable {
}
}
func fetchUnreadCount(_ feedID: String, _ database: FMDatabase) -> Int {
func fetchUnreadCount(_ webFeedID: String, _ database: FMDatabase) -> Int {
// Count only the articles that would appear in the UI.
// * Must be unread.
// * Must not be deleted.
// * Must be either 1) starred or 2) dateArrived must be newer than cutoff date.
let sql = "select count(*) from articles natural join statuses where feedID=? and read=0 and userDeleted=0 and (starred=1 or dateArrived>?);"
return numberWithSQLAndParameters(sql, [feedID, articleCutoffDate], in: database)
return numberWithSQLAndParameters(sql, [webFeedID, articleCutoffDate], in: database)
}
func fetchArticlesMatching(_ searchString: String, _ database: FMDatabase) -> Set<Article> {

View File

@ -15,7 +15,7 @@ import Articles
struct DatabaseArticle: Hashable {
let articleID: String
let feedID: String
let webFeedID: String
let uniqueID: String
let title: String?
let contentHTML: String?

View File

@ -14,10 +14,10 @@ import RSParser
extension Article {
init(databaseArticle: DatabaseArticle, accountID: String, authors: Set<Author>?, attachments: Set<Attachment>?) {
self.init(accountID: accountID, articleID: databaseArticle.articleID, feedID: databaseArticle.feedID, uniqueID: databaseArticle.uniqueID, title: databaseArticle.title, contentHTML: databaseArticle.contentHTML, contentText: databaseArticle.contentText, url: databaseArticle.url, externalURL: databaseArticle.externalURL, summary: databaseArticle.summary, imageURL: databaseArticle.imageURL, bannerImageURL: databaseArticle.bannerImageURL, datePublished: databaseArticle.datePublished, dateModified: databaseArticle.dateModified, authors: authors, attachments: attachments, status: databaseArticle.status)
self.init(accountID: accountID, articleID: databaseArticle.articleID, webFeedID: databaseArticle.webFeedID, uniqueID: databaseArticle.uniqueID, title: databaseArticle.title, contentHTML: databaseArticle.contentHTML, contentText: databaseArticle.contentText, url: databaseArticle.url, externalURL: databaseArticle.externalURL, summary: databaseArticle.summary, imageURL: databaseArticle.imageURL, bannerImageURL: databaseArticle.bannerImageURL, datePublished: databaseArticle.datePublished, dateModified: databaseArticle.dateModified, authors: authors, attachments: attachments, status: databaseArticle.status)
}
init(parsedItem: ParsedItem, maximumDateAllowed: Date, accountID: String, feedID: String, status: ArticleStatus) {
init(parsedItem: ParsedItem, maximumDateAllowed: Date, accountID: String, webFeedID: String, status: ArticleStatus) {
let authors = Author.authorsWithParsedAuthors(parsedItem.authors)
let attachments = Attachment.attachmentsWithParsedAttachments(parsedItem.attachments)
@ -35,7 +35,7 @@ extension Article {
dateModified = nil
}
self.init(accountID: accountID, articleID: parsedItem.syncServiceID, feedID: feedID, uniqueID: parsedItem.uniqueID, title: parsedItem.title, contentHTML: parsedItem.contentHTML, contentText: parsedItem.contentText, url: parsedItem.url, externalURL: parsedItem.externalURL, summary: parsedItem.summary, imageURL: parsedItem.imageURL, bannerImageURL: parsedItem.bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, attachments: attachments, status: status)
self.init(accountID: accountID, articleID: parsedItem.syncServiceID, webFeedID: webFeedID, uniqueID: parsedItem.uniqueID, title: parsedItem.title, contentHTML: parsedItem.contentHTML, contentText: parsedItem.contentText, url: parsedItem.url, externalURL: parsedItem.externalURL, summary: parsedItem.summary, imageURL: parsedItem.imageURL, bannerImageURL: parsedItem.bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, attachments: attachments, status: status)
}
private func addPossibleStringChangeWithKeyPath(_ comparisonKeyPath: KeyPath<Article,String?>, _ otherArticle: Article, _ key: String, _ dictionary: inout DatabaseDictionary) {
@ -84,11 +84,11 @@ extension Article {
// return Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) })
// }
static func articlesWithFeedIDsAndItems(_ feedIDsAndItems: [String: Set<ParsedItem>], _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
static func articlesWithWebFeedIDsAndItems(_ webFeedIDsAndItems: [String: Set<ParsedItem>], _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
let maximumDateAllowed = Date().addingTimeInterval(60 * 60 * 24) // Allow dates up to about 24 hours ahead of now
var articles = Set<Article>()
for (feedID, parsedItems) in feedIDsAndItems {
let feedArticles = Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) })
for (webFeedID, parsedItems) in webFeedIDsAndItems {
let feedArticles = Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, webFeedID: webFeedID, status: statusesDictionary[$0.articleID]!) })
articles.formUnion(feedArticles)
}
return articles
@ -101,7 +101,7 @@ extension Article: DatabaseObject {
var d = DatabaseDictionary()
d[DatabaseKey.articleID] = articleID
d[DatabaseKey.feedID] = feedID
d[DatabaseKey.feedID] = webFeedID
d[DatabaseKey.uniqueID] = uniqueID
if let title = title {

View File

@ -17,6 +17,6 @@ extension ParsedItem {
return s
}
// Must be same calculation as for Article.
return Article.calculatedArticleID(feedID: feedURL, uniqueID: uniqueID)
return Article.calculatedArticleID(webFeedID: feedURL, uniqueID: uniqueID)
}
}

View File

@ -33,7 +33,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
var faviconDownloader: FaviconDownloader!
var imageDownloader: ImageDownloader!
var authorAvatarDownloader: AuthorAvatarDownloader!
var feedIconDownloader: FeedIconDownloader!
var webFeedIconDownloader: WebFeedIconDownloader!
var appName: String!
var refreshTimer: AccountRefreshTimer?
@ -186,7 +186,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
imageDownloader = ImageDownloader(folder: imagesFolder)
authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader)
feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder)
webFeedIconDownloader = WebFeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder)
updateSortMenuItems()
updateGroupByFeedMenuItem()
@ -195,7 +195,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
mainWindowController?.window?.center()
}
NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
DispatchQueue.main.async {
@ -299,12 +299,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
}
}
@objc func feedSettingDidChange(_ note: Notification) {
@objc func webFeedSettingDidChange(_ note: Notification) {
guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else {
guard let feed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else {
return
}
if key == Feed.FeedSettingKey.homePageURL || key == Feed.FeedSettingKey.faviconURL {
if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL {
let _ = faviconDownloader.favicon(for: feed)
}
}

View File

@ -31,7 +31,7 @@
<!--Feed-->
<scene sceneID="vUh-Rc-fPi">
<objects>
<viewController title="Feed" storyboardIdentifier="Feed" showSeguePresentationStyle="single" id="sfH-oR-GXm" customClass="FeedInspectorViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<viewController title="Feed" storyboardIdentifier="Feed" showSeguePresentationStyle="single" id="sfH-oR-GXm" customClass="WebFeedInspectorViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="ecA-UY-KEd">
<rect key="frame" x="0.0" y="0.0" width="256" height="332"/>
<autoresizingMask key="autoresizingMask"/>

View File

@ -10,7 +10,7 @@ import AppKit
import Articles
import Account
final class FeedInspectorViewController: NSViewController, Inspector {
final class WebFeedInspectorViewController: NSViewController, Inspector {
@IBOutlet weak var iconView: IconView!
@IBOutlet weak var nameTextField: NSTextField?
@ -19,7 +19,7 @@ final class FeedInspectorViewController: NSViewController, Inspector {
@IBOutlet weak var isNotifyAboutNewArticlesCheckBox: NSButton!
@IBOutlet weak var isReaderViewAlwaysOnCheckBox: NSButton?
private var feed: Feed? {
private var feed: WebFeed? {
didSet {
if feed != oldValue {
updateUI()
@ -37,7 +37,7 @@ final class FeedInspectorViewController: NSViewController, Inspector {
}
func canInspect(_ objects: [Any]) -> Bool {
return objects.count == 1 && objects.first is Feed
return objects.count == 1 && objects.first is WebFeed
}
// MARK: NSViewController
@ -64,7 +64,7 @@ final class FeedInspectorViewController: NSViewController, Inspector {
}
extension FeedInspectorViewController: NSTextFieldDelegate {
extension WebFeedInspectorViewController: NSTextFieldDelegate {
func controlTextDidChange(_ note: Notification) {
guard let feed = feed, let nameTextField = nameTextField else {
@ -75,10 +75,10 @@ extension FeedInspectorViewController: NSTextFieldDelegate {
}
private extension FeedInspectorViewController {
private extension WebFeedInspectorViewController {
func updateFeed() {
guard let objects = objects, objects.count == 1, let singleFeed = objects.first as? Feed else {
guard let objects = objects, objects.count == 1, let singleFeed = objects.first as? WebFeed else {
feed = nil
return
}
@ -101,7 +101,7 @@ private extension FeedInspectorViewController {
return
}
if let feedIcon = appDelegate.feedIconDownloader.icon(for: feed) {
if let feedIcon = appDelegate.webFeedIconDownloader.icon(for: feed) {
iconView.iconImage = feedIcon
return
}

View File

@ -53,14 +53,14 @@ class AddFeedController: AddFeedWindowControllerDelegate {
}
let account = accountAndFolderSpecifier.account
if account.hasFeed(withURL: url.absoluteString) {
if account.hasWebFeed(withURL: url.absoluteString) {
showAlreadySubscribedError(url.absoluteString)
return
}
BatchUpdate.shared.start()
account.createFeed(url: url.absoluteString, name: title, container: container) { result in
account.createWebFeed(url: url.absoluteString, name: title, container: container) { result in
DispatchQueue.main.async {
self.endShowingProgress()
@ -70,7 +70,7 @@ class AddFeedController: AddFeedWindowControllerDelegate {
switch result {
case .success(let feed):
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed])
case .failure(let error):
switch error {
case AccountError.createErrorAlreadySubscribed:

View File

@ -172,7 +172,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
}
}
if let feed = currentFeedOrFolder as? Feed, let noteObject = noteObject as? Feed {
if let feed = currentFeedOrFolder as? WebFeed, let noteObject = noteObject as? WebFeed {
if feed == noteObject {
updateWindowTitle()
return
@ -482,7 +482,7 @@ extension MainWindowController: TimelineContainerViewControllerDelegate {
if let articles = articles {
if articles.count == 1 {
activityManager.reading(fetcher: nil, article: articles.first)
if articles.first?.feed?.isArticleExtractorAlwaysOn ?? false {
if articles.first?.webFeed?.isArticleExtractorAlwaysOn ?? false {
detailState = .loading
startArticleExtractorForCurrentLink()
} else {

View File

@ -52,7 +52,7 @@ struct PasteboardFolder: Hashable {
}
if let foundType = pasteboardType {
if let folderDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardFeedDictionary {
if let folderDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardWebFeedDictionary {
self.init(dictionary: folderDictionary)
return
}
@ -72,7 +72,7 @@ struct PasteboardFolder: Hashable {
// MARK: - Writing
func internalDictionary() -> PasteboardFolderDictionary {
var d = PasteboardFeedDictionary()
var d = PasteboardWebFeedDictionary()
d[PasteboardFolder.Key.name] = name
if let folderID = folderID {
d[PasteboardFolder.Key.folderID] = folderID
@ -131,7 +131,7 @@ private extension FolderPasteboardWriter {
return PasteboardFolder(name: folder.name ?? "", folderID: String(folder.folderID), accountID: folder.account?.accountID)
}
var internalDictionary: PasteboardFeedDictionary {
var internalDictionary: PasteboardWebFeedDictionary {
return pasteboardFolder.internalDictionary()
}
}

View File

@ -11,9 +11,9 @@ import Articles
import Account
import RSCore
typealias PasteboardFeedDictionary = [String: String]
typealias PasteboardWebFeedDictionary = [String: String]
struct PasteboardFeed: Hashable {
struct PasteboardWebFeed: Hashable {
private struct Key {
static let url = "URL"
@ -23,12 +23,12 @@ struct PasteboardFeed: Hashable {
// Internal
static let accountID = "accountID"
static let accountType = "accountType"
static let feedID = "feedID"
static let webFeedID = "webFeedID"
static let editedName = "editedName"
}
let url: String
let feedID: String?
let webFeedID: String?
let homePageURL: String?
let name: String?
let editedName: String?
@ -36,9 +36,9 @@ struct PasteboardFeed: Hashable {
let accountType: AccountType?
let isLocalFeed: Bool
init(url: String, feedID: String?, homePageURL: String?, name: String?, editedName: String?, accountID: String?, accountType: AccountType?) {
init(url: String, webFeedID: String?, homePageURL: String?, name: String?, editedName: String?, accountID: String?, accountType: AccountType?) {
self.url = url.rs_normalizedURL()
self.feedID = feedID
self.webFeedID = webFeedID
self.homePageURL = homePageURL?.rs_normalizedURL()
self.name = name
self.editedName = editedName
@ -49,7 +49,7 @@ struct PasteboardFeed: Hashable {
// MARK: - Reading
init?(dictionary: PasteboardFeedDictionary) {
init?(dictionary: PasteboardWebFeedDictionary) {
guard let url = dictionary[Key.url] else {
return nil
}
@ -57,7 +57,7 @@ struct PasteboardFeed: Hashable {
let homePageURL = dictionary[Key.homePageURL]
let name = dictionary[Key.name]
let accountID = dictionary[Key.accountID]
let feedID = dictionary[Key.feedID]
let webFeedID = dictionary[Key.webFeedID]
let editedName = dictionary[Key.editedName]
var accountType: AccountType? = nil
@ -65,19 +65,19 @@ struct PasteboardFeed: Hashable {
accountType = AccountType(rawValue: accountTypeInt)
}
self.init(url: url, feedID: feedID, homePageURL: homePageURL, name: name, editedName: editedName, accountID: accountID, accountType: accountType)
self.init(url: url, webFeedID: webFeedID, homePageURL: homePageURL, name: name, editedName: editedName, accountID: accountID, accountType: accountType)
}
init?(pasteboardItem: NSPasteboardItem) {
var pasteboardType: NSPasteboard.PasteboardType?
if pasteboardItem.types.contains(FeedPasteboardWriter.feedUTIInternalType) {
pasteboardType = FeedPasteboardWriter.feedUTIInternalType
if pasteboardItem.types.contains(WebFeedPasteboardWriter.webFeedUTIInternalType) {
pasteboardType = WebFeedPasteboardWriter.webFeedUTIInternalType
}
else if pasteboardItem.types.contains(FeedPasteboardWriter.feedUTIType) {
pasteboardType = FeedPasteboardWriter.feedUTIType
else if pasteboardItem.types.contains(WebFeedPasteboardWriter.webFeedUTIType) {
pasteboardType = WebFeedPasteboardWriter.webFeedUTIType
}
if let foundType = pasteboardType {
if let feedDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardFeedDictionary {
if let feedDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardWebFeedDictionary {
self.init(dictionary: feedDictionary)
return
}
@ -94,7 +94,7 @@ struct PasteboardFeed: Hashable {
if let foundType = pasteboardType {
if let possibleURLString = pasteboardItem.string(forType: foundType) {
if possibleURLString.rs_stringMayBeURL() {
self.init(url: possibleURLString, feedID: nil, homePageURL: nil, name: nil, editedName: nil, accountID: nil, accountType: nil)
self.init(url: possibleURLString, webFeedID: nil, homePageURL: nil, name: nil, editedName: nil, accountID: nil, accountType: nil)
return
}
}
@ -103,18 +103,18 @@ struct PasteboardFeed: Hashable {
return nil
}
static func pasteboardFeeds(with pasteboard: NSPasteboard) -> Set<PasteboardFeed>? {
static func pasteboardFeeds(with pasteboard: NSPasteboard) -> Set<PasteboardWebFeed>? {
guard let items = pasteboard.pasteboardItems else {
return nil
}
let feeds = items.compactMap { PasteboardFeed(pasteboardItem: $0) }
return feeds.isEmpty ? nil : Set(feeds)
let webFeeds = items.compactMap { PasteboardWebFeed(pasteboardItem: $0) }
return webFeeds.isEmpty ? nil : Set(webFeeds)
}
// MARK: - Writing
func exportDictionary() -> PasteboardFeedDictionary {
var d = PasteboardFeedDictionary()
func exportDictionary() -> PasteboardWebFeedDictionary {
var d = PasteboardWebFeedDictionary()
d[Key.url] = url
d[Key.homePageURL] = homePageURL ?? ""
if let nameForDisplay = editedName ?? name {
@ -123,54 +123,54 @@ struct PasteboardFeed: Hashable {
return d
}
func internalDictionary() -> PasteboardFeedDictionary {
var d = PasteboardFeedDictionary()
d[PasteboardFeed.Key.feedID] = feedID
d[PasteboardFeed.Key.url] = url
func internalDictionary() -> PasteboardWebFeedDictionary {
var d = PasteboardWebFeedDictionary()
d[PasteboardWebFeed.Key.webFeedID] = webFeedID
d[PasteboardWebFeed.Key.url] = url
if let homePageURL = homePageURL {
d[PasteboardFeed.Key.homePageURL] = homePageURL
d[PasteboardWebFeed.Key.homePageURL] = homePageURL
}
if let name = name {
d[PasteboardFeed.Key.name] = name
d[PasteboardWebFeed.Key.name] = name
}
if let editedName = editedName {
d[PasteboardFeed.Key.editedName] = editedName
d[PasteboardWebFeed.Key.editedName] = editedName
}
if let accountID = accountID {
d[PasteboardFeed.Key.accountID] = accountID
d[PasteboardWebFeed.Key.accountID] = accountID
}
if let accountType = accountType {
d[PasteboardFeed.Key.accountType] = String(accountType.rawValue)
d[PasteboardWebFeed.Key.accountType] = String(accountType.rawValue)
}
return d
}
}
extension Feed: PasteboardWriterOwner {
extension WebFeed: PasteboardWriterOwner {
public var pasteboardWriter: NSPasteboardWriting {
return FeedPasteboardWriter(feed: self)
return WebFeedPasteboardWriter(webFeed: self)
}
}
@objc final class FeedPasteboardWriter: NSObject, NSPasteboardWriting {
@objc final class WebFeedPasteboardWriter: NSObject, NSPasteboardWriting {
private let feed: Feed
static let feedUTI = "com.ranchero.feed"
static let feedUTIType = NSPasteboard.PasteboardType(rawValue: feedUTI)
static let feedUTIInternal = "com.ranchero.NetNewsWire-Evergreen.internal.feed"
static let feedUTIInternalType = NSPasteboard.PasteboardType(rawValue: feedUTIInternal)
private let webFeed: WebFeed
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(feed: Feed) {
self.feed = feed
init(webFeed: WebFeed) {
self.webFeed = webFeed
}
// MARK: - NSPasteboardWriting
func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] {
return [FeedPasteboardWriter.feedUTIType, .URL, .string, FeedPasteboardWriter.feedUTIInternalType]
return [WebFeedPasteboardWriter.webFeedUTIType, .URL, .string, WebFeedPasteboardWriter.webFeedUTIInternalType]
}
func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
@ -179,12 +179,12 @@ extension Feed: PasteboardWriterOwner {
switch type {
case .string:
plist = feed.nameForDisplay
plist = webFeed.nameForDisplay
case .URL:
plist = feed.url
case FeedPasteboardWriter.feedUTIType:
plist = webFeed.url
case WebFeedPasteboardWriter.webFeedUTIType:
plist = exportDictionary
case FeedPasteboardWriter.feedUTIInternalType:
case WebFeedPasteboardWriter.webFeedUTIInternalType:
plist = internalDictionary
default:
plist = nil
@ -194,17 +194,17 @@ extension Feed: PasteboardWriterOwner {
}
}
private extension FeedPasteboardWriter {
private extension WebFeedPasteboardWriter {
var pasteboardFeed: PasteboardFeed {
return PasteboardFeed(url: feed.url, feedID: feed.feedID, homePageURL: feed.homePageURL, name: feed.name, editedName: feed.editedName, accountID: feed.account?.accountID, accountType: feed.account?.type)
var pasteboardFeed: PasteboardWebFeed {
return PasteboardWebFeed(url: webFeed.url, webFeedID: webFeed.webFeedID, homePageURL: webFeed.homePageURL, name: webFeed.name, editedName: webFeed.editedName, accountID: webFeed.account?.accountID, accountType: webFeed.account?.type)
}
var exportDictionary: PasteboardFeedDictionary {
var exportDictionary: PasteboardWebFeedDictionary {
return pasteboardFeed.exportDictionary()
}
var internalDictionary: PasteboardFeedDictionary {
var internalDictionary: PasteboardWebFeedDictionary {
return pasteboardFeed.internalDictionary()
}
}

View File

@ -55,7 +55,7 @@ import Account
func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {
let draggedFolders = PasteboardFolder.pasteboardFolders(with: info.draggingPasteboard)
let draggedFeeds = PasteboardFeed.pasteboardFeeds(with: info.draggingPasteboard)
let draggedFeeds = PasteboardWebFeed.pasteboardFeeds(with: info.draggingPasteboard)
if (draggedFolders == nil && draggedFeeds == nil) || (draggedFolders != nil && draggedFeeds != nil) {
return SidebarOutlineDataSource.dragOperationNone
}
@ -91,7 +91,7 @@ import Account
func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
let draggedFolders = PasteboardFolder.pasteboardFolders(with: info.draggingPasteboard)
let draggedFeeds = PasteboardFeed.pasteboardFeeds(with: info.draggingPasteboard)
let draggedFeeds = PasteboardWebFeed.pasteboardFeeds(with: info.draggingPasteboard)
if (draggedFolders == nil && draggedFeeds == nil) || (draggedFolders != nil && draggedFeeds != nil) {
return false
}
@ -136,7 +136,7 @@ private extension SidebarOutlineDataSource {
// Dont 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 Feed
return node.representedObject is Folder || node.representedObject is WebFeed
}
// MARK: - Drag and Drop
@ -145,7 +145,7 @@ private extension SidebarOutlineDataSource {
case empty, singleLocal, singleNonLocal, multipleLocal, multipleNonLocal, mixed
}
func draggedFeedContentsType(_ draggedFeeds: Set<PasteboardFeed>) -> DraggedFeedsContentsType {
func draggedFeedContentsType(_ draggedFeeds: Set<PasteboardWebFeed>) -> DraggedFeedsContentsType {
if draggedFeeds.isEmpty {
return .empty
}
@ -173,14 +173,14 @@ private extension SidebarOutlineDataSource {
return .multipleNonLocal
}
func singleNonLocalFeed(from feeds: Set<PasteboardFeed>) -> PasteboardFeed? {
func singleNonLocalFeed(from feeds: Set<PasteboardWebFeed>) -> PasteboardWebFeed? {
guard feeds.count == 1, let feed = feeds.first else {
return nil
}
return feed.isLocalFeed ? nil : feed
}
func validateSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardFeed, _ parentNode: Node, _ index: Int) -> NSDragOperation {
func validateSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardWebFeed, _ parentNode: Node, _ index: Int) -> NSDragOperation {
// A non-local feed should always drag on to an Account or Folder node, with NSOutlineViewDropOnItemIndex since we dont know where it would sort till we read the feed.
guard let dropTargetNode = ancestorThatCanAcceptNonLocalFeed(parentNode) else {
return SidebarOutlineDataSource.dragOperationNone
@ -191,7 +191,7 @@ private extension SidebarOutlineDataSource {
return .copy
}
func validateSingleLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardFeed, _ parentNode: Node, _ index: Int) -> NSDragOperation {
func validateSingleLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardWebFeed, _ parentNode: Node, _ index: Int) -> NSDragOperation {
// A local feed should always drag on to an Account or Folder node, and we can provide an index.
guard let dropTargetNode = ancestorThatCanAcceptLocalFeed(parentNode) else {
return SidebarOutlineDataSource.dragOperationNone
@ -212,7 +212,7 @@ private extension SidebarOutlineDataSource {
return localDragOperation()
}
func validateLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set<PasteboardFeed>, _ parentNode: Node, _ index: Int) -> NSDragOperation {
func validateLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set<PasteboardWebFeed>, _ parentNode: Node, _ index: Int) -> NSDragOperation {
// Local feeds should always drag on to an Account or Folder node, and index should be NSOutlineViewDropOnItemIndex since we cant provide multiple indexes.
guard let dropTargetNode = ancestorThatCanAcceptLocalFeed(parentNode) else {
return SidebarOutlineDataSource.dragOperationNone
@ -244,7 +244,7 @@ private extension SidebarOutlineDataSource {
if let folder = node.representedObject as? Folder {
return folder.account
}
if let feed = node.representedObject as? Feed {
if let feed = node.representedObject as? WebFeed {
return feed.account
}
return nil
@ -303,12 +303,12 @@ private extension SidebarOutlineDataSource {
return localDragOperation()
}
func copyFeedInAccount(node: Node, to parentNode: Node) {
guard let feed = node.representedObject as? Feed, let destination = parentNode.representedObject as? Container else {
func copyWebFeedInAccount(node: Node, to parentNode: Node) {
guard let feed = node.representedObject as? WebFeed, let destination = parentNode.representedObject as? Container else {
return
}
destination.account?.addFeed(feed, to: destination) { result in
destination.account?.addWebFeed(feed, to: destination) { result in
switch result {
case .success:
break
@ -318,15 +318,15 @@ private extension SidebarOutlineDataSource {
}
}
func moveFeedInAccount(node: Node, to parentNode: Node) {
guard let feed = node.representedObject as? Feed,
func moveWebFeedInAccount(node: Node, to parentNode: Node) {
guard let feed = node.representedObject as? WebFeed,
let source = node.parent?.representedObject as? Container,
let destination = parentNode.representedObject as? Container else {
return
}
BatchUpdate.shared.start()
source.account?.moveFeed(feed, from: source, to: destination) { result in
source.account?.moveWebFeed(feed, from: source, to: destination) { result in
BatchUpdate.shared.end()
switch result {
case .success:
@ -337,15 +337,15 @@ private extension SidebarOutlineDataSource {
}
}
func copyFeedBetweenAccounts(node: Node, to parentNode: Node) {
guard let feed = node.representedObject as? Feed,
func copyWebFeedBetweenAccounts(node: Node, to parentNode: Node) {
guard let feed = node.representedObject as? WebFeed,
let destinationAccount = nodeAccount(parentNode),
let destinationContainer = parentNode.representedObject as? Container else {
return
}
if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) {
destinationAccount.addFeed(existingFeed, to: destinationContainer) { result in
if let existingFeed = destinationAccount.existingWebFeed(withURL: feed.url) {
destinationAccount.addWebFeed(existingFeed, to: destinationContainer) { result in
switch result {
case .success:
break
@ -354,7 +354,7 @@ private extension SidebarOutlineDataSource {
}
}
} else {
destinationAccount.createFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in
destinationAccount.createWebFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in
switch result {
case .success:
break
@ -365,8 +365,8 @@ private extension SidebarOutlineDataSource {
}
}
func moveFeedBetweenAccounts(node: Node, to parentNode: Node) {
guard let feed = node.representedObject as? Feed,
func moveWebFeedBetweenAccounts(node: Node, to parentNode: Node) {
guard let feed = node.representedObject as? WebFeed,
let sourceAccount = nodeAccount(node),
let sourceContainer = node.parent?.representedObject as? Container,
let destinationAccount = nodeAccount(parentNode),
@ -374,13 +374,13 @@ private extension SidebarOutlineDataSource {
return
}
if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) {
if let existingFeed = destinationAccount.existingWebFeed(withURL: feed.url) {
BatchUpdate.shared.start()
destinationAccount.addFeed(existingFeed, to: destinationContainer) { result in
destinationAccount.addWebFeed(existingFeed, to: destinationContainer) { result in
switch result {
case .success:
sourceAccount.removeFeed(feed, from: sourceContainer) { result in
sourceAccount.removeWebFeed(feed, from: sourceContainer) { result in
BatchUpdate.shared.end()
switch result {
case .success:
@ -398,10 +398,10 @@ private extension SidebarOutlineDataSource {
} else {
BatchUpdate.shared.start()
destinationAccount.createFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in
destinationAccount.createWebFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in
switch result {
case .success:
sourceAccount.removeFeed(feed, from: sourceContainer) { result in
sourceAccount.removeWebFeed(feed, from: sourceContainer) { result in
BatchUpdate.shared.end()
switch result {
case .success:
@ -419,7 +419,7 @@ private extension SidebarOutlineDataSource {
}
}
func acceptLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set<PasteboardFeed>, _ parentNode: Node, _ index: Int) -> Bool {
func acceptLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set<PasteboardWebFeed>, _ parentNode: Node, _ index: Int) -> Bool {
guard let draggedNodes = draggedNodes else {
return false
}
@ -427,15 +427,15 @@ private extension SidebarOutlineDataSource {
draggedNodes.forEach { node in
if sameAccount(node, parentNode) {
if NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false {
copyFeedInAccount(node: node, to: parentNode)
copyWebFeedInAccount(node: node, to: parentNode)
} else {
moveFeedInAccount(node: node, to: parentNode)
moveWebFeedInAccount(node: node, to: parentNode)
}
} else {
if NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false {
copyFeedBetweenAccounts(node: node, to: parentNode)
copyWebFeedBetweenAccounts(node: node, to: parentNode)
} else {
moveFeedBetweenAccounts(node: node, to: parentNode)
moveWebFeedBetweenAccounts(node: node, to: parentNode)
}
}
}
@ -509,10 +509,10 @@ private extension SidebarOutlineDataSource {
switch result {
case .success(let destinationFolder):
let group = DispatchGroup()
for feed in folder.topLevelFeeds {
if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) {
for feed in folder.topLevelWebFeeds {
if let existingFeed = destinationAccount.existingWebFeed(withURL: feed.url) {
group.enter()
destinationAccount.addFeed(existingFeed, to: destinationFolder) { result in
destinationAccount.addWebFeed(existingFeed, to: destinationFolder) { result in
group.leave()
switch result {
case .success:
@ -523,7 +523,7 @@ private extension SidebarOutlineDataSource {
}
} else {
group.enter()
destinationAccount.createFeed(url: feed.url, name: feed.editedName, container: destinationFolder) { result in
destinationAccount.createWebFeed(url: feed.url, name: feed.editedName, container: destinationFolder) { result in
group.leave()
switch result {
case .success:
@ -563,7 +563,7 @@ private extension SidebarOutlineDataSource {
return true
}
func acceptSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardFeed, _ parentNode: Node, _ index: Int) -> Bool {
func acceptSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardWebFeed, _ parentNode: Node, _ index: Int) -> Bool {
guard nodeIsDropTarget(parentNode), index == NSOutlineViewDropOnItemIndex else {
return false
}
@ -580,12 +580,12 @@ private extension SidebarOutlineDataSource {
return true
}
func nodeHasChildRepresentingDraggedFeed(_ parentNode: Node, _ draggedFeed: PasteboardFeed) -> Bool {
func nodeHasChildRepresentingDraggedFeed(_ parentNode: Node, _ draggedFeed: PasteboardWebFeed) -> Bool {
return nodeHasChildRepresentingAnyDraggedFeed(parentNode, Set([draggedFeed]))
}
func nodeRepresentsAnyDraggedFeed(_ node: Node, _ draggedFeeds: Set<PasteboardFeed>) -> Bool {
guard let feed = node.representedObject as? Feed else {
func nodeRepresentsAnyDraggedFeed(_ node: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
guard let feed = node.representedObject as? WebFeed else {
return false
}
for draggedFeed in draggedFeeds {
@ -610,8 +610,8 @@ private extension SidebarOutlineDataSource {
return account
} else if let folder = node.representedObject as? Folder {
return folder.account
} else if let feed = node.representedObject as? Feed {
return feed.account
} else if let webFeed = node.representedObject as? WebFeed {
return webFeed.account
} else {
return nil
}
@ -622,7 +622,7 @@ private extension SidebarOutlineDataSource {
return nodeAccount(node)?.accountID
}
func nodeHasChildRepresentingAnyDraggedFeed(_ parentNode: Node, _ draggedFeeds: Set<PasteboardFeed>) -> Bool {
func nodeHasChildRepresentingAnyDraggedFeed(_ parentNode: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
for node in parentNode.childNodes {
if nodeRepresentsAnyDraggedFeed(node, draggedFeeds) {
return true
@ -631,11 +631,11 @@ private extension SidebarOutlineDataSource {
return false
}
func violatesAccountSpecificBehavior(_ parentNode: Node, _ draggedFeed: PasteboardFeed) -> Bool {
func violatesAccountSpecificBehavior(_ parentNode: Node, _ draggedFeed: PasteboardWebFeed) -> Bool {
return violatesAccountSpecificBehavior(parentNode, Set([draggedFeed]))
}
func violatesAccountSpecificBehavior(_ parentNode: Node, _ draggedFeeds: Set<PasteboardFeed>) -> Bool {
func violatesAccountSpecificBehavior(_ parentNode: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
if violatesDisallowFeedInRootFolder(parentNode) {
return true
}
@ -659,7 +659,7 @@ private extension SidebarOutlineDataSource {
return false
}
func violatesDisallowFeedCopyInRootFolder(_ parentNode: Node, _ draggedFeeds: Set<PasteboardFeed>) -> Bool {
func violatesDisallowFeedCopyInRootFolder(_ parentNode: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
guard let parentAccount = nodeAccount(parentNode), parentAccount.behaviors.contains(.disallowFeedCopyInRootFolder) else {
return false
}
@ -677,7 +677,7 @@ private extension SidebarOutlineDataSource {
return false
}
func indexWhereDraggedFeedWouldAppear(_ parentNode: Node, _ draggedFeed: PasteboardFeed) -> Int {
func indexWhereDraggedFeedWouldAppear(_ parentNode: Node, _ draggedFeed: PasteboardWebFeed) -> Int {
let draggedFeedWrapper = PasteboardFeedObjectWrapper(pasteboardFeed: draggedFeed)
let draggedFeedNode = Node(representedObject: draggedFeedWrapper, parent: nil)
let nodes = parentNode.childNodes + [draggedFeedNode]
@ -706,9 +706,9 @@ final class PasteboardFeedObjectWrapper: DisplayNameProvider {
var nameForDisplay: String {
return pasteboardFeed.editedName ?? pasteboardFeed.name ?? ""
}
let pasteboardFeed: PasteboardFeed
let pasteboardFeed: PasteboardWebFeed
init(pasteboardFeed: PasteboardFeed) {
init(pasteboardFeed: PasteboardWebFeed) {
self.pasteboardFeed = pasteboardFeed
}
}

View File

@ -26,8 +26,8 @@ extension SidebarViewController {
let object = objects.first!
switch object {
case is Feed:
return menuForFeed(object as! Feed)
case is WebFeed:
return menuForWebFeed(object as! WebFeed)
case is Folder:
return menuForFolder(object as! Folder)
case is PseudoFeed:
@ -83,7 +83,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 Feed || object is Folder else {
guard let window = view.window, let menuItem = sender as? NSMenuItem, let object = menuItem.representedObject as? DisplayNameProvider, object is WebFeed || object is Folder else {
return
}
@ -99,7 +99,7 @@ extension SidebarViewController: RenameWindowControllerDelegate {
func renameWindowController(_ windowController: RenameWindowController, didRenameObject object: Any, withNewName name: String) {
if let feed = object as? Feed {
if let feed = object as? WebFeed {
feed.rename(to: name) { result in
switch result {
case .success:
@ -135,32 +135,32 @@ private extension SidebarViewController {
return menu
}
func menuForFeed(_ feed: Feed) -> NSMenu? {
func menuForWebFeed(_ webFeed: WebFeed) -> NSMenu? {
let menu = NSMenu(title: "")
if feed.unreadCount > 0 {
menu.addItem(markAllReadMenuItem([feed]))
if webFeed.unreadCount > 0 {
menu.addItem(markAllReadMenuItem([webFeed]))
menu.addItem(NSMenuItem.separator())
}
if let homePageURL = feed.homePageURL, let _ = URL(string: homePageURL) {
if let homePageURL = webFeed.homePageURL, let _ = URL(string: homePageURL) {
let item = menuItem(NSLocalizedString("Open Home Page", comment: "Command"), #selector(openHomePageFromContextualMenu(_:)), homePageURL)
menu.addItem(item)
menu.addItem(NSMenuItem.separator())
}
let copyFeedURLItem = menuItem(NSLocalizedString("Copy Feed URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), feed.url)
let copyFeedURLItem = menuItem(NSLocalizedString("Copy Feed URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), webFeed.url)
menu.addItem(copyFeedURLItem)
if let homePageURL = feed.homePageURL {
if let homePageURL = webFeed.homePageURL {
let item = menuItem(NSLocalizedString("Copy Home Page URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), homePageURL)
menu.addItem(item)
}
menu.addItem(NSMenuItem.separator())
menu.addItem(renameMenuItem(feed))
menu.addItem(deleteMenuItem([feed]))
menu.addItem(renameMenuItem(webFeed))
menu.addItem(deleteMenuItem([webFeed]))
return menu
}
@ -245,7 +245,7 @@ private extension SidebarViewController {
func objectIsFeedOrFolder(_ object: Any) -> Bool {
return object is Feed || object is Folder
return object is WebFeed || object is Folder
}
func menuItem(_ title: String, _ action: Selector, _ representedObject: Any) -> NSMenuItem {

View File

@ -23,7 +23,7 @@ protocol SidebarDelegate: class {
weak var delegate: SidebarDelegate?
let treeControllerDelegate = FeedTreeControllerDelegate()
let treeControllerDelegate = WebFeedTreeControllerDelegate()
lazy var treeController: TreeController = {
return TreeController(delegate: treeControllerDelegate)
}()
@ -49,7 +49,7 @@ protocol SidebarDelegate: class {
outlineView.dataSource = dataSource
outlineView.doubleAction = #selector(doubleClickedSidebar(_:))
outlineView.setDraggingSourceOperationMask([.move, .copy], forLocal: true)
outlineView.registerForDraggedTypes([FeedPasteboardWriter.feedUTIInternalType, FeedPasteboardWriter.feedUTIType, .URL, .string])
outlineView.registerForDraggedTypes([WebFeedPasteboardWriter.webFeedUTIInternalType, WebFeedPasteboardWriter.webFeedUTIType, .URL, .string])
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(containerChildrenDidChange(_:)), name: .ChildrenDidChange, object: nil)
@ -59,7 +59,7 @@ protocol SidebarDelegate: class {
NotificationCenter.default.addObserver(self, selector: #selector(userDidAddFeed(_:)), name: .UserDidAddFeed, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(batchUpdateDidPerform(_:)), name: .BatchUpdateDidPerform, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(userDidRequestSidebarSelection(_:)), name: .UserDidRequestSidebarSelection, object: nil)
@ -110,7 +110,7 @@ protocol SidebarDelegate: class {
}
@objc func userDidAddFeed(_ notification: Notification) {
guard let feed = notification.userInfo?[UserInfoKey.feed] else {
guard let feed = notification.userInfo?[UserInfoKey.webFeed] else {
return
}
revealAndSelectRepresentedObject(feed as AnyObject)
@ -120,12 +120,12 @@ protocol SidebarDelegate: class {
applyToAvailableCells(configureFavicon)
}
@objc func feedSettingDidChange(_ note: Notification) {
guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else {
@objc func webFeedSettingDidChange(_ note: Notification) {
guard let webFeed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else {
return
}
if key == Feed.FeedSettingKey.homePageURL || key == Feed.FeedSettingKey.faviconURL {
configureCellsForRepresentedObject(feed)
if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL {
configureCellsForRepresentedObject(webFeed)
}
}
@ -140,7 +140,7 @@ protocol SidebarDelegate: class {
}
@objc func userDidRequestSidebarSelection(_ note: Notification) {
guard let feed = note.userInfo?[UserInfoKey.feed] else {
guard let feed = note.userInfo?[UserInfoKey.webFeed] else {
return
}
revealAndSelectRepresentedObject(feed as AnyObject)
@ -370,11 +370,11 @@ private extension SidebarViewController {
return selectedNodes.first!
}
var singleSelectedFeed: Feed? {
var singleSelectedFeed: WebFeed? {
guard let node = singleSelectedNode else {
return nil
}
return node.representedObject as? Feed
return node.representedObject as? WebFeed
}
func rebuildTreeAndReloadDataIfNeeded() {
@ -413,11 +413,11 @@ private extension SidebarViewController {
// For feeds, actually fetch from database.
for object in objects {
if let feed = object as? Feed, let account = feed.account {
if let feed = object as? WebFeed, let account = feed.account {
account.updateUnreadCounts(for: Set([feed]))
}
else if let folder = object as? Folder, let account = folder.account {
account.updateUnreadCounts(for: folder.flattenedFeeds())
account.updateUnreadCounts(for: folder.flattenedWebFeeds())
}
}
}
@ -504,10 +504,10 @@ private extension SidebarViewController {
}
func findFeedNode(_ userInfo: [AnyHashable : Any]?, beginningAt startingNode: Node) -> Node? {
guard let feedID = userInfo?[ArticlePathKey.feedID] as? String else {
guard let webFeedID = userInfo?[ArticlePathKey.webFeedID] as? String else {
return nil
}
if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.feedID == feedID }) {
if let node = startingNode.descendantNode(where: { ($0.representedObject as? WebFeed)?.webFeedID == webFeedID }) {
return node
}
return nil
@ -620,7 +620,7 @@ private extension Node {
if representedObject === object {
return true
}
if let feed1 = object as? Feed, let feed2 = representedObject as? Feed {
if let feed1 = object as? WebFeed, let feed2 = representedObject as? WebFeed {
return feed1 == feed2
}
return false

View File

@ -91,7 +91,7 @@ private extension ArticlePasteboardWriter {
s += "Date: \(article.logicalDatePublished)\n\n"
if let feed = article.feed {
if let feed = article.webFeed {
s += "Feed: \(feed.nameForDisplay)\n"
if let homePageURL = feed.homePageURL {
s += "Home page: \(homePageURL)\n"
@ -106,7 +106,7 @@ private extension ArticlePasteboardWriter {
static let articleID = "articleID" // database ID, unique per account
static let uniqueID = "uniqueID" // unique ID, unique per feed (guid, or possibly calculated)
static let feedURL = "feedURL"
static let feedID = "feedID" // may differ from feedURL if coming from a syncing system
static let webFeedID = "webFeedID" // may differ from feedURL if coming from a syncing system
static let title = "title"
static let contentHTML = "contentHTML"
static let contentText = "contentText"
@ -147,11 +147,11 @@ private extension ArticlePasteboardWriter {
d[Key.articleID] = article.articleID
d[Key.uniqueID] = article.uniqueID
if let feed = article.feed {
if let feed = article.webFeed {
d[Key.feedURL] = feed.url
}
d[Key.feedID] = article.feedID
d[Key.webFeedID] = article.webFeedID
d[Key.title] = article.title ?? nil
d[Key.contentHTML] = article.contentHTML ?? nil
d[Key.contentText] = article.contentText ?? nil

View File

@ -74,12 +74,12 @@ extension TimelineViewController {
@objc func selectFeedInSidebarFromContextualMenu(_ sender: Any?) {
guard let menuItem = sender as? NSMenuItem, let feed = menuItem.representedObject as? Feed else {
guard let menuItem = sender as? NSMenuItem, let feed = menuItem.representedObject as? WebFeed else {
return
}
var userInfo = UserInfoDictionary()
userInfo[UserInfoKey.feed] = feed
userInfo[UserInfoKey.webFeed] = feed
NotificationCenter.default.post(name: .UserDidRequestSidebarSelection, object: self, userInfo: userInfo)
@ -174,7 +174,7 @@ private extension TimelineViewController {
menu.addSeparatorIfNeeded()
if articles.count == 1, let feed = articles.first!.feed {
if articles.count == 1, let feed = articles.first!.webFeed {
menu.addItem(selectFeedInSidebarMenuItem(feed))
if let markAllMenuItem = markAllAsReadMenuItem(feed) {
menu.addItem(markAllMenuItem)
@ -247,13 +247,13 @@ private extension TimelineViewController {
return menuItem(NSLocalizedString("Mark Older as Read", comment: "Command"), #selector(markOlderArticlesReadFromContextualMenu(_:)), articles)
}
func selectFeedInSidebarMenuItem(_ feed: Feed) -> NSMenuItem {
func selectFeedInSidebarMenuItem(_ feed: WebFeed) -> 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: Feed) -> NSMenuItem? {
func markAllAsReadMenuItem(_ feed: WebFeed) -> NSMenuItem? {
let articles = Array(feed.fetchArticles())
guard articles.canMarkAllAsRead() else {

View File

@ -25,7 +25,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
if !representedObjectArraysAreEqual(oldValue, representedObjects) {
unreadCount = 0
if let representedObjects = representedObjects {
if representedObjects.count == 1 && representedObjects.first is Feed {
if representedObjects.count == 1 && representedObjects.first is WebFeed {
showFeedNames = false
}
else {
@ -168,7 +168,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
if !didRegisterForNotifications {
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil)
@ -437,15 +437,15 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
updateUnreadCount()
}
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
guard showIcons, let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
guard showIcons, let feed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed else {
return
}
let indexesToReload = tableView.indexesOfAvailableRowsPassingTest { (row) -> Bool in
guard let article = articles.articleAtRow(row) else {
return false
}
return feed == article.feed
return feed == article.webFeed
}
if let indexesToReload = indexesToReload {
reloadCells(for: indexesToReload)
@ -480,11 +480,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
}
@objc func accountDidDownloadArticles(_ note: Notification) {
guard let feeds = note.userInfo?[Account.UserInfoKey.feeds] as? Set<Feed> else {
guard let feeds = note.userInfo?[Account.UserInfoKey.webFeeds] as? Set<WebFeed> else {
return
}
let shouldFetchAndMergeArticles = representedObjectsContainsAnyFeed(feeds) || representedObjectsContainsAnyPseudoFeed()
let shouldFetchAndMergeArticles = representedObjectsContainsAnyWebFeed(feeds) || representedObjectsContainsAnyPseudoFeed()
if shouldFetchAndMergeArticles {
queueFetchAndMergeArticles()
}
@ -568,7 +568,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
let longTitle = "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?"
let prototypeID = "prototype"
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date())
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
let prototypeCellData = TimelineCellData(article: prototypeArticle, showFeedName: showingFeedNames, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil)
let height = TimelineCellLayout.height(for: 100, cellData: prototypeCellData, appearance: cellAppearance)
@ -717,7 +717,7 @@ extension TimelineViewController: NSTableViewDelegate {
private func configureTimelineCell(_ cell: TimelineTableCellView, article: Article) {
cell.objectValue = article
let iconImage = article.iconImage()
cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, iconImage: iconImage, showIcon: showIcons, featuredImage: nil)
cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.webFeed?.nameForDisplay, iconImage: iconImage, showIcon: showIcons, featuredImage: nil)
}
private func iconFor(_ article: Article) -> IconImage? {
@ -733,11 +733,11 @@ extension TimelineViewController: NSTableViewDelegate {
}
}
guard let feed = article.feed else {
guard let feed = article.webFeed else {
return nil
}
if let feedIcon = appDelegate.feedIconDownloader.icon(for: feed) {
if let feedIcon = appDelegate.webFeedIconDownloader.icon(for: feed) {
return feedIcon
}
@ -1056,23 +1056,23 @@ private extension TimelineViewController {
return representedObjects?.contains(where: { $0 is Folder }) ?? false
}
func representedObjectsContainsAnyFeed(_ feeds: Set<Feed>) -> Bool {
func representedObjectsContainsAnyWebFeed(_ webFeeds: Set<WebFeed>) -> Bool {
// Return true if theres 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? Feed {
for oneFeed in feeds {
if feed.feedID == oneFeed.feedID || feed.url == oneFeed.url {
if let feed = representedObject as? WebFeed {
for oneFeed in webFeeds {
if feed.webFeedID == oneFeed.webFeedID || feed.url == oneFeed.url {
return true
}
}
}
else if let folder = representedObject as? Folder {
for oneFeed in feeds {
if folder.hasFeed(with: oneFeed.feedID) || folder.hasFeed(withURL: oneFeed.url) {
for oneFeed in webFeeds {
if folder.hasWebFeed(with: oneFeed.webFeedID) || folder.hasWebFeed(withURL: oneFeed.url) {
return true
}
}

View File

@ -81,7 +81,7 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
} else {
container = account
}
account.removeFeed(scriptableFeed.feed, from: container!) { result in
account.removeWebFeed(scriptableFeed.webFeed, from: container!) { result in
}
}
}
@ -96,19 +96,19 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
@objc(feeds)
var feeds:NSArray {
return account.topLevelFeeds.map { ScriptableFeed($0, container:self) } as NSArray
return account.topLevelWebFeeds.map { ScriptableFeed($0, container:self) } as NSArray
}
@objc(valueInFeedsWithUniqueID:)
func valueInFeeds(withUniqueID id:String) -> ScriptableFeed? {
let feeds = Array(account.topLevelFeeds)
guard let feed = feeds.first(where:{$0.feedID == id}) else { return nil }
let feeds = Array(account.topLevelWebFeeds)
guard let feed = feeds.first(where:{$0.webFeedID == id}) else { return nil }
return ScriptableFeed(feed, container:self)
}
@objc(valueInFeedsWithName:)
func valueInFeeds(withName name:String) -> ScriptableFeed? {
let feeds = Array(account.topLevelFeeds)
let feeds = Array(account.topLevelWebFeeds)
guard let feed = feeds.first(where:{$0.name == name}) else { return nil }
return ScriptableFeed(feed, container:self)
}
@ -134,13 +134,13 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
@objc(allFeeds)
var allFeeds: NSArray {
var feeds = [ScriptableFeed]()
for feed in account.topLevelFeeds {
for feed in account.topLevelWebFeeds {
feeds.append(ScriptableFeed(feed, container: self))
}
if let folders = account.folders {
for folder in folders {
let scriptableFolder = ScriptableFolder(folder, container: self)
for feed in folder.topLevelFeeds {
for feed in folder.topLevelWebFeeds {
feeds.append(ScriptableFeed(feed, container: scriptableFolder))
}
}

View File

@ -14,11 +14,11 @@ import Articles
@objc(ScriptableFeed)
class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
let feed:Feed
let webFeed:WebFeed
let container:ScriptingObjectContainer
init (_ feed:Feed, container:ScriptingObjectContainer) {
self.feed = feed
init (_ feed:WebFeed, container:ScriptingObjectContainer) {
self.webFeed = feed
self.container = container
}
@ -45,7 +45,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
// but in either case it seems like the accountID would be used as the keydata, so I chose ID
@objc(uniqueId)
var scriptingUniqueId:Any {
return feed.feedID
return webFeed.webFeedID
}
// MARK: --- ScriptingObjectContainer protocol ---
@ -71,7 +71,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
return url
}
class func scriptableFeed(_ feed:Feed, account:Account, folder:Folder?) -> ScriptableFeed {
class func scriptableFeed(_ feed:WebFeed, account:Account, folder:Folder?) -> ScriptableFeed {
let scriptableAccount = ScriptableAccount(account)
if let folder = folder {
let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount)
@ -88,7 +88,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
let (account, folder) = command.accountAndFolderForNewChild()
guard let url = self.urlForNewFeed(arguments:arguments) else {return nil}
if let existingFeed = account.existingFeed(withURL:url) {
if let existingFeed = account.existingWebFeed(withURL:url) {
return scriptableFeed(existingFeed, account:account, folder:folder).objectSpecifier
}
@ -102,10 +102,10 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
// suspendExecution(). When we get the callback, we supply the event result and call resumeExecution().
command.suspendExecution()
account.createFeed(url: url, name: titleFromArgs, container: container) { result in
account.createWebFeed(url: url, name: titleFromArgs, container: container) { result in
switch result {
case .success(let feed):
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed])
let scriptableFeed = self.scriptableFeed(feed, account:account, folder:folder)
command.resumeExecution(withResult:scriptableFeed.objectSpecifier)
case .failure:
@ -121,51 +121,51 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
@objc(url)
var url:String {
return self.feed.url
return self.webFeed.url
}
@objc(name)
var name:String {
return self.feed.name ?? ""
return self.webFeed.name ?? ""
}
@objc(homePageURL)
var homePageURL:String {
return self.feed.homePageURL ?? ""
return self.webFeed.homePageURL ?? ""
}
@objc(iconURL)
var iconURL:String {
return self.feed.iconURL ?? ""
return self.webFeed.iconURL ?? ""
}
@objc(faviconURL)
var faviconURL:String {
return self.feed.faviconURL ?? ""
return self.webFeed.faviconURL ?? ""
}
@objc(opmlRepresentation)
var opmlRepresentation:String {
return self.feed.OPMLString(indentLevel:0, strictConformance: true)
return self.webFeed.OPMLString(indentLevel:0, strictConformance: true)
}
// MARK: --- scriptable elements ---
@objc(authors)
var authors:NSArray {
let feedAuthors = feed.authors ?? []
let feedAuthors = webFeed.authors ?? []
return feedAuthors.map { ScriptableAuthor($0, container:self) } as NSArray
}
@objc(valueInAuthorsWithUniqueID:)
func valueInAuthors(withUniqueID id:String) -> ScriptableAuthor? {
guard let author = feed.authors?.first(where:{$0.authorID == id}) else { return nil }
guard let author = webFeed.authors?.first(where:{$0.authorID == id}) else { return nil }
return ScriptableAuthor(author, container:self)
}
@objc(articles)
var articles:NSArray {
let feedArticles = feed.fetchArticles()
let feedArticles = webFeed.fetchArticles()
// the articles are a set, use the sorting algorithm from the viewer
let sortedArticles = feedArticles.sorted(by:{
return $0.logicalDatePublished > $1.logicalDatePublished
@ -175,7 +175,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
@objc(valueInArticlesWithUniqueID:)
func valueInArticles(withUniqueID id:String) -> ScriptableArticle? {
let articles = feed.fetchArticles()
let articles = webFeed.fetchArticles()
guard let article = articles.first(where:{$0.uniqueID == id}) else { return nil }
return ScriptableArticle(article, container:self)
}

View File

@ -53,7 +53,7 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
func deleteElement(_ element:ScriptingObject) {
if let scriptableFeed = element as? ScriptableFeed {
BatchUpdate.shared.perform {
folder.account?.removeFeed(scriptableFeed.feed, from: folder) { result in }
folder.account?.removeWebFeed(scriptableFeed.webFeed, from: folder) { result in }
}
}
}
@ -97,7 +97,7 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
@objc(feeds)
var feeds:NSArray {
let feeds = Array(folder.topLevelFeeds)
let feeds = Array(folder.topLevelWebFeeds)
return feeds.map { ScriptableFeed($0, container:self) } as NSArray
}

View File

@ -30,7 +30,7 @@ extension NSApplication : ScriptingObjectContainer {
func currentArticle() -> ScriptableArticle? {
var scriptableArticle: ScriptableArticle?
if let currentArticle = appDelegate.scriptingCurrentArticle {
if let feed = currentArticle.feed {
if let feed = currentArticle.webFeed {
let scriptableFeed = ScriptableFeed(feed, container:self)
scriptableArticle = ScriptableArticle(currentArticle, container:scriptableFeed)
}
@ -42,7 +42,7 @@ extension NSApplication : ScriptingObjectContainer {
func selectedArticles() -> NSArray {
let articles = appDelegate.scriptingSelectedArticles
let scriptableArticles:[ScriptableArticle] = articles.compactMap { article in
if let feed = article.feed {
if let feed = article.webFeed {
let scriptableFeed = ScriptableFeed(feed, container:self)
return ScriptableArticle(article, container:scriptableFeed)
} else {
@ -73,11 +73,11 @@ extension NSApplication : ScriptingObjectContainer {
for 'articles of feed "The Shape of Everything" of account "On My Mac"'
*/
func allFeeds() -> [Feed] {
func allFeeds() -> [WebFeed] {
let accounts = AccountManager.shared.activeAccounts
let emptyFeeds:[Feed] = []
return accounts.reduce(emptyFeeds) { (result, nthAccount) -> [Feed] in
let accountFeeds = Array(nthAccount.topLevelFeeds)
let emptyFeeds:[WebFeed] = []
return accounts.reduce(emptyFeeds) { (result, nthAccount) -> [WebFeed] in
let accountFeeds = Array(nthAccount.topLevelWebFeeds)
return result + accountFeeds
}
}
@ -91,7 +91,7 @@ extension NSApplication : ScriptingObjectContainer {
@objc(valueInFeedsWithUniqueID:)
func valueInFeeds(withUniqueID id:String) -> ScriptableFeed? {
let feeds = self.allFeeds()
guard let feed = feeds.first(where:{$0.feedID == id}) else { return nil }
guard let feed = feeds.first(where:{$0.webFeedID == id}) else { return nil }
return ScriptableFeed(feed, container:self)
}
}

View File

@ -34,14 +34,14 @@
512AF9C2236ED52C0066F8BE /* InspectorImageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512AF9C1236ED52C0066F8BE /* InspectorImageHeaderView.swift */; };
512AF9DD236F05230066F8BE /* InteractiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512AF9DC236F05230066F8BE /* InteractiveLabel.swift */; };
512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; };
512E08E72268801200BDCFDD /* FeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */; };
512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */; };
512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512E08F722688F7C00BDCFDD /* MasterFeedTableViewSectionHeader.swift */; };
512E09352268B25900BDCFDD /* UISplitViewController-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512E092B2268B25500BDCFDD /* UISplitViewController-Extensions.swift */; };
512E094D2268B8AB00BDCFDD /* DeleteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */; };
5131463E235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 51314637235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
51314668235A7E4600387FDC /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51314666235A7E4600387FDC /* IntentHandler.swift */; };
513146B2235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */; };
513146B3235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */; };
513146B2235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */; };
513146B3235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */; };
513146B4235A8FD000387FDC /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8520DD8CF200CA8CF5 /* RSCore.framework */; };
513146B6235A8FD000387FDC /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC420DD8E0C00CA8CF5 /* RSDatabase.framework */; };
513146B8235A8FD000387FDC /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */; };
@ -67,7 +67,7 @@
513C5D0A232574D2003D4054 /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FA320DD8D0500CA8CF5 /* RSWeb.framework */; };
513C5D0C232574DA003D4054 /* RSTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; };
513C5D0E232574E4003D4054 /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; };
5141E7392373C18B0013FF27 /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7382373C18B0013FF27 /* FeedInspectorViewController.swift */; };
5141E7392373C18B0013FF27 /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */; };
5141E7562374A2890013FF27 /* ArticleIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */; };
5142192A23522B5500E07E2C /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5142192923522B5500E07E2C /* ImageViewController.swift */; };
514219372352510100E07E2C /* ImageScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514219362352510100E07E2C /* ImageScrollView.swift */; };
@ -171,7 +171,7 @@
51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4527E2265092C00C03939 /* ArticleViewController.swift */; };
51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */; };
51C452862265093600C03939 /* Add.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51C452822265093600C03939 /* Add.storyboard */; };
51C452882265093600C03939 /* AddFeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452842265093600C03939 /* AddFeedViewController.swift */; };
51C452882265093600C03939 /* AddWebFeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452842265093600C03939 /* AddWebFeedViewController.swift */; };
51C4528D2265095F00C03939 /* AddFolderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4528B2265095F00C03939 /* AddFolderViewController.swift */; };
51C4528E2265099C00C03939 /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; };
51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */; };
@ -189,7 +189,7 @@
51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; };
51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; };
51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; };
51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; };
51C452A022650A1900C03939 /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; };
51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; };
51C452A322650A1E00C03939 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; };
51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */; };
@ -290,11 +290,11 @@
65ED3FD8235DEF6C0081F399 /* NSView-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DD9B22153BD7008CE1BF /* NSView-Extensions.swift */; };
65ED3FD9235DEF6C0081F399 /* SidebarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A979E1ED9F130007D329B /* SidebarCell.swift */; };
65ED3FDA235DEF6C0081F399 /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; };
65ED3FDB235DEF6C0081F399 /* FeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */; };
65ED3FDB235DEF6C0081F399 /* WebFeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */; };
65ED3FDC235DEF6C0081F399 /* UnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97631ED9EB96007D329B /* UnreadCountView.swift */; };
65ED3FDD235DEF6C0081F399 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; };
65ED3FDE235DEF6C0081F399 /* CrashReportWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840BEE4021D70E64009BBAFA /* CrashReportWindowController.swift */; };
65ED3FDF235DEF6C0081F399 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; };
65ED3FDF235DEF6C0081F399 /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; };
65ED3FE0235DEF6C0081F399 /* AccountsControlsBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC7122629E1200D921D6 /* AccountsControlsBackgroundView.swift */; };
65ED3FE1235DEF6C0081F399 /* MarkCommandValidationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */; };
65ED3FE2235DEF6C0081F399 /* ArticlePasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */; };
@ -320,7 +320,7 @@
65ED3FF7235DEF6C0081F399 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; };
65ED3FF8235DEF6C0081F399 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB32229AB02C00645299 /* ErrorHandler.swift */; };
65ED3FF9235DEF6C0081F399 /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; };
65ED3FFA235DEF6C0081F399 /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; };
65ED3FFA235DEF6C0081F399 /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */; };
65ED3FFB235DEF6C0081F399 /* AccountsReaderAPIWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */; };
65ED3FFC235DEF6C0081F399 /* AccountsAddLocalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */; };
65ED3FFD235DEF6C0081F399 /* PasteboardFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EA92031617300BC20B7 /* PasteboardFolder.swift */; };
@ -346,7 +346,7 @@
65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; };
65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DDA422168C62008CE1BF /* TimelineContainerViewController.swift */; };
65ED4013235DEF6C0081F399 /* MainWIndowKeyboardHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B5B661FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift */; };
65ED4014235DEF6C0081F399 /* PasteboardFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardFeed.swift */; };
65ED4014235DEF6C0081F399 /* PasteboardWebFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardWebFeed.swift */; };
65ED4015235DEF6C0081F399 /* AccountsDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA2E2279FAB600D19003 /* AccountsDetailViewController.swift */; };
65ED4016235DEF6C0081F399 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977E1ED9EC42007D329B /* DetailViewController.swift */; };
65ED4017235DEF6C0081F399 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC6622629B3900D921D6 /* AppDelegate.swift */; };
@ -469,7 +469,7 @@
841ABA5E20145E9200980E11 /* FolderInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */; };
841ABA6020145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */; };
84216D0322128B9D0049B9B9 /* DetailWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */; };
8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; };
8426118A1FCB67AA0086A189 /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; };
8426119E1FCB6ED40086A189 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; };
842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; };
842E45CE1ED8C308000A8B52 /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; };
@ -489,7 +489,7 @@
845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */; };
845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7C01FC2488C00854A1F /* SmartFeed.swift */; };
84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; };
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; };
8472058120142E8900AD578B /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */; };
8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; };
847CD6CA232F4CBF00FAC46D /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* IconView.swift */; };
847E64A02262783000E00365 /* NSAppleEventDescriptor+UserRecordFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */; };
@ -501,7 +501,7 @@
8483630B2262A3F000DA1D35 /* RenameSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363092262A3F000DA1D35 /* RenameSheet.xib */; };
8483630E2262A3FE00DA1D35 /* MainWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8483630C2262A3FE00DA1D35 /* MainWindow.storyboard */; };
848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848B937121C8C5540038DC0D /* CrashReporter.swift */; };
848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardFeed.swift */; };
848D578E21543519005FFAD5 /* PasteboardWebFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardWebFeed.swift */; };
848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; };
849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; };
849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97511ED9EAC0007D329B /* AddFeedController.swift */; };
@ -510,7 +510,7 @@
849A975C1ED9EB0D007D329B /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; };
849A975E1ED9EB72007D329B /* MainWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A975D1ED9EB72007D329B /* MainWindowController.swift */; };
849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97601ED9EB96007D329B /* SidebarOutlineView.swift */; };
849A97651ED9EB96007D329B /* FeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */; };
849A97651ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */; };
849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97621ED9EB96007D329B /* SidebarViewController.swift */; };
849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97631ED9EB96007D329B /* UnreadCountView.swift */; };
849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */; };
@ -1228,7 +1228,7 @@
51314665235A7E4600387FDC /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
51314666235A7E4600387FDC /* IntentHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = "<group>"; };
51314684235A7EB900387FDC /* NetNewsWire_iOS_IntentsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetNewsWire_iOS_IntentsExtension.entitlements; sourceTree = "<group>"; };
513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedIntentHandler.swift; sourceTree = "<group>"; };
513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedIntentHandler.swift; sourceTree = "<group>"; };
51314706235C41FC00387FDC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = "<group>"; };
51314714235C420900387FDC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intents.strings; sourceTree = "<group>"; };
513228F2233037620033D4ED /* Reachability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = "<group>"; };
@ -1236,7 +1236,7 @@
513C5CE8232571C2003D4054 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
513C5CEB232571C2003D4054 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
513C5CED232571C2003D4054 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
5141E7382373C18B0013FF27 /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = "<group>"; };
5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedInspectorViewController.swift; sourceTree = "<group>"; };
5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleIconSchemeHandler.swift; sourceTree = "<group>"; };
5142192923522B5500E07E2C /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = "<group>"; };
514219362352510100E07E2C /* ImageScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageScrollView.swift; sourceTree = "<group>"; };
@ -1308,7 +1308,7 @@
51C4527E2265092C00C03939 /* ArticleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleViewController.swift; sourceTree = "<group>"; };
51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlattenedAccountFolderPickerData.swift; sourceTree = "<group>"; };
51C452822265093600C03939 /* Add.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Add.storyboard; sourceTree = "<group>"; };
51C452842265093600C03939 /* AddFeedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFeedViewController.swift; sourceTree = "<group>"; };
51C452842265093600C03939 /* AddWebFeedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddWebFeedViewController.swift; sourceTree = "<group>"; };
51C4528B2265095F00C03939 /* AddFolderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderViewController.swift; sourceTree = "<group>"; };
51C452B32265141B00C03939 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; };
51C452B72265178500C03939 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = "<group>"; };
@ -1383,7 +1383,7 @@
841D4D542106B3D500DD04E6 /* Articles.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Articles.xcodeproj; path = ../Frameworks/Articles/Articles.xcodeproj; sourceTree = "<group>"; };
841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ArticlesDatabase.xcodeproj; path = ../Frameworks/ArticlesDatabase/ArticlesDatabase.xcodeproj; sourceTree = "<group>"; };
84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWebViewController.swift; sourceTree = "<group>"; };
842611891FCB67AA0086A189 /* FeedIconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIconDownloader.swift; sourceTree = "<group>"; };
842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedIconDownloader.swift; sourceTree = "<group>"; };
8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadataDownloader.swift; sourceTree = "<group>"; };
8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedImageDownloader.swift; sourceTree = "<group>"; };
842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSHTMLMetadata+Extension.swift"; sourceTree = "<group>"; };
@ -1406,7 +1406,7 @@
845EE7C01FC2488C00854A1F /* SmartFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeed.swift; sourceTree = "<group>"; };
846E77301F6EF5D600A165E2 /* Account.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Account.xcodeproj; path = ../Frameworks/Account/Account.xcodeproj; sourceTree = "<group>"; };
84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkStatusCommand.swift; sourceTree = "<group>"; };
8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = "<group>"; };
8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedInspectorViewController.swift; sourceTree = "<group>"; };
847752FE2008879500D93690 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; };
8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFeedDelegate.swift; sourceTree = "<group>"; };
847CD6C9232F4CBF00FAC46D /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = "<group>"; };
@ -1419,7 +1419,7 @@
8483630A2262A3F000DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/RenameSheet.xib; sourceTree = SOURCE_ROOT; };
8483630D2262A3FE00DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Mac/Base.lproj/MainWindow.storyboard; sourceTree = SOURCE_ROOT; };
848B937121C8C5540038DC0D /* CrashReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrashReporter.swift; sourceTree = "<group>"; };
848D578D21543519005FFAD5 /* PasteboardFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardFeed.swift; sourceTree = "<group>"; };
848D578D21543519005FFAD5 /* PasteboardWebFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardWebFeed.swift; sourceTree = "<group>"; };
848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaviconDownloader.swift; sourceTree = "<group>"; };
849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderWindowController.swift; sourceTree = "<group>"; };
849A97511ED9EAC0007D329B /* AddFeedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedController.swift; path = AddFeed/AddFeedController.swift; sourceTree = "<group>"; };
@ -1428,7 +1428,7 @@
849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultFeedsImporter.swift; sourceTree = "<group>"; };
849A975D1ED9EB72007D329B /* MainWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindowController.swift; sourceTree = "<group>"; };
849A97601ED9EB96007D329B /* SidebarOutlineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarOutlineView.swift; sourceTree = "<group>"; };
849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedTreeControllerDelegate.swift; sourceTree = "<group>"; };
849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebFeedTreeControllerDelegate.swift; sourceTree = "<group>"; };
849A97621ED9EB96007D329B /* SidebarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarViewController.swift; sourceTree = "<group>"; };
849A97631ED9EB96007D329B /* UnreadCountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadCountView.swift; sourceTree = "<group>"; };
849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineTableRowView.swift; sourceTree = "<group>"; };
@ -1688,7 +1688,7 @@
children = (
516A09412361248000EAE89B /* Inspector.storyboard */,
51A16991235E10D600EB091F /* AccountInspectorViewController.swift */,
5141E7382373C18B0013FF27 /* FeedInspectorViewController.swift */,
5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */,
5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */,
512AF9C1236ED52C0066F8BE /* InspectorImageHeaderView.swift */,
);
@ -1707,7 +1707,7 @@
isa = PBXGroup;
children = (
51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */,
849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */,
849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */,
849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */,
);
path = Tree;
@ -1717,7 +1717,7 @@
isa = PBXGroup;
children = (
51314707235C41FC00387FDC /* Intents.intentdefinition */,
513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */,
513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */,
);
path = Intents;
sourceTree = "<group>";
@ -1925,7 +1925,7 @@
51C452822265093600C03939 /* Add.storyboard */,
51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */,
514B7D1E23219F3C00BAC947 /* AddControllerType.swift */,
51C452842265093600C03939 /* AddFeedViewController.swift */,
51C452842265093600C03939 /* AddWebFeedViewController.swift */,
51C4528B2265095F00C03939 /* AddFolderViewController.swift */,
);
path = Add;
@ -2119,7 +2119,7 @@
children = (
845213221FCA5B10003B6E93 /* ImageDownloader.swift */,
84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */,
842611891FCB67AA0086A189 /* FeedIconDownloader.swift */,
842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */,
8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */,
842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */,
);
@ -2210,7 +2210,7 @@
84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */,
849A97601ED9EB96007D329B /* SidebarOutlineView.swift */,
849A97631ED9EB96007D329B /* UnreadCountView.swift */,
848D578D21543519005FFAD5 /* PasteboardFeed.swift */,
848D578D21543519005FFAD5 /* PasteboardWebFeed.swift */,
84AD1EA92031617300BC20B7 /* PasteboardFolder.swift */,
849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */,
844B5B6A1FEA224000C7C76A /* Keyboard */,
@ -2340,7 +2340,7 @@
children = (
84BBB12B20142A4700F054F5 /* Inspector.storyboard */,
84BBB12C20142A4700F054F5 /* InspectorWindowController.swift */,
8472058020142E8900AD578B /* FeedInspectorViewController.swift */,
8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */,
841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */,
841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */,
841ABA4D20145E7300980E11 /* NothingInspectorViewController.swift */,
@ -3679,7 +3679,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
513146B3235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */,
513146B3235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */,
51314705235C41FC00387FDC /* Intents.intentdefinition in Sources */,
51314668235A7E4600387FDC /* IntentHandler.swift in Sources */,
);
@ -3753,11 +3753,11 @@
65ED3FD8235DEF6C0081F399 /* NSView-Extensions.swift in Sources */,
65ED3FD9235DEF6C0081F399 /* SidebarCell.swift in Sources */,
65ED3FDA235DEF6C0081F399 /* ArticleStatusSyncTimer.swift in Sources */,
65ED3FDB235DEF6C0081F399 /* FeedTreeControllerDelegate.swift in Sources */,
65ED3FDB235DEF6C0081F399 /* WebFeedTreeControllerDelegate.swift in Sources */,
65ED3FDC235DEF6C0081F399 /* UnreadCountView.swift in Sources */,
65ED3FDD235DEF6C0081F399 /* ActivityType.swift in Sources */,
65ED3FDE235DEF6C0081F399 /* CrashReportWindowController.swift in Sources */,
65ED3FDF235DEF6C0081F399 /* FeedIconDownloader.swift in Sources */,
65ED3FDF235DEF6C0081F399 /* WebFeedIconDownloader.swift in Sources */,
65ED3FE0235DEF6C0081F399 /* AccountsControlsBackgroundView.swift in Sources */,
65ED3FE1235DEF6C0081F399 /* MarkCommandValidationStatus.swift in Sources */,
65ED3FE2235DEF6C0081F399 /* ArticlePasteboardWriter.swift in Sources */,
@ -3784,7 +3784,7 @@
65ED3FF7235DEF6C0081F399 /* SearchFeedDelegate.swift in Sources */,
65ED3FF8235DEF6C0081F399 /* ErrorHandler.swift in Sources */,
65ED3FF9235DEF6C0081F399 /* ActivityManager.swift in Sources */,
65ED3FFA235DEF6C0081F399 /* FeedInspectorViewController.swift in Sources */,
65ED3FFA235DEF6C0081F399 /* WebFeedInspectorViewController.swift in Sources */,
65ED3FFB235DEF6C0081F399 /* AccountsReaderAPIWindowController.swift in Sources */,
65ED3FFC235DEF6C0081F399 /* AccountsAddLocalWindowController.swift in Sources */,
65ED3FFD235DEF6C0081F399 /* PasteboardFolder.swift in Sources */,
@ -3810,7 +3810,7 @@
65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */,
65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */,
65ED4013235DEF6C0081F399 /* MainWIndowKeyboardHandler.swift in Sources */,
65ED4014235DEF6C0081F399 /* PasteboardFeed.swift in Sources */,
65ED4014235DEF6C0081F399 /* PasteboardWebFeed.swift in Sources */,
65ED4015235DEF6C0081F399 /* AccountsDetailViewController.swift in Sources */,
65ED4016235DEF6C0081F399 /* DetailViewController.swift in Sources */,
65ED4017235DEF6C0081F399 /* AppDelegate.swift in Sources */,
@ -3873,7 +3873,7 @@
files = (
840D617F2029031C009BC708 /* AppDelegate.swift in Sources */,
51236339236915B100951F16 /* RoundedProgressView.swift in Sources */,
512E08E72268801200BDCFDD /* FeedTreeControllerDelegate.swift in Sources */,
512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */,
51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */,
51EF0F79227716380050506E /* ColorHash.swift in Sources */,
5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */,
@ -3891,7 +3891,7 @@
517630232336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift in Sources */,
51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */,
51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */,
513146B2235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */,
513146B2235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */,
51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */,
51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */,
51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */,
@ -3920,7 +3920,7 @@
5F323809231DF9F000706F6B /* VibrantTableViewCell.swift in Sources */,
512E09352268B25900BDCFDD /* UISplitViewController-Extensions.swift in Sources */,
51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */,
51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */,
51C452A022650A1900C03939 /* WebFeedIconDownloader.swift in Sources */,
51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */,
51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */,
51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */,
@ -3945,7 +3945,7 @@
51C452AF2265108300C03939 /* ArticleArray.swift in Sources */,
51C4528E2265099C00C03939 /* SmartFeedsController.swift in Sources */,
51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */,
5141E7392373C18B0013FF27 /* FeedInspectorViewController.swift in Sources */,
5141E7392373C18B0013FF27 /* WebFeedInspectorViewController.swift in Sources */,
5108F6D42375EEEF001ABC45 /* TimelinePreviewTableViewController.swift in Sources */,
84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */,
51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */,
@ -3964,7 +3964,7 @@
51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */,
51C452762265091600C03939 /* MasterTimelineViewController.swift in Sources */,
5108F6D823763094001ABC45 /* TickMarkSlider.swift in Sources */,
51C452882265093600C03939 /* AddFeedViewController.swift in Sources */,
51C452882265093600C03939 /* AddWebFeedViewController.swift in Sources */,
51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */,
51934CCE2310792F006127BE /* ActivityManager.swift in Sources */,
5108F6B72375E612001ABC45 /* CacheCleaner.swift in Sources */,
@ -4040,11 +4040,11 @@
8405DD9C22153BD7008CE1BF /* NSView-Extensions.swift in Sources */,
849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */,
51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */,
849A97651ED9EB96007D329B /* FeedTreeControllerDelegate.swift in Sources */,
849A97651ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift in Sources */,
849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */,
51FE10092346739D0056195D /* ActivityType.swift in Sources */,
840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */,
8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */,
8426118A1FCB67AA0086A189 /* WebFeedIconDownloader.swift in Sources */,
84C9FC7B22629E1200D921D6 /* AccountsControlsBackgroundView.swift in Sources */,
84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */,
84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */,
@ -4070,7 +4070,7 @@
8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */,
51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */,
51FE100A234673A00056195D /* ActivityManager.swift in Sources */,
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */,
8472058120142E8900AD578B /* WebFeedInspectorViewController.swift in Sources */,
55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */,
5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */,
84AD1EAA2031617300BC20B7 /* PasteboardFolder.swift in Sources */,
@ -4098,7 +4098,7 @@
849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */,
8405DDA522168C62008CE1BF /* TimelineContainerViewController.swift in Sources */,
844B5B671FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift in Sources */,
848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */,
848D578E21543519005FFAD5 /* PasteboardWebFeed.swift in Sources */,
5144EA2F2279FAB600D19003 /* AccountsDetailViewController.swift in Sources */,
849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */,
518C3193237B00D9004D740F /* ArticleIconSchemeHandler.swift in Sources */,

View File

@ -29,7 +29,7 @@ class ActivityManager {
}
init() {
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
}
func invalidateCurrentActivities() {
@ -43,7 +43,7 @@ class ActivityManager {
selectingActivity = makeSelectFeedActivity(fetcher: fetcher)
if let feed = fetcher as? Feed {
if let feed = fetcher as? WebFeed {
updateSelectingActivityFeedSearchAttributes(with: feed)
}
@ -106,8 +106,8 @@ class ActivityManager {
}
}
for feed in account.flattenedFeeds() {
ids.append(contentsOf: identifers(for: feed))
for webFeed in account.flattenedWebFeeds() {
ids.append(contentsOf: identifers(for: webFeed))
}
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
@ -117,31 +117,31 @@ class ActivityManager {
var ids = [String]()
ids.append(identifer(for: folder))
for feed in folder.flattenedFeeds() {
ids.append(contentsOf: identifers(for: feed))
for webFeed in folder.flattenedWebFeeds() {
ids.append(contentsOf: identifers(for: webFeed))
}
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
}
static func cleanUp(_ feed: Feed) {
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: identifers(for: feed))
static func cleanUp(_ webFeed: WebFeed) {
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: identifers(for: webFeed))
}
#endif
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed, let activityFeedId = selectingActivity?.userInfo?[ArticlePathKey.feedID] as? String else {
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
guard let webFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed, let activityFeedId = selectingActivity?.userInfo?[ArticlePathKey.webFeedID] as? String else {
return
}
#if os(iOS)
if let article = readingArticle, activityFeedId == article.feedID {
if let article = readingArticle, activityFeedId == article.webFeedID {
updateReadArticleSearchAttributes(with: article)
}
#endif
if activityFeedId == feed.feedID {
updateSelectingActivityFeedSearchAttributes(with: feed)
if activityFeedId == webFeed.webFeedID {
updateSelectingActivityFeedSearchAttributes(with: webFeed)
}
}
@ -224,7 +224,7 @@ private extension ActivityManager {
#endif
func makeKeywords(_ article: Article) -> [String] {
let feedNameKeywords = makeKeywords(article.feed?.nameForDisplay)
let feedNameKeywords = makeKeywords(article.webFeed?.nameForDisplay)
let articleTitleKeywords = makeKeywords(ArticleStringFormatter.truncatedTitle(article))
return feedNameKeywords + articleTitleKeywords
}
@ -233,13 +233,13 @@ private extension ActivityManager {
return value?.components(separatedBy: " ").filter { $0.count > 2 } ?? []
}
func updateSelectingActivityFeedSearchAttributes(with feed: Feed) {
func updateSelectingActivityFeedSearchAttributes(with feed: WebFeed) {
let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String)
attributeSet.title = feed.nameForDisplay
attributeSet.keywords = makeKeywords(feed.nameForDisplay)
attributeSet.relatedUniqueIdentifier = ActivityManager.identifer(for: feed)
if let iconImage = appDelegate.feedIconDownloader.icon(for: feed) {
if let iconImage = appDelegate.webFeedIconDownloader.icon(for: feed) {
attributeSet.thumbnailData = iconImage.image.dataRepresentation()
} else if let iconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
attributeSet.thumbnailData = iconImage.image.dataRepresentation()
@ -267,15 +267,15 @@ private extension ActivityManager {
return "account_\(folder.account!.accountID)_folder_\(folder.nameForDisplay)"
}
static func identifer(for feed: Feed) -> String {
return "account_\(feed.account!.accountID)_feed_\(feed.feedID)"
static func identifer(for feed: WebFeed) -> String {
return "account_\(feed.account!.accountID)_feed_\(feed.webFeedID)"
}
static func identifer(for article: Article) -> String {
return "account_\(article.accountID)_feed_\(article.feedID)_article_\(article.articleID)"
return "account_\(article.accountID)_feed_\(article.webFeedID)_article_\(article.articleID)"
}
static func identifers(for feed: Feed) -> [String] {
static func identifers(for feed: WebFeed) -> [String] {
var ids = [String]()
ids.append(identifer(for: feed))

View File

@ -144,9 +144,9 @@ private extension ArticleRenderer {
d["avatars"] = "<td class=\"header rightAlign avatar\"><img src=\"\(ArticleRenderer.imageIconScheme)://\" height=48 width=48 /></td>";
var feedLink = ""
if let feedTitle = article.feed?.nameForDisplay {
if let feedTitle = article.webFeed?.nameForDisplay {
feedLink = feedTitle
if let feedURL = article.feed?.homePageURL {
if let feedURL = article.webFeed?.homePageURL {
feedLink = feedLink.htmlByAddingLink(feedURL, className: "feedLink")
}
}
@ -184,7 +184,7 @@ private extension ArticleRenderer {
}
func byline() -> String {
guard let authors = article?.authors ?? article?.feed?.authors, !authors.isEmpty else {
guard let authors = article?.authors ?? article?.webFeed?.authors, !authors.isEmpty else {
return ""
}
@ -192,7 +192,7 @@ private extension ArticleRenderer {
// This code assumes that multiple authors would never match the feed name so that
// if there feed owner has an article co-author all authors are given the byline.
if authors.count == 1, let author = authors.first {
if author.name == article?.feed?.nameForDisplay {
if author.name == article?.webFeed?.nameForDisplay {
return ""
}
}
@ -256,10 +256,10 @@ private extension Article {
var baseURL: URL? {
var s = url
if s == nil {
s = feed?.homePageURL
s = webFeed?.homePageURL
}
if s == nil {
s = feed?.url
s = webFeed?.url
}
guard let urlString = s else {

View File

@ -63,7 +63,7 @@ final class DeleteCommand: UndoableCommand {
}
for node in nodes {
if let _ = node.representedObject as? Feed {
if let _ = node.representedObject as? WebFeed {
continue
}
if let _ = node.representedObject as? Folder {
@ -84,7 +84,7 @@ private struct SidebarItemSpecifier {
private weak var account: Account?
private let parentFolder: Folder?
private let folder: Folder?
private let feed: Feed?
private let webFeed: WebFeed?
private let path: ContainerPath
private let errorHandler: (Error) -> ()
@ -104,13 +104,13 @@ private struct SidebarItemSpecifier {
self.parentFolder = node.parentFolder()
if let feed = node.representedObject as? Feed {
self.feed = feed
if let webFeed = node.representedObject as? WebFeed {
self.webFeed = webFeed
self.folder = nil
account = feed.account
account = webFeed.account
}
else if let folder = node.representedObject as? Folder {
self.feed = nil
self.webFeed = nil
self.folder = folder
account = folder.account
}
@ -130,7 +130,7 @@ private struct SidebarItemSpecifier {
func delete(completion: @escaping () -> Void) {
if let feed = feed {
if let webFeed = webFeed {
guard let container = path.resolveContainer() else {
completion()
@ -138,7 +138,7 @@ private struct SidebarItemSpecifier {
}
BatchUpdate.shared.start()
account?.removeFeed(feed, from: container) { result in
account?.removeWebFeed(webFeed, from: container) { result in
BatchUpdate.shared.end()
completion()
self.checkResult(result)
@ -158,22 +158,22 @@ private struct SidebarItemSpecifier {
func restore() {
if let _ = feed {
restoreFeed()
if let _ = webFeed {
restoreWebFeed()
}
else if let _ = folder {
restoreFolder()
}
}
private func restoreFeed() {
private func restoreWebFeed() {
guard let account = account, let feed = feed, let container = path.resolveContainer() else {
guard let account = account, let feed = webFeed, let container = path.resolveContainer() else {
return
}
BatchUpdate.shared.start()
account.restoreFeed(feed, container: container) { result in
account.restoreWebFeed(feed, container: container) { result in
BatchUpdate.shared.end()
self.checkResult(result)
}
@ -257,7 +257,7 @@ private struct DeleteActionName {
var numberOfFolders = 0
for node in nodes {
if let _ = node.representedObject as? Feed {
if let _ = node.representedObject as? WebFeed {
numberOfFeeds += 1
}
else if let _ = node.representedObject as? Folder {

View File

@ -57,7 +57,7 @@ private extension SendToMarsEditCommand {
let body = article.contentHTML ?? article.contentText ?? article.summary
let authorName = article.authors?.first?.name
let sender = SendToBlogEditorApp(targetDesciptor: targetDescriptor, title: article.title, body: body, summary: article.summary, link: article.externalURL, permalink: article.url, subject: nil, creator: authorName, commentsURL: nil, guid: article.uniqueID, sourceName: article.feed?.nameForDisplay, sourceHomeURL: article.feed?.homePageURL, sourceFeedURL: article.feed?.url)
let sender = SendToBlogEditorApp(targetDesciptor: targetDescriptor, title: article.title, body: body, summary: article.summary, link: article.externalURL, permalink: article.url, subject: nil, creator: authorName, commentsURL: nil, guid: article.uniqueID, sourceName: article.webFeed?.nameForDisplay, sourceHomeURL: article.webFeed?.homePageURL, sourceFeedURL: article.webFeed?.url)
let _ = sender.send()
}

View File

@ -68,10 +68,10 @@ private extension Article {
// Feed name, or feed name + author name (if author is specified per-article).
// Includes trailing space.
if let feedName = feed?.nameForDisplay, let authorName = authors?.first?.name {
if let feedName = webFeed?.nameForDisplay, let authorName = authors?.first?.name {
return feedName + ", " + authorName + ": "
}
if let feedName = feed?.nameForDisplay {
if let feedName = webFeed?.nameForDisplay {
return feedName + ": "
}
return ""

View File

@ -42,8 +42,8 @@ private func accountAndArticlesDictionary(_ articles: Set<Article>) -> [String:
extension Article {
var feed: Feed? {
return account?.existingFeed(withFeedID: feedID)
var webFeed: WebFeed? {
return account?.existingWebFeed(withWebFeedID: webFeedID)
}
var preferredLink: String? {
@ -71,26 +71,26 @@ extension Article {
}
}
if let authors = feed?.authors, authors.count == 1, let author = authors.first {
if let authors = webFeed?.authors, authors.count == 1, let author = authors.first {
if let image = appDelegate.authorAvatarDownloader.image(for: author) {
return image
}
}
guard let feed = feed else {
guard let webFeed = webFeed else {
return nil
}
let feedIconImage = appDelegate.feedIconDownloader.icon(for: feed)
let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed)
if feedIconImage != nil {
return feedIconImage
}
if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: webFeed) {
return faviconImage
}
return FaviconGenerator.favicon(feed)
return FaviconGenerator.favicon(webFeed)
}
}
@ -99,7 +99,7 @@ extension Article {
struct ArticlePathKey {
static let accountID = "accountID"
static let accountName = "accountName"
static let feedID = "feedID"
static let webFeedID = "webFeedID"
static let articleID = "articleID"
}
@ -109,7 +109,7 @@ extension Article {
return [
ArticlePathKey.accountID: accountID,
ArticlePathKey.accountName: account?.nameForDisplay ?? "",
ArticlePathKey.feedID: feedID,
ArticlePathKey.webFeedID: webFeedID,
ArticlePathKey.articleID: articleID
]
}
@ -121,7 +121,7 @@ extension Article {
extension Article: SortableArticle {
var sortableName: String {
return feed?.name ?? ""
return webFeed?.name ?? ""
}
var sortableDate: Date {
@ -132,8 +132,8 @@ extension Article: SortableArticle {
return articleID
}
var sortableFeedID: String {
return feedID
var sortableWebFeedID: String {
return webFeedID
}
}

View File

@ -16,7 +16,7 @@ protocol SmallIconProvider {
var smallIcon: IconImage? { get }
}
extension Feed: SmallIconProvider {
extension WebFeed: SmallIconProvider {
var smallIcon: IconImage? {
if let iconImage = appDelegate.faviconDownloader.favicon(for: self) {

View File

@ -41,7 +41,7 @@ final class FaviconDownloader {
}
private let queue: DispatchQueue
private var cache = [Feed: IconImage]() // faviconURL: RSImage
private var cache = [WebFeed: IconImage]() // faviconURL: RSImage
struct UserInfoKey {
static let faviconURL = "faviconURL"
@ -64,21 +64,21 @@ final class FaviconDownloader {
// MARK: - API
func resetCache() {
cache = [Feed: IconImage]()
cache = [WebFeed: IconImage]()
}
func favicon(for feed: Feed) -> IconImage? {
func favicon(for webFeed: WebFeed) -> IconImage? {
assert(Thread.isMainThread)
if let faviconURL = feed.faviconURL {
if let faviconURL = webFeed.faviconURL {
return favicon(with: faviconURL)
}
var homePageURL = feed.homePageURL
var homePageURL = webFeed.homePageURL
if homePageURL == nil {
// Base homePageURL off feedURL if needed. Wont always be accurate, but is good enough.
if let feedURL = URL(string: feed.url), let scheme = feedURL.scheme, let host = feedURL.host {
if let feedURL = URL(string: webFeed.url), let scheme = feedURL.scheme, let host = feedURL.host {
homePageURL = scheme + "://" + host + "/"
}
}
@ -89,16 +89,16 @@ final class FaviconDownloader {
return nil
}
func faviconAsIcon(for feed: Feed) -> IconImage? {
func faviconAsIcon(for webFeed: WebFeed) -> IconImage? {
if let image = cache[feed] {
if let image = cache[webFeed] {
return image
}
if let iconImage = favicon(for: feed), let imageData = iconImage.image.dataRepresentation() {
if let iconImage = favicon(for: webFeed), let imageData = iconImage.image.dataRepresentation() {
if let scaledImage = RSImage.scaledForIcon(imageData) {
let scaledIconImage = IconImage(scaledImage)
cache[feed] = scaledIconImage
cache[webFeed] = scaledIconImage
return scaledIconImage
}
}

View File

@ -14,16 +14,16 @@ final class FaviconGenerator {
private static var faviconGeneratorCache = [String: IconImage]() // feedURL: RSImage
static func favicon(_ feed: Feed) -> IconImage {
static func favicon(_ webFeed: WebFeed) -> IconImage {
if let favicon = FaviconGenerator.faviconGeneratorCache[feed.url] {
if let favicon = FaviconGenerator.faviconGeneratorCache[webFeed.url] {
return favicon
}
let colorHash = ColorHash(feed.url)
let colorHash = ColorHash(webFeed.url)
if let favicon = AppAssets.faviconTemplateImage.maskWithColor(color: colorHash.color.cgColor) {
let iconImage = IconImage(favicon)
FaviconGenerator.faviconGeneratorCache[feed.url] = iconImage
FaviconGenerator.faviconGeneratorCache[webFeed.url] = iconImage
return iconImage
} else {
return IconImage(AppAssets.faviconTemplateImage)

View File

@ -15,10 +15,10 @@ import RSParser
extension Notification.Name {
static let FeedIconDidBecomeAvailable = Notification.Name("FeedIconDidBecomeAvailableNotification") // UserInfoKey.feed
static let WebFeedIconDidBecomeAvailable = Notification.Name("WebFeedIconDidBecomeAvailableNotification") // UserInfoKey.feed
}
public final class FeedIconDownloader {
public final class WebFeedIconDownloader {
private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
@ -45,8 +45,8 @@ public final class FeedIconDownloader {
}()
private var urlsInProgress = Set<String>()
private var cache = [Feed: IconImage]()
private var waitingForFeedURLs = [String: Feed]()
private var cache = [WebFeed: IconImage]()
private var waitingForFeedURLs = [String: WebFeed]()
init(imageDownloader: ImageDownloader, folder: String) {
self.imageDownloader = imageDownloader
@ -58,10 +58,10 @@ public final class FeedIconDownloader {
}
func resetCache() {
cache = [Feed: IconImage]()
cache = [WebFeed: IconImage]()
}
func icon(for feed: Feed) -> IconImage? {
func icon(for feed: WebFeed) -> IconImage? {
if let cachedImage = cache[feed] {
return cachedImage
@ -120,9 +120,9 @@ public final class FeedIconDownloader {
}
private extension FeedIconDownloader {
private extension WebFeedIconDownloader {
func icon(forHomePageURL homePageURL: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
func icon(forHomePageURL homePageURL: String, feed: WebFeed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
if homePagesWithNoIconURLCache.contains(homePageURL) || homePagesWithUglyIcons.contains(homePageURL) {
imageResultBlock(nil)
@ -137,7 +137,7 @@ private extension FeedIconDownloader {
findIconURLForHomePageURL(homePageURL, feed: feed)
}
func icon(forURL url: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
func icon(forURL url: String, feed: WebFeed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
waitingForFeedURLs[url] = feed
guard let imageData = imageDownloader.image(for: url) else {
imageResultBlock(nil)
@ -146,11 +146,11 @@ private extension FeedIconDownloader {
RSImage.scaledForIcon(imageData, imageResultBlock: imageResultBlock)
}
func postFeedIconDidBecomeAvailableNotification(_ feed: Feed) {
func postFeedIconDidBecomeAvailableNotification(_ feed: WebFeed) {
DispatchQueue.main.async {
let userInfo: [AnyHashable: Any] = [UserInfoKey.feed: feed]
NotificationCenter.default.post(name: .FeedIconDidBecomeAvailable, object: self, userInfo: userInfo)
let userInfo: [AnyHashable: Any] = [UserInfoKey.webFeed: feed]
NotificationCenter.default.post(name: .WebFeedIconDidBecomeAvailable, object: self, userInfo: userInfo)
}
}
@ -166,7 +166,7 @@ private extension FeedIconDownloader {
homePageToIconURLCacheDirty = true
}
func findIconURLForHomePageURL(_ homePageURL: String, feed: Feed) {
func findIconURLForHomePageURL(_ homePageURL: String, feed: WebFeed) {
guard !urlsInProgress.contains(homePageURL) else {
return
@ -183,7 +183,7 @@ private extension FeedIconDownloader {
}
}
func pullIconURL(from metadata: RSHTMLMetadata, homePageURL: String, feed: Feed) {
func pullIconURL(from metadata: RSHTMLMetadata, homePageURL: String, feed: WebFeed) {
if let url = metadata.bestWebsiteIconURL() {
cacheIconURL(for: homePageURL, url)
@ -216,11 +216,11 @@ private extension FeedIconDownloader {
}
func queueSaveHomePageToIconURLCacheIfNeeded() {
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded))
WebFeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded))
}
func queueHomePagesWithNoIconURLCacheIfNeeded() {
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded))
WebFeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded))
}
func saveHomePageToIconURLCache() {

View File

@ -13,7 +13,7 @@ protocol SortableArticle {
var sortableName: String { get }
var sortableDate: Date { get }
var sortableArticleID: String { get }
var sortableFeedID: String { get }
var sortableWebFeedID: String { get }
}
struct ArticleSorter {
@ -34,7 +34,7 @@ struct ArticleSorter {
sortByDateDirection: ComparisonResult) -> [T] {
// Group articles by "feed-feedID" - feed ID is used to differentiate between
// two feeds that have the same name
let groupedArticles = Dictionary(grouping: articles) { "\($0.sortableName.lowercased())-\($0.sortableFeedID)" }
let groupedArticles = Dictionary(grouping: articles) { "\($0.sortableName.lowercased())-\($0.sortableWebFeedID)" }
return groupedArticles
.sorted { $0.key < $1.key }
.flatMap { (tuple) -> [T] in

View File

@ -11,7 +11,7 @@ import RSTree
import Articles
import Account
final class FeedTreeControllerDelegate: TreeControllerDelegate {
final class WebFeedTreeControllerDelegate: TreeControllerDelegate {
func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
@ -29,7 +29,7 @@ final class FeedTreeControllerDelegate: TreeControllerDelegate {
}
}
private extension FeedTreeControllerDelegate {
private extension WebFeedTreeControllerDelegate {
func childNodesForRootNode(_ rootNode: Node) -> [Node]? {
@ -52,7 +52,7 @@ private extension FeedTreeControllerDelegate {
let container = containerNode.representedObject as! Container
var children = [AnyObject]()
children.append(contentsOf: Array(container.topLevelFeeds))
children.append(contentsOf: Array(container.topLevelWebFeeds))
if let folders = container.folders {
children.append(contentsOf: Array(folders))
}
@ -78,8 +78,8 @@ private extension FeedTreeControllerDelegate {
func createNode(representedObject: Any, parent: Node) -> Node? {
if let feed = representedObject as? Feed {
return createNode(feed: feed, parent: parent)
if let webFeed = representedObject as? WebFeed {
return createNode(webFeed: webFeed, parent: parent)
}
if let folder = representedObject as? Folder {
return createNode(folder: folder, parent: parent)
@ -91,9 +91,9 @@ private extension FeedTreeControllerDelegate {
return nil
}
func createNode(feed: Feed, parent: Node) -> Node {
func createNode(webFeed: WebFeed, parent: Node) -> Node {
return parent.createChildNode(feed)
return parent.createChildNode(webFeed)
}
func createNode(folder: Folder, parent: Node) -> Node {

View File

@ -17,7 +17,7 @@ struct UserInfoKey {
static let articles = "articles"
static let navigationKeyPressed = "navigationKeyPressed"
static let objects = "objects"
static let feed = "feed"
static let webFeed = "webFeed"
static let url = "url"
static let author = "author"
static let articlePath = "articlePath"

View File

@ -25,8 +25,8 @@ final class UserNotificationManager: NSObject {
}
for article in articles {
if !article.status.read, let feed = article.feed, feed.isNotifyAboutNewArticles ?? false {
sendNotification(feed: feed, article: article)
if !article.status.read, let webFeed = article.webFeed, webFeed.isNotifyAboutNewArticles ?? false {
sendNotification(webFeed: webFeed, article: article)
}
}
}
@ -43,10 +43,10 @@ final class UserNotificationManager: NSObject {
private extension UserNotificationManager {
private func sendNotification(feed: Feed, article: Article) {
private func sendNotification(webFeed: WebFeed, article: Article) {
let content = UNMutableNotificationContent()
content.title = feed.nameForDisplay
content.title = webFeed.nameForDisplay
content.body = ArticleStringFormatter.truncatedTitle(article)
if content.body.isEmpty {
content.body = ArticleStringFormatter.truncatedSummary(article)

View File

@ -10,7 +10,7 @@
<!--Add Feed-->
<scene sceneID="2Tc-JN-edX">
<objects>
<tableViewController storyboardIdentifier="AddFeedViewController" id="7aE-6a-iP7" customClass="AddFeedViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<tableViewController storyboardIdentifier="AddWebFeedViewController" id="7aE-6a-iP7" customClass="AddWebFeedViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" scrollEnabled="NO" dataMode="static" style="grouped" separatorStyle="default" allowsSelection="NO" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="1" sectionFooterHeight="5" id="D0S-TM-mtm">
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -78,7 +78,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vaV-kY-CaE">
<rect key="frame" x="313" y="11.999999999999998" width="42" height="20.333333333333329"/>
<rect key="frame" x="313" y="12" width="42" height="20"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@ -166,12 +166,12 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="1Ce-E7-rG2">
<rect key="frame" x="112.66666666666669" y="108" width="150" height="32"/>
<rect key="frame" x="97.666666666666686" y="108" width="180" height="32"/>
<constraints>
<constraint firstAttribute="width" constant="150" id="IEO-pf-4PB"/>
<constraint firstAttribute="width" constant="180" id="IEO-pf-4PB"/>
</constraints>
<segments>
<segment title="Feed"/>
<segment title="Web Feed"/>
<segment title="Folder"/>
</segments>
<connections>
@ -282,7 +282,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mxj-Bw-Jfx">
<rect key="frame" x="313" y="11.999999999999998" width="42" height="20.333333333333329"/>
<rect key="frame" x="313" y="12" width="42" height="20"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>

View File

@ -105,15 +105,15 @@ private extension AddContainerViewController {
func switchToFeed() {
guard !(currentViewController is AddFeedViewController) else {
guard !(currentViewController is AddWebFeedViewController) else {
return
}
navigationItem.title = NSLocalizedString("Add Feed", comment: "Add Feed")
navigationItem.title = NSLocalizedString("Add Web Feed", comment: "Add Web Feed")
resetUI()
hideCurrentController()
let addFeedController = UIStoryboard.add.instantiateController(ofType: AddFeedViewController.self)
let addFeedController = UIStoryboard.add.instantiateController(ofType: AddWebFeedViewController.self)
addFeedController.initialFeed = initialFeed
addFeedController.initialFeedName = initialFeedName

View File

@ -12,7 +12,7 @@ import RSCore
import RSTree
import RSParser
class AddFeedViewController: UITableViewController, AddContainerViewControllerChild {
class AddWebFeedViewController: UITableViewController, AddContainerViewControllerChild {
@IBOutlet private weak var urlTextField: UITextField!
@IBOutlet private weak var nameTextField: UITextField!
@ -91,7 +91,7 @@ class AddFeedViewController: UITableViewController, AddContainerViewControllerCh
account = containerAccount
}
if account!.hasFeed(withURL: url.absoluteString) {
if account!.hasWebFeed(withURL: url.absoluteString) {
presentError(AccountError.createErrorAlreadySubscribed)
return
}
@ -102,14 +102,14 @@ class AddFeedViewController: UITableViewController, AddContainerViewControllerCh
BatchUpdate.shared.start()
account!.createFeed(url: url.absoluteString, name: feedName, container: container) { result in
account!.createWebFeed(url: url.absoluteString, name: feedName, container: container) { result in
BatchUpdate.shared.end()
switch result {
case .success(let feed):
self.delegate?.processingDidEnd()
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed])
case .failure(let error):
self.presentError(error)
self.delegate?.processingDidCancel()
@ -135,7 +135,7 @@ class AddFeedViewController: UITableViewController, AddContainerViewControllerCh
}
extension AddFeedViewController: UIPickerViewDataSource, UIPickerViewDelegate {
extension AddWebFeedViewController: UIPickerViewDataSource, UIPickerViewDelegate {
func numberOfComponents(in pickerView: UIPickerView) ->Int {
return 1
@ -155,7 +155,7 @@ extension AddFeedViewController: UIPickerViewDataSource, UIPickerViewDelegate {
}
extension AddFeedViewController: UITextFieldDelegate {
extension AddWebFeedViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()

View File

@ -40,7 +40,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
var faviconDownloader: FaviconDownloader!
var imageDownloader: ImageDownloader!
var authorAvatarDownloader: AuthorAvatarDownloader!
var feedIconDownloader: FeedIconDownloader!
var webFeedIconDownloader: WebFeedIconDownloader!
var unreadCount = 0 {
didSet {
@ -192,7 +192,7 @@ private extension AppDelegate {
let tempFolder = tempDir.absoluteString
let tempFolderPath = tempFolder.suffix(from: tempFolder.index(tempFolder.startIndex, offsetBy: 7))
feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader, folder: String(tempFolderPath))
webFeedIconDownloader = WebFeedIconDownloader(imageDownloader: imageDownloader, folder: String(tempFolderPath))
}
private func initializeHomeScreenQuickActions() {

View File

@ -160,10 +160,10 @@
</objects>
<point key="canvasLocation" x="863.768115942029" y="-591.29464285714278"/>
</scene>
<!--Feed Inspector View Controller-->
<!--Web Feed Inspector View Controller-->
<scene sceneID="jnI-2I-AcU">
<objects>
<tableViewController storyboardIdentifier="FeedInspectorViewControllelr" id="lEH-bG-pQW" customClass="FeedInspectorViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<tableViewController storyboardIdentifier="FeedInspectorViewControllelr" id="lEH-bG-pQW" customClass="WebFeedInspectorViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="26V-ZC-Q2R">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>

View File

@ -1,5 +1,5 @@
//
// FeedInspectorViewController.swift
// WebFeedInspectorViewController.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 11/6/19.
@ -9,11 +9,11 @@
import UIKit
import Account
class FeedInspectorViewController: UITableViewController {
class WebFeedInspectorViewController: UITableViewController {
static let preferredContentSizeForFormSheetDisplay = CGSize(width: 460.0, height: 500.0)
var feed: Feed!
var webFeed: WebFeed!
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var notifyAboutNewArticlesSwitch: UISwitch!
@IBOutlet weak var alwaysShowReaderViewSwitch: UISwitch!
@ -22,49 +22,49 @@ class FeedInspectorViewController: UITableViewController {
private var headerView: InspectorIconHeaderView?
private var iconImage: IconImage {
if let feedIcon = appDelegate.feedIconDownloader.icon(for: feed) {
if let feedIcon = appDelegate.webFeedIconDownloader.icon(for: webFeed) {
return feedIcon
}
if let favicon = appDelegate.faviconDownloader.favicon(for: feed) {
if let favicon = appDelegate.faviconDownloader.favicon(for: webFeed) {
return favicon
}
return FaviconGenerator.favicon(feed)
return FaviconGenerator.favicon(webFeed)
}
override func viewDidLoad() {
tableView.register(InspectorIconHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader")
navigationItem.title = feed.nameForDisplay
nameTextField.text = feed.nameForDisplay
navigationItem.title = webFeed.nameForDisplay
nameTextField.text = webFeed.nameForDisplay
notifyAboutNewArticlesSwitch.setOn(feed.isNotifyAboutNewArticles ?? false, animated: false)
alwaysShowReaderViewSwitch.setOn(feed.isArticleExtractorAlwaysOn ?? false, animated: false)
notifyAboutNewArticlesSwitch.setOn(webFeed.isNotifyAboutNewArticles ?? false, animated: false)
alwaysShowReaderViewSwitch.setOn(webFeed.isArticleExtractorAlwaysOn ?? false, animated: false)
homePageLabel.text = feed.homePageURL
feedURLLabel.text = feed.url
homePageLabel.text = webFeed.homePageURL
feedURLLabel.text = webFeed.url
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
}
override func viewDidDisappear(_ animated: Bool) {
if nameTextField.text != feed.nameForDisplay {
if nameTextField.text != webFeed.nameForDisplay {
let nameText = nameTextField.text ?? ""
let newName = nameText.isEmpty ? (feed.name ?? NSLocalizedString("Untitled", comment: "Feed name")) : nameText
feed.rename(to: newName) { _ in }
let newName = nameText.isEmpty ? (webFeed.name ?? NSLocalizedString("Untitled", comment: "Feed name")) : nameText
webFeed.rename(to: newName) { _ in }
}
}
// MARK: Notifications
@objc func feedIconDidBecomeAvailable(_ notification: Notification) {
@objc func webFeedIconDidBecomeAvailable(_ notification: Notification) {
headerView?.iconView.iconImage = iconImage
}
@IBAction func notifyAboutNewArticlesChanged(_ sender: Any) {
feed.isNotifyAboutNewArticles = notifyAboutNewArticlesSwitch.isOn
webFeed.isNotifyAboutNewArticles = notifyAboutNewArticlesSwitch.isOn
}
@IBAction func alwaysShowReaderViewChanged(_ sender: Any) {
feed.isArticleExtractorAlwaysOn = alwaysShowReaderViewSwitch.isOn
webFeed.isArticleExtractorAlwaysOn = alwaysShowReaderViewSwitch.isOn
}
@IBAction func done(_ sender: Any) {
@ -75,7 +75,7 @@ class FeedInspectorViewController: UITableViewController {
// MARK: Table View
extension FeedInspectorViewController {
extension WebFeedInspectorViewController {
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return section == 0 ? 64.0 : super.tableView(tableView, heightForHeaderInSection: section)
@ -95,7 +95,7 @@ extension FeedInspectorViewController {
// MARK: UITextFieldDelegate
extension FeedInspectorViewController: UITextFieldDelegate {
extension WebFeedInspectorViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()

View File

@ -1,5 +1,5 @@
//
// AddFeedIntentHandler.swift
// AddWebFeedIntentHandler.swift
// NetNewsWire
//
// Created by Maurice Parker on 10/18/19.
@ -9,7 +9,7 @@
import Intents
import Account
public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
public class AddWebFeedIntentHandler: NSObject, AddWebFeedIntentHandling {
override init() {
super.init()
@ -18,7 +18,7 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
}
}
public func resolveUrl(for intent: AddFeedIntent, with completion: @escaping (AddFeedUrlResolutionResult) -> Void) {
public func resolveUrl(for intent: AddWebFeedIntent, with completion: @escaping (AddWebFeedUrlResolutionResult) -> Void) {
guard let url = intent.url else {
completion(.unsupported(forReason: .required))
return
@ -26,16 +26,16 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
completion(.success(with: url))
}
public func provideAccountNameOptions(for intent: AddFeedIntent, with completion: @escaping ([String]?, Error?) -> Void) {
public func provideAccountNameOptions(for intent: AddWebFeedIntent, with completion: @escaping ([String]?, Error?) -> Void) {
DispatchQueue.main.async {
let accountNames = AccountManager.shared.activeAccounts.compactMap { $0.nameForDisplay }
completion(accountNames, nil)
}
}
public func resolveAccountName(for intent: AddFeedIntent, with completion: @escaping (AddFeedAccountNameResolutionResult) -> Void) {
public func resolveAccountName(for intent: AddWebFeedIntent, with completion: @escaping (AddWebFeedAccountNameResolutionResult) -> Void) {
guard let accountName = intent.accountName else {
completion(AddFeedAccountNameResolutionResult.notRequired())
completion(AddWebFeedAccountNameResolutionResult.notRequired())
return
}
DispatchQueue.main.async {
@ -47,7 +47,7 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
}
}
public func provideFolderNameOptions(for intent: AddFeedIntent, with completion: @escaping ([String]?, Error?) -> Void) {
public func provideFolderNameOptions(for intent: AddWebFeedIntent, with completion: @escaping ([String]?, Error?) -> Void) {
DispatchQueue.main.async {
guard let accountName = intent.accountName, let account = AccountManager.shared.findActiveAccount(forDisplayName: accountName) else {
completion([String](), nil)
@ -59,9 +59,9 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
}
}
public func resolveFolderName(for intent: AddFeedIntent, with completion: @escaping (AddFeedFolderNameResolutionResult) -> Void) {
public func resolveFolderName(for intent: AddWebFeedIntent, with completion: @escaping (AddWebFeedFolderNameResolutionResult) -> Void) {
guard let accountName = intent.accountName, let folderName = intent.folderName else {
completion(AddFeedFolderNameResolutionResult.notRequired())
completion(AddWebFeedFolderNameResolutionResult.notRequired())
return
}
@ -79,9 +79,9 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
}
}
public func handle(intent: AddFeedIntent, completion: @escaping (AddFeedIntentResponse) -> Void) {
public func handle(intent: AddWebFeedIntent, completion: @escaping (AddWebFeedIntentResponse) -> Void) {
guard let url = intent.url else {
completion(AddFeedIntentResponse(code: .failure, userActivity: nil))
completion(AddWebFeedIntentResponse(code: .failure, userActivity: nil))
return
}
@ -96,7 +96,7 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
}()
guard let validAccount = account else {
completion(AddFeedIntentResponse(code: .failure, userActivity: nil))
completion(AddWebFeedIntentResponse(code: .failure, userActivity: nil))
return
}
@ -109,22 +109,22 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
}()
guard let validContainer = container else {
completion(AddFeedIntentResponse(code: .failure, userActivity: nil))
completion(AddWebFeedIntentResponse(code: .failure, userActivity: nil))
return
}
validAccount.createFeed(url: url.absoluteString, name: nil, container: validContainer) { result in
validAccount.createWebFeed(url: url.absoluteString, name: nil, container: validContainer) { result in
switch result {
case .success:
completion(AddFeedIntentResponse(code: .success, userActivity: nil))
completion(AddWebFeedIntentResponse(code: .success, userActivity: nil))
case .failure(let error):
switch error {
case AccountError.createErrorNotFound:
completion(AddFeedIntentResponse(code: .feedNotFound, userActivity: nil))
completion(AddWebFeedIntentResponse(code: .feedNotFound, userActivity: nil))
case AccountError.createErrorAlreadySubscribed:
completion(AddFeedIntentResponse(code: .alreadySubscribed, userActivity: nil))
completion(AddWebFeedIntentResponse(code: .alreadySubscribed, userActivity: nil))
default:
completion(AddFeedIntentResponse(code: .failure, userActivity: nil))
completion(AddWebFeedIntentResponse(code: .failure, userActivity: nil))
}
}
}

View File

@ -9,11 +9,11 @@
<key>INIntentDefinitionNamespace</key>
<string>U6u7RF</string>
<key>INIntentDefinitionSystemVersion</key>
<string>19A602</string>
<string>19B88</string>
<key>INIntentDefinitionToolsBuildVersion</key>
<string>11B41</string>
<string>11B53</string>
<key>INIntentDefinitionToolsVersion</key>
<string>11.2</string>
<string>11.2.1</string>
<key>INIntents</key>
<array>
<dict>
@ -22,7 +22,7 @@
<key>INIntentConfigurable</key>
<true/>
<key>INIntentDescription</key>
<string>Add a feed</string>
<string>Add a web feed</string>
<key>INIntentDescriptionID</key>
<string>IuAbef</string>
<key>INIntentIneligibleForSuggestions</key>
@ -59,7 +59,7 @@
</dict>
</dict>
<key>INIntentName</key>
<string>AddFeed</string>
<string>AddWebFeed</string>
<key>INIntentParameters</key>
<array>
<dict>
@ -303,7 +303,7 @@
</array>
</dict>
<key>INIntentTitle</key>
<string>Add Feed</string>
<string>Add Web Feed</string>
<key>INIntentTitleID</key>
<string>oV681v</string>
<key>INIntentType</key>

View File

@ -34,7 +34,7 @@
<array/>
<key>IntentsSupported</key>
<array>
<string>AddFeedIntent</string>
<string>AddWebFeedIntent</string>
</array>
</dict>
<key>NSExtensionPointIdentifier</key>

View File

@ -12,8 +12,8 @@ class IntentHandler: INExtension {
override func handler(for intent: INIntent) -> Any {
switch intent {
case is AddFeedIntent:
return AddFeedIntentHandler()
case is AddWebFeedIntent:
return AddWebFeedIntentHandler()
default:
fatalError("Unhandled intent type: \(intent)")
}

View File

@ -117,7 +117,7 @@ private extension KeyboardManager {
static func globalAuxilaryKeyCommands() -> [UIKeyCommand] {
var keys = [UIKeyCommand]()
let addNewFeedTitle = NSLocalizedString("New Feed", comment: "New Feed")
let addNewFeedTitle = NSLocalizedString("New Web Feed", comment: "New Web Feed")
keys.append(KeyboardManager.createKeyCommand(title: addNewFeedTitle, action: "addNewFeed:", input: "n", modifiers: [.command]))
let addNewFolderTitle = NSLocalizedString("New Folder", comment: "New Folder")

View File

@ -34,12 +34,12 @@ class MasterFeedDataSource: UITableViewDiffableDataSource<Node, Node> {
guard let node = itemIdentifier(for: indexPath) else {
return false
}
return node.representedObject is Feed
return node.representedObject is WebFeed
}
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
guard let sourceNode = itemIdentifier(for: sourceIndexPath), let feed = sourceNode.representedObject as? Feed else {
guard let sourceNode = itemIdentifier(for: sourceIndexPath), let webFeed = sourceNode.representedObject as? WebFeed else {
return
}
@ -67,15 +67,15 @@ class MasterFeedDataSource: UITableViewDiffableDataSource<Node, Node> {
}
}()
// Move the Feed
// Move the Web Feed
guard let source = sourceNode.parent?.representedObject as? Container, let destination = destParentNode?.representedObject as? Container else {
return
}
if sameAccount(sourceNode, destParentNode!) {
moveFeedInAccount(feed: feed, sourceContainer: source, destinationContainer: destination)
moveWebFeedInAccount(feed: webFeed, sourceContainer: source, destinationContainer: destination)
} else {
moveFeedBetweenAccounts(feed: feed, sourceContainer: source, destinationContainer: destination)
moveWebFeedBetweenAccounts(feed: webFeed, sourceContainer: source, destinationContainer: destination)
}
}
@ -94,8 +94,8 @@ class MasterFeedDataSource: UITableViewDiffableDataSource<Node, Node> {
return account
} else if let folder = node.representedObject as? Folder {
return folder.account
} else if let feed = node.representedObject as? Feed {
return feed.account
} else if let webFeed = node.representedObject as? WebFeed {
return webFeed.account
} else {
return nil
}
@ -106,9 +106,9 @@ class MasterFeedDataSource: UITableViewDiffableDataSource<Node, Node> {
return nodeAccount(node)?.accountID
}
func moveFeedInAccount(feed: Feed, sourceContainer: Container, destinationContainer: Container) {
func moveWebFeedInAccount(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) {
BatchUpdate.shared.start()
sourceContainer.account?.moveFeed(feed, from: sourceContainer, to: destinationContainer) { result in
sourceContainer.account?.moveWebFeed(feed, from: sourceContainer, to: destinationContainer) { result in
BatchUpdate.shared.end()
switch result {
case .success:
@ -119,15 +119,15 @@ class MasterFeedDataSource: UITableViewDiffableDataSource<Node, Node> {
}
}
func moveFeedBetweenAccounts(feed: Feed, sourceContainer: Container, destinationContainer: Container) {
func moveWebFeedBetweenAccounts(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) {
if let existingFeed = destinationContainer.account?.existingFeed(withURL: feed.url) {
if let existingFeed = destinationContainer.account?.existingWebFeed(withURL: feed.url) {
BatchUpdate.shared.start()
destinationContainer.account?.addFeed(existingFeed, to: destinationContainer) { result in
destinationContainer.account?.addWebFeed(existingFeed, to: destinationContainer) { result in
switch result {
case .success:
sourceContainer.account?.removeFeed(feed, from: sourceContainer) { result in
sourceContainer.account?.removeWebFeed(feed, from: sourceContainer) { result in
BatchUpdate.shared.end()
switch result {
case .success:
@ -145,10 +145,10 @@ class MasterFeedDataSource: UITableViewDiffableDataSource<Node, Node> {
} else {
BatchUpdate.shared.start()
destinationContainer.account?.createFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in
destinationContainer.account?.createWebFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in
switch result {
case .success:
sourceContainer.account?.removeFeed(feed, from: sourceContainer) { result in
sourceContainer.account?.removeWebFeed(feed, from: sourceContainer) { result in
BatchUpdate.shared.end()
switch result {
case .success:

View File

@ -53,9 +53,9 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(feedMetadataDidChange(_:)), name: .FeedMetadataDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(webFeedMetadataDidChange(_:)), name: .WebFeedMetadataDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(userDidAddFeed(_:)), name: .UserDidAddFeed, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
@ -117,31 +117,31 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
applyToAvailableCells(configureIcon)
}
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
guard let webFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed else {
return
}
applyToCellsForRepresentedObject(feed, configureIcon(_:_:))
applyToCellsForRepresentedObject(webFeed, configureIcon(_:_:))
}
@objc func feedSettingDidChange(_ note: Notification) {
guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else {
@objc func webFeedSettingDidChange(_ note: Notification) {
guard let webFeed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else {
return
}
if key == Feed.FeedSettingKey.homePageURL || key == Feed.FeedSettingKey.faviconURL {
configureCellsForRepresentedObject(feed)
if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL {
configureCellsForRepresentedObject(webFeed)
}
}
@objc func feedMetadataDidChange(_ note: Notification) {
@objc func webFeedMetadataDidChange(_ note: Notification) {
reloadAllVisibleCells()
}
@objc func userDidAddFeed(_ notification: Notification) {
guard let feed = notification.userInfo?[UserInfoKey.feed] as? Feed else {
guard let webFeed = notification.userInfo?[UserInfoKey.webFeed] as? WebFeed else {
return
}
discloseFeed(feed, animated: true)
discloseFeed(webFeed, animated: true)
}
@objc func contentSizeCategoryDidChange(_ note: Notification) {
@ -237,13 +237,13 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
renameAction.backgroundColor = UIColor.systemOrange
actions.append(renameAction)
if let feed = dataSource.itemIdentifier(for: indexPath)?.representedObject as? Feed {
if let webFeed = dataSource.itemIdentifier(for: indexPath)?.representedObject as? WebFeed {
let moreTitle = NSLocalizedString("More", comment: "More")
let moreAction = UIContextualAction(style: .normal, title: moreTitle) { [weak self] (action, view, completionHandler) in
if let self = self {
let alert = UIAlertController(title: feed.nameForDisplay, message: nil, preferredStyle: .actionSheet)
let alert = UIAlertController(title: webFeed.nameForDisplay, message: nil, preferredStyle: .actionSheet)
if let popoverController = alert.popoverPresentationController {
popoverController.sourceView = view
popoverController.sourceRect = CGRect(x: view.frame.size.width/2, y: view.frame.size.height/2, width: 1, height: 1)
@ -288,7 +288,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
guard let node = dataSource.itemIdentifier(for: indexPath), !(node.representedObject is PseudoFeed) else {
return nil
}
if node.representedObject is Feed {
if node.representedObject is WebFeed {
return makeFeedContextMenu(indexPath: indexPath, includeDeleteRename: true)
} else {
return makeFolderContextMenu(indexPath: indexPath)
@ -508,7 +508,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
}
}
func discloseFeed(_ feed: Feed, animated: Bool, completion: (() -> Void)? = nil) {
func discloseFeed(_ feed: WebFeed, animated: Bool, completion: (() -> Void)? = nil) {
guard let node = coordinator.rootNode.descendantNodeRepresentingObject(feed as AnyObject) else {
completion?()
@ -674,14 +674,14 @@ private extension MasterFeedViewController {
}
func imageFor(_ node: Node) -> IconImage? {
if let feed = node.representedObject as? Feed {
if let webFeed = node.representedObject as? WebFeed {
let feedIconImage = appDelegate.feedIconDownloader.icon(for: feed)
let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed)
if feedIconImage != nil {
return feedIconImage
}
if let faviconImage = appDelegate.faviconDownloader.favicon(for: feed) {
if let faviconImage = appDelegate.faviconDownloader.favicon(for: webFeed) {
return faviconImage
}
@ -742,7 +742,7 @@ private extension MasterFeedViewController {
if let folder = node.representedObject as? Folder {
return folder.account
}
if let feed = node.representedObject as? Feed {
if let feed = node.representedObject as? WebFeed {
return feed.account
}
return nil
@ -843,7 +843,7 @@ private extension MasterFeedViewController {
func copyFeedPageAction(indexPath: IndexPath) -> UIAction? {
guard let node = dataSource.itemIdentifier(for: indexPath),
let feed = node.representedObject as? Feed,
let feed = node.representedObject as? WebFeed,
let url = URL(string: feed.url) else {
return nil
}
@ -857,7 +857,7 @@ private extension MasterFeedViewController {
func copyFeedPageAlertAction(indexPath: IndexPath, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let node = dataSource.itemIdentifier(for: indexPath),
let feed = node.representedObject as? Feed,
let feed = node.representedObject as? WebFeed,
let url = URL(string: feed.url) else {
return nil
}
@ -872,7 +872,7 @@ private extension MasterFeedViewController {
func copyHomePageAction(indexPath: IndexPath) -> UIAction? {
guard let node = dataSource.itemIdentifier(for: indexPath),
let feed = node.representedObject as? Feed,
let feed = node.representedObject as? WebFeed,
let homePageURL = feed.homePageURL,
let url = URL(string: homePageURL) else {
return nil
@ -887,7 +887,7 @@ private extension MasterFeedViewController {
func copyHomePageAlertAction(indexPath: IndexPath, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let node = dataSource.itemIdentifier(for: indexPath),
let feed = node.representedObject as? Feed,
let feed = node.representedObject as? WebFeed,
let homePageURL = feed.homePageURL,
let url = URL(string: homePageURL) else {
return nil
@ -919,7 +919,7 @@ private extension MasterFeedViewController {
}
func getInfoAction(indexPath: IndexPath) -> UIAction? {
guard let node = dataSource.itemIdentifier(for: indexPath), let feed = node.representedObject as? Feed else {
guard let node = dataSource.itemIdentifier(for: indexPath), let feed = node.representedObject as? WebFeed else {
return nil
}
@ -947,7 +947,7 @@ private extension MasterFeedViewController {
}
func getInfoAlertAction(indexPath: IndexPath, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let node = dataSource.itemIdentifier(for: indexPath), let feed = node.representedObject as? Feed else {
guard let node = dataSource.itemIdentifier(for: indexPath), let feed = node.representedObject as? WebFeed else {
return nil
}
@ -979,7 +979,7 @@ private extension MasterFeedViewController {
return
}
if let feed = node.representedObject as? Feed {
if let feed = node.representedObject as? WebFeed {
feed.rename(to: name) { result in
switch result {
case .success:
@ -1023,7 +1023,7 @@ private extension MasterFeedViewController {
if let folder = deleteNode.representedObject as? Folder {
ActivityManager.cleanUp(folder)
} else if let feed = deleteNode.representedObject as? Feed {
} else if let feed = deleteNode.representedObject as? WebFeed {
ActivityManager.cleanUp(feed)
}

View File

@ -42,7 +42,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
@ -316,16 +316,16 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
}
}
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
titleView?.iconView.iconImage = coordinator.timelineIconImage
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
guard let feed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed else {
return
}
tableView.indexPathsForVisibleRows?.forEach { indexPath in
guard let article = dataSource.itemIdentifier(for: indexPath) else {
return
}
if article.feed == feed, let cell = tableView.cellForRow(at: indexPath) as? MasterTimelineTableViewCell, let image = iconImageFor(article) {
if article.webFeed == feed, let cell = tableView.cellForRow(at: indexPath) as? MasterTimelineTableViewCell, let image = iconImageFor(article) {
cell.setIconImage(image)
}
}
@ -402,7 +402,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
let prototypeID = "prototype"
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date())
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize)
@ -464,7 +464,7 @@ private extension MasterTimelineViewController {
titleView.label.text = coordinator.timelineName
updateTitleUnreadCount()
if coordinator.timelineFetcher is Feed {
if coordinator.timelineFetcher is WebFeed {
titleView.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
let tap = UITapGestureRecognizer(target: self, action:#selector(showFeedInspector(_:)))
titleView.addGestureRecognizer(tap)
@ -525,7 +525,7 @@ private extension MasterTimelineViewController {
let showFeedNames = coordinator.showFeedNames
let showIcon = coordinator.showIcons && iconImage != nil
cell.cellData = MasterTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, iconImage: iconImage, showIcon: showIcon, featuredImage: featuredImage, numberOfLines: numberOfTextLines, iconSize: iconSize)
cell.cellData = MasterTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.webFeed?.nameForDisplay, iconImage: iconImage, showIcon: showIcon, featuredImage: featuredImage, numberOfLines: numberOfTextLines, iconSize: iconSize)
}
@ -590,36 +590,36 @@ private extension MasterTimelineViewController {
}
func discloseFeedAction(_ article: Article) -> UIAction? {
guard let feed = article.feed else { return nil }
guard let webFeed = article.webFeed else { return nil }
let title = NSLocalizedString("Go to Feed", comment: "Go to Feed")
let action = UIAction(title: title, image: AppAssets.openInSidebarImage) { [weak self] action in
self?.coordinator.discloseFeed(feed, animated: true)
self?.coordinator.discloseFeed(webFeed, animated: true)
}
return action
}
func discloseFeedAlertAction(_ article: Article, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let feed = article.feed else { return nil }
guard let webFeed = article.webFeed else { return nil }
let title = NSLocalizedString("Go to Feed", comment: "Go to Feed")
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
self?.coordinator.discloseFeed(feed, animated: true)
self?.coordinator.discloseFeed(webFeed, animated: true)
completionHandler(true)
}
return action
}
func markAllInFeedAsReadAction(_ article: Article) -> UIAction? {
guard let feed = article.feed else { return nil }
guard let webFeed = article.webFeed else { return nil }
let articles = Array(feed.fetchArticles())
let articles = Array(webFeed.fetchArticles())
guard articles.canMarkAllAsRead() else {
return nil
}
let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command")
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, webFeed.nameForDisplay) as String
let action = UIAction(title: title, image: AppAssets.markAllInFeedAsReadImage) { [weak self] action in
self?.coordinator.markAllAsRead(articles)
@ -628,15 +628,15 @@ private extension MasterTimelineViewController {
}
func markAllInFeedAsReadAlertAction(_ article: Article, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let feed = article.feed else { return nil }
guard let webFeed = article.webFeed else { return nil }
let articles = Array(feed.fetchArticles())
let articles = Array(webFeed.fetchArticles())
guard articles.canMarkAllAsRead() else {
return nil
}
let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Mark All as Read in Feed")
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, webFeed.nameForDisplay) as String
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
self?.coordinator.markAllAsRead(articles)

View File

@ -60,7 +60,7 @@
<string>Grant permission to save images from the article.</string>
<key>NSUserActivityTypes</key>
<array>
<string>AddFeedIntent</string>
<string>AddWebFeedIntent</string>
<string>NextUnread</string>
<string>ReadArticle</string>
<string>SelectFeed</string>

View File

@ -88,7 +88,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
set { AppDefaults.displayUndoAvailableTip = newValue }
}
private let treeControllerDelegate = FeedTreeControllerDelegate()
private let treeControllerDelegate = WebFeedTreeControllerDelegate()
private let treeController: TreeController
var stateRestorationActivity: NSUserActivity? {
@ -110,9 +110,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
private(set) var currentFeedIndexPath: IndexPath?
var timelineIconImage: IconImage? {
if let feed = timelineFetcher as? Feed {
if let feed = timelineFetcher as? WebFeed {
let feedIconImage = appDelegate.feedIconDownloader.icon(for: feed)
let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: feed)
if feedIconImage != nil {
return feedIconImage
}
@ -135,7 +135,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
timelineMiddleIndexPath = nil
if timelineFetcher is Feed {
if timelineFetcher is WebFeed {
showFeedNames = false
} else {
showFeedNames = true
@ -442,7 +442,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
}
@objc func accountDidDownloadArticles(_ note: Notification) {
guard let feeds = note.userInfo?[Account.UserInfoKey.feeds] as? Set<Feed> else {
guard let feeds = note.userInfo?[Account.UserInfoKey.webFeeds] as? Set<WebFeed> else {
return
}
@ -607,7 +607,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
masterTimelineViewController?.updateArticleSelection(animated: animated)
if article!.feed?.isArticleExtractorAlwaysOn ?? false {
if article!.webFeed?.isArticleExtractorAlwaysOn ?? false {
startArticleExtractorForCurrentLink()
currentArticleViewController.state = .loading
} else {
@ -780,7 +780,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
markArticlesWithUndo([article], statusKey: .starred, flag: !article.status.starred)
}
func discloseFeed(_ feed: Feed, animated: Bool, completion: (() -> Void)? = nil) {
func discloseFeed(_ feed: WebFeed, animated: Bool, completion: (() -> Void)? = nil) {
masterFeedViewController.discloseFeed(feed, animated: animated) {
completion?()
}
@ -806,19 +806,19 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
}
func showFeedInspector() {
guard let feed = timelineFetcher as? Feed else {
guard let feed = timelineFetcher as? WebFeed else {
return
}
showFeedInspector(for: feed)
}
func showFeedInspector(for feed: Feed) {
func showFeedInspector(for feed: WebFeed) {
let feedInspectorNavController =
UIStoryboard.inspector.instantiateViewController(identifier: "FeedInspectorNavigationViewController") as! UINavigationController
let feedInspectorController = feedInspectorNavController.topViewController as! FeedInspectorViewController
let feedInspectorController = feedInspectorNavController.topViewController as! WebFeedInspectorViewController
feedInspectorNavController.modalPresentationStyle = .formSheet
feedInspectorNavController.preferredContentSize = FeedInspectorViewController.preferredContentSizeForFormSheetDisplay
feedInspectorController.feed = feed
feedInspectorNavController.preferredContentSize = WebFeedInspectorViewController.preferredContentSizeForFormSheetDisplay
feedInspectorController.webFeed = feed
rootSplitViewController.present(feedInspectorNavController, animated: true)
}
@ -878,7 +878,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
func homePageURLForFeed(_ indexPath: IndexPath) -> URL? {
guard let node = nodeFor(indexPath),
let feed = node.representedObject as? Feed,
let feed = node.representedObject as? WebFeed,
let homePageURL = feed.homePageURL,
let url = URL(string: homePageURL) else {
return nil
@ -1460,19 +1460,19 @@ private extension SceneCoordinator {
return false
}
func timelineFetcherContainsAnyFeed(_ feeds: Set<Feed>) -> Bool {
func timelineFetcherContainsAnyFeed(_ feeds: Set<WebFeed>) -> Bool {
// Return true if theres a match or if a folder contains (recursively) one of feeds
if let feed = timelineFetcher as? Feed {
if let feed = timelineFetcher as? WebFeed {
for oneFeed in feeds {
if feed.feedID == oneFeed.feedID || feed.url == oneFeed.url {
if feed.webFeedID == oneFeed.webFeedID || feed.url == oneFeed.url {
return true
}
}
} else if let folder = timelineFetcher as? Folder {
for oneFeed in feeds {
if folder.hasFeed(with: oneFeed.feedID) || folder.hasFeed(withURL: oneFeed.url) {
if folder.hasWebFeed(with: oneFeed.webFeedID) || folder.hasWebFeed(withURL: oneFeed.url) {
return true
}
}
@ -1648,11 +1648,11 @@ private extension SceneCoordinator {
selectFeed(indexPath, animated: false)
}
case .feed(let accountID, let feedID):
guard let accountNode = findAccountNode(accountID: accountID), let feedNode = findFeedNode(feedID: feedID, beginningAt: accountNode) else {
case .webFeed(let accountID, let webFeedID):
guard let accountNode = findAccountNode(accountID: accountID), let feedNode = findWebFeedNode(webFeedID: webFeedID, beginningAt: accountNode) else {
return
}
if let feed = feedNode.representedObject as? Feed {
if let feed = feedNode.representedObject as? WebFeed {
discloseFeed(feed, animated: false)
}
@ -1664,16 +1664,16 @@ private extension SceneCoordinator {
let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any],
let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String,
let accountName = articlePathUserInfo[ArticlePathKey.accountName] as? String,
let feedID = articlePathUserInfo[ArticlePathKey.feedID] as? String,
let webFeedID = articlePathUserInfo[ArticlePathKey.webFeedID] as? String,
let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String else {
return
}
guard let accountNode = findAccountNode(accountID: accountID, accountName: accountName), let feedNode = findFeedNode(feedID: feedID, beginningAt: accountNode) else {
guard let accountNode = findAccountNode(accountID: accountID, accountName: accountName), let feedNode = findWebFeedNode(webFeedID: webFeedID, beginningAt: accountNode) else {
return
}
discloseFeed(feedNode.representedObject as! Feed, animated: false) {
discloseFeed(feedNode.representedObject as! WebFeed, animated: false) {
if let article = self.articles.first(where: { $0.articleID == articleID }) {
self.selectArticle(article)
}
@ -1699,8 +1699,8 @@ private extension SceneCoordinator {
return nil
}
func findFeedNode(feedID: String, beginningAt startingNode: Node) -> Node? {
if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.feedID == feedID }) {
func findWebFeedNode(webFeedID: String, beginningAt startingNode: Node) -> Node? {
if let node = startingNode.descendantNode(where: { ($0.representedObject as? WebFeed)?.webFeedID == webFeedID }) {
return node
}
return nil

View File

@ -67,7 +67,7 @@ private extension TimelinePreviewTableViewController {
let prototypeID = "prototype"
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date())
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
let iconImage = IconImage(AppAssets.faviconTemplateImage.withTintColor(AppAssets.secondaryAccentColor))

View File

@ -108,14 +108,14 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont
account = containerAccount
}
if let urlString = url?.absoluteString, account!.hasFeed(withURL: urlString) {
if let urlString = url?.absoluteString, account!.hasWebFeed(withURL: urlString) {
presentError(AccountError.createErrorAlreadySubscribed)
return
}
let feedName = contentText.isEmpty ? nil : contentText
account!.createFeed(url: url!.absoluteString, name: feedName, container: container!) { result in
account!.createWebFeed(url: url!.absoluteString, name: feedName, container: container!) { result in
switch result {
case .success: