Enable adding feeds to folders.

This commit is contained in:
Maurice Parker 2020-03-30 21:11:57 -05:00
parent 6d3e6914df
commit 203b83d64d
6 changed files with 113 additions and 39 deletions

View File

@ -511,6 +511,13 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return delegate.markArticles(for: self, articles: articles, statusKey: statusKey, flag: flag)
}
func existingContainer(withExternalID externalID: String) -> Container? {
guard self.externalID != externalID else {
return self
}
return existingFolder(withExternalID: externalID)
}
@discardableResult
func ensureFolder(with name: String) -> Folder? {
// TODO: support subfolders, maybe, some day
@ -561,10 +568,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return feed
}
public func existingWebFeed(withExternalID externalID: String) -> WebFeed? {
return externalIDToWebFeedDictionary[externalID]
}
public func addWebFeed(_ feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
delegate.addWebFeed(for: self, with: feed, to: container, completion: completion)
}
@ -1315,6 +1318,11 @@ extension Account {
public func existingWebFeed(withWebFeedID webFeedID: String) -> WebFeed? {
return idToWebFeedDictionary[webFeedID]
}
public func existingWebFeed(withExternalID externalID: String) -> WebFeed? {
return externalIDToWebFeedDictionary[externalID]
}
}
// MARK: - OPMLRepresentable

View File

