Merge changes from mac-release, including performance fix.
This commit is contained in:
commit
c5e0d96adb
|
@ -621,12 +621,40 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping (() -> Void)) {
|
func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping (() -> Void)) {
|
||||||
|
// Used only by an On My Mac account.
|
||||||
feed.takeSettings(from: parsedFeed)
|
feed.takeSettings(from: parsedFeed)
|
||||||
update(feed, parsedItems: parsedFeed.items, completion)
|
let feedIDsAndItems = [feed.feedID: parsedFeed.items]
|
||||||
|
update(feedIDsAndItems: feedIDsAndItems, defaultRead: false, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping (() -> Void)) {
|
||||||
|
guard !feedIDsAndItems.isEmpty else {
|
||||||
|
completion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
database.update(feedIDsAndItems: feedIDsAndItems, defaultRead: defaultRead) { (newArticles, updatedArticles) in
|
||||||
|
var userInfo = [String: Any]()
|
||||||
|
let feeds = Set(feedIDsAndItems.compactMap { (key, _) -> Feed? in
|
||||||
|
self.existingFeed(withFeedID: key)
|
||||||
|
})
|
||||||
|
if let newArticles = newArticles, !newArticles.isEmpty {
|
||||||
|
self.updateUnreadCounts(for: feeds)
|
||||||
|
userInfo[UserInfoKey.newArticles] = newArticles
|
||||||
|
}
|
||||||
|
if let updatedArticles = updatedArticles, !updatedArticles.isEmpty {
|
||||||
|
userInfo[UserInfoKey.updatedArticles] = updatedArticles
|
||||||
|
}
|
||||||
|
userInfo[UserInfoKey.feeds] = feeds
|
||||||
|
|
||||||
|
completion()
|
||||||
|
|
||||||
|
NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func update(_ feed: Feed, parsedItems: Set<ParsedItem>, defaultRead: Bool = false, _ completion: @escaping (() -> Void)) {
|
func update(_ feed: Feed, parsedItems: Set<ParsedItem>, defaultRead: Bool = false, _ completion: @escaping (() -> Void)) {
|
||||||
database.update(feedID: feed.feedID, parsedItems: parsedItems, defaultRead: defaultRead) { (newArticles, updatedArticles) in
|
let feedIDsAndItems = [feed.feedID: parsedItems]
|
||||||
|
database.update(feedIDsAndItems: feedIDsAndItems, defaultRead: defaultRead) { (newArticles, updatedArticles) in
|
||||||
var userInfo = [String: Any]()
|
var userInfo = [String: Any]()
|
||||||
if let newArticles = newArticles, !newArticles.isEmpty {
|
if let newArticles = newArticles, !newArticles.isEmpty {
|
||||||
self.updateUnreadCounts(for: Set([feed]))
|
self.updateUnreadCounts(for: Set([feed]))
|
||||||
|
@ -663,6 +691,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Empty caches that can reasonably be emptied. Call when the app goes in the background, for instance.
|
||||||
|
func emptyCaches() {
|
||||||
|
database.emptyCaches()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Container
|
// MARK: - Container
|
||||||
|
|
||||||
public func flattenedFeeds() -> Set<Feed> {
|
public func flattenedFeeds() -> Set<Feed> {
|
||||||
|
|
|
@ -248,6 +248,15 @@ public final class AccountManager: UnreadCountProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Caches
|
||||||
|
|
||||||
|
/// Empty caches that can reasonably be emptied — when the app moves to the background, for instance.
|
||||||
|
public func emptyCaches() {
|
||||||
|
for account in accounts {
|
||||||
|
account.emptyCaches()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Notifications
|
// MARK: - Notifications
|
||||||
|
|
||||||
@objc dynamic func unreadCountDidChange(_ notification: Notification) {
|
@objc dynamic func unreadCountDidChange(_ notification: Notification) {
|
||||||
|
|
|
@ -730,9 +730,9 @@ private extension FeedbinAccountDelegate {
|
||||||
// Add any feeds we don't have and update any we do
|
// Add any feeds we don't have and update any we do
|
||||||
var subscriptionsToAdd = Set<FeedbinSubscription>()
|
var subscriptionsToAdd = Set<FeedbinSubscription>()
|
||||||
subscriptions.forEach { subscription in
|
subscriptions.forEach { subscription in
|
||||||
|
|
||||||
let subFeedId = String(subscription.feedID)
|
let subFeedId = String(subscription.feedID)
|
||||||
|
|
||||||
if let feed = account.existingFeed(withFeedID: subFeedId) {
|
if let feed = account.existingFeed(withFeedID: subFeedId) {
|
||||||
feed.name = subscription.name
|
feed.name = subscription.name
|
||||||
// If the name has been changed on the server remove the locally edited name
|
// If the name has been changed on the server remove the locally edited name
|
||||||
|
@ -1060,7 +1060,6 @@ private extension FeedbinAccountDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshArticles(_ account: Account, page: String?, completion: @escaping (() -> Void)) {
|
func refreshArticles(_ account: Account, page: String?, completion: @escaping (() -> Void)) {
|
||||||
|
|
||||||
guard let page = page else {
|
guard let page = page else {
|
||||||
completion()
|
completion()
|
||||||
return
|
return
|
||||||
|
@ -1080,42 +1079,16 @@ private extension FeedbinAccountDelegate {
|
||||||
os_log(.error, log: self.log, "Refresh articles for additional pages failed: %@.", error.localizedDescription)
|
os_log(.error, log: self.log, "Refresh articles for additional pages failed: %@.", error.localizedDescription)
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func processEntries(account: Account, entries: [FeedbinEntry]?, completion: @escaping (() -> Void)) {
|
func processEntries(account: Account, entries: [FeedbinEntry]?, completion: @escaping (() -> Void)) {
|
||||||
|
|
||||||
let parsedItems = mapEntriesToParsedItems(entries: entries)
|
let parsedItems = mapEntriesToParsedItems(entries: entries)
|
||||||
let parsedMap = Dictionary(grouping: parsedItems, by: { item in item.feedURL } )
|
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||||
|
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true, completion: completion)
|
||||||
let group = DispatchGroup()
|
|
||||||
|
|
||||||
for (feedID, mapItems) in parsedMap {
|
|
||||||
|
|
||||||
group.enter()
|
|
||||||
|
|
||||||
if let feed = account.existingFeed(withFeedID: feedID) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
account.update(feed, parsedItems: Set(mapItems), defaultRead: true) {
|
|
||||||
group.leave()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
group.leave()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
group.notify(queue: DispatchQueue.main) {
|
|
||||||
completion()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapEntriesToParsedItems(entries: [FeedbinEntry]?) -> Set<ParsedItem> {
|
func mapEntriesToParsedItems(entries: [FeedbinEntry]?) -> Set<ParsedItem> {
|
||||||
|
|
||||||
guard let entries = entries else {
|
guard let entries = entries else {
|
||||||
return Set<ParsedItem>()
|
return Set<ParsedItem>()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import Articles
|
||||||
|
|
||||||
public typealias UnreadCountDictionary = [String: Int] // feedID: unreadCount
|
public typealias UnreadCountDictionary = [String: Int] // feedID: unreadCount
|
||||||
public typealias UnreadCountCompletionBlock = (UnreadCountDictionary) -> Void
|
public typealias UnreadCountCompletionBlock = (UnreadCountDictionary) -> Void
|
||||||
public typealias UpdateArticlesWithFeedCompletionBlock = (Set<Article>?, Set<Article>?) -> Void //newArticles, updatedArticles
|
public typealias UpdateArticlesCompletionBlock = (Set<Article>?, Set<Article>?) -> Void //newArticles, updatedArticles
|
||||||
|
|
||||||
public final class ArticlesDatabase {
|
public final class ArticlesDatabase {
|
||||||
|
|
||||||
|
@ -126,10 +126,11 @@ public final class ArticlesDatabase {
|
||||||
|
|
||||||
// MARK: - Saving and Updating Articles
|
// MARK: - Saving and Updating Articles
|
||||||
|
|
||||||
public func update(feedID: String, parsedItems: Set<ParsedItem>, defaultRead: Bool, completion: @escaping UpdateArticlesWithFeedCompletionBlock) {
|
/// Update articles and save new ones. The key for feedIDsAndItems is feedID.
|
||||||
return articlesTable.update(feedID, parsedItems, defaultRead, completion)
|
public func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
|
||||||
|
articlesTable.update(feedIDsAndItems, defaultRead, completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func ensureStatuses(_ articleIDs: Set<String>, _ defaultRead: Bool, _ statusKey: ArticleStatus.Key, _ flag: Bool) {
|
public func ensureStatuses(_ articleIDs: Set<String>, _ defaultRead: Bool, _ statusKey: ArticleStatus.Key, _ flag: Bool) {
|
||||||
articlesTable.ensureStatuses(articleIDs, defaultRead, statusKey, flag)
|
articlesTable.ensureStatuses(articleIDs, defaultRead, statusKey, flag)
|
||||||
}
|
}
|
||||||
|
@ -151,6 +152,14 @@ public final class ArticlesDatabase {
|
||||||
public func mark(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<ArticleStatus>? {
|
public func mark(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<ArticleStatus>? {
|
||||||
return articlesTable.mark(articles, statusKey, flag)
|
return articlesTable.mark(articles, statusKey, flag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Caches
|
||||||
|
|
||||||
|
/// Call to free up some memory. Should be done when the app is backgrounded, for instance.
|
||||||
|
/// This does not empty *all* caches — just the ones that are empty-able.
|
||||||
|
public func emptyCaches() {
|
||||||
|
articlesTable.emptyCaches()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
|
@ -215,9 +215,9 @@ final class ArticlesTable: DatabaseTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Updating
|
// MARK: - Updating
|
||||||
|
|
||||||
func update(_ feedID: String, _ parsedItems: Set<ParsedItem>, _ read: Bool, _ completion: @escaping UpdateArticlesWithFeedCompletionBlock) {
|
func update(_ feedIDsAndItems: [String: Set<ParsedItem>], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||||
if parsedItems.isEmpty {
|
if feedIDsAndItems.isEmpty {
|
||||||
completion(nil, nil)
|
completion(nil, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -231,30 +231,34 @@ final class ArticlesTable: DatabaseTable {
|
||||||
// 7. Call back with new and updated Articles.
|
// 7. Call back with new and updated Articles.
|
||||||
// 8. Update search index.
|
// 8. Update search index.
|
||||||
|
|
||||||
let articleIDs = Set(parsedItems.map { $0.articleID })
|
var articleIDs = Set<String>()
|
||||||
|
for (_, parsedItems) in feedIDsAndItems {
|
||||||
|
articleIDs.formUnion(parsedItems.articleIDs())
|
||||||
|
}
|
||||||
|
|
||||||
self.queue.update { (database) in
|
self.queue.update { (database) in
|
||||||
let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1
|
let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1
|
||||||
assert(statusesDictionary.count == articleIDs.count)
|
assert(statusesDictionary.count == articleIDs.count)
|
||||||
|
|
||||||
let allIncomingArticles = Article.articlesWithParsedItems(parsedItems, self.accountID, feedID, statusesDictionary) //2
|
let allIncomingArticles = Article.articlesWithFeedIDsAndItems(feedIDsAndItems, self.accountID, statusesDictionary) //2
|
||||||
if allIncomingArticles.isEmpty {
|
if allIncomingArticles.isEmpty {
|
||||||
self.callUpdateArticlesCompletionBlock(nil, nil, completion)
|
self.callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let incomingArticles = self.filterIncomingArticles(allIncomingArticles) //3
|
let incomingArticles = self.filterIncomingArticles(allIncomingArticles) //3
|
||||||
if incomingArticles.isEmpty {
|
if incomingArticles.isEmpty {
|
||||||
self.callUpdateArticlesCompletionBlock(nil, nil, completion)
|
self.callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let fetchedArticles = self.fetchArticlesForFeedID(feedID, withLimits: false, database) //4
|
let incomingArticleIDs = incomingArticles.articleIDs()
|
||||||
|
let fetchedArticles = self.fetchArticles(articleIDs: incomingArticleIDs, database) //4
|
||||||
let fetchedArticlesDictionary = fetchedArticles.dictionary()
|
let fetchedArticlesDictionary = fetchedArticles.dictionary()
|
||||||
|
|
||||||
let newArticles = self.findAndSaveNewArticles(incomingArticles, fetchedArticlesDictionary, database) //5
|
let newArticles = self.findAndSaveNewArticles(incomingArticles, fetchedArticlesDictionary, database) //5
|
||||||
let updatedArticles = self.findAndSaveUpdatedArticles(incomingArticles, fetchedArticlesDictionary, database) //6
|
let updatedArticles = self.findAndSaveUpdatedArticles(incomingArticles, fetchedArticlesDictionary, database) //6
|
||||||
|
|
||||||
self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, completion) //7
|
self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, completion) //7
|
||||||
|
|
||||||
// 8. Update search index.
|
// 8. Update search index.
|
||||||
|
@ -265,16 +269,75 @@ final class ArticlesTable: DatabaseTable {
|
||||||
if let updatedArticles = updatedArticles {
|
if let updatedArticles = updatedArticles {
|
||||||
articlesToIndex.formUnion(updatedArticles)
|
articlesToIndex.formUnion(updatedArticles)
|
||||||
}
|
}
|
||||||
let articleIDs = articlesToIndex.articleIDs()
|
let articleIDsToIndex = articlesToIndex.articleIDs()
|
||||||
if articleIDs.isEmpty {
|
if articleIDsToIndex.isEmpty {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.searchTable.ensureIndexedArticles(for: articleIDs)
|
self.searchTable.ensureIndexedArticles(for: articleIDsToIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func update(_ feedID: String, _ parsedItems: Set<ParsedItem>, _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||||
|
// if parsedItems.isEmpty {
|
||||||
|
// completion(nil, nil)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 1. Ensure statuses for all the incoming articles.
|
||||||
|
// // 2. Create incoming articles with parsedItems.
|
||||||
|
// // 3. Ignore incoming articles that are userDeleted || (!starred and really old)
|
||||||
|
// // 4. Fetch all articles for the feed.
|
||||||
|
// // 5. Create array of Articles not in database and save them.
|
||||||
|
// // 6. Create array of updated Articles and save what’s changed.
|
||||||
|
// // 7. Call back with new and updated Articles.
|
||||||
|
// // 8. Update search index.
|
||||||
|
//
|
||||||
|
// let articleIDs = Set(parsedItems.map { $0.articleID })
|
||||||
|
//
|
||||||
|
// self.queue.update { (database) in
|
||||||
|
// let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1
|
||||||
|
// assert(statusesDictionary.count == articleIDs.count)
|
||||||
|
//
|
||||||
|
// let allIncomingArticles = Article.articlesWithParsedItems(parsedItems, self.accountID, feedID, statusesDictionary) //2
|
||||||
|
// if allIncomingArticles.isEmpty {
|
||||||
|
// self.callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// let incomingArticles = self.filterIncomingArticles(allIncomingArticles) //3
|
||||||
|
// if incomingArticles.isEmpty {
|
||||||
|
// self.callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// let fetchedArticles = self.fetchArticlesForFeedID(feedID, withLimits: false, database) //4
|
||||||
|
// let fetchedArticlesDictionary = fetchedArticles.dictionary()
|
||||||
|
//
|
||||||
|
// let newArticles = self.findAndSaveNewArticles(incomingArticles, fetchedArticlesDictionary, database) //5
|
||||||
|
// let updatedArticles = self.findAndSaveUpdatedArticles(incomingArticles, fetchedArticlesDictionary, database) //6
|
||||||
|
//
|
||||||
|
// self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, completion) //7
|
||||||
|
//
|
||||||
|
// // 8. Update search index.
|
||||||
|
// var articlesToIndex = Set<Article>()
|
||||||
|
// if let newArticles = newArticles {
|
||||||
|
// articlesToIndex.formUnion(newArticles)
|
||||||
|
// }
|
||||||
|
// if let updatedArticles = updatedArticles {
|
||||||
|
// articlesToIndex.formUnion(updatedArticles)
|
||||||
|
// }
|
||||||
|
// let articleIDs = articlesToIndex.articleIDs()
|
||||||
|
// if articleIDs.isEmpty {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// DispatchQueue.main.async {
|
||||||
|
// self.searchTable.ensureIndexedArticles(for: articleIDs)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
func ensureStatuses(_ articleIDs: Set<String>, _ defaultRead: Bool, _ statusKey: ArticleStatus.Key, _ flag: Bool) {
|
func ensureStatuses(_ articleIDs: Set<String>, _ defaultRead: Bool, _ statusKey: ArticleStatus.Key, _ flag: Bool) {
|
||||||
self.queue.update { (database) in
|
self.queue.update { (database) in
|
||||||
let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, defaultRead, database)
|
let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, defaultRead, database)
|
||||||
|
@ -418,6 +481,14 @@ final class ArticlesTable: DatabaseTable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Caches
|
||||||
|
|
||||||
|
func emptyCaches() {
|
||||||
|
queue.run { _ in
|
||||||
|
self.databaseArticlesCache = [String: DatabaseArticle]()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
@ -595,7 +666,7 @@ private extension ArticlesTable {
|
||||||
|
|
||||||
// MARK: - Saving Parsed Items
|
// MARK: - Saving Parsed Items
|
||||||
|
|
||||||
func callUpdateArticlesCompletionBlock(_ newArticles: Set<Article>?, _ updatedArticles: Set<Article>?, _ completion: @escaping UpdateArticlesWithFeedCompletionBlock) {
|
func callUpdateArticlesCompletionBlock(_ newArticles: Set<Article>?, _ updatedArticles: Set<Article>?, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
completion(newArticles, updatedArticles)
|
completion(newArticles, updatedArticles)
|
||||||
}
|
}
|
||||||
|
@ -727,3 +798,8 @@ private extension ArticlesTable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension Set where Element == ParsedItem {
|
||||||
|
func articleIDs() -> Set<String> {
|
||||||
|
return Set<String>(map { $0.articleID })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -79,11 +79,20 @@ extension Article {
|
||||||
return d.count < 1 ? nil : d
|
return d.count < 1 ? nil : d
|
||||||
}
|
}
|
||||||
|
|
||||||
static func articlesWithParsedItems(_ parsedItems: Set<ParsedItem>, _ accountID: String, _ feedID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
|
// static func articlesWithParsedItems(_ parsedItems: Set<ParsedItem>, _ accountID: String, _ feedID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
|
||||||
|
// let maximumDateAllowed = Date().addingTimeInterval(60 * 60 * 24) // Allow dates up to about 24 hours ahead of now
|
||||||
|
// return Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) })
|
||||||
|
// }
|
||||||
|
|
||||||
|
static func articlesWithFeedIDsAndItems(_ feedIDsAndItems: [String: Set<ParsedItem>], _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
|
||||||
let maximumDateAllowed = Date().addingTimeInterval(60 * 60 * 24) // Allow dates up to about 24 hours ahead of now
|
let maximumDateAllowed = Date().addingTimeInterval(60 * 60 * 24) // Allow dates up to about 24 hours ahead of now
|
||||||
return Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) })
|
var articles = Set<Article>()
|
||||||
|
for (feedID, parsedItems) in feedIDsAndItems {
|
||||||
|
let feedArticles = Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) })
|
||||||
|
articles.formUnion(feedArticles)
|
||||||
|
}
|
||||||
|
return articles
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Article: DatabaseObject {
|
extension Article: DatabaseObject {
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
// Created by Brent Simmons on 9/22/17.
|
// Created by Brent Simmons on 9/22/17.
|
||||||
// Copyright © 2017 Ranchero Software. All rights reserved.
|
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
enum FontSize: Int {
|
enum FontSize: Int {
|
||||||
|
@ -228,7 +227,6 @@ struct AppDefaults {
|
||||||
// an issue, this could be changed to proactively look for whether the default has been
|
// an issue, this could be changed to proactively look for whether the default has been
|
||||||
// set _by the user_ to false, and respect that default if it is so-set.
|
// set _by the user_ to false, and respect that default if it is so-set.
|
||||||
// UserDefaults.standard.set(true, forKey: "NSQuitAlwaysKeepsWindows")
|
// UserDefaults.standard.set(true, forKey: "NSQuitAlwaysKeepsWindows")
|
||||||
|
|
||||||
// TODO: revisit the above when coming back to state restoration issues.
|
// TODO: revisit the above when coming back to state restoration issues.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,7 +324,6 @@ private extension AppDefaults {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: -
|
// MARK: -
|
||||||
|
|
||||||
extension UserDefaults {
|
extension UserDefaults {
|
||||||
/// This property exists so that it can conveniently be observed via KVO
|
/// This property exists so that it can conveniently be observed via KVO
|
||||||
@objc var CorreiaSeparators: Bool {
|
@objc var CorreiaSeparators: Bool {
|
||||||
|
@ -338,4 +335,3 @@ extension UserDefaults {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
// Created by Brent Simmons on 7/11/15.
|
// Created by Brent Simmons on 7/11/15.
|
||||||
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
|
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import AppKit
|
import AppKit
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
import Articles
|
import Articles
|
||||||
|
@ -84,7 +83,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - API
|
// MARK: - API
|
||||||
|
|
||||||
func logMessage(_ message: String, type: LogItem.ItemType) {
|
func logMessage(_ message: String, type: LogItem.ItemType) {
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
@ -115,7 +113,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - NSApplicationDelegate
|
// MARK: - NSApplicationDelegate
|
||||||
|
|
||||||
func applicationWillFinishLaunching(_ notification: Notification) {
|
func applicationWillFinishLaunching(_ notification: Notification) {
|
||||||
installAppleEventHandlers()
|
installAppleEventHandlers()
|
||||||
#if TEST
|
#if TEST
|
||||||
|
@ -271,7 +268,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Notifications
|
// MARK: Notifications
|
||||||
|
|
||||||
@objc func unreadCountDidChange(_ note: Notification) {
|
@objc func unreadCountDidChange(_ note: Notification) {
|
||||||
|
|
||||||
if note.object is AccountManager {
|
if note.object is AccountManager {
|
||||||
|
@ -305,7 +301,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Main Window
|
// MARK: Main Window
|
||||||
|
|
||||||
func windowControllerWithName(_ storyboardName: String) -> NSWindowController {
|
func windowControllerWithName(_ storyboardName: String) -> NSWindowController {
|
||||||
|
|
||||||
let storyboard = NSStoryboard(name: NSStoryboard.Name(storyboardName), bundle: nil)
|
let storyboard = NSStoryboard(name: NSStoryboard.Name(storyboardName), bundle: nil)
|
||||||
|
@ -322,7 +317,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: NSUserInterfaceValidations
|
// MARK: NSUserInterfaceValidations
|
||||||
|
|
||||||
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
||||||
if shuttingDown {
|
if shuttingDown {
|
||||||
return false
|
return false
|
||||||
|
@ -362,7 +356,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Add Feed
|
// MARK: Add Feed
|
||||||
|
|
||||||
func addFeed(_ urlString: String?, name: String? = nil, account: Account? = nil, folder: Folder? = nil) {
|
func addFeed(_ urlString: String?, name: String? = nil, account: Account? = nil, folder: Folder? = nil) {
|
||||||
|
|
||||||
createAndShowMainWindow()
|
createAndShowMainWindow()
|
||||||
|
@ -374,14 +367,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Dock Badge
|
// MARK: - Dock Badge
|
||||||
|
|
||||||
@objc func updateDockBadge() {
|
@objc func updateDockBadge() {
|
||||||
let label = unreadCount > 0 && !AppDefaults.hideDockUnreadCount ? "\(unreadCount)" : ""
|
let label = unreadCount > 0 && !AppDefaults.hideDockUnreadCount ? "\(unreadCount)" : ""
|
||||||
NSApplication.shared.dockTile.badgeLabel = label
|
NSApplication.shared.dockTile.badgeLabel = label
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
|
|
||||||
@IBAction func showPreferences(_ sender: Any?) {
|
@IBAction func showPreferences(_ sender: Any?) {
|
||||||
|
|
||||||
if preferencesWindowController == nil {
|
if preferencesWindowController == nil {
|
||||||
|
@ -588,7 +579,6 @@ extension AppDelegate {
|
||||||
// An attached inspector can display incorrectly on certain setups (like mine); default to displaying in a separate window,
|
// An attached inspector can display incorrectly on certain setups (like mine); default to displaying in a separate window,
|
||||||
// and reset the default to a separate window when the preference is toggled off and on again in case the inspector is
|
// and reset the default to a separate window when the preference is toggled off and on again in case the inspector is
|
||||||
// accidentally reattached.
|
// accidentally reattached.
|
||||||
|
|
||||||
AppDefaults.webInspectorStartsAttached = false
|
AppDefaults.webInspectorStartsAttached = false
|
||||||
NotificationCenter.default.post(name: .WebInspectorEnabledDidChange, object: newValue)
|
NotificationCenter.default.post(name: .WebInspectorEnabledDidChange, object: newValue)
|
||||||
#endif
|
#endif
|
||||||
|
@ -649,4 +639,4 @@ extension AppDelegate : ScriptingAppDelegate {
|
||||||
internal var scriptingSelectedArticles: [Article] {
|
internal var scriptingSelectedArticles: [Article] {
|
||||||
return self.scriptingMainWindowController?.scriptingSelectedArticles ?? []
|
return self.scriptingMainWindowController?.scriptingSelectedArticles ?? []
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue