Implement add, move, delete folder operations for feeds.

This commit is contained in:
Maurice Parker 2020-03-31 03:30:53 -05:00
parent df1faa368f
commit 90376dac03
4 changed files with 141 additions and 34 deletions

View File

@ -207,7 +207,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
} }
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) { func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
accountZone.removeWebFeed(feed) { result in accountZone.removeWebFeed(feed, from: container) { result in
switch result { switch result {
case .success: case .success:
container.removeWebFeed(feed) container.removeWebFeed(feed)
@ -218,20 +218,33 @@ final class CloudKitAccountDelegate: AccountDelegate {
} }
} }
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) { func moveWebFeed(for account: Account, with feed: WebFeed, from fromContainer: Container, to toContainer: Container, completion: @escaping (Result<Void, Error>) -> Void) {
from.removeWebFeed(feed) accountZone.moveWebFeed(feed, from: fromContainer, to: toContainer) { result in
to.addWebFeed(feed) switch result {
completion(.success(())) case .success:
fromContainer.removeWebFeed(feed)
toContainer.addWebFeed(feed)
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
} }
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) { func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
container.addWebFeed(feed) accountZone.addWebFeed(feed, to: container) { result in
completion(.success(())) switch result {
case .success:
container.addWebFeed(feed)
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
} }
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) { func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
container.addWebFeed(feed) addWebFeed(for: account, with: feed, to: container, completion: completion)
completion(.success(()))
} }
func createFolder(for account: Account, name: String, completion: @escaping (Result<Folder, Error>) -> Void) { func createFolder(for account: Account, name: String, completion: @escaping (Result<Folder, Error>) -> Void) {

View File

@ -94,8 +94,72 @@ final class CloudKitAccountZone: CloudKitZone {
} }
/// Deletes a web feed from iCloud /// Deletes a web feed from iCloud
func removeWebFeed(_ webFeed: WebFeed, completion: @escaping (Result<Void, Error>) -> Void) { func removeWebFeed(_ webFeed: WebFeed, from: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delete(externalID: webFeed.externalID , completion: completion) guard let fromContainerExternalID = from.externalID else {
completion(.failure(CloudKitZoneError.invalidParameter))
return
}
fetch(externalID: webFeed.externalID) { result in
switch result {
case .success(let record):
if let containerExternalIDs = record[CloudKitWebFeed.Fields.containerExternalIDs] as? [String] {
var containerExternalIDSet = Set(containerExternalIDs)
containerExternalIDSet.remove(fromContainerExternalID)
if containerExternalIDSet.isEmpty {
self.delete(externalID: webFeed.externalID , completion: completion)
} else {
record[CloudKitWebFeed.Fields.containerExternalIDs] = Array(containerExternalIDSet)
self.save(record, completion: completion)
}
}
case .failure(let error):
completion(.failure(error))
}
}
}
func moveWebFeed(_ webFeed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
guard let fromContainerExternalID = from.externalID, let toContainerExternalID = to.externalID else {
completion(.failure(CloudKitZoneError.invalidParameter))
return
}
fetch(externalID: webFeed.externalID) { result in
switch result {
case .success(let record):
if let containerExternalIDs = record[CloudKitWebFeed.Fields.containerExternalIDs] as? [String] {
var containerExternalIDSet = Set(containerExternalIDs)
containerExternalIDSet.remove(fromContainerExternalID)
containerExternalIDSet.insert(toContainerExternalID)
record[CloudKitWebFeed.Fields.containerExternalIDs] = Array(containerExternalIDSet)
self.save(record, completion: completion)
}
case .failure(let error):
completion(.failure(error))
}
}
}
func addWebFeed(_ webFeed: WebFeed, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
guard let toContainerExternalID = to.externalID else {
completion(.failure(CloudKitZoneError.invalidParameter))
return
}
fetch(externalID: webFeed.externalID) { result in
switch result {
case .success(let record):
if let containerExternalIDs = record[CloudKitWebFeed.Fields.containerExternalIDs] as? [String] {
var containerExternalIDSet = Set(containerExternalIDs)
containerExternalIDSet.insert(toContainerExternalID)
record[CloudKitWebFeed.Fields.containerExternalIDs] = Array(containerExternalIDSet)
self.save(record, completion: completion)
}
case .failure(let error):
completion(.failure(error))
}
}
} }
func findOrCreateAccount(completion: @escaping (Result<String, Error>) -> Void) { func findOrCreateAccount(completion: @escaping (Result<String, Error>) -> Void) {

View File

@ -62,8 +62,8 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
} }
func removeWebFeed(_ externalID: String) { func removeWebFeed(_ externalID: String) {
if let webFeed = account?.existingWebFeed(withExternalID: externalID) { if let webFeed = account?.existingWebFeed(withExternalID: externalID), let containers = account?.existingContainers(withWebFeed: webFeed) {
account?.removeWebFeed(webFeed) containers.forEach { $0.removeWebFeed(webFeed) }
} }
} }
@ -107,7 +107,7 @@ private extension CloudKitAcountZoneDelegate {
container.removeWebFeed(webFeed) container.removeWebFeed(webFeed)
} }
case .insert(_, let externalID, _): case .insert(_, let externalID, _):
if let container = existingContainers.first(where: { $0.externalID == externalID }) { if let container = account.existingContainer(withExternalID: externalID) {
container.addWebFeed(webFeed) container.addWebFeed(webFeed)
} }
} }

