Remove username from WebFeed and put it in the URL

This commit is contained in:
Maurice Parker 2020-04-24 13:33:43 -05:00
parent dedb207cae
commit d1ca2cac79
11 changed files with 44 additions and 77 deletions

View File

@ -220,7 +220,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
let editedName = name == nil || name!.isEmpty ? nil : name let editedName = name == nil || name!.isEmpty ? nil : name
// Username should be part of the URL on new feed adds // Username should be part of the URL on new feed adds
if let feedProvider = FeedProviderManager.shared.best(for: urlComponents, with: nil) { if let feedProvider = FeedProviderManager.shared.best(for: urlComponents) {
createProviderWebFeed(for: account, urlComponents: urlComponents, editedName: editedName, container: container, feedProvider: feedProvider, completion: completion) createProviderWebFeed(for: account, urlComponents: urlComponents, editedName: editedName, container: container, feedProvider: feedProvider, completion: completion)
} else { } else {
createRSSWebFeed(for: account, url: url, editedName: editedName, container: container, completion: completion) createRSSWebFeed(for: account, url: url, editedName: editedName, container: container, completion: completion)
@ -292,7 +292,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, username: feed.username, container: container) { result in accountZone.createWebFeed(url: feed.url, 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,7 +494,7 @@ private extension CloudKitAccountDelegate {
self.sendArticleStatus(for: account) { result in self.sendArticleStatus(for: account) { result in
switch result { switch result {
case .success: case .success:
self.refreshProgress.completeTask() self.refreshProgress.clear()
completion(.success(())) completion(.success(()))
case .failure(let error): case .failure(let error):
fail(error) fail(error)
@ -543,6 +543,7 @@ private extension CloudKitAccountDelegate {
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
self.refreshWebFeeds(account, webFeeds) { self.refreshWebFeeds(account, webFeeds) {
self.refreshProgress.clear()
account.metadata.lastArticleFetchEndTime = Date() account.metadata.lastArticleFetchEndTime = Date()
} }
@ -574,7 +575,7 @@ private extension CloudKitAccountDelegate {
refreshProgress.addToNumberOfTasksAndRemaining(2) refreshProgress.addToNumberOfTasksAndRemaining(2)
for webFeed in webFeeds { for webFeed in webFeeds {
if let components = URLComponents(string: webFeed.url), let feedProvider = FeedProviderManager.shared.best(for: components, with: webFeed.username) { if let components = URLComponents(string: webFeed.url), let feedProvider = FeedProviderManager.shared.best(for: components) {
group.enter() group.enter()
feedProvider.refresh(webFeed) { result in feedProvider.refresh(webFeed) { result in
switch result { switch result {
@ -637,23 +638,19 @@ private extension CloudKitAccountDelegate {
case .success(let name): case .success(let name):
// Move the user to the WebFeed and out of the URL guard let urlString = urlComponents.url?.absoluteString else {
var newURLComponents = urlComponents
newURLComponents.user = nil
guard let newURLString = newURLComponents.url?.absoluteString else {
completion(.failure(AccountError.createErrorNotFound)) completion(.failure(AccountError.createErrorNotFound))
return return
} }
self.accountZone.createWebFeed(url: newURLString, editedName: editedName, username: urlComponents.user, container: container) { result in self.accountZone.createWebFeed(url: urlString, editedName: editedName, container: container) { result in
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
switch result { switch result {
case .success(let externalID): case .success(let externalID):
let feed = account.createWebFeed(with: name, url: newURLString, webFeedID: newURLString, homePageURL: nil) let feed = account.createWebFeed(with: name, url: urlString, webFeedID: urlString, homePageURL: nil)
feed.editedName = editedName feed.editedName = editedName
feed.username = urlComponents.user
feed.externalID = externalID feed.externalID = externalID
container.addWebFeed(feed) container.addWebFeed(feed)
@ -662,7 +659,7 @@ private extension CloudKitAccountDelegate {
switch result { switch result {
case .success(let parsedItems): case .success(let parsedItems):
account.update(newURLString, with: parsedItems) { result in account.update(urlString, with: parsedItems) { result in
switch result { switch result {
case .success(let articleChanges): case .success(let articleChanges):
@ -672,18 +669,20 @@ private extension CloudKitAccountDelegate {
self.articlesZone.deleteArticles(deletedArticles) { _ in self.articlesZone.deleteArticles(deletedArticles) { _ in
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
self.articlesZone.sendNewArticles(newArticles) { _ in self.articlesZone.sendNewArticles(newArticles) { _ in
self.refreshProgress.completeTask() self.refreshProgress.clear()
completion(.success(feed)) completion(.success(feed))
} }
} }
case .failure(let error): case .failure(let error):
self.refreshProgress.clear()
completion(.failure(error)) completion(.failure(error))
} }
} }
case .failure: case .failure:
self.refreshProgress.clear()
completion(.failure(AccountError.createErrorNotFound)) completion(.failure(AccountError.createErrorNotFound))
} }
} }
@ -723,7 +722,7 @@ private extension CloudKitAccountDelegate {
return return
} }
self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString, editedName: editedName, username: nil, container: container) { result in self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString, editedName: editedName, container: container) { result in
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
switch result { switch result {
@ -749,17 +748,19 @@ private extension CloudKitAccountDelegate {
self.articlesZone.deleteArticles(deletedArticles) { _ in self.articlesZone.deleteArticles(deletedArticles) { _ in
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
self.articlesZone.sendNewArticles(newArticles) { _ in self.articlesZone.sendNewArticles(newArticles) { _ in
self.refreshProgress.completeTask() self.refreshProgress.clear()
completion(.success(feed)) completion(.success(feed))
} }
} }
case .failure(let error): case .failure(let error):
self.refreshProgress.clear()
completion(.failure(error)) completion(.failure(error))
} }
} }
} else { } else {
self.refreshProgress.clear()
completion(.success(feed)) completion(.success(feed))
} }
@ -810,7 +811,6 @@ extension CloudKitAccountDelegate: LocalAccountRefresherDelegate {
} }
func localAccountRefresherDidFinish(_ refresher: LocalAccountRefresher) { func localAccountRefresherDidFinish(_ refresher: LocalAccountRefresher) {
refreshProgress.clear()
} }
} }

View File

@ -29,7 +29,6 @@ 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 username = "username"
static let containerExternalIDs = "containerExternalIDs" static let containerExternalIDs = "containerExternalIDs"
} }
} }
@ -82,16 +81,13 @@ 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?, username: String?, container: Container, completion: @escaping (Result<String, Error>) -> Void) { func createWebFeed(url: 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
if let editedName = editedName { if let editedName = editedName {
record[CloudKitWebFeed.Fields.editedName] = editedName record[CloudKitWebFeed.Fields.editedName] = editedName
} }
if let username = username {
record[CloudKitWebFeed.Fields.username] = username
}
guard let containerExternalID = container.externalID else { guard let containerExternalID = container.externalID else {
completion(.failure(CloudKitZoneError.invalidParameter)) completion(.failure(CloudKitZoneError.invalidParameter))

View File

@ -13,7 +13,7 @@ import CloudKit
class CloudKitAcountZoneDelegate: CloudKitZoneDelegate { class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
private typealias UnclaimedWebFeed = (url: URL, editedName: String?, username: String?, webFeedExternalID: String) private typealias UnclaimedWebFeed = (url: URL, 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")
@ -72,11 +72,10 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
} }
let editedName = record[CloudKitAccountZone.CloudKitWebFeed.Fields.editedName] as? String let editedName = record[CloudKitAccountZone.CloudKitWebFeed.Fields.editedName] as? String
let username = record[CloudKitAccountZone.CloudKitWebFeed.Fields.username] as? String
if let webFeed = account.existingWebFeed(withExternalID: record.externalID) { if let webFeed = account.existingWebFeed(withExternalID: record.externalID) {
updateWebFeed(webFeed, editedName: editedName, username: username, containerExternalIDs: containerExternalIDs) updateWebFeed(webFeed, editedName: editedName, containerExternalIDs: containerExternalIDs)
completion() completion()
} else { } else {
@ -85,11 +84,11 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
for containerExternalID in containerExternalIDs { for containerExternalID in containerExternalIDs {
group.enter() group.enter()
if let container = account.existingContainer(withExternalID: containerExternalID) { if let container = account.existingContainer(withExternalID: containerExternalID) {
createWebFeedIfNecessary(url: url, editedName: editedName, username: username, webFeedExternalID: record.externalID, container: container) { webFeed in createWebFeedIfNecessary(url: url, editedName: editedName, webFeedExternalID: record.externalID, container: container) { webFeed in
group.leave() group.leave()
} }
} else { } else {
addUnclaimedWebFeed(url: url, editedName: editedName, username: username, webFeedExternalID: record.externalID, containerExternalID: containerExternalID) addUnclaimedWebFeed(url: url, editedName: editedName, webFeedExternalID: record.externalID, containerExternalID: containerExternalID)
group.leave() group.leave()
} }
} }
@ -130,7 +129,7 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
for unclaimedWebFeed in unclaimedWebFeeds { for unclaimedWebFeed in unclaimedWebFeeds {
group.enter() group.enter()
createWebFeedIfNecessary(url: unclaimedWebFeed.url, editedName: unclaimedWebFeed.editedName, username: unclaimedWebFeed.username, webFeedExternalID: unclaimedWebFeed.webFeedExternalID, container: folder) { webFeed in createWebFeedIfNecessary(url: unclaimedWebFeed.url, editedName: unclaimedWebFeed.editedName, webFeedExternalID: unclaimedWebFeed.webFeedExternalID, container: folder) { webFeed in
group.leave() group.leave()
} }
} }
@ -157,10 +156,9 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
private extension CloudKitAcountZoneDelegate { private extension CloudKitAcountZoneDelegate {
func updateWebFeed(_ webFeed: WebFeed, editedName: String?, username: String?, containerExternalIDs: [String]) { func updateWebFeed(_ webFeed: WebFeed, editedName: String?, containerExternalIDs: [String]) {
guard let account = account else { return } guard let account = account else { return }
webFeed.editedName = editedName webFeed.editedName = editedName
webFeed.username = username
let existingContainers = account.existingContainers(withWebFeed: webFeed) let existingContainers = account.existingContainers(withWebFeed: webFeed)
let existingContainerExternalIds = existingContainers.compactMap { $0.externalID } let existingContainerExternalIds = existingContainers.compactMap { $0.externalID }
@ -181,7 +179,7 @@ private extension CloudKitAcountZoneDelegate {
} }
} }
func createWebFeedIfNecessary(url: URL, editedName: String?, username: String?, webFeedExternalID: String, container: Container, completion: @escaping (WebFeed) -> Void) { func createWebFeedIfNecessary(url: URL, editedName: String?, webFeedExternalID: String, container: Container, completion: @escaping (WebFeed) -> Void) {
guard let account = account, let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return } guard let account = account, let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return }
if let webFeed = account.existingWebFeed(withExternalID: webFeedExternalID) { if let webFeed = account.existingWebFeed(withExternalID: webFeedExternalID) {
@ -191,10 +189,9 @@ private extension CloudKitAcountZoneDelegate {
let webFeed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) let webFeed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
webFeed.editedName = editedName webFeed.editedName = editedName
webFeed.username = username
webFeed.externalID = webFeedExternalID webFeed.externalID = webFeedExternalID
if let feedProvider = FeedProviderManager.shared.best(for: urlComponents, with: username) { if let feedProvider = FeedProviderManager.shared.best(for: urlComponents) {
refreshProgress?.addToNumberOfTasksAndRemaining(2) refreshProgress?.addToNumberOfTasksAndRemaining(2)
feedProvider.assignName(urlComponents) { result in feedProvider.assignName(urlComponents) { result in
@ -241,13 +238,13 @@ private extension CloudKitAcountZoneDelegate {
} }
func addUnclaimedWebFeed(url: URL, editedName: String?, username: String?, webFeedExternalID: String, containerExternalID: String) { 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, username: username, webFeedExternalID: webFeedExternalID)) unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, 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, username: username, webFeedExternalID: webFeedExternalID)) unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, editedName: editedName, webFeedExternalID: webFeedExternalID))
self.unclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds self.unclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
} }
} }

View File

@ -19,7 +19,7 @@ public enum FeedProviderAbility {
public protocol FeedProvider { public protocol FeedProvider {
/// Informs the caller of the ability for this feed provider to service the given URL /// Informs the caller of the ability for this feed provider to service the given URL
func ability(_ urlComponents: URLComponents, forUsername: String?) -> FeedProviderAbility func ability(_ urlComponents: URLComponents) -> FeedProviderAbility
/// Provide the iconURL of the given URL /// Provide the iconURL of the given URL
func iconURL(_ urlComponents: URLComponents, completion: @escaping (Result<String, Error>) -> Void) func iconURL(_ urlComponents: URLComponents, completion: @escaping (Result<String, Error>) -> Void)

View File

@ -17,21 +17,21 @@ public final class FeedProviderManager {
public static let shared = FeedProviderManager() public static let shared = FeedProviderManager()
public weak var delegate: FeedProviderManagerDelegate? public weak var delegate: FeedProviderManagerDelegate?
public func best(for offered: URLComponents, with username: String?) -> FeedProvider? { public func best(for offered: URLComponents) -> FeedProvider? {
if let owner = feedProviderMatching(offered, forUsername: username, ability: .owner) { if let owner = feedProviderMatching(offered, ability: .owner) {
return owner return owner
} }
return feedProviderMatching(offered, forUsername: username, ability: .available) return feedProviderMatching(offered, ability: .available)
} }
} }
private extension FeedProviderManager { private extension FeedProviderManager {
func feedProviderMatching(_ offered: URLComponents, forUsername username: String?, ability: FeedProviderAbility) -> FeedProvider? { func feedProviderMatching(_ offered: URLComponents, ability: FeedProviderAbility) -> FeedProvider? {
if let delegate = delegate { if let delegate = delegate {
for feedProvider in delegate.activeFeedProviders { for feedProvider in delegate.activeFeedProviders {
if feedProvider.ability(offered, forUsername: username) == ability { if feedProvider.ability(offered) == ability {
return feedProvider return feedProvider
} }
} }

View File

@ -88,12 +88,12 @@ public struct TwitterFeedProvider: FeedProvider {
version: .oauth1) version: .oauth1)
} }
public func ability(_ urlComponents: URLComponents, forUsername username: String?) -> FeedProviderAbility { public func ability(_ urlComponents: URLComponents) -> FeedProviderAbility {
guard urlComponents.host?.hasSuffix("twitter.com") ?? false else { guard urlComponents.host?.hasSuffix("twitter.com") ?? false else {
return .none return .none
} }
if let username = username { if let username = urlComponents.user {
if username == screenName { if username == screenName {
return .owner return .owner
} else { } else {
@ -101,10 +101,6 @@ public struct TwitterFeedProvider: FeedProvider {
} }
} }
if let user = urlComponents.user, user == screenName {
return .owner
}
return .available return .available
} }

View File

@ -56,7 +56,7 @@ final class LocalAccountDelegate: AccountDelegate {
let group = DispatchGroup() let group = DispatchGroup()
for webFeed in webFeeds { for webFeed in webFeeds {
if let components = URLComponents(string: webFeed.url), let feedProvider = FeedProviderManager.shared.best(for: components, with: webFeed.username) { if let components = URLComponents(string: webFeed.url), let feedProvider = FeedProviderManager.shared.best(for: components) {
refreshProgress.addToNumberOfTasksAndRemaining(1) refreshProgress.addToNumberOfTasksAndRemaining(1)
group.enter() group.enter()
feedProvider.refresh(webFeed) { result in feedProvider.refresh(webFeed) { result in
@ -147,7 +147,7 @@ final class LocalAccountDelegate: AccountDelegate {
} }
// Username should be part of the URL on new feed adds // Username should be part of the URL on new feed adds
if let feedProvider = FeedProviderManager.shared.best(for: urlComponents, with: nil) { if let feedProvider = FeedProviderManager.shared.best(for: urlComponents) {
createProviderWebFeed(for: account, urlComponents: urlComponents, editedName: name, container: container, feedProvider: feedProvider, completion: completion) createProviderWebFeed(for: account, urlComponents: urlComponents, editedName: name, container: container, feedProvider: feedProvider, completion: completion)
} else { } else {
createRSSWebFeed(for: account, url: url, editedName: name, container: container, completion: completion) createRSSWebFeed(for: account, url: url, editedName: name, container: container, completion: completion)
@ -260,24 +260,20 @@ private extension LocalAccountDelegate {
case .success(let name): case .success(let name):
// Move the user to the WebFeed and out of the URL guard let urlString = urlComponents.url?.absoluteString else {
var newURLComponents = urlComponents
newURLComponents.user = nil
guard let newURLString = newURLComponents.url?.absoluteString else {
completion(.failure(AccountError.createErrorNotFound)) completion(.failure(AccountError.createErrorNotFound))
return return
} }
let feed = account.createWebFeed(with: name, url: newURLString, webFeedID: newURLString, homePageURL: nil) let feed = account.createWebFeed(with: name, url: urlString, webFeedID: urlString, homePageURL: nil)
feed.editedName = editedName feed.editedName = editedName
feed.username = urlComponents.user
container.addWebFeed(feed) container.addWebFeed(feed)
feedProvider.refresh(feed) { result in feedProvider.refresh(feed) { result in
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
switch result { switch result {
case .success(let parsedItems): case .success(let parsedItems):
account.update(newURLString, with: parsedItems) { _ in account.update(urlString, with: parsedItems) { _ in
completion(.success(feed)) completion(.success(feed))
} }
case .failure: case .failure:

View File

@ -77,15 +77,6 @@ public final class WebFeed: Feed, Renamable, Hashable {
} }
} }
public var username: String? {
get {
return metadata.username
}
set {
metadata.username = newValue
}
}
public var name: String? public var name: String?
public var authors: Set<Author>? { public var authors: Set<Author>? {

View File

@ -21,7 +21,6 @@ final class WebFeedMetadata: Codable {
case homePageURL case homePageURL
case iconURL case iconURL
case faviconURL case faviconURL
case username
case editedName case editedName
case authors case authors
case contentHash case contentHash
@ -64,14 +63,6 @@ final class WebFeedMetadata: Codable {
} }
} }
var username: String? {
didSet {
if username != oldValue {
valueDidChange(.username)
}
}
}
var editedName: String? { var editedName: String? {
didSet { didSet {
if editedName != oldValue { if editedName != oldValue {

View File

@ -130,9 +130,9 @@ private extension ExtensionPointManager {
} }
} }
func feedProviderMatching(_ offered: URLComponents, forUsername username: String?, ability: FeedProviderAbility) -> FeedProvider? { func feedProviderMatching(_ offered: URLComponents, ability: FeedProviderAbility) -> FeedProvider? {
for extensionPoint in activeExtensionPoints.values { for extensionPoint in activeExtensionPoints.values {
if let feedProvider = extensionPoint as? FeedProvider, feedProvider.ability(offered, forUsername: username) == ability { if let feedProvider = extensionPoint as? FeedProvider, feedProvider.ability(offered) == ability {
return feedProvider return feedProvider
} }
} }

View File

@ -118,7 +118,7 @@ public final class WebFeedIconDownloader {
return nil return nil
} }
if let components = URLComponents(string: feed.url), let feedProvider = FeedProviderManager.shared.best(for: components, with: nil) { if let components = URLComponents(string: feed.url), let feedProvider = FeedProviderManager.shared.best(for: components) {
feedProvider.iconURL(components) { result in feedProvider.iconURL(components) { result in
switch result { switch result {
case .success(let feedProviderURL): case .success(let feedProviderURL):