From 22c2a7a72e8b5630b89f7552f055fb539fc89aa1 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 12 Apr 2020 16:36:35 -0700 Subject: [PATCH 1/5] =?UTF-8?q?Delete=20old=20articles=20in=20a=20graduate?= =?UTF-8?q?d=20way,=20so=20as=20not=20to=20block=20the=20database=20for=20?= =?UTF-8?q?too=20long.=20Also:=20don=E2=80=98t=20delete=20old=20unread=20a?= =?UTF-8?q?rticles=20in=20accounts=20that=20use=20an=20external=20sync=20s?= =?UTF-8?q?ervice.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ArticlesDatabase/ArticlesTable.swift | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift index 9aa7bc06b..796731df8 100644 --- a/Frameworks/ArticlesDatabase/ArticlesTable.swift +++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift @@ -488,20 +488,41 @@ final class ArticlesTable: DatabaseTable { // MARK: - Cleanup /// Delete articles that we won’t show in the UI any longer - /// — their arrival date is before our 90-day recency window. - /// Keep all starred articles, no matter their age. + /// — their arrival date is before our 90-day recency window; + /// they are read; they are not starred. + /// + /// Because deleting articles might block the database for too long, + /// we do this in a careful way: delete articles older than a year, + /// check to see how much time has passed, then decide whether or not to continue. + /// Repeat for successively shorter time intervals. func deleteOldArticles() { - queue.runInTransaction { databaseResult in + precondition(retentionStyle == .syncSystem) - func makeDatabaseCalls(_ database: FMDatabase) { - let sql = "delete from articles where articleID in (select articleID from articles natural join statuses where dateArrived Bool { + let timeElapsed = Date().timeIntervalSince(startTime) + return timeElapsed > 2.0 } + + let dayIntervals = [365, 300, 225, 150] + for dayInterval in dayIntervals { + deleteOldArticles(cutoffDate: startTime.bySubtracting(days: dayInterval)) + if tooMuchTimeHasPassed() { + return + } + } + deleteOldArticles(cutoffDate: self.articleCutoffDate) } } From 6d9eda442fa17306235a62d7abcf996820df4a51 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 12 Apr 2020 17:12:36 -0700 Subject: [PATCH 2/5] =?UTF-8?q?Remove=20userDeleted=20support.=20The=20rea?= =?UTF-8?q?d-items=20filter=20seems=20to=20fill=20the=20need=20to=20hide?= =?UTF-8?q?=20things.=20We=E2=80=99re=20simplifying=20the=20backend=20by?= =?UTF-8?q?=20removing=20this=20unused=20feature.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Frameworks/Account/Account.swift | 2 +- .../FeedWrangler/FeedWranglerAPICaller.swift | 3 -- Frameworks/Articles/ArticleStatus.swift | 15 +++------- .../ArticlesDatabase/ArticlesDatabase.swift | 4 +-- .../ArticlesDatabase/ArticlesTable.swift | 30 +++++++------------ Frameworks/ArticlesDatabase/Constants.swift | 1 - .../Extensions/ArticleStatus+Database.swift | 7 ++--- .../FetchAllUnreadCountsOperation.swift | 2 +- .../FetchFeedUnreadCountOperation.swift | 2 +- .../FetchUnreadCountsForFeedsOperation.swift | 2 +- .../ArticlesDatabase/StatusesTable.swift | 8 ++--- .../Timeline/ArticlePasteboardWriter.swift | 1 - .../Timeline/TimelineViewController.swift | 2 +- Mac/Scriptability/Article+Scriptability.swift | 2 +- Shared/Commands/MarkStatusCommand.swift | 4 --- Technotes/ArticlesAndStatuses.markdown | 4 +-- .../MasterTimelineViewController.swift | 2 +- .../TimelinePreviewTableViewController.swift | 2 +- 18 files changed, 34 insertions(+), 59 deletions(-) diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 42eefa9d1..812ad9693 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -659,7 +659,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, database.fetchStarredArticleIDsAsync(webFeedIDs: flattenedWebFeeds().webFeedIDs(), completion: completion) } - /// Fetch articleIDs for articles that we should have, but don’t. These articles are not userDeleted, and they are either (starred) or (newer than the article cutoff date). + /// Fetch articleIDs for articles that we should have, but don’t. These articles are either (starred) or (newer than the article cutoff date). public func fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate(_ completion: @escaping ArticleIDsCompletionBlock) { database.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate(completion) } diff --git a/Frameworks/Account/FeedWrangler/FeedWranglerAPICaller.swift b/Frameworks/Account/FeedWrangler/FeedWranglerAPICaller.swift index 65c2e41cb..39e1107e6 100644 --- a/Frameworks/Account/FeedWrangler/FeedWranglerAPICaller.swift +++ b/Frameworks/Account/FeedWrangler/FeedWranglerAPICaller.swift @@ -263,9 +263,6 @@ final class FeedWranglerAPICaller: NSObject { case .starred: return URLQueryItem(name: "starred", value: status.flag.description) - - case .userDeleted: - return nil } } queryItems.append(URLQueryItem(name: "feed_item_id", value: articleID)) diff --git a/Frameworks/Articles/ArticleStatus.swift b/Frameworks/Articles/ArticleStatus.swift index 4aaa8ee02..4ccc6b95c 100644 --- a/Frameworks/Articles/ArticleStatus.swift +++ b/Frameworks/Articles/ArticleStatus.swift @@ -19,7 +19,6 @@ public final class ArticleStatus: Hashable { public enum Key: String { case read = "read" case starred = "starred" - case userDeleted = "userDeleted" } public let articleID: String @@ -27,18 +26,16 @@ public final class ArticleStatus: Hashable { public var read = false public var starred = false - public var userDeleted = false - - public init(articleID: String, read: Bool, starred: Bool, userDeleted: Bool, dateArrived: Date) { + + public init(articleID: String, read: Bool, starred: Bool, dateArrived: Date) { self.articleID = articleID self.read = read self.starred = starred - self.userDeleted = userDeleted self.dateArrived = dateArrived } public convenience init(articleID: String, read: Bool, dateArrived: Date) { - self.init(articleID: articleID, read: read, starred: false, userDeleted: false, dateArrived: dateArrived) + self.init(articleID: articleID, read: read, starred: false, dateArrived: dateArrived) } public func boolStatus(forKey key: ArticleStatus.Key) -> Bool { @@ -47,8 +44,6 @@ public final class ArticleStatus: Hashable { return read case .starred: return starred - case .userDeleted: - return userDeleted } } @@ -58,8 +53,6 @@ public final class ArticleStatus: Hashable { read = status case .starred: starred = status - case .userDeleted: - userDeleted = status } } @@ -72,7 +65,7 @@ public final class ArticleStatus: Hashable { // MARK: - Equatable public static func ==(lhs: ArticleStatus, rhs: ArticleStatus) -> Bool { - return lhs.articleID == rhs.articleID && lhs.dateArrived == rhs.dateArrived && lhs.read == rhs.read && lhs.starred == rhs.starred && lhs.userDeleted == rhs.userDeleted + return lhs.articleID == rhs.articleID && lhs.dateArrived == rhs.dateArrived && lhs.read == rhs.read && lhs.starred == rhs.starred } } diff --git a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift index 4ca73e177..aac8852db 100644 --- a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift +++ b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift @@ -213,7 +213,7 @@ public final class ArticlesDatabase { articlesTable.fetchStarredArticleIDsAsync(webFeedIDs, completion) } - /// Fetch articleIDs for articles that we should have, but don’t. These articles are not userDeleted, and they are either (starred) or (newer than the article cutoff date). + /// Fetch articleIDs for articles that we should have, but don’t. These articles are either (starred) or (newer than the article cutoff date). public func fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate(_ completion: @escaping ArticleIDsCompletionBlock) { articlesTable.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate(completion) } @@ -283,7 +283,7 @@ private extension ArticlesDatabase { static let tableCreationStatements = """ CREATE TABLE if not EXISTS articles (articleID TEXT NOT NULL PRIMARY KEY, feedID TEXT NOT NULL, uniqueID TEXT NOT NULL, title TEXT, contentHTML TEXT, contentText TEXT, url TEXT, externalURL TEXT, summary TEXT, imageURL TEXT, bannerImageURL TEXT, datePublished DATE, dateModified DATE, searchRowID INTEGER); - CREATE TABLE if not EXISTS statuses (articleID TEXT NOT NULL PRIMARY KEY, read BOOL NOT NULL DEFAULT 0, starred BOOL NOT NULL DEFAULT 0, userDeleted BOOL NOT NULL DEFAULT 0, dateArrived DATE NOT NULL DEFAULT 0); + CREATE TABLE if not EXISTS statuses (articleID TEXT NOT NULL PRIMARY KEY, read BOOL NOT NULL DEFAULT 0, starred BOOL NOT NULL DEFAULT 0, dateArrived DATE NOT NULL DEFAULT 0); CREATE TABLE if not EXISTS authors (authorID TEXT NOT NULL PRIMARY KEY, name TEXT, url TEXT, avatarURL TEXT, emailAddress TEXT); CREATE TABLE if not EXISTS authorsLookup (authorID TEXT NOT NULL, articleID TEXT NOT NULL, PRIMARY KEY(authorID, articleID)); diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift index 796731df8..e87f699e8 100644 --- a/Frameworks/ArticlesDatabase/ArticlesTable.swift +++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift @@ -181,7 +181,7 @@ final class ArticlesTable: DatabaseTable { // 1. Ensure statuses for all the incoming articles. // 2. Create incoming articles with parsedItems. - // 3. Ignore incoming articles that are userDeleted + // 3. [Deleted - no longer needed] // 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. @@ -197,8 +197,7 @@ final class ArticlesTable: DatabaseTable { let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, false, database) //1 assert(statusesDictionary.count == articleIDs.count) - let allIncomingArticles = Article.articlesWithParsedItems(parsedItems, webFeedID, self.accountID, statusesDictionary) //2 - let incomingArticles = Set(allIncomingArticles.filter { !($0.status.userDeleted) }) //3 + let incomingArticles = Article.articlesWithParsedItems(parsedItems, webFeedID, self.accountID, statusesDictionary) //2 if incomingArticles.isEmpty { self.callUpdateArticlesCompletionBlock(nil, nil, completion) return @@ -251,7 +250,7 @@ final class ArticlesTable: DatabaseTable { // 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) + // 3. Ignore incoming articles that are (!starred and read 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. @@ -326,7 +325,7 @@ final class ArticlesTable: DatabaseTable { func makeDatabaseCalls(_ database: FMDatabase) { let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))! - let sql = "select count(*) from articles natural join statuses where feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?)) and read=0 and userDeleted=0;" + 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] @@ -361,7 +360,7 @@ final class ArticlesTable: DatabaseTable { func makeDatabaseCalls(_ database: FMDatabase) { let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))! - let sql = "select count(*) from articles natural join statuses where feedID in \(placeholders) and read=0 and starred=1 and userDeleted=0;" + let 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 unreadCount = self.numberWithSQLAndParameters(sql, parameters, in: database) @@ -655,7 +654,7 @@ private extension ArticlesTable { // * Must be either 1) starred or 2) dateArrived must be newer than cutoff date. if withLimits { - let sql = "select * from articles natural join statuses where \(whereClause) and userDeleted=0 and (starred=1 or dateArrived>?);" + let sql = "select * from articles natural join statuses where \(whereClause) and (starred=1 or dateArrived>?);" return articlesWithSQL(sql, parameters + [articleCutoffDate as AnyObject], database) } else { @@ -670,7 +669,7 @@ private extension ArticlesTable { // // * Must not be deleted. // // * Must be either 1) starred or 2) dateArrived must be newer than cutoff date. // -// let sql = "select count(*) from articles natural join statuses where feedID=? and read=0 and userDeleted=0 and (starred=1 or dateArrived>?);" +// let sql = "select count(*) from articles natural join statuses where feedID=? and read=0 and (starred=1 or dateArrived>?);" // return numberWithSQLAndParameters(sql, [webFeedID, articleCutoffDate], in: database) // } @@ -726,9 +725,6 @@ private extension ArticlesTable { let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))! var sql = "select articleID from articles natural join statuses where feedID in \(placeholders) and \(statusKey.rawValue)=" sql += value ? "1" : "0" - if statusKey != .userDeleted { - sql += " and userDeleted=0" - } sql += ";" let parameters = Array(webFeedIDs) as [Any] @@ -802,18 +798,18 @@ private extension ArticlesTable { } let parameters = webFeedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject] let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))! - let whereClause = "feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?)) and userDeleted = 0" + let whereClause = "feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?))" return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false) } func fetchStarredArticles(_ webFeedIDs: Set, _ database: FMDatabase) -> Set
{ - // select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred = 1 and userDeleted = 0; + // select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred=1; if webFeedIDs.isEmpty { return Set
() } let parameters = webFeedIDs.map { $0 as AnyObject } let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))! - let whereClause = "feedID in \(placeholders) and starred = 1 and userDeleted = 0" + let whereClause = "feedID in \(placeholders) and starred=1" return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false) } @@ -952,11 +948,7 @@ private extension ArticlesTable { } func articleIsIgnorable(_ article: Article) -> Bool { - // Ignorable articles: either userDeleted==1 or (not starred and arrival date > 4 months). - if article.status.userDeleted { - return true - } - if article.status.starred { + if article.status.starred || !article.status.read { return false } return article.status.dateArrived < articleCutoffDate diff --git a/Frameworks/ArticlesDatabase/Constants.swift b/Frameworks/ArticlesDatabase/Constants.swift index 44e19ef00..ef3911eaa 100644 --- a/Frameworks/ArticlesDatabase/Constants.swift +++ b/Frameworks/ArticlesDatabase/Constants.swift @@ -42,7 +42,6 @@ struct DatabaseKey { // ArticleStatus static let read = "read" static let starred = "starred" - static let userDeleted = "userDeleted" static let dateArrived = "dateArrived" // Tag diff --git a/Frameworks/ArticlesDatabase/Extensions/ArticleStatus+Database.swift b/Frameworks/ArticlesDatabase/Extensions/ArticleStatus+Database.swift index b4d46650e..143a19536 100644 --- a/Frameworks/ArticlesDatabase/Extensions/ArticleStatus+Database.swift +++ b/Frameworks/ArticlesDatabase/Extensions/ArticleStatus+Database.swift @@ -15,9 +15,8 @@ extension ArticleStatus { convenience init(articleID: String, dateArrived: Date, row: FMResultSet) { let read = row.bool(forColumn: DatabaseKey.read) let starred = row.bool(forColumn: DatabaseKey.starred) - let userDeleted = row.bool(forColumn: DatabaseKey.userDeleted) - - self.init(articleID: articleID, read: read, starred: starred, userDeleted: userDeleted, dateArrived: dateArrived) + + self.init(articleID: articleID, read: read, starred: starred, dateArrived: dateArrived) } } @@ -29,7 +28,7 @@ extension ArticleStatus: DatabaseObject { } public func databaseDictionary() -> DatabaseDictionary? { - return [DatabaseKey.articleID: articleID, DatabaseKey.read: read, DatabaseKey.starred: starred, DatabaseKey.userDeleted: userDeleted, DatabaseKey.dateArrived: dateArrived] + return [DatabaseKey.articleID: articleID, DatabaseKey.read: read, DatabaseKey.starred: starred, DatabaseKey.dateArrived: dateArrived] } } diff --git a/Frameworks/ArticlesDatabase/Operations/FetchAllUnreadCountsOperation.swift b/Frameworks/ArticlesDatabase/Operations/FetchAllUnreadCountsOperation.swift index 24b81f87e..2f82d2549 100644 --- a/Frameworks/ArticlesDatabase/Operations/FetchAllUnreadCountsOperation.swift +++ b/Frameworks/ArticlesDatabase/Operations/FetchAllUnreadCountsOperation.swift @@ -49,7 +49,7 @@ public final class FetchAllUnreadCountsOperation: MainThreadOperation { private extension FetchAllUnreadCountsOperation { func fetchUnreadCounts(_ database: FMDatabase) { - let sql = "select distinct feedID, count(*) from articles natural join statuses where read=0 and userDeleted=0 and (starred=1 or dateArrived>?) group by feedID;" + let sql = "select distinct feedID, count(*) from articles natural join statuses where read=0 and (starred=1 or dateArrived>?) group by feedID;" guard let resultSet = database.executeQuery(sql, withArgumentsIn: [cutoffDate]) else { informOperationDelegateOfCompletion() diff --git a/Frameworks/ArticlesDatabase/Operations/FetchFeedUnreadCountOperation.swift b/Frameworks/ArticlesDatabase/Operations/FetchFeedUnreadCountOperation.swift index d3b0bd2d1..4f8c99504 100644 --- a/Frameworks/ArticlesDatabase/Operations/FetchFeedUnreadCountOperation.swift +++ b/Frameworks/ArticlesDatabase/Operations/FetchFeedUnreadCountOperation.swift @@ -52,7 +52,7 @@ public final class FetchFeedUnreadCountOperation: MainThreadOperation { private extension FetchFeedUnreadCountOperation { func fetchUnreadCount(_ database: FMDatabase) { - let sql = "select count(*) from articles natural join statuses where feedID=? and read=0 and userDeleted=0 and (starred=1 or dateArrived>?);" + let sql = "select count(*) from articles natural join statuses where feedID=? and read=0 and (starred=1 or dateArrived>?);" guard let resultSet = database.executeQuery(sql, withArgumentsIn: [webFeedID, cutoffDate]) else { informOperationDelegateOfCompletion() diff --git a/Frameworks/ArticlesDatabase/Operations/FetchUnreadCountsForFeedsOperation.swift b/Frameworks/ArticlesDatabase/Operations/FetchUnreadCountsForFeedsOperation.swift index 4b842bab5..b323f4e4f 100644 --- a/Frameworks/ArticlesDatabase/Operations/FetchUnreadCountsForFeedsOperation.swift +++ b/Frameworks/ArticlesDatabase/Operations/FetchUnreadCountsForFeedsOperation.swift @@ -53,7 +53,7 @@ private extension FetchUnreadCountsForFeedsOperation { func fetchUnreadCounts(_ database: FMDatabase) { let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))! - let sql = "select distinct feedID, count(*) from articles natural join statuses where feedID in \(placeholders) and read=0 and userDeleted=0 and (starred=1 or dateArrived>?) group by feedID;" + let sql = "select distinct feedID, count(*) from articles natural join statuses where feedID in \(placeholders) and read=0 and (starred=1 or dateArrived>?) group by feedID;" var parameters = [Any]() parameters += Array(webFeedIDs) as [Any] diff --git a/Frameworks/ArticlesDatabase/StatusesTable.swift b/Frameworks/ArticlesDatabase/StatusesTable.swift index 04916d3de..e08359e31 100644 --- a/Frameworks/ArticlesDatabase/StatusesTable.swift +++ b/Frameworks/ArticlesDatabase/StatusesTable.swift @@ -13,7 +13,7 @@ import Articles // Article->ArticleStatus is a to-one relationship. // -// CREATE TABLE if not EXISTS statuses (articleID TEXT NOT NULL PRIMARY KEY, read BOOL NOT NULL DEFAULT 0, starred BOOL NOT NULL DEFAULT 0, userDeleted BOOL NOT NULL DEFAULT 0, dateArrived DATE NOT NULL DEFAULT 0); +// CREATE TABLE if not EXISTS statuses (articleID TEXT NOT NULL PRIMARY KEY, read BOOL NOT NULL DEFAULT 0, starred BOOL NOT NULL DEFAULT 0, dateArrived DATE NOT NULL DEFAULT 0); final class StatusesTable: DatabaseTable { @@ -94,11 +94,11 @@ final class StatusesTable: DatabaseTable { // MARK: - Fetching func fetchUnreadArticleIDs() throws -> Set { - return try fetchArticleIDs("select articleID from statuses where read=0 and userDeleted=0;") + return try fetchArticleIDs("select articleID from statuses where read=0;") } func fetchStarredArticleIDs() throws -> Set { - return try fetchArticleIDs("select articleID from statuses where starred=1 and userDeleted=0;") + return try fetchArticleIDs("select articleID from statuses where starred=1;") } func fetchArticleIDsForStatusesWithoutArticlesNewerThan(_ cutoffDate: Date, _ completion: @escaping ArticleIDsCompletionBlock) { @@ -108,7 +108,7 @@ final class StatusesTable: DatabaseTable { var articleIDs = Set() func makeDatabaseCall(_ database: FMDatabase) { - let sql = "select articleID from statuses s where (starred=1 or dateArrived>?) and userDeleted=0 and not exists (select 1 from articles a where a.articleID = s.articleID);" + 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) } diff --git a/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift b/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift index b962ba8e6..a405a8be2 100644 --- a/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift +++ b/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift @@ -120,7 +120,6 @@ private extension ArticlePasteboardWriter { static let dateArrived = "dateArrived" static let read = "read" static let starred = "starred" - static let userDeleted = "userDeleted" static let authors = "authors" // Author diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index 31daeb2ab..061aca84c 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -587,7 +587,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr private func calculateRowHeight(showingFeedNames: Bool) -> CGFloat { let longTitle = "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?" let prototypeID = "prototype" - let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date()) + let 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 prototypeCellData = TimelineCellData(article: prototypeArticle, showFeedName: showingFeedNames, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil) diff --git a/Mac/Scriptability/Article+Scriptability.swift b/Mac/Scriptability/Article+Scriptability.swift index eca5bb971..08381c8d6 100644 --- a/Mac/Scriptability/Article+Scriptability.swift +++ b/Mac/Scriptability/Article+Scriptability.swift @@ -127,7 +127,7 @@ class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectConta @objc(deleted) var deleted:Bool { - return article.status.boolStatus(forKey:.userDeleted) + return false } @objc(imageURL) diff --git a/Shared/Commands/MarkStatusCommand.swift b/Shared/Commands/MarkStatusCommand.swift index 5d78d664a..9c1a26072 100644 --- a/Shared/Commands/MarkStatusCommand.swift +++ b/Shared/Commands/MarkStatusCommand.swift @@ -71,8 +71,6 @@ private extension MarkStatusCommand { static private let markUnreadActionName = NSLocalizedString("Mark Unread", comment: "command") static private let markStarredActionName = NSLocalizedString("Mark Starred", comment: "command") static private let markUnstarredActionName = NSLocalizedString("Mark Unstarred", comment: "command") - static private let markDeletedActionName = NSLocalizedString("Delete", comment: "command") - static private let markUndeletedActionName = NSLocalizedString("Undelete", comment: "command") static func actionName(_ statusKey: ArticleStatus.Key, _ flag: Bool) -> String { @@ -81,8 +79,6 @@ private extension MarkStatusCommand { return flag ? markReadActionName : markUnreadActionName case .starred: return flag ? markStarredActionName : markUnstarredActionName - case .userDeleted: - return flag ? markDeletedActionName : markUndeletedActionName } } diff --git a/Technotes/ArticlesAndStatuses.markdown b/Technotes/ArticlesAndStatuses.markdown index 818cee37f..4cee95529 100644 --- a/Technotes/ArticlesAndStatuses.markdown +++ b/Technotes/ArticlesAndStatuses.markdown @@ -6,7 +6,7 @@ In the database (see ArticlesDatabase), they’re stored in two separate tables: The articles table contains the columns you’d expect: `articleID`, `title`, `contentHTML`, and so on. -The statuses table contains `articleID`, `read`, `starred`, `userDeleted`, and `dateArrived` columns. +The statuses table contains `articleID`, `read`, `starred`, and `dateArrived` columns. This separation is deliberate. There are two main reasons: syncing, and strange behavior. @@ -34,4 +34,4 @@ With the article deleted — and since it has no pubDate — how can the app te Here’s how: it still has the status, and the status includes a `dateArrived` property which is in the distant past — and so NetNewsWire knows that it’s not new but old. -Note that statuses do get deleted eventually, too (in theory) — but that’s after a much longer period of time. \ No newline at end of file +Note that statuses do get deleted eventually, too (in theory) — but that’s after a much longer period of time. diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 8f7b41c24..fdd148c81 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -486,7 +486,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner let longTitle = "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?" let prototypeID = "prototype" - let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date()) + let 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 prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize) diff --git a/iOS/Settings/TimelinePreviewTableViewController.swift b/iOS/Settings/TimelinePreviewTableViewController.swift index e5729d161..8d3fa87b9 100644 --- a/iOS/Settings/TimelinePreviewTableViewController.swift +++ b/iOS/Settings/TimelinePreviewTableViewController.swift @@ -66,7 +66,7 @@ private extension TimelinePreviewTableViewController { let longTitle = "Enim ut tellus elementum sagittis vitae et. Nibh praesent tristique magna sit amet purus gravida quis blandit. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Massa id neque aliquam vestibulum morbi blandit. Ultrices vitae auctor eu augue. Enim eu turpis egestas pretium aenean pharetra magna. Eget gravida cum sociis natoque. Sit amet consectetur adipiscing elit. Auctor eu augue ut lectus arcu bibendum. Maecenas volutpat blandit aliquam etiam erat velit. Ut pharetra sit amet aliquam id diam maecenas ultricies. In hac habitasse platea dictumst quisque sagittis purus sit amet." let prototypeID = "prototype" - let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date()) + 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 iconImage = IconImage(AppAssets.faviconTemplateImage.withTintColor(AppAssets.secondaryAccentColor)) From 1321f0b9ffb5a9981399fb2452ee839cc789250b Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 12 Apr 2020 18:23:20 -0700 Subject: [PATCH 3/5] =?UTF-8?q?Loop=20through=20through=20rows=20using=20t?= =?UTF-8?q?he=20count=20of=20rows=20in=20that=20section,=20rather=20than?= =?UTF-8?q?=20the=20count=20of=20rows=20in=20the=20initial=20indexPath?= =?UTF-8?q?=E2=80=99s=20section.=20Fix=20#1987.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/SceneCoordinator.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 4408a5f30..3773f99b6 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -1693,16 +1693,16 @@ private extension SceneCoordinator { return 0 } }() - - for j in startingRow.. Date: Thu, 16 Apr 2020 13:24:20 -0700 Subject: [PATCH 4/5] Remove Omni from credits and about text. --- Mac/Resources/Credits.rtf | 8 ++++---- iOS/Resources/Thanks.rtf | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Mac/Resources/Credits.rtf b/Mac/Resources/Credits.rtf index 0f51c03ba..d3d86734d 100644 --- a/Mac/Resources/Credits.rtf +++ b/Mac/Resources/Credits.rtf @@ -1,5 +1,5 @@ -{\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf600 -{\fonttbl\f0\fnil\fcharset0 LucidaGrande-Bold;\f1\fnil\fcharset0 LucidaGrande;} +{\rtf1\ansi\ansicpg1252\cocoartf2511 +\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 LucidaGrande-Bold;\f1\fnil\fcharset0 LucidaGrande;} {\colortbl;\red255\green255\blue255;\red0\green0\blue0;} {\*\expandedcolortbl;;\cssrgb\c0\c0\c0\cname textColor;} \vieww14060\viewh15660\viewkind0 @@ -20,7 +20,7 @@ By Brent Simmons\ \pard\pardeftab720\li360\sa60\partightenfactor0 \cf2 App icon and most other icons: {\field{\*\fldinst{HYPERLINK "https://twitter.com/BradEllis"}}{\fldrslt Brad Ellis}}\ Major code contributors: {\field{\*\fldinst{HYPERLINK "https://github.com/olofhellman"}}{\fldrslt Olof Hellman}}, {\field{\*\fldinst{HYPERLINK "https://github.com/vincode-io"}}{\fldrslt Maurice Parker}}, and {\field{\*\fldinst{HYPERLINK "https://github.com/danielpunkass"}}{\fldrslt Daniel Jalkut\ -}}Help book almost entirely by {\field{\*\fldinst{HYPERLINK "https://nostodnayr.net/"}}{\fldrslt Ryan Dotson}}\ +}}Help book by {\field{\*\fldinst{HYPERLINK "https://nostodnayr.net/"}}{\fldrslt Ryan Dotson}}\ Difficult infrastructure by {\field{\*\fldinst{HYPERLINK "https://rhonabwy.com/"}}{\fldrslt Joe Heck}}\ \ \pard\pardeftab720\sa60\partightenfactor0 @@ -37,7 +37,7 @@ Difficult infrastructure by {\field{\*\fldinst{HYPERLINK "https://rhonabwy.com/" \f0\b \cf2 Thanks:\ \pard\pardeftab720\li360\sa60\partightenfactor0 -\f1\b0 \cf2 Thanks to Sheila and my family; thanks to my friends in Seattle and around the globe; thanks to my co-workers and friends at {\field{\*\fldinst{HYPERLINK "https://www.omnigroup.com/"}}{\fldrslt The Omni Group}}; thanks to the ever-patient and ever-awesome NetNewsWire beta testers. Thanks to {\field{\*\fldinst{HYPERLINK "https://github.com/"}}{\fldrslt GitHub}}, {\field{\*\fldinst{HYPERLINK "https://slack.com/"}}{\fldrslt Slack}}, and {\field{\*\fldinst{HYPERLINK "https://circleci.com/"}}{\fldrslt CircleCI}} for making open source collaboration easy and fun.\ +\f1\b0 \cf2 Thanks to Sheila and my family; thanks to my friends in Seattle and around the globe; thanks to the ever-patient and ever-awesome NetNewsWire beta testers. Thanks to {\field{\*\fldinst{HYPERLINK "https://github.com/"}}{\fldrslt GitHub}} and {\field{\*\fldinst{HYPERLINK "https://slack.com/"}}{\fldrslt Slack}} for making open source collaboration easy and fun.\ \ \pard\pardeftab720\sa60\partightenfactor0 diff --git a/iOS/Resources/Thanks.rtf b/iOS/Resources/Thanks.rtf index a79881b62..c3cde3b4c 100644 --- a/iOS/Resources/Thanks.rtf +++ b/iOS/Resources/Thanks.rtf @@ -6,6 +6,6 @@ \deftab720 \pard\pardeftab720\li365\fi-366\sa60\partightenfactor0 -\f0\fs22 \cf2 Thanks to Sheila and my family; thanks to my friends in Seattle and around the globe; thanks to my co-workers and friends at {\field{\*\fldinst{HYPERLINK "https://www.omnigroup.com"}}{\fldrslt the Omni Group}}; thanks to the ever-patient and ever-awesome NetNewsWire beta testers. \ +\f0\fs22 \cf2 Thanks to Sheila and my family; thanks to my friends in Seattle and around the globe; thanks to the ever-patient and ever-awesome NetNewsWire beta testers. \ \pard\tx0\pardeftab720\li360\fi-361\sa60\partightenfactor0 \cf2 Thanks to {\field{\*\fldinst{HYPERLINK "https://shapeof.com/"}}{\fldrslt Gus Mueller}} for {\field{\*\fldinst{HYPERLINK "https://github.com/ccgus/fmdb"}}{\fldrslt FMDB}} by {\field{\*\fldinst{HYPERLINK "http://flyingmeat.com/"}}{\fldrslt Flying Meat Software}}. Thanks to {\field{\*\fldinst{HYPERLINK "https://github.com"}}{\fldrslt GitHub}} and {\field{\*\fldinst{HYPERLINK "https://slack.com"}}{\fldrslt Slack}} for making open source collaboration easy and fun. Thanks to {\field{\*\fldinst{HYPERLINK "https://benubois.com/"}}{\fldrslt Ben Ubois}} at {\field{\*\fldinst{HYPERLINK "https://feedbin.com/"}}{\fldrslt Feedbin}} for all the extra help with syncing and article rendering \'97\'a0and for {\field{\*\fldinst{HYPERLINK "https://feedbin.com/blog/2019/03/11/the-future-of-full-content/"}}{\fldrslt hosting the server for the Reader view}}.} \ No newline at end of file From 928e93c0745a213ccf6cf671ef74f382b06327da Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Thu, 16 Apr 2020 16:26:53 -0500 Subject: [PATCH 5/5] Remove web view recycling as this seems to contribute to the blank article problem on iPadOS --- iOS/Article/WebViewController.swift | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/iOS/Article/WebViewController.swift b/iOS/Article/WebViewController.swift index 6b3fc0926..d8e59ff05 100644 --- a/iOS/Article/WebViewController.swift +++ b/iOS/Article/WebViewController.swift @@ -62,10 +62,6 @@ class WebViewController: UIViewController { let scrollPositionQueue = CoalescingQueue(name: "Article Scroll Position", interval: 0.3, maxInterval: 1.0) var windowScrollY = 0 - deinit { - recycleWebView(webView) - } - override func viewDidLoad() { super.viewDidLoad() @@ -451,23 +447,6 @@ private extension WebViewController { } - func recycleWebView(_ webView: PreloadedWebView?) { - guard let webView = webView else { return } - - webView.removeFromSuperview() - stopMediaPlayback(webView) - cancelImageLoad(webView) - - webView.navigationDelegate = nil - webView.uiDelegate = nil - webView.scrollView.delegate = nil - webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasClicked) - webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasShown) - webView.interactions.removeAll() - - coordinator.webViewProvider.enqueueWebView(webView) - } - func renderPage(_ webView: PreloadedWebView?) { guard let webView = webView else { return }