cloudkit-sane-sharing/src/CloudKitHandler.swift

127 lines
4.7 KiB
Swift

//
// CloudKitHandler.swift
// Lychee
//
// Created by Ryan McGrath on 5/3/18.
// Copyright © 2018 Ryan McGrath. All rights reserved.
//
/**
* CloudKitHandler
*
* Handles communicating with CloudKit and the associated APIs. The CloudKit APIs can be a bit
* unwieldy and verbose, so this library aims to work around those limitations and streamline
* everything.
*/
class CloudKitHandler {
static let shared = CloudKitHandler()
var isConfiguring: Bool = true
var isSyncing: Bool = false
let errorHandler: CloudKitErrorHandler = CloudKitErrorHandler()
var accountStatus: CKAccountStatus = .noAccount
var container: CKContainer = CKContainer(identifier: RYMC_CLOUDKIT_CONTAINER_ID)
var userRecordID: String?
var pendingInvites: [URL] = []
var pendingMetadatas: [CKShareMetadata] = []
private var operationQueue: OperationQueue = OperationQueue()
private var queuedOperations: [CKOperation] = []
// Internal
var backingPreviousChangeToken: CKServerChangeToken?
var backingRootRecord: CKRecord?
init() {
NotificationCenter.default.addObserver(self, selector: #selector(onAccountChanged), name: NSNotification.Name.CKAccountChanged, object: nil)
}
@objc func onAccountChanged() {
isConfiguring = true
isSyncing = false
determineAccountStatus()
}
public func determineAccountStatus() {
container.accountStatus { [unowned self] (status: CKAccountStatus, error: Error?) in
if(error != nil) {
print("Error retrieving account status \(String(describing: error))")
self.finishConfiguration(successful: false)
return
}
if(status != CKAccountStatus.available) {
// error
self.finishConfiguration(successful: false)
return
}
self.container.fetchUserRecordID(completionHandler: { (recordID: CKRecordID?, err: Error?) in
guard let recordID = recordID else {
self.finishConfiguration(successful: false)
return
}
print("User record ID: \(String(describing: recordID.recordName))")
self.userRecordID = recordID.recordName
self.accountStatus = status
self.configure()
})
}
}
func configure() {
let createZone: CKModifyRecordZonesOperation = createPrivateZoneOperation()
let fetchZones: CKFetchRecordZonesOperation = fetchPrivateZonesOperation(cancelIfExistsAlready: [createZone])
createZone.addDependency(fetchZones)
let createPrivateSub: CKModifySubscriptionsOperation = createSubscriptionOperation(.private)
let fetchPrivateSub: CKFetchSubscriptionsOperation = fetchSubscriptionsOperation(.private, cancelIfExistsAlready: [createPrivateSub])
fetchPrivateSub.addDependency(createZone)
createPrivateSub.addDependency(fetchPrivateSub)
let createSharedSub: CKModifySubscriptionsOperation = createSubscriptionOperation(.shared)
let fetchSharedSub: CKFetchSubscriptionsOperation = fetchSubscriptionsOperation(.shared, cancelIfExistsAlready: [createSharedSub])
fetchSharedSub.addDependency(createZone)
createSharedSub.addDependency(fetchSharedSub)
let createRootRecord: CKModifyRecordsOperation = createRootRecordOperation()
let fetchRootRecord: CKFetchRecordsOperation = fetchRootRecordOperation(cancelIfExistsAlready: [createRootRecord])
fetchRootRecord.addDependency(createZone)
createRootRecord.addDependency(fetchRootRecord)
let complete: BlockOperation = BlockOperation(block: { [unowned self] in
self.finishConfiguration()
})
complete.addDependency(createRootRecord)
operationQueue.addOperations([
fetchZones, createZone,
fetchPrivateSub, createPrivateSub,
fetchSharedSub, createSharedSub,
fetchRootRecord, createRootRecord,
complete
], waitUntilFinished: false)
}
func finishConfiguration(successful: Bool = true) {
isConfiguring = false
operationQueue.addOperations(queuedOperations, waitUntilFinished: false)
queuedOperations.removeAll()
}
func addOperations(_ operations: [CKOperation]) {
if(isConfiguring) {
queuedOperations.append(contentsOf: operations)
} else {
operationQueue.addOperations(operations, waitUntilFinished: false)
}
}
func stopAllOperations() {
for operation: Operation in operationQueue.operations.reversed() {
operation.cancel()
}
}
}