Id -> ID renaming.
This commit is contained in:
parent
c62e3293a6
commit
53215c1f80
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(()))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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?
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -38,9 +38,9 @@ final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, Fe
|
||||||
|
|
||||||
// TODO: Fix the below. There’s an error on the os.log line: "Expression type '()' is ambiguous without more context"
|
// TODO: Fix the below. There’s 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)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
|
@ -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 {
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>) -> ())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>) -> ())
|
||||||
}
|
}
|
|
@ -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>) -> ())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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?")
|
||||||
|
|
|
@ -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).")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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])
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.")
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue