Collapse the articles and statuses into a single CKRecord
This commit is contained in:
parent
9eef9ea8dc
commit
71788d8f69
|
@ -112,7 +112,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
|||
|
||||
func processWithArticles(_ articles: Set<Article>) {
|
||||
|
||||
self.articlesZone.modifyArticlesAndStatuses(syncStatuses, articles: articles) { result in
|
||||
self.articlesZone.modifyArticles(syncStatuses, articles: articles) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.database.deleteSelectedForProcessing(syncStatuses.map({ $0.articleID }) )
|
||||
|
@ -151,7 +151,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
|||
func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
os_log(.debug, log: log, "Refreshing article statuses...")
|
||||
|
||||
articlesZone.refreshArticlesAndStatuses() { result in
|
||||
articlesZone.refreshArticles() { result in
|
||||
os_log(.debug, log: self.log, "Done refreshing article statuses.")
|
||||
switch result {
|
||||
case .success:
|
||||
|
@ -616,9 +616,9 @@ private extension CloudKitAccountDelegate {
|
|||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
|
||||
self.articlesZone.deleteArticlesAndStatuses(deletedArticles) { _ in
|
||||
self.articlesZone.deleteArticles(deletedArticles) { _ in
|
||||
self.refreshProgress.completeTask()
|
||||
self.articlesZone.saveNewArticlesAndStatuses(newArticles) { _ in
|
||||
self.articlesZone.saveNewArticles(newArticles) { _ in
|
||||
self.refreshProgress.completeTask()
|
||||
completion()
|
||||
}
|
||||
|
@ -665,9 +665,9 @@ private extension CloudKitAccountDelegate {
|
|||
let newArticles = articleChanges.newArticles ?? Set<Article>()
|
||||
let deletedArticles = articleChanges.deletedArticles ?? Set<Article>()
|
||||
|
||||
self.articlesZone.deleteArticlesAndStatuses(deletedArticles) { _ in
|
||||
self.articlesZone.deleteArticles(deletedArticles) { _ in
|
||||
self.refreshProgress.completeTask()
|
||||
self.articlesZone.saveNewArticlesAndStatuses(newArticles) { _ in
|
||||
self.articlesZone.saveNewArticles(newArticles) { _ in
|
||||
self.refreshProgress.clear()
|
||||
completion(.success(feed))
|
||||
}
|
||||
|
@ -744,9 +744,9 @@ private extension CloudKitAccountDelegate {
|
|||
let newArticles = articleChanges.newArticles ?? Set<Article>()
|
||||
let deletedArticles = articleChanges.deletedArticles ?? Set<Article>()
|
||||
|
||||
self.articlesZone.deleteArticlesAndStatuses(deletedArticles) { _ in
|
||||
self.articlesZone.deleteArticles(deletedArticles) { _ in
|
||||
self.refreshProgress.completeTask()
|
||||
self.articlesZone.saveNewArticlesAndStatuses(newArticles) { _ in
|
||||
self.articlesZone.saveNewArticles(newArticles) { _ in
|
||||
self.refreshProgress.clear()
|
||||
completion(.success(feed))
|
||||
}
|
||||
|
|
|
@ -214,9 +214,9 @@ private extension CloudKitAcountZoneDelegate {
|
|||
switch result {
|
||||
case .success(let articleChanges):
|
||||
|
||||
self.articlesZone?.deleteArticlesAndStatuses(articleChanges.deletedArticles ?? Set<Article>()) { _ in
|
||||
self.articlesZone?.deleteArticles(articleChanges.deletedArticles ?? Set<Article>()) { _ in
|
||||
self.refreshProgress?.completeTask()
|
||||
self.articlesZone?.saveNewArticlesAndStatuses(articleChanges.newArticles ?? Set<Article>()) { _ in
|
||||
self.articlesZone?.saveNewArticles(articleChanges.newArticles ?? Set<Article>()) { _ in
|
||||
self.refreshProgress?.completeTask()
|
||||
completion(webFeed)
|
||||
}
|
||||
|
@ -251,9 +251,9 @@ private extension CloudKitAcountZoneDelegate {
|
|||
BatchUpdate.shared.end()
|
||||
switch result {
|
||||
case .success(let articleChanges):
|
||||
self.articlesZone?.deleteArticlesAndStatuses(articleChanges.deletedArticles ?? Set<Article>()) { _ in
|
||||
self.articlesZone?.deleteArticles(articleChanges.deletedArticles ?? Set<Article>()) { _ in
|
||||
self.refreshProgress?.completeTask()
|
||||
self.articlesZone?.saveNewArticlesAndStatuses(articleChanges.newArticles ?? Set<Article>()) { _ in
|
||||
self.articlesZone?.saveNewArticles(articleChanges.newArticles ?? Set<Article>()) { _ in
|
||||
self.refreshProgress?.completeTask()
|
||||
completion(webFeed)
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ final class CloudKitArticlesZone: CloudKitZone {
|
|||
struct CloudKitArticle {
|
||||
static let recordType = "Article"
|
||||
struct Fields {
|
||||
static let articleStatus = "articleStatus"
|
||||
static let hollow = "hollow"
|
||||
static let webFeedURL = "webFeedURL"
|
||||
static let uniqueID = "uniqueID"
|
||||
static let title = "title"
|
||||
|
@ -42,24 +42,17 @@ final class CloudKitArticlesZone: CloudKitZone {
|
|||
static let datePublished = "datePublished"
|
||||
static let dateModified = "dateModified"
|
||||
static let parsedAuthors = "parsedAuthors"
|
||||
}
|
||||
}
|
||||
|
||||
struct CloudKitArticleStatus {
|
||||
static let recordType = "ArticleStatus"
|
||||
struct Fields {
|
||||
static let webFeedExternalID = "webFeedExternalID"
|
||||
static let read = "read"
|
||||
static let starred = "starred"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init(container: CKContainer) {
|
||||
self.container = container
|
||||
self.database = container.privateCloudDatabase
|
||||
}
|
||||
|
||||
func refreshArticlesAndStatuses(completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
func refreshArticles(completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
fetchChangesInZone() { result in
|
||||
switch result {
|
||||
case .success:
|
||||
|
@ -69,7 +62,7 @@ final class CloudKitArticlesZone: CloudKitZone {
|
|||
self.createZoneRecord() { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.refreshArticlesAndStatuses(completion: completion)
|
||||
self.refreshArticles(completion: completion)
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
|
@ -81,13 +74,13 @@ final class CloudKitArticlesZone: CloudKitZone {
|
|||
}
|
||||
}
|
||||
|
||||
func saveNewArticlesAndStatuses(_ articles: Set<Article>, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
func saveNewArticles(_ articles: Set<Article>, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
guard !articles.isEmpty else {
|
||||
completion(.success(()))
|
||||
return
|
||||
}
|
||||
|
||||
var records = makeNewStatusRecords(articles)
|
||||
var records = [CKRecord]()
|
||||
for article in articles {
|
||||
records.append(contentsOf: makeArticleRecords(article))
|
||||
}
|
||||
|
@ -95,30 +88,30 @@ final class CloudKitArticlesZone: CloudKitZone {
|
|||
saveIfNew(records, completion: completion)
|
||||
}
|
||||
|
||||
func deleteArticlesAndStatuses(_ articles: Set<Article>, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
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: statusID($0.articleID), zoneID: Self.zoneID) }
|
||||
let recordIDs = articles.map { CKRecord.ID(recordName: $0.articleID, zoneID: Self.zoneID) }
|
||||
delete(recordIDs: recordIDs, completion: completion)
|
||||
}
|
||||
|
||||
func modifyArticlesAndStatuses(_ syncStatuses: [SyncStatus], articles: Set<Article>, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
|
||||
var records = makeStatusRecords(syncStatuses, articles)
|
||||
|
||||
func modifyArticles(_ syncStatuses: [SyncStatus], articles: Set<Article>, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
var records = [CKRecord]()
|
||||
|
||||
let saveArticles = articles.filter { $0.status.read == false || $0.status.starred == true }
|
||||
for saveArticle in saveArticles {
|
||||
records.append(contentsOf: makeArticleRecords(saveArticle))
|
||||
}
|
||||
|
||||
let deleteArticleIDs = articles.subtracting(saveArticles).map {
|
||||
return CKRecord.ID(recordName: articleID($0.articleID), zoneID: Self.zoneID)
|
||||
let hollowArticles = articles.subtracting(saveArticles)
|
||||
for hollowArticle in hollowArticles {
|
||||
records.append(contentsOf: makeHollowArticleRecords(hollowArticle))
|
||||
}
|
||||
|
||||
self.modify(recordsToSave: records, recordIDsToDelete: deleteArticleIDs) { result in
|
||||
self.modify(recordsToSave: records, recordIDsToDelete: []) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
|
@ -126,15 +119,18 @@ final class CloudKitArticlesZone: CloudKitZone {
|
|||
self.handleSendArticleStatusError(error, syncStatuses: syncStatuses, starredArticles: articles, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension CloudKitArticlesZone {
|
||||
|
||||
func handleSendArticleStatusError(_ error: Error, syncStatuses: [SyncStatus], starredArticles: Set<Article>, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
if case CloudKitZoneError.userDeletedZone = error {
|
||||
self.createZoneRecord() { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.modifyArticlesAndStatuses(syncStatuses, articles: starredArticles, completion: completion)
|
||||
self.modifyArticles(syncStatuses, articles: starredArticles, completion: completion)
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
|
@ -144,74 +140,13 @@ final class CloudKitArticlesZone: CloudKitZone {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension CloudKitArticlesZone {
|
||||
|
||||
func statusID(_ id: String) -> String {
|
||||
return "s|\(id)"
|
||||
}
|
||||
|
||||
func articleID(_ id: String) -> String {
|
||||
return "a|\(id)"
|
||||
}
|
||||
|
||||
func makeNewStatusRecords(_ articles: Set<Article>) -> [CKRecord] {
|
||||
var records = [CKRecord]()
|
||||
|
||||
for article in articles {
|
||||
let recordID = CKRecord.ID(recordName: statusID(article.articleID), zoneID: Self.zoneID)
|
||||
let record = CKRecord(recordType: CloudKitArticleStatus.recordType, recordID: recordID)
|
||||
if let webFeedExternalID = article.webFeed?.externalID {
|
||||
record[CloudKitArticleStatus.Fields.webFeedExternalID] = webFeedExternalID
|
||||
}
|
||||
record[CloudKitArticleStatus.Fields.read] = "0"
|
||||
records.append(record)
|
||||
}
|
||||
|
||||
return records
|
||||
}
|
||||
|
||||
func makeStatusRecords(_ syncStatuses: [SyncStatus], _ articles: Set<Article>) -> [CKRecord] {
|
||||
var articleDict = [String: Article]()
|
||||
for article in articles {
|
||||
articleDict[article.articleID] = article
|
||||
}
|
||||
|
||||
var records = [String: CKRecord]()
|
||||
|
||||
for status in syncStatuses {
|
||||
|
||||
var record = records[status.articleID]
|
||||
if record == nil {
|
||||
let recordID = CKRecord.ID(recordName: statusID(status.articleID), zoneID: Self.zoneID)
|
||||
record = CKRecord(recordType: CloudKitArticleStatus.recordType, recordID: recordID)
|
||||
records[status.articleID] = record
|
||||
}
|
||||
|
||||
if let webFeedExternalID = articleDict[status.articleID]?.webFeed?.externalID {
|
||||
record![CloudKitArticleStatus.Fields.webFeedExternalID] = webFeedExternalID
|
||||
}
|
||||
|
||||
switch status.key {
|
||||
case .read:
|
||||
record![CloudKitArticleStatus.Fields.read] = status.flag ? "1" : "0"
|
||||
case .starred:
|
||||
record![CloudKitArticleStatus.Fields.starred] = status.flag ? "1" : "0"
|
||||
}
|
||||
}
|
||||
|
||||
return Array(records.values)
|
||||
}
|
||||
|
||||
func makeArticleRecords(_ article: Article) -> [CKRecord] {
|
||||
var records = [CKRecord]()
|
||||
|
||||
let recordID = CKRecord.ID(recordName: articleID(article.articleID), zoneID: Self.zoneID)
|
||||
let recordID = CKRecord.ID(recordName: article.articleID, zoneID: Self.zoneID)
|
||||
let articleRecord = CKRecord(recordType: CloudKitArticle.recordType, recordID: recordID)
|
||||
|
||||
let articleStatusRecordID = CKRecord.ID(recordName: article.articleID, zoneID: Self.zoneID)
|
||||
articleRecord[CloudKitArticle.Fields.articleStatus] = CKRecord.Reference(recordID: articleStatusRecordID, action: .deleteSelf)
|
||||
articleRecord[CloudKitArticle.Fields.hollow] = "0"
|
||||
articleRecord[CloudKitArticle.Fields.webFeedURL] = article.webFeed?.url
|
||||
articleRecord[CloudKitArticle.Fields.uniqueID] = article.uniqueID
|
||||
articleRecord[CloudKitArticle.Fields.title] = article.title
|
||||
|
@ -240,6 +175,35 @@ private extension CloudKitArticlesZone {
|
|||
articleRecord[CloudKitArticle.Fields.parsedAuthors] = parsedAuthors
|
||||
}
|
||||
|
||||
articleRecord[CloudKitArticle.Fields.read] = article.status.read ? "1" : "0"
|
||||
articleRecord[CloudKitArticle.Fields.starred] = article.status.starred ? "1" : "0"
|
||||
|
||||
records.append(articleRecord)
|
||||
return records
|
||||
}
|
||||
|
||||
func makeHollowArticleRecords(_ article: Article) -> [CKRecord] {
|
||||
var records = [CKRecord]()
|
||||
|
||||
let recordID = CKRecord.ID(recordName: article.articleID, zoneID: Self.zoneID)
|
||||
let articleRecord = CKRecord(recordType: CloudKitArticle.recordType, recordID: recordID)
|
||||
|
||||
articleRecord[CloudKitArticle.Fields.hollow] = "1"
|
||||
articleRecord[CloudKitArticle.Fields.webFeedURL] = article.webFeed?.url
|
||||
articleRecord[CloudKitArticle.Fields.uniqueID] = nil
|
||||
articleRecord[CloudKitArticle.Fields.title] = nil
|
||||
articleRecord[CloudKitArticle.Fields.contentHTML] = nil
|
||||
articleRecord[CloudKitArticle.Fields.contentText] = nil
|
||||
articleRecord[CloudKitArticle.Fields.url] = nil
|
||||
articleRecord[CloudKitArticle.Fields.externalURL] = nil
|
||||
articleRecord[CloudKitArticle.Fields.summary] = nil
|
||||
articleRecord[CloudKitArticle.Fields.imageURL] = nil
|
||||
articleRecord[CloudKitArticle.Fields.datePublished] = nil
|
||||
articleRecord[CloudKitArticle.Fields.dateModified] = nil
|
||||
articleRecord[CloudKitArticle.Fields.parsedAuthors] = nil
|
||||
articleRecord[CloudKitArticle.Fields.read] = article.status.read ? "1" : "0"
|
||||
articleRecord[CloudKitArticle.Fields.starred] = article.status.starred ? "1" : "0"
|
||||
|
||||
records.append(articleRecord)
|
||||
return records
|
||||
}
|
||||
|
|
|
@ -62,13 +62,11 @@ private extension CloudKitArticlesZoneDelegate {
|
|||
|
||||
func process(records: [CKRecord], pendingReadStatusArticleIDs: Set<String>, pendingStarredStatusArticleIDs: Set<String>, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
let receivedUnreadArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticleStatus.Fields.read] == "0" }).map({ $0.externalID }))
|
||||
let receivedReadArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticleStatus.Fields.read] == "1" }).map({ $0.externalID }))
|
||||
let receivedUnstarredArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticleStatus.Fields.starred] == "0" }).map({ $0.externalID }))
|
||||
let receivedStarredArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticleStatus.Fields.starred] == "1" }).map({ $0.externalID }))
|
||||
let receivedUnreadArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticle.Fields.read] == "0" }).map({ $0.externalID }))
|
||||
let receivedReadArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticle.Fields.read] == "1" }).map({ $0.externalID }))
|
||||
let receivedUnstarredArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticle.Fields.starred] == "0" }).map({ $0.externalID }))
|
||||
let receivedStarredArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticle.Fields.starred] == "1" }).map({ $0.externalID }))
|
||||
|
||||
let receivedArticles = records.filter({ $0.recordType == CloudKitArticlesZone.CloudKitArticle.recordType })
|
||||
|
||||
let updateableUnreadArticleIDs = receivedUnreadArticleIDs.subtracting(pendingReadStatusArticleIDs)
|
||||
let updateableReadArticleIDs = receivedReadArticleIDs.subtracting(pendingReadStatusArticleIDs)
|
||||
let updateableUnstarredArticleIDs = receivedUnstarredArticleIDs.subtracting(pendingStarredStatusArticleIDs)
|
||||
|
@ -96,7 +94,7 @@ private extension CloudKitArticlesZoneDelegate {
|
|||
group.leave()
|
||||
}
|
||||
|
||||
let parsedItems = receivedArticles.compactMap { makeParsedItem($0) }
|
||||
let parsedItems = records.compactMap { makeParsedItem($0) }
|
||||
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
for (webFeedID, parsedItems) in webFeedIDsAndItems {
|
||||
group.enter()
|
||||
|
@ -114,6 +112,10 @@ private extension CloudKitArticlesZoneDelegate {
|
|||
}
|
||||
|
||||
func makeParsedItem(_ articleRecord: CKRecord) -> ParsedItem? {
|
||||
guard articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.hollow] as? String ?? "0" == "0" else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var parsedAuthors = Set<ParsedAuthor>()
|
||||
|
||||
let decoder = JSONDecoder()
|
||||
|
|
Loading…
Reference in New Issue