Merge branch 'master' into extension-point

This commit is contained in:
Maurice Parker 2020-04-23 16:40:41 -05:00
commit 530f06dfca
11 changed files with 70 additions and 35 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,10 +28,6 @@ struct HTMLMetadataDownloader {
return
}
if let error = error {
appDelegate.logMessage("Error downloading metadata at \(url): \(error)", type: .warning)
}
completion(nil)
}
}

View File

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