diff --git a/Account/Package.swift b/Account/Package.swift index 4fe166dda..f0f75d72e 100644 --- a/Account/Package.swift +++ b/Account/Package.swift @@ -26,6 +26,7 @@ let package = Package( .package(path: "../Feedbin"), .package(path: "../LocalAccount"), .package(path: "../FeedFinder"), + .package(path: "../Feedly"), .package(path: "../CommonErrors") ], targets: [ @@ -47,7 +48,8 @@ let package = Package( "Feedbin", "LocalAccount", "FeedFinder", - "CommonErrors" + "CommonErrors", + "Feedly" ], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency") diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift b/Account/Sources/Account/AccountDelegates/CloudKitAccountDelegate.swift similarity index 100% rename from Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift rename to Account/Sources/Account/AccountDelegates/CloudKitAccountDelegate.swift diff --git a/Account/Sources/Account/Feedly/FeedlyAccountDelegate+OAuth.swift b/Account/Sources/Account/AccountDelegates/FeedlyAccountDelegate+OAuth.swift similarity index 100% rename from Account/Sources/Account/Feedly/FeedlyAccountDelegate+OAuth.swift rename to Account/Sources/Account/AccountDelegates/FeedlyAccountDelegate+OAuth.swift diff --git a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift b/Account/Sources/Account/AccountDelegates/FeedlyAccountDelegate.swift similarity index 99% rename from Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift rename to Account/Sources/Account/AccountDelegates/FeedlyAccountDelegate.swift index eae672971..6a74a749b 100644 --- a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegates/FeedlyAccountDelegate.swift @@ -14,6 +14,7 @@ import os.log import Secrets import Core import CommonErrors +import Feedly final class FeedlyAccountDelegate: AccountDelegate { @@ -761,7 +762,7 @@ final class FeedlyAccountDelegate: AccountDelegate { } @MainActor func accountWillBeDeleted(_ account: Account) { - let logout = FeedlyLogoutOperation(account: account, service: caller, log: log) + let logout = FeedlyLogoutOperation(service: caller, log: log) // Dispatch on the shared queue because the lifetime of the account delegate is uncertain. MainThreadOperationQueue.shared.add(logout) } diff --git a/Account/Sources/Account/Feedly/FeedlyAPICaller.swift b/Account/Sources/Account/Feedly/FeedlyAPICaller.swift index 630712627..e9b5cb2bf 100644 --- a/Account/Sources/Account/Feedly/FeedlyAPICaller.swift +++ b/Account/Sources/Account/Feedly/FeedlyAPICaller.swift @@ -9,6 +9,7 @@ import Foundation import Web import Secrets +import Feedly protocol FeedlyAPICallerDelegate: AnyObject { /// Implemented by the `FeedlyAccountDelegate` reauthorize the client with a fresh OAuth token so the client can retry the unauthorized request. @@ -390,7 +391,7 @@ final class FeedlyAPICaller { extension FeedlyAPICaller: FeedlyAddFeedToCollectionService { - func addFeed(with feedId: FeedlyFeedResourceID, title: String? = nil, toCollectionWith collectionID: String, completion: @escaping (Result<[FeedlyFeed], Error>) -> ()) { + @MainActor func addFeed(with feedId: FeedlyFeedResourceID, title: String? = nil, toCollectionWith collectionID: String, completion: @escaping (Result<[FeedlyFeed], Error>) -> ()) { guard !isSuspended else { return DispatchQueue.main.async { completion(.failure(TransportError.suspended)) diff --git a/Account/Sources/Account/Feedly/FeedlyFeedContainerValidator.swift b/Account/Sources/Account/Feedly/FeedlyFeedContainerValidator.swift index e61f99569..7a3c708bb 100644 --- a/Account/Sources/Account/Feedly/FeedlyFeedContainerValidator.swift +++ b/Account/Sources/Account/Feedly/FeedlyFeedContainerValidator.swift @@ -7,6 +7,7 @@ // import Foundation +import Feedly @MainActor struct FeedlyFeedContainerValidator { var container: Container diff --git a/Account/Sources/Account/Feedly/Models/FeedlyEntryIdentifierProviding.swift b/Account/Sources/Account/Feedly/Models/FeedlyEntryIdentifierProviding.swift deleted file mode 100644 index 874b1a7be..000000000 --- a/Account/Sources/Account/Feedly/Models/FeedlyEntryIdentifierProviding.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// FeedlyEntryIdentifierProviding.swift -// Account -// -// Created by Kiel Gillard on 9/1/20. -// Copyright © 2020 Ranchero Software, LLC. All rights reserved. -// - -import Foundation - -protocol FeedlyEntryIdentifierProviding: AnyObject { - @MainActor var entryIDs: Set { get } -} - -final class FeedlyEntryIdentifierProvider: FeedlyEntryIdentifierProviding { - private (set) var entryIDs: Set - - init(entryIDs: Set = Set()) { - self.entryIDs = entryIDs - } - - @MainActor func addEntryIDs(from provider: FeedlyEntryIdentifierProviding) { - entryIDs.formUnion(provider.entryIDs) - } - - @MainActor func addEntryIDs(in articleIDs: [String]) { - entryIDs.formUnion(articleIDs) - } -} diff --git a/Account/Sources/Account/Feedly/OAuthAccountAuthorizationOperation.swift b/Account/Sources/Account/Feedly/OAuthAccountAuthorizationOperation.swift index 05903db32..1155999b8 100644 --- a/Account/Sources/Account/Feedly/OAuthAccountAuthorizationOperation.swift +++ b/Account/Sources/Account/Feedly/OAuthAccountAuthorizationOperation.swift @@ -10,6 +10,7 @@ import Foundation import AuthenticationServices import Secrets import Core +import Feedly public protocol OAuthAccountAuthorizationOperationDelegate: AnyObject { func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) diff --git a/Account/Sources/Account/Feedly/OAuthAcessTokenRefreshing.swift b/Account/Sources/Account/Feedly/OAuthAcessTokenRefreshing.swift index ca497a1fa..0179a63f0 100644 --- a/Account/Sources/Account/Feedly/OAuthAcessTokenRefreshing.swift +++ b/Account/Sources/Account/Feedly/OAuthAcessTokenRefreshing.swift @@ -8,6 +8,7 @@ import Foundation import Web +import Feedly /// Models section 6 of the OAuth 2.0 Authorization Framework /// https://tools.ietf.org/html/rfc6749#section-6 diff --git a/Account/Sources/Account/Feedly/OAuthAuthorizationClient+Feedly.swift b/Account/Sources/Account/Feedly/OAuthAuthorizationClient+Feedly.swift index a0371870e..a94903e7c 100644 --- a/Account/Sources/Account/Feedly/OAuthAuthorizationClient+Feedly.swift +++ b/Account/Sources/Account/Feedly/OAuthAuthorizationClient+Feedly.swift @@ -8,6 +8,7 @@ import Foundation import Secrets +import Feedly extension OAuthAuthorizationClient { diff --git a/Account/Sources/Account/Feedly/OAuthAuthorizationCodeGranting.swift b/Account/Sources/Account/Feedly/OAuthAuthorizationCodeGranting.swift index e77a9815a..36eaaebe2 100644 --- a/Account/Sources/Account/Feedly/OAuthAuthorizationCodeGranting.swift +++ b/Account/Sources/Account/Feedly/OAuthAuthorizationCodeGranting.swift @@ -9,6 +9,7 @@ import Foundation import Web import Secrets +import Feedly /// Client-specific information for requesting an authorization code grant. /// Accounts are responsible for the scope. diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyAddExistingFeedOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyAddExistingFeedOperation.swift index e82e872b4..820d35398 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyAddExistingFeedOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyAddExistingFeedOperation.swift @@ -11,6 +11,7 @@ import os.log import Web import Secrets import Core +import Feedly @MainActor final class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyCheckpointOperationDelegate { diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyAddFeedToCollectionOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyAddFeedToCollectionOperation.swift index ecd5c2250..38f20324f 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyAddFeedToCollectionOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyAddFeedToCollectionOperation.swift @@ -8,6 +8,7 @@ import Foundation import CommonErrors +import Feedly protocol FeedlyAddFeedToCollectionService { func addFeed(with feedId: FeedlyFeedResourceID, title: String?, toCollectionWith collectionID: String, completion: @escaping (Result<[FeedlyFeed], Error>) -> ()) diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift index 51a5f86de..3cb8cbaf1 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyAddNewFeedOperation.swift @@ -13,8 +13,9 @@ import Web import Secrets import Core import CommonErrors +import Feedly -class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlySearchOperationDelegate, FeedlyCheckpointOperationDelegate { +final class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlySearchOperationDelegate, FeedlyCheckpointOperationDelegate { private let operationQueue = MainThreadOperationQueue() private let folder: Folder diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyCheckpointOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyCheckpointOperation.swift index e46378009..b3614a2d9 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyCheckpointOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyCheckpointOperation.swift @@ -7,6 +7,7 @@ // import Foundation +import Feedly protocol FeedlyCheckpointOperationDelegate: AnyObject { @MainActor func feedlyCheckpointOperationDidReachCheckpoint(_ operation: FeedlyCheckpointOperation) diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift index 08b74c91a..529e2ebb9 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyCreateFeedsForCollectionFoldersOperation.swift @@ -9,6 +9,7 @@ import Foundation import os.log import Core +import Feedly /// Single responsibility is to accurately reflect Collections and their Feeds as Folders and their Feeds. final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation { @@ -23,7 +24,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation { self.log = log } - override func run() { + public override func run() { defer { didFinish() } diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyDownloadArticlesOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyDownloadArticlesOperation.swift index 86f79ad73..3e1cbe742 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyDownloadArticlesOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyDownloadArticlesOperation.swift @@ -10,6 +10,7 @@ import Foundation import os.log import Web import Core +import Feedly class FeedlyDownloadArticlesOperation: FeedlyOperation { @@ -45,12 +46,11 @@ class FeedlyDownloadArticlesOperation: FeedlyOperation { Task { @MainActor in let provider = FeedlyEntryIdentifierProvider(entryIDs: Set(articleIDs)) - let getEntries = FeedlyGetEntriesOperation(service: getEntriesService, provider: provider, log: log) + let getEntries = FeedlyGetEntriesOperation(service: self.getEntriesService, provider: provider, log: self.log) getEntries.delegate = self self.operationQueue.add(getEntries) - let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(account: account, - parsedItemProvider: getEntries, + let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(parsedItemProvider: getEntries, log: log) organiseByFeed.delegate = self organiseByFeed.addDependency(getEntries) diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyFetchIDsForMissingArticlesOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyFetchIDsForMissingArticlesOperation.swift index ffa509a6d..d7f86da24 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyFetchIDsForMissingArticlesOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyFetchIDsForMissingArticlesOperation.swift @@ -8,6 +8,7 @@ import Foundation import os.log +import Feedly final class FeedlyFetchIDsForMissingArticlesOperation: FeedlyOperation, FeedlyEntryIdentifierProviding { diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyIngestStarredArticleIDsOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyIngestStarredArticleIDsOperation.swift index 314a4c236..481869b1c 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyIngestStarredArticleIDsOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyIngestStarredArticleIDsOperation.swift @@ -10,6 +10,7 @@ import Foundation import os.log import SyncDatabase import Secrets +import Feedly /// Clone locally the remote starred article state. /// diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyIngestStreamArticleIDsOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyIngestStreamArticleIDsOperation.swift index 08de1b20a..860abf149 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyIngestStreamArticleIDsOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyIngestStreamArticleIDsOperation.swift @@ -10,6 +10,7 @@ import Foundation import os.log import Secrets import Database +import Feedly /// Ensure a status exists for every article id the user might be interested in. /// diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyIngestUnreadArticleIDsOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyIngestUnreadArticleIDsOperation.swift index 6d9d2493d..10e0969cf 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyIngestUnreadArticleIDsOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyIngestUnreadArticleIDsOperation.swift @@ -11,6 +11,7 @@ import os.log import Parser import SyncDatabase import Secrets +import Feedly /// Clone locally the remote unread article state. /// @@ -27,12 +28,12 @@ final class FeedlyIngestUnreadArticleIDsOperation: FeedlyOperation { private var remoteEntryIDs = Set() private let log: OSLog - convenience init(account: Account, userID: String, service: FeedlyGetStreamIDsService, database: SyncDatabase, newerThan: Date?, log: OSLog) { + public convenience init(account: Account, userID: String, service: FeedlyGetStreamIDsService, database: SyncDatabase, newerThan: Date?, log: OSLog) { let resource = FeedlyCategoryResourceID.Global.all(for: userID) 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) { + public init(account: Account, resource: FeedlyResourceID, service: FeedlyGetStreamIDsService, database: SyncDatabase, newerThan: Date?, log: OSLog) { self.account = account self.resource = resource self.service = service diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyMirrorCollectionsAsFoldersOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyMirrorCollectionsAsFoldersOperation.swift index adbb4a5a9..af769f60d 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyMirrorCollectionsAsFoldersOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyMirrorCollectionsAsFoldersOperation.swift @@ -8,6 +8,7 @@ import Foundation import os.log +import Feedly protocol FeedlyFeedsAndFoldersProviding { @MainActor var feedsAndFolders: [([FeedlyFeed], Folder)] { get } diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyRefreshAccessTokenOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyRefreshAccessTokenOperation.swift index c518f6e55..7486099c2 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyRefreshAccessTokenOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyRefreshAccessTokenOperation.swift @@ -10,6 +10,7 @@ import Foundation import os.log import Web import Secrets +import Feedly final class FeedlyRefreshAccessTokenOperation: FeedlyOperation { diff --git a/Account/Sources/Account/Feedly/Operations/FeedlySyncAllOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlySyncAllOperation.swift index 8b73c2339..85d6647c0 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlySyncAllOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlySyncAllOperation.swift @@ -12,6 +12,7 @@ import SyncDatabase import Web import Secrets import Core +import Feedly /// Compose the operations necessary to get the entire set of articles, feeds and folders with the statuses the user expects between now and a certain date in the past. final class FeedlySyncAllOperation: FeedlyOperation { @@ -83,7 +84,7 @@ final class FeedlySyncAllOperation: FeedlyOperation { // 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). - let getUpdated = FeedlyGetUpdatedArticleIDsOperation(account: account, userID: feedlyUserID, service: getStreamIDsService, newerThan: lastSuccessfulFetchStartDate, log: log) + let getUpdated = FeedlyGetUpdatedArticleIDsOperation(userID: feedlyUserID, service: getStreamIDsService, newerThan: lastSuccessfulFetchStartDate, log: log) getUpdated.delegate = self getUpdated.downloadProgress = downloadProgress getUpdated.addDependency(createFeedsOperation) diff --git a/Account/Sources/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift index 93cf7f651..aa67892b7 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlySyncStreamContentsOperation.swift @@ -12,6 +12,7 @@ import Parser import Web import Secrets import Core +import Feedly final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyGetStreamContentsOperationDelegate, FeedlyCheckpointOperationDelegate { @@ -63,15 +64,14 @@ final class FeedlySyncStreamContentsOperation: FeedlyOperation, FeedlyOperationD } func pageOperations(for continuation: String?) -> [MainThreadOperation] { - let getPage = FeedlyGetStreamContentsOperation(account: account, - resource: resource, + let getPage = FeedlyGetStreamContentsOperation(resource: resource, service: service, continuation: continuation, newerThan: newerThan, log: log) - let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(account: account, parsedItemProvider: getPage, log: log) + let organiseByFeed = FeedlyOrganiseParsedItemsByFeedOperation(parsedItemProvider: getPage, log: log) let updateAccount = FeedlyUpdateAccountFeedsWithItemsOperation(account: account, organisedItemsProvider: organiseByFeed, log: log) diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyUpdateAccountFeedsWithItemsOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyUpdateAccountFeedsWithItemsOperation.swift index a76f1d2e4..623aa4f7a 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyUpdateAccountFeedsWithItemsOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyUpdateAccountFeedsWithItemsOperation.swift @@ -10,6 +10,7 @@ import Foundation import Parser import os.log import Database +import Feedly /// Combine the articles with their feeds for a specific account. final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation { diff --git a/CloudKitSync/Sources/CloudKitSync/CKRecord+Extensions.swift b/CloudKitSync/Sources/CloudKitSync/CKRecord+Extensions.swift index 6fadcda7b..54b9c254b 100644 --- a/CloudKitSync/Sources/CloudKitSync/CKRecord+Extensions.swift +++ b/CloudKitSync/Sources/CloudKitSync/CKRecord+Extensions.swift @@ -11,14 +11,14 @@ import CloudKit public extension CKRecord { - public var externalID: String { + var externalID: String { return recordID.externalID } } public extension CKRecord.ID { - public var externalID: String { + var externalID: String { return recordName } } diff --git a/Feedly/.gitignore b/Feedly/.gitignore new file mode 100644 index 000000000..0023a5340 --- /dev/null +++ b/Feedly/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Feedly/Package.swift b/Feedly/Package.swift new file mode 100644 index 000000000..dbe60617a --- /dev/null +++ b/Feedly/Package.swift @@ -0,0 +1,40 @@ +// swift-tools-version: 5.10 + +import PackageDescription + +let package = Package( + name: "Feedly", + platforms: [.macOS(.v14), .iOS(.v17)], + products: [ + .library( + name: "Feedly", + targets: ["Feedly"]), + ], + dependencies: [ + .package(path: "../Parser"), + .package(path: "../Articles"), + .package(path: "../Secrets"), + .package(path: "../Core"), + .package(path: "../SyncDatabase"), + .package(path: "../Web"), + ], + targets: [ + .target( + name: "Feedly", + dependencies: [ + "Parser", + "Articles", + "Secrets", + "Core", + "SyncDatabase", + "Web" + ], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency") + ] + ), + .testTarget( + name: "FeedlyTests", + dependencies: ["Feedly"]), + ] +) diff --git a/Account/Sources/Account/Feedly/FeedlyAccountDelegateError.swift b/Feedly/Sources/Feedly/FeedlyAccountDelegateError.swift similarity index 96% rename from Account/Sources/Account/Feedly/FeedlyAccountDelegateError.swift rename to Feedly/Sources/Feedly/FeedlyAccountDelegateError.swift index 1c76dbe53..fb1f75147 100644 --- a/Account/Sources/Account/Feedly/FeedlyAccountDelegateError.swift +++ b/Feedly/Sources/Feedly/FeedlyAccountDelegateError.swift @@ -8,7 +8,8 @@ import Foundation -enum FeedlyAccountDelegateError: LocalizedError { +public enum FeedlyAccountDelegateError: LocalizedError, Sendable { + case notLoggedIn case unexpectedResourceID(String) case unableToAddFolder(String) @@ -20,7 +21,7 @@ enum FeedlyAccountDelegateError: LocalizedError { case unableToRenameFeed(String, String) case unableToRemoveFeed(String) - var errorDescription: String? { + public var errorDescription: String? { switch self { 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.") @@ -62,7 +63,7 @@ enum FeedlyAccountDelegateError: LocalizedError { } } - var recoverySuggestion: String? { + public var recoverySuggestion: String? { switch self { case .notLoggedIn: return nil diff --git a/Account/Sources/Account/Feedly/FeedlyResourceProviding.swift b/Feedly/Sources/Feedly/FeedlyResourceProviding.swift similarity index 79% rename from Account/Sources/Account/Feedly/FeedlyResourceProviding.swift rename to Feedly/Sources/Feedly/FeedlyResourceProviding.swift index f3d08a47f..5b6c526d7 100644 --- a/Account/Sources/Account/Feedly/FeedlyResourceProviding.swift +++ b/Feedly/Sources/Feedly/FeedlyResourceProviding.swift @@ -8,13 +8,13 @@ import Foundation -protocol FeedlyResourceProviding { +public protocol FeedlyResourceProviding { @MainActor var resource: FeedlyResourceID { get } } extension FeedlyFeedResourceID: FeedlyResourceProviding { - var resource: FeedlyResourceID { + public var resource: FeedlyResourceID { return self } } diff --git a/Account/Sources/Account/Feedly/Models/FeedlyCategory.swift b/Feedly/Sources/Feedly/Models/FeedlyCategory.swift similarity index 62% rename from Account/Sources/Account/Feedly/Models/FeedlyCategory.swift rename to Feedly/Sources/Feedly/Models/FeedlyCategory.swift index 534788692..ea7068e1b 100644 --- a/Account/Sources/Account/Feedly/Models/FeedlyCategory.swift +++ b/Feedly/Sources/Feedly/Models/FeedlyCategory.swift @@ -8,7 +8,8 @@ import Foundation -struct FeedlyCategory: Decodable { - let label: String - let id: String +public struct FeedlyCategory: Decodable, Sendable { + + public let label: String + public let id: String } diff --git a/Account/Sources/Account/Feedly/Models/FeedlyCollection.swift b/Feedly/Sources/Feedly/Models/FeedlyCollection.swift similarity index 56% rename from Account/Sources/Account/Feedly/Models/FeedlyCollection.swift rename to Feedly/Sources/Feedly/Models/FeedlyCollection.swift index 80322ea51..145a5f936 100644 --- a/Account/Sources/Account/Feedly/Models/FeedlyCollection.swift +++ b/Feedly/Sources/Feedly/Models/FeedlyCollection.swift @@ -8,8 +8,9 @@ import Foundation -struct FeedlyCollection: Codable { - let feeds: [FeedlyFeed] - let label: String - let id: String +public struct FeedlyCollection: Codable, Sendable { + + public let feeds: [FeedlyFeed] + public let label: String + public let id: String } diff --git a/Account/Sources/Account/Feedly/Models/FeedlyCollectionParser.swift b/Feedly/Sources/Feedly/Models/FeedlyCollectionParser.swift similarity index 59% rename from Account/Sources/Account/Feedly/Models/FeedlyCollectionParser.swift rename to Feedly/Sources/Feedly/Models/FeedlyCollectionParser.swift index 229498592..693de807a 100644 --- a/Account/Sources/Account/Feedly/Models/FeedlyCollectionParser.swift +++ b/Feedly/Sources/Feedly/Models/FeedlyCollectionParser.swift @@ -8,16 +8,21 @@ import Foundation -struct FeedlyCollectionParser { - let collection: FeedlyCollection +public struct FeedlyCollectionParser: Sendable { + + public let collection: FeedlyCollection private let rightToLeftTextSantizer = FeedlyRTLTextSanitizer() - var folderName: String { + public var folderName: String { return rightToLeftTextSantizer.sanitize(collection.label) ?? "" } - var externalID: String { + public var externalID: String { return collection.id } + + public init(collection: FeedlyCollection) { + self.collection = collection + } } diff --git a/Account/Sources/Account/Feedly/Models/FeedlyEntry.swift b/Feedly/Sources/Feedly/Models/FeedlyEntry.swift similarity index 78% rename from Account/Sources/Account/Feedly/Models/FeedlyEntry.swift rename to Feedly/Sources/Feedly/Models/FeedlyEntry.swift index cb38fd2e6..385655ee5 100644 --- a/Account/Sources/Account/Feedly/Models/FeedlyEntry.swift +++ b/Feedly/Sources/Feedly/Models/FeedlyEntry.swift @@ -8,58 +8,59 @@ import Foundation -struct FeedlyEntry: Decodable { +public struct FeedlyEntry: Decodable, Sendable { + /// the unique, immutable ID for this particular article. - let id: String - + public let id: String + /// the article’s title. This string does not contain any HTML markup. - let title: String? - - struct Content: Decodable { - - enum Direction: String, Decodable { + public let title: String? + + public struct Content: Decodable, Sendable { + + public enum Direction: String, Decodable, Sendable { case leftToRight = "ltr" case rightToLeft = "rtl" } - let content: String? - let direction: Direction? + public let content: String? + public let direction: Direction? } /// This object typically has two values: “content” for the content itself, and “direction” (“ltr” for left-to-right, “rtl” for right-to-left). The content itself contains sanitized HTML markup. - let content: Content? - + public let content: Content? + /// content object the article summary. See the content object above. - let summary: Content? - + public let summary: Content? + /// the author’s name - let author: String? - + public let author: String? + /// the immutable timestamp, in ms, when this article was processed by the feedly Cloud servers. - let crawled: Date + public let crawled: Date /// the timestamp, in ms, when this article was re-processed and updated by the feedly Cloud servers. - let recrawled: Date? + public let recrawled: Date? /// the feed from which this article was crawled. If present, “streamId” will contain the feed id, “title” will contain the feed title, and “htmlUrl” will contain the feed’s website. - let origin: FeedlyOrigin? - + public let origin: FeedlyOrigin? + /// Used to help find the URL to visit an article on a web site. /// See https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ - let canonical: [FeedlyLink]? + public let canonical: [FeedlyLink]? /// a list of alternate links for this article. Each link object contains a media type and a URL. Typically, a single object is present, with a link to the original web page. - let alternate: [FeedlyLink]? + public let alternate: [FeedlyLink]? /// Was this entry read by the user? If an Authorization header is not provided, this will always return false. If an Authorization header is provided, it will reflect if the user has read this entry or not. - let unread: Bool + public let unread: Bool /// a list of tag objects (“id” and “label”) that the user added to this entry. This value is only returned if an Authorization header is provided, and at least one tag has been added. If the entry has been explicitly marked as read (not the feed itself), the “global.read” tag will be present. - let tags: [FeedlyTag]? + public let tags: [FeedlyTag]? /// a list of category objects (“id” and “label”) that the user associated with the feed of this entry. This value is only returned if an Authorization header is provided. - let categories: [FeedlyCategory]? + public let categories: [FeedlyCategory]? /// A list of media links (videos, images, sound etc) provided by the feed. Some entries do not have a summary or content, only a collection of media links. - let enclosure: [FeedlyLink]? + public let enclosure: [FeedlyLink]? } diff --git a/Feedly/Sources/Feedly/Models/FeedlyEntryIdentifierProviding.swift b/Feedly/Sources/Feedly/Models/FeedlyEntryIdentifierProviding.swift new file mode 100644 index 000000000..06a87a2d0 --- /dev/null +++ b/Feedly/Sources/Feedly/Models/FeedlyEntryIdentifierProviding.swift @@ -0,0 +1,30 @@ +// +// FeedlyEntryIdentifierProviding.swift +// Account +// +// Created by Kiel Gillard on 9/1/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +public protocol FeedlyEntryIdentifierProviding: AnyObject { + @MainActor var entryIDs: Set { get } +} + +public final class FeedlyEntryIdentifierProvider: FeedlyEntryIdentifierProviding { + + private (set) public var entryIDs: Set + + public init(entryIDs: Set = Set()) { + self.entryIDs = entryIDs + } + + @MainActor public func addEntryIDs(from provider: FeedlyEntryIdentifierProviding) { + entryIDs.formUnion(provider.entryIDs) + } + + @MainActor public func addEntryIDs(in articleIDs: [String]) { + entryIDs.formUnion(articleIDs) + } +} diff --git a/Account/Sources/Account/Feedly/Models/FeedlyEntryParser.swift b/Feedly/Sources/Feedly/Models/FeedlyEntryParser.swift similarity index 83% rename from Account/Sources/Account/Feedly/Models/FeedlyEntryParser.swift rename to Feedly/Sources/Feedly/Models/FeedlyEntryParser.swift index c9c7e7c60..f7621b9e3 100644 --- a/Account/Sources/Account/Feedly/Models/FeedlyEntryParser.swift +++ b/Feedly/Sources/Feedly/Models/FeedlyEntryParser.swift @@ -10,17 +10,18 @@ import Foundation import Articles import Parser -struct FeedlyEntryParser { - let entry: FeedlyEntry - +public struct FeedlyEntryParser: Sendable { + + public let entry: FeedlyEntry + private let rightToLeftTextSantizer = FeedlyRTLTextSanitizer() - var id: String { + public var id: String { return entry.id } /// 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? { + public var feedUrl: String? { guard let id = entry.origin?.streamID else { // 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.") @@ -31,7 +32,7 @@ struct FeedlyEntryParser { /// Convoluted external URL logic "documented" here: /// https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ - var externalUrl: String? { + public var externalUrl: String? { let multidimensionalArrayOfLinks = [entry.canonical, entry.alternate] let withExistingValues = multidimensionalArrayOfLinks.compactMap { $0 } let flattened = withExistingValues.flatMap { $0 } @@ -39,32 +40,32 @@ struct FeedlyEntryParser { return webPageLinks.first?.href } - var title: String? { + public var title: String? { return rightToLeftTextSantizer.sanitize(entry.title) } - var contentHMTL: String? { + public var contentHMTL: String? { return entry.content?.content ?? entry.summary?.content } - var contentText: String? { + public var contentText: String? { // We could strip HTML from contentHTML? return nil } - var summary: String? { + public var summary: String? { return rightToLeftTextSantizer.sanitize(entry.summary?.content) } - var datePublished: Date { + public var datePublished: Date { return entry.crawled } - var dateModified: Date? { + public var dateModified: Date? { return entry.recrawled } - var authors: Set? { + public var authors: Set? { guard let name = entry.author else { return nil } @@ -72,14 +73,14 @@ struct FeedlyEntryParser { } /// While there is not yet a tagging interface, articles can still be searched for by tags. - var tags: Set? { + public var tags: Set? { guard let labels = entry.tags?.compactMap({ $0.label }), !labels.isEmpty else { return nil } return Set(labels) } - var attachments: Set? { + public var attachments: Set? { guard let enclosure = entry.enclosure, !enclosure.isEmpty else { return nil } @@ -87,7 +88,7 @@ struct FeedlyEntryParser { return attachments.isEmpty ? nil : Set(attachments) } - var parsedItemRepresentation: ParsedItem? { + public var parsedItemRepresentation: ParsedItem? { guard let feedUrl = feedUrl else { return nil } diff --git a/Account/Sources/Account/Feedly/Models/FeedlyFeed.swift b/Feedly/Sources/Feedly/Models/FeedlyFeed.swift similarity index 52% rename from Account/Sources/Account/Feedly/Models/FeedlyFeed.swift rename to Feedly/Sources/Feedly/Models/FeedlyFeed.swift index eab1eb90a..97fbda4d0 100644 --- a/Account/Sources/Account/Feedly/Models/FeedlyFeed.swift +++ b/Feedly/Sources/Feedly/Models/FeedlyFeed.swift @@ -8,9 +8,10 @@ import Foundation -struct FeedlyFeed: Codable { - let id: String - let title: String? - let updated: Date? - let website: String? +public struct FeedlyFeed: Codable, Sendable { + + public let id: String + public let title: String? + public let updated: Date? + public let website: String? } diff --git a/Account/Sources/Account/Feedly/Models/FeedlyFeedParser.swift b/Feedly/Sources/Feedly/Models/FeedlyFeedParser.swift similarity index 63% rename from Account/Sources/Account/Feedly/Models/FeedlyFeedParser.swift rename to Feedly/Sources/Feedly/Models/FeedlyFeedParser.swift index 45ec69244..9f10640aa 100644 --- a/Account/Sources/Account/Feedly/Models/FeedlyFeedParser.swift +++ b/Feedly/Sources/Feedly/Models/FeedlyFeedParser.swift @@ -8,25 +8,31 @@ import Foundation -struct FeedlyFeedParser { - let feed: FeedlyFeed +public struct FeedlyFeedParser: Sendable { + + public let feed: FeedlyFeed private let rightToLeftTextSantizer = FeedlyRTLTextSanitizer() - var title: String? { + public var title: String? { return rightToLeftTextSantizer.sanitize(feed.title) ?? "" } - var feedID: String { + public var feedID: String { return feed.id } - var url: String { + public var url: String { let resource = FeedlyFeedResourceID(id: feed.id) return resource.url } - var homePageURL: String? { + public var homePageURL: String? { return feed.website } + + public init(feed: FeedlyFeed) { + + self.feed = feed + } } diff --git a/Account/Sources/Account/Feedly/Models/FeedlyFeedsSearchResponse.swift b/Feedly/Sources/Feedly/Models/FeedlyFeedsSearchResponse.swift similarity index 50% rename from Account/Sources/Account/Feedly/Models/FeedlyFeedsSearchResponse.swift rename to Feedly/Sources/Feedly/Models/FeedlyFeedsSearchResponse.swift index 8ddbd563b..baa9f916c 100644 --- a/Account/Sources/Account/Feedly/Models/FeedlyFeedsSearchResponse.swift +++ b/Feedly/Sources/Feedly/Models/FeedlyFeedsSearchResponse.swift @@ -8,12 +8,13 @@ import Foundation -struct FeedlyFeedsSearchResponse: Decodable { - - struct Feed: Decodable { - let title: String - let feedId: String +public struct FeedlyFeedsSearchResponse: Decodable, Sendable { + + public struct Feed: Decodable, Sendable { + + public let title: String + public let feedId: String } - let results: [Feed] + public let results: [Feed] } diff --git a/Account/Sources/Account/Feedly/Models/FeedlyLink.swift b/Feedly/Sources/Feedly/Models/FeedlyLink.swift similarity index 79% rename from Account/Sources/Account/Feedly/Models/FeedlyLink.swift rename to Feedly/Sources/Feedly/Models/FeedlyLink.swift index 879341a08..3924869da 100644 --- a/Account/Sources/Account/Feedly/Models/FeedlyLink.swift +++ b/Feedly/Sources/Feedly/Models/FeedlyLink.swift @@ -8,11 +8,12 @@ import Foundation -struct FeedlyLink: Decodable { - let href: String - +public struct FeedlyLink: Decodable, Sendable { + + public let href: String + /// The mime type of the resource located by `href`. /// When `nil`, it's probably a web page? /// https://groups.google.com/forum/#!searchin/feedly-cloud/feed$20url%7Csort:date/feedly-cloud/Rx3dVd4aTFQ/Hf1ZfLJoCQAJ - let type: String? + public let type: String? } diff --git a/Account/Sources/Account/Feedly/Models/FeedlyOrigin.swift b/Feedly/Sources/Feedly/Models/FeedlyOrigin.swift similarity index 55% rename from Account/Sources/Account/Feedly/Models/FeedlyOrigin.swift rename to Feedly/Sources/Feedly/Models/FeedlyOrigin.swift index 7e4845aac..1cc8bc587 100644 --- a/Account/Sources/Account/Feedly/Models/FeedlyOrigin.swift +++ b/Feedly/Sources/Feedly/Models/FeedlyOrigin.swift @@ -8,8 +8,9 @@ import Foundation -struct FeedlyOrigin: Decodable { - let title: String? - let streamID: String? - let htmlUrl: String? +public struct FeedlyOrigin: Decodable, Sendable { + + public let title: String? + public let streamID: String? + public let htmlUrl: String? } diff --git a/Account/Sources/Account/Feedly/Models/FeedlyRTLTextSanitizer.swift b/Feedly/Sources/Feedly/Models/FeedlyRTLTextSanitizer.swift similarity index 87% rename from Account/Sources/Account/Feedly/Models/FeedlyRTLTextSanitizer.swift rename to Feedly/Sources/Feedly/Models/FeedlyRTLTextSanitizer.swift index 11eb2dcb5..87a44d012 100644 --- a/Account/Sources/Account/Feedly/Models/FeedlyRTLTextSanitizer.swift +++ b/Feedly/Sources/Feedly/Models/FeedlyRTLTextSanitizer.swift @@ -8,11 +8,12 @@ import Foundation -struct FeedlyRTLTextSanitizer { +public struct FeedlyRTLTextSanitizer: Sendable { + private let rightToLeftPrefix = "
" private let rightToLeftSuffix = "
" - func sanitize(_ sourceText: String?) -> String? { + public func sanitize(_ sourceText: String?) -> String? { guard let source = sourceText, !source.isEmpty else { return sourceText } diff --git a/Account/Sources/Account/Feedly/Models/FeedlyResourceID.swift b/Feedly/Sources/Feedly/Models/FeedlyResourceID.swift similarity index 73% rename from Account/Sources/Account/Feedly/Models/FeedlyResourceID.swift rename to Feedly/Sources/Feedly/Models/FeedlyResourceID.swift index 9d3964885..ecf9a82dd 100644 --- a/Account/Sources/Account/Feedly/Models/FeedlyResourceID.swift +++ b/Feedly/Sources/Feedly/Models/FeedlyResourceID.swift @@ -9,16 +9,17 @@ import Foundation /// The kinds of Resource IDs is documented here: https://developer.feedly.com/cloud/ -protocol FeedlyResourceID { +public protocol FeedlyResourceID { /// The resource ID from Feedly. @MainActor var id: String { get } } /// The Feed Resource is documented here: https://developer.feedly.com/cloud/ -struct FeedlyFeedResourceID: FeedlyResourceID { - let id: String - +public struct FeedlyFeedResourceID: FeedlyResourceID, Sendable { + + public let id: String + /// 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 /// since the ID is a legitimate URL. @@ -26,7 +27,7 @@ struct FeedlyFeedResourceID: FeedlyResourceID { /// It is not documented as such and could potentially change. /// Feedly does not include the source feed URL as a separate field. /// See https://developer.feedly.com/v3/feeds/#get-the-metadata-about-a-specific-feed - var url: String { + public var url: String { if let range = id.range(of: "feed/"), range.lowerBound == id.startIndex { var mutant = id mutant.removeSubrange(range) @@ -36,34 +37,40 @@ struct FeedlyFeedResourceID: FeedlyResourceID { // It seems values like "something/https://my.blog/posts.xml" is a legit URL. return id } + + public init(id: String) { + self.id = id + } } extension FeedlyFeedResourceID { + init(url: String) { self.id = "feed/\(url)" } } -struct FeedlyCategoryResourceID: FeedlyResourceID { - let id: String - - enum Global { - - static func uncategorized(for userID: String) -> FeedlyCategoryResourceID { +public struct FeedlyCategoryResourceID: FeedlyResourceID, Sendable { + + public let id: String + + public enum Global { + + public static func uncategorized(for userID: String) -> FeedlyCategoryResourceID { // https://developer.feedly.com/cloud/#global-resource-ids let id = "user/\(userID)/category/global.uncategorized" return FeedlyCategoryResourceID(id: id) } /// All articles from all the feeds the user subscribes to. - static func all(for userID: String) -> FeedlyCategoryResourceID { + public static func all(for userID: String) -> FeedlyCategoryResourceID { // https://developer.feedly.com/cloud/#global-resource-ids let id = "user/\(userID)/category/global.all" return FeedlyCategoryResourceID(id: id) } /// All articles from all the feeds the user loves most. - static func mustRead(for userID: String) -> FeedlyCategoryResourceID { + public static func mustRead(for userID: String) -> FeedlyCategoryResourceID { // https://developer.feedly.com/cloud/#global-resource-ids let id = "user/\(userID)/category/global.must" return FeedlyCategoryResourceID(id: id) @@ -71,12 +78,13 @@ struct FeedlyCategoryResourceID: FeedlyResourceID { } } -struct FeedlyTagResourceID: FeedlyResourceID { - let id: String - - enum Global { - - static func saved(for userID: String) -> FeedlyTagResourceID { +public struct FeedlyTagResourceID: FeedlyResourceID, Sendable { + + public let id: String + + public enum Global { + + public static func saved(for userID: String) -> FeedlyTagResourceID { // https://developer.feedly.com/cloud/#global-resource-ids let id = "user/\(userID)/tag/global.saved" return FeedlyTagResourceID(id: id) diff --git a/Account/Sources/Account/Feedly/Models/FeedlyStream.swift b/Feedly/Sources/Feedly/Models/FeedlyStream.swift similarity index 74% rename from Account/Sources/Account/Feedly/Models/FeedlyStream.swift rename to Feedly/Sources/Feedly/Models/FeedlyStream.swift index fc3e92f38..d447c5959 100644 --- a/Account/Sources/Account/Feedly/Models/FeedlyStream.swift +++ b/Feedly/Sources/Feedly/Models/FeedlyStream.swift @@ -8,19 +8,20 @@ import Foundation -struct FeedlyStream: Decodable { - let id: String - +public struct FeedlyStream: Decodable, Sendable { + + public let id: String + /// Of the most recent entry for this stream (regardless of continuation, newerThan, etc). - let updated: Date? - + public let updated: Date? + /// the continuation id to pass to the next stream call, for pagination. /// This id guarantees that no entry will be duplicated in a stream (meaning, there is no need to de-duplicate entries returned by this call). /// If this value is not returned, it means the end of the stream has been reached. - let continuation: String? - let items: [FeedlyEntry] - - var isStreamEnd: Bool { + public let continuation: String? + public let items: [FeedlyEntry] + + public var isStreamEnd: Bool { return continuation == nil } } diff --git a/Account/Sources/Account/Feedly/Models/FeedlyStreamIDs.swift b/Feedly/Sources/Feedly/Models/FeedlyStreamIDs.swift similarity index 58% rename from Account/Sources/Account/Feedly/Models/FeedlyStreamIDs.swift rename to Feedly/Sources/Feedly/Models/FeedlyStreamIDs.swift index e014fd507..7f28b2805 100644 --- a/Account/Sources/Account/Feedly/Models/FeedlyStreamIDs.swift +++ b/Feedly/Sources/Feedly/Models/FeedlyStreamIDs.swift @@ -8,11 +8,12 @@ import Foundation -struct FeedlyStreamIDs: Decodable { - let continuation: String? - let ids: [String] - - var isStreamEnd: Bool { +public struct FeedlyStreamIDs: Decodable, Sendable { + + public let continuation: String? + public let ids: [String] + + public var isStreamEnd: Bool { return continuation == nil } } diff --git a/Account/Sources/Account/Feedly/Models/FeedlyTag.swift b/Feedly/Sources/Feedly/Models/FeedlyTag.swift similarity index 63% rename from Account/Sources/Account/Feedly/Models/FeedlyTag.swift rename to Feedly/Sources/Feedly/Models/FeedlyTag.swift index 0111b95e2..3b6c16064 100644 --- a/Account/Sources/Account/Feedly/Models/FeedlyTag.swift +++ b/Feedly/Sources/Feedly/Models/FeedlyTag.swift @@ -8,7 +8,8 @@ import Foundation -struct FeedlyTag: Decodable { - let id: String - let label: String? +public struct FeedlyTag: Decodable, Sendable { + + public let id: String + public let label: String? } diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyGetCollectionsOperation.swift b/Feedly/Sources/Feedly/Operations/FeedlyGetCollectionsOperation.swift similarity index 74% rename from Account/Sources/Account/Feedly/Operations/FeedlyGetCollectionsOperation.swift rename to Feedly/Sources/Feedly/Operations/FeedlyGetCollectionsOperation.swift index 69df07264..3ad8b7964 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyGetCollectionsOperation.swift +++ b/Feedly/Sources/Feedly/Operations/FeedlyGetCollectionsOperation.swift @@ -9,25 +9,25 @@ import Foundation import os.log -protocol FeedlyCollectionProviding: AnyObject { - +public protocol FeedlyCollectionProviding: AnyObject { + @MainActor var collections: [FeedlyCollection] { get } } /// Get Collections from Feedly. -final class FeedlyGetCollectionsOperation: FeedlyOperation, FeedlyCollectionProviding { +public final class FeedlyGetCollectionsOperation: FeedlyOperation, FeedlyCollectionProviding { let service: FeedlyGetCollectionsService let log: OSLog - private(set) var collections = [FeedlyCollection]() + private(set) public var collections = [FeedlyCollection]() - init(service: FeedlyGetCollectionsService, log: OSLog) { + public init(service: FeedlyGetCollectionsService, log: OSLog) { self.service = service self.log = log } - override func run() { + public override func run() { os_log(.debug, log: log, "Requesting collections.") service.getCollections { result in diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyGetEntriesOperation.swift b/Feedly/Sources/Feedly/Operations/FeedlyGetEntriesOperation.swift similarity index 79% rename from Account/Sources/Account/Feedly/Operations/FeedlyGetEntriesOperation.swift rename to Feedly/Sources/Feedly/Operations/FeedlyGetEntriesOperation.swift index 094641a4e..62548c699 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyGetEntriesOperation.swift +++ b/Feedly/Sources/Feedly/Operations/FeedlyGetEntriesOperation.swift @@ -11,23 +11,23 @@ import os.log import Parser /// Get full entries for the entry identifiers. -final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding { +public final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding { let service: FeedlyGetEntriesService let provider: FeedlyEntryIdentifierProviding let log: OSLog - init(service: FeedlyGetEntriesService, provider: FeedlyEntryIdentifierProviding, log: OSLog) { + public init(service: FeedlyGetEntriesService, provider: FeedlyEntryIdentifierProviding, log: OSLog) { self.service = service self.provider = provider self.log = log } - private (set) var entries = [FeedlyEntry]() - + private (set) public var entries = [FeedlyEntry]() + private var storedParsedEntries: Set? - var parsedEntries: Set { + public var parsedEntries: Set { if let entries = storedParsedEntries { return entries } @@ -49,11 +49,11 @@ final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, Fe return parsed } - var parsedItemProviderName: String { + public var parsedItemProviderName: String { return name ?? String(describing: Self.self) } - override func run() { + public override func run() { service.getEntries(for: provider.entryIDs) { result in switch result { case .success(let entries): diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyGetStreamContentsOperation.swift b/Feedly/Sources/Feedly/Operations/FeedlyGetStreamContentsOperation.swift similarity index 71% rename from Account/Sources/Account/Feedly/Operations/FeedlyGetStreamContentsOperation.swift rename to Feedly/Sources/Feedly/Operations/FeedlyGetStreamContentsOperation.swift index 8a2b9b8ed..86f82731b 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyGetStreamContentsOperation.swift +++ b/Feedly/Sources/Feedly/Operations/FeedlyGetStreamContentsOperation.swift @@ -10,33 +10,33 @@ import Foundation import Parser import os.log -protocol FeedlyEntryProviding { +public protocol FeedlyEntryProviding { @MainActor var entries: [FeedlyEntry] { get } } -protocol FeedlyParsedItemProviding { +public protocol FeedlyParsedItemProviding { @MainActor var parsedItemProviderName: String { get } @MainActor var parsedEntries: Set { get } } -protocol FeedlyGetStreamContentsOperationDelegate: AnyObject { +public protocol FeedlyGetStreamContentsOperationDelegate: AnyObject { func feedlyGetStreamContentsOperation(_ operation: FeedlyGetStreamContentsOperation, didGetContentsOf stream: FeedlyStream) } /// Get the stream content of a Collection from Feedly. -final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding { - +public final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding { + @MainActor struct ResourceProvider: FeedlyResourceProviding { var resource: FeedlyResourceID } let resourceProvider: FeedlyResourceProviding - var parsedItemProviderName: String { + public var parsedItemProviderName: String { return resourceProvider.resource.id } - var entries: [FeedlyEntry] { + public var entries: [FeedlyEntry] { guard let entries = stream?.items else { // assert(isFinished, "This should only be called when the operation finishes without error.") assertionFailure("Has this operation been addeded as a dependency on the caller?") @@ -45,7 +45,7 @@ final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProvid return entries } - var parsedEntries: Set { + public var parsedEntries: Set { if let entries = storedParsedEntries { return entries } @@ -74,17 +74,16 @@ final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProvid private var storedParsedEntries: Set? - let account: Account let service: FeedlyGetStreamContentsService let unreadOnly: Bool? let newerThan: Date? let continuation: String? let log: OSLog - weak var streamDelegate: FeedlyGetStreamContentsOperationDelegate? + public weak var streamDelegate: FeedlyGetStreamContentsOperationDelegate? + + public init(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.resourceProvider = ResourceProvider(resource: resource) self.service = service self.continuation = continuation @@ -93,11 +92,12 @@ final class FeedlyGetStreamContentsOperation: FeedlyOperation, FeedlyEntryProvid self.log = log } - convenience init(account: Account, resourceProvider: FeedlyResourceProviding, service: FeedlyGetStreamContentsService, newerThan: Date?, unreadOnly: Bool? = nil, log: OSLog) { - self.init(account: account, resource: resourceProvider.resource, service: service, newerThan: newerThan, unreadOnly: unreadOnly, log: log) + convenience init(resourceProvider: FeedlyResourceProviding, service: FeedlyGetStreamContentsService, newerThan: Date?, unreadOnly: Bool? = nil, log: OSLog) { + + self.init(resource: resourceProvider.resource, service: service, newerThan: newerThan, unreadOnly: unreadOnly, log: log) } - override func run() { + public override func run() { service.getStreamContents(for: resourceProvider.resource, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly) { result in switch result { case .success(let stream): diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyGetStreamIDsOperation.swift b/Feedly/Sources/Feedly/Operations/FeedlyGetStreamIDsOperation.swift similarity index 77% rename from Account/Sources/Account/Feedly/Operations/FeedlyGetStreamIDsOperation.swift rename to Feedly/Sources/Feedly/Operations/FeedlyGetStreamIDsOperation.swift index d15032e64..de8ea44a8 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyGetStreamIDsOperation.swift +++ b/Feedly/Sources/Feedly/Operations/FeedlyGetStreamIDsOperation.swift @@ -9,14 +9,14 @@ import Foundation import os.log -protocol FeedlyGetStreamIDsOperationDelegate: AnyObject { +public protocol FeedlyGetStreamIDsOperationDelegate: AnyObject { func feedlyGetStreamIDsOperation(_ operation: FeedlyGetStreamIDsOperation, didGet streamIDs: FeedlyStreamIDs) } /// Single responsibility is to get the stream ids from Feedly. -final class FeedlyGetStreamIDsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding { +public final class FeedlyGetStreamIDsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding { - var entryIDs: Set { + public var entryIDs: Set { guard let ids = streamIDs?.ids else { assertionFailure("Has this operation been addeded as a dependency on the caller?") return [] @@ -26,7 +26,6 @@ final class FeedlyGetStreamIDsOperation: FeedlyOperation, FeedlyEntryIdentifierP private(set) var streamIDs: FeedlyStreamIDs? - let account: Account let service: FeedlyGetStreamIDsService let continuation: String? let resource: FeedlyResourceID @@ -34,8 +33,8 @@ final class FeedlyGetStreamIDsOperation: FeedlyOperation, FeedlyEntryIdentifierP let newerThan: Date? let log: OSLog - init(account: Account, resource: FeedlyResourceID, service: FeedlyGetStreamIDsService, continuation: String? = nil, newerThan: Date? = nil, unreadOnly: Bool?, log: OSLog) { - self.account = account + init(resource: FeedlyResourceID, service: FeedlyGetStreamIDsService, continuation: String? = nil, newerThan: Date? = nil, unreadOnly: Bool?, log: OSLog) { + self.resource = resource self.service = service self.continuation = continuation @@ -46,7 +45,7 @@ final class FeedlyGetStreamIDsOperation: FeedlyOperation, FeedlyEntryIdentifierP weak var streamIDsDelegate: FeedlyGetStreamIDsOperationDelegate? - override func run() { + public override func run() { service.getStreamIDs(for: resource, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly) { result in switch result { case .success(let stream): diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyGetUpdatedArticleIDsOperation.swift b/Feedly/Sources/Feedly/Operations/FeedlyGetUpdatedArticleIDsOperation.swift similarity index 77% rename from Account/Sources/Account/Feedly/Operations/FeedlyGetUpdatedArticleIDsOperation.swift rename to Feedly/Sources/Feedly/Operations/FeedlyGetUpdatedArticleIDsOperation.swift index 9708420fd..383230b64 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyGetUpdatedArticleIDsOperation.swift +++ b/Feedly/Sources/Feedly/Operations/FeedlyGetUpdatedArticleIDsOperation.swift @@ -14,34 +14,33 @@ import Secrets /// /// 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. -class FeedlyGetUpdatedArticleIDsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding { +public final class FeedlyGetUpdatedArticleIDsOperation: FeedlyOperation, FeedlyEntryIdentifierProviding { - private let account: Account private let resource: FeedlyResourceID private let service: FeedlyGetStreamIDsService private let newerThan: Date? private let log: OSLog - init(account: Account, resource: FeedlyResourceID, service: FeedlyGetStreamIDsService, newerThan: Date?, log: OSLog) { - self.account = account + public init(resource: FeedlyResourceID, service: FeedlyGetStreamIDsService, newerThan: Date?, log: OSLog) { + self.resource = resource self.service = service self.newerThan = newerThan self.log = log } - convenience init(account: Account, userID: String, service: FeedlyGetStreamIDsService, newerThan: Date?, log: OSLog) { + public convenience init(userID: String, service: FeedlyGetStreamIDsService, newerThan: Date?, log: OSLog) { let all = FeedlyCategoryResourceID.Global.all(for: userID) - self.init(account: account, resource: all, service: service, newerThan: newerThan, log: log) + self.init(resource: all, service: service, newerThan: newerThan, log: log) } - var entryIDs: Set { + public var entryIDs: Set { return storedUpdatedArticleIDs } private var storedUpdatedArticleIDs = Set() - override func run() { + public override func run() { getStreamIDs(nil) } diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyLogoutOperation.swift b/Feedly/Sources/Feedly/Operations/FeedlyLogoutOperation.swift similarity index 60% rename from Account/Sources/Account/Feedly/Operations/FeedlyLogoutOperation.swift rename to Feedly/Sources/Feedly/Operations/FeedlyLogoutOperation.swift index 29fb7eed6..413d7d7d6 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyLogoutOperation.swift +++ b/Feedly/Sources/Feedly/Operations/FeedlyLogoutOperation.swift @@ -9,24 +9,22 @@ import Foundation import os.log -protocol FeedlyLogoutService { +public protocol FeedlyLogoutService { func logout(completion: @escaping (Result) -> ()) } -final class FeedlyLogoutOperation: FeedlyOperation { +public final class FeedlyLogoutOperation: FeedlyOperation { let service: FeedlyLogoutService - let account: Account let log: OSLog - init(account: Account, service: FeedlyLogoutService, log: OSLog) { + public init(service: FeedlyLogoutService, log: OSLog) { self.service = service - self.account = account self.log = log } - override func run() { - os_log("Requesting logout of %{public}@ account.", "\(account.type)") + public override func run() { + os_log("Requesting logout of Feedly account.") service.logout(completion: didCompleteLogout(_:)) } @@ -34,10 +32,11 @@ final class FeedlyLogoutOperation: FeedlyOperation { assert(Thread.isMainThread) switch result { case .success: - os_log("Logged out of %{public}@ account.", "\(account.type)") + os_log("Logged out of Feedly account.") do { - try account.removeCredentials(type: .oauthAccessToken) - try account.removeCredentials(type: .oauthRefreshToken) + // TODO: fix removing credentials +// try account.removeCredentials(type: .oauthAccessToken) +// try account.removeCredentials(type: .oauthRefreshToken) } catch { // oh well, we tried our best. } diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyOperation.swift b/Feedly/Sources/Feedly/Operations/FeedlyOperation.swift similarity index 58% rename from Account/Sources/Account/Feedly/Operations/FeedlyOperation.swift rename to Feedly/Sources/Feedly/Operations/FeedlyOperation.swift index 4042bc759..8f809aab5 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyOperation.swift +++ b/Feedly/Sources/Feedly/Operations/FeedlyOperation.swift @@ -10,7 +10,7 @@ import Foundation import Web import Core -protocol FeedlyOperationDelegate: AnyObject { +public protocol FeedlyOperationDelegate: AnyObject { @MainActor func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) } @@ -18,10 +18,10 @@ protocol FeedlyOperationDelegate: AnyObject { /// /// Normally we don’t do inheritance — but in this case /// it’s the best option. -@MainActor class FeedlyOperation: MainThreadOperation { +@MainActor open class FeedlyOperation: MainThreadOperation { - weak var delegate: FeedlyOperationDelegate? - var downloadProgress: DownloadProgress? { + public weak var delegate: FeedlyOperationDelegate? + public var downloadProgress: DownloadProgress? { didSet { oldValue?.completeTask() downloadProgress?.addToNumberOfTasksAndRemaining(1) @@ -29,34 +29,36 @@ protocol FeedlyOperationDelegate: AnyObject { } // MainThreadOperation - var isCanceled = false { + public var isCanceled = false { didSet { if isCanceled { didCancel() } } } - var id: Int? - weak var operationDelegate: MainThreadOperationDelegate? - var name: String? - var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock? + public var id: Int? + public weak var operationDelegate: MainThreadOperationDelegate? + public var name: String? + public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock? - func run() { + public init() {} + + open func run() { } - func didFinish() { + open func didFinish() { if !isCanceled { operationDelegate?.operationDidComplete(self) } downloadProgress?.completeTask() } - func didFinish(with error: Error) { + open func didFinish(with error: Error) { delegate?.feedlyOperation(self, didFailWith: error) didFinish() } - func didCancel() { + open func didCancel() { didFinish() } } diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyOrganiseParsedItemsByFeedOperation.swift b/Feedly/Sources/Feedly/Operations/FeedlyOrganiseParsedItemsByFeedOperation.swift similarity index 69% rename from Account/Sources/Account/Feedly/Operations/FeedlyOrganiseParsedItemsByFeedOperation.swift rename to Feedly/Sources/Feedly/Operations/FeedlyOrganiseParsedItemsByFeedOperation.swift index f00757c31..d9e6442d1 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyOrganiseParsedItemsByFeedOperation.swift +++ b/Feedly/Sources/Feedly/Operations/FeedlyOrganiseParsedItemsByFeedOperation.swift @@ -10,36 +10,36 @@ import Foundation import Parser import os.log -protocol FeedlyParsedItemsByFeedProviding { - var parsedItemsByFeedProviderName: String { get } - var parsedItemsKeyedByFeedID: [String: Set] { get } +public protocol FeedlyParsedItemsByFeedProviding { + + @MainActor var parsedItemsByFeedProviderName: String { get } + @MainActor var parsedItemsKeyedByFeedID: [String: Set] { get } } /// Group articles by their feeds. -final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyParsedItemsByFeedProviding { +public final class FeedlyOrganiseParsedItemsByFeedOperation: FeedlyOperation, FeedlyParsedItemsByFeedProviding { - private let account: Account private let parsedItemProvider: FeedlyParsedItemProviding private let log: OSLog - var parsedItemsByFeedProviderName: String { + public var parsedItemsByFeedProviderName: String { return name ?? String(describing: Self.self) } - var parsedItemsKeyedByFeedID: [String : Set] { + public var parsedItemsKeyedByFeedID: [String : Set] { precondition(Thread.isMainThread) // Needs to be on main thread because Feed is a main-thread-only model type. return itemsKeyedByFeedID } private var itemsKeyedByFeedID = [String: Set]() - init(account: Account, parsedItemProvider: FeedlyParsedItemProviding, log: OSLog) { - self.account = account + public init(parsedItemProvider: FeedlyParsedItemProviding, log: OSLog) { + self.parsedItemProvider = parsedItemProvider self.log = log } - override func run() { + public override func run() { defer { didFinish() } diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyRequestStreamsOperation.swift b/Feedly/Sources/Feedly/Operations/FeedlyRequestStreamsOperation.swift similarity index 73% rename from Account/Sources/Account/Feedly/Operations/FeedlyRequestStreamsOperation.swift rename to Feedly/Sources/Feedly/Operations/FeedlyRequestStreamsOperation.swift index cc3b087d8..3feda30cf 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyRequestStreamsOperation.swift +++ b/Feedly/Sources/Feedly/Operations/FeedlyRequestStreamsOperation.swift @@ -15,19 +15,18 @@ protocol FeedlyRequestStreamsOperationDelegate: AnyObject { /// Create one stream request operation for one Feedly collection. /// This is the start of the process of refreshing the entire contents of a Folder. -final class FeedlyRequestStreamsOperation: FeedlyOperation { +public final class FeedlyRequestStreamsOperation: FeedlyOperation { weak var queueDelegate: FeedlyRequestStreamsOperationDelegate? let collectionsProvider: FeedlyCollectionProviding let service: FeedlyGetStreamContentsService - let account: Account let log: OSLog let newerThan: Date? let unreadOnly: Bool? - init(account: Account, collectionsProvider: FeedlyCollectionProviding, newerThan: Date?, unreadOnly: Bool?, service: FeedlyGetStreamContentsService, log: OSLog) { - self.account = account + init(collectionsProvider: FeedlyCollectionProviding, newerThan: Date?, unreadOnly: Bool?, service: FeedlyGetStreamContentsService, log: OSLog) { + self.service = service self.collectionsProvider = collectionsProvider self.newerThan = newerThan @@ -35,7 +34,7 @@ final class FeedlyRequestStreamsOperation: FeedlyOperation { self.log = log } - override func run() { + public override func run() { defer { didFinish() } @@ -46,12 +45,11 @@ final class FeedlyRequestStreamsOperation: FeedlyOperation { for collection in collectionsProvider.collections { let resource = FeedlyCategoryResourceID(id: collection.id) - let operation = FeedlyGetStreamContentsOperation(account: account, - resource: resource, - service: service, - newerThan: newerThan, - unreadOnly: unreadOnly, - log: log) + let operation = FeedlyGetStreamContentsOperation(resource: resource, + service: service, + newerThan: newerThan, + unreadOnly: unreadOnly, + log: log) queueDelegate?.feedlyRequestStreamsOperation(self, enqueue: operation) } diff --git a/Account/Sources/Account/Feedly/Operations/FeedlySearchOperation.swift b/Feedly/Sources/Feedly/Operations/FeedlySearchOperation.swift similarity index 76% rename from Account/Sources/Account/Feedly/Operations/FeedlySearchOperation.swift rename to Feedly/Sources/Feedly/Operations/FeedlySearchOperation.swift index 2f22bd105..e732b9ff1 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlySearchOperation.swift +++ b/Feedly/Sources/Feedly/Operations/FeedlySearchOperation.swift @@ -8,30 +8,30 @@ import Foundation -protocol FeedlySearchService: AnyObject { +public protocol FeedlySearchService: AnyObject { func getFeeds(for query: String, count: Int, locale: String, completion: @escaping (Result) -> ()) } -protocol FeedlySearchOperationDelegate: AnyObject { +public protocol FeedlySearchOperationDelegate: AnyObject { @MainActor func feedlySearchOperation(_ operation: FeedlySearchOperation, didGet response: FeedlyFeedsSearchResponse) } /// Find one and only one feed for a given query (usually, a URL). /// What happens when a feed is found for the URL is delegated to the `searchDelegate`. -class FeedlySearchOperation: FeedlyOperation { +public class FeedlySearchOperation: FeedlyOperation { let query: String let locale: Locale let searchService: FeedlySearchService - weak var searchDelegate: FeedlySearchOperationDelegate? + public weak var searchDelegate: FeedlySearchOperationDelegate? - init(query: String, locale: Locale = .current, service: FeedlySearchService) { + public init(query: String, locale: Locale = .current, service: FeedlySearchService) { self.query = query self.locale = locale self.searchService = service } - override func run() { + public override func run() { searchService.getFeeds(for: query, count: 1, locale: locale.identifier) { result in switch result { case .success(let response): diff --git a/Account/Sources/Account/Feedly/Operations/FeedlySendArticleStatusesOperation.swift b/Feedly/Sources/Feedly/Operations/FeedlySendArticleStatusesOperation.swift similarity index 90% rename from Account/Sources/Account/Feedly/Operations/FeedlySendArticleStatusesOperation.swift rename to Feedly/Sources/Feedly/Operations/FeedlySendArticleStatusesOperation.swift index dfbd9d4bc..45e9df593 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlySendArticleStatusesOperation.swift +++ b/Feedly/Sources/Feedly/Operations/FeedlySendArticleStatusesOperation.swift @@ -11,21 +11,20 @@ import Articles import SyncDatabase import os.log - /// Take changes to statuses of articles locally and apply them to the corresponding the articles remotely. -final class FeedlySendArticleStatusesOperation: FeedlyOperation { +public final class FeedlySendArticleStatusesOperation: FeedlyOperation { private let database: SyncDatabase private let log: OSLog private let service: FeedlyMarkArticlesService - init(database: SyncDatabase, service: FeedlyMarkArticlesService, log: OSLog) { + public init(database: SyncDatabase, service: FeedlyMarkArticlesService, log: OSLog) { self.database = database self.service = service self.log = log } - override func run() { + public override func run() { os_log(.debug, log: log, "Sending article statuses...") Task { @MainActor in diff --git a/Account/Sources/Account/Feedly/Services/FeedlyGetCollectionsService.swift b/Feedly/Sources/Feedly/Services/FeedlyGetCollectionsService.swift similarity index 82% rename from Account/Sources/Account/Feedly/Services/FeedlyGetCollectionsService.swift rename to Feedly/Sources/Feedly/Services/FeedlyGetCollectionsService.swift index 2dbf6861b..e46a4a5b1 100644 --- a/Account/Sources/Account/Feedly/Services/FeedlyGetCollectionsService.swift +++ b/Feedly/Sources/Feedly/Services/FeedlyGetCollectionsService.swift @@ -8,6 +8,6 @@ import Foundation -protocol FeedlyGetCollectionsService: AnyObject { +public protocol FeedlyGetCollectionsService: AnyObject { func getCollections(completion: @escaping (Result<[FeedlyCollection], Error>) -> ()) } diff --git a/Account/Sources/Account/Feedly/Services/FeedlyGetEntriesService.swift b/Feedly/Sources/Feedly/Services/FeedlyGetEntriesService.swift similarity index 84% rename from Account/Sources/Account/Feedly/Services/FeedlyGetEntriesService.swift rename to Feedly/Sources/Feedly/Services/FeedlyGetEntriesService.swift index 7bc00b607..0fefc7f49 100644 --- a/Account/Sources/Account/Feedly/Services/FeedlyGetEntriesService.swift +++ b/Feedly/Sources/Feedly/Services/FeedlyGetEntriesService.swift @@ -8,6 +8,6 @@ import Foundation -protocol FeedlyGetEntriesService: AnyObject { +public protocol FeedlyGetEntriesService: AnyObject { func getEntries(for ids: Set, completion: @escaping (Result<[FeedlyEntry], Error>) -> ()) } diff --git a/Account/Sources/Account/Feedly/Services/FeedlyGetStreamContentsService.swift b/Feedly/Sources/Feedly/Services/FeedlyGetStreamContentsService.swift similarity index 85% rename from Account/Sources/Account/Feedly/Services/FeedlyGetStreamContentsService.swift rename to Feedly/Sources/Feedly/Services/FeedlyGetStreamContentsService.swift index 198cbf7d4..ceac9d931 100644 --- a/Account/Sources/Account/Feedly/Services/FeedlyGetStreamContentsService.swift +++ b/Feedly/Sources/Feedly/Services/FeedlyGetStreamContentsService.swift @@ -8,6 +8,6 @@ import Foundation -protocol FeedlyGetStreamContentsService: AnyObject { +public protocol FeedlyGetStreamContentsService: AnyObject { func getStreamContents(for resource: FeedlyResourceID, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result) -> ()) } diff --git a/Account/Sources/Account/Feedly/Services/FeedlyGetStreamIDsService.swift b/Feedly/Sources/Feedly/Services/FeedlyGetStreamIDsService.swift similarity index 86% rename from Account/Sources/Account/Feedly/Services/FeedlyGetStreamIDsService.swift rename to Feedly/Sources/Feedly/Services/FeedlyGetStreamIDsService.swift index 65cdb8323..7205ddf16 100644 --- a/Account/Sources/Account/Feedly/Services/FeedlyGetStreamIDsService.swift +++ b/Feedly/Sources/Feedly/Services/FeedlyGetStreamIDsService.swift @@ -8,6 +8,6 @@ import Foundation -protocol FeedlyGetStreamIDsService: AnyObject { +public protocol FeedlyGetStreamIDsService: AnyObject { func getStreamIDs(for resource: FeedlyResourceID, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result) -> ()) } diff --git a/Account/Sources/Account/Feedly/Services/FeedlyMarkArticlesService.swift b/Feedly/Sources/Feedly/Services/FeedlyMarkArticlesService.swift similarity index 57% rename from Account/Sources/Account/Feedly/Services/FeedlyMarkArticlesService.swift rename to Feedly/Sources/Feedly/Services/FeedlyMarkArticlesService.swift index 05c54410f..4c7e07c89 100644 --- a/Account/Sources/Account/Feedly/Services/FeedlyMarkArticlesService.swift +++ b/Feedly/Sources/Feedly/Services/FeedlyMarkArticlesService.swift @@ -8,28 +8,29 @@ import Foundation -enum FeedlyMarkAction: String { - case read - case unread - case saved - case unsaved - +public enum FeedlyMarkAction: String, Sendable { + + case read + case unread + case saved + case unsaved + /// These values are paired with the "action" key in POST requests to the markers API. /// See for example: https://developer.feedly.com/v3/markers/#mark-one-or-multiple-articles-as-read - var actionValue: String { - switch self { - case .read: - return "markAsRead" - case .unread: - return "keepUnread" - case .saved: - return "markAsSaved" - case .unsaved: - return "markAsUnsaved" - } - } - } + public var actionValue: String { + switch self { + case .read: + return "markAsRead" + case .unread: + return "keepUnread" + case .saved: + return "markAsSaved" + case .unsaved: + return "markAsUnsaved" + } + } +} -protocol FeedlyMarkArticlesService: AnyObject { +public protocol FeedlyMarkArticlesService: AnyObject { func mark(_ articleIDs: Set, as action: FeedlyMarkAction, completion: @escaping (Result) -> ()) } diff --git a/Feedly/Tests/FeedlyTests/FeedlyTests.swift b/Feedly/Tests/FeedlyTests/FeedlyTests.swift new file mode 100644 index 000000000..15208d112 --- /dev/null +++ b/Feedly/Tests/FeedlyTests/FeedlyTests.swift @@ -0,0 +1,12 @@ +import XCTest +@testable import Feedly + +final class FeedlyTests: XCTestCase { + func testExample() throws { + // XCTest Documentation + // https://developer.apple.com/documentation/xctest + + // Defining Test Cases and Test Methods + // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods + } +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index fe1d0c453..05d3bd787 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -1392,6 +1392,7 @@ 84A3EE52223B667F00557320 /* DefaultFeeds.opml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = DefaultFeeds.opml; sourceTree = ""; }; 84A699132BC34E8500605AB8 /* ArticleExtractor */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = ArticleExtractor; sourceTree = ""; }; 84A699182BC3524C00605AB8 /* LocalAccount */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = LocalAccount; sourceTree = ""; }; + 84A699192BC36EDB00605AB8 /* Feedly */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Feedly; sourceTree = ""; }; 84AD1EA92031617300BC20B7 /* PasteboardFolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardFolder.swift; sourceTree = ""; }; 84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeedPasteboardWriter.swift; sourceTree = ""; }; 84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarOutlineDataSource.swift; sourceTree = ""; }; @@ -2353,6 +2354,7 @@ 84FB9FAE2BC3494B00B7AFC3 /* FeedFinder */, 84A699182BC3524C00605AB8 /* LocalAccount */, 84FB9FAD2BC344F800B7AFC3 /* Feedbin */, + 84A699192BC36EDB00605AB8 /* Feedly */, 84FB9FAC2BC33AFE00B7AFC3 /* NewsBlur */, 84CC98D92BC1DD25006A05C9 /* ReaderAPI */, 845F3D2B2BC268FE00AEBB68 /* CloudKitSync */,