Merge branch 'master' into extension-point
This commit is contained in:
commit
530f06dfca
|
@ -736,7 +736,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
webFeed.takeSettings(from: parsedFeed)
|
||||
let parsedItems = parsedFeed.items
|
||||
guard !parsedItems.isEmpty else {
|
||||
completion(.success(NewAndUpdatedArticles()))
|
||||
completion(.success(ArticleChanges()))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1303,30 +1303,39 @@ private extension Account {
|
|||
}
|
||||
}
|
||||
|
||||
func sendNotificationAbout(_ newAndUpdatedArticles: NewAndUpdatedArticles) {
|
||||
func sendNotificationAbout(_ articleChanges: ArticleChanges) {
|
||||
var webFeeds = Set<WebFeed>()
|
||||
|
||||
if let newArticles = newAndUpdatedArticles.newArticles {
|
||||
if let newArticles = articleChanges.newArticles {
|
||||
webFeeds.formUnion(Set(newArticles.compactMap { $0.webFeed }))
|
||||
}
|
||||
if let updatedArticles = newAndUpdatedArticles.updatedArticles {
|
||||
if let updatedArticles = articleChanges.updatedArticles {
|
||||
webFeeds.formUnion(Set(updatedArticles.compactMap { $0.webFeed }))
|
||||
}
|
||||
|
||||
var shouldSendNotification = false
|
||||
var shouldUpdateUnreadCounts = false
|
||||
var userInfo = [String: Any]()
|
||||
|
||||
if let newArticles = newAndUpdatedArticles.newArticles, !newArticles.isEmpty {
|
||||
if let newArticles = articleChanges.newArticles, !newArticles.isEmpty {
|
||||
shouldSendNotification = true
|
||||
shouldUpdateUnreadCounts = true
|
||||
userInfo[UserInfoKey.newArticles] = newArticles
|
||||
self.updateUnreadCounts(for: webFeeds)
|
||||
}
|
||||
|
||||
if let updatedArticles = newAndUpdatedArticles.updatedArticles, !updatedArticles.isEmpty {
|
||||
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.webFeeds] = webFeeds
|
||||
NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo)
|
||||
|
|
|
@ -726,13 +726,14 @@ private extension CloudKitAccountDelegate {
|
|||
|
||||
extension CloudKitAccountDelegate: LocalAccountRefresherDelegate {
|
||||
|
||||
func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess newAndUpdatedArticles: NewAndUpdatedArticles, completion: @escaping () -> Void) {
|
||||
if let newArticles = newAndUpdatedArticles.newArticles {
|
||||
articlesZone.sendNewArticles(newArticles) { _ in
|
||||
func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess articleChanges: ArticleChanges, completion: @escaping () -> Void) {
|
||||
let newArticles = articleChanges.newArticles ?? Set<Article>()
|
||||
let deletedArticles = articleChanges.deletedArticles ?? Set<Article>()
|
||||
|
||||
articlesZone.deleteArticles(deletedArticles) { _ in
|
||||
self.articlesZone.sendNewArticles(newArticles) { _ in
|
||||
completion()
|
||||
}
|
||||
} else {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -82,10 +82,25 @@ final class CloudKitArticlesZone: CloudKitZone {
|
|||
}
|
||||
|
||||
func sendNewArticles(_ articles: Set<Article>, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
guard !articles.isEmpty else {
|
||||
completion(.success(()))
|
||||
return
|
||||
}
|
||||
|
||||
let records = makeNewStatusRecords(articles)
|
||||
saveIfNew(records, completion: completion)
|
||||
}
|
||||
|
||||
func deleteArticles(_ articles: Set<Article>, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
guard !articles.isEmpty else {
|
||||
completion(.success(()))
|
||||
return
|
||||
}
|
||||
|
||||
let recordIDs = articles.map { CKRecord.ID(recordName: $0.articleID, zoneID: Self.zoneID) }
|
||||
delete(recordIDs: recordIDs, completion: completion)
|
||||
}
|
||||
|
||||
func sendArticleStatus(_ syncStatuses: [SyncStatus], articles: Set<Article>, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
|
||||
var records = makeStatusRecords(syncStatuses, articles)
|
||||
|
|
|
@ -202,7 +202,7 @@ private extension CloudKitArticlesZoneDelegate {
|
|||
|
||||
extension CloudKitArticlesZoneDelegate: LocalAccountRefresherDelegate {
|
||||
|
||||
func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess newAndUpdatedArticles: NewAndUpdatedArticles, completion: @escaping () -> Void) {
|
||||
func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess articleChanges: ArticleChanges, completion: @escaping () -> Void) {
|
||||
completion()
|
||||
}
|
||||
|
||||
|
|
|
@ -371,6 +371,10 @@ extension CloudKitZone {
|
|||
modify(recordsToSave: [], recordIDsToDelete: [recordID], completion: completion)
|
||||
}
|
||||
|
||||
func delete(recordIDs: [CKRecord.ID], completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
modify(recordsToSave: [], recordIDsToDelete: recordIDs, completion: completion)
|
||||
}
|
||||
|
||||
/// Delete a CKRecord using its externalID
|
||||
func delete(externalID: String?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let externalID = externalID else {
|
||||
|
|
|
@ -235,7 +235,7 @@ final class LocalAccountDelegate: AccountDelegate {
|
|||
|
||||
extension LocalAccountDelegate: LocalAccountRefresherDelegate {
|
||||
|
||||
func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess newAndUpdatedArticles: NewAndUpdatedArticles, completion: @escaping () -> Void) {
|
||||
func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess articleChanges: ArticleChanges, completion: @escaping () -> Void) {
|
||||
completion()
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import Articles
|
|||
import ArticlesDatabase
|
||||
|
||||
protocol LocalAccountRefresherDelegate {
|
||||
func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess: NewAndUpdatedArticles, completion: @escaping () -> Void)
|
||||
func localAccountRefresher(_ refresher: LocalAccountRefresher, didProcess: ArticleChanges, completion: @escaping () -> Void)
|
||||
func localAccountRefresher(_ refresher: LocalAccountRefresher, requestCompletedFor: WebFeed)
|
||||
func localAccountRefresherDidFinish(_ refresher: LocalAccountRefresher)
|
||||
}
|
||||
|
|
|
@ -24,22 +24,26 @@ public typealias UnreadCountDictionaryCompletionBlock = (UnreadCountDictionaryCo
|
|||
public typealias SingleUnreadCountResult = Result<Int, DatabaseError>
|
||||
public typealias SingleUnreadCountCompletionBlock = (SingleUnreadCountResult) -> Void
|
||||
|
||||
public struct NewAndUpdatedArticles {
|
||||
public struct ArticleChanges {
|
||||
public let newArticles: Set<Article>?
|
||||
public let updatedArticles: Set<Article>?
|
||||
|
||||
public let deletedArticles: Set<Article>?
|
||||
|
||||
public init() {
|
||||
self.newArticles = Set<Article>()
|
||||
self.updatedArticles = Set<Article>()
|
||||
self.deletedArticles = Set<Article>()
|
||||
}
|
||||
|
||||
public init(newArticles: Set<Article>?, updatedArticles: Set<Article>?) {
|
||||
public init(newArticles: Set<Article>?, updatedArticles: Set<Article>?, deletedArticles: Set<Article>?) {
|
||||
self.newArticles = newArticles
|
||||
self.updatedArticles = updatedArticles
|
||||
self.deletedArticles = deletedArticles
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public typealias UpdateArticlesResult = Result<NewAndUpdatedArticles, DatabaseError>
|
||||
public typealias UpdateArticlesResult = Result<ArticleChanges, DatabaseError>
|
||||
public typealias UpdateArticlesCompletionBlock = (UpdateArticlesResult) -> Void
|
||||
|
||||
public typealias ArticleSetResult = Result<Set<Article>, DatabaseError>
|
||||
|
|
|
@ -175,7 +175,7 @@ final class ArticlesTable: DatabaseTable {
|
|||
func update(_ parsedItems: Set<ParsedItem>, _ webFeedID: String, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
precondition(retentionStyle == .feedBased)
|
||||
if parsedItems.isEmpty {
|
||||
callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||
callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -199,7 +199,7 @@ final class ArticlesTable: DatabaseTable {
|
|||
|
||||
let incomingArticles = Article.articlesWithParsedItems(parsedItems, webFeedID, self.accountID, statusesDictionary) //2
|
||||
if incomingArticles.isEmpty {
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -209,13 +209,19 @@ final class ArticlesTable: DatabaseTable {
|
|||
let newArticles = self.findAndSaveNewArticles(incomingArticles, fetchedArticlesDictionary, database) //5
|
||||
let updatedArticles = self.findAndSaveUpdatedArticles(incomingArticles, fetchedArticlesDictionary, database) //6
|
||||
|
||||
self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, completion) //7
|
||||
// Articles to delete are 1) no longer in feed and 2) older than 30 days.
|
||||
let cutoffDate = Date().bySubtracting(days: 30)
|
||||
let articlesToDelete = fetchedArticles.filter { (article) -> Bool in
|
||||
return article.status.dateArrived < cutoffDate && !articleIDs.contains(article.articleID)
|
||||
}
|
||||
|
||||
self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, articlesToDelete, completion) //7
|
||||
|
||||
self.addArticlesToCache(newArticles)
|
||||
self.addArticlesToCache(updatedArticles)
|
||||
|
||||
// 8. Delete articles no longer in feed.
|
||||
let articleIDsToDelete = fetchedArticles.articleIDs().filter { !(articleIDs.contains($0)) }
|
||||
let articleIDsToDelete = articlesToDelete.articleIDs()
|
||||
if !articleIDsToDelete.isEmpty {
|
||||
self.removeArticles(articleIDsToDelete, database)
|
||||
self.removeArticleIDsFromCache(articleIDsToDelete)
|
||||
|
@ -244,7 +250,7 @@ final class ArticlesTable: DatabaseTable {
|
|||
func update(_ webFeedIDsAndItems: [String: Set<ParsedItem>], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
precondition(retentionStyle == .syncSystem)
|
||||
if webFeedIDsAndItems.isEmpty {
|
||||
callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||
callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -270,13 +276,13 @@ final class ArticlesTable: DatabaseTable {
|
|||
|
||||
let allIncomingArticles = Article.articlesWithWebFeedIDsAndItems(webFeedIDsAndItems, self.accountID, statusesDictionary) //2
|
||||
if allIncomingArticles.isEmpty {
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
|
||||
return
|
||||
}
|
||||
|
||||
let incomingArticles = self.filterIncomingArticles(allIncomingArticles) //3
|
||||
if incomingArticles.isEmpty {
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -287,7 +293,7 @@ final class ArticlesTable: DatabaseTable {
|
|||
let newArticles = self.findAndSaveNewArticles(incomingArticles, fetchedArticlesDictionary, database) //5
|
||||
let updatedArticles = self.findAndSaveUpdatedArticles(incomingArticles, fetchedArticlesDictionary, database) //6
|
||||
|
||||
self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, completion) //7
|
||||
self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, nil, completion) //7
|
||||
|
||||
self.addArticlesToCache(newArticles)
|
||||
self.addArticlesToCache(updatedArticles)
|
||||
|
@ -849,10 +855,10 @@ private extension ArticlesTable {
|
|||
|
||||
// MARK: - Saving Parsed Items
|
||||
|
||||
func callUpdateArticlesCompletionBlock(_ newArticles: Set<Article>?, _ updatedArticles: Set<Article>?, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
let newAndUpdatedArticles = NewAndUpdatedArticles(newArticles: newArticles, updatedArticles: updatedArticles)
|
||||
func callUpdateArticlesCompletionBlock(_ newArticles: Set<Article>?, _ updatedArticles: Set<Article>?, _ deletedArticles: Set<Article>?, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
let articleChanges = ArticleChanges(newArticles: newArticles, updatedArticles: updatedArticles, deletedArticles: deletedArticles)
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(newAndUpdatedArticles))
|
||||
completion(.success(articleChanges))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,10 +28,6 @@ struct HTMLMetadataDownloader {
|
|||
return
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
appDelegate.logMessage("Error downloading metadata at \(url): \(error)", type: .warning)
|
||||
}
|
||||
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
// High Level Settings common to both the iOS application and any extensions we bundle with it
|
||||
MARKETING_VERSION = 5.0.1
|
||||
CURRENT_PROJECT_VERSION = 40
|
||||
CURRENT_PROJECT_VERSION = 41
|
||||
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
|
||||
|
|
Loading…
Reference in New Issue