mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Implement changed retention policy. Now matches iOS.
This commit is contained in:
@@ -543,8 +543,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return database.fetchStarredArticleIDs()
|
||||
}
|
||||
|
||||
public func fetchArticleIDsForStatusesWithoutArticles() -> Set<String> {
|
||||
return database.fetchArticleIDsForStatusesWithoutArticles()
|
||||
public func fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate() -> Set<String> {
|
||||
return database.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate()
|
||||
}
|
||||
|
||||
public func opmlDocument() -> String {
|
||||
@@ -593,33 +593,70 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping (() -> Void)) {
|
||||
// Used only by an On My Mac account.
|
||||
precondition(Thread.isMainThread)
|
||||
precondition(type == .onMyMac) // TODO: allow iCloud
|
||||
feed.takeSettings(from: parsedFeed)
|
||||
let feedIDsAndItems = [feed.feedID: parsedFeed.items]
|
||||
update(feedIDsAndItems: feedIDsAndItems, defaultRead: false, completion: completion)
|
||||
let parsedItems = parsedFeed.items
|
||||
guard !parsedItems.isEmpty else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
database.update(with: parsedItems, feedID: feed.feedID) { articleChanges in
|
||||
self.sendNotificationAbout(articleChanges)
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping (() -> Void)) {
|
||||
assert(Thread.isMainThread)
|
||||
// Used only by syncing systems.
|
||||
precondition(Thread.isMainThread)
|
||||
precondition(type != .onMyMac) // TODO: also make sure type != iCloud
|
||||
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
|
||||
|
||||
database.update(feedIDsAndItems: feedIDsAndItems, defaultRead: defaultRead) { articleChanges in
|
||||
self.sendNotificationAbout(articleChanges)
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
func sendNotificationAbout(_ articleChanges: ArticleChanges) {
|
||||
var webFeeds = Set<Feed>()
|
||||
|
||||
if let newArticles = articleChanges.newArticles {
|
||||
webFeeds.formUnion(Set(newArticles.compactMap { $0.feed }))
|
||||
}
|
||||
if let updatedArticles = articleChanges.updatedArticles {
|
||||
webFeeds.formUnion(Set(updatedArticles.compactMap { $0.feed }))
|
||||
}
|
||||
|
||||
var shouldSendNotification = false
|
||||
var shouldUpdateUnreadCounts = false
|
||||
var userInfo = [String: Any]()
|
||||
|
||||
if let newArticles = articleChanges.newArticles, !newArticles.isEmpty {
|
||||
shouldSendNotification = true
|
||||
shouldUpdateUnreadCounts = true
|
||||
userInfo[UserInfoKey.newArticles] = newArticles
|
||||
}
|
||||
|
||||
if let updatedArticles = articleChanges.updatedArticles, !updatedArticles.isEmpty {
|
||||
shouldSendNotification = true
|
||||
userInfo[UserInfoKey.updatedArticles] = updatedArticles
|
||||
}
|
||||
|
||||
if let deletedArticles = articleChanges.deletedArticles, !deletedArticles.isEmpty {
|
||||
shouldUpdateUnreadCounts = true
|
||||
}
|
||||
|
||||
if shouldUpdateUnreadCounts {
|
||||
self.updateUnreadCounts(for: webFeeds)
|
||||
}
|
||||
|
||||
if shouldSendNotification {
|
||||
userInfo[UserInfoKey.feeds] = webFeeds
|
||||
NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -996,7 +996,7 @@ private extension FeedbinAccountDelegate {
|
||||
os_log(.debug, log: log, "Refreshing missing articles...")
|
||||
let group = DispatchGroup()
|
||||
|
||||
let fetchedArticleIDs = account.fetchArticleIDsForStatusesWithoutArticles()
|
||||
let fetchedArticleIDs = account.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate()
|
||||
let articleIDs = Array(fetchedArticleIDs)
|
||||
let chunkedArticleIDs = articleIDs.chunked(into: 100)
|
||||
|
||||
|
||||
@@ -139,8 +139,15 @@ public final class ArticlesDatabase {
|
||||
|
||||
// MARK: - Saving and Updating Articles
|
||||
|
||||
/// Update articles and save new ones. The key for feedIDsAndItems is feedID.
|
||||
/// Update articles and save new ones — for feed-based systems (local and iCloud).
|
||||
public func update(with parsedItems: Set<ParsedItem>, feedID: String, completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
precondition(retentionStyle == .feedBased)
|
||||
articlesTable.update(parsedItems, feedID, completion)
|
||||
}
|
||||
|
||||
/// Update articles and save new ones — for sync systems (Feedbin, Feedly, etc.).
|
||||
public func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
precondition(retentionStyle == .syncSystem)
|
||||
articlesTable.update(feedIDsAndItems, defaultRead, completion)
|
||||
}
|
||||
|
||||
@@ -158,8 +165,8 @@ public final class ArticlesDatabase {
|
||||
return articlesTable.fetchStarredArticleIDs()
|
||||
}
|
||||
|
||||
public func fetchArticleIDsForStatusesWithoutArticles() -> Set<String> {
|
||||
return articlesTable.fetchArticleIDsForStatusesWithoutArticles()
|
||||
public func fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate() -> Set<String> {
|
||||
return articlesTable.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate()
|
||||
}
|
||||
|
||||
public func mark(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<ArticleStatus>? {
|
||||
|
||||
@@ -393,8 +393,8 @@ final class ArticlesTable: DatabaseTable {
|
||||
return statusesTable.fetchStarredArticleIDs()
|
||||
}
|
||||
|
||||
func fetchArticleIDsForStatusesWithoutArticles() -> Set<String> {
|
||||
return statusesTable.fetchArticleIDsForStatusesWithoutArticles()
|
||||
func fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate() -> Set<String> {
|
||||
return statusesTable.fetchArticleIDsForStatusesWithoutArticlesNewerThan(articleCutoffDate)
|
||||
}
|
||||
|
||||
func mark(_ articles: Set<Article>, _ statusKey: ArticleStatus.Key, _ flag: Bool) -> Set<ArticleStatus>? {
|
||||
|
||||
@@ -100,11 +100,18 @@ final class StatusesTable: DatabaseTable {
|
||||
func fetchStarredArticleIDs() -> Set<String> {
|
||||
return fetchArticleIDs("select articleID from statuses where starred=1;")
|
||||
}
|
||||
|
||||
func fetchArticleIDsForStatusesWithoutArticles() -> Set<String> {
|
||||
return fetchArticleIDs("select articleID from statuses s where (read=0 or starred=1) and not exists (select 1 from articles a where a.articleID = s.articleID);")
|
||||
|
||||
func fetchArticleIDsForStatusesWithoutArticlesNewerThan(_ cutoffDate: Date) -> Set<String> {
|
||||
var articleIDs = Set<String>()
|
||||
queue.runInDatabaseSync { database in
|
||||
let sql = "select articleID from statuses s where (starred=1 or dateArrived>?) and not exists (select 1 from articles a where a.articleID = s.articleID);"
|
||||
if let resultSet = database.executeQuery(sql, withArgumentsIn: [cutoffDate]) {
|
||||
articleIDs = resultSet.mapToSet(self.articleIDWithRow)
|
||||
}
|
||||
}
|
||||
return articleIDs
|
||||
}
|
||||
|
||||
|
||||
func fetchArticleIDs(_ sql: String) -> Set<String> {
|
||||
var articleIDs = Set<String>()
|
||||
queue.runInDatabaseSync { (database) in
|
||||
|
||||
@@ -149,7 +149,6 @@ private extension ArticlePasteboardWriter {
|
||||
d[Key.externalURL] = article.externalURL ?? nil
|
||||
d[Key.summary] = article.summary ?? nil
|
||||
d[Key.imageURL] = article.imageURL ?? nil
|
||||
d[Key.bannerImageURL] = article.bannerImageURL ?? nil
|
||||
d[Key.datePublished] = article.datePublished ?? nil
|
||||
d[Key.dateModified] = article.dateModified ?? nil
|
||||
d[Key.dateArrived] = article.status.dateArrived
|
||||
|
||||
@@ -549,7 +549,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
let longTitle = "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?"
|
||||
let prototypeID = "prototype"
|
||||
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, dateArrived: Date())
|
||||
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, 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: showingFeedNames, feedName: "Prototype Feed Name", avatar: nil, showAvatar: false, featuredImage: nil)
|
||||
let height = TimelineCellLayout.height(for: 100, cellData: prototypeCellData, appearance: cellAppearance)
|
||||
|
||||
Reference in New Issue
Block a user