mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Merge branch 'master' into feature/feed-wrangler
This commit is contained in:
@@ -47,7 +47,7 @@ public enum FetchType {
|
||||
case starred
|
||||
case unread
|
||||
case today
|
||||
case unreadForFolder(Folder)
|
||||
case folder(Folder, Bool)
|
||||
case webFeed(WebFeed)
|
||||
case articleIDs(Set<String>)
|
||||
case search(String)
|
||||
@@ -84,6 +84,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
public var isDeleted = false
|
||||
|
||||
public var containerID: ContainerIdentifier? {
|
||||
return ContainerIdentifier.account(accountID)
|
||||
}
|
||||
|
||||
public var account: Account? {
|
||||
return self
|
||||
}
|
||||
@@ -594,8 +598,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return fetchUnreadArticles()
|
||||
case .today:
|
||||
return fetchTodayArticles()
|
||||
case .unreadForFolder(let folder):
|
||||
return fetchArticles(folder: folder)
|
||||
case .folder(let folder, let readFilter):
|
||||
if readFilter {
|
||||
return fetchUnreadArticles(folder: folder)
|
||||
} else {
|
||||
return fetchArticles(folder: folder)
|
||||
}
|
||||
case .webFeed(let webFeed):
|
||||
return fetchArticles(webFeed: webFeed)
|
||||
case .articleIDs(let articleIDs):
|
||||
@@ -615,8 +623,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
fetchUnreadArticlesAsync(callback)
|
||||
case .today:
|
||||
fetchTodayArticlesAsync(callback)
|
||||
case .unreadForFolder(let folder):
|
||||
fetchArticlesAsync(folder: folder, callback)
|
||||
case .folder(let folder, let readFilter):
|
||||
if readFilter {
|
||||
return fetchUnreadArticlesAsync(folder: folder, callback)
|
||||
} else {
|
||||
return fetchArticlesAsync(folder: folder, callback)
|
||||
}
|
||||
case .webFeed(let webFeed):
|
||||
fetchArticlesAsync(webFeed: webFeed, callback)
|
||||
case .articleIDs(let articleIDs):
|
||||
@@ -892,10 +904,18 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchArticles(folder: Folder) -> Set<Article> {
|
||||
return fetchUnreadArticles(forContainer: folder)
|
||||
return fetchArticles(forContainer: folder)
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(folder: Folder, _ callback: @escaping ArticleSetBlock) {
|
||||
fetchArticlesAsync(forContainer: folder, callback)
|
||||
}
|
||||
|
||||
func fetchUnreadArticles(folder: Folder) -> Set<Article> {
|
||||
return fetchUnreadArticles(forContainer: folder)
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesAsync(folder: Folder, _ callback: @escaping ArticleSetBlock) {
|
||||
fetchUnreadArticlesAsync(forContainer: folder, callback)
|
||||
}
|
||||
|
||||
@@ -950,6 +970,21 @@ private extension Account {
|
||||
}
|
||||
|
||||
|
||||
func fetchArticles(forContainer container: Container) -> Set<Article> {
|
||||
let feeds = container.flattenedWebFeeds()
|
||||
let articles = database.fetchArticles(feeds.webFeedIDs())
|
||||
validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(forContainer container: Container, _ callback: @escaping ArticleSetBlock) {
|
||||
let webFeeds = container.flattenedWebFeeds()
|
||||
database.fetchArticlesAsync(webFeeds.webFeedIDs()) { [weak self] (articles) in
|
||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)
|
||||
callback(articles)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchUnreadArticles(forContainer container: Container) -> Set<Article> {
|
||||
let feeds = container.flattenedWebFeeds()
|
||||
let articles = database.fetchUnreadArticles(feeds.webFeedIDs())
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
5170743C232AEDB500A461A3 /* OPMLFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5170743B232AEDB500A461A3 /* OPMLFile.swift */; };
|
||||
51BB7B84233531BC008E8144 /* AccountBehaviors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */; };
|
||||
51BC8FCC237EC055004F8B56 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC8FCB237EC055004F8B56 /* Feed.swift */; };
|
||||
51BFDECE238B508D00216323 /* ContainerIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BFDECD238B508D00216323 /* ContainerIdentifier.swift */; };
|
||||
51D58755227F53BE00900287 /* FeedbinTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D58754227F53BE00900287 /* FeedbinTag.swift */; };
|
||||
51D5875A227F630B00900287 /* tags_delete.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58757227F630B00900287 /* tags_delete.json */; };
|
||||
51D5875B227F630B00900287 /* tags_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58758227F630B00900287 /* tags_add.json */; };
|
||||
@@ -255,6 +256,7 @@
|
||||
518B2EA52351306200400001 /* Account_project_test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_test.xcconfig; sourceTree = "<group>"; };
|
||||
51BB7B83233531BC008E8144 /* AccountBehaviors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountBehaviors.swift; sourceTree = "<group>"; };
|
||||
51BC8FCB237EC055004F8B56 /* Feed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = "<group>"; };
|
||||
51BFDECD238B508D00216323 /* ContainerIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerIdentifier.swift; sourceTree = "<group>"; };
|
||||
51D58754227F53BE00900287 /* FeedbinTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinTag.swift; sourceTree = "<group>"; };
|
||||
51D58757227F630B00900287 /* tags_delete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_delete.json; sourceTree = "<group>"; };
|
||||
51D58758227F630B00900287 /* tags_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_add.json; sourceTree = "<group>"; };
|
||||
@@ -572,6 +574,7 @@
|
||||
84F73CF0202788D80000BCEF /* ArticleFetcher.swift */,
|
||||
84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */,
|
||||
8419740D1F6DD25F006346C4 /* Container.swift */,
|
||||
51BFDECD238B508D00216323 /* ContainerIdentifier.swift */,
|
||||
84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */,
|
||||
84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */,
|
||||
51BC8FCB237EC055004F8B56 /* Feed.swift */,
|
||||
@@ -1123,6 +1126,7 @@
|
||||
515E4EB52324FF8C0057B0E7 /* CredentialsManager.swift in Sources */,
|
||||
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */,
|
||||
9E1773D5234570E30056A5A8 /* FeedlyEntryParser.swift in Sources */,
|
||||
51BFDECE238B508D00216323 /* ContainerIdentifier.swift in Sources */,
|
||||
9E1D1555233431A600F4944C /* FeedlyOperation.swift in Sources */,
|
||||
9E1AF38B2353D41A008BD1D5 /* FeedlySetStarredArticlesOperation.swift in Sources */,
|
||||
84F1F06E2243524700DA0616 /* AccountMetadata.swift in Sources */,
|
||||
|
||||
@@ -49,11 +49,20 @@ extension WebFeed: ArticleFetcher {
|
||||
extension Folder: ArticleFetcher {
|
||||
|
||||
public func fetchArticles() -> Set<Article> {
|
||||
return fetchUnreadArticles()
|
||||
guard let account = account else {
|
||||
assertionFailure("Expected folder.account, but got nil.")
|
||||
return Set<Article>()
|
||||
}
|
||||
return account.fetchArticles(.folder(self, false))
|
||||
}
|
||||
|
||||
public func fetchArticlesAsync(_ callback: @escaping ArticleSetBlock) {
|
||||
fetchUnreadArticlesAsync(callback)
|
||||
guard let account = account else {
|
||||
assertionFailure("Expected folder.account, but got nil.")
|
||||
callback(Set<Article>())
|
||||
return
|
||||
}
|
||||
account.fetchArticlesAsync(.folder(self, false), callback)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticles() -> Set<Article> {
|
||||
@@ -61,7 +70,7 @@ extension Folder: ArticleFetcher {
|
||||
assertionFailure("Expected folder.account, but got nil.")
|
||||
return Set<Article>()
|
||||
}
|
||||
return account.fetchArticles(.unreadForFolder(self))
|
||||
return account.fetchArticles(.folder(self, true))
|
||||
}
|
||||
|
||||
public func fetchUnreadArticlesAsync(_ callback: @escaping ArticleSetBlock) {
|
||||
@@ -70,6 +79,6 @@ extension Folder: ArticleFetcher {
|
||||
callback(Set<Article>())
|
||||
return
|
||||
}
|
||||
account.fetchArticlesAsync(.unreadForFolder(self), callback)
|
||||
account.fetchArticlesAsync(.folder(self, true), callback)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ extension Notification.Name {
|
||||
public static let ChildrenDidChange = Notification.Name("ChildrenDidChange")
|
||||
}
|
||||
|
||||
public protocol Container: class {
|
||||
public protocol Container: class, ContainerIdentifiable {
|
||||
|
||||
var account: Account? { get }
|
||||
var topLevelWebFeeds: Set<WebFeed> { get set }
|
||||
|
||||
19
Frameworks/Account/ContainerIdentifier.swift
Normal file
19
Frameworks/Account/ContainerIdentifier.swift
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// ContainerIdentifier.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 11/24/19.
|
||||
// Copyright © 2019 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol ContainerIdentifiable {
|
||||
var containerID: ContainerIdentifier? { get }
|
||||
}
|
||||
|
||||
public enum ContainerIdentifier: Hashable {
|
||||
case smartFeedController
|
||||
case account(String) // accountID
|
||||
case folder(String, String) // accountID, folderName
|
||||
}
|
||||
@@ -9,6 +9,14 @@
|
||||
import Foundation
|
||||
import RSCore
|
||||
|
||||
public enum ReadFilterType {
|
||||
case read
|
||||
case none
|
||||
case alwaysRead
|
||||
}
|
||||
|
||||
public protocol Feed: FeedIdentifiable, ArticleFetcher, DisplayNameProvider, UnreadCountProvider {
|
||||
|
||||
var defaultReadFilterType: ReadFilterType { get }
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,18 @@ import RSCore
|
||||
|
||||
public final class Folder: Feed, Renamable, Container, Hashable {
|
||||
|
||||
public var defaultReadFilterType: ReadFilterType {
|
||||
return .read
|
||||
}
|
||||
|
||||
public var containerID: ContainerIdentifier? {
|
||||
guard let accountID = account?.accountID else {
|
||||
assertionFailure("Expected feed.account, but got nil.")
|
||||
return nil
|
||||
}
|
||||
return ContainerIdentifier.folder(accountID, nameForDisplay)
|
||||
}
|
||||
|
||||
public var feedID: FeedIdentifier? {
|
||||
guard let accountID = account?.accountID else {
|
||||
assertionFailure("Expected feed.account, but got nil.")
|
||||
|
||||
@@ -13,6 +13,10 @@ import Articles
|
||||
|
||||
public final class WebFeed: Feed, Renamable, Hashable {
|
||||
|
||||
public var defaultReadFilterType: ReadFilterType {
|
||||
return .none
|
||||
}
|
||||
|
||||
public var feedID: FeedIdentifier? {
|
||||
guard let accountID = account?.accountID else {
|
||||
assertionFailure("Expected feed.account, but got nil.")
|
||||
|
||||
@@ -48,12 +48,16 @@ public final class ArticlesDatabase {
|
||||
return articlesTable.fetchArticles(webFeedID)
|
||||
}
|
||||
|
||||
public func fetchArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchArticles(webFeedIDs)
|
||||
}
|
||||
|
||||
public func fetchArticles(articleIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchArticles(articleIDs: articleIDs)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticles(_ webFeedID: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchUnreadArticles(webFeedID)
|
||||
public func fetchUnreadArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchUnreadArticles(webFeedIDs)
|
||||
}
|
||||
|
||||
public func fetchTodayArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
@@ -78,6 +82,10 @@ public final class ArticlesDatabase {
|
||||
articlesTable.fetchArticlesAsync(webFeedID, callback)
|
||||
}
|
||||
|
||||
public func fetchArticlesAsync(_ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
articlesTable.fetchArticlesAsync(webFeedIDs, callback)
|
||||
}
|
||||
|
||||
public func fetchArticlesAsync(articleIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
articlesTable.fetchArticlesAsync(articleIDs: articleIDs, callback)
|
||||
}
|
||||
|
||||
@@ -60,6 +60,25 @@ final class ArticlesTable: DatabaseTable {
|
||||
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [webFeedID as AnyObject], withLimits: withLimits)
|
||||
}
|
||||
|
||||
func fetchArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return fetchArticles{ self.fetchArticles(webFeedIDs, $0) }
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(_ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticles(webFeedIDs, $0) }, callback)
|
||||
}
|
||||
|
||||
private func fetchArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0
|
||||
if webFeedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let whereClause = "feedID in \(placeholders)"
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: true)
|
||||
}
|
||||
|
||||
// MARK: - Fetching Articles by articleID
|
||||
|
||||
func fetchArticles(articleIDs: Set<String>) -> Set<Article> {
|
||||
|
||||
Reference in New Issue
Block a user