Implement changed retention policy. Now matches iOS.

This commit is contained in:
Brent Simmons
2020-08-02 21:06:50 -07:00
parent 314987e484
commit 010abbdf55
7 changed files with 81 additions and 31 deletions

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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>? {

View File

@@ -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>? {

View File

@@ -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

View File

@@ -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

View File

@@ -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)