Add send statuses to CloudKit.

This commit is contained in:
Maurice Parker 2020-04-01 11:46:37 -05:00
parent 9a1b7f5225
commit 44231937cd
4 changed files with 175 additions and 7 deletions

View File

@ -56,6 +56,8 @@
5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5165D73022837F3400D9D53D /* InitialFeedDownloader.swift */; };
5170743C232AEDB500A461A3 /* OPMLFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5170743B232AEDB500A461A3 /* OPMLFile.swift */; };
519E84A62433D49000D238B0 /* OPMLNormalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E84A52433D49000D238B0 /* OPMLNormalizer.swift */; };
519E84A82434C5EF00D238B0 /* CloudKitArticlesZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E84A72434C5EF00D238B0 /* CloudKitArticlesZone.swift */; };
519E84AA2434C60400D238B0 /* CloudKitArticlesZoneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E84A92434C60400D238B0 /* CloudKitArticlesZoneDelegate.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 */; };
@ -290,6 +292,8 @@
5170743B232AEDB500A461A3 /* OPMLFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLFile.swift; sourceTree = "<group>"; };
518B2EA52351306200400001 /* Account_project_test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_test.xcconfig; sourceTree = "<group>"; };
519E84A52433D49000D238B0 /* OPMLNormalizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLNormalizer.swift; sourceTree = "<group>"; };
519E84A72434C5EF00D238B0 /* CloudKitArticlesZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitArticlesZone.swift; sourceTree = "<group>"; };
519E84A92434C60400D238B0 /* CloudKitArticlesZoneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitArticlesZoneDelegate.swift; 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>"; };
@ -522,6 +526,8 @@
5103A9D82422546800410853 /* CloudKitAccountDelegate.swift */,
51E4DB2F2426353D0091EB5B /* CloudKitAccountZone.swift */,
512DD4CC2431098700C17B1F /* CloudKitAccountZoneDelegate.swift */,
519E84A72434C5EF00D238B0 /* CloudKitArticlesZone.swift */,
519E84A92434C60400D238B0 /* CloudKitArticlesZoneDelegate.swift */,
51E4DB2D242633ED0091EB5B /* CloudKitZone.swift */,
51C034DE242D65D20014DC71 /* CloudKitZoneResult.swift */,
);
@ -1085,6 +1091,7 @@
9EA643D5239306AC0018A28C /* FeedlyFeedsSearchResponse.swift in Sources */,
9EAEC60E2332FEC20085D7C9 /* FeedlyFeed.swift in Sources */,
5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */,
519E84AA2434C60400D238B0 /* CloudKitArticlesZoneDelegate.swift in Sources */,
512DD4CB2431000600C17B1F /* CKRecord+Extensions.swift in Sources */,
3B826DAF2385C81C00FC1ADB /* FeedWranglerGenericResult.swift in Sources */,
9ECC9A85234DC16E009B5144 /* FeedlyAccountDelegateError.swift in Sources */,
@ -1165,6 +1172,7 @@
51BB7B84233531BC008E8144 /* AccountBehaviors.swift in Sources */,
9E1773D923458D590056A5A8 /* FeedlyResourceId.swift in Sources */,
9EE4CCFA234F106600FBAE4B /* FeedlyFeedContainerValidator.swift in Sources */,
519E84A82434C5EF00D238B0 /* CloudKitArticlesZone.swift in Sources */,
552032FC229D5D5A009559E0 /* ReaderAPIUnreadEntry.swift in Sources */,
9EC688EA232B973C00A8D0A2 /* FeedlyAPICaller.swift in Sources */,
9E1773D32345700F0056A5A8 /* FeedlyLink.swift in Sources */,

View File

