Implement CloudKit feed add.
This commit is contained in:
parent
ecc20ad9e3
commit
6ce82fc28b
@ -545,7 +545,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
let feed = WebFeed(account: self, url: url, metadata: metadata)
|
||||
feed.name = name
|
||||
feed.homePageURL = homePageURL
|
||||
|
||||
return feed
|
||||
}
|
||||
|
||||
@ -683,7 +682,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
|
||||
func update(_ webFeed: WebFeed, with parsedFeed: ParsedFeed, _ completion: @escaping DatabaseCompletionBlock) {
|
||||
// Used only by an On My Mac account.
|
||||
// Used only by an On My Mac and iCloud accounts.
|
||||
webFeed.takeSettings(from: parsedFeed)
|
||||
let webFeedIDsAndItems = [webFeed.webFeedID: parsedFeed.items]
|
||||
update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: false, completion: completion)
|
||||
|
@ -56,7 +56,7 @@
|
||||
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 */; };
|
||||
51C034DF242D65D20014DC71 /* CloudKitResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C034DE242D65D20014DC71 /* CloudKitResult.swift */; };
|
||||
51C034DF242D65D20014DC71 /* CloudKitZoneResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C034DE242D65D20014DC71 /* CloudKitZoneResult.swift */; };
|
||||
51C034E1242D660D0014DC71 /* CKError+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C034E0242D660D0014DC71 /* CKError+Extensions.swift */; };
|
||||
51D58755227F53BE00900287 /* FeedbinTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D58754227F53BE00900287 /* FeedbinTag.swift */; };
|
||||
51D5875A227F630B00900287 /* tags_delete.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58757227F630B00900287 /* tags_delete.json */; };
|
||||
@ -287,7 +287,7 @@
|
||||
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>"; };
|
||||
51C034DE242D65D20014DC71 /* CloudKitResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitResult.swift; sourceTree = "<group>"; };
|
||||
51C034DE242D65D20014DC71 /* CloudKitZoneResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitZoneResult.swift; sourceTree = "<group>"; };
|
||||
51C034E0242D660D0014DC71 /* CKError+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CKError+Extensions.swift"; sourceTree = "<group>"; };
|
||||
51D58754227F53BE00900287 /* FeedbinTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinTag.swift; sourceTree = "<group>"; };
|
||||
51D58757227F630B00900287 /* tags_delete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_delete.json; sourceTree = "<group>"; };
|
||||
@ -514,8 +514,8 @@
|
||||
51C034E0242D660D0014DC71 /* CKError+Extensions.swift */,
|
||||
5103A9D82422546800410853 /* CloudKitAccountDelegate.swift */,
|
||||
51E4DB2F2426353D0091EB5B /* CloudKitAccountZone.swift */,
|
||||
51C034DE242D65D20014DC71 /* CloudKitResult.swift */,
|
||||
51E4DB2D242633ED0091EB5B /* CloudKitZone.swift */,
|
||||
51C034DE242D65D20014DC71 /* CloudKitZoneResult.swift */,
|
||||
);
|
||||
path = CloudKit;
|
||||
sourceTree = "<group>";
|
||||
@ -1184,7 +1184,7 @@
|
||||
3B826DAC2385C81C00FC1ADB /* FeedWranglerAccountDelegate.swift in Sources */,
|
||||
769F295938E5A30D03DFF88F /* NewsBlurAccountDelegate.swift in Sources */,
|
||||
769F2BA02EF5F329CDE45F5A /* NewsBlurAPICaller.swift in Sources */,
|
||||
51C034DF242D65D20014DC71 /* CloudKitResult.swift in Sources */,
|
||||
51C034DF242D65D20014DC71 /* CloudKitZoneResult.swift in Sources */,
|
||||
179DB28CF49F73A945EBF5DB /* NewsBlurLoginResponse.swift in Sources */,
|
||||
179DBF4DE2562D4C532F6008 /* NewsBlurFeed.swift in Sources */,
|
||||
179DB02FFBC17AC9798F0EBC /* NewsBlurStory.swift in Sources */,
|
||||
|
@ -45,14 +45,6 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
||||
return refresher.progress
|
||||
}
|
||||
|
||||
// init() {
|
||||
// accountZone.startUp() { result in
|
||||
// if case .failure(let error) = result {
|
||||
// os_log(.error, log: self.log, "Account zone startup error: %@.", error.localizedDescription)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
init(dataFolder: String) {
|
||||
accountZone = CloudKitAccountZone(container: container)
|
||||
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("Sync.sqlite3")
|
||||
@ -140,22 +132,35 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
|
||||
|
||||
InitialFeedDownloader.download(url) { parsedFeed in
|
||||
self.refreshProgress.completeTask()
|
||||
self.accountZone.createFeed(url: urlString, editedName: name) { result in
|
||||
switch result {
|
||||
case .success(let externalID):
|
||||
|
||||
let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
|
||||
|
||||
InitialFeedDownloader.download(url) { parsedFeed in
|
||||
self.refreshProgress.completeTask()
|
||||
|
||||
if let parsedFeed = parsedFeed {
|
||||
account.update(feed, with: parsedFeed, {_ in})
|
||||
if let parsedFeed = parsedFeed {
|
||||
account.update(feed, with: parsedFeed, {_ in
|
||||
|
||||
feed.editedName = name
|
||||
feed.externalID = externalID
|
||||
|
||||
container.addWebFeed(feed)
|
||||
completion(.success(feed))
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
self.refreshProgress.completeTask()
|
||||
completion(.failure(error)) // TODO: need to handle userDeletedZone
|
||||
}
|
||||
|
||||
feed.editedName = name
|
||||
|
||||
container.addWebFeed(feed)
|
||||
completion(.success(feed))
|
||||
|
||||
}
|
||||
|
||||
|
||||
case .failure:
|
||||
self.refreshProgress.completeTask()
|
||||
completion(.failure(AccountError.createErrorNotFound))
|
||||
|
@ -9,17 +9,17 @@
|
||||
import Foundation
|
||||
import CloudKit
|
||||
|
||||
enum CloudKitResult {
|
||||
enum CloudKitZoneResult {
|
||||
case success
|
||||
case retry(afterSeconds: Double)
|
||||
case chunk
|
||||
case limitExceeded
|
||||
case changeTokenExpired
|
||||
case partialFailure
|
||||
case partialFailure(errors: [CKRecord.ID: CKError])
|
||||
case serverRecordChanged
|
||||
case noZone
|
||||
case failure(error: Error)
|
||||
|
||||
static func resolve(_ error: Error?) -> CloudKitResult {
|
||||
static func resolve(_ error: Error?) -> CloudKitZoneResult {
|
||||
|
||||
guard error != nil else { return .success }
|
||||
|
||||
@ -39,11 +39,17 @@ enum CloudKitResult {
|
||||
case .serverRecordChanged:
|
||||
return .serverRecordChanged
|
||||
case .partialFailure:
|
||||
return .partialFailure
|
||||
if let partialErrors = ckError.userInfo[CKPartialErrorsByItemIDKey] as? [CKRecord.ID: CKError] {
|
||||
if anyZoneErrors(partialErrors) {
|
||||
return .noZone
|
||||
} else {
|
||||
return .partialFailure(errors: partialErrors)
|
||||
}
|
||||
} else {
|
||||
return .failure(error: error!)
|
||||
}
|
||||
case .limitExceeded:
|
||||
return .chunk
|
||||
case .zoneNotFound, .userDeletedZone:
|
||||
return .noZone
|
||||
return .limitExceeded
|
||||
default:
|
||||
return .failure(error: error!)
|
||||
}
|
||||
@ -51,3 +57,11 @@ enum CloudKitResult {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension CloudKitZoneResult {
|
||||
|
||||
static func anyZoneErrors(_ errors: [CKRecord.ID: CKError]) -> Bool {
|
||||
return errors.values.contains(where: { $0.code == .zoneNotFound || $0.code == .userDeletedZone } )
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
import CloudKit
|
||||
|
||||
public enum CloudKitZoneError: Error {
|
||||
case userDeletedZone
|
||||
case unknown
|
||||
}
|
||||
|
||||
@ -146,14 +147,13 @@ extension CloudKitZone {
|
||||
|
||||
guard let self = self else { return }
|
||||
|
||||
switch CloudKitResult.resolve(error) {
|
||||
switch CloudKitZoneResult.resolve(error) {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(()))
|
||||
}
|
||||
case .noZone:
|
||||
case .zoneNotFound:
|
||||
self.createZoneRecord() { result in
|
||||
// TODO: Need to rebuild (push) zone data here...
|
||||
switch result {
|
||||
case .success:
|
||||
self.modify(recordsToStore: recordsToStore, recordIDsToDelete: recordIDsToDelete, completion: completion)
|
||||
@ -161,11 +161,15 @@ extension CloudKitZone {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
case .userDeletedZone:
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(CloudKitZoneError.userDeletedZone))
|
||||
}
|
||||
case .retry(let timeToWait):
|
||||
self.retryOperationIfPossible(retryAfter: timeToWait) {
|
||||
self.modify(recordsToStore: recordsToStore, recordIDsToDelete: recordIDsToDelete, completion: completion)
|
||||
}
|
||||
case .chunk:
|
||||
case .limitExceeded:
|
||||
/// CloudKit says maximum number of items in a single request is 400.
|
||||
/// So I think 300 should be fine by them.
|
||||
let chunkedRecords = recordsToStore.chunked(into: 300)
|
||||
@ -173,7 +177,9 @@ extension CloudKitZone {
|
||||
self.modify(recordsToStore: chunk, recordIDsToDelete: recordIDsToDelete, completion: completion)
|
||||
}
|
||||
default:
|
||||
return
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(error!))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
74
Frameworks/Account/CloudKit/CloudKitZoneResult.swift
Normal file
74
Frameworks/Account/CloudKit/CloudKitZoneResult.swift
Normal file
@ -0,0 +1,74 @@
|
||||
//
|
||||
// CloudKitResult.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 3/26/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CloudKit
|
||||
|
||||
enum CloudKitZoneResult {
|
||||
case success
|
||||
case retry(afterSeconds: Double)
|
||||
case limitExceeded
|
||||
case changeTokenExpired
|
||||
case partialFailure(errors: [CKRecord.ID: CKError])
|
||||
case serverRecordChanged
|
||||
case zoneNotFound
|
||||
case userDeletedZone
|
||||
case failure(error: Error)
|
||||
|
||||
static func resolve(_ error: Error?) -> CloudKitZoneResult {
|
||||
|
||||
guard error != nil else { return .success }
|
||||
|
||||
guard let ckError = error as? CKError else {
|
||||
return .failure(error: error!)
|
||||
}
|
||||
|
||||
switch ckError.code {
|
||||
case .serviceUnavailable, .requestRateLimited, .zoneBusy:
|
||||
if let retry = ckError.userInfo[CKErrorRetryAfterKey] as? Double {
|
||||
return .retry(afterSeconds: retry)
|
||||
} else {
|
||||
return .failure(error: error!)
|
||||
}
|
||||
case .changeTokenExpired:
|
||||
return .changeTokenExpired
|
||||
case .serverRecordChanged:
|
||||
return .serverRecordChanged
|
||||
case .partialFailure:
|
||||
if let partialErrors = ckError.userInfo[CKPartialErrorsByItemIDKey] as? [CKRecord.ID: CKError] {
|
||||
if let zoneResult = anyZoneErrors(partialErrors) {
|
||||
return zoneResult
|
||||
} else {
|
||||
return .partialFailure(errors: partialErrors)
|
||||
}
|
||||
} else {
|
||||
return .failure(error: error!)
|
||||
}
|
||||
case .limitExceeded:
|
||||
return .limitExceeded
|
||||
default:
|
||||
return .failure(error: error!)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension CloudKitZoneResult {
|
||||
|
||||
static func anyZoneErrors(_ errors: [CKRecord.ID: CKError]) -> CloudKitZoneResult? {
|
||||
if errors.values.contains(where: { $0.code == .zoneNotFound } ) {
|
||||
return .zoneNotFound
|
||||
}
|
||||
if errors.values.contains(where: { $0.code == .userDeletedZone } ) {
|
||||
return .userDeletedZone
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
@ -58,16 +58,12 @@ class AddFeedController: AddFeedWindowControllerDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
|
||||
account.createWebFeed(url: url.absoluteString, name: title, container: container) { result in
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.endShowingProgress()
|
||||
}
|
||||
|
||||
BatchUpdate.shared.end()
|
||||
|
||||
switch result {
|
||||
case .success(let feed):
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed])
|
||||
|
Loading…
x
Reference in New Issue
Block a user