Continue renaming webFeed to feed.

This commit is contained in:
Brent Simmons
2024-11-02 11:08:58 -07:00
parent 2d3ef95619
commit 4fa4c6a541
95 changed files with 1156 additions and 1160 deletions

View File

@@ -57,7 +57,7 @@ public enum FetchType {
case unread(_: Int? = nil)
case today(_: Int? = nil)
case folder(Folder, Bool)
case webFeed(Feed)
case feed(Feed)
case articleIDs(Set<String>)
case search(String)
case searchWithArticleIDs(String, Set<String>)
@@ -74,7 +74,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
public static let articleIDs = "articleIDs" // StatusesDidChange
public static let statusKey = "statusKey" // StatusesDidChange
public static let statusFlag = "statusFlag" // StatusesDidChange
public static let webFeeds = "webFeeds" // AccountDidDownloadArticles, StatusesDidChange
public static let feeds = "feeds" // AccountDidDownloadArticles, StatusesDidChange
public static let syncErrors = "syncErrors" // AccountsDidFailToSyncWithErrors
}
@@ -162,23 +162,23 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return nil
}
private var webFeedDictionariesNeedUpdate = true
private var _idToWebFeedDictionary = [String: Feed]()
var idToWebFeedDictionary: [String: Feed] {
if webFeedDictionariesNeedUpdate {
rebuildWebFeedDictionaries()
private var feedDictionariesNeedUpdate = true
private var _idToFeedDictionary = [String: Feed]()
var idToFeedDictionary: [String: Feed] {
if feedDictionariesNeedUpdate {
rebuildFeedDictionaries()
}
return _idToWebFeedDictionary
return _idToFeedDictionary
}
private var _externalIDToWebFeedDictionary = [String: Feed]()
var externalIDToWebFeedDictionary: [String: Feed] {
if webFeedDictionariesNeedUpdate {
rebuildWebFeedDictionaries()
private var _externalIDToFeedDictionary = [String: Feed]()
var externalIDToFeedDictionary: [String: Feed] {
if feedDictionariesNeedUpdate {
rebuildFeedDictionaries()
}
return _externalIDToWebFeedDictionary
return _externalIDToFeedDictionary
}
var flattenedWebFeedURLs: Set<String> {
var flattenedFeedURLs: Set<String> {
return Set(flattenedFeeds().map({ $0.url }))
}
@@ -214,8 +214,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
private var unreadCounts = [String: Int]() // [feedID: Int]
private var _flattenedWebFeeds = Set<Feed>()
private var flattenedWebFeedsNeedUpdate = true
private var _flattenedFeeds = Set<Feed>()
private var flattenedFeedsNeedUpdate = 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)
@@ -225,9 +225,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
}
private lazy var webFeedMetadataFile = WebFeedMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("FeedMetadata.plist"), account: self)
typealias WebFeedMetadataDictionary = [String: WebFeedMetadata]
var webFeedMetadata = WebFeedMetadataDictionary()
private lazy var feedMetadataFile = FeedMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("FeedMetadata.plist"), account: self)
typealias FeedMetadataDictionary = [String: FeedMetadata]
var feedMetadata = FeedMetadataDictionary()
public var unreadCount = 0 {
didSet {
@@ -323,7 +323,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
NotificationCenter.default.addObserver(self, selector: #selector(childrenDidChange(_:)), name: .ChildrenDidChange, object: nil)
metadataFile.load()
webFeedMetadataFile.load()
feedMetadataFile.load()
opmlFile.load()
var shouldHandleRetentionPolicyChange = false
@@ -339,7 +339,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
self.metadata.performedApril2020RetentionPolicyChange = true
}
self.database.cleanupDatabaseAtStartup(subscribedToWebFeedIDs: self.flattenedFeeds().webFeedIDs())
self.database.cleanupDatabaseAtStartup(subscribedToFeedIDs: self.flattenedFeeds().feedIDs())
self.fetchAllUnreadCounts()
}
@@ -495,7 +495,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
public func save() {
metadataFile.save()
webFeedMetadataFile.save()
feedMetadataFile.save()
opmlFile.save()
}
@@ -506,13 +506,13 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
func addOPMLItems(_ items: [RSOPMLItem]) {
for item in items {
if let feedSpecifier = item.feedSpecifier {
addWebFeed(newWebFeed(with: feedSpecifier))
addFeed(newFeed(with: feedSpecifier))
} else {
if let title = item.titleFromAttributes, let folder = ensureFolder(with: title) {
folder.externalID = item.attributes?["nnw_externalID"] as? String
item.children?.forEach { itemChild in
if let feedSpecifier = itemChild.feedSpecifier {
folder.addWebFeed(newWebFeed(with: feedSpecifier))
folder.addFeed(newFeed(with: feedSpecifier))
}
}
}
@@ -535,13 +535,13 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return existingFolder(withExternalID: externalID)
}
func existingContainers(withWebFeed webFeed: Feed) -> [Container] {
func existingContainers(withFeed feed: Feed) -> [Container] {
var containers = [Container]()
if topLevelFeeds.contains(webFeed) {
if topLevelFeeds.contains(feed) {
containers.append(self)
}
folders?.forEach { folder in
if folder.topLevelFeeds.contains(webFeed) {
if folder.topLevelFeeds.contains(feed) {
containers.append(folder)
}
}
@@ -586,9 +586,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return folders?.first(where: { $0.externalID == externalID })
}
func newWebFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> Feed {
func newFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> Feed {
let feedURL = opmlFeedSpecifier.feedURL
let metadata = webFeedMetadata(feedURL: feedURL, feedID: feedURL)
let metadata = feedMetadata(feedURL: feedURL, feedID: feedURL)
let feed = Feed(account: self, url: opmlFeedSpecifier.feedURL, metadata: metadata)
if let feedTitle = opmlFeedSpecifier.title {
if feed.name == nil {
@@ -599,35 +599,35 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
public func addFeed(_ feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.addWebFeed(for: self, with: feed, to: container, completion: completion)
delegate.addFeed(for: self, with: feed, to: container, completion: completion)
}
public func createFeed(url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
delegate.createWebFeed(for: self, url: url, name: name, container: container, validateFeed: validateFeed, completion: completion)
delegate.createFeed(for: self, url: url, name: name, container: container, validateFeed: validateFeed, completion: completion)
}
func createWebFeed(with name: String?, url: String, webFeedID: String, homePageURL: String?) -> Feed {
let metadata = webFeedMetadata(feedURL: url, feedID: webFeedID)
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)
feed.name = name
feed.homePageURL = homePageURL
return feed
}
public func removeWebFeed(_ feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.removeWebFeed(for: self, with: feed, from: container, completion: completion)
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 moveFeed(_ feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.moveWebFeed(for: self, with: feed, from: from, to: to, completion: completion)
delegate.moveFeed(for: self, with: feed, from: from, to: to, completion: completion)
}
public func renameWebFeed(_ feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.renameWebFeed(for: self, with: feed, to: name, 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 restoreWebFeed(_ feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.restoreWebFeed(for: self, feed: feed, container: container, 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 addFolder(_ name: String, completion: @escaping (Result<Folder, Error>) -> Void) {
@@ -646,8 +646,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
delegate.restoreFolder(for: self, folder: folder, completion: completion)
}
func clearWebFeedMetadata(_ feed: Feed) {
webFeedMetadata[feed.url] = nil
func clearFeedMetadata(_ feed: Feed) {
feedMetadata[feed.url] = nil
}
func addFolder(_ folder: Folder) {
@@ -656,8 +656,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
structureDidChange()
}
public func updateUnreadCounts(for webFeeds: Set<Feed>, completion: VoidCompletionBlock? = nil) {
fetchUnreadCounts(for: webFeeds, completion: completion)
public func updateUnreadCounts(for feeds: Set<Feed>, completion: VoidCompletionBlock? = nil) {
fetchUnreadCounts(for: feeds, completion: completion)
}
public func fetchArticles(_ fetchType: FetchType) throws -> Set<Article> {
@@ -674,8 +674,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} else {
return try fetchArticles(folder: folder)
}
case .webFeed(let webFeed):
return try fetchArticles(webFeed: webFeed)
case .feed(let feed):
return try fetchArticles(feed: feed)
case .articleIDs(let articleIDs):
return try fetchArticles(articleIDs: articleIDs)
case .search(let searchString):
@@ -699,8 +699,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} else {
return fetchArticlesAsync(folder: folder, completion)
}
case .webFeed(let webFeed):
fetchArticlesAsync(webFeed: webFeed, completion)
case .feed(let feed):
fetchArticlesAsync(feed: feed, completion)
case .articleIDs(let articleIDs):
fetchArticlesAsync(articleIDs: articleIDs, completion)
case .search(let searchString):
@@ -711,15 +711,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
public func fetchUnreadCountForToday(_ completion: @escaping SingleUnreadCountCompletionBlock) {
database.fetchUnreadCountForToday(for: flattenedFeeds().webFeedIDs(), completion: completion)
database.fetchUnreadCountForToday(for: flattenedFeeds().feedIDs(), completion: completion)
}
public func fetchUnreadCountForStarredArticles(_ completion: @escaping SingleUnreadCountCompletionBlock) {
database.fetchStarredAndUnreadCount(for: flattenedFeeds().webFeedIDs(), completion: completion)
database.fetchStarredAndUnreadCount(for: flattenedFeeds().feedIDs(), completion: completion)
}
public func fetchCountForStarredArticles() throws -> Int {
return try database.fetchStarredArticlesCount(flattenedFeeds().webFeedIDs())
return try database.fetchStarredArticlesCount(flattenedFeeds().feedIDs())
}
public func fetchUnreadArticleIDs(_ completion: @escaping ArticleIDsCompletionBlock) {
@@ -735,43 +735,43 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
database.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate(completion)
}
public func unreadCount(for webFeed: Feed) -> Int {
return unreadCounts[webFeed.feedID] ?? 0
public func unreadCount(for feed: Feed) -> Int {
return unreadCounts[feed.feedID] ?? 0
}
public func setUnreadCount(_ unreadCount: Int, for webFeed: Feed) {
unreadCounts[webFeed.feedID] = unreadCount
public func setUnreadCount(_ unreadCount: Int, for feed: Feed) {
unreadCounts[feed.feedID] = unreadCount
}
public func structureDidChange() {
// Feeds were added or deleted. Or folders added or deleted.
// Or feeds inside folders were added or deleted.
opmlFile.markAsDirty()
flattenedWebFeedsNeedUpdate = true
webFeedDictionariesNeedUpdate = true
flattenedFeedsNeedUpdate = true
feedDictionariesNeedUpdate = true
}
func update(_ webFeed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping UpdateArticlesCompletionBlock) {
func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping UpdateArticlesCompletionBlock) {
// Used only by an On My Mac or iCloud account.
precondition(Thread.isMainThread)
precondition(type == .onMyMac || type == .cloudKit)
webFeed.takeSettings(from: parsedFeed)
feed.takeSettings(from: parsedFeed)
let parsedItems = parsedFeed.items
guard !parsedItems.isEmpty else {
completion(.success(ArticleChanges()))
return
}
update(webFeed.feedID, with: parsedItems, completion: completion)
update(feed.feedID, with: parsedItems, completion: completion)
}
func update(_ webFeedID: String, with parsedItems: Set<ParsedItem>, deleteOlder: Bool = true, completion: @escaping UpdateArticlesCompletionBlock) {
func update(_ feedID: String, with parsedItems: Set<ParsedItem>, deleteOlder: Bool = true, completion: @escaping UpdateArticlesCompletionBlock) {
// Used only by an On My Mac or iCloud account.
precondition(Thread.isMainThread)
precondition(type == .onMyMac || type == .cloudKit)
database.update(with: parsedItems, webFeedID: webFeedID, deleteOlder: deleteOlder) { updateArticlesResult in
database.update(with: parsedItems, feedID: feedID, deleteOlder: deleteOlder) { updateArticlesResult in
switch updateArticlesResult {
case .success(let articleChanges):
self.sendNotificationAbout(articleChanges)
@@ -782,16 +782,16 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
}
func update(webFeedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping DatabaseCompletionBlock) {
func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping DatabaseCompletionBlock) {
// Used only by syncing systems.
precondition(Thread.isMainThread)
precondition(type != .onMyMac && type != .cloudKit)
guard !webFeedIDsAndItems.isEmpty else {
guard !feedIDsAndItems.isEmpty else {
completion(nil)
return
}
database.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: defaultRead) { updateArticlesResult in
database.update(feedIDsAndItems: feedIDsAndItems, defaultRead: defaultRead) { updateArticlesResult in
switch updateArticlesResult {
case .success(let newAndUpdatedArticles):
self.sendNotificationAbout(newAndUpdatedArticles)
@@ -901,36 +901,36 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
public func flattenedFeeds() -> Set<Feed> {
assert(Thread.isMainThread)
if flattenedWebFeedsNeedUpdate {
updateFlattenedWebFeeds()
if flattenedFeedsNeedUpdate {
updateFlattenedFeeds()
}
return _flattenedWebFeeds
return _flattenedFeeds
}
public func removeWebFeed(_ webFeed: Feed) {
topLevelFeeds.remove(webFeed)
public func removeFeed(_ feed: Feed) {
topLevelFeeds.remove(feed)
structureDidChange()
postChildrenDidChangeNotification()
}
public func removeFeeds(_ webFeeds: Set<Feed>) {
guard !webFeeds.isEmpty else {
public func removeFeeds(_ feeds: Set<Feed>) {
guard !feeds.isEmpty else {
return
}
topLevelFeeds.subtract(webFeeds)
topLevelFeeds.subtract(feeds)
structureDidChange()
postChildrenDidChangeNotification()
}
public func addWebFeed(_ webFeed: Feed) {
topLevelFeeds.insert(webFeed)
public func addFeed(_ feed: Feed) {
topLevelFeeds.insert(feed)
structureDidChange()
postChildrenDidChangeNotification()
}
func addFeedIfNotInAnyFolder(_ webFeed: Feed) {
if !flattenedFeeds().contains(webFeed) {
addWebFeed(webFeed)
func addFeedIfNotInAnyFolder(_ feed: Feed) {
if !flattenedFeeds().contains(feed) {
addFeed(feed)
}
}
@@ -976,8 +976,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
@objc func batchUpdateDidPerform(_ note: Notification) {
flattenedWebFeedsNeedUpdate = true
rebuildWebFeedDictionaries()
flattenedFeedsNeedUpdate = true
rebuildFeedDictionaries()
updateUnreadCount()
}
@@ -1023,11 +1023,11 @@ extension Account: AccountMetadataDelegate {
// MARK: - FeedMetadataDelegate
extension Account: WebFeedMetadataDelegate {
extension Account: FeedMetadataDelegate {
func valueDidChange(_ feedMetadata: WebFeedMetadata, key: WebFeedMetadata.CodingKeys) {
webFeedMetadataFile.markAsDirty()
guard let feed = existingWebFeed(withWebFeedID: feedMetadata.feedID) else {
func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys) {
feedMetadataFile.markAsDirty()
guard let feed = existingFeed(withFeedID: feedMetadata.feedID) else {
return
}
feed.postFeedSettingDidChangeNotification(key)
@@ -1039,11 +1039,11 @@ extension Account: WebFeedMetadataDelegate {
private extension Account {
func fetchStarredArticles(limit: Int?) throws -> Set<Article> {
return try database.fetchStarredArticles(flattenedFeeds().webFeedIDs(), limit)
return try database.fetchStarredArticles(flattenedFeeds().feedIDs(), limit)
}
func fetchStarredArticlesAsync(limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
database.fetchedStarredArticlesAsync(flattenedFeeds().webFeedIDs(), limit, completion)
database.fetchedStarredArticlesAsync(flattenedFeeds().feedIDs(), limit, completion)
}
func fetchUnreadArticles(limit: Int?) throws -> Set<Article> {
@@ -1055,11 +1055,11 @@ private extension Account {
}
func fetchTodayArticles(limit: Int?) throws -> Set<Article> {
return try database.fetchTodayArticles(flattenedFeeds().webFeedIDs(), limit)
return try database.fetchTodayArticles(flattenedFeeds().feedIDs(), limit)
}
func fetchTodayArticlesAsync(limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
database.fetchTodayArticlesAsync(flattenedFeeds().webFeedIDs(), limit, completion)
database.fetchTodayArticlesAsync(flattenedFeeds().feedIDs(), limit, completion)
}
func fetchArticles(folder: Folder) throws -> Set<Article> {
@@ -1078,17 +1078,17 @@ private extension Account {
fetchUnreadArticlesAsync(forContainer: folder, limit: nil, completion)
}
func fetchArticles(webFeed: Feed) throws -> Set<Article> {
let articles = try database.fetchArticles(webFeed.feedID)
validateUnreadCount(webFeed, articles)
func fetchArticles(feed: Feed) throws -> Set<Article> {
let articles = try database.fetchArticles(feed.feedID)
validateUnreadCount(feed, articles)
return articles
}
func fetchArticlesAsync(webFeed: Feed, _ completion: @escaping ArticleSetResultBlock) {
database.fetchArticlesAsync(webFeed.feedID) { [weak self] articleSetResult in
func fetchArticlesAsync(feed: Feed, _ completion: @escaping ArticleSetResultBlock) {
database.fetchArticlesAsync(feed.feedID) { [weak self] articleSetResult in
switch articleSetResult {
case .success(let articles):
self?.validateUnreadCount(webFeed, articles)
self?.validateUnreadCount(feed, articles)
completion(.success(articles))
case .failure(let databaseError):
completion(.failure(databaseError))
@@ -1097,7 +1097,7 @@ private extension Account {
}
func fetchArticlesMatching(_ searchString: String) throws -> Set<Article> {
return try database.fetchArticlesMatching(searchString, flattenedFeeds().webFeedIDs())
return try database.fetchArticlesMatching(searchString, flattenedFeeds().feedIDs())
}
func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>) throws -> Set<Article> {
@@ -1105,7 +1105,7 @@ private extension Account {
}
func fetchArticlesMatchingAsync(_ searchString: String, _ completion: @escaping ArticleSetResultBlock) {
database.fetchArticlesMatchingAsync(searchString, flattenedFeeds().webFeedIDs(), completion)
database.fetchArticlesMatchingAsync(searchString, flattenedFeeds().feedIDs(), completion)
}
func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
@@ -1120,25 +1120,25 @@ private extension Account {
return database.fetchArticlesAsync(articleIDs: articleIDs, completion)
}
func fetchUnreadArticles(webFeed: Feed) throws -> Set<Article> {
let articles = try database.fetchUnreadArticles(Set([webFeed.feedID]), nil)
validateUnreadCount(webFeed, articles)
func fetchUnreadArticles(feed: Feed) throws -> Set<Article> {
let articles = try database.fetchUnreadArticles(Set([feed.feedID]), nil)
validateUnreadCount(feed, articles)
return articles
}
func fetchArticles(forContainer container: Container) throws -> Set<Article> {
let feeds = container.flattenedFeeds()
let articles = try database.fetchArticles(feeds.webFeedIDs())
let articles = try database.fetchArticles(feeds.feedIDs())
validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
return articles
}
func fetchArticlesAsync(forContainer container: Container, _ completion: @escaping ArticleSetResultBlock) {
let webFeeds = container.flattenedFeeds()
database.fetchArticlesAsync(webFeeds.webFeedIDs()) { [weak self] (articleSetResult) in
let feeds = container.flattenedFeeds()
database.fetchArticlesAsync(feeds.feedIDs()) { [weak self] (articleSetResult) in
switch articleSetResult {
case .success(let articles):
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)
self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
completion(.success(articles))
case .failure(let databaseError):
completion(.failure(databaseError))
@@ -1148,8 +1148,8 @@ private extension Account {
func fetchUnreadArticles(forContainer container: Container, limit: Int?) throws -> Set<Article> {
let feeds = container.flattenedFeeds()
let articles = try database.fetchUnreadArticles(feeds.webFeedIDs(), limit)
let articles = try database.fetchUnreadArticles(feeds.feedIDs(), limit)
// We don't validate limit queries because they, by definition, won't correctly match the
// complete unread state for the given container.
if limit == nil {
@@ -1160,15 +1160,15 @@ private extension Account {
}
func fetchUnreadArticlesAsync(forContainer container: Container, limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
let webFeeds = container.flattenedFeeds()
database.fetchUnreadArticlesAsync(webFeeds.webFeedIDs(), limit) { [weak self] (articleSetResult) in
let feeds = container.flattenedFeeds()
database.fetchUnreadArticlesAsync(feeds.feedIDs(), limit) { [weak self] (articleSetResult) in
switch articleSetResult {
case .success(let articles):
// We don't validate limit queries because they, by definition, won't correctly match the
// complete unread state for the given container.
if limit == nil {
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)
self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
}
completion(.success(articles))
@@ -1178,34 +1178,34 @@ private extension Account {
}
}
func validateUnreadCountsAfterFetchingUnreadArticles(_ webFeeds: Set<Feed>, _ articles: Set<Article>) {
func validateUnreadCountsAfterFetchingUnreadArticles(_ feeds: Set<Feed>, _ 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]() // [WebFeedID: Int]
var unreadCountStorage = [String: Int]() // [FeedID: Int]
for article in articles where !article.status.read {
unreadCountStorage[article.webFeedID, default: 0] += 1
unreadCountStorage[article.feedID, default: 0] += 1
}
webFeeds.forEach { (webFeed) in
let unreadCount = unreadCountStorage[webFeed.feedID, default: 0]
webFeed.unreadCount = unreadCount
feeds.forEach { (feed) in
let unreadCount = unreadCountStorage[feed.feedID, default: 0]
feed.unreadCount = unreadCount
}
}
func validateUnreadCount(_ webFeed: Feed, _ articles: Set<Article>) {
func validateUnreadCount(_ feed: Feed, _ 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.webFeed == webFeed && !article.status.read {
if article.feed == feed && !article.status.read {
return result + 1
}
return result
}
webFeed.unreadCount = feedUnreadCount
feed.unreadCount = feedUnreadCount
}
}
@@ -1213,29 +1213,29 @@ private extension Account {
private extension Account {
func webFeedMetadata(feedURL: String, feedID: String) -> WebFeedMetadata {
if let d = webFeedMetadata[feedURL] {
func feedMetadata(feedURL: String, feedID: String) -> FeedMetadata {
if let d = feedMetadata[feedURL] {
assert(d.delegate === self)
return d
}
let d = WebFeedMetadata(feedID: feedID)
let d = FeedMetadata(feedID: feedID)
d.delegate = self
webFeedMetadata[feedURL] = d
feedMetadata[feedURL] = d
return d
}
func updateFlattenedWebFeeds() {
func updateFlattenedFeeds() {
var feeds = Set<Feed>()
feeds.formUnion(topLevelFeeds)
for folder in folders! {
feeds.formUnion(folder.flattenedFeeds())
}
_flattenedWebFeeds = feeds
flattenedWebFeedsNeedUpdate = false
_flattenedFeeds = feeds
flattenedFeedsNeedUpdate = false
}
func rebuildWebFeedDictionaries() {
func rebuildFeedDictionaries() {
var idDictionary = [String: Feed]()
var externalIDDictionary = [String: Feed]()
@@ -1246,9 +1246,9 @@ private extension Account {
}
}
_idToWebFeedDictionary = idDictionary
_externalIDToWebFeedDictionary = externalIDDictionary
webFeedDictionariesNeedUpdate = false
_idToFeedDictionary = idDictionary
_externalIDToFeedDictionary = externalIDDictionary
feedDictionariesNeedUpdate = false
}
func updateUnreadCount() {
@@ -1263,7 +1263,7 @@ private extension Account {
}
func noteStatusesForArticlesDidChange(_ articles: Set<Article>) {
let feeds = Set(articles.compactMap { $0.webFeed })
let feeds = Set(articles.compactMap { $0.feed })
let statuses = Set(articles.map { $0.status })
let articleIDs = Set(articles.map { $0.articleID })
@@ -1271,7 +1271,7 @@ private extension Account {
// 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.articleIDs: articleIDs, UserInfoKey.webFeeds: feeds])
NotificationCenter.default.post(name: .StatusesDidChange, object: self, userInfo: [UserInfoKey.statuses: statuses, UserInfoKey.articles: articles, UserInfoKey.articleIDs: articleIDs, UserInfoKey.feeds: feeds])
}
func noteStatusesForArticleIDsDidChange(articleIDs: Set<String>, statusKey: ArticleStatus.Key, flag: Bool) {
@@ -1313,8 +1313,8 @@ private extension Account {
}
func fetchUnreadCounts(_ feeds: Set<Feed>, _ completion: VoidCompletionBlock?) {
let webFeedIDs = Set(feeds.map { $0.feedID })
database.fetchUnreadCounts(for: webFeedIDs) { result in
let feedIDs = Set(feeds.map { $0.feedID })
database.fetchUnreadCounts(for: feedIDs) { result in
if let unreadCountDictionary = try? result.get() {
self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: feeds)
}
@@ -1351,13 +1351,13 @@ private extension Account {
}
func sendNotificationAbout(_ articleChanges: ArticleChanges) {
var webFeeds = Set<Feed>()
var feeds = Set<Feed>()
if let newArticles = articleChanges.newArticles {
webFeeds.formUnion(Set(newArticles.compactMap { $0.webFeed }))
feeds.formUnion(Set(newArticles.compactMap { $0.feed }))
}
if let updatedArticles = articleChanges.updatedArticles {
webFeeds.formUnion(Set(updatedArticles.compactMap { $0.webFeed }))
feeds.formUnion(Set(updatedArticles.compactMap { $0.feed }))
}
var shouldSendNotification = false
@@ -1380,11 +1380,11 @@ private extension Account {
}
if shouldUpdateUnreadCounts {
self.updateUnreadCounts(for: webFeeds)
self.updateUnreadCounts(for: feeds)
}
if shouldSendNotification {
userInfo[UserInfoKey.webFeeds] = webFeeds
userInfo[UserInfoKey.feeds] = feeds
NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo)
}
}
@@ -1394,12 +1394,12 @@ private extension Account {
extension Account {
public func existingWebFeed(withWebFeedID webFeedID: String) -> Feed? {
return idToWebFeedDictionary[webFeedID]
public func existingFeed(withFeedID feedID: String) -> Feed? {
return idToFeedDictionary[feedID]
}
public func existingWebFeed(withExternalID externalID: String) -> Feed? {
return externalIDToWebFeedDictionary[externalID]
public func existingFeed(withExternalID externalID: String) -> Feed? {
return externalIDToFeedDictionary[externalID]
}
}

View File

@@ -36,13 +36,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 createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void)
func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
func addWebFeed(for account: Account, with: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void)
func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void)
func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void)
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, 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 restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void)
func restoreFeed(for account: Account, feed: Feed, 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, completion: @escaping (Result<Void, Error>) -> Void)

View File

@@ -203,9 +203,9 @@ public final class AccountManager: UnreadCountProvider {
if let account = existingAccount(with: accountID) {
return account.existingFolder(with: folderName)
}
case .webFeed(let accountID, let webFeedID):
case .feed(let accountID, let feedID):
if let account = existingAccount(with: accountID) {
return account.existingWebFeed(withWebFeedID: webFeedID)
return account.existingFeed(withFeedID: feedID)
}
default:
break
@@ -330,7 +330,7 @@ public final class AccountManager: UnreadCountProvider {
public func anyAccountHasAtLeastOneFeed() -> Bool {
for account in activeAccounts {
if account.hasAtLeastOneWebFeed() {
if account.hasAtLeastOneFeed() {
return true
}
}

View File

@@ -21,7 +21,7 @@ public protocol ArticleFetcher {
extension Feed: ArticleFetcher {
public func fetchArticles() throws -> Set<Article> {
return try account?.fetchArticles(.webFeed(self)) ?? Set<Article>()
return try account?.fetchArticles(.feed(self)) ?? Set<Article>()
}
public func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
@@ -30,7 +30,7 @@ extension Feed: ArticleFetcher {
completion(.success(Set<Article>()))
return
}
account.fetchArticlesAsync(.webFeed(self), completion)
account.fetchArticlesAsync(.feed(self), completion)
}
public func fetchUnreadArticles() throws -> Set<Article> {
@@ -43,7 +43,7 @@ extension Feed: ArticleFetcher {
completion(.success(Set<Article>()))
return
}
account.fetchArticlesAsync(.webFeed(self)) { articleSetResult in
account.fetchArticlesAsync(.feed(self)) { articleSetResult in
switch articleSetResult {
case .success(let articles):
completion(.success(articles.unreadArticles()))

View File

@@ -175,7 +175,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
}
func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
func createFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
guard let url = URL(string: urlString) else {
completion(.failure(LocalAccountDelegateError.invalidParameter))
return
@@ -183,13 +183,13 @@ final class CloudKitAccountDelegate: AccountDelegate {
let editedName = name == nil || name!.isEmpty ? nil : name
createRSSWebFeed(for: account, url: url, editedName: editedName, container: container, validateFeed: validateFeed, completion: completion)
createRSSFeed(for: account, url: url, editedName: editedName, container: container, validateFeed: validateFeed, completion: completion)
}
func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
let editedName = name.isEmpty ? nil : name
refreshProgress.addToNumberOfTasksAndRemaining(1)
accountZone.renameWebFeed(feed, editedName: editedName) { result in
accountZone.renameFeed(feed, editedName: editedName) { result in
self.refreshProgress.completeTask()
switch result {
case .success:
@@ -202,19 +202,19 @@ final class CloudKitAccountDelegate: AccountDelegate {
}
}
func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
removeWebFeedFromCloud(for: account, with: feed, from: container) { result in
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
removeFeedFromCloud(for: account, with: feed, from: container) { result in
switch result {
case .success:
account.clearWebFeedMetadata(feed)
container.removeWebFeed(feed)
account.clearFeedMetadata(feed)
container.removeFeed(feed)
completion(.success(()))
case .failure(let error):
switch error {
case CloudKitZoneError.corruptAccount:
// We got into a bad state and should remove the feed to clear up the bad data
account.clearWebFeedMetadata(feed)
container.removeWebFeed(feed)
account.clearFeedMetadata(feed)
container.removeFeed(feed)
default:
completion(.failure(error))
}
@@ -222,14 +222,14 @@ final class CloudKitAccountDelegate: AccountDelegate {
}
}
func moveWebFeed(for account: Account, with feed: Feed, from fromContainer: Container, to toContainer: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func moveFeed(for account: Account, with feed: Feed, from fromContainer: Container, to toContainer: Container, completion: @escaping (Result<Void, Error>) -> Void) {
refreshProgress.addToNumberOfTasksAndRemaining(1)
accountZone.moveWebFeed(feed, from: fromContainer, to: toContainer) { result in
accountZone.moveFeed(feed, from: fromContainer, to: toContainer) { result in
self.refreshProgress.completeTask()
switch result {
case .success:
fromContainer.removeWebFeed(feed)
toContainer.addWebFeed(feed)
fromContainer.removeFeed(feed)
toContainer.addFeed(feed)
completion(.success(()))
case .failure(let error):
self.processAccountError(account, error)
@@ -238,13 +238,13 @@ final class CloudKitAccountDelegate: AccountDelegate {
}
}
func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
refreshProgress.addToNumberOfTasksAndRemaining(1)
accountZone.addWebFeed(feed, to: container) { result in
accountZone.addFeed(feed, to: container) { result in
self.refreshProgress.completeTask()
switch result {
case .success:
container.addWebFeed(feed)
container.addFeed(feed)
completion(.success(()))
case .failure(let error):
self.processAccountError(account, error)
@@ -253,8 +253,8 @@ final class CloudKitAccountDelegate: AccountDelegate {
}
}
func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
switch result {
case .success:
completion(.success(()))
@@ -301,21 +301,21 @@ final class CloudKitAccountDelegate: AccountDelegate {
func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
refreshProgress.addToNumberOfTasksAndRemaining(2)
accountZone.findWebFeedExternalIDs(for: folder) { result in
accountZone.findFeedExternalIDs(for: folder) { result in
self.refreshProgress.completeTask()
switch result {
case .success(let webFeedExternalIDs):
case .success(let feedExternalIDs):
let webFeeds = webFeedExternalIDs.compactMap { account.existingWebFeed(withExternalID: $0) }
let feeds = feedExternalIDs.compactMap { account.existingFeed(withExternalID: $0) }
let group = DispatchGroup()
var errorOccurred = false
for webFeed in webFeeds {
for feed in feeds {
group.enter()
self.removeWebFeedFromCloud(for: account, with: webFeed, from: folder) { result in
self.removeFeedFromCloud(for: account, with: feed, from: folder) { result in
group.leave()
if case .failure(let error) = result {
os_log(.error, log: self.log, "Remove folder, remove webfeed error: %@.", error.localizedDescription)
os_log(.error, log: self.log, "Remove folder, remove feed error: %@.", error.localizedDescription)
errorOccurred = true
}
}
@@ -374,7 +374,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
folder.topLevelFeeds.remove(feed)
group.enter()
self.restoreWebFeed(for: account, feed: feed, container: folder) { result in
self.restoreFeed(for: account, feed: feed, container: folder) { result in
self.refreshProgress.completeTask()
group.leave()
switch result {
@@ -485,8 +485,8 @@ private extension CloudKitAccountDelegate {
accountZone.fetchChangesInZone() { result in
self.refreshProgress.completeTask()
let webFeeds = account.flattenedFeeds()
self.refreshProgress.addToNumberOfTasksAndRemaining(webFeeds.count)
let feeds = account.flattenedFeeds()
self.refreshProgress.addToNumberOfTasksAndRemaining(feeds.count)
switch result {
case .success:
@@ -495,7 +495,7 @@ private extension CloudKitAccountDelegate {
switch result {
case .success:
self.combinedRefresh(account, webFeeds) { result in
self.combinedRefresh(account, feeds) { result in
self.refreshProgress.clear()
switch result {
case .success:
@@ -518,8 +518,8 @@ private extension CloudKitAccountDelegate {
func standardRefreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
let intialWebFeedsCount = account.flattenedFeeds().count
refreshProgress.addToNumberOfTasksAndRemaining(3 + intialWebFeedsCount)
let intialFeedsCount = account.flattenedFeeds().count
refreshProgress.addToNumberOfTasksAndRemaining(3 + intialFeedsCount)
func fail(_ error: Error) {
self.processAccountError(account, error)
@@ -532,14 +532,14 @@ private extension CloudKitAccountDelegate {
case .success:
self.refreshProgress.completeTask()
let webFeeds = account.flattenedFeeds()
self.refreshProgress.addToNumberOfTasksAndRemaining(webFeeds.count - intialWebFeedsCount)
let feeds = account.flattenedFeeds()
self.refreshProgress.addToNumberOfTasksAndRemaining(feeds.count - intialFeedsCount)
self.refreshArticleStatus(for: account) { result in
switch result {
case .success:
self.refreshProgress.completeTask()
self.combinedRefresh(account, webFeeds) { result in
self.combinedRefresh(account, feeds) { result in
self.sendArticleStatus(for: account, showProgress: true) { _ in
self.refreshProgress.clear()
if case .failure(let error) = result {
@@ -562,12 +562,12 @@ private extension CloudKitAccountDelegate {
}
func combinedRefresh(_ account: Account, _ webFeeds: Set<Feed>, completion: @escaping (Result<Void, Error>) -> Void) {
func combinedRefresh(_ account: Account, _ feeds: Set<Feed>, completion: @escaping (Result<Void, Error>) -> Void) {
let group = DispatchGroup()
group.enter()
refresher.refreshFeeds(webFeeds) {
refresher.refreshFeeds(feeds) {
group.leave()
}
@@ -576,13 +576,13 @@ private extension CloudKitAccountDelegate {
}
}
func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
func createRSSFeed(for account: Account, url: URL, editedName: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
func addDeadFeed() {
let feed = account.createWebFeed(with: editedName, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
container.addWebFeed(feed)
let feed = account.createFeed(with: editedName, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
container.addFeed(feed)
self.accountZone.createWebFeed(url: url.absoluteString,
self.accountZone.createFeed(url: url.absoluteString,
name: editedName,
editedName: nil,
homePageURL: nil,
@@ -594,7 +594,7 @@ private extension CloudKitAccountDelegate {
feed.externalID = externalID
completion(.success(feed))
case .failure(let error):
container.removeWebFeed(feed)
container.removeFeed(feed)
completion(.failure(error))
}
}
@@ -623,9 +623,9 @@ private extension CloudKitAccountDelegate {
return
}
let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
feed.editedName = editedName
container.addWebFeed(feed)
container.addFeed(feed)
InitialFeedDownloader.download(url) { parsedFeed in
self.refreshProgress.completeTask()
@@ -635,7 +635,7 @@ private extension CloudKitAccountDelegate {
switch result {
case .success:
self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString,
self.accountZone.createFeed(url: bestFeedSpecifier.urlString,
name: parsedFeed.title,
editedName: editedName,
homePageURL: parsedFeed.homePageURL,
@@ -648,7 +648,7 @@ private extension CloudKitAccountDelegate {
self.sendNewArticlesToTheCloud(account, feed)
completion(.success(feed))
case .failure(let error):
container.removeWebFeed(feed)
container.removeFeed(feed)
self.refreshProgress.completeTasks(2)
completion(.failure(error))
}
@@ -656,7 +656,7 @@ private extension CloudKitAccountDelegate {
}
case .failure(let error):
container.removeWebFeed(feed)
container.removeFeed(feed)
self.refreshProgress.completeTasks(3)
completion(.failure(error))
}
@@ -664,7 +664,7 @@ private extension CloudKitAccountDelegate {
}
} else {
self.refreshProgress.completeTasks(3)
container.removeWebFeed(feed)
container.removeFeed(feed)
completion(.failure(AccountError.createErrorNotFound))
}
@@ -684,7 +684,7 @@ private extension CloudKitAccountDelegate {
}
func sendNewArticlesToTheCloud(_ account: Account, _ feed: Feed) {
account.fetchArticlesAsync(.webFeed(feed)) { result in
account.fetchArticlesAsync(.feed(feed)) { result in
switch result {
case .success(let articles):
self.storeArticleChanges(new: articles, updated: Set<Article>(), deleted: Set<Article>()) {
@@ -771,17 +771,17 @@ private extension CloudKitAccountDelegate {
}
func removeWebFeedFromCloud(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func removeFeedFromCloud(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
refreshProgress.addToNumberOfTasksAndRemaining(2)
accountZone.removeWebFeed(feed, from: container) { result in
accountZone.removeFeed(feed, from: container) { result in
self.refreshProgress.completeTask()
switch result {
case .success:
guard let webFeedExternalID = feed.externalID else {
guard let feedExternalID = feed.externalID else {
completion(.success(()))
return
}
self.articlesZone.deleteArticles(webFeedExternalID) { result in
self.articlesZone.deleteArticles(feedExternalID) { result in
feed.dropConditionalGetInfo()
self.refreshProgress.completeTask()
completion(result)

View File

@@ -29,7 +29,7 @@ final class CloudKitAccountZone: CloudKitZone {
weak var database: CKDatabase?
var delegate: CloudKitZoneDelegate?
struct CloudKitWebFeed {
struct CloudKitFeed {
static let recordType = "AccountWebFeed"
struct Fields {
static let url = "url"
@@ -60,13 +60,13 @@ final class CloudKitAccountZone: CloudKitZone {
var feedRecords = [String: CKRecord]()
func processFeed(feedSpecifier: RSOPMLFeedSpecifier, containerExternalID: String) {
if let webFeedRecord = feedRecords[feedSpecifier.feedURL], var containerExternalIDs = webFeedRecord[CloudKitWebFeed.Fields.containerExternalIDs] as? [String] {
if let feedRecord = feedRecords[feedSpecifier.feedURL], var containerExternalIDs = feedRecord[CloudKitFeed.Fields.containerExternalIDs] as? [String] {
containerExternalIDs.append(containerExternalID)
webFeedRecord[CloudKitWebFeed.Fields.containerExternalIDs] = containerExternalIDs
feedRecord[CloudKitFeed.Fields.containerExternalIDs] = containerExternalIDs
} else {
let webFeedRecord = newWebFeedCKRecord(feedSpecifier: feedSpecifier, containerExternalID: containerExternalID)
records.append(webFeedRecord)
feedRecords[feedSpecifier.feedURL] = webFeedRecord
let feedRecord = newFeedCKRecord(feedSpecifier: feedSpecifier, containerExternalID: containerExternalID)
records.append(feedRecord)
feedRecords[feedSpecifier.feedURL] = feedRecord
}
}
@@ -90,23 +90,23 @@ final class CloudKitAccountZone: CloudKitZone {
}
/// Persist a web feed record to iCloud and return the external key
func createWebFeed(url: String, name: String?, editedName: String?, homePageURL: String?, container: Container, completion: @escaping (Result<String, Error>) -> Void) {
func createFeed(url: String, name: String?, editedName: String?, homePageURL: String?, container: Container, completion: @escaping (Result<String, Error>) -> Void) {
let recordID = CKRecord.ID(recordName: url.md5String, zoneID: zoneID)
let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: recordID)
record[CloudKitWebFeed.Fields.url] = url
record[CloudKitWebFeed.Fields.name] = name
let record = CKRecord(recordType: CloudKitFeed.recordType, recordID: recordID)
record[CloudKitFeed.Fields.url] = url
record[CloudKitFeed.Fields.name] = name
if let editedName = editedName {
record[CloudKitWebFeed.Fields.editedName] = editedName
record[CloudKitFeed.Fields.editedName] = editedName
}
if let homePageURL = homePageURL {
record[CloudKitWebFeed.Fields.homePageURL] = homePageURL
record[CloudKitFeed.Fields.homePageURL] = homePageURL
}
guard let containerExternalID = container.externalID else {
completion(.failure(CloudKitZoneError.corruptAccount))
return
}
record[CloudKitWebFeed.Fields.containerExternalIDs] = [containerExternalID]
record[CloudKitFeed.Fields.containerExternalIDs] = [containerExternalID]
save(record) { result in
switch result {
@@ -119,15 +119,15 @@ final class CloudKitAccountZone: CloudKitZone {
}
/// Rename the given web feed
func renameWebFeed(_ webFeed: Feed, editedName: String?, completion: @escaping (Result<Void, Error>) -> Void) {
guard let externalID = webFeed.externalID else {
func renameFeed(_ feed: Feed, editedName: String?, completion: @escaping (Result<Void, Error>) -> Void) {
guard let externalID = feed.externalID else {
completion(.failure(CloudKitZoneError.corruptAccount))
return
}
let recordID = CKRecord.ID(recordName: externalID, zoneID: zoneID)
let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: recordID)
record[CloudKitWebFeed.Fields.editedName] = editedName
let record = CKRecord(recordType: CloudKitFeed.recordType, recordID: recordID)
record[CloudKitFeed.Fields.editedName] = editedName
save(record) { result in
switch result {
@@ -140,22 +140,22 @@ final class CloudKitAccountZone: CloudKitZone {
}
/// Removes a web feed from a container and optionally deletes it, calling the completion with true if deleted
func removeWebFeed(_ webFeed: Feed, from: Container, completion: @escaping (Result<Bool, Error>) -> Void) {
func removeFeed(_ feed: Feed, from: Container, completion: @escaping (Result<Bool, Error>) -> Void) {
guard let fromContainerExternalID = from.externalID else {
completion(.failure(CloudKitZoneError.corruptAccount))
return
}
fetch(externalID: webFeed.externalID) { result in
fetch(externalID: feed.externalID) { result in
switch result {
case .success(let record):
if let containerExternalIDs = record[CloudKitWebFeed.Fields.containerExternalIDs] as? [String] {
if let containerExternalIDs = record[CloudKitFeed.Fields.containerExternalIDs] as? [String] {
var containerExternalIDSet = Set(containerExternalIDs)
containerExternalIDSet.remove(fromContainerExternalID)
if containerExternalIDSet.isEmpty {
self.delete(externalID: webFeed.externalID) { result in
self.delete(externalID: feed.externalID) { result in
switch result {
case .success:
completion(.success(true))
@@ -166,7 +166,7 @@ final class CloudKitAccountZone: CloudKitZone {
} else {
record[CloudKitWebFeed.Fields.containerExternalIDs] = Array(containerExternalIDSet)
record[CloudKitFeed.Fields.containerExternalIDs] = Array(containerExternalIDSet)
self.save(record) { result in
switch result {
case .success:
@@ -189,20 +189,20 @@ final class CloudKitAccountZone: CloudKitZone {
}
}
func moveWebFeed(_ webFeed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func moveFeed(_ feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
guard let fromContainerExternalID = from.externalID, let toContainerExternalID = to.externalID else {
completion(.failure(CloudKitZoneError.corruptAccount))
return
}
fetch(externalID: webFeed.externalID) { result in
fetch(externalID: feed.externalID) { result in
switch result {
case .success(let record):
if let containerExternalIDs = record[CloudKitWebFeed.Fields.containerExternalIDs] as? [String] {
if let containerExternalIDs = record[CloudKitFeed.Fields.containerExternalIDs] as? [String] {
var containerExternalIDSet = Set(containerExternalIDs)
containerExternalIDSet.remove(fromContainerExternalID)
containerExternalIDSet.insert(toContainerExternalID)
record[CloudKitWebFeed.Fields.containerExternalIDs] = Array(containerExternalIDSet)
record[CloudKitFeed.Fields.containerExternalIDs] = Array(containerExternalIDSet)
self.save(record, completion: completion)
}
case .failure(let error):
@@ -211,19 +211,19 @@ final class CloudKitAccountZone: CloudKitZone {
}
}
func addWebFeed(_ webFeed: Feed, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func addFeed(_ feed: Feed, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
guard let toContainerExternalID = to.externalID else {
completion(.failure(CloudKitZoneError.corruptAccount))
return
}
fetch(externalID: webFeed.externalID) { result in
fetch(externalID: feed.externalID) { result in
switch result {
case .success(let record):
if let containerExternalIDs = record[CloudKitWebFeed.Fields.containerExternalIDs] as? [String] {
if let containerExternalIDs = record[CloudKitFeed.Fields.containerExternalIDs] as? [String] {
var containerExternalIDSet = Set(containerExternalIDs)
containerExternalIDSet.insert(toContainerExternalID)
record[CloudKitWebFeed.Fields.containerExternalIDs] = Array(containerExternalIDSet)
record[CloudKitFeed.Fields.containerExternalIDs] = Array(containerExternalIDSet)
self.save(record, completion: completion)
}
case .failure(let error):
@@ -232,20 +232,20 @@ final class CloudKitAccountZone: CloudKitZone {
}
}
func findWebFeedExternalIDs(for folder: Folder, completion: @escaping (Result<[String], Error>) -> Void) {
func findFeedExternalIDs(for folder: Folder, completion: @escaping (Result<[String], Error>) -> Void) {
guard let folderExternalID = folder.externalID else {
completion(.failure(CloudKitAccountZoneError.unknown))
return
}
let predicate = NSPredicate(format: "containerExternalIDs CONTAINS %@", folderExternalID)
let ckQuery = CKQuery(recordType: CloudKitWebFeed.recordType, predicate: predicate)
let ckQuery = CKQuery(recordType: CloudKitFeed.recordType, predicate: predicate)
query(ckQuery) { result in
switch result {
case .success(let records):
let webFeedExternalIds = records.map { $0.externalID }
completion(.success(webFeedExternalIds))
let feedExternalIds = records.map { $0.externalID }
completion(.success(feedExternalIds))
case .failure(let error):
completion(.failure(error))
}
@@ -322,16 +322,16 @@ final class CloudKitAccountZone: CloudKitZone {
private extension CloudKitAccountZone {
func newWebFeedCKRecord(feedSpecifier: RSOPMLFeedSpecifier, containerExternalID: String) -> CKRecord {
let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: generateRecordID())
record[CloudKitWebFeed.Fields.url] = feedSpecifier.feedURL
func newFeedCKRecord(feedSpecifier: RSOPMLFeedSpecifier, containerExternalID: String) -> CKRecord {
let record = CKRecord(recordType: CloudKitFeed.recordType, recordID: generateRecordID())
record[CloudKitFeed.Fields.url] = feedSpecifier.feedURL
if let editedName = feedSpecifier.title {
record[CloudKitWebFeed.Fields.editedName] = editedName
record[CloudKitFeed.Fields.editedName] = editedName
}
if let homePageURL = feedSpecifier.homePageURL {
record[CloudKitWebFeed.Fields.homePageURL] = homePageURL
record[CloudKitFeed.Fields.homePageURL] = homePageURL
}
record[CloudKitWebFeed.Fields.containerExternalIDs] = [containerExternalID]
record[CloudKitFeed.Fields.containerExternalIDs] = [containerExternalID]
return record
}

View File

@@ -15,9 +15,9 @@ import Articles
class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
private typealias UnclaimedWebFeed = (url: URL, name: String?, editedName: String?, homePageURL: String?, webFeedExternalID: String)
private var newUnclaimedWebFeeds = [String: [UnclaimedWebFeed]]()
private var existingUnclaimedWebFeeds = [String: [Feed]]()
private typealias UnclaimedFeed = (url: URL, name: String?, editedName: String?, homePageURL: String?, feedExternalID: String)
private var newUnclaimedFeeds = [String: [UnclaimedFeed]]()
private var existingUnclaimedFeeds = [String: [Feed]]()
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit")
@@ -34,8 +34,8 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
func cloudKitDidModify(changed: [CKRecord], deleted: [CloudKitRecordKey], completion: @escaping (Result<Void, Error>) -> Void) {
for deletedRecordKey in deleted {
switch deletedRecordKey.recordType {
case CloudKitAccountZone.CloudKitWebFeed.recordType:
removeWebFeed(deletedRecordKey.recordID.externalID)
case CloudKitAccountZone.CloudKitFeed.recordType:
removeFeed(deletedRecordKey.recordID.externalID)
case CloudKitAccountZone.CloudKitContainer.recordType:
removeContainer(deletedRecordKey.recordID.externalID)
default:
@@ -45,8 +45,8 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
for changedRecord in changed {
switch changedRecord.recordType {
case CloudKitAccountZone.CloudKitWebFeed.recordType:
addOrUpdateWebFeed(changedRecord)
case CloudKitAccountZone.CloudKitFeed.recordType:
addOrUpdateFeed(changedRecord)
case CloudKitAccountZone.CloudKitContainer.recordType:
addOrUpdateContainer(changedRecord)
default:
@@ -57,36 +57,36 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
completion(.success(()))
}
func addOrUpdateWebFeed(_ record: CKRecord) {
func addOrUpdateFeed(_ record: CKRecord) {
guard let account = account,
let urlString = record[CloudKitAccountZone.CloudKitWebFeed.Fields.url] as? String,
let containerExternalIDs = record[CloudKitAccountZone.CloudKitWebFeed.Fields.containerExternalIDs] as? [String],
let urlString = record[CloudKitAccountZone.CloudKitFeed.Fields.url] as? String,
let containerExternalIDs = record[CloudKitAccountZone.CloudKitFeed.Fields.containerExternalIDs] as? [String],
let url = URL(string: urlString) else {
return
}
let name = record[CloudKitAccountZone.CloudKitWebFeed.Fields.name] as? String
let editedName = record[CloudKitAccountZone.CloudKitWebFeed.Fields.editedName] as? String
let homePageURL = record[CloudKitAccountZone.CloudKitWebFeed.Fields.homePageURL] as? String
let name = record[CloudKitAccountZone.CloudKitFeed.Fields.name] as? String
let editedName = record[CloudKitAccountZone.CloudKitFeed.Fields.editedName] as? String
let homePageURL = record[CloudKitAccountZone.CloudKitFeed.Fields.homePageURL] as? String
if let webFeed = account.existingWebFeed(withExternalID: record.externalID) {
updateWebFeed(webFeed, name: name, editedName: editedName, homePageURL: homePageURL, containerExternalIDs: containerExternalIDs)
if let feed = account.existingFeed(withExternalID: record.externalID) {
updateFeed(feed, name: name, editedName: editedName, homePageURL: homePageURL, containerExternalIDs: containerExternalIDs)
} else {
for containerExternalID in containerExternalIDs {
if let container = account.existingContainer(withExternalID: containerExternalID) {
createWebFeedIfNecessary(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: record.externalID, container: container)
createFeedIfNecessary(url: url, name: name, editedName: editedName, homePageURL: homePageURL, feedExternalID: record.externalID, container: container)
} else {
addNewUnclaimedWebFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: record.externalID, containerExternalID: containerExternalID)
addNewUnclaimedFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, feedExternalID: record.externalID, containerExternalID: containerExternalID)
}
}
}
}
func removeWebFeed(_ externalID: String) {
if let webFeed = account?.existingWebFeed(withExternalID: externalID), let containers = account?.existingContainers(withWebFeed: webFeed) {
func removeFeed(_ externalID: String) {
if let feed = account?.existingFeed(withExternalID: externalID), let containers = account?.existingContainers(withFeed: feed) {
containers.forEach {
webFeed.dropConditionalGetInfo()
$0.removeWebFeed(webFeed)
feed.dropConditionalGetInfo()
$0.removeFeed(feed)
}
}
}
@@ -109,24 +109,24 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
guard let container = folder, let containerExternalID = container.externalID else { return }
if let newUnclaimedWebFeeds = newUnclaimedWebFeeds[containerExternalID] {
for newUnclaimedWebFeed in newUnclaimedWebFeeds {
createWebFeedIfNecessary(url: newUnclaimedWebFeed.url,
name: newUnclaimedWebFeed.name,
editedName: newUnclaimedWebFeed.editedName,
homePageURL: newUnclaimedWebFeed.homePageURL,
webFeedExternalID: newUnclaimedWebFeed.webFeedExternalID,
if let newUnclaimedFeeds = newUnclaimedFeeds[containerExternalID] {
for newUnclaimedFeed in newUnclaimedFeeds {
createFeedIfNecessary(url: newUnclaimedFeed.url,
name: newUnclaimedFeed.name,
editedName: newUnclaimedFeed.editedName,
homePageURL: newUnclaimedFeed.homePageURL,
feedExternalID: newUnclaimedFeed.feedExternalID,
container: container)
}
self.newUnclaimedWebFeeds.removeValue(forKey: containerExternalID)
self.newUnclaimedFeeds.removeValue(forKey: containerExternalID)
}
if let existingUnclaimedWebFeeds = existingUnclaimedWebFeeds[containerExternalID] {
for existingUnclaimedWebFeed in existingUnclaimedWebFeeds {
container.addWebFeed(existingUnclaimedWebFeed)
if let existingUnclaimedFeeds = existingUnclaimedFeeds[containerExternalID] {
for existingUnclaimedFeed in existingUnclaimedFeeds {
container.addFeed(existingUnclaimedFeed)
}
self.existingUnclaimedWebFeeds.removeValue(forKey: containerExternalID)
self.existingUnclaimedFeeds.removeValue(forKey: containerExternalID)
}
}
@@ -140,14 +140,14 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
private extension CloudKitAcountZoneDelegate {
func updateWebFeed(_ webFeed: Feed, name: String?, editedName: String?, homePageURL: String?, containerExternalIDs: [String]) {
func updateFeed(_ feed: Feed, name: String?, editedName: String?, homePageURL: String?, containerExternalIDs: [String]) {
guard let account = account else { return }
webFeed.name = name
webFeed.editedName = editedName
webFeed.homePageURL = homePageURL
feed.name = name
feed.editedName = editedName
feed.homePageURL = homePageURL
let existingContainers = account.existingContainers(withWebFeed: webFeed)
let existingContainers = account.existingContainers(withFeed: feed)
let existingContainerExternalIds = existingContainers.compactMap { $0.externalID }
let diff = containerExternalIDs.difference(from: existingContainerExternalIds)
@@ -156,50 +156,50 @@ private extension CloudKitAcountZoneDelegate {
switch change {
case .remove(_, let externalID, _):
if let container = existingContainers.first(where: { $0.externalID == externalID }) {
container.removeWebFeed(webFeed)
container.removeFeed(feed)
}
case .insert(_, let externalID, _):
if let container = account.existingContainer(withExternalID: externalID) {
container.addWebFeed(webFeed)
container.addFeed(feed)
} else {
addExistingUnclaimedWebFeed(webFeed, containerExternalID: externalID)
addExistingUnclaimedFeed(feed, containerExternalID: externalID)
}
}
}
}
func createWebFeedIfNecessary(url: URL, name: String?, editedName: String?, homePageURL: String?, webFeedExternalID: String, container: Container) {
func createFeedIfNecessary(url: URL, name: String?, editedName: String?, homePageURL: String?, feedExternalID: String, container: Container) {
guard let account = account else { return }
if account.existingWebFeed(withExternalID: webFeedExternalID) != nil {
if account.existingFeed(withExternalID: feedExternalID) != nil {
return
}
let webFeed = account.createWebFeed(with: name, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: homePageURL)
webFeed.editedName = editedName
webFeed.externalID = webFeedExternalID
container.addWebFeed(webFeed)
let feed = account.createFeed(with: name, url: url.absoluteString, feedID: url.absoluteString, homePageURL: homePageURL)
feed.editedName = editedName
feed.externalID = feedExternalID
container.addFeed(feed)
}
func addNewUnclaimedWebFeed(url: URL, name: String?, editedName: String?, homePageURL: String?, webFeedExternalID: String, containerExternalID: String) {
if var unclaimedWebFeeds = self.newUnclaimedWebFeeds[containerExternalID] {
unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: webFeedExternalID))
self.newUnclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
func addNewUnclaimedFeed(url: URL, name: String?, editedName: String?, homePageURL: String?, feedExternalID: String, containerExternalID: String) {
if var unclaimedFeeds = self.newUnclaimedFeeds[containerExternalID] {
unclaimedFeeds.append(UnclaimedFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, feedExternalID: feedExternalID))
self.newUnclaimedFeeds[containerExternalID] = unclaimedFeeds
} else {
var unclaimedWebFeeds = [UnclaimedWebFeed]()
unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: webFeedExternalID))
self.newUnclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
var unclaimedFeeds = [UnclaimedFeed]()
unclaimedFeeds.append(UnclaimedFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, feedExternalID: feedExternalID))
self.newUnclaimedFeeds[containerExternalID] = unclaimedFeeds
}
}
func addExistingUnclaimedWebFeed(_ webFeed: Feed, containerExternalID: String) {
if var unclaimedWebFeeds = self.existingUnclaimedWebFeeds[containerExternalID] {
unclaimedWebFeeds.append(webFeed)
self.existingUnclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
func addExistingUnclaimedFeed(_ feed: Feed, containerExternalID: String) {
if var unclaimedFeeds = self.existingUnclaimedFeeds[containerExternalID] {
unclaimedFeeds.append(feed)
self.existingUnclaimedFeeds[containerExternalID] = unclaimedFeeds
} else {
var unclaimedWebFeeds = [Feed]()
unclaimedWebFeeds.append(webFeed)
self.existingUnclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
var unclaimedFeeds = [Feed]()
unclaimedFeeds.append(feed)
self.existingUnclaimedFeeds[containerExternalID] = unclaimedFeeds
}
}

View File

@@ -31,7 +31,7 @@ final class CloudKitArticlesZone: CloudKitZone {
static let recordType = "Article"
struct Fields {
static let articleStatus = "articleStatus"
static let webFeedURL = "webFeedURL"
static let feedURL = "webFeedURL"
static let uniqueID = "uniqueID"
static let title = "title"
static let contentHTML = "contentHTML"
@@ -51,7 +51,7 @@ final class CloudKitArticlesZone: CloudKitZone {
struct CloudKitArticleStatus {
static let recordType = "ArticleStatus"
struct Fields {
static let webFeedExternalID = "webFeedExternalID"
static let feedExternalID = "webFeedExternalID"
static let read = "read"
static let starred = "starred"
}
@@ -106,8 +106,8 @@ final class CloudKitArticlesZone: CloudKitZone {
}
}
func deleteArticles(_ webFeedExternalID: String, completion: @escaping ((Result<Void, Error>) -> Void)) {
let predicate = NSPredicate(format: "webFeedExternalID = %@", webFeedExternalID)
func deleteArticles(_ feedExternalID: String, completion: @escaping ((Result<Void, Error>) -> Void)) {
let predicate = NSPredicate(format: "webFeedExternalID = %@", feedExternalID)
let ckQuery = CKQuery(recordType: CloudKitArticleStatus.recordType, predicate: predicate)
delete(ckQuery: ckQuery, completion: completion)
}
@@ -190,8 +190,8 @@ private extension CloudKitArticlesZone {
func makeStatusRecord(_ article: Article) -> CKRecord {
let recordID = CKRecord.ID(recordName: statusID(article.articleID), zoneID: zoneID)
let record = CKRecord(recordType: CloudKitArticleStatus.recordType, recordID: recordID)
if let webFeedExternalID = article.webFeed?.externalID {
record[CloudKitArticleStatus.Fields.webFeedExternalID] = webFeedExternalID
if let feedExternalID = article.feed?.externalID {
record[CloudKitArticleStatus.Fields.feedExternalID] = feedExternalID
}
record[CloudKitArticleStatus.Fields.read] = article.status.read ? "1" : "0"
record[CloudKitArticleStatus.Fields.starred] = article.status.starred ? "1" : "0"
@@ -202,8 +202,8 @@ private extension CloudKitArticlesZone {
let recordID = CKRecord.ID(recordName: statusID(statusUpdate.articleID), zoneID: zoneID)
let record = CKRecord(recordType: CloudKitArticleStatus.recordType, recordID: recordID)
if let webFeedExternalID = statusUpdate.article?.webFeed?.externalID {
record[CloudKitArticleStatus.Fields.webFeedExternalID] = webFeedExternalID
if let feedExternalID = statusUpdate.article?.feed?.externalID {
record[CloudKitArticleStatus.Fields.feedExternalID] = feedExternalID
}
record[CloudKitArticleStatus.Fields.read] = statusUpdate.isRead ? "1" : "0"
@@ -218,7 +218,7 @@ private extension CloudKitArticlesZone {
let articleStatusRecordID = CKRecord.ID(recordName: statusID(article.articleID), zoneID: zoneID)
record[CloudKitArticle.Fields.articleStatus] = CKRecord.Reference(recordID: articleStatusRecordID, action: .deleteSelf)
record[CloudKitArticle.Fields.webFeedURL] = article.webFeed?.url
record[CloudKitArticle.Fields.feedURL] = article.feed?.url
record[CloudKitArticle.Fields.uniqueID] = article.uniqueID
record[CloudKitArticle.Fields.title] = article.title
record[CloudKitArticle.Fields.contentHTML] = article.contentHTML

View File

@@ -137,12 +137,12 @@ private extension CloudKitArticlesZoneDelegate {
group.enter()
compressionQueue.async {
let parsedItems = records.compactMap { self.makeParsedItem($0) }
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
DispatchQueue.main.async {
for (webFeedID, parsedItems) in webFeedIDsAndItems {
for (feedID, parsedItems) in feedIDsAndItems {
group.enter()
self.account?.update(webFeedID, with: parsedItems, deleteOlder: false) { result in
self.account?.update(feedID, with: parsedItems, deleteOlder: false) { result in
switch result {
case .success(let articleChanges):
guard let deletes = articleChanges.deletedArticles, !deletes.isEmpty else {
@@ -196,7 +196,7 @@ private extension CloudKitArticlesZoneDelegate {
}
guard let uniqueID = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.uniqueID] as? String,
let webFeedURL = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.webFeedURL] as? String else {
let feedURL = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.feedURL] as? String else {
return nil
}
@@ -216,7 +216,7 @@ private extension CloudKitArticlesZoneDelegate {
let parsedItem = ParsedItem(syncServiceID: nil,
uniqueID: uniqueID,
feedURL: webFeedURL,
feedURL: feedURL,
url: articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.url] as? String,
externalURL: articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.externalURL] as? String,
title: articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.title] as? String,

View File

@@ -23,23 +23,23 @@ public protocol Container: AnyObject, ContainerIdentifiable {
var folders: Set<Folder>? { get set }
var externalID: String? { get set }
func hasAtLeastOneWebFeed() -> Bool
func hasAtLeastOneFeed() -> Bool
func objectIsChild(_ object: AnyObject) -> Bool
func hasChildFolder(with: String) -> Bool
func childFolder(with: String) -> Folder?
func removeWebFeed(_ webFeed: Feed)
func addWebFeed(_ webFeed: Feed)
func removeFeed(_ feed: Feed)
func addFeed(_ feed: Feed)
//Recursive  checks subfolders
func flattenedFeeds() -> Set<Feed>
func has(_ webFeed: Feed) -> Bool
func hasWebFeed(with webFeedID: String) -> Bool
func has(_ feed: Feed) -> Bool
func hasFeed(with feedID: String) -> Bool
func hasFeed(withURL url: String) -> Bool
func existingWebFeed(withWebFeedID: String) -> Feed?
func existingFeed(withFeedID: String) -> Feed?
func existingFeed(withURL url: String) -> Feed?
func existingWebFeed(withExternalID externalID: String) -> Feed?
func existingFeed(withExternalID externalID: String) -> Feed?
func existingFolder(with name: String) -> Folder?
func existingFolder(withID: Int) -> Folder?
@@ -48,7 +48,7 @@ public protocol Container: AnyObject, ContainerIdentifiable {
public extension Container {
func hasAtLeastOneWebFeed() -> Bool {
func hasAtLeastOneFeed() -> Bool {
return topLevelFeeds.count > 0
}
@@ -89,8 +89,8 @@ public extension Container {
return feeds
}
func hasWebFeed(with webFeedID: String) -> Bool {
return existingWebFeed(withWebFeedID: webFeedID) != nil
func hasFeed(with feedID: String) -> Bool {
return existingFeed(withFeedID: feedID) != nil
}
func hasFeed(withURL url: String) -> Bool {
@@ -101,9 +101,9 @@ public extension Container {
return flattenedFeeds().contains(feed)
}
func existingWebFeed(withWebFeedID webFeedID: String) -> Feed? {
func existingFeed(withFeedID feedID: String) -> Feed? {
for feed in flattenedFeeds() {
if feed.feedID == webFeedID {
if feed.feedID == feedID {
return feed
}
}
@@ -119,7 +119,7 @@ public extension Container {
return nil
}
func existingWebFeed(withExternalID externalID: String) -> Feed? {
func existingFeed(withExternalID externalID: String) -> Feed? {
for feed in flattenedFeeds() {
if feed.externalID == externalID {
return feed

View File

@@ -40,7 +40,7 @@ extension Feed {
authors = Author.authorsWithParsedAuthors(parsedFeed.authors)
}
func postFeedSettingDidChangeNotification(_ codingKey: WebFeedMetadata.CodingKeys) {
func postFeedSettingDidChangeNotification(_ codingKey: FeedMetadata.CodingKeys) {
let userInfo = [Feed.FeedSettingUserInfoKey: codingKey.stringValue]
NotificationCenter.default.post(name: .FeedSettingDidChange, object: self, userInfo: userInfo)
}
@@ -56,8 +56,8 @@ public extension Article {
return manager.existingAccount(with: accountID)
}
var webFeed: Feed? {
return account?.existingWebFeed(withWebFeedID: webFeedID)
var feed: Feed? {
return account?.existingFeed(withFeedID: feedID)
}
}

View File

@@ -1,5 +1,5 @@
//
// WebFeed.swift
// Feed.swift
// NetNewsWire
//
// Created by Brent Simmons on 7/1/17.
@@ -22,7 +22,7 @@ public final class Feed: SidebarItem, Renamable, Hashable {
assertionFailure("Expected feed.account, but got nil.")
return nil
}
return SidebarItemIdentifier.webFeed(accountID, feedID)
return SidebarItemIdentifier.feed(accountID, feedID)
}
public weak var account: Account?
@@ -203,7 +203,7 @@ public final class Feed: SidebarItem, Renamable, Hashable {
public func rename(to newName: String, completion: @escaping (Result<Void, Error>) -> Void) {
guard let account = account else { return }
account.renameWebFeed(self, to: newName, completion: completion)
account.renameFeed(self, to: newName, completion: completion)
}
// MARK: - UnreadCountProvider
@@ -238,7 +238,7 @@ public final class Feed: SidebarItem, Renamable, Hashable {
#endif
}
var metadata: WebFeedMetadata
var metadata: FeedMetadata
// MARK: - Private
@@ -246,7 +246,7 @@ public final class Feed: SidebarItem, Renamable, Hashable {
// MARK: - Init
init(account: Account, url: String, metadata: WebFeedMetadata) {
init(account: Account, url: String, metadata: FeedMetadata) {
self.account = account
self.accountID = account.accountID
self.url = url
@@ -306,16 +306,16 @@ extension Feed: OPMLRepresentable {
extension Set where Element == Feed {
func webFeedIDs() -> Set<String> {
func feedIDs() -> Set<String> {
return Set<String>(map { $0.feedID })
}
func sorted() -> Array<Feed> {
return sorted(by: { (webFeed1, webFeed2) -> Bool in
if webFeed1.nameForDisplay.localizedStandardCompare(webFeed2.nameForDisplay) == .orderedSame {
return webFeed1.url < webFeed2.url
return sorted(by: { (feed1, feed2) -> Bool in
if feed1.nameForDisplay.localizedStandardCompare(feed2.nameForDisplay) == .orderedSame {
return feed1.url < feed2.url
}
return webFeed1.nameForDisplay.localizedStandardCompare(webFeed2.nameForDisplay) == .orderedAscending
return feed1.nameForDisplay.localizedStandardCompare(feed2.nameForDisplay) == .orderedAscending
})
}

View File

@@ -1,5 +1,5 @@
//
// WebFeedMetadata.swift
// FeedMetadata.swift
// NetNewsWire
//
// Created by Brent Simmons on 3/12/19.
@@ -10,11 +10,11 @@ import Foundation
import RSWeb
import Articles
protocol WebFeedMetadataDelegate: AnyObject {
func valueDidChange(_ feedMetadata: WebFeedMetadata, key: WebFeedMetadata.CodingKeys)
protocol FeedMetadataDelegate: AnyObject {
func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys)
}
final class WebFeedMetadata: Codable {
final class FeedMetadata: Codable {
enum CodingKeys: String, CodingKey {
case feedID
@@ -137,7 +137,7 @@ final class WebFeedMetadata: Codable {
}
}
weak var delegate: WebFeedMetadataDelegate?
weak var delegate: FeedMetadataDelegate?
init(feedID: String) {
self.feedID = feedID

View File

@@ -1,5 +1,5 @@
//
// WebFeedMetadataFile.swift
// FeedMetadataFile.swift
// Account
//
// Created by Maurice Parker on 9/13/19.
@@ -10,9 +10,9 @@ import Foundation
import os.log
import RSCore
final class WebFeedMetadataFile {
final class FeedMetadataFile {
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "webFeedMetadataFile")
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "FeedMetadataFile")
private let fileURL: URL
private let account: Account
@@ -36,9 +36,9 @@ final class WebFeedMetadataFile {
func load() {
if let fileData = try? Data(contentsOf: fileURL) {
let decoder = PropertyListDecoder()
account.webFeedMetadata = (try? decoder.decode(Account.WebFeedMetadataDictionary.self, from: fileData)) ?? Account.WebFeedMetadataDictionary()
account.feedMetadata = (try? decoder.decode(Account.FeedMetadataDictionary.self, from: fileData)) ?? Account.FeedMetadataDictionary()
}
account.webFeedMetadata.values.forEach { $0.delegate = account }
account.feedMetadata.values.forEach { $0.delegate = account }
}
func save() {
@@ -59,7 +59,7 @@ final class WebFeedMetadataFile {
}
private extension WebFeedMetadataFile {
private extension FeedMetadataFile {
func queueSaveToDiskIfNeeded() {
saveQueue.add(self, #selector(saveToDiskIfNeeded))
@@ -72,10 +72,10 @@ private extension WebFeedMetadataFile {
}
}
private func metadataForOnlySubscribedToFeeds() -> Account.WebFeedMetadataDictionary {
let webFeedIDs = account.idToWebFeedDictionary.keys
return account.webFeedMetadata.filter { (feedID: String, metadata: WebFeedMetadata) -> Bool in
return webFeedIDs.contains(metadata.feedID)
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)
}
}

View File

@@ -315,7 +315,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
fatalError()
}
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
refreshProgress.addToNumberOfTasksAndRemaining(2)
self.refreshCredentials(for: account) {
@@ -338,13 +338,13 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
private func addFeedWranglerSubscription(account: Account, subscription sub: FeedWranglerSubscription, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
DispatchQueue.main.async {
let feed = account.createWebFeed(with: sub.title, url: sub.feedURL, webFeedID: String(sub.feedID), homePageURL: sub.siteURL)
let feed = account.createFeed(with: sub.title, url: sub.feedURL, feedID: String(sub.feedID), homePageURL: sub.siteURL)
account.addFeed(feed, to: container) { result in
switch result {
case .success:
if let name = name {
account.renameWebFeed(feed, to: name) { result in
account.renameFeed(feed, to: name) { result in
switch result {
case .success:
self.initialFeedDownload(account: account, feed: feed, completion: completion)
@@ -383,7 +383,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
}
}
func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
refreshProgress.addToNumberOfTasksAndRemaining(2)
self.refreshCredentials(for: account) {
@@ -408,7 +408,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
}
}
func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
// just add to account, folders are not supported
DispatchQueue.main.async {
account.addFeedIfNotInAnyFolder(feed)
@@ -416,7 +416,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
}
}
func removeWebFeed(for account: Account, with feed: Feed, from 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) {
refreshProgress.addToNumberOfTasksAndRemaining(2)
self.refreshCredentials(for: account) {
@@ -427,8 +427,8 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
switch result {
case .success:
DispatchQueue.main.async {
account.clearWebFeedMetadata(feed)
account.removeWebFeed(feed)
account.clearFeedMetadata(feed)
account.removeFeed(feed)
completion(.success(()))
}
@@ -442,11 +442,11 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
}
}
func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: 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) {
fatalError()
}
func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
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
switch result {
@@ -457,7 +457,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
}
}
} else {
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
switch result {
case .success:
completion(.success(()))
@@ -540,7 +540,7 @@ private extension FeedWranglerAccountDelegate {
subscriptions.forEach { subscription in
let subscriptionId = String(subscription.feedID)
if let feed = account.existingWebFeed(withWebFeedID: subscriptionId) {
if let feed = account.existingFeed(withFeedID: subscriptionId) {
feed.name = subscription.title
feed.editedName = nil
feed.homePageURL = subscription.siteURL
@@ -552,9 +552,9 @@ private extension FeedWranglerAccountDelegate {
subscriptionsToAdd.forEach { subscription in
let feedId = String(subscription.feedID)
let feed = account.createWebFeed(with: subscription.title, url: subscription.feedURL, webFeedID: feedId, homePageURL: subscription.siteURL)
let feed = account.createFeed(with: subscription.title, url: subscription.feedURL, feedID: feedId, homePageURL: subscription.siteURL)
feed.externalID = nil
account.addWebFeed(feed)
account.addFeed(feed)
}
}
@@ -568,7 +568,7 @@ private extension FeedWranglerAccountDelegate {
}
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { $0.feedURL }).mapValues { Set($0) }
account.update(webFeedIDsAndItems: feedIDsAndItems, defaultRead: true) { _ in
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) { _ in
completion()
}
}

View File

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

View File

@@ -300,7 +300,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
guard folder.hasAtLeastOneWebFeed() else {
guard folder.hasAtLeastOneFeed() else {
folder.name = name
return
}
@@ -328,7 +328,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.hasAtLeastOneWebFeed() else {
guard folder.hasAtLeastOneFeed() else {
account.removeFolder(folder)
completion(.success(()))
return
@@ -368,7 +368,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
switch result {
case .success:
DispatchQueue.main.async {
account.clearWebFeedMetadata(feed)
account.clearFeedMetadata(feed)
}
case .failure(let error):
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
@@ -388,7 +388,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
refreshProgress.addToNumberOfTasksAndRemaining(1)
caller.createSubscription(url: url) { result in
@@ -420,7 +420,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
// This error should never happen
guard let subscriptionID = feed.externalID else {
@@ -447,7 +447,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
func removeWebFeed(for account: Account, with feed: Feed, from 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) {
if feed.folderRelationship?.count ?? 0 > 1 {
deleteTagging(for: account, with: feed, from: container, completion: completion)
} else {
@@ -455,14 +455,14 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
}
func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: 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) {
if from is Account {
addWebFeed(for: account, with: feed, to: to, completion: completion)
addFeed(for: account, with: feed, to: to, completion: completion)
} else {
deleteTagging(for: account, with: feed, from: from) { result in
switch result {
case .success:
self.addWebFeed(for: account, with: feed, to: to, completion: completion)
self.addFeed(for: account, with: feed, to: to, completion: completion)
case .failure(let error):
completion(.failure(error))
}
@@ -470,18 +470,18 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
}
func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
if let folder = container as? Folder, let webFeedID = Int(feed.feedID) {
if let folder = container as? Folder, let feedID = Int(feed.feedID) {
refreshProgress.addToNumberOfTasksAndRemaining(1)
caller.createTagging(webFeedID: webFeedID, name: folder.name ?? "") { result in
caller.createTagging(feedID: feedID, 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.removeWebFeed(feed)
folder.addWebFeed(feed)
account.removeFeed(feed)
folder.addFeed(feed)
completion(.success(()))
}
case .failure(let error):
@@ -502,7 +502,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
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
@@ -514,7 +514,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
}
} else {
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
switch result {
case .success:
completion(.success(()))
@@ -535,7 +535,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
folder.topLevelFeeds.remove(feed)
group.enter()
restoreWebFeed(for: account, feed: feed, container: folder) { result in
restoreFeed(for: account, feed: feed, container: folder) { result in
group.leave()
switch result {
case .success:
@@ -781,7 +781,7 @@ private extension FeedbinAccountDelegate {
folders.forEach { folder in
if !tagNames.contains(folder.name ?? "") {
for feed in folder.topLevelFeeds {
account.addWebFeed(feed)
account.addFeed(feed)
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
}
account.removeFolder(folder)
@@ -820,7 +820,7 @@ private extension FeedbinAccountDelegate {
for folder in folders {
for feed in folder.topLevelFeeds {
if !subFeedIds.contains(feed.feedID) {
folder.removeWebFeed(feed)
folder.removeFeed(feed)
}
}
}
@@ -828,7 +828,7 @@ private extension FeedbinAccountDelegate {
for feed in account.topLevelFeeds {
if !subFeedIds.contains(feed.feedID) {
account.removeWebFeed(feed)
account.removeFeed(feed)
}
}
@@ -838,7 +838,7 @@ private extension FeedbinAccountDelegate {
let subFeedId = String(subscription.feedID)
if let feed = account.existingWebFeed(withWebFeedID: subFeedId) {
if let feed = account.existingFeed(withFeedID: subFeedId) {
feed.name = subscription.name
// If the name has been changed on the server remove the locally edited name
feed.editedName = nil
@@ -854,9 +854,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.createWebFeed(with: subscription.name, url: subscription.url, webFeedID: String(subscription.feedID), homePageURL: subscription.homePageURL)
let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: String(subscription.feedID), homePageURL: subscription.homePageURL)
feed.externalID = String(subscription.subscriptionID)
account.addWebFeed(feed)
account.addFeed(feed)
}
}
@@ -890,9 +890,9 @@ private extension FeedbinAccountDelegate {
// Move any feeds not in the folder to the account
for feed in folder.topLevelFeeds {
if !taggingFeedIDs.contains(feed.feedID) {
folder.removeWebFeed(feed)
folder.removeFeed(feed)
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
account.addWebFeed(feed)
account.addFeed(feed)
}
}
@@ -902,11 +902,11 @@ private extension FeedbinAccountDelegate {
for tagging in groupedTaggings {
let taggingFeedID = String(tagging.feedID)
if !folderFeedIds.contains(taggingFeedID) {
guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else {
guard let feed = account.existingFeed(withFeedID: taggingFeedID) else {
continue
}
saveFolderRelationship(for: feed, withFolderName: folderName, id: String(tagging.taggingID))
folder.addWebFeed(feed)
folder.addFeed(feed)
}
}
@@ -917,7 +917,7 @@ private extension FeedbinAccountDelegate {
// Remove all feeds from the account container that have a tag
for feed in account.topLevelFeeds {
if taggedFeedIDs.contains(feed.feedID) {
account.removeWebFeed(feed)
account.removeFeed(feed)
}
}
}
@@ -1017,7 +1017,7 @@ private extension FeedbinAccountDelegate {
}
if let bestSpecifier = FeedSpecifier.bestFeed(in: Set(feedSpecifiers)) {
createWebFeed(for: account, url: bestSpecifier.urlString, name: name, container: container, validateFeed: true, completion: completion)
createFeed(for: account, url: bestSpecifier.urlString, name: name, container: container, validateFeed: true, completion: completion)
} else {
DispatchQueue.main.async {
completion(.failure(FeedbinAccountDelegateError.invalidParameter))
@@ -1029,7 +1029,7 @@ private extension FeedbinAccountDelegate {
DispatchQueue.main.async {
let feed = account.createWebFeed(with: sub.name, url: sub.url, webFeedID: String(sub.feedID), homePageURL: sub.homePageURL)
let feed = account.createFeed(with: sub.name, url: sub.url, feedID: String(sub.feedID), homePageURL: sub.homePageURL)
feed.externalID = String(sub.subscriptionID)
feed.iconURL = sub.jsonFeed?.icon
feed.faviconURL = sub.jsonFeed?.favicon
@@ -1038,7 +1038,7 @@ private extension FeedbinAccountDelegate {
switch result {
case .success:
if let name = name {
account.renameWebFeed(feed, to: name) { result in
account.renameFeed(feed, to: name) { result in
switch result {
case .success:
self.initialFeedDownload(account: account, feed: feed, completion: completion)
@@ -1248,8 +1248,8 @@ private extension FeedbinAccountDelegate {
func processEntries(account: Account, entries: [FeedbinEntry]?, completion: @escaping DatabaseCompletionBlock) {
let parsedItems = mapEntriesToParsedItems(entries: entries)
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true, completion: completion)
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true, completion: completion)
}
func mapEntriesToParsedItems(entries: [FeedbinEntry]?) -> Set<ParsedItem> {
@@ -1381,7 +1381,7 @@ private extension FeedbinAccountDelegate {
case .success:
DispatchQueue.main.async {
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
folder.removeWebFeed(feed)
folder.removeFeed(feed)
account.addFeedIfNotInAnyFolder(feed)
completion(.success(()))
}
@@ -1394,7 +1394,7 @@ private extension FeedbinAccountDelegate {
}
} else {
if let account = container as? Account {
account.removeWebFeed(feed)
account.removeFeed(feed)
}
completion(.success(()))
}
@@ -1415,11 +1415,11 @@ private extension FeedbinAccountDelegate {
switch result {
case .success:
DispatchQueue.main.async {
account.clearWebFeedMetadata(feed)
account.removeWebFeed(feed)
account.clearFeedMetadata(feed)
account.removeFeed(feed)
if let folders = account.folders {
for folder in folders {
folder.removeWebFeed(feed)
folder.removeFeed(feed)
}
}
completion(.success(()))

View File

@@ -314,7 +314,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
}
}
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
do {
guard let credentials = credentials else {
@@ -347,7 +347,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
}
}
func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
func renameFeed(for account: Account, with feed: Feed, 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)))
@@ -374,7 +374,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
feed.editedName = name
}
func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
do {
guard let credentials = credentials else {
@@ -405,7 +405,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
}
}
func removeWebFeed(for account: Account, with feed: Feed, from 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) {
guard let folder = container as? Folder, let collectionId = folder.externalID else {
return DispatchQueue.main.async {
completion(.failure(FeedlyAccountDelegateError.unableToRemoveFeed(feed)))
@@ -417,48 +417,48 @@ final class FeedlyAccountDelegate: AccountDelegate {
case .success:
completion(.success(()))
case .failure(let error):
folder.addWebFeed(feed)
folder.addFeed(feed)
completion(.failure(error))
}
}
folder.removeWebFeed(feed)
folder.removeFeed(feed)
}
func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: 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) {
guard let from = from as? Folder, let to = to as? Folder else {
return DispatchQueue.main.async {
completion(.failure(FeedlyAccountDelegateError.addFeedChooseFolder))
}
}
addWebFeed(for: account, with: feed, to: to) { [weak self] addResult in
addFeed(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?.removeWebFeed(for: account, with: feed, from: from) { removeResult in
self?.removeFeed(for: account, with: feed, from: from) { removeResult in
switch removeResult {
case .success:
completion(.success(()))
case .failure:
from.addWebFeed(feed)
from.addFeed(feed)
completion(.failure(FeedlyAccountDelegateError.unableToMoveFeedBetweenFolders(feed, from, to)))
}
}
case .failure(let error):
from.addWebFeed(feed)
to.removeWebFeed(feed)
from.addFeed(feed)
to.removeFeed(feed)
completion(.failure(error))
}
}
// optimistically move the feed, undoing as appropriate to the failure
from.removeWebFeed(feed)
to.addWebFeed(feed)
from.removeFeed(feed)
to.addFeed(feed)
}
func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
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
switch result {
@@ -469,7 +469,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
}
}
} else {
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
switch result {
case .success:
completion(.success(()))
@@ -488,7 +488,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
folder.topLevelFeeds.remove(feed)
group.enter()
restoreWebFeed(for: account, feed: feed, container: folder) { result in
restoreFeed(for: account, feed: feed, container: folder) { result in
group.leave()
switch result {
case .success:

View File

@@ -19,7 +19,7 @@ struct FeedlyEntryParser {
return entry.id
}
/// When ingesting articles, the feedURL must match a feed's `webFeedID` for the article to be reachable between it and its matching feed. It reminds me of a foreign key.
/// When ingesting articles, the feedURL must match a feed's `feedID` for the article to be reachable between it and its matching feed. It reminds me of a foreign key.
var feedUrl: String? {
guard let id = entry.origin?.streamId else {
// At this point, check Feedly's API isn't glitching or the response has not changed structure.

View File

@@ -17,7 +17,7 @@ struct FeedlyFeedParser {
return rightToLeftTextSantizer.sanitize(feed.title) ?? ""
}
var webFeedID: String {
var feedID: String {
return feed.id
}

View File

@@ -137,7 +137,7 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
guard let handler = addCompletionHandler else {
return
}
if let feedResource = feedResourceId, let feed = folder.existingWebFeed(withWebFeedID: feedResource.id) {
if let feedResource = feedResourceId, let feed = folder.existingFeed(withFeedID: feedResource.id) {
handler(.success(feed))
}
else {

View File

@@ -58,7 +58,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
.compactMap { (collectionFeed, folder) -> (Feed, Folder) in
// find an existing feed previously added to the account
if let feed = account.existingWebFeed(withWebFeedID: collectionFeed.id) {
if let feed = account.existingFeed(withFeedID: collectionFeed.id) {
// If the feed was renamed on Feedly, ensure we ingest the new name.
if feed.nameForDisplay != collectionFeed.title {
@@ -83,9 +83,9 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
// no existing feed, create a new one
let parser = FeedlyFeedParser(feed: collectionFeed)
let feed = account.createWebFeed(with: parser.title,
let feed = account.createFeed(with: parser.title,
url: parser.url,
webFeedID: parser.webFeedID,
feedID: parser.feedID,
homePageURL: parser.homePageURL)
// So the same feed isn't created more than once.
@@ -97,7 +97,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.addWebFeed(feed)
folder.addFeed(feed)
}
}

View File

@@ -24,15 +24,15 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
}
override func run() {
let webFeedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId
let feedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) { databaseError in
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) { databaseError in
if let error = databaseError {
self.didFinish(with: error)
return
}
os_log(.debug, log: self.log, "Updated %i feeds for \"%@\"", webFeedIDsAndItems.count, self.organisedItemsProvider.parsedItemsByFeedProviderName)
os_log(.debug, log: self.log, "Updated %i feeds for \"%@\"", feedIDsAndItems.count, self.organisedItemsProvider.parsedItemsByFeedProviderName)
self.didFinish()
}
}

View File

@@ -113,7 +113,7 @@ public final class Folder: SidebarItem, Renamable, Container, Hashable {
return topLevelFeeds.contains(feed)
}
public func addWebFeed(_ feed: Feed) {
public func addFeed(_ feed: Feed) {
topLevelFeeds.insert(feed)
postChildrenDidChangeNotification()
}
@@ -126,7 +126,7 @@ public final class Folder: SidebarItem, Renamable, Container, Hashable {
postChildrenDidChangeNotification()
}
public func removeWebFeed(_ feed: Feed) {
public func removeFeed(_ feed: Feed) {
topLevelFeeds.remove(feed)
postChildrenDidChangeNotification()
}

View File

@@ -50,13 +50,13 @@ final class LocalAccountDelegate: AccountDelegate {
return
}
let webFeeds = account.flattenedFeeds()
refreshProgress.addToNumberOfTasksAndRemaining(webFeeds.count)
let feeds = account.flattenedFeeds()
refreshProgress.addToNumberOfTasksAndRemaining(feeds.count)
let group = DispatchGroup()
group.enter()
refresher?.refreshFeeds(webFeeds) {
refresher?.refreshFeeds(feeds) {
group.leave()
}
@@ -122,38 +122,38 @@ final class LocalAccountDelegate: AccountDelegate {
}
func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
func createFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
guard let url = URL(string: urlString) else {
completion(.failure(LocalAccountDelegateError.invalidParameter))
return
}
createRSSWebFeed(for: account, url: url, editedName: name, container: container, completion: completion)
createRSSFeed(for: account, url: url, editedName: name, container: container, completion: completion)
}
func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
feed.editedName = name
completion(.success(()))
}
func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
container.removeWebFeed(feed)
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
container.removeFeed(feed)
completion(.success(()))
}
func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
from.removeWebFeed(feed)
to.addWebFeed(feed)
func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
from.removeFeed(feed)
to.addFeed(feed)
completion(.success(()))
}
func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
container.addWebFeed(feed)
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
container.addFeed(feed)
completion(.success(()))
}
func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
container.addWebFeed(feed)
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
container.addFeed(feed)
completion(.success(()))
}
@@ -231,7 +231,7 @@ extension LocalAccountDelegate: LocalAccountRefresherDelegate {
private extension LocalAccountDelegate {
func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
func createRSSFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
// We need to use a batch update here because we need to assign add the feed to the
// container before the name has been downloaded. This will put it in the sidebar
@@ -261,9 +261,9 @@ private extension LocalAccountDelegate {
self.refreshProgress.completeTask()
if let parsedFeed = parsedFeed {
let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
feed.editedName = editedName
container.addWebFeed(feed)
container.addFeed(feed)
account.update(feed, with: parsedFeed, {_ in
BatchUpdate.shared.end()

View File

@@ -48,7 +48,7 @@ extension NewsBlurAccountDelegate {
folders.forEach { folder in
if !folderNames.contains(folder.name ?? "") {
for feed in folder.topLevelFeeds {
account.addWebFeed(feed)
account.addFeed(feed)
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
}
account.removeFolder(folder)
@@ -86,7 +86,7 @@ extension NewsBlurAccountDelegate {
for folder in folders {
for feed in folder.topLevelFeeds {
if !newsBlurFeedIds.contains(feed.feedID) {
folder.removeWebFeed(feed)
folder.removeFeed(feed)
}
}
}
@@ -94,7 +94,7 @@ extension NewsBlurAccountDelegate {
for feed in account.topLevelFeeds {
if !newsBlurFeedIds.contains(feed.feedID) {
account.removeWebFeed(feed)
account.removeFeed(feed)
}
}
@@ -103,13 +103,13 @@ extension NewsBlurAccountDelegate {
feeds.forEach { feed in
let subFeedId = String(feed.feedID)
if let webFeed = account.existingWebFeed(withWebFeedID: subFeedId) {
webFeed.name = feed.name
if let feed = account.existingFeed(withFeedID: subFeedId) {
feed.name = feed.name
// If the name has been changed on the server remove the locally edited name
webFeed.editedName = nil
webFeed.homePageURL = feed.homePageURL
webFeed.externalID = String(feed.feedID)
webFeed.faviconURL = feed.faviconURL
feed.editedName = nil
feed.homePageURL = feed.homePageURL
feed.externalID = String(feed.feedID)
feed.faviconURL = feed.faviconURL
}
else {
feedsToAdd.insert(feed)
@@ -118,9 +118,9 @@ extension NewsBlurAccountDelegate {
// Actually add feeds all in one go, so we dont trigger various rebuilding things that Account does.
feedsToAdd.forEach { feed in
let webFeed = account.createWebFeed(with: feed.name, url: feed.feedURL, webFeedID: String(feed.feedID), homePageURL: feed.homePageURL)
webFeed.externalID = String(feed.feedID)
account.addWebFeed(webFeed)
let feed = account.createFeed(with: feed.name, url: feed.feedURL, feedID: String(feed.feedID), homePageURL: feed.homePageURL)
feed.externalID = String(feed.feedID)
account.addFeed(feed)
}
}
@@ -157,9 +157,9 @@ extension NewsBlurAccountDelegate {
// Move any feeds not in the folder to the account
for feed in folder.topLevelFeeds {
if !newsBlurFolderFeedIDs.contains(feed.feedID) {
folder.removeWebFeed(feed)
folder.removeFeed(feed)
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
account.addWebFeed(feed)
account.addFeed(feed)
}
}
@@ -169,11 +169,11 @@ extension NewsBlurAccountDelegate {
for relationship in folderRelationships {
let folderFeedID = String(relationship.feedID)
if !folderFeedIds.contains(folderFeedID) {
guard let feed = account.existingWebFeed(withWebFeedID: folderFeedID) else {
guard let feed = account.existingFeed(withFeedID: folderFeedID) else {
continue
}
saveFolderRelationship(for: feed, withFolderName: folderName, id: relationship.folderName)
folder.addWebFeed(feed)
folder.addFeed(feed)
}
}
}
@@ -184,12 +184,12 @@ extension NewsBlurAccountDelegate {
let newsBlurFolderFeedIDs = folderRelationships.map { String($0.feedID) }
for feed in account.topLevelFeeds {
if !newsBlurFolderFeedIDs.contains(feed.feedID) {
account.removeWebFeed(feed)
account.removeFeed(feed)
}
}
} else {
for feed in account.topLevelFeeds {
account.removeWebFeed(feed)
account.removeFeed(feed)
}
}
@@ -419,24 +419,24 @@ extension NewsBlurAccountDelegate {
}
DispatchQueue.main.async {
let webFeed = account.createWebFeed(with: feed.name, url: feed.feedURL, webFeedID: String(feed.feedID), homePageURL: feed.homePageURL)
webFeed.externalID = String(feed.feedID)
webFeed.faviconURL = feed.faviconURL
let feed = account.createFeed(with: feed.name, url: feed.feedURL, feedID: String(feed.feedID), homePageURL: feed.homePageURL)
feed.externalID = String(feed.feedID)
feed.faviconURL = feed.faviconURL
account.addFeed(webFeed, to: container) { result in
account.addFeed(feed, to: container) { result in
switch result {
case .success:
if let name = name {
account.renameWebFeed(webFeed, to: name) { result in
account.renameFeed(feed, to: name) { result in
switch result {
case .success:
self.initialFeedDownload(account: account, feed: webFeed, completion: completion)
self.initialFeedDownload(account: account, feed: feed, completion: completion)
case .failure(let error):
completion(.failure(error))
}
}
} else {
self.initialFeedDownload(account: account, feed: webFeed, completion: completion)
self.initialFeedDownload(account: account, feed: feed, completion: completion)
}
case .failure(let error):
completion(.failure(error))
@@ -532,17 +532,17 @@ extension NewsBlurAccountDelegate {
let feedID = feed.feedID
if folderName == nil {
account.removeWebFeed(feed)
account.removeFeed(feed)
}
if let folders = account.folders {
for folder in folders where folderName != nil && folder.name == folderName {
folder.removeWebFeed(feed)
folder.removeFeed(feed)
}
}
if account.existingWebFeed(withWebFeedID: feedID) != nil {
account.clearWebFeedMetadata(feed)
if account.existingFeed(withFeedID: feedID) != nil {
account.clearFeedMetadata(feed)
}
completion(.success(()))

View File

@@ -331,17 +331,17 @@ final class NewsBlurAccountDelegate: AccountDelegate {
return datePublished >= since
}
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL }).mapValues {
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL }).mapValues {
Set($0)
}
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) { error in
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) { error in
if let error = error {
completion(.failure(error))
return
}
completion(.success(!webFeedIDsAndItems.isEmpty))
completion(.success(!feedIDsAndItems.isEmpty))
}
}
@@ -423,7 +423,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
}
}
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> ()) {
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> ()) {
refreshProgress.addToNumberOfTasksAndRemaining(1)
let folderName = (container as? Folder)?.name
@@ -442,7 +442,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
}
}
func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> ()) {
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> ()) {
guard let feedID = feed.externalID else {
completion(.failure(NewsBlurError.invalidParameter))
return
@@ -469,11 +469,11 @@ final class NewsBlurAccountDelegate: AccountDelegate {
}
}
func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
guard let folder = container as? Folder else {
DispatchQueue.main.async {
if let account = container as? Account {
account.addWebFeed(feed)
account.addFeed(feed)
}
completion(.success(()))
}
@@ -483,16 +483,16 @@ final class NewsBlurAccountDelegate: AccountDelegate {
let folderName = folder.name ?? ""
saveFolderRelationship(for: feed, withFolderName: folderName, id: folderName)
folder.addWebFeed(feed)
folder.addFeed(feed)
completion(.success(()))
}
func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
deleteFeed(for: account, with: feed, from: container, completion: completion)
}
func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> ()) {
func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> ()) {
guard let feedID = feed.externalID else {
completion(.failure(NewsBlurError.invalidParameter))
return
@@ -509,8 +509,8 @@ final class NewsBlurAccountDelegate: AccountDelegate {
switch result {
case .success:
from.removeWebFeed(feed)
to.addWebFeed(feed)
from.removeFeed(feed)
to.addFeed(feed)
completion(.success(()))
case .failure(let error):
@@ -519,7 +519,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
}
}
func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
if let existingFeed = account.existingFeed(withURL: feed.url) {
account.addFeed(existingFeed, to: container) { result in
switch result {
@@ -530,7 +530,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
}
}
} else {
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
switch result {
case .success:
completion(.success(()))
@@ -562,7 +562,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
case .success(let folder):
for feed in feedsToRestore {
group.enter()
self.restoreWebFeed(for: account, feed: feed, container: folder) { result in
self.restoreFeed(for: account, feed: feed, container: folder) { result in
group.leave()
switch result {
case .success:

View File

@@ -358,7 +358,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
switch result {
case .success:
DispatchQueue.main.async {
account.clearWebFeedMetadata(feed)
account.clearFeedMetadata(feed)
}
case .failure(let error):
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
@@ -390,7 +390,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
guard let url = URL(string: url) else {
completion(.failure(ReaderAPIAccountDelegateError.invalidParameter))
return
@@ -439,7 +439,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
// This error should never happen
guard let subscriptionID = feed.externalID else {
@@ -466,7 +466,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
func removeWebFeed(for account: Account, with feed: Feed, from 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) {
guard let subscriptionID = feed.externalID else {
completion(.failure(ReaderAPIAccountDelegateError.invalidParameter))
return
@@ -478,11 +478,11 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
switch result {
case .success:
DispatchQueue.main.async {
account.clearWebFeedMetadata(feed)
account.removeWebFeed(feed)
account.clearFeedMetadata(feed)
account.removeFeed(feed)
if let folders = account.folders {
for folder in folders {
folder.removeWebFeed(feed)
folder.removeFeed(feed)
}
}
completion(.success(()))
@@ -496,9 +496,9 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
}
func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: 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) {
if from is Account {
addWebFeed(for: account, with: feed, to: to, completion: completion)
addFeed(for: account, with: feed, to: to, completion: completion)
} else {
guard
let subscriptionId = feed.externalID,
@@ -514,8 +514,8 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
self.refreshProgress.completeTask()
switch result {
case .success:
from.removeWebFeed(feed)
to.addWebFeed(feed)
from.removeFeed(feed)
to.addFeed(feed)
completion(.success(()))
case .failure(let error):
completion(.failure(error))
@@ -524,7 +524,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
}
func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
if let folder = container as? Folder, let feedExternalID = feed.externalID {
refreshProgress.addToNumberOfTasksAndRemaining(1)
caller.createTagging(subscriptionID: feedExternalID, tagName: folder.name ?? "") { result in
@@ -533,8 +533,8 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
case .success:
DispatchQueue.main.async {
self.saveFolderRelationship(for: feed, folderExternalID: folder.externalID, feedExternalID: feedExternalID)
account.removeWebFeed(feed)
folder.addWebFeed(feed)
account.removeFeed(feed)
folder.addFeed(feed)
completion(.success(()))
}
case .failure(let error):
@@ -554,7 +554,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
}
func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
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
@@ -566,7 +566,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
}
} else {
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
switch result {
case .success:
completion(.success(()))
@@ -587,7 +587,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
folder.topLevelFeeds.remove(feed)
group.enter()
restoreWebFeed(for: account, feed: feed, container: folder) { result in
restoreFeed(for: account, feed: feed, container: folder) { result in
group.leave()
switch result {
case .success:
@@ -720,7 +720,7 @@ private extension ReaderAPIAccountDelegate {
folders.forEach { folder in
if !readerFolderExternalIDs.contains(folder.externalID ?? "") {
for feed in folder.topLevelFeeds {
account.addWebFeed(feed)
account.addFeed(feed)
clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
}
account.removeFolder(folder)
@@ -760,7 +760,7 @@ private extension ReaderAPIAccountDelegate {
for folder in folders {
for feed in folder.topLevelFeeds {
if !subFeedIds.contains(feed.feedID) {
folder.removeWebFeed(feed)
folder.removeFeed(feed)
}
}
}
@@ -768,22 +768,22 @@ private extension ReaderAPIAccountDelegate {
for feed in account.topLevelFeeds {
if !subFeedIds.contains(feed.feedID) {
account.clearWebFeedMetadata(feed)
account.removeWebFeed(feed)
account.clearFeedMetadata(feed)
account.removeFeed(feed)
}
}
// Add any feeds we don't have and update any we do
subscriptions.forEach { subscription in
if let feed = account.existingWebFeed(withWebFeedID: subscription.feedID) {
if let feed = account.existingFeed(withFeedID: subscription.feedID) {
feed.name = subscription.name
feed.editedName = nil
feed.homePageURL = subscription.homePageURL
} else {
let feed = account.createWebFeed(with: subscription.name, url: subscription.url, webFeedID: subscription.feedID, homePageURL: subscription.homePageURL)
let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: subscription.feedID, homePageURL: subscription.homePageURL)
feed.externalID = subscription.feedID
account.addWebFeed(feed)
account.addFeed(feed)
}
}
@@ -820,9 +820,9 @@ private extension ReaderAPIAccountDelegate {
// Move any feeds not in the folder to the account
for feed in folder.topLevelFeeds {
if !taggingFeedIDs.contains(feed.feedID) {
folder.removeWebFeed(feed)
folder.removeFeed(feed)
clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
account.addWebFeed(feed)
account.addFeed(feed)
}
}
@@ -832,11 +832,11 @@ private extension ReaderAPIAccountDelegate {
for subscription in groupedTaggings {
let taggingFeedID = subscription.feedID
if !folderFeedIds.contains(taggingFeedID) {
guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else {
guard let feed = account.existingFeed(withFeedID: taggingFeedID) else {
continue
}
saveFolderRelationship(for: feed, folderExternalID: folderExternalID, feedExternalID: subscription.feedID)
folder.addWebFeed(feed)
folder.addFeed(feed)
}
}
@@ -847,7 +847,7 @@ private extension ReaderAPIAccountDelegate {
// Remove all feeds from the account container that have a tag
for feed in account.topLevelFeeds {
if taggedFeedIDs.contains(feed.feedID) {
account.removeWebFeed(feed)
account.removeFeed(feed)
}
}
}
@@ -921,14 +921,14 @@ private extension ReaderAPIAccountDelegate {
DispatchQueue.main.async {
let feed = account.createWebFeed(with: sub.name, url: sub.url, webFeedID: String(sub.feedID), homePageURL: sub.homePageURL)
let feed = account.createFeed(with: sub.name, url: sub.url, feedID: String(sub.feedID), homePageURL: sub.homePageURL)
feed.externalID = String(sub.feedID)
account.addFeed(feed, to: container) { result in
switch result {
case .success:
if let name = name {
self.renameWebFeed(for: account, with: feed, to: name) { result in
self.renameFeed(for: account, with: feed, to: name) { result in
switch result {
case .success:
self.initialFeedDownload(account: account, feed: feed, completion: completion)
@@ -952,7 +952,7 @@ private extension ReaderAPIAccountDelegate {
refreshProgress.addToNumberOfTasksAndRemaining(5)
// Download the initial articles
self.caller.retrieveItemIDs(type: .allForFeed, webFeedID: feed.feedID) { result in
self.caller.retrieveItemIDs(type: .allForFeed, feedID: feed.feedID) { result in
self.refreshProgress.completeTask()
switch result {
case .success(let articleIDs):
@@ -1032,8 +1032,8 @@ private extension ReaderAPIAccountDelegate {
func processEntries(account: Account, entries: [ReaderAPIEntry]?, completion: @escaping VoidCompletionBlock) {
let parsedItems = mapEntriesToParsedItems(account: account, entries: entries)
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) { _ in
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) { _ in
completion()
}
}

View File

@@ -554,7 +554,7 @@ final class ReaderAPICaller: NSObject {
}
func retrieveItemIDs(type: ItemIDType, webFeedID: String? = nil, completion: @escaping ((Result<[String], Error>) -> Void)) {
func retrieveItemIDs(type: ItemIDType, feedID: String? = nil, completion: @escaping ((Result<[String], Error>) -> Void)) {
guard let baseURL = apiBaseURL else {
completion(.failure(CredentialsError.incompleteCredentials))
return
@@ -579,13 +579,13 @@ final class ReaderAPICaller: NSObject {
queryItems.append(URLQueryItem(name: "ot", value: String(Int(sinceTimeInterval))))
queryItems.append(URLQueryItem(name: "s", value: ReaderStreams.readingList.rawValue))
case .allForFeed:
guard let webFeedID = webFeedID else {
guard let feedID = feedID else {
completion(.failure(ReaderAPIAccountDelegateError.invalidParameter))
return
}
let sinceTimeInterval = (Calendar.current.date(byAdding: .month, value: -3, to: Date()) ?? Date()).timeIntervalSince1970
queryItems.append(URLQueryItem(name: "ot", value: String(Int(sinceTimeInterval))))
queryItems.append(URLQueryItem(name: "s", value: webFeedID))
queryItems.append(URLQueryItem(name: "s", value: feedID))
case .unread:
queryItems.append(URLQueryItem(name: "s", value: ReaderStreams.readingList.rawValue))
queryItems.append(URLQueryItem(name: "xt", value: ReaderState.read.rawValue))

View File

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

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?.topLevelWebFeeds.count ?? 0)
XCTAssertEqual(2, account.topLevelWebFeeds.count)
XCTAssertEqual(156, folder?.topLevelFeeds.count ?? 0)
XCTAssertEqual(2, account.topLevelFeeds.count)
// Test Adding a Feed to the folder
testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "JSON/taggings_add.json"
@@ -45,8 +45,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase {
}
waitForExpectations(timeout: 5, handler: nil)
XCTAssertEqual(157, folder?.topLevelWebFeeds.count ?? 0)
XCTAssertEqual(1, account.topLevelWebFeeds.count)
XCTAssertEqual(157, folder?.topLevelFeeds.count ?? 0)
XCTAssertEqual(1, account.topLevelFeeds.count)
// Test Deleting some Feeds from the folder
testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "JSON/taggings_delete.json"
@@ -57,8 +57,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase {
}
waitForExpectations(timeout: 5, handler: nil)
XCTAssertEqual(153, folder?.topLevelWebFeeds.count ?? 0)
XCTAssertEqual(5, account.topLevelWebFeeds.count)
XCTAssertEqual(153, folder?.topLevelFeeds.count ?? 0)
XCTAssertEqual(5, account.topLevelFeeds.count)
TestAccountManager.shared.deleteAccount(account)

View File

@@ -36,9 +36,9 @@ class AccountFeedbinSyncTest: XCTestCase {
}
waitForExpectations(timeout: 5, handler: nil)
XCTAssertEqual(224, account.flattenedWebFeeds().count)
XCTAssertEqual(224, account.flattenedFeeds().count)
let daringFireball = account.idToWebFeedDictionary["1296379"]
let daringFireball = account.idToFeedDictionary["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.flattenedWebFeeds().count)
XCTAssertEqual(225, account.flattenedFeeds().count)
let bPixels = account.idToWebFeedDictionary["1096623"]
let bPixels = account.idToFeedDictionary["1096623"]
XCTAssertEqual("Beautiful Pixels", bPixels?.name)
XCTAssertEqual("https://feedpress.me/beautifulpixels", bPixels?.url)
XCTAssertEqual("https://beautifulpixels.com/", bPixels?.homePageURL)

View File

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

View File

@@ -55,7 +55,7 @@ class FeedlyEntryParserTests: XCTestCase {
XCTAssertEqual(item.uniqueID, entry.id)
// The following is not an error.
// The feedURL must match the webFeedID for the article to be connected to its matching feed.
// The feedURL must match the feedID for the article to be connected to its matching feed.
XCTAssertEqual(item.feedURL, origin.streamId)
XCTAssertEqual(item.title, entry.title)
XCTAssertEqual(item.contentHTML, content.content)

View File

@@ -22,7 +22,7 @@ class FeedlyFeedParserTests: XCTestCase {
XCTAssertEqual(parser.title, name)
XCTAssertEqual(parser.homePageURL, website)
XCTAssertEqual(parser.url, url)
XCTAssertEqual(parser.webFeedID, id)
XCTAssertEqual(parser.feedID, id)
}
func testSanitization() {
@@ -36,6 +36,6 @@ class FeedlyFeedParserTests: XCTestCase {
XCTAssertEqual(parser.title, name)
XCTAssertEqual(parser.homePageURL, website)
XCTAssertEqual(parser.url, url)
XCTAssertEqual(parser.webFeedID, id)
XCTAssertEqual(parser.feedID, id)
}
}

View File

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

View File

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

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 webFeedID: String // Likely a URL, but not necessarily
public let feedID: 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?
@@ -28,9 +28,9 @@ public struct Article: Hashable {
public let authors: Set<Author>?
public let 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?, datePublished: Date?, dateModified: Date?, authors: Set<Author>?, 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?, datePublished: Date?, dateModified: Date?, authors: Set<Author>?, status: ArticleStatus) {
self.accountID = accountID
self.webFeedID = webFeedID
self.feedID = feedID
self.uniqueID = uniqueID
self.title = title
self.contentHTML = contentHTML
@@ -48,12 +48,12 @@ public struct Article: Hashable {
self.articleID = articleID
}
else {
self.articleID = Article.calculatedArticleID(webFeedID: webFeedID, uniqueID: uniqueID)
self.articleID = Article.calculatedArticleID(feedID: feedID, uniqueID: uniqueID)
}
}
public static func calculatedArticleID(webFeedID: String, uniqueID: String) -> String {
return databaseIDWithString("\(webFeedID) \(uniqueID)")
public static func calculatedArticleID(feedID: String, uniqueID: String) -> String {
return databaseIDWithString("\(feedID) \(uniqueID)")
}
// MARK: - Hashable
@@ -65,7 +65,7 @@ public struct Article: Hashable {
// MARK: - Equatable
static public func ==(lhs: Article, rhs: Article) -> Bool {
return lhs.articleID == rhs.articleID && lhs.accountID == rhs.accountID && lhs.webFeedID == rhs.webFeedID && lhs.uniqueID == rhs.uniqueID && lhs.title == rhs.title && lhs.contentHTML == rhs.contentHTML && lhs.contentText == rhs.contentText && lhs.rawLink == rhs.rawLink && lhs.rawExternalLink == rhs.rawExternalLink && lhs.summary == rhs.summary && lhs.rawImageLink == rhs.rawImageLink && lhs.datePublished == rhs.datePublished && lhs.dateModified == rhs.dateModified && lhs.authors == rhs.authors
return lhs.articleID == rhs.articleID && lhs.accountID == rhs.accountID && lhs.feedID == rhs.feedID && lhs.uniqueID == rhs.uniqueID && lhs.title == rhs.title && lhs.contentHTML == rhs.contentHTML && lhs.contentText == rhs.contentText && lhs.rawLink == rhs.rawLink && lhs.rawExternalLink == rhs.rawExternalLink && lhs.summary == rhs.summary && lhs.rawImageLink == rhs.rawImageLink && lhs.datePublished == rhs.datePublished && lhs.dateModified == rhs.dateModified && lhs.authors == rhs.authors
}
}

View File

@@ -17,7 +17,7 @@ import Articles
// Main thread only.
public typealias UnreadCountDictionary = [String: Int] // webFeedID: unreadCount
public typealias UnreadCountDictionary = [String: Int] // feedID: unreadCount
public typealias UnreadCountDictionaryCompletionResult = Result<UnreadCountDictionary,DatabaseError>
public typealias UnreadCountDictionaryCompletionBlock = (UnreadCountDictionaryCompletionResult) -> Void
@@ -90,36 +90,36 @@ public final class ArticlesDatabase {
// MARK: - Fetching Articles
public func fetchArticles(_ webFeedID: String) throws -> Set<Article> {
return try articlesTable.fetchArticles(webFeedID)
public func fetchArticles(_ feedID: String) throws -> Set<Article> {
return try articlesTable.fetchArticles(feedID)
}
public func fetchArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
return try articlesTable.fetchArticles(webFeedIDs)
public func fetchArticles(_ feedIDs: Set<String>) throws -> Set<Article> {
return try articlesTable.fetchArticles(feedIDs)
}
public func fetchArticles(articleIDs: Set<String>) throws -> Set<Article> {
return try articlesTable.fetchArticles(articleIDs: articleIDs)
}
public func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try articlesTable.fetchUnreadArticles(webFeedIDs, limit)
public func fetchUnreadArticles(_ feedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try articlesTable.fetchUnreadArticles(feedIDs, limit)
}
public func fetchTodayArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try articlesTable.fetchArticlesSince(webFeedIDs, todayCutoffDate(), limit)
public func fetchTodayArticles(_ feedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try articlesTable.fetchArticlesSince(feedIDs, todayCutoffDate(), limit)
}
public func fetchStarredArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try articlesTable.fetchStarredArticles(webFeedIDs, limit)
public func fetchStarredArticles(_ feedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try articlesTable.fetchStarredArticles(feedIDs, limit)
}
public func fetchStarredArticlesCount(_ webFeedIDs: Set<String>) throws -> Int {
return try articlesTable.fetchStarredArticlesCount(webFeedIDs)
public func fetchStarredArticlesCount(_ feedIDs: Set<String>) throws -> Int {
return try articlesTable.fetchStarredArticlesCount(feedIDs)
}
public func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) throws -> Set<Article> {
return try articlesTable.fetchArticlesMatching(searchString, webFeedIDs)
public func fetchArticlesMatching(_ searchString: String, _ feedIDs: Set<String>) throws -> Set<Article> {
return try articlesTable.fetchArticlesMatching(searchString, feedIDs)
}
public func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>) throws -> Set<Article> {
@@ -128,32 +128,32 @@ public final class ArticlesDatabase {
// MARK: - Fetching Articles Async
public func fetchArticlesAsync(_ webFeedID: String, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchArticlesAsync(webFeedID, completion)
public func fetchArticlesAsync(_ feedID: String, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchArticlesAsync(feedID, completion)
}
public func fetchArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchArticlesAsync(webFeedIDs, completion)
public func fetchArticlesAsync(_ feedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchArticlesAsync(feedIDs, completion)
}
public func fetchArticlesAsync(articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchArticlesAsync(articleIDs: articleIDs, completion)
}
public func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchUnreadArticlesAsync(webFeedIDs, limit, completion)
public func fetchUnreadArticlesAsync(_ feedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchUnreadArticlesAsync(feedIDs, limit, completion)
}
public func fetchTodayArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchArticlesSinceAsync(webFeedIDs, todayCutoffDate(), limit, completion)
public func fetchTodayArticlesAsync(_ feedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchArticlesSinceAsync(feedIDs, todayCutoffDate(), limit, completion)
}
public func fetchedStarredArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchStarredArticlesAsync(webFeedIDs, limit, completion)
public func fetchedStarredArticlesAsync(_ feedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchStarredArticlesAsync(feedIDs, limit, completion)
}
public func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchArticlesMatchingAsync(searchString, webFeedIDs, completion)
public func fetchArticlesMatchingAsync(_ searchString: String, _ feedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchArticlesMatchingAsync(searchString, feedIDs, completion)
}
public func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
@@ -174,8 +174,8 @@ public final class ArticlesDatabase {
}
/// Fetch unread count for a single feed.
public func fetchUnreadCount(_ webFeedID: String, _ completion: @escaping SingleUnreadCountCompletionBlock) {
let operation = FetchFeedUnreadCountOperation(webFeedID: webFeedID, databaseQueue: queue, cutoffDate: articlesTable.articleCutoffDate)
public func fetchUnreadCount(_ feedID: String, _ completion: @escaping SingleUnreadCountCompletionBlock) {
let operation = FetchFeedUnreadCountOperation(feedID: feedID, databaseQueue: queue, cutoffDate: articlesTable.articleCutoffDate)
operation.completionBlock = { operation in
let fetchOperation = operation as! FetchFeedUnreadCountOperation
completion(fetchOperation.result)
@@ -183,9 +183,9 @@ public final class ArticlesDatabase {
operationQueue.add(operation)
}
/// Fetch non-zero unread counts for given webFeedIDs.
public func fetchUnreadCounts(for webFeedIDs: Set<String>, _ completion: @escaping UnreadCountDictionaryCompletionBlock) {
let operation = FetchUnreadCountsForFeedsOperation(webFeedIDs: webFeedIDs, databaseQueue: queue)
/// Fetch non-zero unread counts for given feedIDs.
public func fetchUnreadCounts(for feedIDs: Set<String>, _ completion: @escaping UnreadCountDictionaryCompletionBlock) {
let operation = FetchUnreadCountsForFeedsOperation(feedIDs: feedIDs, databaseQueue: queue)
operation.completionBlock = { operation in
let fetchOperation = operation as! FetchUnreadCountsForFeedsOperation
completion(fetchOperation.result)
@@ -193,30 +193,30 @@ public final class ArticlesDatabase {
operationQueue.add(operation)
}
public func fetchUnreadCountForToday(for webFeedIDs: Set<String>, completion: @escaping SingleUnreadCountCompletionBlock) {
fetchUnreadCount(for: webFeedIDs, since: todayCutoffDate(), completion: completion)
public func fetchUnreadCountForToday(for feedIDs: Set<String>, completion: @escaping SingleUnreadCountCompletionBlock) {
fetchUnreadCount(for: feedIDs, since: todayCutoffDate(), completion: completion)
}
public func fetchUnreadCount(for webFeedIDs: Set<String>, since: Date, completion: @escaping SingleUnreadCountCompletionBlock) {
articlesTable.fetchUnreadCount(webFeedIDs, since, completion)
public func fetchUnreadCount(for feedIDs: Set<String>, since: Date, completion: @escaping SingleUnreadCountCompletionBlock) {
articlesTable.fetchUnreadCount(feedIDs, since, completion)
}
public func fetchStarredAndUnreadCount(for webFeedIDs: Set<String>, completion: @escaping SingleUnreadCountCompletionBlock) {
articlesTable.fetchStarredAndUnreadCount(webFeedIDs, completion)
public func fetchStarredAndUnreadCount(for feedIDs: Set<String>, completion: @escaping SingleUnreadCountCompletionBlock) {
articlesTable.fetchStarredAndUnreadCount(feedIDs, completion)
}
// MARK: - Saving, Updating, and Deleting Articles
/// Update articles and save new ones  for feed-based systems (local and iCloud).
public func update(with parsedItems: Set<ParsedItem>, webFeedID: String, deleteOlder: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
public func update(with parsedItems: Set<ParsedItem>, feedID: String, deleteOlder: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
precondition(retentionStyle == .feedBased)
articlesTable.update(parsedItems, webFeedID, deleteOlder, completion)
articlesTable.update(parsedItems, feedID, deleteOlder, completion)
}
/// Update articles and save new ones for sync systems (Feedbin, Feedly, etc.).
public func update(webFeedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
public func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
precondition(retentionStyle == .syncSystem)
articlesTable.update(webFeedIDsAndItems, defaultRead, completion)
articlesTable.update(feedIDsAndItems, defaultRead, completion)
}
/// Delete articles
@@ -293,11 +293,11 @@ public final class ArticlesDatabase {
/// This prevents the database from growing forever. If we didnt do this:
/// 1) The database would grow to an inordinate size, and
/// 2) the app would become very slow.
public func cleanupDatabaseAtStartup(subscribedToWebFeedIDs: Set<String>) {
public func cleanupDatabaseAtStartup(subscribedToFeedIDs: Set<String>) {
if retentionStyle == .syncSystem {
articlesTable.deleteOldArticles()
}
articlesTable.deleteArticlesNotInSubscribedToFeedIDs(subscribedToWebFeedIDs)
articlesTable.deleteArticlesNotInSubscribedToFeedIDs(subscribedToFeedIDs)
articlesTable.deleteOldStatuses()
}

View File

@@ -48,20 +48,20 @@ final class ArticlesTable: DatabaseTable {
// MARK: - Fetching Articles for Feed
func fetchArticles(_ webFeedID: String) throws -> Set<Article> {
return try fetchArticles{ self.fetchArticlesForFeedID(webFeedID, $0) }
func fetchArticles(_ feedID: String) throws -> Set<Article> {
return try fetchArticles{ self.fetchArticlesForFeedID(feedID, $0) }
}
func fetchArticlesAsync(_ webFeedID: String, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchArticlesForFeedID(webFeedID, $0) }, completion)
func fetchArticlesAsync(_ feedID: String, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchArticlesForFeedID(feedID, $0) }, completion)
}
func fetchArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
return try fetchArticles{ self.fetchArticles(webFeedIDs, $0) }
func fetchArticles(_ feedIDs: Set<String>) throws -> Set<Article> {
return try fetchArticles{ self.fetchArticles(feedIDs, $0) }
}
func fetchArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchArticles(webFeedIDs, $0) }, completion)
func fetchArticlesAsync(_ feedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchArticles(feedIDs, $0) }, completion)
}
// MARK: - Fetching Articles by articleID
@@ -76,36 +76,36 @@ final class ArticlesTable: DatabaseTable {
// MARK: - Fetching Unread Articles
func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try fetchArticles{ self.fetchUnreadArticles(webFeedIDs, limit, $0) }
func fetchUnreadArticles(_ feedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try fetchArticles{ self.fetchUnreadArticles(feedIDs, limit, $0) }
}
func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchUnreadArticles(webFeedIDs, limit, $0) }, completion)
func fetchUnreadArticlesAsync(_ feedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchUnreadArticles(feedIDs, limit, $0) }, completion)
}
// MARK: - Fetching Today Articles
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?) throws -> Set<Article> {
return try fetchArticles{ self.fetchArticlesSince(webFeedIDs, cutoffDate, limit, $0) }
func fetchArticlesSince(_ feedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?) throws -> Set<Article> {
return try fetchArticles{ self.fetchArticlesSince(feedIDs, cutoffDate, limit, $0) }
}
func fetchArticlesSinceAsync(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchArticlesSince(webFeedIDs, cutoffDate, limit, $0) }, completion)
func fetchArticlesSinceAsync(_ feedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchArticlesSince(feedIDs, cutoffDate, limit, $0) }, completion)
}
// MARK: - Fetching Starred Articles
func fetchStarredArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try fetchArticles{ self.fetchStarredArticles(webFeedIDs, limit, $0) }
func fetchStarredArticles(_ feedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try fetchArticles{ self.fetchStarredArticles(feedIDs, limit, $0) }
}
func fetchStarredArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchStarredArticles(webFeedIDs, limit, $0) }, completion)
func fetchStarredArticlesAsync(_ feedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchStarredArticles(feedIDs, limit, $0) }, completion)
}
func fetchStarredArticlesCount(_ webFeedIDs: Set<String>) throws -> Int {
return try fetchArticlesCount{ self.fetchStarredArticlesCount(webFeedIDs, $0) }
func fetchStarredArticlesCount(_ feedIDs: Set<String>) throws -> Int {
return try fetchArticlesCount{ self.fetchStarredArticlesCount(feedIDs, $0) }
}
// MARK: - Fetching Search Articles
@@ -129,9 +129,9 @@ final class ArticlesTable: DatabaseTable {
return articles
}
func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) throws -> Set<Article> {
func fetchArticlesMatching(_ searchString: String, _ feedIDs: Set<String>) throws -> Set<Article> {
var articles = try fetchArticlesMatching(searchString)
articles = articles.filter{ webFeedIDs.contains($0.webFeedID) }
articles = articles.filter{ feedIDs.contains($0.feedID) }
return articles
}
@@ -141,8 +141,8 @@ final class ArticlesTable: DatabaseTable {
return articles
}
func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchArticlesMatching(searchString, webFeedIDs, $0) }, completion)
func fetchArticlesMatchingAsync(_ searchString: String, _ feedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchArticlesMatching(searchString, feedIDs, $0) }, completion)
}
func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
@@ -195,7 +195,7 @@ final class ArticlesTable: DatabaseTable {
// MARK: - Updating and Deleting
func update(_ parsedItems: Set<ParsedItem>, _ webFeedID: String, _ deleteOlder: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
func update(_ parsedItems: Set<ParsedItem>, _ feedID: String, _ deleteOlder: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
precondition(retentionStyle == .feedBased)
if parsedItems.isEmpty {
callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
@@ -220,13 +220,13 @@ final class ArticlesTable: DatabaseTable {
let (statusesDictionary, _) = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, false, database) //1
assert(statusesDictionary.count == articleIDs.count)
let incomingArticles = Article.articlesWithParsedItems(parsedItems, webFeedID, self.accountID, statusesDictionary) //2
let incomingArticles = Article.articlesWithParsedItems(parsedItems, feedID, self.accountID, statusesDictionary) //2
if incomingArticles.isEmpty {
self.callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
return
}
let fetchedArticles = self.fetchArticlesForFeedID(webFeedID, database) //4
let fetchedArticles = self.fetchArticlesForFeedID(feedID, database) //4
let fetchedArticlesDictionary = fetchedArticles.dictionary()
let newArticles = self.findAndSaveNewArticles(incomingArticles, fetchedArticlesDictionary, database) //5
@@ -275,9 +275,9 @@ final class ArticlesTable: DatabaseTable {
}
}
func update(_ webFeedIDsAndItems: [String: Set<ParsedItem>], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
func update(_ feedIDsAndItems: [String: Set<ParsedItem>], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
precondition(retentionStyle == .syncSystem)
if webFeedIDsAndItems.isEmpty {
if feedIDsAndItems.isEmpty {
callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
return
}
@@ -295,14 +295,14 @@ final class ArticlesTable: DatabaseTable {
func makeDatabaseCalls(_ database: FMDatabase) {
var articleIDs = Set<String>()
for (_, parsedItems) in webFeedIDsAndItems {
for (_, parsedItems) in feedIDsAndItems {
articleIDs.formUnion(parsedItems.articleIDs())
}
let (statusesDictionary, _) = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1
assert(statusesDictionary.count == articleIDs.count)
let allIncomingArticles = Article.articlesWithWebFeedIDsAndItems(webFeedIDsAndItems, self.accountID, statusesDictionary) //2
let allIncomingArticles = Article.articlesWithFeedIDsAndItems(feedIDsAndItems, self.accountID, statusesDictionary) //2
if allIncomingArticles.isEmpty {
self.callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
return
@@ -371,9 +371,9 @@ final class ArticlesTable: DatabaseTable {
// MARK: - Unread Counts
func fetchUnreadCount(_ webFeedIDs: Set<String>, _ since: Date, _ completion: @escaping SingleUnreadCountCompletionBlock) {
func fetchUnreadCount(_ feedIDs: Set<String>, _ since: Date, _ completion: @escaping SingleUnreadCountCompletionBlock) {
// Get unread count for today, for instance.
if webFeedIDs.isEmpty {
if feedIDs.isEmpty {
completion(.success(0))
return
}
@@ -381,11 +381,11 @@ final class ArticlesTable: DatabaseTable {
queue.runInDatabase { databaseResult in
func makeDatabaseCalls(_ database: FMDatabase) {
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.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;"
var parameters = [Any]()
parameters += Array(webFeedIDs) as [Any]
parameters += Array(feedIDs) as [Any]
parameters += [since] as [Any]
parameters += [since] as [Any]
@@ -407,8 +407,8 @@ final class ArticlesTable: DatabaseTable {
}
}
func fetchStarredAndUnreadCount(_ webFeedIDs: Set<String>, _ completion: @escaping SingleUnreadCountCompletionBlock) {
if webFeedIDs.isEmpty {
func fetchStarredAndUnreadCount(_ feedIDs: Set<String>, _ completion: @escaping SingleUnreadCountCompletionBlock) {
if feedIDs.isEmpty {
completion(.success(0))
return
}
@@ -416,9 +416,9 @@ final class ArticlesTable: DatabaseTable {
queue.runInDatabase { databaseResult in
func makeDatabaseCalls(_ database: FMDatabase) {
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
let sql = "select count(*) from articles natural join statuses where feedID in \(placeholders) and read=0 and starred=1;"
let parameters = Array(webFeedIDs) as [Any]
let parameters = Array(feedIDs) as [Any]
let unreadCount = self.numberWithSQLAndParameters(sql, parameters, in: database)
@@ -609,16 +609,16 @@ 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(_ webFeedIDs: Set<String>) {
if webFeedIDs.isEmpty {
func deleteArticlesNotInSubscribedToFeedIDs(_ feedIDs: Set<String>) {
if feedIDs.isEmpty {
return
}
queue.runInDatabase { databaseResult in
func makeDatabaseCalls(_ database: FMDatabase) {
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
let sql = "select articleID from articles where feedID not in \(placeholders);"
let parameters = Array(webFeedIDs) as [Any]
let parameters = Array(feedIDs) as [Any]
guard let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) else {
return
}
@@ -820,24 +820,24 @@ private extension ArticlesTable {
return articlesWithResultSet(resultSet, database)
}
func fetchArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
func fetchArticles(_ feedIDs: 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 webFeedIDs.isEmpty {
if feedIDs.isEmpty {
return Set<Article>()
}
let parameters = webFeedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let parameters = feedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
let whereClause = "feedID in \(placeholders)"
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
func fetchUnreadArticles(_ feedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0
if webFeedIDs.isEmpty {
if feedIDs.isEmpty {
return Set<Article>()
}
let parameters = webFeedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let parameters = feedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
var whereClause = "feedID in \(placeholders) and read=0"
if let limit = limit {
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
@@ -845,8 +845,8 @@ private extension ArticlesTable {
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchArticlesForFeedID(_ webFeedID: String, _ database: FMDatabase) -> Set<Article> {
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [webFeedID as AnyObject])
func fetchArticlesForFeedID(_ feedID: String, _ database: FMDatabase) -> Set<Article> {
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [feedID as AnyObject])
}
func fetchArticles(articleIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
@@ -859,15 +859,15 @@ private extension ArticlesTable {
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
func fetchArticlesSince(_ feedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?, _ 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 webFeedIDs.isEmpty {
if feedIDs.isEmpty {
return Set<Article>()
}
let parameters = webFeedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject]
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let parameters = feedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject]
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
var whereClause = "feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?))"
if let limit = limit {
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
@@ -875,13 +875,13 @@ private extension ArticlesTable {
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchStarredArticles(_ webFeedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
func fetchStarredArticles(_ feedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred=1;
if webFeedIDs.isEmpty {
if feedIDs.isEmpty {
return Set<Article>()
}
let parameters = webFeedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let parameters = feedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
var whereClause = "feedID in \(placeholders) and starred=1"
if let limit = limit {
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
@@ -889,21 +889,21 @@ private extension ArticlesTable {
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchStarredArticlesCount(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Int {
func fetchStarredArticlesCount(_ feedIDs: Set<String>, _ database: FMDatabase) -> Int {
// select count from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred=1;
if webFeedIDs.isEmpty {
if feedIDs.isEmpty {
return 0
}
let parameters = webFeedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let parameters = feedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
let whereClause = "feedID in \(placeholders) and starred=1"
return fetchArticleCountsWithWhereClause(database, whereClause: whereClause, parameters: parameters)
}
func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
func fetchArticlesMatching(_ searchString: String, _ feedIDs: 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{ webFeedIDs.contains($0.webFeedID) }
return articles.filter{ feedIDs.contains($0.feedID) }
}
func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {

View File

@@ -19,7 +19,7 @@ extension Article {
assertionFailure("Expected articleID.")
return nil
}
guard let webFeedID = row.string(forColumn: DatabaseKey.feedID) else {
guard let feedID = row.string(forColumn: DatabaseKey.feedID) else {
assertionFailure("Expected feedID.")
return nil
}
@@ -38,10 +38,10 @@ extension Article {
let datePublished = row.date(forColumn: DatabaseKey.datePublished)
let dateModified = row.date(forColumn: DatabaseKey.dateModified)
self.init(accountID: accountID, articleID: articleID, webFeedID: webFeedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, datePublished: datePublished, dateModified: dateModified, authors: nil, status: status)
self.init(accountID: accountID, articleID: articleID, feedID: feedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, datePublished: datePublished, dateModified: dateModified, authors: nil, status: status)
}
init(parsedItem: ParsedItem, maximumDateAllowed: Date, accountID: String, webFeedID: String, status: ArticleStatus) {
init(parsedItem: ParsedItem, maximumDateAllowed: Date, accountID: String, feedID: String, status: ArticleStatus) {
let authors = Author.authorsWithParsedAuthors(parsedItem.authors)
// Deal with future datePublished and dateModified dates.
@@ -58,7 +58,7 @@ extension Article {
dateModified = nil
}
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, datePublished: datePublished, dateModified: dateModified, authors: authors, status: status)
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, datePublished: datePublished, dateModified: dateModified, authors: authors, status: status)
}
private func addPossibleStringChangeWithKeyPath(_ comparisonKeyPath: KeyPath<Article,String?>, _ otherArticle: Article, _ key: String, _ dictionary: inout DatabaseDictionary) {
@@ -71,7 +71,7 @@ extension Article {
if authors.isEmpty {
return self
}
return Article(accountID: self.accountID, articleID: self.articleID, webFeedID: self.webFeedID, uniqueID: self.uniqueID, title: self.title, contentHTML: self.contentHTML, contentText: self.contentText, url: self.rawLink, externalURL: self.rawExternalLink, summary: self.summary, imageURL: self.rawImageLink, datePublished: self.datePublished, dateModified: self.dateModified, authors: authors, status: self.status)
return Article(accountID: self.accountID, articleID: self.articleID, feedID: self.feedID, uniqueID: self.uniqueID, title: self.title, contentHTML: self.contentHTML, contentText: self.contentText, url: self.rawLink, externalURL: self.rawExternalLink, summary: self.summary, imageURL: self.rawImageLink, datePublished: self.datePublished, dateModified: self.dateModified, authors: authors, status: self.status)
}
func changesFrom(_ existingArticle: Article) -> DatabaseDictionary? {
@@ -117,22 +117,22 @@ extension Article {
return Date().addingTimeInterval(60 * 60 * 24) // Allow dates up to about 24 hours ahead of now
}
static func articlesWithWebFeedIDsAndItems(_ webFeedIDsAndItems: [String: Set<ParsedItem>], _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
static func articlesWithFeedIDsAndItems(_ feedIDsAndItems: [String: Set<ParsedItem>], _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
let maximumDateAllowed = _maximumDateAllowed()
var feedArticles = Set<Article>()
for (webFeedID, parsedItems) in webFeedIDsAndItems {
for (feedID, parsedItems) in feedIDsAndItems {
for parsedItem in parsedItems {
let status = statusesDictionary[parsedItem.articleID]!
let article = Article(parsedItem: parsedItem, maximumDateAllowed: maximumDateAllowed, accountID: accountID, webFeedID: webFeedID, status: status)
let article = Article(parsedItem: parsedItem, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: status)
feedArticles.insert(article)
}
}
return feedArticles
}
static func articlesWithParsedItems(_ parsedItems: Set<ParsedItem>, _ webFeedID: String, _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
static func articlesWithParsedItems(_ parsedItems: Set<ParsedItem>, _ feedID: String, _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
let maximumDateAllowed = _maximumDateAllowed()
return Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, webFeedID: webFeedID, status: statusesDictionary[$0.articleID]!) })
return Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) })
}
}
@@ -142,7 +142,7 @@ extension Article: @retroactive DatabaseObject {
var d = DatabaseDictionary()
d[DatabaseKey.articleID] = articleID
d[DatabaseKey.feedID] = webFeedID
d[DatabaseKey.feedID] = feedID
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(webFeedID: feedURL, uniqueID: uniqueID)
return Article.calculatedArticleID(feedID: feedURL, uniqueID: uniqueID)
}
}

View File

@@ -63,8 +63,8 @@ private extension FetchAllUnreadCountsOperation {
return
}
let unreadCount = resultSet.long(forColumnIndex: 1)
if let webFeedID = resultSet.string(forColumnIndex: 0) {
unreadCountDictionary[webFeedID] = unreadCount
if let feedID = resultSet.string(forColumnIndex: 0) {
unreadCountDictionary[feedID] = unreadCount
}
}
resultSet.close()

View File

@@ -25,10 +25,10 @@ public final class FetchFeedUnreadCountOperation: MainThreadOperation {
private let queue: DatabaseQueue
private let cutoffDate: Date
private let webFeedID: String
private let feedID: String
init(webFeedID: String, databaseQueue: DatabaseQueue, cutoffDate: Date) {
self.webFeedID = webFeedID
init(feedID: String, databaseQueue: DatabaseQueue, cutoffDate: Date) {
self.feedID = feedID
self.queue = databaseQueue
self.cutoffDate = cutoffDate
}
@@ -55,7 +55,7 @@ private extension FetchFeedUnreadCountOperation {
func fetchUnreadCount(_ database: FMDatabase) {
let sql = "select count(*) from articles natural join statuses where feedID=? and read=0;"
guard let resultSet = database.executeQuery(sql, withArgumentsIn: [webFeedID]) else {
guard let resultSet = database.executeQuery(sql, withArgumentsIn: [feedID]) else {
informOperationDelegateOfCompletion()
return
}

View File

@@ -24,10 +24,10 @@ public final class FetchUnreadCountsForFeedsOperation: MainThreadOperation {
public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock?
private let queue: DatabaseQueue
private let webFeedIDs: Set<String>
private let feedIDs: Set<String>
init(webFeedIDs: Set<String>, databaseQueue: DatabaseQueue) {
self.webFeedIDs = webFeedIDs
init(feedIDs: Set<String>, databaseQueue: DatabaseQueue) {
self.feedIDs = feedIDs
self.queue = databaseQueue
}
@@ -51,10 +51,10 @@ public final class FetchUnreadCountsForFeedsOperation: MainThreadOperation {
private extension FetchUnreadCountsForFeedsOperation {
func fetchUnreadCounts(_ database: FMDatabase) {
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
let sql = "select distinct feedID, count(*) from articles natural join statuses where feedID in \(placeholders) and read=0 group by feedID;"
let parameters = Array(webFeedIDs) as [Any]
let parameters = Array(feedIDs) as [Any]
guard let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) else {
informOperationDelegateOfCompletion()
@@ -74,8 +74,8 @@ private extension FetchUnreadCountsForFeedsOperation {
return
}
let unreadCount = resultSet.long(forColumnIndex: 1)
if let webFeedID = resultSet.string(forColumnIndex: 0) {
unreadCountDictionary[webFeedID] = unreadCount
if let feedID = resultSet.string(forColumnIndex: 0) {
unreadCountDictionary[feedID] = unreadCount
}
}
resultSet.close()

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23504" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23504"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="AddWebFeedWindowController" customModule="NetNewsWire" customModuleProvider="target">
<customObject id="-2" userLabel="File's Owner" customClass="AddFeedWindowController" customModule="NetNewsWire" customModuleProvider="target">
<connections>
<outlet property="addButton" destination="dtI-Hu-rFb" id="D11-zR-dWH"/>
<outlet property="folderPopupButton" destination="6vt-DL-mVR" id="98M-xt-ZYU"/>
@@ -20,21 +20,21 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="217"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="480" height="216"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<view key="contentView" misplaced="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="3" width="480" height="214"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hVI-F6-nNT">
<rect key="frame" x="33" y="180" width="35" height="16"/>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hVI-F6-nNT">
<rect key="frame" x="33" y="178" width="35" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="URL:" id="8jE-9v-BT2">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gbr-mI-Uzj" userLabel="URL Text Field">
<rect key="frame" x="74" y="123" width="386" height="73"/>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gbr-mI-Uzj" userLabel="URL Text Field">
<rect key="frame" x="74" y="121" width="386" height="73"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="386" id="Wfx-Jk-wQ0"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="73" id="x84-xj-BzJ"/>
@@ -48,24 +48,24 @@
<outlet property="delegate" destination="-2" id="gcI-CI-e5I"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sM9-DX-M0c">
<rect key="frame" x="22" y="95" width="46" height="16"/>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sM9-DX-M0c">
<rect key="frame" x="22" y="93" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Name:" id="8ca-Qp-BkT">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TzV-3k-fXd" userLabel="Name Text Field">
<rect key="frame" x="74" y="92" width="386" height="21"/>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TzV-3k-fXd" userLabel="Name Text Field">
<rect key="frame" x="74" y="90" width="386" height="21"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="Optional" drawsBackground="YES" usesSingleLineMode="YES" id="pLP-pL-5R5">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dNV-oD-vzR">
<rect key="frame" x="18" y="64" width="50" height="16"/>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dNV-oD-vzR">
<rect key="frame" x="18" y="63" width="50" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Folder:" id="Kwx-7B-CIu">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -73,10 +73,10 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="6vt-DL-mVR" userLabel="Folder Popup">
<rect key="frame" x="72" y="58" width="391" height="25"/>
<rect key="frame" x="71" y="56" width="393" height="25"/>
<popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="tLJ-zY-CcZ" id="0cM-5q-Snl">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="OpL-Uf-woJ">
<items>
<menuItem title="Item 1" state="on" id="tLJ-zY-CcZ"/>
@@ -87,7 +87,7 @@
</popUpButtonCell>
</popUpButton>
<button hidden="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="QcY-PB-8Y0">
<rect key="frame" x="68" y="13" width="166" height="32"/>
<rect key="frame" x="67" y="13" width="160" height="32"/>
<buttonCell key="cell" type="push" title="Open Feed Directory" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="wKl-a9-7FY">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -97,7 +97,7 @@
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hXq-IS-19x">
<rect key="frame" x="302" y="13" width="82" height="32"/>
<rect key="frame" x="317" y="13" width="76" height="32"/>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Dop-HC-6Q9">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -110,7 +110,7 @@ Gw
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dtI-Hu-rFb">
<rect key="frame" x="384" y="13" width="82" height="32"/>
<rect key="frame" x="391" y="13" width="76" height="32"/>
<buttonCell key="cell" type="push" title="Add" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6NK-Ql-drk">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>

View File

@@ -11,7 +11,7 @@ import Articles
import Account
import UserNotifications
final class WebFeedInspectorViewController: NSViewController, Inspector {
final class FeedInspectorViewController: NSViewController, Inspector {
@IBOutlet weak var iconView: IconView!
@IBOutlet weak var nameTextField: NSTextField?
@@ -35,7 +35,7 @@ final class WebFeedInspectorViewController: NSViewController, Inspector {
let isFallbackInspector = false
var objects: [Any]? {
didSet {
renameWebFeedIfNecessary()
renameFeedIfNecessary()
updateFeed()
}
}
@@ -58,7 +58,7 @@ final class WebFeedInspectorViewController: NSViewController, Inspector {
}
override func viewDidDisappear() {
renameWebFeedIfNecessary()
renameFeedIfNecessary()
}
// MARK: Actions
@@ -112,15 +112,15 @@ final class WebFeedInspectorViewController: NSViewController, Inspector {
}
extension WebFeedInspectorViewController: NSTextFieldDelegate {
extension FeedInspectorViewController: NSTextFieldDelegate {
func controlTextDidEndEditing(_ note: Notification) {
renameWebFeedIfNecessary()
renameFeedIfNecessary()
}
}
private extension WebFeedInspectorViewController {
private extension FeedInspectorViewController {
func updateFeed() {
guard let objects = objects, objects.count == 1, let singleFeed = objects.first as? Feed else {
@@ -202,7 +202,7 @@ private extension WebFeedInspectorViewController {
}
}
func renameWebFeedIfNecessary() {
func renameFeedIfNecessary() {
guard let feed = feed,
let account = feed.account,
let nameTextField = nameTextField,
@@ -210,7 +210,7 @@ private extension WebFeedInspectorViewController {
return
}
account.renameWebFeed(feed, to: nameTextField.stringValue) { [weak self] result in
account.renameFeed(feed, to: nameTextField.stringValue) { [weak self] result in
if case .failure(let error) = result {
self?.presentError(error)
} else {

View File

@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17506" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="cfG-Pn-VJS">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="cfG-Pn-VJS">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17506"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23504"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@@ -32,12 +31,12 @@
<!--Feed-->
<scene sceneID="vUh-Rc-fPi">
<objects>
<viewController title="Feed" storyboardIdentifier="Feed" showSeguePresentationStyle="single" id="sfH-oR-GXm" customClass="WebFeedInspectorViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<viewController title="Feed" storyboardIdentifier="Feed" showSeguePresentationStyle="single" id="sfH-oR-GXm" customClass="FeedInspectorViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="ecA-UY-KEd">
<rect key="frame" x="0.0" y="0.0" width="273" height="322"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="IWu-80-XC5">
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="IWu-80-XC5">
<rect key="frame" x="20" y="190" width="233" height="56"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="56" id="zV3-AX-gyC"/>
@@ -54,7 +53,7 @@ Field</string>
<outlet property="delegate" destination="sfH-oR-GXm" id="Dd0-5H-8HH"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="2WO-Iu-p5e">
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="2WO-Iu-p5e">
<rect key="frame" x="18" y="96" width="237" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Home Page" usesSingleLineMode="YES" id="Fg8-rA-G5J">
<font key="font" metaFont="system"/>
@@ -62,7 +61,7 @@ Field</string>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="1000" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zm0-15-BFy">
<textField focusRingType="none" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zm0-15-BFy">
<rect key="frame" x="18" y="76" width="237" height="16"/>
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" title="http://example.com/" id="L2p-ur-j7a">
<font key="font" metaFont="system"/>
@@ -70,7 +69,7 @@ Field</string>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ju6-Zo-8X4">
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ju6-Zo-8X4">
<rect key="frame" x="18" y="40" width="237" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Feed" usesSingleLineMode="YES" id="zzB-rX-1dK">
<font key="font" metaFont="system"/>
@@ -78,7 +77,7 @@ Field</string>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="1000" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="Vvk-KG-JlG">
<textField focusRingType="none" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="Vvk-KG-JlG">
<rect key="frame" x="18" y="20" width="237" height="16"/>
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" title="http://example.com/feed" id="HpC-rK-YGK">
<font key="font" metaFont="system"/>
@@ -169,7 +168,7 @@ Field</string>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="NSFolder" id="C4n-vS-297"/>
</imageView>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="jHf-rc-GNr" userLabel="Folder Name Field">
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="jHf-rc-GNr" userLabel="Folder Name Field">
<rect key="frame" x="20" y="20" width="227" height="56"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="56" id="Ele-JD-mB2"/>
@@ -221,7 +220,7 @@ Field</string>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="NSSmartBadgeTemplate" id="Z52-bd-Lgz"/>
</imageView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="4Xp-FX-kn3">
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="4Xp-FX-kn3">
<rect key="frame" x="18" y="20" width="231" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Label" id="3v9-Z7-d7l">
<font key="font" metaFont="system"/>
@@ -256,7 +255,7 @@ Field</string>
<rect key="frame" x="0.0" y="0.0" width="267" height="56"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="icb-M6-R2N">
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="icb-M6-R2N">
<rect key="frame" x="18" y="20" width="231" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Nothing to inspect" id="iLD-8q-EAJ">
<font key="font" metaFont="system"/>
@@ -264,7 +263,7 @@ Field</string>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zQp-oc-Qtc">
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zQp-oc-Qtc">
<rect key="frame" x="18" y="20" width="231" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Multiple selection" id="5oG-0x-T8O">
<font key="font" metaFont="system"/>

View File

@@ -37,7 +37,7 @@ class AddFeedController: AddFeedWindowControllerDelegate {
let folderTreeControllerDelegate = FolderTreeControllerDelegate()
let folderTreeController = TreeController(delegate: folderTreeControllerDelegate)
addFeedWindowController = AddWebFeedWindowController(urlString: urlString ?? urlStringFromPasteboard,
addFeedWindowController = AddFeedWindowController(urlString: urlString ?? urlStringFromPasteboard,
name: name,
account: account,
folder: folder,

View File

@@ -16,8 +16,8 @@ protocol AddFeedWindowControllerDelegate: AnyObject {
func addFeedWindowControllerUserDidCancel(_: AddFeedWindowController)
}
protocol AddFeedWindowController {
var window: NSWindow? { get }
func runSheetOnWindow(_ hostWindow: NSWindow)
}
//protocol AddFeedWindowController {
//
// var window: NSWindow? { get }
// func runSheetOnWindow(_ hostWindow: NSWindow)
//}

View File

@@ -12,8 +12,8 @@ import RSTree
import Articles
import Account
class AddWebFeedWindowController : NSWindowController, AddFeedWindowController {
final class AddFeedWindowController : NSWindowController {
@IBOutlet var urlTextField: NSTextField!
@IBOutlet var nameTextField: NSTextField!
@IBOutlet var addButton: NSButton!
@@ -38,7 +38,7 @@ class AddWebFeedWindowController : NSWindowController, AddFeedWindowController {
var hostWindow: NSWindow!
convenience init(urlString: String?, name: String?, account: Account?, folder: Folder?, folderTreeController: TreeController, delegate: AddFeedWindowControllerDelegate?) {
self.init(windowNibName: NSNib.Name("AddWebFeedSheet"))
self.init(windowNibName: NSNib.Name("AddFeedSheet"))
self.urlString = urlString
self.initialName = name
self.initialAccount = account
@@ -117,9 +117,6 @@ class AddWebFeedWindowController : NSWindowController, AddFeedWindowController {
@objc func controlTextDidChange(_ obj: Notification) {
updateUI()
}
}
private extension AddWebFeedWindowController {
private func updateUI() {
addButton.isEnabled = urlTextField.stringValue.mayBeURL && selectedContainer() != nil

View File

@@ -130,7 +130,7 @@ final class DetailWebViewController: NSViewController {
NotificationCenter.default.addObserver(self, selector: #selector(webInspectorEnabledDidChange(_:)), name: .WebInspectorEnabledDidChange, object: nil)
#endif
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, 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)
@@ -141,7 +141,7 @@ final class DetailWebViewController: NSViewController {
// MARK: Notifications
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
reloadArticleImage()
}

View File

@@ -32,7 +32,7 @@ extension SidebarViewController {
switch object {
case is Feed:
return menuForWebFeed(object as! Feed)
return menuForFeed(object as! Feed)
case is Folder:
return menuForFolder(object as! Folder)
case is PseudoFeed:
@@ -206,34 +206,34 @@ private extension SidebarViewController {
return menu
}
func menuForWebFeed(_ webFeed: Feed) -> NSMenu? {
func menuForFeed(_ feed: Feed) -> NSMenu? {
let menu = NSMenu(title: "")
if webFeed.unreadCount > 0 {
menu.addItem(markAllReadMenuItem([webFeed]))
if feed.unreadCount > 0 {
menu.addItem(markAllReadMenuItem([feed]))
menu.addItem(NSMenuItem.separator())
}
if let homePageURL = webFeed.homePageURL, let _ = URL(string: homePageURL) {
if let homePageURL = feed.homePageURL, let _ = URL(string: homePageURL) {
let item = menuItem(NSLocalizedString("Open Home Page", comment: "Command"), #selector(openHomePageFromContextualMenu(_:)), homePageURL.decodedURLString ?? homePageURL)
menu.addItem(item)
menu.addItem(NSMenuItem.separator())
}
let copyFeedURLItem = menuItem(NSLocalizedString("Copy Feed URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), webFeed.url.decodedURLString ?? webFeed.url)
let copyFeedURLItem = menuItem(NSLocalizedString("Copy Feed URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), feed.url.decodedURLString ?? feed.url)
menu.addItem(copyFeedURLItem)
if let homePageURL = webFeed.homePageURL {
if let homePageURL = feed.homePageURL {
let item = menuItem(NSLocalizedString("Copy Home Page URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), homePageURL.decodedURLString ?? homePageURL)
menu.addItem(item)
}
menu.addItem(NSMenuItem.separator())
let notificationText = webFeed.notificationDisplayName.capitalized
let notificationText = feed.notificationDisplayName.capitalized
let notificationMenuItem = menuItem(notificationText, #selector(toggleNotificationsFromContextMenu(_:)), webFeed)
if webFeed.isNotifyAboutNewArticles == nil || webFeed.isNotifyAboutNewArticles! == false {
let notificationMenuItem = menuItem(notificationText, #selector(toggleNotificationsFromContextMenu(_:)), feed)
if feed.isNotifyAboutNewArticles == nil || feed.isNotifyAboutNewArticles! == false {
notificationMenuItem.state = .off
} else {
notificationMenuItem.state = .on
@@ -241,9 +241,9 @@ private extension SidebarViewController {
menu.addItem(notificationMenuItem)
let articleExtractorText = NSLocalizedString("Always Use Reader View", comment: "Always Use Reader View")
let articleExtractorMenuItem = menuItem(articleExtractorText, #selector(toggleArticleExtractorFromContextMenu(_:)), webFeed)
let articleExtractorMenuItem = menuItem(articleExtractorText, #selector(toggleArticleExtractorFromContextMenu(_:)), feed)
if webFeed.isArticleExtractorAlwaysOn == nil || webFeed.isArticleExtractorAlwaysOn! == false {
if feed.isArticleExtractorAlwaysOn == nil || feed.isArticleExtractorAlwaysOn! == false {
articleExtractorMenuItem.state = .off
} else {
articleExtractorMenuItem.state = .on
@@ -252,8 +252,8 @@ private extension SidebarViewController {
menu.addItem(NSMenuItem.separator())
menu.addItem(renameMenuItem(webFeed))
menu.addItem(deleteMenuItem([webFeed]))
menu.addItem(renameMenuItem(feed))
menu.addItem(deleteMenuItem([feed]))
return menu
}

View File

@@ -448,8 +448,8 @@ protocol SidebarDelegate: AnyObject {
if isReadFiltered, let feedID = feed.sidebarItemID {
self.treeControllerDelegate.addFilterException(feedID)
if let webFeed = feed as? Feed, let account = webFeed.account {
let parentFolder = account.sortedFolders?.first(where: { $0.objectIsChild(webFeed) })
if let feed = feed as? Feed, let account = feed.account {
let parentFolder = account.sortedFolders?.first(where: { $0.objectIsChild(feed) })
if let parentFolderFeedID = parentFolder?.sidebarItemID {
self.treeControllerDelegate.addFilterException(parentFolderFeedID)
}
@@ -540,10 +540,10 @@ private extension SidebarViewController {
if folderFeed.account?.existingFolder(withID: folderFeed.folderID) != nil {
treeControllerDelegate.addFilterException(feedID)
}
} else if let webFeed = feed as? Feed {
if webFeed.account?.existingWebFeed(withWebFeedID: webFeed.feedID) != nil {
} else if let feed = feed as? Feed {
if feed.account?.existingFeed(withFeedID: feed.feedID) != nil {
treeControllerDelegate.addFilterException(feedID)
addParentFolderToFilterExceptions(webFeed)
addParentFolderToFilterExceptions(feed)
}
}
}
@@ -738,10 +738,10 @@ private extension SidebarViewController {
}
func findFeedNode(_ userInfo: [AnyHashable : Any]?, beginningAt startingNode: Node) -> Node? {
guard let webFeedID = userInfo?[ArticlePathKey.webFeedID] as? String else {
guard let feedID = userInfo?[ArticlePathKey.feedID] as? String else {
return nil
}
if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.feedID == webFeedID }) {
if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.feedID == feedID }) {
return node
}
return nil

View File

@@ -111,7 +111,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 webFeedID = "webFeedID" // may differ from feedURL if coming from a syncing system
static let feedID = "feedID" // may differ from feedURL if coming from a syncing system
static let title = "title"
static let contentHTML = "contentHTML"
static let contentText = "contentText"
@@ -147,7 +147,7 @@ private extension ArticlePasteboardWriter {
d[Key.feedURL] = feed.url
}
d[Key.webFeedID] = article.webFeedID
d[Key.feedID] = article.feedID
d[Key.title] = article.title ?? nil
d[Key.contentHTML] = article.contentHTML ?? nil
d[Key.contentText] = article.contentText ?? nil

View File

@@ -141,8 +141,8 @@ extension TimelineContainerViewController: TimelineDelegate {
delegate?.timelineSelectionDidChange(self, articles: selectedArticles, mode: mode(for: timelineViewController))
}
func timelineRequestedWebFeedSelection(_: TimelineViewController, webFeed: Feed) {
delegate?.timelineRequestedFeedSelection(self, feed: webFeed)
func timelineRequestedFeedSelection(_: TimelineViewController, feed: Feed) {
delegate?.timelineRequestedFeedSelection(self, feed: feed)
}
func timelineInvalidatedRestorationState(_: TimelineViewController) {

View File

@@ -65,10 +65,10 @@ extension TimelineViewController {
}
@objc func selectFeedInSidebarFromContextualMenu(_ sender: Any?) {
guard let menuItem = sender as? NSMenuItem, let webFeed = menuItem.representedObject as? Feed else {
guard let menuItem = sender as? NSMenuItem, let feed = menuItem.representedObject as? Feed else {
return
}
delegate?.timelineRequestedWebFeedSelection(self, webFeed: webFeed)
delegate?.timelineRequestedFeedSelection(self, feed: feed)
}
@objc func markAllInFeedAsRead(_ sender: Any?) {

View File

@@ -14,7 +14,7 @@ import os.log
protocol TimelineDelegate: AnyObject {
func timelineSelectionDidChange(_: TimelineViewController, selectedArticles: [Article]?)
func timelineRequestedWebFeedSelection(_: TimelineViewController, webFeed: Feed)
func timelineRequestedFeedSelection(_: TimelineViewController, feed: Feed)
func timelineInvalidatedRestorationState(_: TimelineViewController)
}
@@ -214,7 +214,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(webFeedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, 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)
@@ -593,7 +593,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
updateUnreadCount()
}
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
guard showIcons, let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
return
}
@@ -636,11 +636,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
}
@objc func accountDidDownloadArticles(_ note: Notification) {
guard let feeds = note.userInfo?[Account.UserInfoKey.webFeeds] as? Set<Feed> else {
guard let feeds = note.userInfo?[Account.UserInfoKey.feeds] as? Set<Feed> else {
return
}
let shouldFetchAndMergeArticles = representedObjectsContainsAnyWebFeed(feeds) || representedObjectsContainsAnyPseudoFeed()
let shouldFetchAndMergeArticles = representedObjectsContainsAnyFeed(feeds) || representedObjectsContainsAnyPseudoFeed()
if shouldFetchAndMergeArticles {
queueFetchAndMergeArticles()
}
@@ -724,8 +724,8 @@ 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, dateArrived: Date())
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, datePublished: nil, dateModified: nil, authors: nil, status: status)
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, datePublished: nil, dateModified: nil, authors: nil, status: status)
let prototypeCellData = TimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Prototype Feed Name", byline: nil, iconImage: nil, showIcon: false, featuredImage: nil)
let height = TimelineCellLayout.height(for: 100, cellData: prototypeCellData, appearance: cellAppearance)
return height
@@ -1226,7 +1226,7 @@ private extension TimelineViewController {
return representedObjects?.contains(where: { $0 is Folder }) ?? false
}
func representedObjectsContainsAnyWebFeed(_ webFeeds: Set<Feed>) -> Bool {
func representedObjectsContainsAnyFeed(_ feeds: Set<Feed>) -> Bool {
// Return true if theres a match or if a folder contains (recursively) one of feeds
guard let representedObjects = representedObjects else {
@@ -1234,15 +1234,15 @@ private extension TimelineViewController {
}
for representedObject in representedObjects {
if let feed = representedObject as? Feed {
for oneFeed in webFeeds {
for oneFeed in feeds {
if feed.feedID == oneFeed.feedID || feed.url == oneFeed.url {
return true
}
}
}
else if let folder = representedObject as? Folder {
for oneFeed in webFeeds {
if folder.hasWebFeed(with: oneFeed.feedID) || folder.hasFeed(withURL: oneFeed.url) {
for oneFeed in feeds {
if folder.hasFeed(with: oneFeed.feedID) || folder.hasFeed(withURL: oneFeed.url) {
return true
}
}

View File

@@ -60,8 +60,8 @@
<element type="account">
<cocoa key="accounts"/>
</element>
<element type="webFeed">
<cocoa key="webFeeds"/>
<element type="feed">
<cocoa key="feeds"/>
</element>
</class>
@@ -88,23 +88,23 @@
<property name="active" code="Actv" type="boolean" access="rw" description="Whether or not the account is active">
<cocoa key="scriptingIsActive"/>
</property>
<property name="allWebFeeds" code="Feds" access="r" description="All feeds, including feeds inside folders">
<cocoa key="allWebFeeds"/>
<type type="webFeed" list="yes"/>
<property name="allFeeds" code="Feds" access="r" description="All feeds, including feeds inside folders">
<cocoa key="allFeeds"/>
<type type="feed" list="yes"/>
</property>
<property name="opml representation" code="OPML" type="text" access="r" description="OPML representation for the account">
<cocoa key="opmlRepresentation"/>
</property>
<element type="webFeed">
<cocoa key="webFeeds"/>
<element type="feed">
<cocoa key="feeds"/>
</element>
<element type="folder">
<cocoa key="folders"/>
</element>
</class>
<class name="webFeed" code="Feed" plural="webFeeds" description="An RSS feed">
<cocoa class="ScriptableWebFeed"/>
<class name="feed" code="Feed" plural="feeds" description="An RSS feed">
<cocoa class="ScriptableFeed"/>
<property name="name" code="pnam" type="text" access="r" description="The name of the feed">
<cocoa key="name"/>
</property>
@@ -165,8 +165,8 @@
<property name="opml representation" code="OPML" type="text" access="r" description="OPML representation for the folder">
<cocoa key="opmlRepresentation"/>
</property>
<element type="webFeed">
<cocoa key="webFeeds"/>
<element type="feed">
<cocoa key="feeds"/>
</element>
</class>
@@ -217,7 +217,7 @@
<property name="image url" code="IURL" type="text" access="r" description="an image url for the article">
<cocoa key="imageURL"/>
</property>
<property name="feed" code="Feed" type="webFeed" access="r" description="the containing feed">
<property name="feed" code="Feed" type="feed" access="r" description="the containing feed">
<cocoa key="feed"/>
</property>
<element type="author">

View File

@@ -73,7 +73,7 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
account.removeFolder(scriptableFolder.folder) { result in
}
}
} else if let scriptableFeed = element as? ScriptableWebFeed {
} else if let scriptableFeed = element as? ScriptableFeed {
BatchUpdate.shared.perform {
var container: Container? = nil
if let scriptableFolder = scriptableFeed.container as? ScriptableFolder {
@@ -81,7 +81,7 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
} else {
container = account
}
account.removeWebFeed(scriptableFeed.webFeed, from: container!) { result in
account.removeFeed(scriptableFeed.feed, from: container!) { result in
}
}
}
@@ -94,22 +94,22 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
// MARK: --- Scriptable elements ---
@objc(webFeeds)
var webFeeds:NSArray {
return account.topLevelFeeds.map { ScriptableWebFeed($0, container:self) } as NSArray
@objc(feeds)
var feeds:NSArray {
return account.topLevelFeeds.map { ScriptableFeed($0, container:self) } as NSArray
}
@objc(valueInWebFeedsWithUniqueID:)
func valueInWebFeeds(withUniqueID id:String) -> ScriptableWebFeed? {
guard let feed = account.existingWebFeed(withWebFeedID: id) else { return nil }
return ScriptableWebFeed(feed, container:self)
@objc(valueInFeedsWithUniqueID:)
func valueInFeeds(withUniqueID id:String) -> ScriptableFeed? {
guard let feed = account.existingFeed(withFeedID: id) else { return nil }
return ScriptableFeed(feed, container:self)
}
@objc(valueInWebFeedsWithName:)
func valueInWebFeeds(withName name:String) -> ScriptableWebFeed? {
@objc(valueInFeedsWithName:)
func valueInFeeds(withName name:String) -> ScriptableFeed? {
let feeds = Array(account.flattenedFeeds())
guard let feed = feeds.first(where:{$0.name == name}) else { return nil }
return ScriptableWebFeed(feed, container:self)
return ScriptableFeed(feed, container:self)
}
@objc(folders)
@@ -130,21 +130,21 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
// MARK: --- Scriptable properties ---
@objc(allWebFeeds)
var allWebFeeds: NSArray {
var webFeeds = [ScriptableWebFeed]()
for webFeed in account.topLevelFeeds {
webFeeds.append(ScriptableWebFeed(webFeed, container: self))
@objc(allFeeds)
var allFeeds: NSArray {
var feeds = [ScriptableFeed]()
for feed in account.topLevelFeeds {
feeds.append(ScriptableFeed(feed, container: self))
}
if let folders = account.folders {
for folder in folders {
let scriptableFolder = ScriptableFolder(folder, container: self)
for webFeed in folder.topLevelFeeds {
webFeeds.append(ScriptableWebFeed(webFeed, container: scriptableFolder))
for feed in folder.topLevelFeeds {
feeds.append(ScriptableFeed(feed, container: scriptableFolder))
}
}
}
return webFeeds as NSArray
return feeds as NSArray
}
@objc(opmlRepresentation)

View File

@@ -94,8 +94,8 @@ extension AppDelegate : AppDelegateAppleEvents {
class NetNewsWireCreateElementCommand : NSCreateCommand {
override func performDefaultImplementation() -> Any? {
let classDescription = self.createClassDescription
if (classDescription.className == "webFeed") {
return ScriptableWebFeed.handleCreateElement(command:self)
if (classDescription.className == "feed") {
return ScriptableFeed.handleCreateElement(command:self)
} else if (classDescription.className == "folder") {
return ScriptableFolder.handleCreateElement(command:self)
}

View File

@@ -142,12 +142,12 @@ class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
}
@objc(feed)
var feed: ScriptableWebFeed? {
var feed: ScriptableFeed? {
guard let parentFeed = self.article.feed,
let account = parentFeed.account
else { return nil }
return ScriptableWebFeed(parentFeed, container: ScriptableAccount(account))
return ScriptableFeed(parentFeed, container: ScriptableAccount(account))
}
}

View File

@@ -51,9 +51,9 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
}
func deleteElement(_ element:ScriptingObject) {
if let scriptableFeed = element as? ScriptableWebFeed {
if let scriptableFeed = element as? ScriptableFeed {
BatchUpdate.shared.perform {
folder.account?.removeWebFeed(scriptableFeed.webFeed, from: folder) { result in }
folder.account?.removeFeed(scriptableFeed.feed, from: folder) { result in }
}
}
}
@@ -95,10 +95,10 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
// MARK: --- Scriptable elements ---
@objc(webFeeds)
var webFeeds:NSArray {
@objc(feeds)
var feeds:NSArray {
let feeds = Array(folder.topLevelFeeds)
return feeds.map { ScriptableWebFeed($0, container:self) } as NSArray
return feeds.map { ScriptableFeed($0, container:self) } as NSArray
}
// MARK: --- Scriptable properties ---

View File

@@ -31,7 +31,7 @@ extension NSApplication : ScriptingObjectContainer {
var scriptableArticle: ScriptableArticle?
if let currentArticle = appDelegate.scriptingCurrentArticle {
if let feed = currentArticle.feed {
let scriptableFeed = ScriptableWebFeed(feed, container:self)
let scriptableFeed = ScriptableFeed(feed, container:self)
scriptableArticle = ScriptableArticle(currentArticle, container:scriptableFeed)
}
}
@@ -43,7 +43,7 @@ extension NSApplication : ScriptingObjectContainer {
let articles = appDelegate.scriptingSelectedArticles
let scriptableArticles:[ScriptableArticle] = articles.compactMap { article in
if let feed = article.feed {
let scriptableFeed = ScriptableWebFeed(feed, container:self)
let scriptableFeed = ScriptableFeed(feed, container:self)
return ScriptableArticle(article, container:scriptableFeed)
} else {
return nil
@@ -73,7 +73,7 @@ extension NSApplication : ScriptingObjectContainer {
for 'articles of feed "The Shape of Everything" of account "On My Mac"'
*/
func allWebFeeds() -> [Feed] {
func allFeeds() -> [Feed] {
let accounts = AccountManager.shared.activeAccounts
let emptyFeeds:[Feed] = []
return accounts.reduce(emptyFeeds) { (result, nthAccount) -> [Feed] in
@@ -82,17 +82,17 @@ extension NSApplication : ScriptingObjectContainer {
}
}
@objc(webFeeds)
func webFeeds() -> NSArray {
let webFeeds = self.allWebFeeds()
return webFeeds.map { ScriptableWebFeed($0, container:self) } as NSArray
@objc(feeds)
func feeds() -> NSArray {
let feeds = self.allFeeds()
return feeds.map { ScriptableFeed($0, container:self) } as NSArray
}
@objc(valueInWebFeedsWithUniqueID:)
func valueInWebFeeds(withUniqueID id:String) -> ScriptableWebFeed? {
let webFeeds = self.allWebFeeds()
guard let webFeed = webFeeds.first(where:{$0.feedID == id}) else { return nil }
return ScriptableWebFeed(webFeed, container:self)
@objc(valueInFeedsWithUniqueID:)
func valueInFeeds(withUniqueID id:String) -> ScriptableFeed? {
let feeds = self.allFeeds()
guard let feed = feeds.first(where:{$0.feedID == id}) else { return nil }
return ScriptableFeed(feed, container:self)
}
}

View File

@@ -11,14 +11,14 @@ import RSParser
import Account
import Articles
@objc(ScriptableWebFeed)
class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
@objc(ScriptableFeed)
class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
let webFeed:Feed
let container:ScriptingObjectContainer
let feed: Feed
let container: ScriptingObjectContainer
init (_ webFeed:Feed, container:ScriptingObjectContainer) {
self.webFeed = webFeed
init (_ feed:Feed, container:ScriptingObjectContainer) {
self.feed = feed
self.container = container
}
@@ -36,7 +36,7 @@ class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
// MARK: --- ScriptingObject protocol ---
var scriptingKey: String {
return "webFeeds"
return "feeds"
}
// MARK: --- UniqueIdScriptingObject protocol ---
@@ -45,7 +45,7 @@ class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
// 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 webFeed.feedID
return feed.feedID
}
// MARK: --- ScriptingObjectContainer protocol ---
@@ -71,13 +71,13 @@ class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
return url
}
class func scriptableFeed(_ feed:Feed, account:Account, folder:Folder?) -> ScriptableWebFeed {
class func scriptableFeed(_ feed:Feed, account:Account, folder:Folder?) -> ScriptableFeed {
let scriptableAccount = ScriptableAccount(account)
if let folder = folder {
let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount)
return ScriptableWebFeed(feed, container:scriptableFolder)
return ScriptableFeed(feed, container:scriptableFolder)
} else {
return ScriptableWebFeed(feed, container:scriptableAccount)
return ScriptableFeed(feed, container:scriptableAccount)
}
}
@@ -121,51 +121,51 @@ class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
@objc(url)
var url:String {
return self.webFeed.url
return self.feed.url
}
@objc(name)
var name:String {
return self.webFeed.name ?? ""
return self.feed.name ?? ""
}
@objc(homePageURL)
var homePageURL:String {
return self.webFeed.homePageURL ?? ""
return self.feed.homePageURL ?? ""
}
@objc(iconURL)
var iconURL:String {
return self.webFeed.iconURL ?? ""
return self.feed.iconURL ?? ""
}
@objc(faviconURL)
var faviconURL:String {
return self.webFeed.faviconURL ?? ""
return self.feed.faviconURL ?? ""
}
@objc(opmlRepresentation)
var opmlRepresentation:String {
return self.webFeed.OPMLString(indentLevel:0)
return self.feed.OPMLString(indentLevel:0)
}
// MARK: --- scriptable elements ---
@objc(authors)
var authors:NSArray {
let feedAuthors = webFeed.authors ?? []
let feedAuthors = feed.authors ?? []
return feedAuthors.map { ScriptableAuthor($0, container:self) } as NSArray
}
@objc(valueInAuthorsWithUniqueID:)
func valueInAuthors(withUniqueID id:String) -> ScriptableAuthor? {
guard let author = webFeed.authors?.first(where:{$0.authorID == id}) else { return nil }
guard let author = feed.authors?.first(where:{$0.authorID == id}) else { return nil }
return ScriptableAuthor(author, container:self)
}
@objc(articles)
var articles:NSArray {
let feedArticles = (try? webFeed.fetchArticles()) ?? Set<Article>()
let feedArticles = (try? feed.fetchArticles()) ?? Set<Article>()
// 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 ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
@objc(valueInArticlesWithUniqueID:)
func valueInArticles(withUniqueID id:String) -> ScriptableArticle? {
let articles = (try? webFeed.fetchArticles()) ?? Set<Article>()
let articles = (try? feed.fetchArticles()) ?? Set<Article>()
guard let article = articles.first(where:{$0.uniqueID == id}) else { return nil }
return ScriptableArticle(article, container:self)
}

View File

@@ -160,7 +160,7 @@
513F32812593EF180003048F /* Account in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 516B695E24D2F33B00B5702F /* Account */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
513F32882593EF8F0003048F /* RSCore in Frameworks */ = {isa = PBXBuildFile; productRef = 513F32872593EF8F0003048F /* RSCore */; };
513F32892593EF8F0003048F /* RSCore in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 513F32872593EF8F0003048F /* RSCore */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
5141E7392373C18B0013FF27 /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */; };
5141E7392373C18B0013FF27 /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7382373C18B0013FF27 /* FeedInspectorViewController.swift */; };
5142192A23522B5500E07E2C /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5142192923522B5500E07E2C /* ImageViewController.swift */; };
514219372352510100E07E2C /* ImageScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514219362352510100E07E2C /* ImageScrollView.swift */; };
5142194B2353C1CF00E07E2C /* main_mac.js in Resources */ = {isa = PBXBuildFile; fileRef = 5142194A2353C1CF00E07E2C /* main_mac.js */; };
@@ -427,7 +427,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 /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */; };
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.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 */; };
@@ -861,7 +861,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 /* WebFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedInspectorViewController.swift; sourceTree = "<group>"; };
5141E7382373C18B0013FF27 /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = "<group>"; };
5141E7552374A2890013FF27 /* DetailIconSchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailIconSchemeHandler.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>"; };
@@ -1068,7 +1068,7 @@
845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarredFeedDelegate.swift; sourceTree = "<group>"; };
845EE7C01FC2488C00854A1F /* SmartFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeed.swift; sourceTree = "<group>"; };
84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkStatusCommand.swift; sourceTree = "<group>"; };
8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedInspectorViewController.swift; sourceTree = "<group>"; };
8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.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>"; };
@@ -1430,7 +1430,7 @@
516A09412361248000EAE89B /* Inspector.storyboard */,
51A16991235E10D600EB091F /* AccountInspectorViewController.swift */,
5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */,
5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */,
5141E7382373C18B0013FF27 /* FeedInspectorViewController.swift */,
);
path = Inspector;
sourceTree = "<group>";
@@ -2068,7 +2068,7 @@
children = (
84BBB12B20142A4700F054F5 /* Inspector.storyboard */,
84BBB12C20142A4700F054F5 /* InspectorWindowController.swift */,
8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */,
8472058020142E8900AD578B /* FeedInspectorViewController.swift */,
841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */,
841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */,
841ABA4D20145E7300980E11 /* NothingInspectorViewController.swift */,
@@ -3206,7 +3206,7 @@
51C9DE5823EA2EF4003D5A6D /* WrapperScriptMessageHandler.swift in Sources */,
51B5C87D23F2346200032075 /* ExtensionContainersFile.swift in Sources */,
51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */,
5141E7392373C18B0013FF27 /* WebFeedInspectorViewController.swift in Sources */,
5141E7392373C18B0013FF27 /* FeedInspectorViewController.swift in Sources */,
C5A6ED6D23C9B0C800AB6BE2 /* UIActivityViewController-Extensions.swift in Sources */,
5108F6D42375EEEF001ABC45 /* TimelinePreviewTableViewController.swift in Sources */,
84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */,
@@ -3354,7 +3354,7 @@
8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */,
51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */,
51FE100A234673A00056195D /* ActivityManager.swift in Sources */,
8472058120142E8900AD578B /* WebFeedInspectorViewController.swift in Sources */,
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */,
55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */,
5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */,
84AD1EAA2031617300BC20B7 /* PasteboardFolder.swift in Sources */,

View File

@@ -39,7 +39,7 @@ class ActivityManager {
}
init() {
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
}
func invalidateCurrentActivities() {
@@ -53,8 +53,8 @@ class ActivityManager {
selectingActivity = makeSelectFeedActivity(feed: feed)
if let webFeed = feed as? Feed {
updateSelectingActivityFeedSearchAttributes(with: webFeed)
if let feed = feed as? Feed {
updateSelectingActivityFeedSearchAttributes(with: feed)
}
donate(selectingActivity!)
@@ -116,8 +116,8 @@ class ActivityManager {
}
}
for webFeed in account.flattenedFeeds() {
ids.append(contentsOf: identifiers(for: webFeed))
for feed in account.flattenedFeeds() {
ids.append(contentsOf: identifiers(for: feed))
}
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
@@ -127,31 +127,31 @@ class ActivityManager {
var ids = [String]()
ids.append(identifier(for: folder))
for webFeed in folder.flattenedFeeds() {
ids.append(contentsOf: identifiers(for: webFeed))
for feed in folder.flattenedFeeds() {
ids.append(contentsOf: identifiers(for: feed))
}
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
}
static func cleanUp(_ webFeed: Feed) {
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: identifiers(for: webFeed))
static func cleanUp(_ feed: Feed) {
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: identifiers(for: feed))
}
#endif
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
guard let webFeed = note.userInfo?[UserInfoKey.feed] as? Feed, let activityFeedId = selectingActivity?.userInfo?[ArticlePathKey.webFeedID] as? String else {
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed, let activityFeedId = selectingActivity?.userInfo?[ArticlePathKey.feedID] as? String else {
return
}
#if os(iOS)
if let article = readingArticle, activityFeedId == article.webFeedID {
if let article = readingArticle, activityFeedId == article.feedID {
updateReadArticleSearchAttributes(with: article)
}
#endif
if activityFeedId == webFeed.feedID {
updateSelectingActivityFeedSearchAttributes(with: webFeed)
if activityFeedId == feed.feedID {
updateSelectingActivityFeedSearchAttributes(with: feed)
}
}
@@ -282,7 +282,7 @@ private extension ActivityManager {
}
static func identifier(for article: Article) -> String {
return "account_\(article.accountID)_feed_\(article.webFeedID)_article_\(article.articleID)"
return "account_\(article.accountID)_feed_\(article.feedID)_article_\(article.articleID)"
}
static func identifiers(for feed: Feed) -> [String] {

View File

@@ -98,7 +98,7 @@ private struct SidebarItemSpecifier {
private weak var account: Account?
private let parentFolder: Folder?
private let folder: Folder?
private let webFeed: Feed?
private let feed: Feed?
private let path: ContainerPath
private let errorHandler: (Error) -> ()
@@ -118,13 +118,13 @@ private struct SidebarItemSpecifier {
self.parentFolder = node.parentFolder()
if let webFeed = node.representedObject as? Feed {
self.webFeed = webFeed
if let feed = node.representedObject as? Feed {
self.feed = feed
self.folder = nil
account = webFeed.account
account = feed.account
}
else if let folder = node.representedObject as? Folder {
self.webFeed = nil
self.feed = nil
self.folder = folder
account = folder.account
}
@@ -144,7 +144,7 @@ private struct SidebarItemSpecifier {
func delete(completion: @escaping () -> Void) {
if let webFeed = webFeed {
if let feed = feed {
guard let container = path.resolveContainer() else {
completion()
@@ -152,7 +152,7 @@ private struct SidebarItemSpecifier {
}
BatchUpdate.shared.start()
account?.removeWebFeed(webFeed, from: container) { result in
account?.removeFeed(feed, from: container) { result in
BatchUpdate.shared.end()
completion()
self.checkResult(result)
@@ -172,22 +172,22 @@ private struct SidebarItemSpecifier {
func restore() {
if let _ = webFeed {
restoreWebFeed()
if let _ = feed {
restoreFeed()
}
else if let _ = folder {
restoreFolder()
}
}
private func restoreWebFeed() {
private func restoreFeed() {
guard let account = account, let feed = webFeed, let container = path.resolveContainer() else {
guard let account = account, let feed = feed, let container = path.resolveContainer() else {
return
}
BatchUpdate.shared.start()
account.restoreWebFeed(feed, container: container) { result in
account.restoreFeed(feed, container: container) { result in
BatchUpdate.shared.end()
self.checkResult(result)
}

View File

@@ -1,5 +1,5 @@
//
// AddWebFeedDefaultContainer.swift
// AddFeedDefaultContainer.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 11/16/19.

View File

@@ -43,7 +43,7 @@ private func accountAndArticlesDictionary(_ articles: Set<Article>) -> [String:
extension Article {
var feed: Feed? {
return account?.existingWebFeed(withWebFeedID: webFeedID)
return account?.existingFeed(withFeedID: feedID)
}
var url: URL? {
@@ -121,11 +121,11 @@ extension Article {
return IconImageCache.shared.imageForArticle(self)
}
func iconImageUrl(webFeed: Feed) -> URL? {
func iconImageUrl(feed: Feed) -> URL? {
if let image = iconImage() {
let fm = FileManager.default
var path = fm.urls(for: .cachesDirectory, in: .userDomainMask)[0]
let feedID = webFeed.feedID.replacingOccurrences(of: "/", with: "_")
let feedID = feed.feedID.replacingOccurrences(of: "/", with: "_")
#if os(macOS)
path.appendPathComponent(feedID + "_smallIcon.tiff")
#else
@@ -193,7 +193,7 @@ extension Article {
struct ArticlePathKey {
static let accountID = "accountID"
static let accountName = "accountName"
static let webFeedID = "webFeedID"
static let feedID = "feedID"
static let articleID = "articleID"
}
@@ -203,7 +203,7 @@ extension Article {
return [
ArticlePathKey.accountID: accountID,
ArticlePathKey.accountName: account?.nameForDisplay ?? "",
ArticlePathKey.webFeedID: webFeedID,
ArticlePathKey.feedID: feedID,
ArticlePathKey.articleID: articleID
]
}
@@ -226,8 +226,8 @@ extension Article: SortableArticle {
return articleID
}
var sortableWebFeedID: String {
return webFeedID
var sortableFeedID: String {
return feedID
}
}

View File

@@ -72,18 +72,18 @@ final class FaviconDownloader {
cache = [Feed: IconImage]()
}
func favicon(for webFeed: Feed) -> IconImage? {
func favicon(for feed: Feed) -> IconImage? {
assert(Thread.isMainThread)
var homePageURL = webFeed.homePageURL
if let faviconURL = webFeed.faviconURL {
var homePageURL = feed.homePageURL
if let faviconURL = feed.faviconURL {
return favicon(with: faviconURL, homePageURL: homePageURL)
}
if homePageURL == nil {
// Base homePageURL off feedURL if needed. Wont always be accurate, but is good enough.
if let feedURL = URL(string: webFeed.url), let scheme = feedURL.scheme, let host = feedURL.host {
if let feedURL = URL(string: feed.url), let scheme = feedURL.scheme, let host = feedURL.host {
homePageURL = scheme + "://" + host + "/"
}
}
@@ -94,16 +94,16 @@ final class FaviconDownloader {
return nil
}
func faviconAsIcon(for webFeed: Feed) -> IconImage? {
func faviconAsIcon(for feed: Feed) -> IconImage? {
if let image = cache[webFeed] {
if let image = cache[feed] {
return image
}
if let iconImage = favicon(for: webFeed), let imageData = iconImage.image.dataRepresentation() {
if let iconImage = favicon(for: feed), let imageData = iconImage.image.dataRepresentation() {
if let scaledImage = RSImage.scaledForIcon(imageData) {
let scaledIconImage = IconImage(scaledImage)
cache[webFeed] = scaledIconImage
cache[feed] = scaledIconImage
return scaledIconImage
}
}

View File

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

View File

@@ -15,7 +15,7 @@ class IconImageCache {
static var shared = IconImageCache()
private var smartFeedIconImageCache = [SidebarItemIdentifier: IconImage]()
private var webFeedIconImageCache = [SidebarItemIdentifier: IconImage]()
private var feedIconImageCache = [SidebarItemIdentifier: IconImage]()
private var faviconImageCache = [SidebarItemIdentifier: IconImage]()
private var smallIconImageCache = [SidebarItemIdentifier: IconImage]()
private var authorIconImageCache = [Author: IconImage]()
@@ -38,7 +38,7 @@ class IconImageCache {
if let smartFeed = feed as? PseudoFeed {
return imageForSmartFeed(smartFeed, feedID)
}
if let webFeed = feed as? Feed, let iconImage = imageForWebFeed(webFeed, feedID) {
if let feed = feed as? Feed, let iconImage = imageForFeed(feed, feedID) {
return iconImage
}
if let smallIconProvider = feed as? SmallIconProvider {
@@ -60,7 +60,7 @@ class IconImageCache {
func emptyCache() {
smartFeedIconImageCache = [SidebarItemIdentifier: IconImage]()
webFeedIconImageCache = [SidebarItemIdentifier: IconImage]()
feedIconImageCache = [SidebarItemIdentifier: IconImage]()
faviconImageCache = [SidebarItemIdentifier: IconImage]()
smallIconImageCache = [SidebarItemIdentifier: IconImage]()
authorIconImageCache = [Author: IconImage]()
@@ -80,18 +80,18 @@ private extension IconImageCache {
return nil
}
func imageForWebFeed(_ webFeed: Feed, _ feedID: SidebarItemIdentifier) -> IconImage? {
if let iconImage = webFeedIconImageCache[feedID] {
func imageForFeed(_ feed: Feed, _ feedID: SidebarItemIdentifier) -> IconImage? {
if let iconImage = feedIconImageCache[feedID] {
return iconImage
}
if let iconImage = appDelegate.feedIconDownloader.icon(for: webFeed) {
webFeedIconImageCache[feedID] = iconImage
if let iconImage = appDelegate.feedIconDownloader.icon(for: feed) {
feedIconImageCache[feedID] = iconImage
return iconImage
}
if let faviconImage = faviconImageCache[feedID] {
return faviconImage
}
if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: webFeed) {
if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
faviconImageCache[feedID] = faviconImage
return faviconImage
}

View File

@@ -13,7 +13,7 @@ protocol SortableArticle {
var sortableName: String { get }
var sortableDate: Date { get }
var sortableArticleID: String { get }
var sortableWebFeedID: String { get }
var sortableFeedID: 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.sortableWebFeedID)" }
let groupedArticles = Dictionary(grouping: articles) { "\($0.sortableName.lowercased())-\($0.sortableFeedID)" }
return groupedArticles
.sorted { $0.key < $1.key }
.flatMap { (tuple) -> [T] in

View File

@@ -66,9 +66,9 @@ private extension FeedTreeControllerDelegate {
var children = [AnyObject]()
for webFeed in container.topLevelFeeds {
if let feedID = webFeed.sidebarItemID, !(!filterExceptions.contains(feedID) && isReadFiltered && webFeed.unreadCount == 0) {
children.append(webFeed)
for feed in container.topLevelFeeds {
if let feedID = feed.sidebarItemID, !(!filterExceptions.contains(feedID) && isReadFiltered && feed.unreadCount == 0) {
children.append(feed)
}
}
@@ -100,8 +100,8 @@ private extension FeedTreeControllerDelegate {
}
func createNode(representedObject: Any, parent: Node) -> Node? {
if let webFeed = representedObject as? Feed {
return createNode(webFeed: webFeed, parent: parent)
if let feed = representedObject as? Feed {
return createNode(feed: feed, parent: parent)
}
if let folder = representedObject as? Folder {
@@ -115,8 +115,8 @@ private extension FeedTreeControllerDelegate {
return nil
}
func createNode(webFeed: Feed, parent: Node) -> Node {
return parent.createChildNode(webFeed)
func createNode(feed: Feed, parent: Node) -> Node {
return parent.createChildNode(feed)
}
func createNode(folder: Folder, parent: Node) -> Node {

View File

@@ -26,8 +26,8 @@ final class UserNotificationManager: NSObject {
}
for article in articles {
if !article.status.read, let webFeed = article.feed, webFeed.isNotifyAboutNewArticles ?? false {
sendNotification(webFeed: webFeed, article: article)
if !article.status.read, let feed = article.feed, feed.isNotifyAboutNewArticles ?? false {
sendNotification(feed: feed, article: article)
}
}
}
@@ -53,21 +53,21 @@ final class UserNotificationManager: NSObject {
private extension UserNotificationManager {
func sendNotification(webFeed: Feed, article: Article) {
func sendNotification(feed: Feed, article: Article) {
let content = UNMutableNotificationContent()
content.title = webFeed.nameForDisplay
content.title = feed.nameForDisplay
if !ArticleStringFormatter.truncatedTitle(article).isEmpty {
content.subtitle = ArticleStringFormatter.truncatedTitle(article)
}
content.body = ArticleStringFormatter.truncatedSummary(article)
content.threadIdentifier = webFeed.feedID
content.summaryArgument = "\(webFeed.nameForDisplay)"
content.threadIdentifier = feed.feedID
content.summaryArgument = "\(feed.nameForDisplay)"
content.summaryArgumentCount = 1
content.sound = UNNotificationSound.default
content.userInfo = [UserInfoKey.articlePath: article.pathUserInfo]
content.categoryIdentifier = "NEW_ARTICLE_NOTIFICATION_CATEGORY"
if let attachment = thumbnailAttachment(for: article, webFeed: webFeed) {
if let attachment = thumbnailAttachment(for: article, feed: feed) {
content.attachments.append(attachment)
}
@@ -78,12 +78,12 @@ private extension UserNotificationManager {
/// Determine if there is an available icon for the article. This will then move it to the caches directory and make it avialble for the notification.
/// - Parameters:
/// - article: `Article`
/// - webFeed: `WebFeed`
/// - feed: `Feed`
/// - Returns: A `UNNotifcationAttachment` if an icon is available. Otherwise nil.
/// - Warning: In certain scenarios, this will return the `faviconTemplateImage`.
func thumbnailAttachment(for article: Article, webFeed: Feed) -> UNNotificationAttachment? {
if let imageURL = article.iconImageUrl(webFeed: webFeed) {
let thumbnail = try? UNNotificationAttachment(identifier: webFeed.feedID, url: imageURL, options: nil)
func thumbnailAttachment(for article: Article, feed: Feed) -> UNNotificationAttachment? {
if let imageURL = article.iconImageUrl(feed: feed) {
let thumbnail = try? UNNotificationAttachment(identifier: feed.feedID, url: imageURL, options: nil)
return thumbnail
}
return nil

View File

@@ -19,10 +19,10 @@ class ArticleSorterTests: XCTestCase {
func testSortByDateAscending() {
let now = Date()
let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-60.0), sortableArticleID: "1", sortableWebFeedID: "4")
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(60.0), sortableArticleID: "2", sortableWebFeedID: "6")
let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(120.0), sortableArticleID: "3", sortableWebFeedID: "6")
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-120.0), sortableArticleID: "4", sortableWebFeedID: "5")
let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-60.0), sortableArticleID: "1", sortableFeedID: "4")
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(60.0), sortableArticleID: "2", sortableFeedID: "6")
let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(120.0), sortableArticleID: "3", sortableFeedID: "6")
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-120.0), sortableArticleID: "4", sortableFeedID: "5")
let articles = [article1, article2, article3, article4]
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
@@ -40,11 +40,11 @@ class ArticleSorterTests: XCTestCase {
let now = Date()
// Articles with the same date should end up being sorted by their article ID
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "1")
let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "2")
let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableArticleID: "3", sortableWebFeedID: "3")
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableArticleID: "4", sortableWebFeedID: "4")
let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableArticleID: "5", sortableWebFeedID: "5")
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "1")
let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "2")
let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "3")
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableArticleID: "4", sortableFeedID: "4")
let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableArticleID: "5", sortableFeedID: "5")
let articles = [article1, article2, article3, article4, article5]
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
@@ -62,15 +62,15 @@ class ArticleSorterTests: XCTestCase {
func testSortByDateAscendingWithGroupByFeed() {
let now = Date()
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: Date(timeInterval: -100.0, since: now), sortableArticleID: "1", sortableWebFeedID: "1")
let article2 = TestArticle(sortableName: "Jenny's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "2")
let article3 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableWebFeedID: "2")
let article4 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -1000.0, since: now), sortableArticleID: "1", sortableWebFeedID: "3")
let article5 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableWebFeedID: "3")
let article6 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: 10.0, since: now), sortableArticleID: "3", sortableWebFeedID: "2")
let article7 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "1")
let article8 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "0")
let article9 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "0")
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: Date(timeInterval: -100.0, since: now), sortableArticleID: "1", sortableFeedID: "1")
let article2 = TestArticle(sortableName: "Jenny's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "2")
let article3 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "2")
let article4 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -1000.0, since: now), sortableArticleID: "1", sortableFeedID: "3")
let article5 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "3")
let article6 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: 10.0, since: now), sortableArticleID: "3", sortableFeedID: "2")
let article7 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "1")
let article8 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "0")
let article9 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "0")
let articles = [article1, article2, article3, article4, article5, article6, article7, article8, article9]
let sortedArticles = ArticleSorter.sortedByDate(articles: articles, sortDirection: .orderedAscending, groupByFeed: true)
@@ -97,10 +97,10 @@ class ArticleSorterTests: XCTestCase {
func testSortByDateDescending() {
let now = Date()
let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-60.0), sortableArticleID: "1", sortableWebFeedID: "4")
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(60.0), sortableArticleID: "2", sortableWebFeedID: "6")
let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(120.0), sortableArticleID: "3", sortableWebFeedID: "6")
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-120.0), sortableArticleID: "4", sortableWebFeedID: "5")
let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-60.0), sortableArticleID: "1", sortableFeedID: "4")
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(60.0), sortableArticleID: "2", sortableFeedID: "6")
let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(120.0), sortableArticleID: "3", sortableFeedID: "6")
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-120.0), sortableArticleID: "4", sortableFeedID: "5")
let articles = [article1, article2, article3, article4]
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
@@ -118,11 +118,11 @@ class ArticleSorterTests: XCTestCase {
let now = Date()
// Articles with the same date should end up being sorted by their article ID
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "1")
let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "2")
let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableArticleID: "3", sortableWebFeedID: "3")
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableArticleID: "4", sortableWebFeedID: "4")
let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableArticleID: "5", sortableWebFeedID: "5")
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "1")
let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "2")
let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "3")
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableArticleID: "4", sortableFeedID: "4")
let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableArticleID: "5", sortableFeedID: "5")
let articles = [article1, article2, article3, article4, article5]
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
@@ -140,15 +140,15 @@ class ArticleSorterTests: XCTestCase {
func testSortByDateDescendingWithGroupByFeed() {
let now = Date()
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: Date(timeInterval: -100.0, since: now), sortableArticleID: "1", sortableWebFeedID: "1")
let article2 = TestArticle(sortableName: "Jenny's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "2")
let article3 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableWebFeedID: "2")
let article4 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -1000.0, since: now), sortableArticleID: "1", sortableWebFeedID: "3")
let article5 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableWebFeedID: "3")
let article6 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: 10.0, since: now), sortableArticleID: "3", sortableWebFeedID: "2")
let article7 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "1")
let article8 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "0")
let article9 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "0")
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: Date(timeInterval: -100.0, since: now), sortableArticleID: "1", sortableFeedID: "1")
let article2 = TestArticle(sortableName: "Jenny's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "2")
let article3 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "2")
let article4 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -1000.0, since: now), sortableArticleID: "1", sortableFeedID: "3")
let article5 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "3")
let article6 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: 10.0, since: now), sortableArticleID: "3", sortableFeedID: "2")
let article7 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "1")
let article8 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "0")
let article9 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "0")
let articles = [article1, article2, article3, article4, article5, article6, article7, article8, article9]
let sortedArticles = ArticleSorter.sortedByDate(articles: articles, sortDirection: .orderedDescending, groupByFeed: true)
@@ -175,11 +175,11 @@ class ArticleSorterTests: XCTestCase {
func testGroupByFeedWithCaseInsensitiveFeedNames() {
let now = Date()
let article1 = TestArticle(sortableName: "phil's feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "1")
let article2 = TestArticle(sortableName: "PhIl's FEed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "1")
let article3 = TestArticle(sortableName: "APPLE's feed", sortableDate: now, sortableArticleID: "3", sortableWebFeedID: "2")
let article4 = TestArticle(sortableName: "PHIL'S FEED", sortableDate: now, sortableArticleID: "4", sortableWebFeedID: "1")
let article5 = TestArticle(sortableName: "apple's feed", sortableDate: now, sortableArticleID: "5", sortableWebFeedID: "2")
let article1 = TestArticle(sortableName: "phil's feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "1")
let article2 = TestArticle(sortableName: "PhIl's FEed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "1")
let article3 = TestArticle(sortableName: "APPLE's feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "2")
let article4 = TestArticle(sortableName: "PHIL'S FEED", sortableDate: now, sortableArticleID: "4", sortableFeedID: "1")
let article5 = TestArticle(sortableName: "apple's feed", sortableDate: now, sortableArticleID: "5", sortableFeedID: "2")
let articles = [article1, article2, article3, article4, article5]
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
@@ -201,11 +201,11 @@ class ArticleSorterTests: XCTestCase {
let now = Date()
// Articles with the same feed name should be sorted by feed ID
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "2")
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "2")
let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "3", sortableWebFeedID: "1")
let article4 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "4", sortableWebFeedID: "2")
let article5 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "5", sortableWebFeedID: "1")
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "2")
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "2")
let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "1")
let article4 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "4", sortableFeedID: "2")
let article5 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "5", sortableFeedID: "1")
let articles = [article1, article2, article3, article4, article5]
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
@@ -226,7 +226,7 @@ private struct TestArticle: SortableArticle, Equatable {
let sortableName: String
let sortableDate: Date
let sortableArticleID: String
let sortableWebFeedID: String
let sortableFeedID: String
}
private extension Array where Element == TestArticle {

View File

@@ -1,7 +1,7 @@
-- this script just tests that no error was generated from the script
try
tell application "NetNewsWire"
exists webFeed 1 of account 1
exists feed 1 of account 1
end tell
on error message
return {test_result:false, script_result:message}

View File

@@ -1,7 +1,7 @@
-- this script just tests that no error was generated from the script
try
tell application "NetNewsWire"
opml representation of webFeed 1 of account 1
opml representation of feed 1 of account 1
end tell
on error message
return {test_result:false, script_result:message}

View File

@@ -1,7 +1,7 @@
-- this script just tests that no error was generated from the script
try
tell application "NetNewsWire"
{name, url} of every webFeed of every account
{name, url} of every feed of every account
end tell
on error message
return {test_result:false, script_result:message}

View File

@@ -2,7 +2,7 @@
-- and that the returned list is greater than 0
try
tell application "NetNewsWire"
set namesResult to name of every author of every webFeed of every account
set namesResult to name of every author of every feed of every account
end tell
set test_result to ((count items of namesResult) > 0)
on error message

View File

@@ -1,7 +1,7 @@
-- this script just tests that no error was generated from the script
try
tell application "NetNewsWire"
title of every article of webFeed "Six Colors" where read is true
title of every article of feed "Six Colors" where read is true
end tell
on error message
return {test_result:false, script_result:message}

View File

@@ -42,7 +42,7 @@ class SharingTests: XCTestCase {
let articleId = randomId()
return Article(accountID: randomId(),
articleID: articleId,
webFeedID: randomId(),
feedID: randomId(),
uniqueID: randomId(),
title: title,
contentHTML: nil,

View File

@@ -147,7 +147,7 @@ class AddFeedViewController: UITableViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 2 {
let navController = UIStoryboard.add.instantiateViewController(withIdentifier: "AddWebFeedFolderNavViewController") as! UINavigationController
let navController = UIStoryboard.add.instantiateViewController(withIdentifier: "AddFeedFolderNavViewController") as! UINavigationController
navController.modalPresentationStyle = .currentContext
let folderViewController = navController.topViewController as! AddFeedFolderViewController
folderViewController.delegate = self
@@ -159,7 +159,7 @@ class AddFeedViewController: UITableViewController {
}
// MARK: AddWebFeedFolderViewControllerDelegate
// MARK: AddFeedFolderViewControllerDelegate
extension AddFeedViewController: AddFeedFolderViewControllerDelegate {
func didSelect(container: Container) {

View File

@@ -68,7 +68,7 @@ class WebViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, 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(currentArticleThemeDidChangeNotification(_:)), name: .CurrentArticleThemeDidChangeNotification, object: nil)
@@ -83,7 +83,7 @@ class WebViewController: UIViewController {
// MARK: Notifications
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
reloadArticleImage()
}
@@ -451,8 +451,8 @@ extension WebViewController: WKScriptMessageHandler {
case MessageName.imageWasClicked:
imageWasClicked(body: message.body as? String)
case MessageName.showFeedInspector:
if let webFeed = article?.feed {
coordinator.showFeedInspector(for: webFeed)
if let feed = article?.feed {
coordinator.showFeedInspector(for: feed)
}
default:
return

View File

@@ -1,5 +1,5 @@
//
// WebFeedInspectorViewController.swift
// FeedInspectorViewController.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 11/6/19.
@@ -11,11 +11,11 @@ import Account
import SafariServices
import UserNotifications
class WebFeedInspectorViewController: UITableViewController {
class FeedInspectorViewController: UITableViewController {
static let preferredContentSizeForFormSheetDisplay = CGSize(width: 460.0, height: 500.0)
var webFeed: Feed!
var feed: Feed!
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var notifyAboutNewArticlesSwitch: UISwitch!
@IBOutlet weak var alwaysShowReaderViewSwitch: UISwitch!
@@ -24,13 +24,13 @@ class WebFeedInspectorViewController: UITableViewController {
private var headerView: InspectorIconHeaderView?
private var iconImage: IconImage? {
return IconImageCache.shared.imageForFeed(webFeed)
return IconImageCache.shared.imageForFeed(feed)
}
private let homePageIndexPath = IndexPath(row: 0, section: 1)
private var shouldHideHomePageSection: Bool {
return webFeed.homePageURL == nil
return feed.homePageURL == nil
}
private var userNotificationSettings: UNNotificationSettings?
@@ -38,18 +38,18 @@ class WebFeedInspectorViewController: UITableViewController {
override func viewDidLoad() {
tableView.register(InspectorIconHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader")
navigationItem.title = webFeed.nameForDisplay
nameTextField.text = webFeed.nameForDisplay
navigationItem.title = feed.nameForDisplay
nameTextField.text = feed.nameForDisplay
notifyAboutNewArticlesSwitch.setOn(webFeed.isNotifyAboutNewArticles ?? false, animated: false)
notifyAboutNewArticlesSwitch.setOn(feed.isNotifyAboutNewArticles ?? false, animated: false)
alwaysShowReaderViewSwitch.setOn(webFeed.isArticleExtractorAlwaysOn ?? false, animated: false)
alwaysShowReaderViewSwitch.setOn(feed.isArticleExtractorAlwaysOn ?? false, animated: false)
homePageLabel.text = webFeed.homePageURL?.decodedURLString
feedURLLabel.text = webFeed.url.decodedURLString
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
homePageLabel.text = feed.homePageURL?.decodedURLString
feedURLLabel.text = feed.url.decodedURLString
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(updateNotificationSettings), name: UIApplication.willEnterForegroundNotification, object: nil)
}
@@ -59,15 +59,15 @@ class WebFeedInspectorViewController: UITableViewController {
}
override func viewDidDisappear(_ animated: Bool) {
if nameTextField.text != webFeed.nameForDisplay {
if nameTextField.text != feed.nameForDisplay {
let nameText = nameTextField.text ?? ""
let newName = nameText.isEmpty ? (webFeed.name ?? NSLocalizedString("Untitled", comment: "Feed name")) : nameText
webFeed.rename(to: newName) { _ in }
let newName = nameText.isEmpty ? (feed.name ?? NSLocalizedString("Untitled", comment: "Feed name")) : nameText
feed.rename(to: newName) { _ in }
}
}
// MARK: Notifications
@objc func webFeedIconDidBecomeAvailable(_ notification: Notification) {
@objc func feedIconDidBecomeAvailable(_ notification: Notification) {
headerView?.iconView.iconImage = iconImage
}
@@ -80,13 +80,13 @@ class WebFeedInspectorViewController: UITableViewController {
notifyAboutNewArticlesSwitch.isOn = !notifyAboutNewArticlesSwitch.isOn
present(notificationUpdateErrorAlert(), animated: true, completion: nil)
} else if settings.authorizationStatus == .authorized {
webFeed.isNotifyAboutNewArticles = notifyAboutNewArticlesSwitch.isOn
feed.isNotifyAboutNewArticles = notifyAboutNewArticlesSwitch.isOn
} else {
UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .sound, .alert]) { (granted, error) in
self.updateNotificationSettings()
if granted {
DispatchQueue.main.async {
self.webFeed.isNotifyAboutNewArticles = self.notifyAboutNewArticlesSwitch.isOn
self.feed.isNotifyAboutNewArticles = self.notifyAboutNewArticlesSwitch.isOn
UIApplication.shared.registerForRemoteNotifications()
}
} else {
@@ -99,7 +99,7 @@ class WebFeedInspectorViewController: UITableViewController {
}
@IBAction func alwaysShowReaderViewChanged(_ sender: Any) {
webFeed.isArticleExtractorAlwaysOn = alwaysShowReaderViewSwitch.isOn
feed.isArticleExtractorAlwaysOn = alwaysShowReaderViewSwitch.isOn
}
@IBAction func done(_ sender: Any) {
@@ -128,7 +128,7 @@ class WebFeedInspectorViewController: UITableViewController {
// MARK: Table View
extension WebFeedInspectorViewController {
extension FeedInspectorViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
let numberOfSections = super.numberOfSections(in: tableView)
@@ -150,7 +150,7 @@ extension WebFeedInspectorViewController {
return cell
}
label.numberOfLines = 2
label.text = webFeed.notificationDisplayName.capitalized
label.text = feed.notificationDisplayName.capitalized
}
return cell
}
@@ -171,7 +171,7 @@ extension WebFeedInspectorViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if shift(indexPath) == homePageIndexPath,
let homePageUrlString = webFeed.homePageURL,
let homePageUrlString = feed.homePageURL,
let homePageUrl = URL(string: homePageUrlString) {
let safari = SFSafariViewController(url: homePageUrl)
@@ -186,7 +186,7 @@ extension WebFeedInspectorViewController {
// MARK: UITextFieldDelegate
extension WebFeedInspectorViewController: UITextFieldDelegate {
extension FeedInspectorViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
@@ -197,7 +197,7 @@ extension WebFeedInspectorViewController: UITextFieldDelegate {
// MARK: UNUserNotificationCenter
extension WebFeedInspectorViewController {
extension FeedInspectorViewController {
@objc
func updateNotificationSettings() {

View File

@@ -13,11 +13,11 @@ import Account
extension MasterFeedViewController: UITableViewDragDelegate {
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
guard let node = coordinator.nodeFor(indexPath), let webFeed = node.representedObject as? Feed else {
guard let node = coordinator.nodeFor(indexPath), let feed = node.representedObject as? Feed else {
return [UIDragItem]()
}
let data = webFeed.url.data(using: .utf8)
let data = feed.url.data(using: .utf8)
let itemProvider = NSItemProvider()
itemProvider.registerDataRepresentation(forTypeIdentifier: kUTTypeURL as String, visibility: .ownProcess) { completion in

View File

@@ -31,8 +31,8 @@ extension MasterFeedViewController: UITableViewDropDelegate {
// Validate account specific behaviors...
if destAccount.behaviors.contains(.disallowFeedInMultipleFolders),
let sourceNode = session.localDragSession?.items.first?.localObject as? Node,
let sourceWebFeed = sourceNode.representedObject as? Feed,
sourceWebFeed.account?.accountID != destAccount.accountID && destAccount.hasFeed(withURL: sourceWebFeed.url) {
let sourceFeed = sourceNode.representedObject as? Feed,
sourceFeed.account?.accountID != destAccount.accountID && destAccount.hasFeed(withURL: sourceFeed.url) {
return UITableViewDropProposal(operation: .forbidden)
}
@@ -91,16 +91,16 @@ extension MasterFeedViewController: UITableViewDropDelegate {
}
}()
guard let destination = destinationContainer, let webFeed = dragNode.representedObject as? Feed else { return }
guard let destination = destinationContainer, let feed = dragNode.representedObject as? Feed else { return }
if source.account == destination.account {
moveWebFeedInAccount(feed: webFeed, sourceContainer: source, destinationContainer: destination)
moveFeedInAccount(feed: feed, sourceContainer: source, destinationContainer: destination)
} else {
moveWebFeedBetweenAccounts(feed: webFeed, sourceContainer: source, destinationContainer: destination)
moveFeedBetweenAccounts(feed: feed, sourceContainer: source, destinationContainer: destination)
}
}
func moveWebFeedInAccount(feed: Feed, sourceContainer: Container, destinationContainer: Container) {
func moveFeedInAccount(feed: Feed, sourceContainer: Container, destinationContainer: Container) {
guard sourceContainer !== destinationContainer else { return }
BatchUpdate.shared.start()
@@ -115,7 +115,7 @@ extension MasterFeedViewController: UITableViewDropDelegate {
}
}
func moveWebFeedBetweenAccounts(feed: Feed, sourceContainer: Container, destinationContainer: Container) {
func moveFeedBetweenAccounts(feed: Feed, sourceContainer: Container, destinationContainer: Container) {
if let existingFeed = destinationContainer.account?.existingFeed(withURL: feed.url) {
@@ -123,7 +123,7 @@ extension MasterFeedViewController: UITableViewDropDelegate {
destinationContainer.account?.addFeed(existingFeed, to: destinationContainer) { result in
switch result {
case .success:
sourceContainer.account?.removeWebFeed(feed, from: sourceContainer) { result in
sourceContainer.account?.removeFeed(feed, from: sourceContainer) { result in
BatchUpdate.shared.end()
switch result {
case .success:
@@ -144,7 +144,7 @@ extension MasterFeedViewController: UITableViewDropDelegate {
destinationContainer.account?.createFeed(url: feed.url, name: feed.editedName, container: destinationContainer, validateFeed: false) { result in
switch result {
case .success:
sourceContainer.account?.removeWebFeed(feed, from: sourceContainer) { result in
sourceContainer.account?.removeFeed(feed, from: sourceContainer) { result in
BatchUpdate.shared.end()
switch result {
case .success:

View File

@@ -68,8 +68,8 @@ 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(webFeedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .FeedSettingDidChange, 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(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
@@ -129,19 +129,19 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
applyToAvailableCells(configureIcon)
}
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
guard let webFeed = note.userInfo?[UserInfoKey.feed] as? Feed else {
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
return
}
applyToCellsForRepresentedObject(webFeed, configureIcon(_:_:))
applyToCellsForRepresentedObject(feed, configureIcon(_:_:))
}
@objc func webFeedSettingDidChange(_ note: Notification) {
guard let webFeed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else {
@objc func feedSettingDidChange(_ note: Notification) {
guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else {
return
}
if key == Feed.FeedSettingKey.homePageURL || key == Feed.FeedSettingKey.faviconURL {
configureCellsForRepresentedObject(webFeed)
configureCellsForRepresentedObject(feed)
}
}
@@ -268,13 +268,13 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
renameAction.backgroundColor = UIColor.systemOrange
actions.append(renameAction)
if let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed {
if let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed {
let moreTitle = NSLocalizedString("More", comment: "More")
let moreAction = UIContextualAction(style: .normal, title: moreTitle) { [weak self] (action, view, completion) in
if let self = self {
let alert = UIAlertController(title: webFeed.nameForDisplay, message: nil, preferredStyle: .actionSheet)
let alert = UIAlertController(title: feed.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)
@@ -324,7 +324,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
return nil
}
if feed is Feed {
return makeWebFeedContextMenu(indexPath: indexPath, includeDeleteRename: true)
return makeFeedContextMenu(indexPath: indexPath, includeDeleteRename: true)
} else if feed is Folder {
return makeFolderContextMenu(indexPath: indexPath)
} else if feed is PseudoFeed {
@@ -439,9 +439,9 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel)
let addWebFeedActionTitle = NSLocalizedString("Add Web Feed", comment: "Add Web Feed")
let addWebFeedAction = UIAlertAction(title: addWebFeedActionTitle, style: .default) { _ in
self.coordinator.showAddWebFeed()
let addFeedActionTitle = NSLocalizedString("Add Feed", comment: "Add Feed")
let addFeedAction = UIAlertAction(title: addFeedActionTitle, style: .default) { _ in
self.coordinator.showAddFeed()
}
let addWebFolderdActionTitle = NSLocalizedString("Add Folder", comment: "Add Folder")
@@ -449,7 +449,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
self.coordinator.showAddFolder()
}
alertController.addAction(addWebFeedAction)
alertController.addAction(addFeedAction)
alertController.addAction(addWebFolderAction)
alertController.addAction(cancelAction)
@@ -649,11 +649,11 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
var menuItems: [UIAction] = []
let addWebFeedActionTitle = NSLocalizedString("Add Feed", comment: "Add Feed")
let addWebFeedAction = UIAction(title: addWebFeedActionTitle, image: AppAssets.plus) { _ in
self.coordinator.showAddWebFeed()
let addFeedActionTitle = NSLocalizedString("Add Feed", comment: "Add Feed")
let addFeedAction = UIAction(title: addFeedActionTitle, image: AppAssets.plus) { _ in
self.coordinator.showAddFeed()
}
menuItems.append(addWebFeedAction)
menuItems.append(addFeedAction)
let addWebFolderActionTitle = NSLocalizedString("Add Folder", comment: "Add Folder")
let addWebFolderAction = UIAction(title: addWebFolderActionTitle, image: AppAssets.folderOutlinePlus) { _ in
@@ -896,7 +896,7 @@ private extension MasterFeedViewController {
coordinator.collapse(node)
}
func makeWebFeedContextMenu(indexPath: IndexPath, includeDeleteRename: Bool) -> UIContextMenuConfiguration {
func makeFeedContextMenu(indexPath: IndexPath, includeDeleteRename: Bool) -> UIContextMenuConfiguration {
return UIContextMenuConfiguration(identifier: MasterFeedRowIdentifier(indexPath: indexPath), previewProvider: nil, actionProvider: { [ weak self] suggestedActions in
guard let self = self else { return nil }
@@ -1000,8 +1000,8 @@ private extension MasterFeedViewController {
}
func copyFeedPageAction(indexPath: IndexPath) -> UIAction? {
guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
let url = URL(string: webFeed.url) else {
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
let url = URL(string: feed.url) else {
return nil
}
@@ -1013,8 +1013,8 @@ private extension MasterFeedViewController {
}
func copyFeedPageAlertAction(indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
let url = URL(string: webFeed.url) else {
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
let url = URL(string: feed.url) else {
return nil
}
@@ -1027,8 +1027,8 @@ private extension MasterFeedViewController {
}
func copyHomePageAction(indexPath: IndexPath) -> UIAction? {
guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
let homePageURL = webFeed.homePageURL,
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
let homePageURL = feed.homePageURL,
let url = URL(string: homePageURL) else {
return nil
}
@@ -1041,8 +1041,8 @@ private extension MasterFeedViewController {
}
func copyHomePageAlertAction(indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
let homePageURL = webFeed.homePageURL,
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
let homePageURL = feed.homePageURL,
let url = URL(string: homePageURL) else {
return nil
}
@@ -1056,14 +1056,14 @@ private extension MasterFeedViewController {
}
func markAllAsReadAlertAction(indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
webFeed.unreadCount > 0,
let articles = try? webFeed.fetchArticles(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
feed.unreadCount > 0,
let articles = try? feed.fetchArticles(), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
return nil
}
let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command")
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, webFeed.nameForDisplay) as String
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String
let cancel = {
completion(true)
}
@@ -1096,13 +1096,13 @@ private extension MasterFeedViewController {
}
func getInfoAction(indexPath: IndexPath) -> UIAction? {
guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed else {
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed else {
return nil
}
let title = NSLocalizedString("Get Info", comment: "Get Info")
let action = UIAction(title: title, image: AppAssets.infoImage) { [weak self] action in
self?.coordinator.showFeedInspector(for: webFeed)
self?.coordinator.showFeedInspector(for: feed)
}
return action
}
@@ -1124,13 +1124,13 @@ private extension MasterFeedViewController {
}
func getInfoAlertAction(indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? Feed else {
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed else {
return nil
}
let title = NSLocalizedString("Get Info", comment: "Get Info")
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
self?.coordinator.showFeedInspector(for: webFeed)
self?.coordinator.showFeedInspector(for: feed)
completion(true)
}
return action
@@ -1196,8 +1196,8 @@ private extension MasterFeedViewController {
return
}
if let webFeed = feed as? Feed {
webFeed.rename(to: name) { result in
if let feed = feed as? Feed {
feed.rename(to: name) { result in
switch result {
case .success:
break

View File

@@ -52,7 +52,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(webFeedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, 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)
@@ -447,7 +447,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
}
}
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
if let titleView = navigationItem.titleView as? MasterTimelineTitleView {
titleView.iconView.iconImage = coordinator.timelineIconImage
@@ -548,7 +548,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
let prototypeID = "prototype"
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, dateArrived: Date())
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, datePublished: nil, dateModified: nil, authors: nil, status: status)
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, datePublished: nil, dateModified: nil, authors: nil, status: status)
let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Prototype Feed Name", byline: nil, iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize)
@@ -846,31 +846,31 @@ private extension MasterTimelineViewController {
}
func discloseFeedAction(_ article: Article) -> UIAction? {
guard let webFeed = article.feed,
!coordinator.timelineFeedIsEqualTo(webFeed) else { return nil }
guard let feed = article.feed,
!coordinator.timelineFeedIsEqualTo(feed) 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.discloseWebFeed(webFeed, animations: [.scroll, .navigation])
self?.coordinator.discloseFeed(feed, animations: [.scroll, .navigation])
}
return action
}
func discloseFeedAlertAction(_ article: Article, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let webFeed = article.feed,
!coordinator.timelineFeedIsEqualTo(webFeed) else { return nil }
guard let feed = article.feed,
!coordinator.timelineFeedIsEqualTo(feed) 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.discloseWebFeed(webFeed, animations: [.scroll, .navigation])
self?.coordinator.discloseFeed(feed, animations: [.scroll, .navigation])
completion(true)
}
return action
}
func markAllInFeedAsReadAction(_ article: Article, indexPath: IndexPath) -> UIAction? {
guard let webFeed = article.feed else { return nil }
guard let fetchedArticles = try? webFeed.fetchArticles() else {
guard let feed = article.feed else { return nil }
guard let fetchedArticles = try? feed.fetchArticles() else {
return nil
}
@@ -881,7 +881,7 @@ private extension MasterTimelineViewController {
let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command")
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, webFeed.nameForDisplay) as String
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String
let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
@@ -892,8 +892,8 @@ private extension MasterTimelineViewController {
}
func markAllInFeedAsReadAlertAction(_ article: Article, indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let webFeed = article.feed else { return nil }
guard let fetchedArticles = try? webFeed.fetchArticles() else {
guard let feed = article.feed else { return nil }
guard let fetchedArticles = try? feed.fetchArticles() else {
return nil
}
@@ -903,7 +903,7 @@ private extension MasterTimelineViewController {
}
let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Mark All as Read in Feed")
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, webFeed.nameForDisplay) as String
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String
let cancel = {
completion(true)
}

View File

@@ -96,7 +96,7 @@ class RootSplitViewController: UISplitViewController {
}
@objc func addNewFeed(_ sender: Any?) {
coordinator.showAddWebFeed()
coordinator.showAddFeed()
}
@objc func addNewFolder(_ sender: Any?) {

View File

@@ -397,7 +397,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
case .readArticle:
self.handleReadArticle(activity.userInfo)
case .addFeedIntent:
self.showAddWebFeed()
self.showAddFeed()
}
}
}
@@ -545,10 +545,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
}
@objc func userDidAddFeed(_ notification: Notification) {
guard let webFeed = notification.userInfo?[UserInfoKey.feed] as? Feed else {
guard let feed = notification.userInfo?[UserInfoKey.feed] as? Feed else {
return
}
discloseWebFeed(webFeed, animations: [.scroll, .navigation])
discloseFeed(feed, animations: [.scroll, .navigation])
}
@objc func userDefaultsDidChange(_ note: Notification) {
@@ -557,7 +557,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
}
@objc func accountDidDownloadArticles(_ note: Notification) {
guard let feeds = note.userInfo?[Account.UserInfoKey.webFeeds] as? Set<Feed> else {
guard let feeds = note.userInfo?[Account.UserInfoKey.feeds] as? Set<Feed> else {
return
}
@@ -1128,25 +1128,25 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
return timelineFeed == feed
}
func discloseWebFeed(_ webFeed: Feed, initialLoad: Bool = false, animations: Animations = [], completion: (() -> Void)? = nil) {
func discloseFeed(_ feed: Feed, initialLoad: Bool = false, animations: Animations = [], completion: (() -> Void)? = nil) {
if isSearching {
masterTimelineViewController?.hideSearch()
}
guard let account = webFeed.account else {
guard let account = feed.account else {
completion?()
return
}
let parentFolder = account.sortedFolders?.first(where: { $0.objectIsChild(webFeed) })
let parentFolder = account.sortedFolders?.first(where: { $0.objectIsChild(feed) })
markExpanded(account)
if let parentFolder = parentFolder {
markExpanded(parentFolder)
}
if let webFeedFeedID = webFeed.sidebarItemID {
self.treeControllerDelegate.addFilterException(webFeedFeedID)
if let sidebarItemID = feed.sidebarItemID {
self.treeControllerDelegate.addFilterException(sidebarItemID)
}
if let parentFolderFeedID = parentFolder?.sidebarItemID {
self.treeControllerDelegate.addFilterException(parentFolderFeedID)
@@ -1155,7 +1155,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
rebuildBackingStores(initialLoad: initialLoad, completion: {
self.treeControllerDelegate.resetFilterExceptions()
self.selectFeed(nil) {
self.selectFeed(webFeed, animations: animations, completion: completion)
self.selectFeed(feed, animations: animations, completion: completion)
}
})
@@ -1196,9 +1196,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
}
func showFeedInspector() {
let timelineWebFeed = timelineFeed as? Feed
let timelineFeed = timelineFeed as? Feed
let articleFeed = currentArticle?.feed
guard let feed = timelineWebFeed ?? articleFeed else {
guard let feed = timelineFeed ?? articleFeed else {
return
}
showFeedInspector(for: feed)
@@ -1207,19 +1207,19 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
func showFeedInspector(for feed: Feed) {
let feedInspectorNavController =
UIStoryboard.inspector.instantiateViewController(identifier: "FeedInspectorNavigationViewController") as! UINavigationController
let feedInspectorController = feedInspectorNavController.topViewController as! WebFeedInspectorViewController
let feedInspectorController = feedInspectorNavController.topViewController as! FeedInspectorViewController
feedInspectorNavController.modalPresentationStyle = .formSheet
feedInspectorNavController.preferredContentSize = WebFeedInspectorViewController.preferredContentSizeForFormSheetDisplay
feedInspectorController.webFeed = feed
feedInspectorNavController.preferredContentSize = FeedInspectorViewController.preferredContentSizeForFormSheetDisplay
feedInspectorController.feed = feed
rootSplitViewController.present(feedInspectorNavController, animated: true)
}
func showAddWebFeed(initialFeed: String? = nil, initialFeedName: String? = nil) {
func showAddFeed(initialFeed: String? = nil, initialFeedName: String? = nil) {
// Since Add Feed can be opened from anywhere with a keyboard shortcut, we have to deselect any currently selected feeds
selectFeed(nil)
let addNavViewController = UIStoryboard.add.instantiateViewController(withIdentifier: "AddWebFeedViewControllerNav") as! UINavigationController
let addNavViewController = UIStoryboard.add.instantiateViewController(withIdentifier: "AddFeedViewControllerNav") as! UINavigationController
let addViewController = addNavViewController.topViewController as! AddFeedViewController
addViewController.initialFeed = initialFeed
@@ -1476,10 +1476,10 @@ private extension SceneCoordinator {
if folderFeed.account?.existingFolder(withID: folderFeed.folderID) != nil {
treeControllerDelegate.addFilterException(feedID)
}
} else if let webFeed = feed as? Feed {
if webFeed.account?.existingWebFeed(withWebFeedID: webFeed.feedID) != nil {
} else if let feed = feed as? Feed {
if feed.account?.existingFeed(withFeedID: feed.feedID) != nil {
treeControllerDelegate.addFilterException(feedID)
addParentFolderToFilterExceptions(webFeed)
addParentFolderToFilterExceptions(feed)
}
}
}
@@ -2002,7 +2002,7 @@ private extension SceneCoordinator {
if !unsortedArticleIDs.contains(article.articleID) {
updatedArticles.insert(article)
}
if article.account?.existingWebFeed(withWebFeedID: article.webFeedID) == nil {
if article.account?.existingFeed(withFeedID: article.feedID) == nil {
updatedArticles.remove(article)
}
}
@@ -2086,7 +2086,7 @@ private extension SceneCoordinator {
}
} else if let folder = timelineFeed as? Folder {
for oneFeed in feeds {
if folder.hasWebFeed(with: oneFeed.feedID) || folder.hasFeed(withURL: oneFeed.url) {
if folder.hasFeed(with: oneFeed.feedID) || folder.hasFeed(withURL: oneFeed.url) {
return true
}
}
@@ -2284,14 +2284,14 @@ private extension SceneCoordinator {
}
})
case .webFeed(let accountID, let webFeedID):
case .feed(let accountID, let feedID):
guard let accountNode = findAccountNode(accountID: accountID),
let account = accountNode.representedObject as? Account,
let webFeed = account.existingWebFeed(withWebFeedID: webFeedID) else {
let feed = account.existingFeed(withFeedID: feedID) else {
return
}
self.discloseWebFeed(webFeed, initialLoad: true) {
self.discloseFeed(feed, initialLoad: true) {
self.masterFeedViewController.focus()
}
}
@@ -2303,7 +2303,7 @@ private extension SceneCoordinator {
guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any],
let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String,
let accountName = articlePathUserInfo[ArticlePathKey.accountName] as? String,
let webFeedID = articlePathUserInfo[ArticlePathKey.webFeedID] as? String,
let feedID = articlePathUserInfo[ArticlePathKey.feedID] as? String,
let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String,
let accountNode = findAccountNode(accountID: accountID, accountName: accountName),
let account = accountNode.representedObject as? Account else {
@@ -2312,20 +2312,20 @@ private extension SceneCoordinator {
exceptionArticleFetcher = SingleArticleFetcher(account: account, articleID: articleID)
if restoreFeedSelection(userInfo, accountID: accountID, webFeedID: webFeedID, articleID: articleID) {
if restoreFeedSelection(userInfo, accountID: accountID, feedID: feedID, articleID: articleID) {
return
}
guard let webFeed = account.existingWebFeed(withWebFeedID: webFeedID) else {
guard let feed = account.existingFeed(withFeedID: feedID) else {
return
}
discloseWebFeed(webFeed) {
discloseFeed(feed) {
self.selectArticleInCurrentFeed(articleID)
}
}
func restoreFeedSelection(_ userInfo: [AnyHashable : Any], accountID: String, webFeedID: String, articleID: String) -> Bool {
func restoreFeedSelection(_ userInfo: [AnyHashable : Any], accountID: String, feedID: String, articleID: String) -> Bool {
guard let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : AnyHashable],
let feedIdentifier = SidebarItemIdentifier(userInfo: feedIdentifierUserInfo),
let isShowingExtractedArticle = userInfo[UserInfoKey.isShowingExtractedArticle] as? Bool,
@@ -2345,11 +2345,11 @@ private extension SceneCoordinator {
}
return found
case .webFeed:
case .feed:
let found = selectFeedAndArticle(feedIdentifier: feedIdentifier, articleID: articleID, isShowingExtractedArticle: isShowingExtractedArticle, articleWindowScrollY: articleWindowScrollY)
if found {
treeControllerDelegate.addFilterException(feedIdentifier)
if let webFeedNode = nodeFor(feedID: feedIdentifier), let folder = webFeedNode.parent?.representedObject as? Folder, let folderFeedID = folder.sidebarItemID {
if let feedNode = nodeFor(feedID: feedIdentifier), let folder = feedNode.parent?.representedObject as? Folder, let folderFeedID = folder.sidebarItemID {
treeControllerDelegate.addFilterException(folderFeedID)
}
}
@@ -2378,8 +2378,8 @@ private extension SceneCoordinator {
return nil
}
func findWebFeedNode(webFeedID: String, beginningAt startingNode: Node) -> Node? {
if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.feedID == webFeedID }) {
func findFeedNode(feedID: String, beginningAt startingNode: Node) -> Node? {
if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.feedID == feedID }) {
return node
}
return nil

View File

@@ -112,7 +112,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
if urlString.starts(with: "feed:") || urlString.starts(with: "feeds:") {
let normalizedURLString = urlString.normalizedURL
if normalizedURLString.mayBeURL {
self.coordinator.showAddWebFeed(initialFeed: normalizedURLString, initialFeedName: nil)
self.coordinator.showAddFeed(initialFeed: normalizedURLString, initialFeedName: nil)
}
}
@@ -217,7 +217,7 @@ private extension SceneDelegate {
case "com.ranchero.NetNewsWire.ShowSearch":
coordinator.showSearch()
case "com.ranchero.NetNewsWire.ShowAdd":
coordinator.showAddWebFeed()
coordinator.showAddFeed()
default:
break
}

View File

@@ -371,7 +371,7 @@ private extension SettingsViewController {
func addFeed() {
self.dismiss(animated: true)
let addNavViewController = UIStoryboard.add.instantiateViewController(withIdentifier: "AddWebFeedViewControllerNav") as! UINavigationController
let addNavViewController = UIStoryboard.add.instantiateViewController(withIdentifier: "AddFeedViewControllerNav") as! UINavigationController
let addViewController = addNavViewController.topViewController as! AddFeedViewController
addViewController.initialFeed = AccountManager.netNewsWireNewsURL
addViewController.initialFeedName = NSLocalizedString("NetNewsWire News", comment: "NetNewsWire News")

View File

@@ -67,7 +67,7 @@ private extension TimelinePreviewTableViewController {
let prototypeID = "prototype"
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, dateArrived: Date())
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, datePublished: nil, dateModified: nil, authors: nil, status: status)
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, datePublished: nil, dateModified: nil, authors: nil, status: status)
let iconImage = IconImage(AppAssets.faviconTemplateImage.withTintColor(AppAssets.secondaryAccentColor))