@ -155,9 +155,9 @@ final class CloudKitAccountDelegate: AccountDelegate {
return
}
self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString, editedName: name) { result in
self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString, editedName: name, container: container) { result in
switch result {
case .success(let externalID):
case .success(let containerWebFeed):
let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
@ -168,7 +168,8 @@ final class CloudKitAccountDelegate: AccountDelegate {
account.update(feed, with: parsedFeed, {_ in
feed.editedName = name
feed.externalID = externalID
feed.externalID = containerWebFeed.webFeedExternalID
feed.folderRelationship?[containerWebFeed.containerWebFeedExternalID] = containerWebFeed.containerExternalID
container.addWebFeed(feed)
completion(.success(feed))

View File

@ -13,6 +13,8 @@ import CloudKit
final class CloudKitAccountZone: CloudKitZone {
typealias ContainerWebFeed = (webFeedExternalID: String, containerWebFeedExternalID: String, containerExternalID: String)
static var zoneID: CKRecordZone.ID {
return CKRecordZone.ID(zoneName: "Account", ownerName: CKCurrentUserDefaultName)
}
@ -40,29 +42,51 @@ 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?, completion: @escaping (Result<String, Error>) -> Void) {
let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: generateRecordID())
record[CloudKitWebFeed.Fields.url] = url
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
if let editedName = editedName {
record[CloudKitWebFeed.Fields.editedName] = editedName
webFeedRecord[CloudKitWebFeed.Fields.editedName] = editedName
}
save(record: record) { result in
guard let containerExternalID = container.externalID else {
completion(.failure(CloudKitZoneError.invalidParameter))
return
}
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
switch result {
case .success:
completion(.success(record.externalID))
let cwf = ContainerWebFeed(webFeedExternalID: webFeedRecord.externalID,
containerWebFeedExternalID: containerWebFeedRecord.externalID,
containerExternalID: containerExternalID)
completion(.success(cwf))
case .failure(let error):
completion(.failure(error))
}
}
}
/// Rename the given web feed
func renameWebFeed(_ webFeed: WebFeed, editedName: String?, completion: @escaping (Result<Void, Error>) -> Void) {
guard let externalID = webFeed.externalID else {
completion(.failure(CloudKitZoneError.invalidParameter))
@ -73,7 +97,7 @@ final class CloudKitAccountZone: CloudKitZone {
let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: recordID)
record[CloudKitWebFeed.Fields.editedName] = editedName
save(record: record) { result in
save([record]) { result in
switch result {
case .success:
completion(.success(()))
@ -116,7 +140,7 @@ final class CloudKitAccountZone: CloudKitZone {
let record = CKRecord(recordType: CloudKitContainer.recordType, recordID: recordID)
record[CloudKitContainer.Fields.name] = name
save(record: record) { result in
save([record]) { result in
switch result {
case .success:
completion(.success(()))
@ -139,7 +163,7 @@ private extension CloudKitAccountZone {
record[CloudKitContainer.Fields.name] = name
record[CloudKitContainer.Fields.isAccount] = isAccount ? "true" : "false"
save(record: record) { result in
save([record]) { result in
switch result {
case .success:
completion(.success(record.externalID))

View File

@ -13,6 +13,9 @@ 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?
@ -29,6 +32,8 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
addOrUpdateWebFeed(record)
case CloudKitAccountZone.CloudKitContainer.recordType:
addOrUpdateContainer(record)
case CloudKitAccountZone.CloudKitContainerWebFeed.recordType:
addOrUpdateContainerWebFeed(record)
default:
assertionFailure("Unknown record type: \(record.recordType)")
}
@ -37,9 +42,11 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
func cloudKitDidDelete(recordType: CKRecord.RecordType, recordID: CKRecord.ID) {
switch recordType {
case CloudKitAccountZone.CloudKitWebFeed.recordType:
removeWebFeed(recordID.externalID)
break
case CloudKitAccountZone.CloudKitContainer.recordType:
removeContainer(recordID.externalID)
case CloudKitAccountZone.CloudKitContainerWebFeed.recordType:
removeContainerWebFeed(recordID.externalID)
default:
assertionFailure("Unknown record type: \(recordID.externalID)")
}
@ -53,20 +60,12 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
if let webFeed = account.existingWebFeed(withExternalID: record.externalID) {
webFeed.editedName = editedName
} else {
if let urlString = record[CloudKitAccountZone.CloudKitWebFeed.Fields.url] as? String, let url = URL(string: urlString) {
downloadAndAddWebFeed(url: url, editedName: editedName, externalID: record.externalID)
} else {
os_log(.error, log: self.log, "Failed to add or update web feed.")
if let urlString = record[CloudKitAccountZone.CloudKitWebFeed.Fields.url] as? String {
unclaimedWebFeeds[record.externalID] = UnclaimedWebFeed(url: urlString, editedName: editedName)
}
}
}
func removeWebFeed(_ externalID: String) {
if let webFeed = account?.existingWebFeed(withExternalID: externalID) {
account?.removeWebFeed(webFeed)
}
}
func addOrUpdateContainer(_ record: CKRecord) {
guard let account = account,
let name = record[CloudKitAccountZone.CloudKitContainer.Fields.name] as? String,
@ -87,17 +86,34 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
}
}
}
private extension CloudKitAcountZoneDelegate {
func downloadAndAddWebFeed(url: URL, editedName: String?, externalID: String) {
guard let account = account else { return }
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 webFeed = account.createWebFeed(with: editedName, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
webFeed.editedName = editedName
webFeed.externalID = externalID
account.addWebFeed(webFeed)
let containerWebFeedExternalID = record.externalID
let containerExternalID = containerReference.recordID.externalID
let webFeedExternalID = webFeedReference.recordID.externalID
guard let container = account.existingContainer(withExternalID: containerExternalID) else { return }
if let webFeed = account.existingWebFeed(withExternalID: webFeedExternalID) {
webFeed.folderRelationship?[containerWebFeedExternalID] = containerExternalID
container.addWebFeed(webFeed)
return
}
guard let unclaimedWebFeed = unclaimedWebFeeds[webFeedExternalID] else { return }
unclaimedWebFeeds.removeValue(forKey: webFeedExternalID)
let webFeed = account.createWebFeed(with: nil, url: unclaimedWebFeed.url, webFeedID: unclaimedWebFeed.url, homePageURL: nil)
webFeed.editedName = unclaimedWebFeed.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
@ -106,7 +122,22 @@ private extension CloudKitAcountZoneDelegate {
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)
}
}

View File

@ -96,8 +96,8 @@ extension CloudKitZone {
}
}
func save(record: CKRecord, completion: @escaping (Result<Void, Error>) -> Void) {
modify(recordsToSave: [record], recordIDsToDelete: [], completion: completion)
func save(_ records: [CKRecord], completion: @escaping (Result<Void, Error>) -> Void) {
modify(recordsToSave: records, recordIDsToDelete: [], completion: completion)
}
func delete(externalID: String?, completion: @escaping (Result<Void, Error>) -> Void) {

View File

@ -39,6 +39,7 @@ public protocol Container: class, ContainerIdentifiable {
func hasWebFeed(withURL url: String) -> Bool
func existingWebFeed(withWebFeedID: String) -> WebFeed?
func existingWebFeed(withURL url: String) -> WebFeed?
func existingWebFeed(withExternalID externalID: String) -> WebFeed?
func existingFolder(with name: String) -> Folder?
func existingFolder(withID: Int) -> Folder?
@ -117,6 +118,15 @@ public extension Container {
}
return nil
}
func existingWebFeed(withExternalID externalID: String) -> WebFeed? {
for feed in flattenedWebFeeds() {
if feed.externalID == externalID {
return feed
}
}
return nil
}
func existingFolder(with name: String) -> Folder? {
guard let folders = folders else {