Id -> ID renaming.

This commit is contained in:
Brent Simmons 2024-04-07 16:09:23 -07:00
parent c62e3293a6
commit 53215c1f80
70 changed files with 535 additions and 535 deletions

View File

@ -1038,13 +1038,13 @@ private extension FeedbinAccountDelegate {
os_log(.debug, log: log, "Syncing feeds with %ld subscriptions.", subscriptions.count) os_log(.debug, log: log, "Syncing feeds with %ld subscriptions.", subscriptions.count)
let subFeedIds = subscriptions.map { String($0.feedID) } let subFeedIDs = subscriptions.map { String($0.feedID) }
// Remove any feeds that are no longer in the subscriptions // Remove any feeds that are no longer in the subscriptions
if let folders = account.folders { if let folders = account.folders {
for folder in folders { for folder in folders {
for feed in folder.topLevelFeeds { for feed in folder.topLevelFeeds {
if !subFeedIds.contains(feed.feedID) { if !subFeedIDs.contains(feed.feedID) {
folder.removeFeed(feed) folder.removeFeed(feed)
} }
} }
@ -1052,7 +1052,7 @@ private extension FeedbinAccountDelegate {
} }
for feed in account.topLevelFeeds { for feed in account.topLevelFeeds {
if !subFeedIds.contains(feed.feedID) { if !subFeedIDs.contains(feed.feedID) {
account.removeFeed(feed) account.removeFeed(feed)
} }
} }
@ -1061,9 +1061,9 @@ private extension FeedbinAccountDelegate {
var subscriptionsToAdd = Set<FeedbinSubscription>() var subscriptionsToAdd = Set<FeedbinSubscription>()
subscriptions.forEach { subscription in subscriptions.forEach { subscription in
let subFeedId = String(subscription.feedID) let subFeedID = String(subscription.feedID)
if let feed = account.existingFeed(withFeedID: subFeedId) { if let feed = account.existingFeed(withFeedID: subFeedID) {
feed.name = subscription.name feed.name = subscription.name
// If the name has been changed on the server remove the locally edited name // If the name has been changed on the server remove the locally edited name
feed.editedName = nil feed.editedName = nil
@ -1122,11 +1122,11 @@ private extension FeedbinAccountDelegate {
} }
// Add any feeds not in the folder // Add any feeds not in the folder
let folderFeedIds = folder.topLevelFeeds.map { $0.feedID } let folderFeedIDs = folder.topLevelFeeds.map { $0.feedID }
for tagging in groupedTaggings { for tagging in groupedTaggings {
let taggingFeedID = String(tagging.feedID) let taggingFeedID = String(tagging.feedID)
if !folderFeedIds.contains(taggingFeedID) { if !folderFeedIDs.contains(taggingFeedID) {
guard let feed = account.existingFeed(withFeedID: taggingFeedID) else { guard let feed = account.existingFeed(withFeedID: taggingFeedID) else {
continue continue
} }

View File

@ -84,13 +84,13 @@ extension NewsBlurAccountDelegate {
os_log(.debug, log: log, "Syncing feeds with %ld feeds.", feeds.count) os_log(.debug, log: log, "Syncing feeds with %ld feeds.", feeds.count)
let newsBlurFeedIds = feeds.map { String($0.feedID) } let newsBlurFeedIDs = feeds.map { String($0.feedID) }
// Remove any feeds that are no longer in the subscriptions // Remove any feeds that are no longer in the subscriptions
if let folders = account.folders { if let folders = account.folders {
for folder in folders { for folder in folders {
for feed in folder.topLevelFeeds { for feed in folder.topLevelFeeds {
if !newsBlurFeedIds.contains(feed.feedID) { if !newsBlurFeedIDs.contains(feed.feedID) {
folder.removeFeed(feed) folder.removeFeed(feed)
} }
} }
@ -98,7 +98,7 @@ extension NewsBlurAccountDelegate {
} }
for feed in account.topLevelFeeds { for feed in account.topLevelFeeds {
if !newsBlurFeedIds.contains(feed.feedID) { if !newsBlurFeedIDs.contains(feed.feedID) {
account.removeFeed(feed) account.removeFeed(feed)
} }
} }
@ -106,9 +106,9 @@ extension NewsBlurAccountDelegate {
// Add any feeds we don't have and update any we do // Add any feeds we don't have and update any we do
var feedsToAdd = Set<NewsBlurFeed>() var feedsToAdd = Set<NewsBlurFeed>()
feeds.forEach { feed in feeds.forEach { feed in
let subFeedId = String(feed.feedID) let subFeedID = String(feed.feedID)
if let feed = account.existingFeed(withFeedID: subFeedId) { if let feed = account.existingFeed(withFeedID: subFeedID) {
feed.name = feed.name feed.name = feed.name
// If the name has been changed on the server remove the locally edited name // If the name has been changed on the server remove the locally edited name
feed.editedName = nil feed.editedName = nil
@ -169,11 +169,11 @@ extension NewsBlurAccountDelegate {
} }
// Add any feeds not in the folder // Add any feeds not in the folder
let folderFeedIds = folder.topLevelFeeds.map { $0.feedID } let folderFeedIDs = folder.topLevelFeeds.map { $0.feedID }
for relationship in folderRelationships { for relationship in folderRelationships {
let folderFeedID = String(relationship.feedID) let folderFeedID = String(relationship.feedID)
if !folderFeedIds.contains(folderFeedID) { if !folderFeedIDs.contains(folderFeedID) {
guard let feed = account.existingFeed(withFeedID: folderFeedID) else { guard let feed = account.existingFeed(withFeedID: folderFeedID) else {
continue continue
} }

View File

@ -816,7 +816,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
} }
func accountDidInitialize(_ account: Account) { func accountDidInitialize(_ account: Account) {
credentials = try? account.retrieveCredentials(type: .newsBlurSessionId) credentials = try? account.retrieveCredentials(type: .newsBlurSessionID)
} }
func accountWillBeDeleted(_ account: Account) { func accountWillBeDeleted(_ account: Account) {

View File

@ -570,13 +570,13 @@ private extension ReaderAPIAccountDelegate {
os_log(.debug, log: log, "Syncing feeds with %ld subscriptions.", subscriptions.count) os_log(.debug, log: log, "Syncing feeds with %ld subscriptions.", subscriptions.count)
let subFeedIds = subscriptions.map { $0.feedID } let subFeedIDs = subscriptions.map { $0.feedID }
// Remove any feeds that are no longer in the subscriptions // Remove any feeds that are no longer in the subscriptions
if let folders = account.folders { if let folders = account.folders {
for folder in folders { for folder in folders {
for feed in folder.topLevelFeeds { for feed in folder.topLevelFeeds {
if !subFeedIds.contains(feed.feedID) { if !subFeedIDs.contains(feed.feedID) {
folder.removeFeed(feed) folder.removeFeed(feed)
} }
} }
@ -584,7 +584,7 @@ private extension ReaderAPIAccountDelegate {
} }
for feed in account.topLevelFeeds { for feed in account.topLevelFeeds {
if !subFeedIds.contains(feed.feedID) { if !subFeedIDs.contains(feed.feedID) {
account.clearFeedMetadata(feed) account.clearFeedMetadata(feed)
account.removeFeed(feed) account.removeFeed(feed)
} }
@ -644,11 +644,11 @@ private extension ReaderAPIAccountDelegate {
} }
// Add any feeds not in the folder // Add any feeds not in the folder
let folderFeedIds = folder.topLevelFeeds.map { $0.feedID } let folderFeedIDs = folder.topLevelFeeds.map { $0.feedID }
for subscription in groupedTaggings { for subscription in groupedTaggings {
let taggingFeedID = subscription.feedID let taggingFeedID = subscription.feedID
if !folderFeedIds.contains(taggingFeedID) { if !folderFeedIDs.contains(taggingFeedID) {
guard let feed = account.existingFeed(withFeedID: taggingFeedID) else { guard let feed = account.existingFeed(withFeedID: taggingFeedID) else {
continue continue
} }

View File

@ -247,8 +247,8 @@ enum CloudKitAccountZoneError: LocalizedError {
query(ckQuery) { result in query(ckQuery) { result in
switch result { switch result {
case .success(let records): case .success(let records):
let feedExternalIds = records.map { $0.externalID } let feedExternalIDs = records.map { $0.externalID }
completion(.success(feedExternalIds)) completion(.success(feedExternalIDs))
case .failure(let error): case .failure(let error):
completion(.failure(error)) completion(.failure(error))
} }

View File

@ -147,9 +147,9 @@ private extension CloudKitAcountZoneDelegate {
feed.homePageURL = homePageURL feed.homePageURL = homePageURL
let existingContainers = account.existingContainers(withFeed: feed) let existingContainers = account.existingContainers(withFeed: feed)
let existingContainerExternalIds = existingContainers.compactMap { $0.externalID } let existingContainerExternalIDs = existingContainers.compactMap { $0.externalID }
let diff = containerExternalIDs.difference(from: existingContainerExternalIds) let diff = containerExternalIDs.difference(from: existingContainerExternalIDs)
for change in diff { for change in diff {
switch change { switch change {

View File

@ -294,13 +294,13 @@ final class FeedlyAPICaller {
completion(.failure(CredentialsError.incompleteCredentials)) completion(.failure(CredentialsError.incompleteCredentials))
} }
} }
guard let encodedId = encodeForURLPath(id) else { guard let encodedID = encodeForURLPath(id) else {
return DispatchQueue.main.async { return DispatchQueue.main.async {
completion(.failure(FeedlyAccountDelegateError.unexpectedResourceId(id))) completion(.failure(FeedlyAccountDelegateError.unexpectedResourceID(id)))
} }
} }
var components = baseUrlComponents var components = baseUrlComponents
components.percentEncodedPath = "/v3/collections/\(encodedId)" components.percentEncodedPath = "/v3/collections/\(encodedID)"
guard let url = components.url else { guard let url = components.url else {
fatalError("\(components) does not produce a valid URL.") fatalError("\(components) does not produce a valid URL.")
@ -326,7 +326,7 @@ final class FeedlyAPICaller {
} }
} }
func removeFeed(_ feedId: String, fromCollectionWith collectionId: String, completion: @escaping (Result<Void, Error>) -> ()) { func removeFeed(_ feedId: String, fromCollectionWith collectionID: String, completion: @escaping (Result<Void, Error>) -> ()) {
guard !isSuspended else { guard !isSuspended else {
return DispatchQueue.main.async { return DispatchQueue.main.async {
completion(.failure(TransportError.suspended)) completion(.failure(TransportError.suspended))
@ -339,14 +339,14 @@ final class FeedlyAPICaller {
} }
} }
guard let encodedCollectionId = encodeForURLPath(collectionId) else { guard let encodedCollectionID = encodeForURLPath(collectionID) else {
return DispatchQueue.main.async { return DispatchQueue.main.async {
completion(.failure(FeedlyAccountDelegateError.unexpectedResourceId(collectionId))) completion(.failure(FeedlyAccountDelegateError.unexpectedResourceID(collectionID)))
} }
} }
var components = baseUrlComponents var components = baseUrlComponents
components.percentEncodedPath = "/v3/collections/\(encodedCollectionId)/feeds/.mdelete" components.percentEncodedPath = "/v3/collections/\(encodedCollectionID)/feeds/.mdelete"
guard let url = components.url else { guard let url = components.url else {
fatalError("\(components) does not produce a valid URL.") fatalError("\(components) does not produce a valid URL.")
@ -390,7 +390,7 @@ final class FeedlyAPICaller {
extension FeedlyAPICaller: FeedlyAddFeedToCollectionService { extension FeedlyAPICaller: FeedlyAddFeedToCollectionService {
func addFeed(with feedId: FeedlyFeedResourceId, title: String? = nil, toCollectionWith collectionId: String, completion: @escaping (Result<[FeedlyFeed], Error>) -> ()) { func addFeed(with feedId: FeedlyFeedResourceID, title: String? = nil, toCollectionWith collectionID: String, completion: @escaping (Result<[FeedlyFeed], Error>) -> ()) {
guard !isSuspended else { guard !isSuspended else {
return DispatchQueue.main.async { return DispatchQueue.main.async {
completion(.failure(TransportError.suspended)) completion(.failure(TransportError.suspended))
@ -403,13 +403,13 @@ extension FeedlyAPICaller: FeedlyAddFeedToCollectionService {
} }
} }
guard let encodedId = encodeForURLPath(collectionId) else { guard let encodedID = encodeForURLPath(collectionID) else {
return DispatchQueue.main.async { return DispatchQueue.main.async {
completion(.failure(FeedlyAccountDelegateError.unexpectedResourceId(collectionId))) completion(.failure(FeedlyAccountDelegateError.unexpectedResourceID(collectionID)))
} }
} }
var components = baseUrlComponents var components = baseUrlComponents
components.percentEncodedPath = "/v3/collections/\(encodedId)/feeds" components.percentEncodedPath = "/v3/collections/\(encodedID)/feeds"
guard let url = components.url else { guard let url = components.url else {
fatalError("\(components) does not produce a valid URL.") fatalError("\(components) does not produce a valid URL.")
@ -606,7 +606,7 @@ extension FeedlyAPICaller: FeedlyGetCollectionsService {
extension FeedlyAPICaller: FeedlyGetStreamContentsService { extension FeedlyAPICaller: FeedlyGetStreamContentsService {
@MainActor func getStreamContents(for resource: FeedlyResourceId, continuation: String? = nil, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStream, Error>) -> ()) { @MainActor func getStreamContents(for resource: FeedlyResourceID, continuation: String? = nil, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStream, Error>) -> ()) {
guard !isSuspended else { guard !isSuspended else {
return DispatchQueue.main.async { return DispatchQueue.main.async {
completion(.failure(TransportError.suspended)) completion(.failure(TransportError.suspended))
@ -672,9 +672,9 @@ extension FeedlyAPICaller: FeedlyGetStreamContentsService {
} }
} }
extension FeedlyAPICaller: FeedlyGetStreamIdsService { extension FeedlyAPICaller: FeedlyGetStreamIDsService {
@MainActor func getStreamIds(for resource: FeedlyResourceId, continuation: String? = nil, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStreamIds, Error>) -> ()) { @MainActor func getStreamIDs(for resource: FeedlyResourceID, continuation: String? = nil, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStreamIDs, Error>) -> ()) {
guard !isSuspended else { guard !isSuspended else {
return DispatchQueue.main.async { return DispatchQueue.main.async {
completion(.failure(TransportError.suspended)) completion(.failure(TransportError.suspended))
@ -725,7 +725,7 @@ extension FeedlyAPICaller: FeedlyGetStreamIdsService {
request.addValue("application/json", forHTTPHeaderField: "Accept-Type") request.addValue("application/json", forHTTPHeaderField: "Accept-Type")
request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization) request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization)
send(request: request, resultType: FeedlyStreamIds.self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in send(request: request, resultType: FeedlyStreamIDs.self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in
switch result { switch result {
case .success(let (_, collections)): case .success(let (_, collections)):
if let response = collections { if let response = collections {
@ -800,10 +800,10 @@ extension FeedlyAPICaller: FeedlyMarkArticlesService {
private struct MarkerEntriesBody: Encodable { private struct MarkerEntriesBody: Encodable {
let type = "entries" let type = "entries"
var action: String var action: String
var entryIds: [String] var entryIDs: [String]
} }
func mark(_ articleIds: Set<String>, as action: FeedlyMarkAction, completion: @escaping (Result<Void, Error>) -> ()) { func mark(_ articleIDs: Set<String>, as action: FeedlyMarkAction, completion: @escaping (Result<Void, Error>) -> ()) {
guard !isSuspended else { guard !isSuspended else {
return DispatchQueue.main.async { return DispatchQueue.main.async {
completion(.failure(TransportError.suspended)) completion(.failure(TransportError.suspended))
@ -822,11 +822,11 @@ extension FeedlyAPICaller: FeedlyMarkArticlesService {
fatalError("\(components) does not produce a valid URL.") fatalError("\(components) does not produce a valid URL.")
} }
let articleIdChunks = Array(articleIds).chunked(into: 300) let articleIDChunks = Array(articleIDs).chunked(into: 300)
let dispatchGroup = DispatchGroup() let dispatchGroup = DispatchGroup()
var groupError: Error? = nil var groupError: Error? = nil
for articleIdChunk in articleIdChunks { for articleIDChunk in articleIDChunks {
var request = URLRequest(url: url) var request = URLRequest(url: url)
request.httpMethod = "POST" request.httpMethod = "POST"
@ -835,7 +835,7 @@ extension FeedlyAPICaller: FeedlyMarkArticlesService {
request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization) request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization)
do { do {
let body = MarkerEntriesBody(action: action.actionValue, entryIds: Array(articleIdChunk)) let body = MarkerEntriesBody(action: action.actionValue, entryIDs: Array(articleIDChunk))
let encoder = JSONEncoder() let encoder = JSONEncoder()
let data = try encoder.encode(body) let data = try encoder.encode(body)
request.httpBody = data request.httpBody = data

View File

@ -30,7 +30,7 @@ extension FeedlyAccountDelegate: OAuthAuthorizationGranting {
static func oauthAuthorizationCodeGrantRequest(secretsProvider: SecretsProvider) -> URLRequest { static func oauthAuthorizationCodeGrantRequest(secretsProvider: SecretsProvider) -> URLRequest {
let client = environment.oauthAuthorizationClient(secretsProvider: secretsProvider) let client = environment.oauthAuthorizationClient(secretsProvider: secretsProvider)
let authorizationRequest = OAuthAuthorizationRequest(clientId: client.id, let authorizationRequest = OAuthAuthorizationRequest(clientID: client.id,
redirectUri: client.redirectUri, redirectUri: client.redirectUri,
scope: oauthAuthorizationGrantScope, scope: oauthAuthorizationGrantScope,
state: client.state) state: client.state)

View File

@ -139,7 +139,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
let log = self.log let log = self.log
let syncAllOperation = FeedlySyncAllOperation(account: account, feedlyUserId: credentials.username, caller: caller, database: database, lastSuccessfulFetchStartDate: accountMetadata?.lastArticleFetchStartTime, downloadProgress: refreshProgress, log: log) let syncAllOperation = FeedlySyncAllOperation(account: account, feedlyUserID: credentials.username, caller: caller, database: database, lastSuccessfulFetchStartDate: accountMetadata?.lastArticleFetchStartTime, downloadProgress: refreshProgress, log: log)
syncAllOperation.downloadProgress = refreshProgress syncAllOperation.downloadProgress = refreshProgress
@ -234,7 +234,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
let group = DispatchGroup() let group = DispatchGroup()
let ingestUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, userId: credentials.username, service: caller, database: database, newerThan: nil, log: log) let ingestUnread = FeedlyIngestUnreadArticleIDsOperation(account: account, userID: credentials.username, service: caller, database: database, newerThan: nil, log: log)
group.enter() group.enter()
ingestUnread.completionBlock = { _ in ingestUnread.completionBlock = { _ in
@ -242,7 +242,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
} }
let ingestStarred = FeedlyIngestStarredArticleIdsOperation(account: account, userId: credentials.username, service: caller, database: database, newerThan: nil, log: log) let ingestStarred = FeedlyIngestStarredArticleIDsOperation(account: account, userID: credentials.username, service: caller, database: database, newerThan: nil, log: log)
group.enter() group.enter()
ingestStarred.completionBlock = { _ in ingestStarred.completionBlock = { _ in
@ -447,7 +447,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
feedName: name, feedName: name,
searchService: caller, searchService: caller,
addToCollectionService: caller, addToCollectionService: caller,
syncUnreadIdsService: caller, syncUnreadIDsService: caller,
getStreamContentsService: caller, getStreamContentsService: caller,
database: database, database: database,
container: container, container: container,
@ -483,18 +483,18 @@ final class FeedlyAccountDelegate: AccountDelegate {
} }
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) { func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
let folderCollectionIds = account.folders?.filter { $0.has(feed) }.compactMap { $0.externalID } let folderCollectionIDs = account.folders?.filter { $0.has(feed) }.compactMap { $0.externalID }
guard let collectionIds = folderCollectionIds, let collectionId = collectionIds.first else { guard let collectionIDs = folderCollectionIDs, let collectionID = collectionIDs.first else {
completion(.failure(FeedlyAccountDelegateError.unableToRenameFeed(feed.nameForDisplay, name))) completion(.failure(FeedlyAccountDelegateError.unableToRenameFeed(feed.nameForDisplay, name)))
return return
} }
let feedId = FeedlyFeedResourceId(id: feed.feedID) let feedID = FeedlyFeedResourceID(id: feed.feedID)
let editedNameBefore = feed.editedName let editedNameBefore = feed.editedName
// Adding an existing feed updates it. // Adding an existing feed updates it.
// Updating feed name in one folder/collection updates it for all folders/collections. // Updating feed name in one folder/collection updates it for all folders/collections.
caller.addFeed(with: feedId, title: name, toCollectionWith: collectionId) { result in caller.addFeed(with: feedID, title: name, toCollectionWith: collectionID) { result in
switch result { switch result {
case .success: case .success:
completion(.success(())) completion(.success(()))
@ -531,7 +531,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
throw FeedlyAccountDelegateError.notLoggedIn throw FeedlyAccountDelegateError.notLoggedIn
} }
let resource = FeedlyFeedResourceId(id: feed.feedID) let resource = FeedlyFeedResourceID(id: feed.feedID)
let addExistingFeed = try FeedlyAddExistingFeedOperation(account: account, let addExistingFeed = try FeedlyAddExistingFeedOperation(account: account,
credentials: credentials, credentials: credentials,
resource: resource, resource: resource,
@ -571,13 +571,13 @@ final class FeedlyAccountDelegate: AccountDelegate {
} }
private func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) { private func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
guard let folder = container as? Folder, let collectionId = folder.externalID else { guard let folder = container as? Folder, let collectionID = folder.externalID else {
return DispatchQueue.main.async { return DispatchQueue.main.async {
completion(.failure(FeedlyAccountDelegateError.unableToRemoveFeed(feed.nameForDisplay))) completion(.failure(FeedlyAccountDelegateError.unableToRemoveFeed(feed.nameForDisplay)))
} }
} }
caller.removeFeed(feed.feedID, fromCollectionWith: collectionId) { result in caller.removeFeed(feed.feedID, fromCollectionWith: collectionID) { result in
switch result { switch result {
case .success: case .success:
completion(.success(())) completion(.success(()))

View File

@ -10,7 +10,7 @@ import Foundation
enum FeedlyAccountDelegateError: LocalizedError { enum FeedlyAccountDelegateError: LocalizedError {
case notLoggedIn case notLoggedIn
case unexpectedResourceId(String) case unexpectedResourceID(String)
case unableToAddFolder(String) case unableToAddFolder(String)
case unableToRenameFolder(String, String) case unableToRenameFolder(String, String)
case unableToRemoveFolder(String) case unableToRemoveFolder(String)
@ -25,9 +25,9 @@ enum FeedlyAccountDelegateError: LocalizedError {
case .notLoggedIn: case .notLoggedIn:
return NSLocalizedString("Please add the Feedly account again. If this problem persists, open Keychain Access and delete all feedly.com entries, then try again.", comment: "Feedly Credentials not found.") return NSLocalizedString("Please add the Feedly account again. If this problem persists, open Keychain Access and delete all feedly.com entries, then try again.", comment: "Feedly Credentials not found.")
case .unexpectedResourceId(let resourceId): case .unexpectedResourceID(let resourceID):
let template = NSLocalizedString("Could not encode the identifier “%@”.", comment: "Feedly Could not encode resource id to send to Feedly.") let template = NSLocalizedString("Could not encode the identifier “%@”.", comment: "Feedly Could not encode resource id to send to Feedly.")
return String(format: template, resourceId) return String(format: template, resourceID)
case .unableToAddFolder(let name): case .unableToAddFolder(let name):
let template = NSLocalizedString("Could not create a folder named “%@”.", comment: "Feedly Could not create a folder/collection.") let template = NSLocalizedString("Could not create a folder named “%@”.", comment: "Feedly Could not create a folder/collection.")
@ -67,7 +67,7 @@ enum FeedlyAccountDelegateError: LocalizedError {
case .notLoggedIn: case .notLoggedIn:
return nil return nil
case .unexpectedResourceId: case .unexpectedResourceID:
let template = NSLocalizedString("Please contact NetNewsWire support.", comment: "Feedly Recovery suggestion for not being able to encode a resource id to send to Feedly..") let template = NSLocalizedString("Please contact NetNewsWire support.", comment: "Feedly Recovery suggestion for not being able to encode a resource id to send to Feedly..")
return String(format: template) return String(format: template)

View File

@ -16,10 +16,10 @@ import Foundation
throw FeedlyAccountDelegateError.addFeedChooseFolder throw FeedlyAccountDelegateError.addFeedChooseFolder
} }
guard let collectionId = folder.externalID else { guard let collectionID = folder.externalID else {
throw FeedlyAccountDelegateError.addFeedInvalidFolder(folder.nameForDisplay) throw FeedlyAccountDelegateError.addFeedInvalidFolder(folder.nameForDisplay)
} }
return (folder, collectionId) return (folder, collectionID)
} }
} }

View File

@ -9,12 +9,12 @@
import Foundation import Foundation
protocol FeedlyResourceProviding { protocol FeedlyResourceProviding {
@MainActor var resource: FeedlyResourceId { get } @MainActor var resource: FeedlyResourceID { get }
} }
extension FeedlyFeedResourceId: FeedlyResourceProviding { extension FeedlyFeedResourceID: FeedlyResourceProviding {
var resource: FeedlyResourceId { var resource: FeedlyResourceID {
return self return self
} }
} }

View File

@ -23,7 +23,7 @@ final class FeedlyEntryIdentifierProvider: FeedlyEntryIdentifierProviding {
entryIDs.formUnion(provider.entryIDs) entryIDs.formUnion(provider.entryIDs)
} }
@MainActor func addEntryIDs(in articleIds: [String]) { @MainActor func addEntryIDs(in articleIDs: [String]) {
entryIDs.formUnion(articleIds) entryIDs.formUnion(articleIDs)
} }
} }

View File

@ -21,7 +21,7 @@ struct FeedlyEntryParser {
/// When ingesting articles, the feedURL must match a feed's `feedID` for the article to be reachable between it and its matching feed. It reminds me of a foreign key. /// When ingesting articles, the feedURL must match a feed's `feedID` for the article to be reachable between it and its matching feed. It reminds me of a foreign key.
var feedUrl: String? { var feedUrl: String? {
guard let id = entry.origin?.streamId else { guard let id = entry.origin?.streamID else {
// At this point, check Feedly's API isn't glitching or the response has not changed structure. // At this point, check Feedly's API isn't glitching or the response has not changed structure.
assertionFailure("Entries need to be traceable to a feed or this entry will be dropped.") assertionFailure("Entries need to be traceable to a feed or this entry will be dropped.")
return nil return nil

View File

@ -22,7 +22,7 @@ struct FeedlyFeedParser {
} }
var url: String { var url: String {
let resource = FeedlyFeedResourceId(id: feed.id) let resource = FeedlyFeedResourceID(id: feed.id)
return resource.url return resource.url
} }

View File

@ -10,6 +10,6 @@ import Foundation
struct FeedlyOrigin: Decodable { struct FeedlyOrigin: Decodable {
let title: String? let title: String?
let streamId: String? let streamID: String?
let htmlUrl: String? let htmlUrl: String?
} }

View File

@ -1,5 +1,5 @@
// //
// FeedlyResourceId.swift // FeedlyResourceID.swift
// Account // Account
// //
// Created by Kiel Gillard on 3/10/19. // Created by Kiel Gillard on 3/10/19.
@ -8,20 +8,20 @@
import Foundation import Foundation
/// The kinds of Resource Ids is documented here: https://developer.feedly.com/cloud/ /// The kinds of Resource IDs is documented here: https://developer.feedly.com/cloud/
protocol FeedlyResourceId { protocol FeedlyResourceID {
/// The resource Id from Feedly. /// The resource ID from Feedly.
@MainActor var id: String { get } @MainActor var id: String { get }
} }
/// The Feed Resource is documented here: https://developer.feedly.com/cloud/ /// The Feed Resource is documented here: https://developer.feedly.com/cloud/
struct FeedlyFeedResourceId: FeedlyResourceId { struct FeedlyFeedResourceID: FeedlyResourceID {
let id: String let id: String
/// The location of the kind of resource a concrete type represents. /// The location of the kind of resource a concrete type represents.
/// If the concrete type cannot strip the resource type from the Id, it should just return the Id /// If the concrete type cannot strip the resource type from the ID, it should just return the ID
/// since the Id is a legitimate URL. /// since the ID is a legitimate URL.
/// This is basically assuming Feedly prefixes source feed URLs with `feed/`. /// This is basically assuming Feedly prefixes source feed URLs with `feed/`.
/// It is not documented as such and could potentially change. /// It is not documented as such and could potentially change.
/// Feedly does not include the source feed URL as a separate field. /// Feedly does not include the source feed URL as a separate field.
@ -38,48 +38,48 @@ struct FeedlyFeedResourceId: FeedlyResourceId {
} }
} }
extension FeedlyFeedResourceId { extension FeedlyFeedResourceID {
init(url: String) { init(url: String) {
self.id = "feed/\(url)" self.id = "feed/\(url)"
} }
} }
struct FeedlyCategoryResourceId: FeedlyResourceId { struct FeedlyCategoryResourceID: FeedlyResourceID {
let id: String let id: String
enum Global { enum Global {
static func uncategorized(for userId: String) -> FeedlyCategoryResourceId { static func uncategorized(for userID: String) -> FeedlyCategoryResourceID {
// https://developer.feedly.com/cloud/#global-resource-ids // https://developer.feedly.com/cloud/#global-resource-ids
let id = "user/\(userId)/category/global.uncategorized" let id = "user/\(userID)/category/global.uncategorized"
return FeedlyCategoryResourceId(id: id) return FeedlyCategoryResourceID(id: id)
} }
/// All articles from all the feeds the user subscribes to. /// All articles from all the feeds the user subscribes to.
static func all(for userId: String) -> FeedlyCategoryResourceId { static func all(for userID: String) -> FeedlyCategoryResourceID {
// https://developer.feedly.com/cloud/#global-resource-ids // https://developer.feedly.com/cloud/#global-resource-ids
let id = "user/\(userId)/category/global.all" let id = "user/\(userID)/category/global.all"
return FeedlyCategoryResourceId(id: id) return FeedlyCategoryResourceID(id: id)
} }
/// All articles from all the feeds the user loves most. /// All articles from all the feeds the user loves most.
static func mustRead(for userId: String) -> FeedlyCategoryResourceId { static func mustRead(for userID: String) -> FeedlyCategoryResourceID {
// https://developer.feedly.com/cloud/#global-resource-ids // https://developer.feedly.com/cloud/#global-resource-ids
let id = "user/\(userId)/category/global.must" let id = "user/\(userID)/category/global.must"
return FeedlyCategoryResourceId(id: id) return FeedlyCategoryResourceID(id: id)
} }
} }
} }
struct FeedlyTagResourceId: FeedlyResourceId { struct FeedlyTagResourceID: FeedlyResourceID {
let id: String let id: String
enum Global { enum Global {
static func saved(for userId: String) -> FeedlyTagResourceId { static func saved(for userID: String) -> FeedlyTagResourceID {
// https://developer.feedly.com/cloud/#global-resource-ids // https://developer.feedly.com/cloud/#global-resource-ids
let id = "user/\(userId)/tag/global.saved" let id = "user/\(userID)/tag/global.saved"
return FeedlyTagResourceId(id: id) return FeedlyTagResourceID(id: id)
} }
} }
} }

View File

@ -1,5 +1,5 @@
// //
// FeedlyStreamIds.swift // FeedlyStreamIDs.swift
// Account // Account
// //
// Created by Kiel Gillard on 18/10/19. // Created by Kiel Gillard on 18/10/19.
@ -8,7 +8,7 @@
import Foundation import Foundation
struct FeedlyStreamIds: Decodable { struct FeedlyStreamIDs: Decodable {
let continuation: String? let continuation: String?
let ids: [String] let ids: [String]

View File

@ -17,13 +17,13 @@ public struct OAuthRefreshAccessTokenRequest: Encodable {
public var scope: String? public var scope: String?
// Possibly not part of the standard but specific to certain implementations (e.g.: Feedly). // Possibly not part of the standard but specific to certain implementations (e.g.: Feedly).
public var clientId: String public var clientID: String
public var clientSecret: String public var clientSecret: String
public init(refreshToken: String, scope: String?, client: OAuthAuthorizationClient) { public init(refreshToken: String, scope: String?, client: OAuthAuthorizationClient) {
self.refreshToken = refreshToken self.refreshToken = refreshToken
self.scope = scope self.scope = scope
self.clientId = client.id self.clientID = client.id
self.clientSecret = client.secret self.clientSecret = client.secret
} }
} }

View File

@ -30,13 +30,13 @@ public struct OAuthAuthorizationClient: Equatable {
/// https://tools.ietf.org/html/rfc6749#section-4.1.1 /// https://tools.ietf.org/html/rfc6749#section-4.1.1
public struct OAuthAuthorizationRequest { public struct OAuthAuthorizationRequest {
public let responseType = "code" public let responseType = "code"
public var clientId: String public var clientID: String
public var redirectUri: String public var redirectUri: String
public var scope: String public var scope: String
public var state: String? public var state: String?
public init(clientId: String, redirectUri: String, scope: String, state: String?) { public init(clientID: String, redirectUri: String, scope: String, state: String?) {
self.clientId = clientId self.clientID = clientID
self.redirectUri = redirectUri self.redirectUri = redirectUri
self.scope = scope self.scope = scope
self.state = state self.state = state
@ -45,7 +45,7 @@ public struct OAuthAuthorizationRequest {
public var queryItems: [URLQueryItem] { public var queryItems: [URLQueryItem] {
return [ return [
URLQueryItem(name: "response_type", value: responseType), URLQueryItem(name: "response_type", value: responseType),
URLQueryItem(name: "client_id", value: clientId), URLQueryItem(name: "client_id", value: clientID),
URLQueryItem(name: "scope", value: scope), URLQueryItem(name: "scope", value: scope),
URLQueryItem(name: "redirect_uri", value: redirectUri), URLQueryItem(name: "redirect_uri", value: redirectUri),
] ]
@ -114,7 +114,7 @@ public struct OAuthAccessTokenRequest: Encodable {
public var code: String public var code: String
public var redirectUri: String public var redirectUri: String
public var state: String? public var state: String?
public var clientId: String public var clientID: String
// Possibly not part of the standard but specific to certain implementations (e.g.: Feedly). // Possibly not part of the standard but specific to certain implementations (e.g.: Feedly).
public var clientSecret: String public var clientSecret: String
@ -124,7 +124,7 @@ public struct OAuthAccessTokenRequest: Encodable {
self.code = authorizationResponse.code self.code = authorizationResponse.code
self.redirectUri = client.redirectUri self.redirectUri = client.redirectUri
self.state = authorizationResponse.state self.state = authorizationResponse.state
self.clientId = client.id self.clientID = client.id
self.clientSecret = client.secret self.clientSecret = client.secret
self.scope = scope self.scope = scope
} }

View File

@ -17,10 +17,10 @@ import Core
private let operationQueue = MainThreadOperationQueue() private let operationQueue = MainThreadOperationQueue()
var addCompletionHandler: ((Result<Void, Error>) -> ())? var addCompletionHandler: ((Result<Void, Error>) -> ())?
@MainActor init(account: Account, credentials: Credentials, resource: FeedlyFeedResourceId, service: FeedlyAddFeedToCollectionService, container: Container, progress: DownloadProgress, log: OSLog, customFeedName: String? = nil) throws { @MainActor init(account: Account, credentials: Credentials, resource: FeedlyFeedResourceID, service: FeedlyAddFeedToCollectionService, container: Container, progress: DownloadProgress, log: OSLog, customFeedName: String? = nil) throws {
let validator = FeedlyFeedContainerValidator(container: container) let validator = FeedlyFeedContainerValidator(container: container)
let (folder, collectionId) = try validator.getValidContainer() let (folder, collectionID) = try validator.getValidContainer()
self.operationQueue.suspend() self.operationQueue.suspend()
@ -28,7 +28,7 @@ import Core
self.downloadProgress = progress self.downloadProgress = progress
let addRequest = FeedlyAddFeedToCollectionOperation(folder: folder, feedResource: resource, feedName: customFeedName, collectionId: collectionId, service: service) let addRequest = FeedlyAddFeedToCollectionOperation(folder: folder, feedResource: resource, feedName: customFeedName, collectionID: collectionID, service: service)
addRequest.delegate = self addRequest.delegate = self
addRequest.downloadProgress = progress addRequest.downloadProgress = progress
self.operationQueue.add(addRequest) self.operationQueue.add(addRequest)

View File

@ -10,33 +10,33 @@ import Foundation
import CommonErrors import CommonErrors
protocol FeedlyAddFeedToCollectionService { protocol FeedlyAddFeedToCollectionService {
func addFeed(with feedId: FeedlyFeedResourceId, title: String?, toCollectionWith collectionId: String, completion: @escaping (Result<[FeedlyFeed], Error>) -> ()) func addFeed(with feedId: FeedlyFeedResourceID, title: String?, toCollectionWith collectionID: String, completion: @escaping (Result<[FeedlyFeed], Error>) -> ())
} }
final class FeedlyAddFeedToCollectionOperation: FeedlyOperation, FeedlyFeedsAndFoldersProviding, FeedlyResourceProviding { final class FeedlyAddFeedToCollectionOperation: FeedlyOperation, FeedlyFeedsAndFoldersProviding, FeedlyResourceProviding {
let feedName: String? let feedName: String?
let collectionId: String let collectionID: String
let service: FeedlyAddFeedToCollectionService let service: FeedlyAddFeedToCollectionService
let folder: Folder let folder: Folder
let feedResource: FeedlyFeedResourceId let feedResource: FeedlyFeedResourceID
init(folder: Folder, feedResource: FeedlyFeedResourceId, feedName: String? = nil, collectionId: String, service: FeedlyAddFeedToCollectionService) { init(folder: Folder, feedResource: FeedlyFeedResourceID, feedName: String? = nil, collectionID: String, service: FeedlyAddFeedToCollectionService) {
self.folder = folder self.folder = folder
self.feedResource = feedResource self.feedResource = feedResource
self.feedName = feedName self.feedName = feedName
self.collectionId = collectionId self.collectionID = collectionID
self.service = service self.service = service
} }
private(set) var feedsAndFolders = [([FeedlyFeed], Folder)]() private(set) var feedsAndFolders = [([FeedlyFeed], Folder)]()
var resource: FeedlyResourceId { var resource: FeedlyResourceID {
return feedResource return feedResource
} }
override func run() { override func run() {
service.addFeed(with: feedResource, title: feedName, toCollectionWith: collectionId) { [weak self] result in service.addFeed(with: feedResource, title: feedName, toCollectionWith: collectionID) { [weak self] result in
guard let self = self else { guard let self = self else {
return return
} }
@ -56,9 +56,9 @@ private extension FeedlyAddFeedToCollectionOperation {
case .success(let feedlyFeeds): case .success(let feedlyFeeds):
feedsAndFolders = [(feedlyFeeds, folder)] feedsAndFolders = [(feedlyFeeds, folder)]
let feedsWithCreatedFeedId = feedlyFeeds.filter { $0.id == resource.id } let feedsWithCreatedFeedID = feedlyFeeds.filter { $0.id == resource.id }
if feedsWithCreatedFeedId.isEmpty { if feedsWithCreatedFeedID.isEmpty {
didFinish(with: AccountError.createErrorNotFound) didFinish(with: AccountError.createErrorNotFound)
} else { } else {
didFinish() didFinish()

View File

@ -18,24 +18,24 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
private let operationQueue = MainThreadOperationQueue() private let operationQueue = MainThreadOperationQueue()
private let folder: Folder private let folder: Folder
private let collectionId: String private let collectionID: String
private let url: String private let url: String
private let account: Account private let account: Account
private let credentials: Credentials private let credentials: Credentials
private let database: SyncDatabase private let database: SyncDatabase
private let feedName: String? private let feedName: String?
private let addToCollectionService: FeedlyAddFeedToCollectionService private let addToCollectionService: FeedlyAddFeedToCollectionService
private let syncUnreadIdsService: FeedlyGetStreamIdsService private let syncUnreadIDsService: FeedlyGetStreamIDsService
private let getStreamContentsService: FeedlyGetStreamContentsService private let getStreamContentsService: FeedlyGetStreamContentsService
private let log: OSLog private let log: OSLog
private var feedResourceId: FeedlyFeedResourceId? private var feedResourceID: FeedlyFeedResourceID?
var addCompletionHandler: ((Result<Feed, Error>) -> ())? var addCompletionHandler: ((Result<Feed, Error>) -> ())?
@MainActor init(account: Account, credentials: Credentials, url: String, feedName: String?, searchService: FeedlySearchService, addToCollectionService: FeedlyAddFeedToCollectionService, syncUnreadIdsService: FeedlyGetStreamIdsService, getStreamContentsService: FeedlyGetStreamContentsService, database: SyncDatabase, container: Container, progress: DownloadProgress, log: OSLog) throws { @MainActor init(account: Account, credentials: Credentials, url: String, feedName: String?, searchService: FeedlySearchService, addToCollectionService: FeedlyAddFeedToCollectionService, syncUnreadIDsService: FeedlyGetStreamIDsService, getStreamContentsService: FeedlyGetStreamContentsService, database: SyncDatabase, container: Container, progress: DownloadProgress, log: OSLog) throws {
let validator = FeedlyFeedContainerValidator(container: container) let validator = FeedlyFeedContainerValidator(container: container)
(self.folder, self.collectionId) = try validator.getValidContainer() (self.folder, self.collectionID) = try validator.getValidContainer()
self.url = url self.url = url
self.operationQueue.suspend() self.operationQueue.suspend()
@ -44,7 +44,7 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
self.database = database self.database = database
self.feedName = feedName self.feedName = feedName
self.addToCollectionService = addToCollectionService self.addToCollectionService = addToCollectionService
self.syncUnreadIdsService = syncUnreadIdsService self.syncUnreadIDsService = syncUnreadIDsService
self.getStreamContentsService = getStreamContentsService self.getStreamContentsService = getStreamContentsService
self.log = log self.log = log
@ -84,10 +84,10 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
return didFinish(with: AccountError.createErrorNotFound) return didFinish(with: AccountError.createErrorNotFound)
} }
let feedResourceId = FeedlyFeedResourceId(id: first.feedId) let feedResourceID = FeedlyFeedResourceID(id: first.feedId)
self.feedResourceId = feedResourceId self.feedResourceID = feedResourceID
let addRequest = FeedlyAddFeedToCollectionOperation(folder: folder, feedResource: feedResourceId, feedName: feedName, collectionId: collectionId, service: addToCollectionService) let addRequest = FeedlyAddFeedToCollectionOperation(folder: folder, feedResource: feedResourceID, feedName: feedName, collectionID: collectionID, service: addToCollectionService)
addRequest.delegate = self addRequest.delegate = self
addRequest.downloadProgress = downloadProgress addRequest.downloadProgress = downloadProgress
operationQueue.add(addRequest) operationQueue.add(addRequest)
@ -98,13 +98,13 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
createFeeds.downloadProgress = downloadProgress createFeeds.downloadProgress = downloadProgress
operationQueue.add(createFeeds) operationQueue.add(createFeeds)
let syncUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, userId: credentials.username, service: syncUnreadIdsService, database: database, newerThan: nil, log: log) let syncUnread = FeedlyIngestUnreadArticleIDsOperation(account: account, userID: credentials.username, service: syncUnreadIDsService, database: database, newerThan: nil, log: log)
syncUnread.addDependency(createFeeds) syncUnread.addDependency(createFeeds)
syncUnread.downloadProgress = downloadProgress syncUnread.downloadProgress = downloadProgress
syncUnread.delegate = self syncUnread.delegate = self
operationQueue.add(syncUnread) operationQueue.add(syncUnread)
let syncFeed = FeedlySyncStreamContentsOperation(account: account, resource: feedResourceId, service: getStreamContentsService, isPagingEnabled: false, newerThan: nil, log: log) let syncFeed = FeedlySyncStreamContentsOperation(account: account, resource: feedResourceID, service: getStreamContentsService, isPagingEnabled: false, newerThan: nil, log: log)
syncFeed.addDependency(syncUnread) syncFeed.addDependency(syncUnread)
syncFeed.downloadProgress = downloadProgress syncFeed.downloadProgress = downloadProgress
syncFeed.delegate = self syncFeed.delegate = self
@ -138,7 +138,7 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
guard let handler = addCompletionHandler else { guard let handler = addCompletionHandler else {
return return
} }
if let feedResource = feedResourceId, let feed = folder.existingFeed(withFeedID: feedResource.id) { if let feedResource = feedResourceID, let feed = folder.existingFeed(withFeedID: feedResource.id) {
handler(.success(feed)) handler(.success(feed))
} }
else { else {

View File

@ -15,17 +15,17 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation {
private let account: Account private let account: Account
private let log: OSLog private let log: OSLog
private let missingArticleEntryIdProvider: FeedlyEntryIdentifierProviding private let missingArticleEntryIDProvider: FeedlyEntryIdentifierProviding
private let updatedArticleEntryIdProvider: FeedlyEntryIdentifierProviding private let updatedArticleEntryIDProvider: FeedlyEntryIdentifierProviding
private let getEntriesService: FeedlyGetEntriesService private let getEntriesService: FeedlyGetEntriesService
private let operationQueue = MainThreadOperationQueue() private let operationQueue = MainThreadOperationQueue()
private let finishOperation: FeedlyCheckpointOperation private let finishOperation: FeedlyCheckpointOperation
@MainActor init(account: Account, missingArticleEntryIdProvider: FeedlyEntryIdentifierProviding, updatedArticleEntryIdProvider: FeedlyEntryIdentifierProviding, getEntriesService: FeedlyGetEntriesService, log: OSLog) { @MainActor init(account: Account, missingArticleEntryIDProvider: FeedlyEntryIdentifierProviding, updatedArticleEntryIDProvider: FeedlyEntryIdentifierProviding, getEntriesService: FeedlyGetEntriesService, log: OSLog) {
self.account = account self.account = account
self.operationQueue.suspend() self.operationQueue.suspend()
self.missingArticleEntryIdProvider = missingArticleEntryIdProvider self.missingArticleEntryIDProvider = missingArticleEntryIDProvider
self.updatedArticleEntryIdProvider = updatedArticleEntryIdProvider self.updatedArticleEntryIDProvider = updatedArticleEntryIDProvider
self.getEntriesService = getEntriesService self.getEntriesService = getEntriesService
self.finishOperation = FeedlyCheckpointOperation() self.finishOperation = FeedlyCheckpointOperation()
self.log = log self.log = log
@ -35,16 +35,16 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation {
} }
override func run() { override func run() {
var articleIds = missingArticleEntryIdProvider.entryIDs var articleIDs = missingArticleEntryIDProvider.entryIDs
articleIds.formUnion(updatedArticleEntryIdProvider.entryIDs) articleIDs.formUnion(updatedArticleEntryIDProvider.entryIDs)
os_log(.debug, log: log, "Requesting %{public}i articles.", articleIds.count) os_log(.debug, log: log, "Requesting %{public}i articles.", articleIDs.count)
let feedlyAPILimitBatchSize = 1000 let feedlyAPILimitBatchSize = 1000
for articleIds in Array(articleIds).chunked(into: feedlyAPILimitBatchSize) { for articleIDs in Array(articleIDs).chunked(into: feedlyAPILimitBatchSize) {
Task { @MainActor in Task { @MainActor in
let provider = FeedlyEntryIdentifierProvider(entryIDs: Set(articleIds)) let provider = FeedlyEntryIdentifierProvider(entryIDs: Set(articleIDs))
let getEntries = FeedlyGetEntriesOperation(service: getEntriesService, provider: provider, log: log) let getEntries = FeedlyGetEntriesOperation(service: getEntriesService, provider: provider, log: log)
getEntries.delegate = self getEntries.delegate = self
self.operationQueue.add(getEntries) self.operationQueue.add(getEntries)

View File

@ -1,5 +1,5 @@
// //
// FeedlyFetchIdsForMissingArticlesOperation.swift // FeedlyFetchIDsForMissingArticlesOperation.swift
// Account // Account
// //
// Created by Kiel Gillard on 7/1/20. // Created by Kiel Gillard on 7/1/20.
@ -9,7 +9,7 @@
import Foundation import Foundation
import os.log import os.log
final class FeedlyFetchIdsForMissingArticlesOperation: FeedlyOperation, FeedlyEntryIdentifierProviding { final class FeedlyFetchIDsForMissingArticlesOperation: FeedlyOperation, FeedlyEntryIdentifierProviding {
private let account: Account private let account: Account

View File

@ -38,9 +38,9 @@ final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, Fe
// TODO: Fix the below. Theres an error on the os.log line: "Expression type '()' is ambiguous without more context" // TODO: Fix the below. Theres an error on the os.log line: "Expression type '()' is ambiguous without more context"
// if parsed.count != entries.count { // if parsed.count != entries.count {
// let entryIds = Set(entries.map { $0.id }) // let entryIDs = Set(entries.map { $0.id })
// let parsedIds = Set(parsed.map { $0.uniqueID }) // let parsedIDs = Set(parsed.map { $0.uniqueID })
// let difference = entryIds.subtracting(parsedIds) // let difference = entryIDs.subtracting(parsedIDs)
// os_log(.debug, log: log, "%{public}@ dropping articles with ids: %{public}@.", self, difference) // os_log(.debug, log: log, "%{public}@ dropping articles with ids: %{public}@.", self, difference)
// } // }

View File

@ -27,7 +27,7 @@ protocol FeedlyGetStreamContentsOperationDelegate: AnyObject {
final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding { final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding {
@MainActor struct ResourceProvider: FeedlyResourceProviding { @MainActor struct ResourceProvider: FeedlyResourceProviding {
var resource: FeedlyResourceId var resource: FeedlyResourceID
} }
let resourceProvider: FeedlyResourceProviding let resourceProvider: FeedlyResourceProviding
@ -55,9 +55,9 @@ final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProvid
}) })
if parsed.count != entries.count { if parsed.count != entries.count {
let entryIds = Set(entries.map { $0.id }) let entryIDs = Set(entries.map { $0.id })
let parsedIds = Set(parsed.map { $0.uniqueID }) let parsedIDs = Set(parsed.map { $0.uniqueID })
let difference = entryIds.subtracting(parsedIds) let difference = entryIDs.subtracting(parsedIDs)
os_log(.debug, log: log, "Dropping articles with ids: %{public}@.", difference) os_log(.debug, log: log, "Dropping articles with ids: %{public}@.", difference)
} }
@ -83,7 +83,7 @@ final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProvid
weak var streamDelegate: FeedlyGetStreamContentsOperationDelegate? weak var streamDelegate: FeedlyGetStreamContentsOperationDelegate?
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamContentsService, continuation: String? = nil, newerThan: Date?, unreadOnly: Bool? = nil, log: OSLog) { init(account: Account, resource: FeedlyResourceID, service: FeedlyGetStreamContentsService, continuation: String? = nil, newerThan: Date?, unreadOnly: Bool? = nil, log: OSLog) {
self.account = account self.account = account
self.resourceProvider = ResourceProvider(resource: resource) self.resourceProvider = ResourceProvider(resource: resource)
self.service = service self.service = service

View File

@ -1,5 +1,5 @@
// //
// FeedlyGetStreamIdsOperation.swift // FeedlyGetStreamIDsOperation.swift
// Account // Account
// //
// Created by Kiel Gillard on 18/10/19. // Created by Kiel Gillard on 18/10/19.
@ -9,32 +9,32 @@
import Foundation import Foundation
import os.log import os.log
protocol FeedlyGetStreamIdsOperationDelegate: AnyObject { protocol FeedlyGetStreamIDsOperationDelegate: AnyObject {
func feedlyGetStreamIdsOperation(_ operation: FeedlyGetStreamIdsOperation, didGet streamIds: FeedlyStreamIds) func feedlyGetStreamIDsOperation(_ operation: FeedlyGetStreamIDsOperation, didGet streamIDs: FeedlyStreamIDs)
} }
/// Single responsibility is to get the stream ids from Feedly. /// Single responsibility is to get the stream ids from Feedly.
final class FeedlyGetStreamIdsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding { final class FeedlyGetStreamIDsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding {
var entryIDs: Set<String> { var entryIDs: Set<String> {
guard let ids = streamIds?.ids else { guard let ids = streamIDs?.ids else {
assertionFailure("Has this operation been addeded as a dependency on the caller?") assertionFailure("Has this operation been addeded as a dependency on the caller?")
return [] return []
} }
return Set(ids) return Set(ids)
} }
private(set) var streamIds: FeedlyStreamIds? private(set) var streamIDs: FeedlyStreamIDs?
let account: Account let account: Account
let service: FeedlyGetStreamIdsService let service: FeedlyGetStreamIDsService
let continuation: String? let continuation: String?
let resource: FeedlyResourceId let resource: FeedlyResourceID
let unreadOnly: Bool? let unreadOnly: Bool?
let newerThan: Date? let newerThan: Date?
let log: OSLog let log: OSLog
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, continuation: String? = nil, newerThan: Date? = nil, unreadOnly: Bool?, log: OSLog) { init(account: Account, resource: FeedlyResourceID, service: FeedlyGetStreamIDsService, continuation: String? = nil, newerThan: Date? = nil, unreadOnly: Bool?, log: OSLog) {
self.account = account self.account = account
self.resource = resource self.resource = resource
self.service = service self.service = service
@ -44,15 +44,15 @@ final class FeedlyGetStreamIdsOperation: FeedlyOperation, FeedlyEntryIdentifierP
self.log = log self.log = log
} }
weak var streamIdsDelegate: FeedlyGetStreamIdsOperationDelegate? weak var streamIDsDelegate: FeedlyGetStreamIDsOperationDelegate?
override func run() { override func run() {
service.getStreamIds(for: resource, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly) { result in service.getStreamIDs(for: resource, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly) { result in
switch result { switch result {
case .success(let stream): case .success(let stream):
self.streamIds = stream self.streamIDs = stream
self.streamIdsDelegate?.feedlyGetStreamIdsOperation(self, didGet: stream) self.streamIDsDelegate?.feedlyGetStreamIDsOperation(self, didGet: stream)
self.didFinish() self.didFinish()

View File

@ -1,5 +1,5 @@
// //
// FeedlyGetUpdatedArticleIdsOperation.swift // FeedlyGetUpdatedArticleIDsOperation.swift
// Account // Account
// //
// Created by Kiel Gillard on 11/1/20. // Created by Kiel Gillard on 11/1/20.
@ -14,15 +14,15 @@ import Secrets
/// ///
/// Typically, it pages through the article ids of the global.all stream. /// Typically, it pages through the article ids of the global.all stream.
/// When all the article ids are collected, it is the responsibility of another operation to download them when appropriate. /// When all the article ids are collected, it is the responsibility of another operation to download them when appropriate.
class FeedlyGetUpdatedArticleIdsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding { class FeedlyGetUpdatedArticleIDsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding {
private let account: Account private let account: Account
private let resource: FeedlyResourceId private let resource: FeedlyResourceID
private let service: FeedlyGetStreamIdsService private let service: FeedlyGetStreamIDsService
private let newerThan: Date? private let newerThan: Date?
private let log: OSLog private let log: OSLog
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, newerThan: Date?, log: OSLog) { init(account: Account, resource: FeedlyResourceID, service: FeedlyGetStreamIDsService, newerThan: Date?, log: OSLog) {
self.account = account self.account = account
self.resource = resource self.resource = resource
self.service = service self.service = service
@ -30,48 +30,48 @@ class FeedlyGetUpdatedArticleIdsOperation: FeedlyOperation, FeedlyEntryIdentifie
self.log = log self.log = log
} }
convenience init(account: Account, userId: String, service: FeedlyGetStreamIdsService, newerThan: Date?, log: OSLog) { convenience init(account: Account, userID: String, service: FeedlyGetStreamIDsService, newerThan: Date?, log: OSLog) {
let all = FeedlyCategoryResourceId.Global.all(for: userId) let all = FeedlyCategoryResourceID.Global.all(for: userID)
self.init(account: account, resource: all, service: service, newerThan: newerThan, log: log) self.init(account: account, resource: all, service: service, newerThan: newerThan, log: log)
} }
var entryIDs: Set<String> { var entryIDs: Set<String> {
return storedUpdatedArticleIds return storedUpdatedArticleIDs
} }
private var storedUpdatedArticleIds = Set<String>() private var storedUpdatedArticleIDs = Set<String>()
override func run() { override func run() {
getStreamIds(nil) getStreamIDs(nil)
} }
private func getStreamIds(_ continuation: String?) { private func getStreamIDs(_ continuation: String?) {
guard let date = newerThan else { guard let date = newerThan else {
os_log(.debug, log: log, "No date provided so everything must be new (nothing is updated).") os_log(.debug, log: log, "No date provided so everything must be new (nothing is updated).")
didFinish() didFinish()
return return
} }
service.getStreamIds(for: resource, continuation: continuation, newerThan: date, unreadOnly: nil, completion: didGetStreamIds(_:)) service.getStreamIDs(for: resource, continuation: continuation, newerThan: date, unreadOnly: nil, completion: didGetStreamIDs(_:))
} }
private func didGetStreamIds(_ result: Result<FeedlyStreamIds, Error>) { private func didGetStreamIDs(_ result: Result<FeedlyStreamIDs, Error>) {
guard !isCanceled else { guard !isCanceled else {
didFinish() didFinish()
return return
} }
switch result { switch result {
case .success(let streamIds): case .success(let streamIDs):
storedUpdatedArticleIds.formUnion(streamIds.ids) storedUpdatedArticleIDs.formUnion(streamIDs.ids)
guard let continuation = streamIds.continuation else { guard let continuation = streamIDs.continuation else {
os_log(.debug, log: log, "%{public}i articles updated since last successful sync start date.", storedUpdatedArticleIds.count) os_log(.debug, log: log, "%{public}i articles updated since last successful sync start date.", storedUpdatedArticleIDs.count)
didFinish() didFinish()
return return
} }
getStreamIds(continuation) getStreamIDs(continuation)
case .failure(let error): case .failure(let error):
didFinish(with: error) didFinish(with: error)

View File

@ -1,5 +1,5 @@
// //
// FeedlyIngestStarredArticleIdsOperation.swift // FeedlyIngestStarredArticleIDsOperation.swift
// Account // Account
// //
// Created by Kiel Gillard on 15/10/19. // Created by Kiel Gillard on 15/10/19.
@ -17,21 +17,21 @@ import Secrets
/// When all the article ids are collected, a status is created for each. /// When all the article ids are collected, a status is created for each.
/// The article ids previously marked as starred but not collected become unstarred. /// The article ids previously marked as starred but not collected become unstarred.
/// So this operation has side effects *for the entire account* it operates on. /// So this operation has side effects *for the entire account* it operates on.
final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation { final class FeedlyIngestStarredArticleIDsOperation: FeedlyOperation {
private let account: Account private let account: Account
private let resource: FeedlyResourceId private let resource: FeedlyResourceID
private let service: FeedlyGetStreamIdsService private let service: FeedlyGetStreamIDsService
private let database: SyncDatabase private let database: SyncDatabase
private var remoteEntryIds = Set<String>() private var remoteEntryIDs = Set<String>()
private let log: OSLog private let log: OSLog
convenience init(account: Account, userId: String, service: FeedlyGetStreamIdsService, database: SyncDatabase, newerThan: Date?, log: OSLog) { convenience init(account: Account, userID: String, service: FeedlyGetStreamIDsService, database: SyncDatabase, newerThan: Date?, log: OSLog) {
let resource = FeedlyTagResourceId.Global.saved(for: userId) let resource = FeedlyTagResourceID.Global.saved(for: userID)
self.init(account: account, resource: resource, service: service, database: database, newerThan: newerThan, log: log) self.init(account: account, resource: resource, service: service, database: database, newerThan: newerThan, log: log)
} }
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, database: SyncDatabase, newerThan: Date?, log: OSLog) { init(account: Account, resource: FeedlyResourceID, service: FeedlyGetStreamIDsService, database: SyncDatabase, newerThan: Date?, log: OSLog) {
self.account = account self.account = account
self.resource = resource self.resource = resource
self.service = service self.service = service
@ -40,30 +40,30 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
} }
override func run() { override func run() {
getStreamIds(nil) getStreamIDs(nil)
} }
private func getStreamIds(_ continuation: String?) { private func getStreamIDs(_ continuation: String?) {
service.getStreamIds(for: resource, continuation: continuation, newerThan: nil, unreadOnly: nil, completion: didGetStreamIds(_:)) service.getStreamIDs(for: resource, continuation: continuation, newerThan: nil, unreadOnly: nil, completion: didGetStreamIDs(_:))
} }
private func didGetStreamIds(_ result: Result<FeedlyStreamIds, Error>) { private func didGetStreamIDs(_ result: Result<FeedlyStreamIDs, Error>) {
guard !isCanceled else { guard !isCanceled else {
didFinish() didFinish()
return return
} }
switch result { switch result {
case .success(let streamIds): case .success(let streamIDs):
remoteEntryIds.formUnion(streamIds.ids) remoteEntryIDs.formUnion(streamIDs.ids)
guard let continuation = streamIds.continuation else { guard let continuation = streamIDs.continuation else {
removeEntryIdsWithPendingStatus() removeEntryIDsWithPendingStatus()
return return
} }
getStreamIds(continuation) getStreamIDs(continuation)
case .failure(let error): case .failure(let error):
didFinish(with: error) didFinish(with: error)
@ -71,7 +71,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
} }
/// Do not override pending statuses with the remote statuses of the same articles, otherwise an article will temporarily re-acquire the remote status before the pending status is pushed and subseqently pulled. /// Do not override pending statuses with the remote statuses of the same articles, otherwise an article will temporarily re-acquire the remote status before the pending status is pushed and subseqently pulled.
private func removeEntryIdsWithPendingStatus() { private func removeEntryIDsWithPendingStatus() {
guard !isCanceled else { guard !isCanceled else {
didFinish() didFinish()
return return
@ -81,7 +81,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
do { do {
if let pendingArticleIDs = try await self.database.selectPendingStarredStatusArticleIDs() { if let pendingArticleIDs = try await self.database.selectPendingStarredStatusArticleIDs() {
self.remoteEntryIds.subtract(pendingArticleIDs) self.remoteEntryIDs.subtract(pendingArticleIDs)
} }
self.updateStarredStatuses() self.updateStarredStatuses()
} catch { } catch {
@ -120,7 +120,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
var markAsStarredError: Error? var markAsStarredError: Error?
var markAsUnstarredError: Error? var markAsUnstarredError: Error?
let remoteStarredArticleIDs = remoteEntryIds let remoteStarredArticleIDs = remoteEntryIDs
do { do {
try await account.markAsStarred(remoteStarredArticleIDs) try await account.markAsStarred(remoteStarredArticleIDs)
} catch { } catch {

View File

@ -1,5 +1,5 @@
// //
// FeedlyIngestStreamArticleIdsOperation.swift // FeedlyIngestStreamArticleIDsOperation.swift
// Account // Account
// //
// Created by Kiel Gillard on 9/1/20. // Created by Kiel Gillard on 9/1/20.
@ -16,34 +16,34 @@ import Database
/// Typically, it pages through the article ids of the global.all stream. /// Typically, it pages through the article ids of the global.all stream.
/// As the article ids are collected, a default read status is created for each. /// As the article ids are collected, a default read status is created for each.
/// So this operation has side effects *for the entire account* it operates on. /// So this operation has side effects *for the entire account* it operates on.
class FeedlyIngestStreamArticleIdsOperation: FeedlyOperation { class FeedlyIngestStreamArticleIDsOperation: FeedlyOperation {
private let account: Account private let account: Account
private let resource: FeedlyResourceId private let resource: FeedlyResourceID
private let service: FeedlyGetStreamIdsService private let service: FeedlyGetStreamIDsService
private let log: OSLog private let log: OSLog
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, log: OSLog) { init(account: Account, resource: FeedlyResourceID, service: FeedlyGetStreamIDsService, log: OSLog) {
self.account = account self.account = account
self.resource = resource self.resource = resource
self.service = service self.service = service
self.log = log self.log = log
} }
convenience init(account: Account, userId: String, service: FeedlyGetStreamIdsService, log: OSLog) { convenience init(account: Account, userID: String, service: FeedlyGetStreamIDsService, log: OSLog) {
let all = FeedlyCategoryResourceId.Global.all(for: userId) let all = FeedlyCategoryResourceID.Global.all(for: userID)
self.init(account: account, resource: all, service: service, log: log) self.init(account: account, resource: all, service: service, log: log)
} }
override func run() { override func run() {
getStreamIds(nil) getStreamIDs(nil)
} }
private func getStreamIds(_ continuation: String?) { private func getStreamIDs(_ continuation: String?) {
service.getStreamIds(for: resource, continuation: continuation, newerThan: nil, unreadOnly: nil, completion: didGetStreamIds(_:)) service.getStreamIDs(for: resource, continuation: continuation, newerThan: nil, unreadOnly: nil, completion: didGetStreamIDs(_:))
} }
private func didGetStreamIds(_ result: Result<FeedlyStreamIds, Error>) { private func didGetStreamIDs(_ result: Result<FeedlyStreamIDs, Error>) {
guard !isCanceled else { guard !isCanceled else {
didFinish() didFinish()
return return
@ -62,7 +62,7 @@ class FeedlyIngestStreamArticleIdsOperation: FeedlyOperation {
return return
} }
self.getStreamIds(continuation) self.getStreamIDs(continuation)
} catch { } catch {
self.didFinish(with: error) self.didFinish(with: error)
return return

View File

@ -1,5 +1,5 @@
// //
// FeedlyIngestUnreadArticleIdsOperation.swift // FeedlyIngestUnreadArticleIDsOperation.swift
// Account // Account
// //
// Created by Kiel Gillard on 18/10/19. // Created by Kiel Gillard on 18/10/19.
@ -18,21 +18,21 @@ import Secrets
/// When all the unread article ids are collected, a status is created for each. /// When all the unread article ids are collected, a status is created for each.
/// The article ids previously marked as unread but not collected become read. /// The article ids previously marked as unread but not collected become read.
/// So this operation has side effects *for the entire account* it operates on. /// So this operation has side effects *for the entire account* it operates on.
final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation { final class FeedlyIngestUnreadArticleIDsOperation: FeedlyOperation {
private let account: Account private let account: Account
private let resource: FeedlyResourceId private let resource: FeedlyResourceID
private let service: FeedlyGetStreamIdsService private let service: FeedlyGetStreamIDsService
private let database: SyncDatabase private let database: SyncDatabase
private var remoteEntryIds = Set<String>() private var remoteEntryIDs = Set<String>()
private let log: OSLog private let log: OSLog
convenience init(account: Account, userId: String, service: FeedlyGetStreamIdsService, database: SyncDatabase, newerThan: Date?, log: OSLog) { convenience init(account: Account, userID: String, service: FeedlyGetStreamIDsService, database: SyncDatabase, newerThan: Date?, log: OSLog) {
let resource = FeedlyCategoryResourceId.Global.all(for: userId) let resource = FeedlyCategoryResourceID.Global.all(for: userID)
self.init(account: account, resource: resource, service: service, database: database, newerThan: newerThan, log: log) self.init(account: account, resource: resource, service: service, database: database, newerThan: newerThan, log: log)
} }
init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamIdsService, database: SyncDatabase, newerThan: Date?, log: OSLog) { init(account: Account, resource: FeedlyResourceID, service: FeedlyGetStreamIDsService, database: SyncDatabase, newerThan: Date?, log: OSLog) {
self.account = account self.account = account
self.resource = resource self.resource = resource
self.service = service self.service = service
@ -41,30 +41,30 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
} }
override func run() { override func run() {
getStreamIds(nil) getStreamIDs(nil)
} }
private func getStreamIds(_ continuation: String?) { private func getStreamIDs(_ continuation: String?) {
service.getStreamIds(for: resource, continuation: continuation, newerThan: nil, unreadOnly: true, completion: didGetStreamIds(_:)) service.getStreamIDs(for: resource, continuation: continuation, newerThan: nil, unreadOnly: true, completion: didGetStreamIDs(_:))
} }
private func didGetStreamIds(_ result: Result<FeedlyStreamIds, Error>) { private func didGetStreamIDs(_ result: Result<FeedlyStreamIDs, Error>) {
guard !isCanceled else { guard !isCanceled else {
didFinish() didFinish()
return return
} }
switch result { switch result {
case .success(let streamIds): case .success(let streamIDs):
remoteEntryIds.formUnion(streamIds.ids) remoteEntryIDs.formUnion(streamIDs.ids)
guard let continuation = streamIds.continuation else { guard let continuation = streamIDs.continuation else {
removeEntryIdsWithPendingStatus() removeEntryIDsWithPendingStatus()
return return
} }
getStreamIds(continuation) getStreamIDs(continuation)
case .failure(let error): case .failure(let error):
didFinish(with: error) didFinish(with: error)
@ -72,7 +72,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
} }
/// Do not override pending statuses with the remote statuses of the same articles, otherwise an article will temporarily re-acquire the remote status before the pending status is pushed and subseqently pulled. /// Do not override pending statuses with the remote statuses of the same articles, otherwise an article will temporarily re-acquire the remote status before the pending status is pushed and subseqently pulled.
private func removeEntryIdsWithPendingStatus() { private func removeEntryIDsWithPendingStatus() {
guard !isCanceled else { guard !isCanceled else {
didFinish() didFinish()
return return
@ -82,7 +82,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
do { do {
if let pendingArticleIDs = try await self.database.selectPendingReadStatusArticleIDs() { if let pendingArticleIDs = try await self.database.selectPendingReadStatusArticleIDs() {
self.remoteEntryIds.subtract(pendingArticleIDs) self.remoteEntryIDs.subtract(pendingArticleIDs)
} }
self.updateUnreadStatuses() self.updateUnreadStatuses()
} catch { } catch {
@ -116,7 +116,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
return return
} }
let remoteUnreadArticleIDs = remoteEntryIds let remoteUnreadArticleIDs = remoteEntryIDs
Task { @MainActor in Task { @MainActor in

View File

@ -12,7 +12,7 @@ import os.log
protocol FeedlyParsedItemsByFeedProviding { protocol FeedlyParsedItemsByFeedProviding {
var parsedItemsByFeedProviderName: String { get } var parsedItemsByFeedProviderName: String { get }
var parsedItemsKeyedByFeedId: [String: Set<ParsedItem>] { get } var parsedItemsKeyedByFeedID: [String: Set<ParsedItem>] { get }
} }
/// Group articles by their feeds. /// Group articles by their feeds.
@ -26,12 +26,12 @@ final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyPar
return name ?? String(describing: Self.self) return name ?? String(describing: Self.self)
} }
var parsedItemsKeyedByFeedId: [String : Set<ParsedItem>] { var parsedItemsKeyedByFeedID: [String : Set<ParsedItem>] {
precondition(Thread.isMainThread) // Needs to be on main thread because Feed is a main-thread-only model type. precondition(Thread.isMainThread) // Needs to be on main thread because Feed is a main-thread-only model type.
return itemsKeyedByFeedId return itemsKeyedByFeedID
} }
private var itemsKeyedByFeedId = [String: Set<ParsedItem>]() private var itemsKeyedByFeedID = [String: Set<ParsedItem>]()
init(account: Account, parsedItemProvider: FeedlyParsedItemProviding, log: OSLog) { init(account: Account, parsedItemProvider: FeedlyParsedItemProviding, log: OSLog) {
self.account = account self.account = account
@ -62,6 +62,6 @@ final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyPar
os_log(.debug, log: log, "Grouped %i items by %i feeds for %@", items.count, dict.count, parsedItemProvider.parsedItemProviderName) os_log(.debug, log: log, "Grouped %i items by %i feeds for %@", items.count, dict.count, parsedItemProvider.parsedItemProviderName)
itemsKeyedByFeedId = dict itemsKeyedByFeedID = dict
} }
} }

View File

@ -45,7 +45,7 @@ final class FeedlyRequestStreamsOperation: FeedlyOperation {
// TODO: Prioritise the must read collection/category before others so the most important content for the user loads first. // TODO: Prioritise the must read collection/category before others so the most important content for the user loads first.
for collection in collectionsProvider.collections { for collection in collectionsProvider.collections {
let resource = FeedlyCategoryResourceId(id: collection.id) let resource = FeedlyCategoryResourceID(id: collection.id)
let operation = FeedlyGetStreamContentsOperation(account: account, let operation = FeedlyGetStreamContentsOperation(account: account,
resource: resource, resource: resource,
service: service, service: service,

View File

@ -53,12 +53,12 @@ private extension FeedlySendArticleStatusesOperation {
let group = DispatchGroup() let group = DispatchGroup()
for pairing in statuses { for pairing in statuses {
let articleIds = pending.filter { $0.key == pairing.status && $0.flag == pairing.flag } let articleIDs = pending.filter { $0.key == pairing.status && $0.flag == pairing.flag }
guard !articleIds.isEmpty else { guard !articleIDs.isEmpty else {
continue continue
} }
let ids = Set(articleIds.map { $0.articleID }) let ids = Set(articleIDs.map { $0.articleID })
let database = self.database let database = self.database
group.enter() group.enter()
service.mark(ids, as: pairing.action) { result in service.mark(ids, as: pairing.action) { result in

View File

@ -34,7 +34,7 @@ final class FeedlySyncAllOperation: FeedlyOperation {
/// ///
/// Download articles for statuses at the union of those statuses without its corresponding article and those included in 3 (changed since last successful sync). /// Download articles for statuses at the union of those statuses without its corresponding article and those included in 3 (changed since last successful sync).
/// ///
@MainActor init(account: Account, feedlyUserId: String, lastSuccessfulFetchStartDate: Date?, markArticlesService: FeedlyMarkArticlesService, getUnreadService: FeedlyGetStreamIdsService, getCollectionsService: FeedlyGetCollectionsService, getStreamContentsService: FeedlyGetStreamContentsService, getStarredService: FeedlyGetStreamIdsService, getStreamIdsService: FeedlyGetStreamIdsService, getEntriesService: FeedlyGetEntriesService, database: SyncDatabase, downloadProgress: DownloadProgress, log: OSLog) { @MainActor init(account: Account, feedlyUserID: String, lastSuccessfulFetchStartDate: Date?, markArticlesService: FeedlyMarkArticlesService, getUnreadService: FeedlyGetStreamIDsService, getCollectionsService: FeedlyGetCollectionsService, getStreamContentsService: FeedlyGetStreamContentsService, getStarredService: FeedlyGetStreamIDsService, getStreamIDsService: FeedlyGetStreamIDsService, getEntriesService: FeedlyGetEntriesService, database: SyncDatabase, downloadProgress: DownloadProgress, log: OSLog) {
self.syncUUID = UUID() self.syncUUID = UUID()
self.log = log self.log = log
self.operationQueue.suspend() self.operationQueue.suspend()
@ -68,53 +68,53 @@ final class FeedlySyncAllOperation: FeedlyOperation {
createFeedsOperation.addDependency(mirrorCollectionsAsFolders) createFeedsOperation.addDependency(mirrorCollectionsAsFolders)
self.operationQueue.add(createFeedsOperation) self.operationQueue.add(createFeedsOperation)
let getAllArticleIds = FeedlyIngestStreamArticleIdsOperation(account: account, userId: feedlyUserId, service: getStreamIdsService, log: log) let getAllArticleIDs = FeedlyIngestStreamArticleIDsOperation(account: account, userID: feedlyUserID, service: getStreamIDsService, log: log)
getAllArticleIds.delegate = self getAllArticleIDs.delegate = self
getAllArticleIds.downloadProgress = downloadProgress getAllArticleIDs.downloadProgress = downloadProgress
getAllArticleIds.addDependency(createFeedsOperation) getAllArticleIDs.addDependency(createFeedsOperation)
self.operationQueue.add(getAllArticleIds) self.operationQueue.add(getAllArticleIDs)
// Get each page of unread article ids in the global.all stream for the last 31 days (nil = Feedly API default). // Get each page of unread article ids in the global.all stream for the last 31 days (nil = Feedly API default).
let getUnread = FeedlyIngestUnreadArticleIdsOperation(account: account, userId: feedlyUserId, service: getUnreadService, database: database, newerThan: nil, log: log) let getUnread = FeedlyIngestUnreadArticleIDsOperation(account: account, userID: feedlyUserID, service: getUnreadService, database: database, newerThan: nil, log: log)
getUnread.delegate = self getUnread.delegate = self
getUnread.addDependency(getAllArticleIds) getUnread.addDependency(getAllArticleIDs)
getUnread.downloadProgress = downloadProgress getUnread.downloadProgress = downloadProgress
self.operationQueue.add(getUnread) self.operationQueue.add(getUnread)
// Get each page of the article ids which have been update since the last successful fetch start date. // Get each page of the article ids which have been update since the last successful fetch start date.
// If the date is nil, this operation provides an empty set (everything is new, nothing is updated). // If the date is nil, this operation provides an empty set (everything is new, nothing is updated).
let getUpdated = FeedlyGetUpdatedArticleIdsOperation(account: account, userId: feedlyUserId, service: getStreamIdsService, newerThan: lastSuccessfulFetchStartDate, log: log) let getUpdated = FeedlyGetUpdatedArticleIDsOperation(account: account, userID: feedlyUserID, service: getStreamIDsService, newerThan: lastSuccessfulFetchStartDate, log: log)
getUpdated.delegate = self getUpdated.delegate = self
getUpdated.downloadProgress = downloadProgress getUpdated.downloadProgress = downloadProgress
getUpdated.addDependency(createFeedsOperation) getUpdated.addDependency(createFeedsOperation)
self.operationQueue.add(getUpdated) self.operationQueue.add(getUpdated)
// Get each page of the article ids for starred articles. // Get each page of the article ids for starred articles.
let getStarred = FeedlyIngestStarredArticleIdsOperation(account: account, userId: feedlyUserId, service: getStarredService, database: database, newerThan: nil, log: log) let getStarred = FeedlyIngestStarredArticleIDsOperation(account: account, userID: feedlyUserID, service: getStarredService, database: database, newerThan: nil, log: log)
getStarred.delegate = self getStarred.delegate = self
getStarred.downloadProgress = downloadProgress getStarred.downloadProgress = downloadProgress
getStarred.addDependency(createFeedsOperation) getStarred.addDependency(createFeedsOperation)
self.operationQueue.add(getStarred) self.operationQueue.add(getStarred)
// Now all the possible article ids we need have a status, fetch the article ids for missing articles. // Now all the possible article ids we need have a status, fetch the article ids for missing articles.
let getMissingIds = FeedlyFetchIdsForMissingArticlesOperation(account: account) let getMissingIDs = FeedlyFetchIDsForMissingArticlesOperation(account: account)
getMissingIds.delegate = self getMissingIDs.delegate = self
getMissingIds.downloadProgress = downloadProgress getMissingIDs.downloadProgress = downloadProgress
getMissingIds.addDependency(getAllArticleIds) getMissingIDs.addDependency(getAllArticleIDs)
getMissingIds.addDependency(getUnread) getMissingIDs.addDependency(getUnread)
getMissingIds.addDependency(getStarred) getMissingIDs.addDependency(getStarred)
getMissingIds.addDependency(getUpdated) getMissingIDs.addDependency(getUpdated)
self.operationQueue.add(getMissingIds) self.operationQueue.add(getMissingIDs)
// Download all the missing and updated articles // Download all the missing and updated articles
let downloadMissingArticles = FeedlyDownloadArticlesOperation(account: account, let downloadMissingArticles = FeedlyDownloadArticlesOperation(account: account,
missingArticleEntryIdProvider: getMissingIds, missingArticleEntryIDProvider: getMissingIDs,
updatedArticleEntryIdProvider: getUpdated, updatedArticleEntryIDProvider: getUpdated,
getEntriesService: getEntriesService, getEntriesService: getEntriesService,
log: log) log: log)
downloadMissingArticles.delegate = self downloadMissingArticles.delegate = self
downloadMissingArticles.downloadProgress = downloadProgress downloadMissingArticles.downloadProgress = downloadProgress
downloadMissingArticles.addDependency(getMissingIds) downloadMissingArticles.addDependency(getMissingIDs)
downloadMissingArticles.addDependency(getUpdated) downloadMissingArticles.addDependency(getUpdated)
self.operationQueue.add(downloadMissingArticles) self.operationQueue.add(downloadMissingArticles)
@ -126,8 +126,8 @@ final class FeedlySyncAllOperation: FeedlyOperation {
self.operationQueue.add(finishOperation) self.operationQueue.add(finishOperation)
} }
@MainActor convenience init(account: Account, feedlyUserId: String, caller: FeedlyAPICaller, database: SyncDatabase, lastSuccessfulFetchStartDate: Date?, downloadProgress: DownloadProgress, log: OSLog) { @MainActor convenience init(account: Account, feedlyUserID: String, caller: FeedlyAPICaller, database: SyncDatabase, lastSuccessfulFetchStartDate: Date?, downloadProgress: DownloadProgress, log: OSLog) {
self.init(account: account, feedlyUserId: feedlyUserId, lastSuccessfulFetchStartDate: lastSuccessfulFetchStartDate, markArticlesService: caller, getUnreadService: caller, getCollectionsService: caller, getStreamContentsService: caller, getStarredService: caller, getStreamIdsService: caller, getEntriesService: caller, database: database, downloadProgress: downloadProgress, log: log) self.init(account: account, feedlyUserID: feedlyUserID, lastSuccessfulFetchStartDate: lastSuccessfulFetchStartDate, markArticlesService: caller, getUnreadService: caller, getCollectionsService: caller, getStreamContentsService: caller, getStarredService: caller, getStreamIDsService: caller, getEntriesService: caller, database: database, downloadProgress: downloadProgress, log: log)
} }
override func run() { override func run() {

View File

@ -16,7 +16,7 @@ import Core
final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyGetStreamContentsOperationDelegate, FeedlyCheckpointOperationDelegate { final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyGetStreamContentsOperationDelegate, FeedlyCheckpointOperationDelegate {
private let account: Account private let account: Account
private let resource: FeedlyResourceId private let resource: FeedlyResourceID
private let operationQueue = MainThreadOperationQueue() private let operationQueue = MainThreadOperationQueue()
private let service: FeedlyGetStreamContentsService private let service: FeedlyGetStreamContentsService
private let newerThan: Date? private let newerThan: Date?
@ -24,7 +24,7 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
private let log: OSLog private let log: OSLog
private let finishOperation: FeedlyCheckpointOperation private let finishOperation: FeedlyCheckpointOperation
@MainActor init(account: Account, resource: FeedlyResourceId, service: FeedlyGetStreamContentsService, isPagingEnabled: Bool, newerThan: Date?, log: OSLog) { @MainActor init(account: Account, resource: FeedlyResourceID, service: FeedlyGetStreamContentsService, isPagingEnabled: Bool, newerThan: Date?, log: OSLog) {
self.account = account self.account = account
self.resource = resource self.resource = resource
self.service = service self.service = service
@ -42,7 +42,7 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD
} }
@MainActor convenience init(account: Account, credentials: Credentials, service: FeedlyGetStreamContentsService, newerThan: Date?, log: OSLog) { @MainActor convenience init(account: Account, credentials: Credentials, service: FeedlyGetStreamContentsService, newerThan: Date?, log: OSLog) {
let all = FeedlyCategoryResourceId.Global.all(for: credentials.username) let all = FeedlyCategoryResourceID.Global.all(for: credentials.username)
self.init(account: account, resource: all, service: service, isPagingEnabled: true, newerThan: newerThan, log: log) self.init(account: account, resource: all, service: service, isPagingEnabled: true, newerThan: newerThan, log: log)
} }

View File

@ -27,7 +27,7 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
override func run() { override func run() {
let feedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId let feedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedID
Task { @MainActor in Task { @MainActor in
do { do {

View File

@ -9,5 +9,5 @@
import Foundation import Foundation
protocol FeedlyGetStreamContentsService: AnyObject { protocol FeedlyGetStreamContentsService: AnyObject {
func getStreamContents(for resource: FeedlyResourceId, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStream, Error>) -> ()) func getStreamContents(for resource: FeedlyResourceID, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStream, Error>) -> ())
} }

View File

@ -1,5 +1,5 @@
// //
// FeedlyGetStreamIdsService.swift // FeedlyGetStreamIDsService.swift
// Account // Account
// //
// Created by Kiel Gillard on 21/10/19. // Created by Kiel Gillard on 21/10/19.
@ -8,6 +8,6 @@
import Foundation import Foundation
protocol FeedlyGetStreamIdsService: AnyObject { protocol FeedlyGetStreamIDsService: AnyObject {
func getStreamIds(for resource: FeedlyResourceId, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStreamIds, Error>) -> ()) func getStreamIDs(for resource: FeedlyResourceID, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStreamIDs, Error>) -> ())
} }

View File

@ -31,5 +31,5 @@ enum FeedlyMarkAction: String {
} }
protocol FeedlyMarkArticlesService: AnyObject { protocol FeedlyMarkArticlesService: AnyObject {
func mark(_ articleIds: Set<String>, as action: FeedlyMarkAction, completion: @escaping (Result<Void, Error>) -> ()) func mark(_ articleIDs: Set<String>, as action: FeedlyMarkAction, completion: @escaping (Result<Void, Error>) -> ())
} }

View File

@ -36,7 +36,7 @@ public extension URLRequest {
URLQueryItem(name: "password", value: credentials.secret), URLQueryItem(name: "password", value: credentials.secret),
] ]
httpBody = postData.enhancedPercentEncodedQuery?.data(using: .utf8) httpBody = postData.enhancedPercentEncodedQuery?.data(using: .utf8)
case .newsBlurSessionId: case .newsBlurSessionID:
setValue("\(NewsBlurAPICaller.sessionIDCookieKey)=\(credentials.secret)", forHTTPHeaderField: "Cookie") setValue("\(NewsBlurAPICaller.sessionIDCookieKey)=\(credentials.secret)", forHTTPHeaderField: "Cookie")
httpShouldHandleCookies = true httpShouldHandleCookies = true
case .readerBasic: case .readerBasic:

View File

@ -64,7 +64,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
waitForExpectations(timeout: 2) waitForExpectations(timeout: 2)
let feedIds = Set([feedsForFolderOne, feedsForFolderTwo] let feedIDs = Set([feedsForFolderOne, feedsForFolderTwo]
.flatMap { $0 } .flatMap { $0 }
.map { $0.id }) .map { $0.id })
@ -73,28 +73,28 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
.map { $0.title }) .map { $0.title })
let accountFeeds = account.flattenedFeeds() let accountFeeds = account.flattenedFeeds()
let ingestedIds = Set(accountFeeds.map { $0.feedID }) let ingestedIDs = Set(accountFeeds.map { $0.feedID })
let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay }) let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay })
let missingIds = feedIds.subtracting(ingestedIds) let missingIDs = feedIDs.subtracting(ingestedIDs)
let missingTitles = feedTitles.subtracting(ingestedTitles) let missingTitles = feedTitles.subtracting(ingestedTitles)
XCTAssertTrue(missingIds.isEmpty, "Failed to ingest feeds with these ids.") XCTAssertTrue(missingIDs.isEmpty, "Failed to ingest feeds with these ids.")
XCTAssertTrue(missingTitles.isEmpty, "Failed to ingest feeds with these titles.") XCTAssertTrue(missingTitles.isEmpty, "Failed to ingest feeds with these titles.")
let expectedFolderAndFeedIds = namesAndFeeds let expectedFolderAndFeedIDs = namesAndFeeds
.sorted { $0.0.id < $1.0.id } .sorted { $0.0.id < $1.0.id }
.map { folder, feeds -> [String: [String]] in .map { folder, feeds -> [String: [String]] in
return [folder.id: feeds.map { $0.id }.sorted(by: <)] return [folder.id: feeds.map { $0.id }.sorted(by: <)]
} }
let ingestedFolderAndFeedIds = (account.folders ?? Set()) let ingestedFolderAndFeedIDs = (account.folders ?? Set())
.sorted { $0.externalID! < $1.externalID! } .sorted { $0.externalID! < $1.externalID! }
.compactMap { folder -> [String: [String]]? in .compactMap { folder -> [String: [String]]? in
return [folder.externalID!: folder.topLevelFeeds.map { $0.feedID }.sorted(by: <)] return [folder.externalID!: folder.topLevelFeeds.map { $0.feedID }.sorted(by: <)]
} }
XCTAssertEqual(expectedFolderAndFeedIds, ingestedFolderAndFeedIds, "Did not ingest feeds in their corresponding folders.") XCTAssertEqual(expectedFolderAndFeedIDs, ingestedFolderAndFeedIDs, "Did not ingest feeds in their corresponding folders.")
} }
func testRemoveFeeds() { func testRemoveFeeds() {
@ -157,7 +157,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
waitForExpectations(timeout: 2) waitForExpectations(timeout: 2)
let feedIds = Set([feedsForFolderOne, feedsForFolderTwo] let feedIDs = Set([feedsForFolderOne, feedsForFolderTwo]
.flatMap { $0 } .flatMap { $0 }
.map { $0.id }) .map { $0.id })
@ -166,30 +166,30 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
.map { $0.title }) .map { $0.title })
let accountFeeds = account.flattenedFeeds() let accountFeeds = account.flattenedFeeds()
let ingestedIds = Set(accountFeeds.map { $0.feedID }) let ingestedIDs = Set(accountFeeds.map { $0.feedID })
let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay }) let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay })
XCTAssertEqual(ingestedIds.count, feedIds.count) XCTAssertEqual(ingestedIDs.count, feedIDs.count)
XCTAssertEqual(ingestedTitles.count, feedTitles.count) XCTAssertEqual(ingestedTitles.count, feedTitles.count)
let missingIds = feedIds.subtracting(ingestedIds) let missingIDs = feedIDs.subtracting(ingestedIDs)
let missingTitles = feedTitles.subtracting(ingestedTitles) let missingTitles = feedTitles.subtracting(ingestedTitles)
XCTAssertTrue(missingIds.isEmpty, "Failed to ingest feeds with these ids.") XCTAssertTrue(missingIDs.isEmpty, "Failed to ingest feeds with these ids.")
XCTAssertTrue(missingTitles.isEmpty, "Failed to ingest feeds with these titles.") XCTAssertTrue(missingTitles.isEmpty, "Failed to ingest feeds with these titles.")
let expectedFolderAndFeedIds = namesAndFeeds let expectedFolderAndFeedIDs = namesAndFeeds
.sorted { $0.0.id < $1.0.id } .sorted { $0.0.id < $1.0.id }
.map { folder, feeds -> [String: [String]] in .map { folder, feeds -> [String: [String]] in
return [folder.id: feeds.map { $0.id }.sorted(by: <)] return [folder.id: feeds.map { $0.id }.sorted(by: <)]
} }
let ingestedFolderAndFeedIds = (account.folders ?? Set()) let ingestedFolderAndFeedIDs = (account.folders ?? Set())
.sorted { $0.externalID! < $1.externalID! } .sorted { $0.externalID! < $1.externalID! }
.compactMap { folder -> [String: [String]]? in .compactMap { folder -> [String: [String]]? in
return [folder.externalID!: folder.topLevelFeeds.map { $0.feedID }.sorted(by: <)] return [folder.externalID!: folder.topLevelFeeds.map { $0.feedID }.sorted(by: <)]
} }
XCTAssertEqual(expectedFolderAndFeedIds, ingestedFolderAndFeedIds, "Did not ingest feeds to their corresponding folders.") XCTAssertEqual(expectedFolderAndFeedIDs, ingestedFolderAndFeedIDs, "Did not ingest feeds to their corresponding folders.")
} }
} }

View File

@ -38,7 +38,7 @@ class FeedlyEntryParserTests: XCTestCase {
let parser = FeedlyEntryParser(entry: entry) let parser = FeedlyEntryParser(entry: entry)
XCTAssertEqual(parser.id, entry.id) XCTAssertEqual(parser.id, entry.id)
XCTAssertEqual(parser.feedUrl, origin.streamId) XCTAssertEqual(parser.feedUrl, origin.streamID)
XCTAssertEqual(parser.externalUrl, canonicalLink.href) XCTAssertEqual(parser.externalUrl, canonicalLink.href)
XCTAssertEqual(parser.title, entry.title) XCTAssertEqual(parser.title, entry.title)
XCTAssertEqual(parser.contentHMTL, content.content) XCTAssertEqual(parser.contentHMTL, content.content)
@ -56,7 +56,7 @@ class FeedlyEntryParserTests: XCTestCase {
// The following is not an error. // The following is not an error.
// The feedURL must match the feedID for the article to be connected to its matching feed. // The feedURL must match the feedID for the article to be connected to its matching feed.
XCTAssertEqual(item.feedURL, origin.streamId) XCTAssertEqual(item.feedURL, origin.streamID)
XCTAssertEqual(item.title, entry.title) XCTAssertEqual(item.title, entry.title)
XCTAssertEqual(item.contentHTML, content.content) XCTAssertEqual(item.contentHTML, content.content)
XCTAssertEqual(item.contentText, nil, "Is it now free of HTML characters?") XCTAssertEqual(item.contentText, nil, "Is it now free of HTML characters?")

View File

@ -36,21 +36,21 @@ class FeedlyGetCollectionsOperationTests: XCTestCase {
let ids = Set(getCollections.collections.map { $0.id }) let ids = Set(getCollections.collections.map { $0.id })
let missingLabels = labelsInJSON.subtracting(labels) let missingLabels = labelsInJSON.subtracting(labels)
let missingIds = idsInJSON.subtracting(ids) let missingIDs = idsInJSON.subtracting(ids)
XCTAssertEqual(getCollections.collections.count, collections.count, "Mismatch between collections provided by operation and test JSON collections.") XCTAssertEqual(getCollections.collections.count, collections.count, "Mismatch between collections provided by operation and test JSON collections.")
XCTAssertTrue(missingLabels.isEmpty, "Collections with these labels did not have a corresponding \(FeedlyCollection.self) value with the same name.") XCTAssertTrue(missingLabels.isEmpty, "Collections with these labels did not have a corresponding \(FeedlyCollection.self) value with the same name.")
XCTAssertTrue(missingIds.isEmpty, "Collections with these ids did not have a corresponding \(FeedlyCollection.self) with the same id.") XCTAssertTrue(missingIDs.isEmpty, "Collections with these ids did not have a corresponding \(FeedlyCollection.self) with the same id.")
for collection in collections { for collection in collections {
let collectionId = collection["id"] as! String let collectionID = collection["id"] as! String
let collectionFeeds = collection["feeds"] as! [[String: Any]] let collectionFeeds = collection["feeds"] as! [[String: Any]]
let collectionFeedIds = Set(collectionFeeds.map { $0["id"] as! String }) let collectionFeedIDs = Set(collectionFeeds.map { $0["id"] as! String })
for operationCollection in getCollections.collections where operationCollection.id == collectionId { for operationCollection in getCollections.collections where operationCollection.id == collectionID {
let feedIds = Set(operationCollection.feeds.map { $0.id }) let feedIDs = Set(operationCollection.feeds.map { $0.id })
let missingIds = collectionFeedIds.subtracting(feedIds) let missingIDs = collectionFeedIDs.subtracting(feedIDs)
XCTAssertTrue(missingIds.isEmpty, "Feeds with these ids were not found in the \"\(operationCollection.label)\" \(FeedlyCollection.self).") XCTAssertTrue(missingIDs.isEmpty, "Feeds with these ids were not found in the \"\(operationCollection.label)\" \(FeedlyCollection.self).")
} }
} }
} }

View File

@ -28,7 +28,7 @@ class FeedlyGetStreamContentsOperationTests: XCTestCase {
func testGetStreamContentsFailure() { func testGetStreamContentsFailure() {
let service = TestGetStreamContentsService() let service = TestGetStreamContentsService()
let resource = FeedlyCategoryResourceId(id: "user/1234/category/5678") let resource = FeedlyCategoryResourceID(id: "user/1234/category/5678")
let getStreamContents = FeedlyGetStreamContentsOperation(account: account, resource: resource, service: service, continuation: nil, newerThan: nil, unreadOnly: nil, log: support.log) let getStreamContents = FeedlyGetStreamContentsOperation(account: account, resource: resource, service: service, continuation: nil, newerThan: nil, unreadOnly: nil, log: support.log)
@ -48,7 +48,7 @@ class FeedlyGetStreamContentsOperationTests: XCTestCase {
func testValuesPassingForGetStreamContents() { func testValuesPassingForGetStreamContents() {
let service = TestGetStreamContentsService() let service = TestGetStreamContentsService()
let resource = FeedlyCategoryResourceId(id: "user/1234/category/5678") let resource = FeedlyCategoryResourceID(id: "user/1234/category/5678")
let continuation: String? = "abcdefg" let continuation: String? = "abcdefg"
let newerThan: Date? = Date(timeIntervalSinceReferenceDate: 86) let newerThan: Date? = Date(timeIntervalSinceReferenceDate: 86)
@ -85,9 +85,9 @@ class FeedlyGetStreamContentsOperationTests: XCTestCase {
XCTAssertEqual(stream.updated, mockStream.updated) XCTAssertEqual(stream.updated, mockStream.updated)
XCTAssertEqual(stream.continuation, mockStream.continuation) XCTAssertEqual(stream.continuation, mockStream.continuation)
let streamIds = stream.items.map { $0.id } let streamIDs = stream.items.map { $0.id }
let mockStreamIds = mockStream.items.map { $0.id } let mockStreamIDs = mockStream.items.map { $0.id }
XCTAssertEqual(streamIds, mockStreamIds) XCTAssertEqual(streamIDs, mockStreamIDs)
} }
func testGetStreamContentsFromJSON() { func testGetStreamContentsFromJSON() {
@ -96,7 +96,7 @@ class FeedlyGetStreamContentsOperationTests: XCTestCase {
let jsonName = "JSON/feedly_macintosh_initial" let jsonName = "JSON/feedly_macintosh_initial"
transport.testFiles["/v3/streams/contents"] = "\(jsonName).json" transport.testFiles["/v3/streams/contents"] = "\(jsonName).json"
let resource = FeedlyCategoryResourceId(id: "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815") let resource = FeedlyCategoryResourceID(id: "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815")
let getStreamContents = FeedlyGetStreamContentsOperation(account: account, resource: resource, service: caller, continuation: nil, newerThan: nil, unreadOnly: nil, log: support.log) let getStreamContents = FeedlyGetStreamContentsOperation(account: account, resource: resource, service: caller, continuation: nil, newerThan: nil, unreadOnly: nil, log: support.log)
let completionExpectation = expectation(description: "Did Finish") let completionExpectation = expectation(description: "Did Finish")

View File

@ -1,5 +1,5 @@
// //
// FeedlyGetStreamIdsOperationTests.swift // FeedlyGetStreamIDsOperationTests.swift
// AccountTests // AccountTests
// //
// Created by Kiel Gillard on 23/10/19. // Created by Kiel Gillard on 23/10/19.
@ -9,7 +9,7 @@
import XCTest import XCTest
@testable import Account @testable import Account
class FeedlyGetStreamIdsOperationTests: XCTestCase { class FeedlyGetStreamIDsOperationTests: XCTestCase {
private var account: Account! private var account: Account!
private let support = FeedlyTestSupport() private let support = FeedlyTestSupport()
@ -26,39 +26,39 @@ class FeedlyGetStreamIdsOperationTests: XCTestCase {
super.tearDown() super.tearDown()
} }
func testGetStreamIdsFailure() { func testGetStreamIDsFailure() {
let service = TestGetStreamIdsService() let service = TestGetStreamIDsService()
let resource = FeedlyCategoryResourceId(id: "user/1234/category/5678") let resource = FeedlyCategoryResourceID(id: "user/1234/category/5678")
let getStreamIds = FeedlyGetStreamIdsOperation(account: account, resource: resource, service: service, continuation: nil, newerThan: nil, unreadOnly: nil, log: support.log) let getStreamIDs = FeedlyGetStreamIDsOperation(account: account, resource: resource, service: service, continuation: nil, newerThan: nil, unreadOnly: nil, log: support.log)
service.mockResult = .failure(URLError(.fileDoesNotExist)) service.mockResult = .failure(URLError(.fileDoesNotExist))
let completionExpectation = expectation(description: "Did Finish") let completionExpectation = expectation(description: "Did Finish")
getStreamIds.completionBlock = { _ in getStreamIDs.completionBlock = { _ in
completionExpectation.fulfill() completionExpectation.fulfill()
} }
MainThreadOperationQueue.shared.add(getStreamIds) MainThreadOperationQueue.shared.add(getStreamIDs)
waitForExpectations(timeout: 2) waitForExpectations(timeout: 2)
XCTAssertNil(getStreamIds.streamIds) XCTAssertNil(getStreamIDs.streamIDs)
} }
func testValuesPassingForGetStreamIds() { func testValuesPassingForGetStreamIDs() {
let service = TestGetStreamIdsService() let service = TestGetStreamIDsService()
let resource = FeedlyCategoryResourceId(id: "user/1234/category/5678") let resource = FeedlyCategoryResourceID(id: "user/1234/category/5678")
let continuation: String? = "gfdsa" let continuation: String? = "gfdsa"
let newerThan: Date? = Date(timeIntervalSinceReferenceDate: 1000) let newerThan: Date? = Date(timeIntervalSinceReferenceDate: 1000)
let unreadOnly: Bool? = false let unreadOnly: Bool? = false
let getStreamIds = FeedlyGetStreamIdsOperation(account: account, resource: resource, service: service, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly, log: support.log) let getStreamIDs = FeedlyGetStreamIDsOperation(account: account, resource: resource, service: service, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly, log: support.log)
let mockStreamIds = FeedlyStreamIds(continuation: "1234", ids: ["item/1", "item/2", "item/3"]) let mockStreamIDs = FeedlyStreamIDs(continuation: "1234", ids: ["item/1", "item/2", "item/3"])
service.mockResult = .success(mockStreamIds) service.mockResult = .success(mockStreamIDs)
service.getStreamIdsExpectation = expectation(description: "Did Call Service") service.getStreamIDsExpectation = expectation(description: "Did Call Service")
service.parameterTester = { serviceResource, serviceContinuation, serviceNewerThan, serviceUnreadOnly in service.parameterTester = { serviceResource, serviceContinuation, serviceNewerThan, serviceUnreadOnly in
// Verify these values given to the operation are passed to the service. // Verify these values given to the operation are passed to the service.
XCTAssertEqual(serviceResource.id, resource.id) XCTAssertEqual(serviceResource.id, resource.id)
@ -68,49 +68,49 @@ class FeedlyGetStreamIdsOperationTests: XCTestCase {
} }
let completionExpectation = expectation(description: "Did Finish") let completionExpectation = expectation(description: "Did Finish")
getStreamIds.completionBlock = { _ in getStreamIDs.completionBlock = { _ in
completionExpectation.fulfill() completionExpectation.fulfill()
} }
MainThreadOperationQueue.shared.add(getStreamIds) MainThreadOperationQueue.shared.add(getStreamIDs)
waitForExpectations(timeout: 2) waitForExpectations(timeout: 2)
guard let streamIds = getStreamIds.streamIds else { guard let streamIDs = getStreamIDs.streamIDs else {
XCTFail("\(FeedlyGetStreamIdsOperation.self) did not store the stream.") XCTFail("\(FeedlyGetStreamIDsOperation.self) did not store the stream.")
return return
} }
XCTAssertEqual(streamIds.continuation, mockStreamIds.continuation) XCTAssertEqual(streamIDs.continuation, mockStreamIDs.continuation)
XCTAssertEqual(streamIds.ids, mockStreamIds.ids) XCTAssertEqual(streamIDs.ids, mockStreamIDs.ids)
} }
func testGetStreamIdsFromJSON() { func testGetStreamIDsFromJSON() {
let support = FeedlyTestSupport() let support = FeedlyTestSupport()
let (transport, caller) = support.makeMockNetworkStack() let (transport, caller) = support.makeMockNetworkStack()
let jsonName = "JSON/feedly_unreads_1000" let jsonName = "JSON/feedly_unreads_1000"
transport.testFiles["/v3/streams/ids"] = "\(jsonName).json" transport.testFiles["/v3/streams/ids"] = "\(jsonName).json"
let resource = FeedlyCategoryResourceId(id: "user/1234/category/5678") let resource = FeedlyCategoryResourceID(id: "user/1234/category/5678")
let getStreamIds = FeedlyGetStreamIdsOperation(account: account, resource: resource, service: caller, continuation: nil, newerThan: nil, unreadOnly: nil, log: support.log) let getStreamIDs = FeedlyGetStreamIDsOperation(account: account, resource: resource, service: caller, continuation: nil, newerThan: nil, unreadOnly: nil, log: support.log)
let completionExpectation = expectation(description: "Did Finish") let completionExpectation = expectation(description: "Did Finish")
getStreamIds.completionBlock = { _ in getStreamIDs.completionBlock = { _ in
completionExpectation.fulfill() completionExpectation.fulfill()
} }
MainThreadOperationQueue.shared.add(getStreamIds) MainThreadOperationQueue.shared.add(getStreamIDs)
waitForExpectations(timeout: 2) waitForExpectations(timeout: 2)
guard let streamIds = getStreamIds.streamIds else { guard let streamIDs = getStreamIDs.streamIDs else {
return XCTFail("Expected to have a stream of identifiers.") return XCTFail("Expected to have a stream of identifiers.")
} }
let streamIdsJSON = support.testJSON(named: jsonName) as! [String:Any] let streamIDsJSON = support.testJSON(named: jsonName) as! [String:Any]
let continuation = streamIdsJSON["continuation"] as! String let continuation = streamIDsJSON["continuation"] as! String
XCTAssertEqual(streamIds.continuation, continuation) XCTAssertEqual(streamIDs.continuation, continuation)
XCTAssertEqual(streamIds.ids, streamIdsJSON["ids"] as! [String]) XCTAssertEqual(streamIDs.ids, streamIDsJSON["ids"] as! [String])
} }
} }

View File

@ -47,16 +47,16 @@ class FeedlyMirrorCollectionsAsFoldersOperationTests: XCTestCase {
let folders = account.folders ?? Set() let folders = account.folders ?? Set()
let folderNames = Set(folders.compactMap { $0.nameForDisplay }) let folderNames = Set(folders.compactMap { $0.nameForDisplay })
let folderExternalIds = Set(folders.compactMap { $0.externalID }) let folderExternalIDs = Set(folders.compactMap { $0.externalID })
let collectionLabels = Set(provider.collections.map { $0.label }) let collectionLabels = Set(provider.collections.map { $0.label })
let collectionIds = Set(provider.collections.map { $0.id }) let collectionIDs = Set(provider.collections.map { $0.id })
let missingNames = collectionLabels.subtracting(folderNames) let missingNames = collectionLabels.subtracting(folderNames)
let missingIds = collectionIds.subtracting(folderExternalIds) let missingIDs = collectionIDs.subtracting(folderExternalIDs)
XCTAssertTrue(missingNames.isEmpty, "Collections with these labels have no corresponding folder.") XCTAssertTrue(missingNames.isEmpty, "Collections with these labels have no corresponding folder.")
XCTAssertTrue(missingIds.isEmpty, "Collections with these ids have no corresponding folder.") XCTAssertTrue(missingIDs.isEmpty, "Collections with these ids have no corresponding folder.")
// XCTAssertEqual(mirrorOperation.collectionsAndFolders.count, provider.collections.count, "Mismatch between collections and folders.") // XCTAssertEqual(mirrorOperation.collectionsAndFolders.count, provider.collections.count, "Mismatch between collections and folders.")
} }
@ -90,16 +90,16 @@ class FeedlyMirrorCollectionsAsFoldersOperationTests: XCTestCase {
let folders = account.folders ?? Set() let folders = account.folders ?? Set()
let folderNames = Set(folders.compactMap { $0.nameForDisplay }) let folderNames = Set(folders.compactMap { $0.nameForDisplay })
let folderExternalIds = Set(folders.compactMap { $0.externalID }) let folderExternalIDs = Set(folders.compactMap { $0.externalID })
let collectionLabels = Set(provider.collections.map { $0.label }) let collectionLabels = Set(provider.collections.map { $0.label })
let collectionIds = Set(provider.collections.map { $0.id }) let collectionIDs = Set(provider.collections.map { $0.id })
let remainingNames = folderNames.subtracting(collectionLabels) let remainingNames = folderNames.subtracting(collectionLabels)
let remainingIds = folderExternalIds.subtracting(collectionIds) let remainingIDs = folderExternalIDs.subtracting(collectionIDs)
XCTAssertTrue(remainingNames.isEmpty, "Folders with these names remain with no corresponding collection.") XCTAssertTrue(remainingNames.isEmpty, "Folders with these names remain with no corresponding collection.")
XCTAssertTrue(remainingIds.isEmpty, "Folders with these ids remain with no corresponding collection.") XCTAssertTrue(remainingIDs.isEmpty, "Folders with these ids remain with no corresponding collection.")
XCTAssertTrue(removeFolders.feedsAndFolders.isEmpty) XCTAssertTrue(removeFolders.feedsAndFolders.isEmpty)
} }
@ -137,29 +137,29 @@ class FeedlyMirrorCollectionsAsFoldersOperationTests: XCTestCase {
let folders = account.folders ?? Set() let folders = account.folders ?? Set()
let folderNames = Set(folders.compactMap { $0.nameForDisplay }) let folderNames = Set(folders.compactMap { $0.nameForDisplay })
let folderExternalIds = Set(folders.compactMap { $0.externalID }) let folderExternalIDs = Set(folders.compactMap { $0.externalID })
let collectionLabels = Set(provider.collections.map { $0.label }) let collectionLabels = Set(provider.collections.map { $0.label })
let collectionIds = Set(provider.collections.map { $0.id }) let collectionIDs = Set(provider.collections.map { $0.id })
let missingNames = collectionLabels.subtracting(folderNames) let missingNames = collectionLabels.subtracting(folderNames)
let missingIds = collectionIds.subtracting(folderExternalIds) let missingIDs = collectionIDs.subtracting(folderExternalIDs)
XCTAssertTrue(missingNames.isEmpty, "Collections with these labels have no corresponding folder.") XCTAssertTrue(missingNames.isEmpty, "Collections with these labels have no corresponding folder.")
XCTAssertTrue(missingIds.isEmpty, "Collections with these ids have no corresponding folder.") XCTAssertTrue(missingIDs.isEmpty, "Collections with these ids have no corresponding folder.")
let collectionIdsAndFeedIds = provider.collections.map { collection -> [String:[String]] in let collectionIDsAndFeedIDs = provider.collections.map { collection -> [String:[String]] in
return [collection.id: collection.feeds.map { $0.id }.sorted(by: <)] return [collection.id: collection.feeds.map { $0.id }.sorted(by: <)]
} }
let folderIdsAndFeedIds = mirrorOperation.feedsAndFolders.compactMap { feeds, folder -> [String:[String]]? in let folderIDsAndFeedIDs = mirrorOperation.feedsAndFolders.compactMap { feeds, folder -> [String:[String]]? in
guard let id = folder.externalID else { guard let id = folder.externalID else {
return nil return nil
} }
return [id: feeds.map { $0.id }.sorted(by: <)] return [id: feeds.map { $0.id }.sorted(by: <)]
} }
XCTAssertEqual(collectionIdsAndFeedIds, folderIdsAndFeedIds, "Did not map folders to feeds correctly.") XCTAssertEqual(collectionIDsAndFeedIDs, folderIDsAndFeedIDs, "Did not map folders to feeds correctly.")
} }
func testRemovingFolderRemovesFeeds() { func testRemovingFolderRemovesFeeds() {

View File

@ -29,13 +29,13 @@ class FeedlyOrganiseParsedItemsByFeedOperationTests: XCTestCase {
struct TestParsedItemsProvider: FeedlyParsedItemProviding { struct TestParsedItemsProvider: FeedlyParsedItemProviding {
let parsedItemProviderName = "TestParsedItemsProvider" let parsedItemProviderName = "TestParsedItemsProvider"
var resource: FeedlyResourceId var resource: FeedlyResourceID
var parsedEntries: Set<ParsedItem> var parsedEntries: Set<ParsedItem>
} }
func testNoEntries() { func testNoEntries() {
let entries = support.makeParsedItemTestDataFor(numberOfFeeds: 0, numberOfItemsInFeeds: 0) let entries = support.makeParsedItemTestDataFor(numberOfFeeds: 0, numberOfItemsInFeeds: 0)
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789") let resource = FeedlyCategoryResourceID(id: "user/12345/category/6789")
let parsedEntries = Set(entries.values.flatMap { $0 }) let parsedEntries = Set(entries.values.flatMap { $0 })
let provider = TestParsedItemsProvider(resource: resource, parsedEntries: parsedEntries) let provider = TestParsedItemsProvider(resource: resource, parsedEntries: parsedEntries)
@ -50,13 +50,13 @@ class FeedlyOrganiseParsedItemsByFeedOperationTests: XCTestCase {
waitForExpectations(timeout: 2) waitForExpectations(timeout: 2)
let itemsAndFeedIds = organise.parsedItemsKeyedByFeedId let itemsAndFeedIDs = organise.parsedItemsKeyedByFeedID
XCTAssertEqual(itemsAndFeedIds, entries) XCTAssertEqual(itemsAndFeedIDs, entries)
} }
func testGroupsOneEntryByFeedId() { func testGroupsOneEntryByFeedId() {
let entries = support.makeParsedItemTestDataFor(numberOfFeeds: 1, numberOfItemsInFeeds: 1) let entries = support.makeParsedItemTestDataFor(numberOfFeeds: 1, numberOfItemsInFeeds: 1)
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789") let resource = FeedlyCategoryResourceID(id: "user/12345/category/6789")
let parsedEntries = Set(entries.values.flatMap { $0 }) let parsedEntries = Set(entries.values.flatMap { $0 })
let provider = TestParsedItemsProvider(resource: resource, parsedEntries: parsedEntries) let provider = TestParsedItemsProvider(resource: resource, parsedEntries: parsedEntries)
@ -71,13 +71,13 @@ class FeedlyOrganiseParsedItemsByFeedOperationTests: XCTestCase {
waitForExpectations(timeout: 2) waitForExpectations(timeout: 2)
let itemsAndFeedIds = organise.parsedItemsKeyedByFeedId let itemsAndFeedIDs = organise.parsedItemsKeyedByFeedID
XCTAssertEqual(itemsAndFeedIds, entries) XCTAssertEqual(itemsAndFeedIDs, entries)
} }
func testGroupsManyEntriesByFeedId() { func testGroupsManyEntriesByFeedId() {
let entries = support.makeParsedItemTestDataFor(numberOfFeeds: 100, numberOfItemsInFeeds: 100) let entries = support.makeParsedItemTestDataFor(numberOfFeeds: 100, numberOfItemsInFeeds: 100)
let resource = FeedlyCategoryResourceId(id: "user/12345/category/6789") let resource = FeedlyCategoryResourceID(id: "user/12345/category/6789")
let parsedEntries = Set(entries.values.flatMap { $0 }) let parsedEntries = Set(entries.values.flatMap { $0 })
let provider = TestParsedItemsProvider(resource: resource, parsedEntries: parsedEntries) let provider = TestParsedItemsProvider(resource: resource, parsedEntries: parsedEntries)
@ -92,7 +92,7 @@ class FeedlyOrganiseParsedItemsByFeedOperationTests: XCTestCase {
waitForExpectations(timeout: 2) waitForExpectations(timeout: 2)
let itemsAndFeedIds = organise.parsedItemsKeyedByFeedId let itemsAndFeedIDs = organise.parsedItemsKeyedByFeedID
XCTAssertEqual(itemsAndFeedIds, entries) XCTAssertEqual(itemsAndFeedIDs, entries)
} }
} }

View File

@ -1,5 +1,5 @@
// //
// FeedlyResourceIdTests.swift // FeedlyResourceIDTests.swift
// AccountTests // AccountTests
// //
// Created by Kiel Gillard on 3/10/19. // Created by Kiel Gillard on 3/10/19.
@ -9,15 +9,15 @@
import XCTest import XCTest
@testable import Account @testable import Account
class FeedlyResourceIdTests: XCTestCase { class FeedlyResourceIDTests: XCTestCase {
func testFeedResourceId() { func testFeedResourceID() {
let expectedUrl = "http://ranchero.com/blog/atom.xml" let expectedUrl = "http://ranchero.com/blog/atom.xml"
let feedResource = FeedlyFeedResourceId(id: "feed/\(expectedUrl)") let feedResource = FeedlyFeedResourceID(id: "feed/\(expectedUrl)")
let urlResource = FeedlyFeedResourceId(id: expectedUrl) let urlResource = FeedlyFeedResourceID(id: expectedUrl)
let otherResource = FeedlyFeedResourceId(id: "whiskey/\(expectedUrl)") let otherResource = FeedlyFeedResourceID(id: "whiskey/\(expectedUrl)")
let invalidResource = FeedlyFeedResourceId(id: "") let invalidResource = FeedlyFeedResourceID(id: "")
XCTAssertEqual(feedResource.url, expectedUrl) XCTAssertEqual(feedResource.url, expectedUrl)
XCTAssertEqual(urlResource.url, expectedUrl) XCTAssertEqual(urlResource.url, expectedUrl)

View File

@ -46,8 +46,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
} }
func testSendUnreadSuccess() { func testSendUnreadSuccess() {
let articleIds = Set((0..<100).map { "feed/0/article/\($0)" }) let articleIDs = Set((0..<100).map { "feed/0/article/\($0)" })
let statuses = articleIds.map { SyncStatus(articleID: $0, key: .read, flag: false) } let statuses = articleIDs.map { SyncStatus(articleID: $0, key: .read, flag: false) }
let insertExpectation = expectation(description: "Inserted Statuses") let insertExpectation = expectation(description: "Inserted Statuses")
container.database.insertStatuses(statuses) { error in container.database.insertStatuses(statuses) { error in
@ -59,8 +59,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
let service = TestMarkArticlesService() let service = TestMarkArticlesService()
service.mockResult = .success(()) service.mockResult = .success(())
service.parameterTester = { serviceArticleIds, action in service.parameterTester = { serviceArticleIDs, action in
XCTAssertEqual(serviceArticleIds, articleIds) XCTAssertEqual(serviceArticleIDs, articleIDs)
XCTAssertEqual(action, .unread) XCTAssertEqual(action, .unread)
} }
@ -89,8 +89,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
} }
func testSendUnreadFailure() { func testSendUnreadFailure() {
let articleIds = Set((0..<100).map { "feed/0/article/\($0)" }) let articleIDs = Set((0..<100).map { "feed/0/article/\($0)" })
let statuses = articleIds.map { SyncStatus(articleID: $0, key: .read, flag: false) } let statuses = articleIDs.map { SyncStatus(articleID: $0, key: .read, flag: false) }
let insertExpectation = expectation(description: "Inserted Statuses") let insertExpectation = expectation(description: "Inserted Statuses")
container.database.insertStatuses(statuses) { error in container.database.insertStatuses(statuses) { error in
@ -102,8 +102,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
let service = TestMarkArticlesService() let service = TestMarkArticlesService()
service.mockResult = .failure(URLError(.timedOut)) service.mockResult = .failure(URLError(.timedOut))
service.parameterTester = { serviceArticleIds, action in service.parameterTester = { serviceArticleIDs, action in
XCTAssertEqual(serviceArticleIds, articleIds) XCTAssertEqual(serviceArticleIDs, articleIDs)
XCTAssertEqual(action, .unread) XCTAssertEqual(action, .unread)
} }
@ -132,8 +132,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
} }
func testSendReadSuccess() { func testSendReadSuccess() {
let articleIds = Set((0..<100).map { "feed/0/article/\($0)" }) let articleIDs = Set((0..<100).map { "feed/0/article/\($0)" })
let statuses = articleIds.map { SyncStatus(articleID: $0, key: .read, flag: true) } let statuses = articleIDs.map { SyncStatus(articleID: $0, key: .read, flag: true) }
let insertExpectation = expectation(description: "Inserted Statuses") let insertExpectation = expectation(description: "Inserted Statuses")
container.database.insertStatuses(statuses) { error in container.database.insertStatuses(statuses) { error in
@ -145,8 +145,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
let service = TestMarkArticlesService() let service = TestMarkArticlesService()
service.mockResult = .success(()) service.mockResult = .success(())
service.parameterTester = { serviceArticleIds, action in service.parameterTester = { serviceArticleIDs, action in
XCTAssertEqual(serviceArticleIds, articleIds) XCTAssertEqual(serviceArticleIDs, articleIDs)
XCTAssertEqual(action, .read) XCTAssertEqual(action, .read)
} }
@ -175,8 +175,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
} }
func testSendReadFailure() { func testSendReadFailure() {
let articleIds = Set((0..<100).map { "feed/0/article/\($0)" }) let articleIDs = Set((0..<100).map { "feed/0/article/\($0)" })
let statuses = articleIds.map { SyncStatus(articleID: $0, key: .read, flag: true) } let statuses = articleIDs.map { SyncStatus(articleID: $0, key: .read, flag: true) }
let insertExpectation = expectation(description: "Inserted Statuses") let insertExpectation = expectation(description: "Inserted Statuses")
container.database.insertStatuses(statuses) { error in container.database.insertStatuses(statuses) { error in
@ -188,8 +188,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
let service = TestMarkArticlesService() let service = TestMarkArticlesService()
service.mockResult = .failure(URLError(.timedOut)) service.mockResult = .failure(URLError(.timedOut))
service.parameterTester = { serviceArticleIds, action in service.parameterTester = { serviceArticleIDs, action in
XCTAssertEqual(serviceArticleIds, articleIds) XCTAssertEqual(serviceArticleIDs, articleIDs)
XCTAssertEqual(action, .read) XCTAssertEqual(action, .read)
} }
@ -218,8 +218,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
} }
func testSendStarredSuccess() { func testSendStarredSuccess() {
let articleIds = Set((0..<100).map { "feed/0/article/\($0)" }) let articleIDs = Set((0..<100).map { "feed/0/article/\($0)" })
let statuses = articleIds.map { SyncStatus(articleID: $0, key: .starred, flag: true) } let statuses = articleIDs.map { SyncStatus(articleID: $0, key: .starred, flag: true) }
let insertExpectation = expectation(description: "Inserted Statuses") let insertExpectation = expectation(description: "Inserted Statuses")
container.database.insertStatuses(statuses) { error in container.database.insertStatuses(statuses) { error in
@ -231,8 +231,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
let service = TestMarkArticlesService() let service = TestMarkArticlesService()
service.mockResult = .success(()) service.mockResult = .success(())
service.parameterTester = { serviceArticleIds, action in service.parameterTester = { serviceArticleIDs, action in
XCTAssertEqual(serviceArticleIds, articleIds) XCTAssertEqual(serviceArticleIDs, articleIDs)
XCTAssertEqual(action, .saved) XCTAssertEqual(action, .saved)
} }
@ -261,8 +261,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
} }
func testSendStarredFailure() { func testSendStarredFailure() {
let articleIds = Set((0..<100).map { "feed/0/article/\($0)" }) let articleIDs = Set((0..<100).map { "feed/0/article/\($0)" })
let statuses = articleIds.map { SyncStatus(articleID: $0, key: .starred, flag: true) } let statuses = articleIDs.map { SyncStatus(articleID: $0, key: .starred, flag: true) }
let insertExpectation = expectation(description: "Inserted Statuses") let insertExpectation = expectation(description: "Inserted Statuses")
container.database.insertStatuses(statuses) { error in container.database.insertStatuses(statuses) { error in
@ -274,8 +274,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
let service = TestMarkArticlesService() let service = TestMarkArticlesService()
service.mockResult = .failure(URLError(.timedOut)) service.mockResult = .failure(URLError(.timedOut))
service.parameterTester = { serviceArticleIds, action in service.parameterTester = { serviceArticleIDs, action in
XCTAssertEqual(serviceArticleIds, articleIds) XCTAssertEqual(serviceArticleIDs, articleIDs)
XCTAssertEqual(action, .saved) XCTAssertEqual(action, .saved)
} }
@ -304,8 +304,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
} }
func testSendUnstarredSuccess() { func testSendUnstarredSuccess() {
let articleIds = Set((0..<100).map { "feed/0/article/\($0)" }) let articleIDs = Set((0..<100).map { "feed/0/article/\($0)" })
let statuses = articleIds.map { SyncStatus(articleID: $0, key: .starred, flag: false) } let statuses = articleIDs.map { SyncStatus(articleID: $0, key: .starred, flag: false) }
let insertExpectation = expectation(description: "Inserted Statuses") let insertExpectation = expectation(description: "Inserted Statuses")
container.database.insertStatuses(statuses) { error in container.database.insertStatuses(statuses) { error in
@ -317,8 +317,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
let service = TestMarkArticlesService() let service = TestMarkArticlesService()
service.mockResult = .success(()) service.mockResult = .success(())
service.parameterTester = { serviceArticleIds, action in service.parameterTester = { serviceArticleIDs, action in
XCTAssertEqual(serviceArticleIds, articleIds) XCTAssertEqual(serviceArticleIDs, articleIDs)
XCTAssertEqual(action, .unsaved) XCTAssertEqual(action, .unsaved)
} }
@ -347,8 +347,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
} }
func testSendUnstarredFailure() { func testSendUnstarredFailure() {
let articleIds = Set((0..<100).map { "feed/0/article/\($0)" }) let articleIDs = Set((0..<100).map { "feed/0/article/\($0)" })
let statuses = articleIds.map { SyncStatus(articleID: $0, key: .starred, flag: false) } let statuses = articleIDs.map { SyncStatus(articleID: $0, key: .starred, flag: false) }
let insertExpectation = expectation(description: "Inserted Statuses") let insertExpectation = expectation(description: "Inserted Statuses")
container.database.insertStatuses(statuses) { error in container.database.insertStatuses(statuses) { error in
@ -360,8 +360,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
let service = TestMarkArticlesService() let service = TestMarkArticlesService()
service.mockResult = .failure(URLError(.timedOut)) service.mockResult = .failure(URLError(.timedOut))
service.parameterTester = { serviceArticleIds, action in service.parameterTester = { serviceArticleIDs, action in
XCTAssertEqual(serviceArticleIds, articleIds) XCTAssertEqual(serviceArticleIDs, articleIDs)
XCTAssertEqual(action, .unsaved) XCTAssertEqual(action, .unsaved)
} }
@ -390,13 +390,13 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
} }
func testSendAllSuccess() { func testSendAllSuccess() {
let articleIds = Set((0..<100).map { "feed/0/article/\($0)" }) let articleIDs = Set((0..<100).map { "feed/0/article/\($0)" })
let keys = [SyncStatus.Key.read, .starred] let keys = [SyncStatus.Key.read, .starred]
let flags = [true, false] let flags = [true, false]
let statuses = articleIds.map { articleId -> SyncStatus in let statuses = articleIDs.map { articleID -> SyncStatus in
let key = keys.randomElement()! let key = keys.randomElement()!
let flag = flags.randomElement()! let flag = flags.randomElement()!
let status = SyncStatus(articleID: articleId, key: key, flag: flag) let status = SyncStatus(articleID: articleID, key: key, flag: flag)
return status return status
} }
@ -410,7 +410,7 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
let service = TestMarkArticlesService() let service = TestMarkArticlesService()
service.mockResult = .success(()) service.mockResult = .success(())
service.parameterTester = { serviceArticleIds, action in service.parameterTester = { serviceArticleIDs, action in
let syncStatuses: [SyncStatus] let syncStatuses: [SyncStatus]
switch action { switch action {
case .read: case .read:
@ -422,8 +422,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
case .unsaved: case .unsaved:
syncStatuses = statuses.filter { $0.key == .starred && $0.flag == false } syncStatuses = statuses.filter { $0.key == .starred && $0.flag == false }
} }
let expectedArticleIds = Set(syncStatuses.map { $0.articleID }) let expectedArticleIDs = Set(syncStatuses.map { $0.articleID })
XCTAssertEqual(serviceArticleIds, expectedArticleIds) XCTAssertEqual(serviceArticleIDs, expectedArticleIDs)
} }
let send = FeedlySendArticleStatusesOperation(database: container.database, service: service, log: support.log) let send = FeedlySendArticleStatusesOperation(database: container.database, service: service, log: support.log)
@ -450,13 +450,13 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
} }
func testSendAllFailure() { func testSendAllFailure() {
let articleIds = Set((0..<100).map { "feed/0/article/\($0)" }) let articleIDs = Set((0..<100).map { "feed/0/article/\($0)" })
let keys = [SyncStatus.Key.read, .starred] let keys = [SyncStatus.Key.read, .starred]
let flags = [true, false] let flags = [true, false]
let statuses = articleIds.map { articleId -> SyncStatus in let statuses = articleIDs.map { articleID -> SyncStatus in
let key = keys.randomElement()! let key = keys.randomElement()!
let flag = flags.randomElement()! let flag = flags.randomElement()!
let status = SyncStatus(articleID: articleId, key: key, flag: flag) let status = SyncStatus(articleID: articleID, key: key, flag: flag)
return status return status
} }
@ -470,7 +470,7 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
let service = TestMarkArticlesService() let service = TestMarkArticlesService()
service.mockResult = .failure(URLError(.timedOut)) service.mockResult = .failure(URLError(.timedOut))
service.parameterTester = { serviceArticleIds, action in service.parameterTester = { serviceArticleIDs, action in
let syncStatuses: [SyncStatus] let syncStatuses: [SyncStatus]
switch action { switch action {
case .read: case .read:
@ -482,8 +482,8 @@ class FeedlySendArticleStatusesOperationTests: XCTestCase {
case .unsaved: case .unsaved:
syncStatuses = statuses.filter { $0.key == .starred && $0.flag == false } syncStatuses = statuses.filter { $0.key == .starred && $0.flag == false }
} }
let expectedArticleIds = Set(syncStatuses.map { $0.articleID }) let expectedArticleIDs = Set(syncStatuses.map { $0.articleID })
XCTAssertEqual(serviceArticleIds, expectedArticleIds) XCTAssertEqual(serviceArticleIDs, expectedArticleIDs)
} }
let send = FeedlySendArticleStatusesOperation(database: container.database, service: service, log: support.log) let send = FeedlySendArticleStatusesOperation(database: container.database, service: service, log: support.log)

View File

@ -28,7 +28,7 @@ class FeedlySyncStreamContentsOperationTests: XCTestCase {
func testIngestsOnePageSuccess() throws { func testIngestsOnePageSuccess() throws {
let service = TestGetStreamContentsService() let service = TestGetStreamContentsService()
let resource = FeedlyCategoryResourceId(id: "user/1234/category/5678") let resource = FeedlyCategoryResourceID(id: "user/1234/category/5678")
let newerThan: Date? = Date(timeIntervalSinceReferenceDate: 0) let newerThan: Date? = Date(timeIntervalSinceReferenceDate: 0)
let items = service.makeMockFeedlyEntryItem() let items = service.makeMockFeedlyEntryItem()
service.mockResult = .success(FeedlyStream(id: resource.id, updated: nil, continuation: nil, items: items)) service.mockResult = .success(FeedlyStream(id: resource.id, updated: nil, continuation: nil, items: items))
@ -55,14 +55,14 @@ class FeedlySyncStreamContentsOperationTests: XCTestCase {
waitForExpectations(timeout: 2) waitForExpectations(timeout: 2)
let expectedArticleIds = Set(items.map { $0.id }) let expectedArticleIDs = Set(items.map { $0.id })
let expectedArticles = try account.fetchArticles(.articleIDs(expectedArticleIds)) let expectedArticles = try account.fetchArticles(.articleIDs(expectedArticleIDs))
XCTAssertEqual(expectedArticles.count, expectedArticleIds.count, "Did not fetch all the articles.") XCTAssertEqual(expectedArticles.count, expectedArticleIDs.count, "Did not fetch all the articles.")
} }
func testIngestsOnePageFailure() { func testIngestsOnePageFailure() {
let service = TestGetStreamContentsService() let service = TestGetStreamContentsService()
let resource = FeedlyCategoryResourceId(id: "user/1234/category/5678") let resource = FeedlyCategoryResourceID(id: "user/1234/category/5678")
let newerThan: Date? = Date(timeIntervalSinceReferenceDate: 0) let newerThan: Date? = Date(timeIntervalSinceReferenceDate: 0)
service.mockResult = .failure(URLError(.timedOut)) service.mockResult = .failure(URLError(.timedOut))
@ -92,7 +92,7 @@ class FeedlySyncStreamContentsOperationTests: XCTestCase {
func testIngestsManyPagesSuccess() throws { func testIngestsManyPagesSuccess() throws {
let service = TestGetPagedStreamContentsService() let service = TestGetPagedStreamContentsService()
let resource = FeedlyCategoryResourceId(id: "user/1234/category/5678") let resource = FeedlyCategoryResourceID(id: "user/1234/category/5678")
let newerThan: Date? = Date(timeIntervalSinceReferenceDate: 0) let newerThan: Date? = Date(timeIntervalSinceReferenceDate: 0)
let continuations = (1...10).map { "\($0)" } let continuations = (1...10).map { "\($0)" }
@ -131,8 +131,8 @@ class FeedlySyncStreamContentsOperationTests: XCTestCase {
waitForExpectations(timeout: 30) waitForExpectations(timeout: 30)
// Find articles inserted. // Find articles inserted.
let articleIds = Set(service.pages.values.map { $0.items }.flatMap { $0 }.map { $0.id }) let articleIDs = Set(service.pages.values.map { $0.items }.flatMap { $0 }.map { $0.id })
let articles = try account.fetchArticles(.articleIDs(articleIds)) let articles = try account.fetchArticles(.articleIDs(articleIDs))
XCTAssertEqual(articleIds.count, articles.count) XCTAssertEqual(articleIDs.count, articles.count)
} }
} }

View File

@ -99,18 +99,18 @@ class FeedlyTestSupport {
func checkFoldersAndFeeds(in account: Account, againstCollectionsAndFeedsInJSONNamed name: String, subdirectory: String? = nil) { func checkFoldersAndFeeds(in account: Account, againstCollectionsAndFeedsInJSONNamed name: String, subdirectory: String? = nil) {
let collections = testJSON(named: name, subdirectory: subdirectory) as! [[String:Any]] let collections = testJSON(named: name, subdirectory: subdirectory) as! [[String:Any]]
let collectionNames = Set(collections.map { $0["label"] as! String }) let collectionNames = Set(collections.map { $0["label"] as! String })
let collectionIds = Set(collections.map { $0["id"] as! String }) let collectionIDs = Set(collections.map { $0["id"] as! String })
let folders = account.folders ?? Set() let folders = account.folders ?? Set()
let folderNames = Set(folders.compactMap { $0.name }) let folderNames = Set(folders.compactMap { $0.name })
let folderIds = Set(folders.compactMap { $0.externalID }) let folderIDs = Set(folders.compactMap { $0.externalID })
let missingNames = collectionNames.subtracting(folderNames) let missingNames = collectionNames.subtracting(folderNames)
let missingIds = collectionIds.subtracting(folderIds) let missingIDs = collectionIDs.subtracting(folderIDs)
XCTAssertEqual(folders.count, collections.count, "Mismatch between collections and folders.") XCTAssertEqual(folders.count, collections.count, "Mismatch between collections and folders.")
XCTAssertTrue(missingNames.isEmpty, "Collections with these names did not have a corresponding folder with the same name.") XCTAssertTrue(missingNames.isEmpty, "Collections with these names did not have a corresponding folder with the same name.")
XCTAssertTrue(missingIds.isEmpty, "Collections with these ids did not have a corresponding folder with the same id.") XCTAssertTrue(missingIDs.isEmpty, "Collections with these ids did not have a corresponding folder with the same id.")
for collection in collections { for collection in collections {
checkSingleFolderAndFeeds(in: account, againstOneCollectionAndFeedsInJSONPayload: collection) checkSingleFolderAndFeeds(in: account, againstOneCollectionAndFeedsInJSONPayload: collection)
@ -134,11 +134,11 @@ class FeedlyTestSupport {
XCTAssertEqual(collectionFeeds.count, folderFeeds.count) XCTAssertEqual(collectionFeeds.count, folderFeeds.count)
let collectionFeedIds = Set(collectionFeeds.map { $0["id"] as! String }) let collectionFeedIDs = Set(collectionFeeds.map { $0["id"] as! String })
let folderFeedIds = Set(folderFeeds.map { $0.feedID }) let folderFeedIDs = Set(folderFeeds.map { $0.feedID })
let missingFeedIds = collectionFeedIds.subtracting(folderFeedIds) let missingFeedIDs = collectionFeedIDs.subtracting(folderFeedIDs)
XCTAssertTrue(missingFeedIds.isEmpty, "Feeds with these ids were not found in the \"\(label)\" folder.") XCTAssertTrue(missingFeedIDs.isEmpty, "Feeds with these ids were not found in the \"\(label)\" folder.")
} }
func checkArticles(in account: Account, againstItemsInStreamInJSONNamed name: String, subdirectory: String? = nil) throws { func checkArticles(in account: Account, againstItemsInStreamInJSONNamed name: String, subdirectory: String? = nil) throws {
@ -152,7 +152,7 @@ class FeedlyTestSupport {
private struct ArticleItem { private struct ArticleItem {
var id: String var id: String
var feedId: String var feedID: String
var content: String var content: String
var JSON: [String: Any] var JSON: [String: Any]
var unread: Bool var unread: Bool
@ -177,7 +177,7 @@ class FeedlyTestSupport {
self.id = item["id"] as! String self.id = item["id"] as! String
let origin = item["origin"] as! [String: Any] let origin = item["origin"] as! [String: Any]
self.feedId = origin["streamId"] as! String self.feedID = origin["streamId"] as! String
let content = item["content"] as? [String: Any] let content = item["content"] as? [String: Any]
let summary = item["summary"] as? [String: Any] let summary = item["summary"] as? [String: Any]
@ -192,12 +192,12 @@ class FeedlyTestSupport {
let items = stream["items"] as! [[String: Any]] let items = stream["items"] as! [[String: Any]]
let articleItems = items.map { ArticleItem(item: $0) } let articleItems = items.map { ArticleItem(item: $0) }
let itemIds = Set(articleItems.map { $0.id }) let itemIDs = Set(articleItems.map { $0.id })
let articles = try testAccount.fetchArticles(.articleIDs(itemIds)) let articles = try testAccount.fetchArticles(.articleIDs(itemIDs))
let articleIds = Set(articles.map { $0.articleID }) let articleIDs = Set(articles.map { $0.articleID })
let missing = itemIds.subtracting(articleIds) let missing = itemIDs.subtracting(articleIDs)
XCTAssertEqual(items.count, articles.count) XCTAssertEqual(items.count, articles.count)
XCTAssertTrue(missing.isEmpty, "Items with these ids did not have a corresponding article with the same id.") XCTAssertTrue(missing.isEmpty, "Items with these ids did not have a corresponding article with the same id.")
@ -212,61 +212,61 @@ class FeedlyTestSupport {
} }
} }
func checkUnreadStatuses(in account: Account, againstIdsInStreamInJSONNamed name: String, subdirectory: String? = nil, testCase: XCTestCase) { func checkUnreadStatuses(in account: Account, againstIDsInStreamInJSONNamed name: String, subdirectory: String? = nil, testCase: XCTestCase) {
let streamIds = testJSON(named: name, subdirectory: subdirectory) as! [String:Any] let streadIDs = testJSON(named: name, subdirectory: subdirectory) as! [String:Any]
checkUnreadStatuses(in: account, correspondToIdsInJSONPayload: streamIds, testCase: testCase) checkUnreadStatuses(in: account, correspondToIDsInJSONPayload: streadIDs, testCase: testCase)
} }
func checkUnreadStatuses(in testAccount: Account, correspondToIdsInJSONPayload streamIds: [String: Any], testCase: XCTestCase) { func checkUnreadStatuses(in testAccount: Account, correspondToIDsInJSONPayload streadIDs: [String: Any], testCase: XCTestCase) {
let ids = Set(streamIds["ids"] as! [String]) let ids = Set(streadIDs["ids"] as! [String])
let fetchIdsExpectation = testCase.expectation(description: "Fetch Article Ids") let fetchIDsExpectation = testCase.expectation(description: "Fetch Article IDs")
testAccount.fetchUnreadArticleIDs { articleIdsResult in testAccount.fetchUnreadArticleIDs { articleIDsResult in
do { do {
let articleIds = try articleIdsResult.get() let articleIDs = try articleIDsResult.get()
// Unread statuses can be paged from Feedly. // Unread statuses can be paged from Feedly.
// Instead of joining test data, the best we can do is // Instead of joining test data, the best we can do is
// make sure that these ids are marked as unread (a subset of the total). // make sure that these ids are marked as unread (a subset of the total).
XCTAssertTrue(ids.isSubset(of: articleIds), "Some articles in `ids` are not marked as unread.") XCTAssertTrue(ids.isSubset(of: articleIDs), "Some articles in `ids` are not marked as unread.")
fetchIdsExpectation.fulfill() fetchIDsExpectation.fulfill()
} catch { } catch {
XCTFail("Error unwrapping article IDs: \(error)") XCTFail("Error unwrapping article IDs: \(error)")
} }
} }
testCase.wait(for: [fetchIdsExpectation], timeout: 2) testCase.wait(for: [fetchIDsExpectation], timeout: 2)
} }
func checkStarredStatuses(in account: Account, againstItemsInStreamInJSONNamed name: String, subdirectory: String? = nil, testCase: XCTestCase) { func checkStarredStatuses(in account: Account, againstItemsInStreamInJSONNamed name: String, subdirectory: String? = nil, testCase: XCTestCase) {
let streamIds = testJSON(named: name, subdirectory: subdirectory) as! [String:Any] let streadIDs = testJSON(named: name, subdirectory: subdirectory) as! [String:Any]
checkStarredStatuses(in: account, correspondToStreamItemsIn: streamIds, testCase: testCase) checkStarredStatuses(in: account, correspondToStreamItemsIn: streadIDs, testCase: testCase)
} }
func checkStarredStatuses(in testAccount: Account, correspondToStreamItemsIn stream: [String: Any], testCase: XCTestCase) { func checkStarredStatuses(in testAccount: Account, correspondToStreamItemsIn stream: [String: Any], testCase: XCTestCase) {
let items = stream["items"] as! [[String: Any]] let items = stream["items"] as! [[String: Any]]
let ids = Set(items.map { $0["id"] as! String }) let ids = Set(items.map { $0["id"] as! String })
let fetchIdsExpectation = testCase.expectation(description: "Fetch Article Ids") let fetchIDsExpectation = testCase.expectation(description: "Fetch Article Ids")
testAccount.fetchStarredArticleIDs { articleIdsResult in testAccount.fetchStarredArticleIDs { articleIDsResult in
do { do {
let articleIds = try articleIdsResult.get() let articleIDs = try articleIDsResult.get()
// Starred articles can be paged from Feedly. // Starred articles can be paged from Feedly.
// Instead of joining test data, the best we can do is // Instead of joining test data, the best we can do is
// make sure that these articles are marked as starred (a subset of the total). // make sure that these articles are marked as starred (a subset of the total).
XCTAssertTrue(ids.isSubset(of: articleIds), "Some articles in `ids` are not marked as starred.") XCTAssertTrue(ids.isSubset(of: articleIDs), "Some articles in `ids` are not marked as starred.")
fetchIdsExpectation.fulfill() fetchIDsExpectation.fulfill()
} catch { } catch {
XCTFail("Error unwrapping article IDs: \(error)") XCTFail("Error unwrapping article IDs: \(error)")
} }
} }
testCase.wait(for: [fetchIdsExpectation], timeout: 2) testCase.wait(for: [fetchIDsExpectation], timeout: 2)
} }
func check(_ entries: [FeedlyEntry], correspondToStreamItemsIn stream: [String: Any]) { func check(_ entries: [FeedlyEntry], correspondToStreamItemsIn stream: [String: Any]) {
let items = stream["items"] as! [[String: Any]] let items = stream["items"] as! [[String: Any]]
let itemIds = Set(items.map { $0["id"] as! String }) let itemIDs = Set(items.map { $0["id"] as! String })
let articleIds = Set(entries.map { $0.id }) let articleIDs = Set(entries.map { $0.id })
let missing = itemIds.subtracting(articleIds) let missing = itemIDs.subtracting(articleIDs)
XCTAssertEqual(items.count, entries.count) XCTAssertEqual(items.count, entries.count)
XCTAssertTrue(missing.isEmpty, "Failed to create \(FeedlyEntry.self) values from objects in the JSON with these ids.") XCTAssertTrue(missing.isEmpty, "Failed to create \(FeedlyEntry.self) values from objects in the JSON with these ids.")
@ -274,9 +274,9 @@ class FeedlyTestSupport {
func makeParsedItemTestDataFor(numberOfFeeds: Int, numberOfItemsInFeeds: Int) -> [String: Set<ParsedItem>] { func makeParsedItemTestDataFor(numberOfFeeds: Int, numberOfItemsInFeeds: Int) -> [String: Set<ParsedItem>] {
let ids = (0..<numberOfFeeds).map { "feed/\($0)" } let ids = (0..<numberOfFeeds).map { "feed/\($0)" }
let feedIdsAndItemCounts = ids.map { ($0, numberOfItemsInFeeds) } let feedIDsAndItemCounts = ids.map { ($0, numberOfItemsInFeeds) }
let entries = feedIdsAndItemCounts.map { (feedId, count) -> (String, [Int]) in let entries = feedIDsAndItemCounts.map { (feedId, count) -> (String, [Int]) in
return (feedId, (0..<count).map { $0 }) return (feedId, (0..<count).map { $0 })
}.map { pair -> (String, Set<ParsedItem>) in }.map { pair -> (String, Set<ParsedItem>) in
@ -300,7 +300,7 @@ class FeedlyTestSupport {
attachments: nil) attachments: nil)
} }
return (pair.0, Set(items)) return (pair.0, Set(items))
}.reduce([String: Set<ParsedItem>](minimumCapacity: feedIdsAndItemCounts.count)) { (dict, pair) in }.reduce([String: Set<ParsedItem>](minimumCapacity: feedIDsAndItemCounts.count)) { (dict, pair) in
var mutant = dict var mutant = dict
mutant[pair.0] = pair.1 mutant[pair.0] = pair.1
return mutant return mutant

View File

@ -11,11 +11,11 @@ import XCTest
final class TestGetPagedStreamContentsService: FeedlyGetStreamContentsService { final class TestGetPagedStreamContentsService: FeedlyGetStreamContentsService {
var parameterTester: ((FeedlyResourceId, String?, Date?, Bool?) -> ())? var parameterTester: ((FeedlyResourceID, String?, Date?, Bool?) -> ())?
var getStreamContentsExpectation: XCTestExpectation? var getStreamContentsExpectation: XCTestExpectation?
var pages = [String: FeedlyStream]() var pages = [String: FeedlyStream]()
func addAtLeastOnePage(for resource: FeedlyResourceId, continuations: [String], numberOfEntriesPerPage count: Int) { func addAtLeastOnePage(for resource: FeedlyResourceID, continuations: [String], numberOfEntriesPerPage count: Int) {
pages = [String: FeedlyStream](minimumCapacity: continuations.count + 1) pages = [String: FeedlyStream](minimumCapacity: continuations.count + 1)
// A continuation is an identifier for the next page. // A continuation is an identifier for the next page.
@ -31,7 +31,7 @@ final class TestGetPagedStreamContentsService: FeedlyGetStreamContentsService {
} }
} }
private func makeStreamContents(for resource: FeedlyResourceId, continuation: String?, between range: Range<Int>) -> FeedlyStream { private func makeStreamContents(for resource: FeedlyResourceID, continuation: String?, between range: Range<Int>) -> FeedlyStream {
let entries = range.map { index -> FeedlyEntry in let entries = range.map { index -> FeedlyEntry in
let content = FeedlyEntry.Content(content: "Content \(index)", let content = FeedlyEntry.Content(content: "Content \(index)",
direction: .leftToRight) direction: .leftToRight)
@ -61,11 +61,11 @@ final class TestGetPagedStreamContentsService: FeedlyGetStreamContentsService {
return stream return stream
} }
static func getPagingKey(for stream: FeedlyResourceId, continuation: String?) -> String { static func getPagingKey(for stream: FeedlyResourceID, continuation: String?) -> String {
return "\(stream.id)@\(continuation ?? "")" return "\(stream.id)@\(continuation ?? "")"
} }
func getStreamContents(for resource: FeedlyResourceId, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStream, Error>) -> ()) { func getStreamContents(for resource: FeedlyResourceID, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStream, Error>) -> ()) {
let key = TestGetPagedStreamContentsService.getPagingKey(for: resource, continuation: continuation) let key = TestGetPagedStreamContentsService.getPagingKey(for: resource, continuation: continuation)
guard let page = pages[key] else { guard let page = pages[key] else {
XCTFail("Missing page for \(resource.id) and continuation \(String(describing: continuation)). Test may time out because the completion will not be called.") XCTFail("Missing page for \(resource.id) and continuation \(String(describing: continuation)). Test may time out because the completion will not be called.")

View File

@ -1,5 +1,5 @@
// //
// TestGetPagedStreamIdsService.swift // TestGetPagedStreadIDsService.swift
// AccountTests // AccountTests
// //
// Created by Kiel Gillard on 29/10/19. // Created by Kiel Gillard on 29/10/19.
@ -9,14 +9,14 @@
import XCTest import XCTest
@testable import Account @testable import Account
final class TestGetPagedStreamIdsService: FeedlyGetStreamIdsService { final class TestGetPagedStreadIDsService: FeedlyGetStreamIDsService {
var parameterTester: ((FeedlyResourceId, String?, Date?, Bool?) -> ())? var parameterTester: ((FeedlyResourceID, String?, Date?, Bool?) -> ())?
var getStreamIdsExpectation: XCTestExpectation? var getStreadIDsExpectation: XCTestExpectation?
var pages = [String: FeedlyStreamIds]() var pages = [String: FeedlyStreamIDs]()
func addAtLeastOnePage(for resource: FeedlyResourceId, continuations: [String], numberOfEntriesPerPage count: Int) { func addAtLeastOnePage(for resource: FeedlyResourceID, continuations: [String], numberOfEntriesPerPage count: Int) {
pages = [String: FeedlyStreamIds](minimumCapacity: continuations.count + 1) pages = [String: FeedlyStreamIDs](minimumCapacity: continuations.count + 1)
// A continuation is an identifier for the next page. // A continuation is an identifier for the next page.
// The first page has a nil identifier. // The first page has a nil identifier.
@ -25,24 +25,24 @@ final class TestGetPagedStreamIdsService: FeedlyGetStreamIdsService {
for index in -1..<continuations.count { for index in -1..<continuations.count {
let nextIndex = index + 1 let nextIndex = index + 1
let continuation: String? = nextIndex < continuations.count ? continuations[nextIndex] : nil let continuation: String? = nextIndex < continuations.count ? continuations[nextIndex] : nil
let page = makeStreamIds(for: resource, continuation: continuation, between: 0..<count) let page = makeStreadIDs(for: resource, continuation: continuation, between: 0..<count)
let key = TestGetPagedStreamIdsService.getPagingKey(for: resource, continuation: index < 0 ? nil : continuations[index]) let key = TestGetPagedStreadIDsService.getPagingKey(for: resource, continuation: index < 0 ? nil : continuations[index])
pages[key] = page pages[key] = page
} }
} }
private func makeStreamIds(for resource: FeedlyResourceId, continuation: String?, between range: Range<Int>) -> FeedlyStreamIds { private func makeStreadIDs(for resource: FeedlyResourceID, continuation: String?, between range: Range<Int>) -> FeedlyStreamIDs {
let entryIds = range.map { _ in UUID().uuidString } let entryIDs = range.map { _ in UUID().uuidString }
let stream = FeedlyStreamIds(continuation: continuation, ids: entryIds) let stream = FeedlyStreamIDs(continuation: continuation, ids: entryIDs)
return stream return stream
} }
static func getPagingKey(for stream: FeedlyResourceId, continuation: String?) -> String { static func getPagingKey(for stream: FeedlyResourceID, continuation: String?) -> String {
return "\(stream.id)@\(continuation ?? "")" return "\(stream.id)@\(continuation ?? "")"
} }
func getStreamIds(for resource: FeedlyResourceId, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStreamIds, Error>) -> ()) { func getStreamIDs(for resource: FeedlyResourceID, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStreamIDs, Error>) -> ()) {
let key = TestGetPagedStreamIdsService.getPagingKey(for: resource, continuation: continuation) let key = TestGetPagedStreadIDsService.getPagingKey(for: resource, continuation: continuation)
guard let page = pages[key] else { guard let page = pages[key] else {
XCTFail("Missing page for \(resource.id) and continuation \(String(describing: continuation)). Test may time out because the completion will not be called.") XCTFail("Missing page for \(resource.id) and continuation \(String(describing: continuation)). Test may time out because the completion will not be called.")
return return
@ -50,7 +50,7 @@ final class TestGetPagedStreamIdsService: FeedlyGetStreamIdsService {
parameterTester?(resource, continuation, newerThan, unreadOnly) parameterTester?(resource, continuation, newerThan, unreadOnly)
DispatchQueue.main.async { DispatchQueue.main.async {
completion(.success(page)) completion(.success(page))
self.getStreamIdsExpectation?.fulfill() self.getStreadIDsExpectation?.fulfill()
} }
} }
} }

View File

@ -12,10 +12,10 @@ import XCTest
final class TestGetStreamContentsService: FeedlyGetStreamContentsService { final class TestGetStreamContentsService: FeedlyGetStreamContentsService {
var mockResult: Result<FeedlyStream, Error>? var mockResult: Result<FeedlyStream, Error>?
var parameterTester: ((FeedlyResourceId, String?, Date?, Bool?) -> ())? var parameterTester: ((FeedlyResourceID, String?, Date?, Bool?) -> ())?
var getStreamContentsExpectation: XCTestExpectation? var getStreamContentsExpectation: XCTestExpectation?
func getStreamContents(for resource: FeedlyResourceId, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStream, Error>) -> ()) { func getStreamContents(for resource: FeedlyResourceID, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStream, Error>) -> ()) {
guard let result = mockResult else { guard let result = mockResult else {
XCTFail("Missing mock result. Test may time out because the completion will not be called.") XCTFail("Missing mock result. Test may time out because the completion will not be called.")
return return

View File

@ -1,5 +1,5 @@
// //
// TestGetStreamIdsService.swift // TestGetStreadIDsService.swift
// AccountTests // AccountTests
// //
// Created by Kiel Gillard on 29/10/19. // Created by Kiel Gillard on 29/10/19.
@ -9,13 +9,13 @@
import XCTest import XCTest
@testable import Account @testable import Account
final class TestGetStreamIdsService: FeedlyGetStreamIdsService { final class TestGetStreadIDsService: FeedlyGetStreamIDsService {
var mockResult: Result<FeedlyStreamIds, Error>? var mockResult: Result<FeedlyStreamIDs, Error>?
var parameterTester: ((FeedlyResourceId, String?, Date?, Bool?) -> ())? var parameterTester: ((FeedlyResourceID, String?, Date?, Bool?) -> ())?
var getStreamIdsExpectation: XCTestExpectation? var getStreadIDsExpectation: XCTestExpectation?
func getStreamIds(for resource: FeedlyResourceId, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStreamIds, Error>) -> ()) { func getStreamIDs(for resource: FeedlyResourceID, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStreamIDs, Error>) -> ()) {
guard let result = mockResult else { guard let result = mockResult else {
XCTFail("Missing mock result. Test may time out because the completion will not be called.") XCTFail("Missing mock result. Test may time out because the completion will not be called.")
return return
@ -23,7 +23,7 @@ final class TestGetStreamIdsService: FeedlyGetStreamIdsService {
parameterTester?(resource, continuation, newerThan, unreadOnly) parameterTester?(resource, continuation, newerThan, unreadOnly)
DispatchQueue.main.async { DispatchQueue.main.async {
completion(result) completion(result)
self.getStreamIdsExpectation?.fulfill() self.getStreadIDsExpectation?.fulfill()
} }
} }
} }

View File

@ -15,10 +15,10 @@ class TestMarkArticlesService: FeedlyMarkArticlesService {
var parameterTester: ((Set<String>, FeedlyMarkAction) -> ())? var parameterTester: ((Set<String>, FeedlyMarkAction) -> ())?
var mockResult: Result<Void, Error> = .success(()) var mockResult: Result<Void, Error> = .success(())
func mark(_ articleIds: Set<String>, as action: FeedlyMarkAction, completion: @escaping (Result<Void, Error>) -> ()) { func mark(_ articleIDs: Set<String>, as action: FeedlyMarkAction, completion: @escaping (Result<Void, Error>) -> ()) {
DispatchQueue.main.async { DispatchQueue.main.async {
self.parameterTester?(articleIds, action) self.parameterTester?(articleIDs, action)
completion(self.mockResult) completion(self.mockResult)
self.didMarkExpectation?.fulfill() self.didMarkExpectation?.fulfill()
} }

View File

@ -108,7 +108,7 @@ class AccountsNewsBlurWindowController: NSWindowController {
do { do {
try self.account?.removeCredentials(type: .newsBlurBasic) try self.account?.removeCredentials(type: .newsBlurBasic)
try self.account?.removeCredentials(type: .newsBlurSessionId) try self.account?.removeCredentials(type: .newsBlurSessionID)
try self.account?.storeCredentials(credentials) try self.account?.storeCredentials(credentials)
try self.account?.storeCredentials(validatedCredentials) try self.account?.storeCredentials(validatedCredentials)

View File

@ -122,10 +122,10 @@ import Core
@objc(valueInFoldersWithUniqueID:) @objc(valueInFoldersWithUniqueID:)
func valueInFolders(withUniqueID id:NSNumber) -> ScriptableFolder? { func valueInFolders(withUniqueID id:NSNumber) -> ScriptableFolder? {
let folderId = id.intValue let folderID = id.intValue
let foldersSet = account.folders ?? Set<Folder>() let foldersSet = account.folders ?? Set<Folder>()
let folders = Array(foldersSet) let folders = Array(foldersSet)
guard let folder = folders.first(where:{$0.folderID == folderId}) else { return nil } guard let folder = folders.first(where:{$0.folderID == folderID}) else { return nil }
return ScriptableFolder(folder, container:self) return ScriptableFolder(folder, container:self)
} }

View File

@ -30,9 +30,9 @@ extension ScriptingObjectContainer {
let containerClassDescription = self.scriptingClassDescription let containerClassDescription = self.scriptingClassDescription
let containerScriptObjectSpecifier = self.objectSpecifier let containerScriptObjectSpecifier = self.objectSpecifier
let scriptingKey = object.scriptingKey let scriptingKey = object.scriptingKey
let uniqueId = object.scriptingUniqueId let uniqueID = object.scriptingUniqueId
let specifier = NSUniqueIDSpecifier(containerClassDescription:containerClassDescription, let specifier = NSUniqueIDSpecifier(containerClassDescription:containerClassDescription,
containerSpecifier:containerScriptObjectSpecifier, key:scriptingKey, uniqueID: uniqueId) containerSpecifier:containerScriptObjectSpecifier, key:scriptingKey, uniqueID: uniqueID)
return specifier return specifier
} }
} }

View File

@ -56,7 +56,7 @@ import Secrets
let cookies = HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url) let cookies = HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url)
for cookie in cookies where cookie.name == Self.sessionIDCookieKey { for cookie in cookies where cookie.name == Self.sessionIDCookieKey {
let credentials = Credentials(type: .newsBlurSessionId, username: username, secret: cookie.value) let credentials = Credentials(type: .newsBlurSessionID, username: username, secret: cookie.value)
completion(.success(credentials)) completion(.success(credentials))
return return
} }

View File

@ -20,7 +20,7 @@ public extension URLRequest {
} }
let credentialsType = credentials.type let credentialsType = credentials.type
precondition(credentialsType == .newsBlurBasic || credentialsType == .newsBlurSessionId) precondition(credentialsType == .newsBlurBasic || credentialsType == .newsBlurSessionID)
if credentialsType == .newsBlurBasic { if credentialsType == .newsBlurBasic {
@ -34,7 +34,7 @@ public extension URLRequest {
] ]
httpBody = postData.enhancedPercentEncodedQuery?.data(using: .utf8) httpBody = postData.enhancedPercentEncodedQuery?.data(using: .utf8)
} else if credentialsType == .newsBlurSessionId { } else if credentialsType == .newsBlurSessionID {
setValue("\(NewsBlurAPICaller.sessionIDCookieKey)=\(credentials.secret)", forHTTPHeaderField: "Cookie") setValue("\(NewsBlurAPICaller.sessionIDCookieKey)=\(credentials.secret)", forHTTPHeaderField: "Cookie")
httpShouldHandleCookies = true httpShouldHandleCookies = true

View File

@ -64,7 +64,7 @@ public enum CreateReaderAPISubscriptionResult: Sendable {
case subscriptionEdit = "/reader/api/0/subscription/edit" case subscriptionEdit = "/reader/api/0/subscription/edit"
case subscriptionAdd = "/reader/api/0/subscription/quickadd" case subscriptionAdd = "/reader/api/0/subscription/quickadd"
case contents = "/reader/api/0/stream/items/contents" case contents = "/reader/api/0/stream/items/contents"
case itemIds = "/reader/api/0/stream/items/ids" case itemIDs = "/reader/api/0/stream/items/ids"
case editTag = "/reader/api/0/edit-tag" case editTag = "/reader/api/0/edit-tag"
} }
@ -452,7 +452,7 @@ public enum CreateReaderAPISubscriptionResult: Sendable {
} }
let url = baseURL let url = baseURL
.appendingPathComponent(ReaderAPIEndpoints.itemIds.rawValue) .appendingPathComponent(ReaderAPIEndpoints.itemIDs.rawValue)
.appendingQueryItems(queryItems) .appendingQueryItems(queryItems)
guard let callURL = url else { guard let callURL = url else {

View File

@ -16,7 +16,7 @@ public enum CredentialsError: Error, Sendable {
public enum CredentialsType: String, Sendable { public enum CredentialsType: String, Sendable {
case basic = "password" case basic = "password"
case newsBlurBasic = "newsBlurBasic" case newsBlurBasic = "newsBlurBasic"
case newsBlurSessionId = "newsBlurSessionId" case newsBlurSessionID = "newsBlurSessionId"
case readerBasic = "readerBasic" case readerBasic = "readerBasic"
case readerAPIKey = "readerAPIKey" case readerAPIKey = "readerAPIKey"
case oauthAccessToken = "oauthAccessToken" case oauthAccessToken = "oauthAccessToken"

View File

@ -159,17 +159,17 @@ import CoreSpotlight
#endif #endif
@objc func feedIconDidBecomeAvailable(_ note: Notification) { @objc func feedIconDidBecomeAvailable(_ note: Notification) {
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed, let activityFeedId = selectingActivity?.userInfo?[ArticlePathKey.feedID] as? String else { guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed, let activityFeedID = selectingActivity?.userInfo?[ArticlePathKey.feedID] as? String else {
return return
} }
#if os(iOS) #if os(iOS)
if let article = readingArticle, activityFeedId == article.feedID { if let article = readingArticle, activityFeedID == article.feedID {
updateReadArticleSearchAttributes(with: article) updateReadArticleSearchAttributes(with: article)
} }
#endif #endif
if activityFeedId == feed.feedID { if activityFeedID == feed.feedID {
updateSelectingActivityFeedSearchAttributes(with: feed) updateSelectingActivityFeedSearchAttributes(with: feed)
} }
} }

View File

@ -39,11 +39,11 @@ class SharingTests: XCTestCase {
} }
private func article(titled title: String) -> Article { private func article(titled title: String) -> Article {
let articleId = randomId() let articleID = randomID()
return Article(accountID: randomId(), return Article(accountID: randomID(),
articleID: articleId, articleID: articleID,
feedID: randomId(), feedID: randomID(),
uniqueID: randomId(), uniqueID: randomID(),
title: title, title: title,
contentHTML: nil, contentHTML: nil,
contentText: nil, contentText: nil,
@ -54,11 +54,11 @@ class SharingTests: XCTestCase {
datePublished: nil, datePublished: nil,
dateModified: nil, dateModified: nil,
authors: nil, authors: nil,
status: ArticleStatus(articleID: articleId, read: true, dateArrived: Date()) status: ArticleStatus(articleID: articleID, read: true, dateArrived: Date())
) )
} }
private func randomId() -> String { private func randomID() -> String {
return UUID().uuidString return UUID().uuidString
} }

View File

@ -137,7 +137,7 @@ class NewsBlurAccountViewController: UITableViewController {
do { do {
try self.account?.removeCredentials(type: .newsBlurBasic) try self.account?.removeCredentials(type: .newsBlurBasic)
try self.account?.removeCredentials(type: .newsBlurSessionId) try self.account?.removeCredentials(type: .newsBlurSessionID)
try self.account?.storeCredentials(credentials) try self.account?.storeCredentials(credentials)
try self.account?.storeCredentials(validatedCredentials) try self.account?.storeCredentials(validatedCredentials)

View File

@ -89,7 +89,7 @@ struct SidebarItemNode: Hashable {
private var lastSearchScope: SearchScope? = nil private var lastSearchScope: SearchScope? = nil
private var isSearching: Bool = false private var isSearching: Bool = false
private var savedSearchArticles: ArticleArray? = nil private var savedSearchArticles: ArticleArray? = nil
private var savedSearchArticleIds: Set<String>? = nil private var savedSearchArticleIDs: Set<String>? = nil
private(set) var sortDirection = AppDefaults.shared.timelineSortDirection { private(set) var sortDirection = AppDefaults.shared.timelineSortDirection {
didSet { didSet {
@ -869,7 +869,7 @@ struct SidebarItemNode: Hashable {
isSearching = true isSearching = true
preSearchTimelineFeed = timelineFeed preSearchTimelineFeed = timelineFeed
savedSearchArticles = articles savedSearchArticles = articles
savedSearchArticleIds = Set(articles.map { $0.articleID }) savedSearchArticleIDs = Set(articles.map { $0.articleID })
setTimelineFeed(nil, animated: true) setTimelineFeed(nil, animated: true)
selectArticle(nil) selectArticle(nil)
} }
@ -887,7 +887,7 @@ struct SidebarItemNode: Hashable {
lastSearchString = "" lastSearchString = ""
lastSearchScope = nil lastSearchScope = nil
preSearchTimelineFeed = nil preSearchTimelineFeed = nil
savedSearchArticleIds = nil savedSearchArticleIDs = nil
savedSearchArticles = nil savedSearchArticles = nil
isSearching = false isSearching = false
selectArticle(nil) selectArticle(nil)
@ -909,7 +909,7 @@ struct SidebarItemNode: Hashable {
case .global: case .global:
setTimelineFeed(SmartFeed(delegate: SearchFeedDelegate(searchString: searchString)), animated: true) setTimelineFeed(SmartFeed(delegate: SearchFeedDelegate(searchString: searchString)), animated: true)
case .timeline: case .timeline:
setTimelineFeed(SmartFeed(delegate: SearchTimelineFeedDelegate(searchString: searchString, articleIDs: savedSearchArticleIds!)), animated: true) setTimelineFeed(SmartFeed(delegate: SearchTimelineFeedDelegate(searchString: searchString, articleIDs: savedSearchArticleIDs!)), animated: true)
} }
lastSearchString = searchString lastSearchString = searchString

View File

@ -34,9 +34,9 @@ extension UIStoryboard {
func instantiateController<T>(ofType type: T.Type = T.self) -> T where T: UIViewController { func instantiateController<T>(ofType type: T.Type = T.self) -> T where T: UIViewController {
let storyboardId = String(describing: type) let storyboardID = String(describing: type)
guard let viewController = instantiateViewController(withIdentifier: storyboardId) as? T else { guard let viewController = instantiateViewController(withIdentifier: storyboardID) as? T else {
print("Unable to load view with Scene Identifier: \(storyboardId)") print("Unable to load view with Scene Identifier: \(storyboardID)")
fatalError() fatalError()
} }