View File

@ -71,7 +71,7 @@ extension CloudKitZone {
case .success: case .success:
break break
case .retry(let timeToWait): case .retry(let timeToWait):
self.retryOperationIfPossible(retryAfter: timeToWait) { self.retryIfPossible(after: timeToWait) {
self.subscribe() self.subscribe()
} }
default: default:
@ -96,20 +96,6 @@ extension CloudKitZone {
} }
} }
func save(_ record: CKRecord, completion: @escaping (Result<Void, Error>) -> Void) {
modify(recordsToSave: [record], recordIDsToDelete: [], completion: completion)
}
func delete(externalID: String?, completion: @escaping (Result<Void, Error>) -> Void) {
guard let externalID = externalID else {
completion(.failure(CloudKitZoneError.invalidParameter))
return
}
let recordID = CKRecord.ID(recordName: externalID, zoneID: Self.zoneID)
modify(recordsToSave: [], recordIDsToDelete: [recordID], completion: completion)
}
func query(_ query: CKQuery, completion: @escaping (Result<[CKRecord], Error>) -> Void) { func query(_ query: CKQuery, completion: @escaping (Result<[CKRecord], Error>) -> Void) {
guard let database = database else { guard let database = database else {
completion(.failure(CloudKitZoneError.unknown)) completion(.failure(CloudKitZoneError.unknown))
@ -125,7 +111,7 @@ extension CloudKitZone {
completion(.failure(CloudKitZoneError.unknown)) completion(.failure(CloudKitZoneError.unknown))
} }
case .retry(let timeToWait): case .retry(let timeToWait):
self.retryOperationIfPossible(retryAfter: timeToWait) { self.retryIfPossible(after: timeToWait) {
self.query(query, completion: completion) self.query(query, completion: completion)
} }
default: default:
@ -134,6 +120,50 @@ extension CloudKitZone {
} }
} }
func fetch(externalID: String?, completion: @escaping (Result<CKRecord, Error>) -> Void) {
guard let externalID = externalID else {
completion(.failure(CloudKitZoneError.invalidParameter))
return
}
let recordID = CKRecord.ID(recordName: externalID, zoneID: Self.zoneID)
database?.fetch(withRecordID: recordID) { record, error in
switch CloudKitZoneResult.resolve(error) {
case .success:
DispatchQueue.main.async {
if let record = record {
completion(.success(record))
} else {
completion(.failure(CloudKitZoneError.unknown))
}
}
case .retry(let timeToWait):
self.retryIfPossible(after: timeToWait) {
self.fetch(externalID: externalID, completion: completion)
}
default:
DispatchQueue.main.async {
completion(.failure(error!))
}
}
}
}
func save(_ record: CKRecord, completion: @escaping (Result<Void, Error>) -> Void) {
modify(recordsToSave: [record], recordIDsToDelete: [], completion: completion)
}
func delete(externalID: String?, completion: @escaping (Result<Void, Error>) -> Void) {
guard let externalID = externalID else {
completion(.failure(CloudKitZoneError.invalidParameter))
return
}
let recordID = CKRecord.ID(recordName: externalID, zoneID: Self.zoneID)
modify(recordsToSave: [], recordIDsToDelete: [recordID], completion: completion)
}
func modify(recordsToSave: [CKRecord], recordIDsToDelete: [CKRecord.ID], completion: @escaping (Result<Void, Error>) -> Void) { func modify(recordsToSave: [CKRecord], recordIDsToDelete: [CKRecord.ID], completion: @escaping (Result<Void, Error>) -> Void) {
let op = CKModifyRecordsOperation(recordsToSave: recordsToSave, recordIDsToDelete: recordIDsToDelete) let op = CKModifyRecordsOperation(recordsToSave: recordsToSave, recordIDsToDelete: recordIDsToDelete)
@ -173,7 +203,7 @@ extension CloudKitZone {
completion(.failure(CloudKitZoneError.userDeletedZone)) completion(.failure(CloudKitZoneError.userDeletedZone))
} }
case .retry(let timeToWait): case .retry(let timeToWait):
self.retryOperationIfPossible(retryAfter: timeToWait) { self.retryIfPossible(after: timeToWait) {
self.modify(recordsToSave: recordsToSave, recordIDsToDelete: recordIDsToDelete, completion: completion) self.modify(recordsToSave: recordsToSave, recordIDsToDelete: recordIDsToDelete, completion: completion)
} }
case .limitExceeded: case .limitExceeded:
@ -230,7 +260,7 @@ extension CloudKitZone {
self.changeToken = token self.changeToken = token
} }
case .retry(let timeToWait): case .retry(let timeToWait):
self.retryOperationIfPossible(retryAfter: timeToWait) { self.retryIfPossible(after: timeToWait) {
self.fetchChangesInZone(completion: completion) self.fetchChangesInZone(completion: completion)
} }
default: default:
@ -299,8 +329,8 @@ private extension CloudKitZone {
} }
} }
func retryOperationIfPossible(retryAfter: Double, block: @escaping () -> ()) { func retryIfPossible(after: Double, block: @escaping () -> ()) {
let delayTime = DispatchTime.now() + retryAfter let delayTime = DispatchTime.now() + after
DispatchQueue.main.asyncAfter(deadline: delayTime, execute: { DispatchQueue.main.asyncAfter(deadline: delayTime, execute: {
block() block()
}) })