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)
|
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
|
@discardableResult
|
||||||
func ensureFolder(with name: String) -> Folder? {
|
func ensureFolder(with name: String) -> Folder? {
|
||||||
// TODO: support subfolders, maybe, some day
|
// 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
|
self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString, editedName: name, container: container) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let containerWebFeed):
|
case .success(let externalID):
|
||||||
|
|
||||||
let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
|
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
|
account.update(feed, with: parsedFeed, {_ in
|
||||||
|
|
||||||
feed.editedName = name
|
feed.editedName = name
|
||||||
feed.externalID = containerWebFeed.webFeedExternalID
|
feed.externalID = externalID
|
||||||
feed.folderRelationship?[containerWebFeed.containerWebFeedExternalID] = containerWebFeed.containerExternalID
|
|
||||||
|
|
||||||
container.addWebFeed(feed)
|
container.addWebFeed(feed)
|
||||||
completion(.success(feed))
|
completion(.success(feed))
|
||||||
|
@ -31,6 +31,7 @@ final class CloudKitAccountZone: CloudKitZone {
|
|||||||
struct Fields {
|
struct Fields {
|
||||||
static let url = "url"
|
static let url = "url"
|
||||||
static let editedName = "editedName"
|
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) {
|
init(container: CKContainer) {
|
||||||
self.container = container
|
self.container = container
|
||||||
self.database = container.privateCloudDatabase
|
self.database = container.privateCloudDatabase
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Persist a web feed record to iCloud and return the external key
|
/// 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) {
|
func createWebFeed(url: String, editedName: String?, container: Container, completion: @escaping (Result<String, Error>) -> Void) {
|
||||||
let webFeedRecord = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: generateRecordID())
|
let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: generateRecordID())
|
||||||
webFeedRecord[CloudKitWebFeed.Fields.url] = url
|
record[CloudKitWebFeed.Fields.url] = url
|
||||||
if let editedName = editedName {
|
if let editedName = editedName {
|
||||||
webFeedRecord[CloudKitWebFeed.Fields.editedName] = editedName
|
record[CloudKitWebFeed.Fields.editedName] = editedName
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let containerExternalID = container.externalID else {
|
guard let containerExternalID = container.externalID else {
|
||||||
completion(.failure(CloudKitZoneError.invalidParameter))
|
completion(.failure(CloudKitZoneError.invalidParameter))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
record[CloudKitWebFeed.Fields.containerExternalIDs] = [containerExternalID]
|
||||||
|
|
||||||
let containerRecordID = CKRecord.ID(recordName: containerExternalID, zoneID: Self.zoneID)
|
save(record) { result in
|
||||||
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
|
|
||||||
switch result {
|
switch result {
|
||||||
case .success:
|
case .success:
|
||||||
let cwf = ContainerWebFeed(webFeedExternalID: webFeedRecord.externalID,
|
completion(.success(record.externalID))
|
||||||
containerWebFeedExternalID: containerWebFeedRecord.externalID,
|
|
||||||
containerExternalID: containerExternalID)
|
|
||||||
completion(.success(cwf))
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
completion(.failure(error))
|
completion(.failure(error))
|
||||||
}
|
}
|
||||||
@ -97,7 +83,7 @@ final class CloudKitAccountZone: CloudKitZone {
|
|||||||
let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: recordID)
|
let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: recordID)
|
||||||
record[CloudKitWebFeed.Fields.editedName] = editedName
|
record[CloudKitWebFeed.Fields.editedName] = editedName
|
||||||
|
|
||||||
save([record]) { result in
|
save(record) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success:
|
case .success:
|
||||||
completion(.success(()))
|
completion(.success(()))
|
||||||
@ -140,7 +126,7 @@ final class CloudKitAccountZone: CloudKitZone {
|
|||||||
let record = CKRecord(recordType: CloudKitContainer.recordType, recordID: recordID)
|
let record = CKRecord(recordType: CloudKitContainer.recordType, recordID: recordID)
|
||||||
record[CloudKitContainer.Fields.name] = name
|
record[CloudKitContainer.Fields.name] = name
|
||||||
|
|
||||||
save([record]) { result in
|
save(record) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success:
|
case .success:
|
||||||
completion(.success(()))
|
completion(.success(()))
|
||||||
@ -163,7 +149,7 @@ private extension CloudKitAccountZone {
|
|||||||
record[CloudKitContainer.Fields.name] = name
|
record[CloudKitContainer.Fields.name] = name
|
||||||
record[CloudKitContainer.Fields.isAccount] = isAccount ? "true" : "false"
|
record[CloudKitContainer.Fields.isAccount] = isAccount ? "true" : "false"
|
||||||
|
|
||||||
save([record]) { result in
|
save(record) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success:
|
case .success:
|
||||||
completion(.success(record.externalID))
|
completion(.success(record.externalID))
|
||||||
|
@ -13,9 +13,6 @@ import CloudKit
|
|||||||
|
|
||||||
class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
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")
|
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit")
|
||||||
|
|
||||||
weak var account: Account?
|
weak var account: Account?
|
||||||
@ -32,8 +29,6 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
|||||||
addOrUpdateWebFeed(record)
|
addOrUpdateWebFeed(record)
|
||||||
case CloudKitAccountZone.CloudKitContainer.recordType:
|
case CloudKitAccountZone.CloudKitContainer.recordType:
|
||||||
addOrUpdateContainer(record)
|
addOrUpdateContainer(record)
|
||||||
case CloudKitAccountZone.CloudKitContainerWebFeed.recordType:
|
|
||||||
addOrUpdateContainerWebFeed(record)
|
|
||||||
default:
|
default:
|
||||||
assertionFailure("Unknown record type: \(record.recordType)")
|
assertionFailure("Unknown record type: \(record.recordType)")
|
||||||
}
|
}
|
||||||
@ -42,27 +37,33 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
|||||||
func cloudKitDidDelete(recordType: CKRecord.RecordType, recordID: CKRecord.ID) {
|
func cloudKitDidDelete(recordType: CKRecord.RecordType, recordID: CKRecord.ID) {
|
||||||
switch recordType {
|
switch recordType {
|
||||||
case CloudKitAccountZone.CloudKitWebFeed.recordType:
|
case CloudKitAccountZone.CloudKitWebFeed.recordType:
|
||||||
break
|
removeWebFeed(recordID.externalID)
|
||||||
case CloudKitAccountZone.CloudKitContainer.recordType:
|
case CloudKitAccountZone.CloudKitContainer.recordType:
|
||||||
removeContainer(recordID.externalID)
|
removeContainer(recordID.externalID)
|
||||||
case CloudKitAccountZone.CloudKitContainerWebFeed.recordType:
|
|
||||||
removeContainerWebFeed(recordID.externalID)
|
|
||||||
default:
|
default:
|
||||||
assertionFailure("Unknown record type: \(recordID.externalID)")
|
assertionFailure("Unknown record type: \(recordID.externalID)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addOrUpdateWebFeed(_ record: CKRecord) {
|
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
|
let editedName = record[CloudKitAccountZone.CloudKitWebFeed.Fields.editedName] as? String
|
||||||
|
|
||||||
if let webFeed = account.existingWebFeed(withExternalID: record.externalID) {
|
if let webFeed = account.existingWebFeed(withExternalID: record.externalID) {
|
||||||
webFeed.editedName = editedName
|
updateWebFeed(webFeed, editedName: editedName, containerExternalIDs: containerExternalIDs)
|
||||||
} else {
|
} else {
|
||||||
if let urlString = record[CloudKitAccountZone.CloudKitWebFeed.Fields.url] as? String {
|
addWebFeed(url: url, editedName: editedName, webFeedExternalID: record.externalID, containerExternalID: defaultContainerExternalID)
|
||||||
unclaimedWebFeeds[record.externalID] = UnclaimedWebFeed(url: urlString, editedName: editedName)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeWebFeed(_ externalID: String) {
|
||||||
|
if let webFeed = account?.existingWebFeed(withExternalID: externalID) {
|
||||||
|
account?.removeWebFeed(webFeed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
private extension CloudKitAcountZoneDelegate {
|
||||||
let containerExternalID = containerReference.recordID.externalID
|
|
||||||
let webFeedExternalID = webFeedReference.recordID.externalID
|
|
||||||
|
|
||||||
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) {
|
let existingContainers = account.existingContainers(withWebFeed: webFeed)
|
||||||
webFeed.folderRelationship?[containerWebFeedExternalID] = containerExternalID
|
let existingContainerExternalIds = existingContainers.compactMap { $0.externalID }
|
||||||
container.addWebFeed(webFeed)
|
|
||||||
return
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
guard let unclaimedWebFeed = unclaimedWebFeeds[webFeedExternalID] else { return }
|
func addWebFeed(url: URL, editedName: String?, webFeedExternalID: String, containerExternalID: String) {
|
||||||
unclaimedWebFeeds.removeValue(forKey: webFeedExternalID)
|
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)
|
let webFeed = account.createWebFeed(with: editedName, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
|
||||||
webFeed.editedName = unclaimedWebFeed.editedName
|
webFeed.editedName = editedName
|
||||||
webFeed.externalID = webFeedExternalID
|
webFeed.externalID = webFeedExternalID
|
||||||
webFeed.folderRelationship = [String: String]()
|
|
||||||
webFeed.folderRelationship![containerWebFeedExternalID] = containerExternalID
|
|
||||||
container.addWebFeed(webFeed)
|
container.addWebFeed(webFeed)
|
||||||
|
|
||||||
guard let url = URL(string: unclaimedWebFeed.url) else { return }
|
|
||||||
|
|
||||||
refreshProgress?.addToNumberOfTasksAndRemaining(1)
|
refreshProgress?.addToNumberOfTasksAndRemaining(1)
|
||||||
InitialFeedDownloader.download(url) { parsedFeed in
|
InitialFeedDownloader.download(url) { parsedFeed in
|
||||||
self.refreshProgress?.completeTask()
|
self.refreshProgress?.completeTask()
|
||||||
@ -122,22 +129,7 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
|||||||
account.update(webFeed, with: parsedFeed, {_ in })
|
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) {
|
func save(_ record: CKRecord, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
modify(recordsToSave: records, recordIDsToDelete: [], completion: completion)
|
modify(recordsToSave: [record], recordIDsToDelete: [], completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func delete(externalID: String?, completion: @escaping (Result<Void, Error>) -> Void) {
|
func delete(externalID: String?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user