From 9c571271e696493823ff9d7e0f1331dd6304fc15 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 19 Nov 2017 15:40:02 -0800 Subject: [PATCH] =?UTF-8?q?Create=20SmartFeed=20class.=20It=E2=80=99s=20fo?= =?UTF-8?q?r=20Unread=20and=20Starred=20pseudo-feeds=20=E2=80=94=20and=20w?= =?UTF-8?q?ill=20also=20be=20used=20later=20on=20by=20predicate-based=20sm?= =?UTF-8?q?art=20feeds.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Evergreen.xcodeproj/project.pbxproj | 16 ++++++++--- Evergreen/AppDelegate.swift | 5 +++- .../{TodayFeed.swift => SmartFeed.swift} | 27 +++++++++++++------ .../PseudoFeeds/StarredFeedDelegate.swift | 22 +++++++++++++++ Evergreen/PseudoFeeds/TodayFeedDelegate.swift | 22 +++++++++++++++ Frameworks/Account/Account.swift | 5 ++++ Frameworks/Database/ArticlesTable.swift | 17 ++++++++++++ Frameworks/Database/CreateStatements.sql | 2 ++ Frameworks/Database/Database.swift | 5 ++++ 9 files changed, 108 insertions(+), 13 deletions(-) rename Evergreen/PseudoFeeds/{TodayFeed.swift => SmartFeed.swift} (73%) create mode 100644 Evergreen/PseudoFeeds/StarredFeedDelegate.swift create mode 100644 Evergreen/PseudoFeeds/TodayFeedDelegate.swift diff --git a/Evergreen.xcodeproj/project.pbxproj b/Evergreen.xcodeproj/project.pbxproj index 92f627412..b6a6fda8e 100644 --- a/Evergreen.xcodeproj/project.pbxproj +++ b/Evergreen.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ 842E45E51ED8C6B7000A8B52 /* MainWindowSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45E41ED8C6B7000A8B52 /* MainWindowSplitView.swift */; }; 842E45E71ED8C747000A8B52 /* DB5.plist in Resources */ = {isa = PBXBuildFile; fileRef = 842E45E61ED8C747000A8B52 /* DB5.plist */; }; 84513F901FAA63950023A1A9 /* FeedListControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84513F8F1FAA63950023A1A9 /* FeedListControlsView.swift */; }; + 845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */; }; + 845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7C01FC2488C00854A1F /* SmartFeed.swift */; }; 845F52ED1FB2B9FC00C10BF0 /* FeedPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845F52EC1FB2B9FC00C10BF0 /* FeedPasteboardWriter.swift */; }; 846E773D1F6EF67A00A165E2 /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 846E773A1F6EF5D700A165E2 /* Account.framework */; }; 846E773E1F6EF67A00A165E2 /* Account.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 846E773A1F6EF5D700A165E2 /* Account.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -99,7 +101,7 @@ 84F204DE1FAACB8B0076E152 /* FeedListTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DD1FAACB8B0076E152 /* FeedListTimelineViewController.swift */; }; 84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; 84F2D5371FC22FCC00998D64 /* PseudoFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */; }; - 84F2D5381FC22FCC00998D64 /* TodayFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5361FC22FCB00998D64 /* TodayFeed.swift */; }; + 84F2D5381FC22FCC00998D64 /* TodayFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5361FC22FCB00998D64 /* TodayFeedDelegate.swift */; }; 84F2D53A1FC2308B00998D64 /* UnreadFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */; }; 84FB9A2F1EDCD6C4003D53B9 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; }; 84FB9A301EDCD6C4003D53B9 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -400,6 +402,8 @@ 842E45E61ED8C747000A8B52 /* DB5.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = DB5.plist; path = Evergreen/Resources/DB5.plist; sourceTree = ""; }; 84513F8F1FAA63950023A1A9 /* FeedListControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListControlsView.swift; sourceTree = ""; }; 845B14A51FC2299E0013CF92 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarredFeedDelegate.swift; sourceTree = ""; }; + 845EE7C01FC2488C00854A1F /* SmartFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeed.swift; sourceTree = ""; }; 845F52EC1FB2B9FC00C10BF0 /* FeedPasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedPasteboardWriter.swift; sourceTree = ""; }; 846E77161F6EF5D000A165E2 /* Database.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Database.xcodeproj; path = Frameworks/Database/Database.xcodeproj; sourceTree = ""; }; 846E77301F6EF5D600A165E2 /* Account.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Account.xcodeproj; path = Frameworks/Account/Account.xcodeproj; sourceTree = ""; }; @@ -479,7 +483,7 @@ 84F204DD1FAACB8B0076E152 /* FeedListTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListTimelineViewController.swift; sourceTree = ""; }; 84F204DF1FAACBB30076E152 /* ArticleArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleArray.swift; sourceTree = ""; }; 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PseudoFeed.swift; sourceTree = ""; }; - 84F2D5361FC22FCB00998D64 /* TodayFeed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TodayFeed.swift; sourceTree = ""; }; + 84F2D5361FC22FCB00998D64 /* TodayFeedDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TodayFeedDelegate.swift; sourceTree = ""; }; 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadFeed.swift; sourceTree = ""; }; 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Frameworks/Vendor/Sparkle.framework; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ @@ -900,8 +904,10 @@ isa = PBXGroup; children = ( 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */, - 84F2D5361FC22FCB00998D64 /* TodayFeed.swift */, 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */, + 845EE7C01FC2488C00854A1F /* SmartFeed.swift */, + 84F2D5361FC22FCB00998D64 /* TodayFeedDelegate.swift */, + 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */, ); name = PseudoFeeds; path = Evergreen/PseudoFeeds; @@ -1285,6 +1291,7 @@ 849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */, 849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */, 84F2D5371FC22FCC00998D64 /* PseudoFeed.swift in Sources */, + 845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */, 84702AA41FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift in Sources */, 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */, 849A97651ED9EB96007D329B /* SidebarTreeControllerDelegate.swift in Sources */, @@ -1299,10 +1306,11 @@ 849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */, 849A97791ED9EC04007D329B /* TimelineStringUtilities.swift in Sources */, 84F204CE1FAACB660076E152 /* FeedListViewController.swift in Sources */, + 845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */, 849A97981ED9EFAA007D329B /* Node-Extensions.swift in Sources */, 849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */, 849A97831ED9EC63007D329B /* StatusBarView.swift in Sources */, - 84F2D5381FC22FCC00998D64 /* TodayFeed.swift in Sources */, + 84F2D5381FC22FCC00998D64 /* TodayFeedDelegate.swift in Sources */, 849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */, 849A97921ED9EF65007D329B /* IndeterminateProgressWindowController.swift in Sources */, 849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */, diff --git a/Evergreen/AppDelegate.swift b/Evergreen/AppDelegate.swift index f1afe9f31..dae4550ca 100644 --- a/Evergreen/AppDelegate.swift +++ b/Evergreen/AppDelegate.swift @@ -97,7 +97,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, currentTheme = themeLoader.defaultTheme - pseudoFeeds = [TodayFeed(), UnreadFeed()] + let todayFeed = SmartFeed(delegate: TodayFeedDelegate()) + let unreadFeed = UnreadFeed() + let starredFeed = SmartFeed(delegate: StarredFeedDelegate()) + pseudoFeeds = [todayFeed, unreadFeed, starredFeed] createAndShowMainWindow() diff --git a/Evergreen/PseudoFeeds/TodayFeed.swift b/Evergreen/PseudoFeeds/SmartFeed.swift similarity index 73% rename from Evergreen/PseudoFeeds/TodayFeed.swift rename to Evergreen/PseudoFeeds/SmartFeed.swift index 89b2ab12f..8635b5a53 100644 --- a/Evergreen/PseudoFeeds/TodayFeed.swift +++ b/Evergreen/PseudoFeeds/SmartFeed.swift @@ -1,5 +1,5 @@ // -// TodayFeed.swift +// SmartFeed.swift // Evergreen // // Created by Brent Simmons on 11/19/17. @@ -7,12 +7,22 @@ // import Foundation +import RSCore import Data import Account -final class TodayFeed: PseudoFeed { +protocol SmartFeedDelegate: DisplayNameProvider { - let nameForDisplay = NSLocalizedString("Today", comment: "Today pseudo-feed title") + func fetchUnreadCount(for: Account, callback: @escaping (Int) -> Void) +} + +final class SmartFeed: PseudoFeed { + + var nameForDisplay: String { + get { + return delegate.nameForDisplay + } + } var unreadCount = 0 { didSet { @@ -22,11 +32,13 @@ final class TodayFeed: PseudoFeed { } } + private let delegate: SmartFeedDelegate private var timer: Timer? private var unreadCounts = [Account: Int]() - init() { + init(delegate: SmartFeedDelegate) { + self.delegate = delegate NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) startTimer() // Fetch unread count at startup } @@ -39,13 +51,13 @@ final class TodayFeed: PseudoFeed { } } -private extension TodayFeed { +private extension SmartFeed { // MARK: - Unread Counts private func fetchUnreadCount(for account: Account) { - account.fetchUnreadCountForToday { (accountUnreadCount) in + delegate.fetchUnreadCount(for: account) { (accountUnreadCount) in self.unreadCounts[account] = accountUnreadCount self.updateUnreadCount() } @@ -82,10 +94,9 @@ private extension TodayFeed { stopTimer() - timer = Timer.scheduledTimer(withTimeInterval: TodayFeed.fetchCoalescingDelay, repeats: false, block: { (timer) in + timer = Timer.scheduledTimer(withTimeInterval: SmartFeed.fetchCoalescingDelay, repeats: false, block: { (timer) in self.fetchUnreadCounts() self.stopTimer() }) } } - diff --git a/Evergreen/PseudoFeeds/StarredFeedDelegate.swift b/Evergreen/PseudoFeeds/StarredFeedDelegate.swift new file mode 100644 index 000000000..424ad530a --- /dev/null +++ b/Evergreen/PseudoFeeds/StarredFeedDelegate.swift @@ -0,0 +1,22 @@ +// +// StarredFeedDelegate.swift +// Evergreen +// +// Created by Brent Simmons on 11/19/17. +// Copyright © 2017 Ranchero Software. All rights reserved. +// + +import Foundation +import Data +import Account + + +struct StarredFeedDelegate: SmartFeedDelegate { + + let nameForDisplay = NSLocalizedString("Starred", comment: "Starred pseudo-feed title") + + func fetchUnreadCount(for account: Account, callback: @escaping (Int) -> Void) { + + account.fetchUnreadCountForStarredArticles(callback) + } +} diff --git a/Evergreen/PseudoFeeds/TodayFeedDelegate.swift b/Evergreen/PseudoFeeds/TodayFeedDelegate.swift new file mode 100644 index 000000000..58ae61095 --- /dev/null +++ b/Evergreen/PseudoFeeds/TodayFeedDelegate.swift @@ -0,0 +1,22 @@ +// +// TodayFeedDelegate.swift +// Evergreen +// +// Created by Brent Simmons on 11/19/17. +// Copyright © 2017 Ranchero Software. All rights reserved. +// + +import Foundation +import Data +import Account + +struct TodayFeedDelegate: SmartFeedDelegate { + + let nameForDisplay = NSLocalizedString("Today", comment: "Today pseudo-feed title") + + func fetchUnreadCount(for account: Account, callback: @escaping (Int) -> Void) { + + account.fetchUnreadCountForToday(callback) + } +} + diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index cd0995185..034db558b 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -330,6 +330,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, database.fetchUnreadCount(for: flattenedFeeds(), since: startOfToday, callback: callback) } + public func fetchUnreadCountForStarredArticles(_ callback: @escaping (Int) -> Void) { + + database.fetchStarredAndUnreadCount(for: flattenedFeeds(), callback: callback) + } + // MARK: - Notifications @objc func downloadProgressDidChange(_ note: Notification) { diff --git a/Frameworks/Database/ArticlesTable.swift b/Frameworks/Database/ArticlesTable.swift index d0d88384f..1f0d8eb0f 100644 --- a/Frameworks/Database/ArticlesTable.swift +++ b/Frameworks/Database/ArticlesTable.swift @@ -164,6 +164,23 @@ final class ArticlesTable: DatabaseTable { } } + func fetchStarredAndUnreadCount(_ feeds: Set, _ callback: @escaping (Int) -> Void) { + + let feedIDs = feeds.feedIDs() + queue.fetch { (database) in + + 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 and userDeleted=0;" + let parameters = Array(feedIDs) as [Any] + + let unreadCount = self.numberWithSQLAndParameters(sql, parameters, in: database) + + DispatchQueue.main.async() { + callback(unreadCount) + } + } + } + // MARK: Status func mark(_ articles: Set
, _ statusKey: ArticleStatus.Key, _ flag: Bool) -> Set? { diff --git a/Frameworks/Database/CreateStatements.sql b/Frameworks/Database/CreateStatements.sql index 31ea1bfe9..6ffecce8c 100644 --- a/Frameworks/Database/CreateStatements.sql +++ b/Frameworks/Database/CreateStatements.sql @@ -15,3 +15,5 @@ CREATE INDEX if not EXISTS articles_feedID_index on articles (feedID); CREATE INDEX if not EXISTS tags_tagName_index on tags (tagName COLLATE NOCASE); CREATE INDEX if not EXISTS statuses_read_index on statuses (read); +CREATE INDEX if not EXISTS statuses_starred_index on statuses (starred); + diff --git a/Frameworks/Database/Database.swift b/Frameworks/Database/Database.swift index e37a50d20..2365274bf 100644 --- a/Frameworks/Database/Database.swift +++ b/Frameworks/Database/Database.swift @@ -66,6 +66,11 @@ public final class Database { articlesTable.fetchUnreadCount(feeds, since, callback) } + public func fetchStarredAndUnreadCount(for feeds: Set, callback: @escaping (Int) -> Void) { + + articlesTable.fetchStarredAndUnreadCount(feeds, callback) + } + // MARK: - Saving and Updating Articles public func update(feed: Feed, parsedFeed: ParsedFeed, completion: @escaping UpdateArticlesWithFeedCompletionBlock) {