@ -30,8 +30,9 @@ final class CloudKitAccountDelegate: AccountDelegate {
return CKContainer(identifier: "iCloud.\(orgID).NetNewsWire")
}()
private lazy var zones = [accountZone]
private lazy var zones: [CloudKitZone] = [accountZone, articlesZone]
private let accountZone: CloudKitAccountZone
private let articlesZone: CloudKitArticlesZone
private let refresher = LocalAccountRefresher()
@ -48,8 +49,11 @@ final class CloudKitAccountDelegate: AccountDelegate {
init(dataFolder: String) {
accountZone = CloudKitAccountZone(container: container)
articlesZone = CloudKitArticlesZone(container: container)
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("Sync.sqlite3")
database = SyncDatabase(databaseFilePath: databaseFilePath)
accountZone.refreshProgress = refreshProgress
}
@ -75,10 +79,29 @@ final class CloudKitAccountDelegate: AccountDelegate {
accountZone.fetchChangesInZone() { result in
switch result {
case .success:
self.refresher.refreshFeeds(account.flattenedWebFeeds()) {
BatchUpdate.shared.end()
account.metadata.lastArticleFetchEndTime = Date()
completion(.success(()))
self.sendArticleStatus(for: account) { result in
switch result {
case .success:
self.refreshArticleStatus(for: account) { result in
switch result {
case .success:
self.refresher.refreshFeeds(account.flattenedWebFeeds()) {
BatchUpdate.shared.end()
account.metadata.lastArticleFetchEndTime = Date()
completion(.success(()))
}
case .failure(let error):
completion(.failure(error))
}
}
case .failure(let error):
completion(.failure(error))
}
}
case .failure(let error):
BatchUpdate.shared.end()
@ -88,11 +111,44 @@ final class CloudKitAccountDelegate: AccountDelegate {
}
func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
completion(.success(()))
os_log(.debug, log: log, "Sending article statuses...")
database.selectForProcessing { result in
func processStatuses(_ syncStatuses: [SyncStatus]) {
self.articlesZone.sendArticleStatus(syncStatuses) { result in
switch result {
case .success:
os_log(.debug, log: self.log, "Done sending article statuses.")
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
}
switch result {
case .success(let syncStatuses):
processStatuses(syncStatuses)
case .failure(let databaseError):
completion(.failure(databaseError))
}
}
}
func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
completion(.success(()))
os_log(.debug, log: log, "Refreshing article statuses...")
articlesZone.fetchChangesInZone() { result in
os_log(.debug, log: self.log, "Done refreshing article statuses.")
switch result {
case .success:
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
}
func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
@ -353,6 +409,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
func accountDidInitialize(_ account: Account) {
accountZone.delegate = CloudKitAcountZoneDelegate(account: account, refreshProgress: refreshProgress)
articlesZone.delegate = CloudKitArticlesZoneDelegate(account: account)
if account.externalID == nil {
accountZone.findOrCreateAccount() { result in

View File

@ -0,0 +1,68 @@
//
// CloudKitArticlesZone.swift
// Account
//
// Created by Maurice Parker on 4/1/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import os.log
import RSWeb
import CloudKit
import SyncDatabase
final class CloudKitArticlesZone: CloudKitZone {
static var zoneID: CKRecordZone.ID {
return CKRecordZone.ID(zoneName: "Articles", ownerName: CKCurrentUserDefaultName)
}
var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit")
weak var container: CKContainer?
weak var database: CKDatabase?
weak var refreshProgress: DownloadProgress? = nil
var delegate: CloudKitZoneDelegate? = nil
struct CloudKitArticleStatus {
static let recordType = "ArticleStatus"
struct Fields {
static let read = "read"
static let starred = "starred"
static let userDeleted = "userDeleted"
}
}
init(container: CKContainer) {
self.container = container
self.database = container.privateCloudDatabase
}
func sendArticleStatus(_ syncStatuses: [SyncStatus], completion: @escaping ((Result<Void, Error>) -> Void)) {
var records = [String: CKRecord]()
for status in syncStatuses {
var record = records[status.articleID]
if record == nil {
let recordID = CKRecord.ID(recordName: status.articleID, zoneID: Self.zoneID)
record = CKRecord(recordType: CloudKitArticleStatus.recordType, recordID: recordID)
records[status.articleID] = record
}
switch status.key {
case .read:
record![CloudKitArticleStatus.Fields.read] = status.flag ? "1" : "0"
case .starred:
record![CloudKitArticleStatus.Fields.starred] = status.flag ? "1" : "0"
case .userDeleted:
record![CloudKitArticleStatus.Fields.userDeleted] = status.flag ? "1" : "0"
}
}
modify(recordsToSave: Array(records.values), recordIDsToDelete: [], completion: completion)
}
}

View File

@ -0,0 +1,35 @@
//
// CloudKitArticlesZoneDelegate.swift
// Account
//
// Created by Maurice Parker on 4/1/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import os.log
import CloudKit
class CloudKitArticlesZoneDelegate: CloudKitZoneDelegate {
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit")
weak var account: Account?
init(account: Account) {
self.account = account
}
func cloudKitDidChange(record: CKRecord) {
// switch record.recordType {
// case CloudKitAccountZone.CloudKitWebFeed.recordType:
// default:
// assertionFailure("Unknown record type: \(record.recordType)")
// }
}
func cloudKitDidDelete(recordType: CKRecord.RecordType, recordID: CKRecord.ID) {
// Article downloads clean up old articles and statuses
}
}