Rebuild the import OPML and initial device add processes.

This commit is contained in:
Maurice Parker 2020-04-26 11:26:41 -05:00
parent 09addec369
commit 48dbc4958f
3 changed files with 64 additions and 207 deletions

View File

@ -97,11 +97,6 @@ final class CloudKitAccountDelegate: AccountDelegate {
} }
func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) { func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
guard refreshProgress.isComplete else {
completion(.success(()))
return
}
os_log(.debug, log: log, "Sending article statuses...") os_log(.debug, log: log, "Sending article statuses...")
database.selectForProcessing { result in database.selectForProcessing { result in
@ -154,11 +149,6 @@ final class CloudKitAccountDelegate: AccountDelegate {
func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) { func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
guard refreshProgress.isComplete else {
completion(.success(()))
return
}
os_log(.debug, log: log, "Refreshing article statuses...") os_log(.debug, log: log, "Refreshing article statuses...")
articlesZone.refreshArticles() { result in articlesZone.refreshArticles() { result in
@ -214,7 +204,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
let normalizedItems = OPMLNormalizer.normalize(opmlItems) let normalizedItems = OPMLNormalizer.normalize(opmlItems)
self.accountZone.importOPML(rootExternalID: rootExternalID, items: normalizedItems) { _ in self.accountZone.importOPML(rootExternalID: rootExternalID, items: normalizedItems) { _ in
self.initialRefreshAll(for: account, completion: completion) self.standardRefreshAll(for: account, completion: completion)
} }
} }
@ -309,7 +299,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
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) {
refreshProgress.addToNumberOfTasksAndRemaining(1) refreshProgress.addToNumberOfTasksAndRemaining(1)
accountZone.createWebFeed(url: feed.url, editedName: feed.editedName, container: container) { result in accountZone.createWebFeed(url: feed.url, name: feed.name, editedName: feed.editedName, container: container) { result in
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
switch result { switch result {
case .success(let externalID): case .success(let externalID):
@ -494,32 +484,22 @@ private extension CloudKitAccountDelegate {
completion(.failure(error)) completion(.failure(error))
} }
refreshProgress.addToNumberOfTasksAndRemaining(3) refreshProgress.addToNumberOfTasksAndRemaining(2)
refreshArticleStatus(for: account) { result in accountZone.fetchChangesInZone() { result in
switch result {
case .success:
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
self.accountZone.fetchChangesInZone() { result in
switch result {
case .success:
self.refreshProgress.completeTask()
self.sendArticleStatus(for: account) { result in
switch result { switch result {
case .success: case .success:
self.refreshProgress.clear() self.refreshArticleStatus(for: account) { result in
self.refreshProgress.completeTask()
switch result {
case .success:
account.metadata.lastArticleFetchEndTime = Date() account.metadata.lastArticleFetchEndTime = Date()
completion(.success(())) completion(.success(()))
case .failure(let error): case .failure(let error):
fail(error) fail(error)
} }
} }
case .failure(let error):
fail(error)
}
}
case .failure(let error): case .failure(let error):
fail(error) fail(error)
} }
@ -667,7 +647,7 @@ private extension CloudKitAccountDelegate {
return return
} }
self.accountZone.createWebFeed(url: urlString, editedName: editedName, container: container) { result in self.accountZone.createWebFeed(url: urlString, name: name, editedName: editedName, container: container) { result in
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
switch result { switch result {
@ -751,15 +731,8 @@ private extension CloudKitAccountDelegate {
return return
} }
self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString, editedName: editedName, container: container) { result in
self.refreshProgress.completeTask()
switch result {
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)
feed.editedName = editedName feed.editedName = editedName
feed.externalID = externalID
container.addWebFeed(feed) container.addWebFeed(feed)
InitialFeedDownloader.download(url) { parsedFeed in InitialFeedDownloader.download(url) { parsedFeed in
@ -769,8 +742,16 @@ private extension CloudKitAccountDelegate {
account.update(feed, with: parsedFeed) { result in account.update(feed, with: parsedFeed) { result in
switch result { switch result {
case .success(let articleChanges): case .success(let articleChanges):
BatchUpdate.shared.end() BatchUpdate.shared.end()
self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString, name: parsedFeed.title, editedName: editedName, container: container) { result in
self.refreshProgress.completeTask()
switch result {
case .success(let externalID):
feed.externalID = externalID
var newAndUpdatedArticles = articleChanges.newArticles ?? Set<Article>() var newAndUpdatedArticles = articleChanges.newArticles ?? Set<Article>()
newAndUpdatedArticles.formUnion(articleChanges.updatedArticles ?? Set<Article>()) newAndUpdatedArticles.formUnion(articleChanges.updatedArticles ?? Set<Article>())
let deletedArticles = articleChanges.deletedArticles ?? Set<Article>() let deletedArticles = articleChanges.deletedArticles ?? Set<Article>()
@ -786,6 +767,14 @@ private extension CloudKitAccountDelegate {
} }
} }
case .failure(let error):
BatchUpdate.shared.end()
self.refreshProgress.clear()
completion(.failure(error))
}
}
case .failure(let error): case .failure(let error):
self.refreshProgress.clear() self.refreshProgress.clear()
completion(.failure(error)) completion(.failure(error))
@ -799,13 +788,6 @@ private extension CloudKitAccountDelegate {
} }
case .failure(let error):
BatchUpdate.shared.end()
self.refreshProgress.clear()
completion(.failure(error))
}
}
case .failure: case .failure:
BatchUpdate.shared.end() BatchUpdate.shared.end()
self.refreshProgress.clear() self.refreshProgress.clear()

View File

@ -28,6 +28,7 @@ final class CloudKitAccountZone: CloudKitZone {
static let recordType = "AccountWebFeed" static let recordType = "AccountWebFeed"
struct Fields { struct Fields {
static let url = "url" static let url = "url"
static let name = "name"
static let editedName = "editedName" static let editedName = "editedName"
static let containerExternalIDs = "containerExternalIDs" static let containerExternalIDs = "containerExternalIDs"
} }
@ -81,10 +82,11 @@ final class CloudKitAccountZone: CloudKitZone {
} }
/// 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<String, Error>) -> Void) { func createWebFeed(url: String, name: String?, editedName: String?, container: Container, completion: @escaping (Result<String, Error>) -> Void) {
let recordID = CKRecord.ID(recordName: url.md5String, zoneID: Self.zoneID) let recordID = CKRecord.ID(recordName: url.md5String, zoneID: Self.zoneID)
let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: recordID) let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: recordID)
record[CloudKitWebFeed.Fields.url] = url record[CloudKitWebFeed.Fields.url] = url
record[CloudKitWebFeed.Fields.name] = name
if let editedName = editedName { if let editedName = editedName {
record[CloudKitWebFeed.Fields.editedName] = editedName record[CloudKitWebFeed.Fields.editedName] = editedName
} }

View File

@ -15,7 +15,7 @@ import Articles
class CloudKitAcountZoneDelegate: CloudKitZoneDelegate { class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
private typealias UnclaimedWebFeed = (url: URL, editedName: String?, webFeedExternalID: String) private typealias UnclaimedWebFeed = (url: URL, name: String?, editedName: String?, webFeedExternalID: String)
private var unclaimedWebFeeds = [String: [UnclaimedWebFeed]]() 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")
@ -42,65 +42,41 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
} }
} }
let group = DispatchGroup()
for changedRecord in changed { for changedRecord in changed {
switch changedRecord.recordType { switch changedRecord.recordType {
case CloudKitAccountZone.CloudKitWebFeed.recordType: case CloudKitAccountZone.CloudKitWebFeed.recordType:
group.enter() addOrUpdateWebFeed(changedRecord)
addOrUpdateWebFeed(changedRecord) {
group.leave()
}
case CloudKitAccountZone.CloudKitContainer.recordType: case CloudKitAccountZone.CloudKitContainer.recordType:
group.enter() addOrUpdateContainer(changedRecord)
addOrUpdateContainer(changedRecord) {
group.leave()
}
default: default:
assertionFailure("Unknown record type: \(changedRecord.recordType)") assertionFailure("Unknown record type: \(changedRecord.recordType)")
} }
} }
group.notify(queue: DispatchQueue.main) {
completion(.success(())) completion(.success(()))
} }
}
func addOrUpdateWebFeed(_ record: CKRecord, completion: @escaping () -> Void) { func addOrUpdateWebFeed(_ record: CKRecord) {
guard let account = account, guard let account = account,
let urlString = record[CloudKitAccountZone.CloudKitWebFeed.Fields.url] as? String, let urlString = record[CloudKitAccountZone.CloudKitWebFeed.Fields.url] as? String,
let containerExternalIDs = record[CloudKitAccountZone.CloudKitWebFeed.Fields.containerExternalIDs] as? [String], let containerExternalIDs = record[CloudKitAccountZone.CloudKitWebFeed.Fields.containerExternalIDs] as? [String],
let url = URL(string: urlString) else { let url = URL(string: urlString) else {
completion()
return return
} }
let name = record[CloudKitAccountZone.CloudKitWebFeed.Fields.name] as? String
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) {
updateWebFeed(webFeed, name: name, editedName: editedName, containerExternalIDs: containerExternalIDs)
updateWebFeed(webFeed, editedName: editedName, containerExternalIDs: containerExternalIDs)
completion()
} else { } else {
let group = DispatchGroup()
for containerExternalID in containerExternalIDs { for containerExternalID in containerExternalIDs {
group.enter()
if let container = account.existingContainer(withExternalID: containerExternalID) { if let container = account.existingContainer(withExternalID: containerExternalID) {
createWebFeedIfNecessary(url: url, editedName: editedName, webFeedExternalID: record.externalID, container: container) { webFeed in createWebFeedIfNecessary(url: url, name: name, editedName: editedName, webFeedExternalID: record.externalID, container: container)
group.leave()
}
} else { } else {
addUnclaimedWebFeed(url: url, editedName: editedName, webFeedExternalID: record.externalID, containerExternalID: containerExternalID) addUnclaimedWebFeed(url: url, name: name, editedName: editedName, webFeedExternalID: record.externalID, containerExternalID: containerExternalID)
group.leave()
} }
} }
group.notify(queue: DispatchQueue.main) {
completion()
}
} }
} }
@ -110,12 +86,11 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
} }
} }
func addOrUpdateContainer(_ record: CKRecord, completion: @escaping () -> Void) { func addOrUpdateContainer(_ record: CKRecord) {
guard let account = account, guard let account = account,
let name = record[CloudKitAccountZone.CloudKitContainer.Fields.name] as? String, let name = record[CloudKitAccountZone.CloudKitContainer.Fields.name] as? String,
let isAccount = record[CloudKitAccountZone.CloudKitContainer.Fields.isAccount] as? String, let isAccount = record[CloudKitAccountZone.CloudKitContainer.Fields.isAccount] as? String,
isAccount != "1" else { isAccount != "1" else {
completion()
return return
} }
@ -128,26 +103,17 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
} }
if let folder = folder, let containerExternalID = folder.externalID, let unclaimedWebFeeds = unclaimedWebFeeds[containerExternalID] { if let folder = folder, let containerExternalID = folder.externalID, let unclaimedWebFeeds = unclaimedWebFeeds[containerExternalID] {
let group = DispatchGroup()
for unclaimedWebFeed in unclaimedWebFeeds { for unclaimedWebFeed in unclaimedWebFeeds {
group.enter() createWebFeedIfNecessary(url: unclaimedWebFeed.url,
createWebFeedIfNecessary(url: unclaimedWebFeed.url, editedName: unclaimedWebFeed.editedName, webFeedExternalID: unclaimedWebFeed.webFeedExternalID, container: folder) { webFeed in name: unclaimedWebFeed.name,
group.leave() editedName: unclaimedWebFeed.editedName,
} webFeedExternalID: unclaimedWebFeed.webFeedExternalID,
container: folder)
} }
group.notify(queue: DispatchQueue.main) {
self.unclaimedWebFeeds.removeValue(forKey: containerExternalID) self.unclaimedWebFeeds.removeValue(forKey: containerExternalID)
completion()
} }
} else {
completion()
}
} }
func removeContainer(_ externalID: String) { func removeContainer(_ externalID: String) {
@ -160,8 +126,10 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
private extension CloudKitAcountZoneDelegate { private extension CloudKitAcountZoneDelegate {
func updateWebFeed(_ webFeed: WebFeed, editedName: String?, containerExternalIDs: [String]) { func updateWebFeed(_ webFeed: WebFeed, name: String?, editedName: String?, containerExternalIDs: [String]) {
guard let account = account else { return } guard let account = account else { return }
webFeed.name = name
webFeed.editedName = editedName webFeed.editedName = editedName
let existingContainers = account.existingContainers(withWebFeed: webFeed) let existingContainers = account.existingContainers(withWebFeed: webFeed)
@ -183,121 +151,26 @@ private extension CloudKitAcountZoneDelegate {
} }
} }
func createWebFeedIfNecessary(url: URL, editedName: String?, webFeedExternalID: String, container: Container, completion: @escaping (WebFeed) -> Void) { func createWebFeedIfNecessary(url: URL, name: String?, editedName: String?, webFeedExternalID: String, container: Container) {
guard let account = account, let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return } guard let account = account else { return }
if let webFeed = account.existingWebFeed(withExternalID: webFeedExternalID) { if account.existingWebFeed(withExternalID: webFeedExternalID) != nil {
completion(webFeed)
return return
} }
let webFeed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) let webFeed = account.createWebFeed(with: name, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
webFeed.editedName = editedName webFeed.editedName = editedName
webFeed.externalID = webFeedExternalID webFeed.externalID = webFeedExternalID
if let feedProvider = FeedProviderManager.shared.best(for: urlComponents) {
refreshProgress?.addToNumberOfTasksAndRemaining(5)
feedProvider.assignName(urlComponents) { result in
self.refreshProgress?.completeTask()
switch result {
case .success(let name):
webFeed.name = name
container.addWebFeed(webFeed) container.addWebFeed(webFeed)
feedProvider.refresh(webFeed) { result in
self.refreshProgress?.completeTask()
switch result {
case .success(let parsedItems):
account.update(url.absoluteString, with: parsedItems) { result in
switch result {
case .success(let articleChanges):
var newAndUpdatedArticles = articleChanges.newArticles ?? Set<Article>()
newAndUpdatedArticles.formUnion(articleChanges.updatedArticles ?? Set<Article>())
let deletedArticles = articleChanges.deletedArticles ?? Set<Article>()
newAndUpdatedArticles = newAndUpdatedArticles.subtracting(deletedArticles)
self.articlesZone?.deleteArticles(deletedArticles) { _ in
self.refreshProgress?.completeTask()
self.articlesZone?.saveNewArticles(newAndUpdatedArticles) { _ in
self.refreshProgress?.completeTask()
self.articlesZone?.fetchChangesInZone() { _ in
self.refreshProgress?.completeTask()
completion(webFeed)
}
}
} }
case .failure: func addUnclaimedWebFeed(url: URL, name: String?, editedName: String?, webFeedExternalID: String, containerExternalID: String) {
completion(webFeed)
}
}
case .failure:
completion(webFeed)
}
}
case .failure:
completion(webFeed)
}
}
} else {
refreshProgress?.addToNumberOfTasksAndRemaining(4)
BatchUpdate.shared.start()
InitialFeedDownloader.download(url) { parsedFeed in
self.refreshProgress?.completeTask()
if let parsedFeed = parsedFeed {
container.addWebFeed(webFeed)
account.update(webFeed, with: parsedFeed, { result in
BatchUpdate.shared.end()
switch result {
case .success(let articleChanges):
var newAndUpdatedArticles = articleChanges.newArticles ?? Set<Article>()
newAndUpdatedArticles.formUnion(articleChanges.updatedArticles ?? Set<Article>())
let deletedArticles = articleChanges.deletedArticles ?? Set<Article>()
newAndUpdatedArticles = newAndUpdatedArticles.subtracting(deletedArticles)
self.articlesZone?.deleteArticles(deletedArticles) { _ in
self.refreshProgress?.completeTask()
self.articlesZone?.saveNewArticles(newAndUpdatedArticles) { _ in
self.refreshProgress?.completeTask()
self.articlesZone?.fetchChangesInZone() { _ in
self.refreshProgress?.completeTask()
completion(webFeed)
}
}
}
case .failure:
completion(webFeed)
}
})
} else {
BatchUpdate.shared.end()
completion(webFeed)
}
}
}
}
func addUnclaimedWebFeed(url: URL, editedName: String?, webFeedExternalID: String, containerExternalID: String) {
if var unclaimedWebFeeds = self.unclaimedWebFeeds[containerExternalID] { if var unclaimedWebFeeds = self.unclaimedWebFeeds[containerExternalID] {
unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, editedName: editedName, webFeedExternalID: webFeedExternalID)) unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, name: name, editedName: editedName, webFeedExternalID: webFeedExternalID))
self.unclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds self.unclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
} else { } else {
var unclaimedWebFeeds = [UnclaimedWebFeed]() var unclaimedWebFeeds = [UnclaimedWebFeed]()
unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, editedName: editedName, webFeedExternalID: webFeedExternalID)) unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, name: name, editedName: editedName, webFeedExternalID: webFeedExternalID))
self.unclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds self.unclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
} }
} }