Remove centralized CloudKit syncing code.

This commit is contained in:
Maurice Parker
2020-04-05 15:05:08 -05:00
parent fb807809d7
commit 390173dcb4
5 changed files with 9 additions and 238 deletions

View File

@@ -42,7 +42,6 @@
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA48227B497600D19003 /* FeedbinAPICaller.swift */; };
5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */; };
514BF5202391B0DB00902FE8 /* SingleArticleFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514BF51F2391B0DB00902FE8 /* SingleArticleFetcher.swift */; };
515000002438682300C1A442 /* CloudKitPublicZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5150FFFF2438682300C1A442 /* CloudKitPublicZone.swift */; };
5150FFFE243823B800C1A442 /* CloudKitError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5150FFFD243823B800C1A442 /* CloudKitError.swift */; };
5154367B228EEB28005E1CDF /* FeedbinImportResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5154367A228EEB28005E1CDF /* FeedbinImportResult.swift */; };
515E4EB52324FF8C0057B0E7 /* CredentialsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515E4EB22324FF8C0057B0E7 /* CredentialsManager.swift */; };
@@ -60,7 +59,6 @@
519E84A62433D49000D238B0 /* OPMLNormalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E84A52433D49000D238B0 /* OPMLNormalizer.swift */; };
519E84A82434C5EF00D238B0 /* CloudKitArticlesZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E84A72434C5EF00D238B0 /* CloudKitArticlesZone.swift */; };
519E84AC2435019100D238B0 /* CloudKitArticlesZoneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E84AB2435019100D238B0 /* CloudKitArticlesZoneDelegate.swift */; };
51B544672438F410003F03BF /* CKContainer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B544662438F410003F03BF /* CKContainer+Extensions.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 */; };
@@ -280,7 +278,6 @@
5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAccountDelegate.swift; sourceTree = "<group>"; };
514BF51F2391B0DB00902FE8 /* SingleArticleFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleArticleFetcher.swift; sourceTree = "<group>"; };
5150FFFD243823B800C1A442 /* CloudKitError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudKitError.swift; sourceTree = "<group>"; };
5150FFFF2438682300C1A442 /* CloudKitPublicZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitPublicZone.swift; sourceTree = "<group>"; };
5154367A228EEB28005E1CDF /* FeedbinImportResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinImportResult.swift; sourceTree = "<group>"; };
515E4EB22324FF8C0057B0E7 /* CredentialsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialsManager.swift; sourceTree = "<group>"; };
515E4EB32324FF8C0057B0E7 /* URLRequest+RSWeb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URLRequest+RSWeb.swift"; sourceTree = "<group>"; };
@@ -298,7 +295,6 @@
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>"; };
519E84AB2435019100D238B0 /* CloudKitArticlesZoneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudKitArticlesZoneDelegate.swift; sourceTree = "<group>"; };
51B544662438F410003F03BF /* CKContainer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CKContainer+Extensions.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>"; };
@@ -525,7 +521,6 @@
5103A9D7242253DC00410853 /* CloudKit */ = {
isa = PBXGroup;
children = (
51B544662438F410003F03BF /* CKContainer+Extensions.swift */,
512DD4CA2431000600C17B1F /* CKRecord+Extensions.swift */,
5103A9D82422546800410853 /* CloudKitAccountDelegate.swift */,
51E4DB2F2426353D0091EB5B /* CloudKitAccountZone.swift */,
@@ -533,7 +528,6 @@
519E84A72434C5EF00D238B0 /* CloudKitArticlesZone.swift */,
519E84AB2435019100D238B0 /* CloudKitArticlesZoneDelegate.swift */,
5150FFFD243823B800C1A442 /* CloudKitError.swift */,
5150FFFF2438682300C1A442 /* CloudKitPublicZone.swift */,
51E4DB2D242633ED0091EB5B /* CloudKitZone.swift */,
51C034DE242D65D20014DC71 /* CloudKitZoneResult.swift */,
);
@@ -1116,7 +1110,6 @@
84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */,
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */,
515000002438682300C1A442 /* CloudKitPublicZone.swift in Sources */,
5103A9D92422546800410853 /* CloudKitAccountDelegate.swift in Sources */,
5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */,
9E784EBE237E890600099B1B /* FeedlyLogoutOperation.swift in Sources */,
@@ -1159,7 +1152,6 @@
84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */,
51BC8FCC237EC055004F8B56 /* Feed.swift in Sources */,
846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */,
51B544672438F410003F03BF /* CKContainer+Extensions.swift in Sources */,
9EA643CF2391D3560018A28C /* FeedlyAddExistingFeedOperation.swift in Sources */,
55203300229D5D5A009559E0 /* ReaderAPICaller.swift in Sources */,
9E1D154F233371DD00F4944C /* FeedlyGetCollectionsOperation.swift in Sources */,

View File

@@ -1,41 +0,0 @@
//
// CKContainer+Extensions.swift
// Account
//
// Created by Maurice Parker on 4/4/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import CloudKit
extension CKContainer {
private static let userRecordIDKey = "cloudkit.server.userRecordID"
var userRecordID: String? {
get {
return UserDefaults.standard.string(forKey: Self.userRecordIDKey)
}
set {
guard let userRecordID = newValue else {
UserDefaults.standard.removeObject(forKey: Self.userRecordIDKey)
return
}
UserDefaults.standard.set(userRecordID, forKey: Self.userRecordIDKey)
}
}
func fetchUserRecordID() {
guard userRecordID == nil else { return }
fetchUserRecordID { recordID, error in
guard let recordID = recordID, error == nil else {
return
}
DispatchQueue.main.async {
self.userRecordID = recordID.recordName
}
}
}
}

View File

@@ -30,10 +30,9 @@ final class CloudKitAccountDelegate: AccountDelegate {
return CKContainer(identifier: "iCloud.\(orgID).NetNewsWire")
}()
private lazy var zones: [CloudKitZone] = [accountZone, articlesZone, publicZone]
private lazy var zones: [CloudKitZone] = [accountZone, articlesZone]
private let accountZone: CloudKitAccountZone
private let articlesZone: CloudKitArticlesZone
private let publicZone: CloudKitPublicZone
private let refresher = LocalAccountRefresher()
@@ -49,7 +48,6 @@ final class CloudKitAccountDelegate: AccountDelegate {
init(dataFolder: String) {
accountZone = CloudKitAccountZone(container: container)
articlesZone = CloudKitArticlesZone(container: container)
publicZone = CloudKitPublicZone(container: container)
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("Sync.sqlite3")
database = SyncDatabase(databaseFilePath: databaseFilePath)
@@ -195,17 +193,10 @@ final class CloudKitAccountDelegate: AccountDelegate {
}
}
refreshProgress.addToNumberOfTasksAndRemaining(2)
publicZone.manageSubscriptions(webFeedURLs) { result in
self.refreshProgress.completeTask()
switch result {
case .success:
self.accountZone.importOPML(rootExternalID: rootExternalID, items: normalizedItems) { _ in
self.refreshAll(for: account, downloadFeeds: false, completion: completion)
}
case .failure(let error):
completion(.failure(error))
}
// Add one task here to show we started immediately. We don't need to complete is because refreshAll clears everything at the end.
refreshProgress.addToNumberOfTasksAndRemaining(1)
self.accountZone.importOPML(rootExternalID: rootExternalID, items: normalizedItems) { _ in
self.refreshAll(for: account, downloadFeeds: false, completion: completion)
}
}
@@ -219,7 +210,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
}
BatchUpdate.shared.start()
refreshProgress.addToNumberOfTasksAndRemaining(4)
refreshProgress.addToNumberOfTasksAndRemaining(3)
FeedFinder.find(url: url) { result in
self.refreshProgress.completeTask()
@@ -250,13 +241,6 @@ final class CloudKitAccountDelegate: AccountDelegate {
feed.externalID = externalID
container.addWebFeed(feed)
self.publicZone.manageSubscriptions(account.flattenedWebFeedURLs) { result in
self.refreshProgress.completeTask()
if case .failure(let error) = result {
os_log(.error, log: self.log, "An error occurred while creating the subscription: %@", error.localizedDescription)
}
}
InitialFeedDownloader.download(url) { parsedFeed in
self.refreshProgress.completeTask()
@@ -302,25 +286,13 @@ final class CloudKitAccountDelegate: AccountDelegate {
}
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
refreshProgress.addToNumberOfTasksAndRemaining(2)
refreshProgress.addToNumberOfTasksAndRemaining(1)
accountZone.removeWebFeed(feed, from: container) { result in
self.refreshProgress.completeTask()
switch result {
case .success(let deleted):
case .success:
container.removeWebFeed(feed)
if deleted {
self.publicZone.manageSubscriptions(account.flattenedWebFeedURLs) { result in
self.refreshProgress.completeTask()
switch result {
case .success:
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
} else {
completion(.success(()))
}
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
@@ -484,7 +456,6 @@ final class CloudKitAccountDelegate: AccountDelegate {
// Check to see if this is a new account and initialize anything we need
if account.externalID == nil {
container.fetchUserRecordID()
accountZone.findOrCreateAccount() { result in
switch result {
case .success(let externalID):

View File

@@ -1,105 +0,0 @@
//
// CloudKitPublicZone.swift
// Account
//
// Created by Maurice Parker on 4/4/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import CloudKit
import os.log
final class CloudKitPublicZone: CloudKitZone {
static var zoneID: CKRecordZone.ID {
return CKRecordZone.default().zoneID
}
var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit")
weak var container: CKContainer?
weak var database: CKDatabase?
var delegate: CloudKitZoneDelegate?
struct CloudKitWebFeed {
static let recordType = "WebFeed"
struct Fields {
static let url = "url"
static let httpLastModified = "httpLastModified"
static let httpEtag = "httpEtag"
}
}
struct CloudKitWebFeedCheck {
static let recordType = "WebFeedCheck"
struct Fields {
static let webFeed = "webFeed"
static let lastCheck = "lastCheck"
}
}
struct CloudKitUserSubscription {
static let recordType = "UserSubscription"
struct Fields {
static let userRecordID = "userRecordID"
static let webFeed = "webFeed"
static let subscriptionID = "subscriptionID"
}
}
init(container: CKContainer) {
self.container = container
self.database = container.publicCloudDatabase
}
func subscribeToZoneChanges() {}
func receiveRemoteNotification(userInfo: [AnyHashable : Any], completion: @escaping () -> Void) {
completion()
}
/// Create any new subscriptions and delete any old ones
func manageSubscriptions(_ webFeedURLs: Set<String>, completion: @escaping (Result<Void, Error>) -> Void) {
var webFeedRecords = [CKRecord]()
for webFeedURL in webFeedURLs {
let webFeedRecordID = CKRecord.ID(recordName: webFeedURL.md5String, zoneID: Self.zoneID)
let webFeedRecord = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: webFeedRecordID)
webFeedRecord[CloudKitWebFeed.Fields.url] = webFeedURL
webFeedRecord[CloudKitWebFeed.Fields.httpLastModified] = ""
webFeedRecord[CloudKitWebFeed.Fields.httpEtag] = ""
webFeedRecords.append(webFeedRecord)
}
self.saveIfNew(webFeedRecords) { _ in
var subscriptions = [CKSubscription]()
let webFeedURLChunks = Array(webFeedURLs).chunked(into: 20)
for webFeedURLChunk in webFeedURLChunks {
let predicate = NSPredicate(format: "url in %@", webFeedURLChunk)
let subscription = CKQuerySubscription(recordType: CloudKitWebFeed.recordType, predicate: predicate, options: [.firesOnRecordUpdate])
let info = CKSubscription.NotificationInfo()
info.shouldSendContentAvailable = true
info.desiredKeys = [CloudKitWebFeed.Fields.httpLastModified, CloudKitWebFeed.Fields.httpEtag]
subscription.notificationInfo = info
subscriptions.append(subscription)
}
self.fetchAllUserSubscriptions() { result in
switch result {
case .success(let subscriptionsToDelete):
let subscriptionToDeleteIDs = subscriptionsToDelete.map({ $0.subscriptionID })
self.modify(subscriptionsToSave: subscriptions, subscriptionIDsToDelete: subscriptionToDeleteIDs, completion: completion)
case .failure(let error):
completion(.failure(error))
}
}
}
}
}

View File

@@ -337,32 +337,6 @@ extension CloudKitZone {
}
}
/// Bulk add (or modify I suppose) and delete of subscriptions
func modify(subscriptionsToSave: [CKSubscription], subscriptionIDsToDelete: [CKSubscription.ID], completion: @escaping (Result<Void, Error>) -> Void) {
let op = CKModifySubscriptionsOperation(subscriptionsToSave: subscriptionsToSave, subscriptionIDsToDelete: subscriptionIDsToDelete)
op.modifySubscriptionsCompletionBlock = { [weak self] (_, _, error) in
guard let self = self else { return }
switch CloudKitZoneResult.resolve(error) {
case .success:
DispatchQueue.main.async {
completion(.success(()))
}
case .retry(let timeToWait):
self.retryIfPossible(after: timeToWait) {
self.modify(subscriptionsToSave: subscriptionsToSave, subscriptionIDsToDelete: subscriptionIDsToDelete, completion: completion)
}
default:
DispatchQueue.main.async {
completion(.failure(CloudKitError(error!)))
}
}
}
database?.add(op)
}
/// Modify and delete the supplied CKRecords and CKRecord.IDs
func modify(recordsToSave: [CKRecord], recordIDsToDelete: [CKRecord.ID], completion: @escaping (Result<Void, Error>) -> Void) {
let op = CKModifyRecordsOperation(recordsToSave: recordsToSave, recordIDsToDelete: recordIDsToDelete)
@@ -432,26 +406,6 @@ extension CloudKitZone {
database?.add(op)
}
/// Fetch all the subscriptions that a user has in the current database in all zones
func fetchAllUserSubscriptions(completion: @escaping (Result<[CKSubscription], Error>) -> Void) {
database?.fetchAllSubscriptions() { subscriptions, error in
switch CloudKitZoneResult.resolve(error) {
case .success:
DispatchQueue.main.async {
completion(.success((subscriptions!)))
}
case .retry(let timeToWait):
self.retryIfPossible(after: timeToWait) {
self.fetchAllUserSubscriptions(completion: completion)
}
default:
DispatchQueue.main.async {
completion(.failure(CloudKitError(error!)))
}
}
}
}
/// Fetch all the changes in the CKZone since the last time we checked
func fetchChangesInZone(completion: @escaping (Result<Void, Error>) -> Void) {