Refactored add feed code to be more reliable.
This commit is contained in:
parent
203b83d64d
commit
df1faa368f
|
@ -518,6 +518,19 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
return existingFolder(withExternalID: externalID)
|
||||
}
|
||||
|
||||
func existingContainers(withWebFeed webFeed: WebFeed) -> [Container] {
|
||||
var containers = [Container]()
|
||||
if topLevelWebFeeds.contains(webFeed) {
|
||||
containers.append(self)
|
||||
}
|
||||
folders?.forEach { folder in
|
||||
if folder.topLevelWebFeeds.contains(webFeed) {
|
||||
containers.append(folder)
|
||||
}
|
||||
}
|
||||
return containers
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func ensureFolder(with name: String) -> Folder? {
|
||||
// TODO: support subfolders, maybe, some day
|
||||
|
|
|
@ -157,7 +157,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
|||
|
||||
self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString, editedName: name, container: container) { result in
|
||||
switch result {
|
||||
case .success(let containerWebFeed):
|
||||
case .success(let externalID):
|
||||
|
||||
let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
|
||||
|
||||
|
@ -168,8 +168,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
|||
account.update(feed, with: parsedFeed, {_ in
|
||||
|
||||
feed.editedName = name
|
||||
feed.externalID = containerWebFeed.webFeedExternalID
|
||||
feed.folderRelationship?[containerWebFeed.containerWebFeedExternalID] = containerWebFeed.containerExternalID
|
||||
feed.externalID = externalID
|
||||
|
||||
container.addWebFeed(feed)
|
||||
completion(.success(feed))
|
||||
|
|
|
@ -31,6 +31,7 @@ final class CloudKitAccountZone: CloudKitZone {
|
|||
struct Fields {
|
||||
static let url = "url"
|
||||
static let editedName = "editedName"
|
||||
static let containerExternalIDs = "folderExternalIDs"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,44 +43,29 @@ final class CloudKitAccountZone: CloudKitZone {
|
|||
}
|
||||
}
|
||||
|
||||
struct CloudKitContainerWebFeed {
|
||||
static let recordType = "ContainerWebFeed"
|
||||
struct Fields {
|
||||
static let container = "container"
|
||||
static let webFeed = "webFeed"
|
||||
}
|
||||
}
|
||||
|
||||
init(container: CKContainer) {
|
||||
self.container = container
|
||||
self.database = container.privateCloudDatabase
|
||||
}
|
||||
|
||||
/// Persist a web feed record to iCloud and return the external key
|
||||
func createWebFeed(url: String, editedName: String?, container: Container, completion: @escaping (Result<ContainerWebFeed, Error>) -> Void) {
|
||||
let webFeedRecord = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: generateRecordID())
|
||||
webFeedRecord[CloudKitWebFeed.Fields.url] = url
|
||||
func createWebFeed(url: String, editedName: String?, container: Container, completion: @escaping (Result<String, Error>) -> Void) {
|
||||
let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: generateRecordID())
|
||||
record[CloudKitWebFeed.Fields.url] = url
|
||||
if let editedName = editedName {
|
||||
webFeedRecord[CloudKitWebFeed.Fields.editedName] = editedName
|
||||
record[CloudKitWebFeed.Fields.editedName] = editedName
|
||||
}
|
||||
|
||||
guard let containerExternalID = container.externalID else {
|
||||
completion(.failure(CloudKitZoneError.invalidParameter))
|
||||
return
|
||||
}
|
||||
record[CloudKitWebFeed.Fields.containerExternalIDs] = [containerExternalID]
|
||||
|
||||
let containerRecordID = CKRecord.ID(recordName: containerExternalID, zoneID: Self.zoneID)
|
||||
let containerWebFeedRecord = CKRecord(recordType: CloudKitContainerWebFeed.recordType, recordID: generateRecordID())
|
||||
containerWebFeedRecord[CloudKitContainerWebFeed.Fields.container] = CKRecord.Reference(recordID: containerRecordID, action: .deleteSelf)
|
||||
containerWebFeedRecord[CloudKitContainerWebFeed.Fields.webFeed] = CKRecord.Reference(record: webFeedRecord, action: .deleteSelf)
|
||||
|
||||
save([webFeedRecord, containerWebFeedRecord]) { result in
|
||||
save(record) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
let cwf = ContainerWebFeed(webFeedExternalID: webFeedRecord.externalID,
|
||||
containerWebFeedExternalID: containerWebFeedRecord.externalID,
|
||||
containerExternalID: containerExternalID)
|
||||
completion(.success(cwf))
|
||||
completion(.success(record.externalID))
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
|
@ -97,7 +83,7 @@ final class CloudKitAccountZone: CloudKitZone {
|
|||
let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: recordID)
|
||||
record[CloudKitWebFeed.Fields.editedName] = editedName
|
||||
|
||||
save([record]) { result in
|
||||
save(record) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
|
@ -140,7 +126,7 @@ final class CloudKitAccountZone: CloudKitZone {
|
|||
let record = CKRecord(recordType: CloudKitContainer.recordType, recordID: recordID)
|
||||
record[CloudKitContainer.Fields.name] = name
|
||||
|
||||
save([record]) { result in
|
||||
save(record) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
|
@ -163,7 +149,7 @@ private extension CloudKitAccountZone {
|
|||
record[CloudKitContainer.Fields.name] = name
|
||||
record[CloudKitContainer.Fields.isAccount] = isAccount ? "true" : "false"
|
||||
|
||||
save([record]) { result in
|
||||
save(record) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(record.externalID))
|
||||
|
|
|
@ -13,9 +13,6 @@ import CloudKit
|
|||
|
||||
class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
||||
|
||||
private typealias UnclaimedWebFeed = (url: String, editedName: String?)
|
||||
private var unclaimedWebFeeds = [String: UnclaimedWebFeed]()
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit")
|
||||
|
||||
weak var account: Account?
|
||||
|
@ -32,8 +29,6 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
|||
addOrUpdateWebFeed(record)
|
||||
case CloudKitAccountZone.CloudKitContainer.recordType:
|
||||
addOrUpdateContainer(record)
|
||||
case CloudKitAccountZone.CloudKitContainerWebFeed.recordType:
|
||||
addOrUpdateContainerWebFeed(record)
|
||||
default:
|
||||
assertionFailure("Unknown record type: \(record.recordType)")
|
||||
}
|
||||
|
@ -42,28 +37,34 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
|||
func cloudKitDidDelete(recordType: CKRecord.RecordType, recordID: CKRecord.ID) {
|
||||
switch recordType {
|
||||
case CloudKitAccountZone.CloudKitWebFeed.recordType:
|
||||
break
|
||||
removeWebFeed(recordID.externalID)
|
||||
case CloudKitAccountZone.CloudKitContainer.recordType:
|
||||
removeContainer(recordID.externalID)
|
||||
case CloudKitAccountZone.CloudKitContainerWebFeed.recordType:
|
||||
removeContainerWebFeed(recordID.externalID)
|
||||
default:
|
||||
assertionFailure("Unknown record type: \(recordID.externalID)")
|
||||
}
|
||||
}
|
||||
|
||||
func addOrUpdateWebFeed(_ record: CKRecord) {
|
||||
guard let account = account else { return }
|
||||
guard let account = account,
|
||||
let urlString = record[CloudKitAccountZone.CloudKitWebFeed.Fields.url] as? String,
|
||||
let containerExternalIDs = record[CloudKitAccountZone.CloudKitWebFeed.Fields.containerExternalIDs] as? [String],
|
||||
let defaultContainerExternalID = containerExternalIDs.first,
|
||||
let url = URL(string: urlString) else { return }
|
||||
|
||||
let editedName = record[CloudKitAccountZone.CloudKitWebFeed.Fields.editedName] as? String
|
||||
|
||||
if let webFeed = account.existingWebFeed(withExternalID: record.externalID) {
|
||||
webFeed.editedName = editedName
|
||||
updateWebFeed(webFeed, editedName: editedName, containerExternalIDs: containerExternalIDs)
|
||||
} else {
|
||||
if let urlString = record[CloudKitAccountZone.CloudKitWebFeed.Fields.url] as? String {
|
||||
unclaimedWebFeeds[record.externalID] = UnclaimedWebFeed(url: urlString, editedName: editedName)
|
||||
addWebFeed(url: url, editedName: editedName, webFeedExternalID: record.externalID, containerExternalID: defaultContainerExternalID)
|
||||
}
|
||||
}
|
||||
|
||||
func removeWebFeed(_ externalID: String) {
|
||||
if let webFeed = account?.existingWebFeed(withExternalID: externalID) {
|
||||
account?.removeWebFeed(webFeed)
|
||||
}
|
||||
}
|
||||
|
||||
func addOrUpdateContainer(_ record: CKRecord) {
|
||||
|
@ -86,35 +87,41 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
func addOrUpdateContainerWebFeed(_ record: CKRecord) {
|
||||
guard let account = account,
|
||||
let containerReference = record[CloudKitAccountZone.CloudKitContainerWebFeed.Fields.container] as? CKRecord.Reference,
|
||||
let webFeedReference = record[CloudKitAccountZone.CloudKitContainerWebFeed.Fields.webFeed] as? CKRecord.Reference else { return }
|
||||
}
|
||||
|
||||
let containerWebFeedExternalID = record.externalID
|
||||
let containerExternalID = containerReference.recordID.externalID
|
||||
let webFeedExternalID = webFeedReference.recordID.externalID
|
||||
private extension CloudKitAcountZoneDelegate {
|
||||
|
||||
guard let container = account.existingContainer(withExternalID: containerExternalID) else { return }
|
||||
func updateWebFeed(_ webFeed: WebFeed, editedName: String?, containerExternalIDs: [String]) {
|
||||
guard let account = account else { return }
|
||||
webFeed.editedName = editedName
|
||||
|
||||
if let webFeed = account.existingWebFeed(withExternalID: webFeedExternalID) {
|
||||
webFeed.folderRelationship?[containerWebFeedExternalID] = containerExternalID
|
||||
let existingContainers = account.existingContainers(withWebFeed: webFeed)
|
||||
let existingContainerExternalIds = existingContainers.compactMap { $0.externalID }
|
||||
|
||||
let diff = containerExternalIDs.difference(from: existingContainerExternalIds)
|
||||
|
||||
for change in diff {
|
||||
switch change {
|
||||
case .remove(_, let externalID, _):
|
||||
if let container = existingContainers.first(where: { $0.externalID == externalID }) {
|
||||
container.removeWebFeed(webFeed)
|
||||
}
|
||||
case .insert(_, let externalID, _):
|
||||
if let container = existingContainers.first(where: { $0.externalID == externalID }) {
|
||||
container.addWebFeed(webFeed)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard let unclaimedWebFeed = unclaimedWebFeeds[webFeedExternalID] else { return }
|
||||
unclaimedWebFeeds.removeValue(forKey: webFeedExternalID)
|
||||
func addWebFeed(url: URL, editedName: String?, webFeedExternalID: String, containerExternalID: String) {
|
||||
guard let account = account, let container = account.existingContainer(withExternalID: containerExternalID) else { return }
|
||||
|
||||
let webFeed = account.createWebFeed(with: nil, url: unclaimedWebFeed.url, webFeedID: unclaimedWebFeed.url, homePageURL: nil)
|
||||
webFeed.editedName = unclaimedWebFeed.editedName
|
||||
let webFeed = account.createWebFeed(with: editedName, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
|
||||
webFeed.editedName = editedName
|
||||
webFeed.externalID = webFeedExternalID
|
||||
webFeed.folderRelationship = [String: String]()
|
||||
webFeed.folderRelationship![containerWebFeedExternalID] = containerExternalID
|
||||
container.addWebFeed(webFeed)
|
||||
|
||||
guard let url = URL(string: unclaimedWebFeed.url) else { return }
|
||||
|
||||
refreshProgress?.addToNumberOfTasksAndRemaining(1)
|
||||
InitialFeedDownloader.download(url) { parsedFeed in
|
||||
self.refreshProgress?.completeTask()
|
||||
|
@ -122,22 +129,7 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
|||
account.update(webFeed, with: parsedFeed, {_ in })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeContainerWebFeed(_ containerWebFeedExternalID: String) {
|
||||
guard let account = account,
|
||||
let webFeed = account.flattenedWebFeeds().first(where: { $0.folderRelationship?.keys.contains(containerWebFeedExternalID) ?? false }),
|
||||
let containerExternalId = webFeed.folderRelationship?[containerWebFeedExternalID] else { return }
|
||||
|
||||
webFeed.folderRelationship?.removeValue(forKey: containerWebFeedExternalID)
|
||||
|
||||
guard account.externalID != containerExternalId else {
|
||||
account.removeWebFeed(webFeed)
|
||||
return
|
||||
}
|
||||
|
||||
guard let folder = account.existingFolder(withExternalID: containerExternalId) else { return }
|
||||
folder.removeWebFeed(webFeed)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -96,8 +96,8 @@ extension CloudKitZone {
|
|||
}
|
||||
}
|
||||
|
||||
func save(_ records: [CKRecord], completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
modify(recordsToSave: records, recordIDsToDelete: [], completion: completion)
|
||||
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) {
|
||||
|
|
Loading…
Reference in New Issue