diff --git a/Account/Sources/Account/AccountDelegates/FeedlyAccountDelegate.swift b/Account/Sources/Account/AccountDelegates/FeedlyAccountDelegate.swift index 4b88b6e3f..d8e02ae22 100644 --- a/Account/Sources/Account/AccountDelegates/FeedlyAccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegates/FeedlyAccountDelegate.swift @@ -377,44 +377,24 @@ final class FeedlyAccountDelegate: AccountDelegate { func renameFeed(for account: Account, with feed: Feed, to name: String) async throws { - try await withCheckedThrowingContinuation { continuation in - - self.renameFeed(for: account, with: feed, to: name) { result in - switch result { - case .success: - continuation.resume() - case .failure(let error): - continuation.resume(throwing: error) - } - } - } - } - - func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { let folderCollectionIDs = account.folders?.filter { $0.has(feed) }.compactMap { $0.externalID } guard let collectionIDs = folderCollectionIDs, let collectionID = collectionIDs.first else { - completion(.failure(FeedlyAccountDelegateError.unableToRenameFeed(feed.nameForDisplay, name))) - return + throw FeedlyAccountDelegateError.unableToRenameFeed(feed.nameForDisplay, name) } let feedID = FeedlyFeedResourceID(id: feed.feedID) let editedNameBefore = feed.editedName - // Adding an existing feed updates it. - // Updating feed name in one folder/collection updates it for all folders/collections. - caller.addFeed(with: feedID, title: name, toCollectionWith: collectionID) { result in - switch result { - case .success: - completion(.success(())) - - case .failure(let error): - feed.editedName = editedNameBefore - completion(.failure(error)) - } - } - - // optimistically set the name + // Optimistically set the name feed.editedName = name + + do { + // Adding an existing feed updates it. + // Updating feed name in one folder/collection updates it for all folders/collections. + try await caller.addFeed(with: feedID, title: name, toCollectionWith: collectionID) + } catch { + feed.editedName = editedNameBefore + } } func addFeed(for account: Account, with feed: Feed, to container: any Container) async throws { diff --git a/Account/Sources/Account/Feedly/FeedlyAPICaller.swift b/Account/Sources/Account/Feedly/FeedlyAPICaller.swift index e19f82bf4..8303e881b 100644 --- a/Account/Sources/Account/Feedly/FeedlyAPICaller.swift +++ b/Account/Sources/Account/Feedly/FeedlyAPICaller.swift @@ -155,7 +155,7 @@ protocol FeedlyAPICallerDelegate: AnyObject { guard !isSuspended else { throw TransportError.suspended } - var request = try urlRequest(path: "/v3/opml", method: HTTPMethod.post, addJSONHeaders: false, addOauthToken: true) + var request = try urlRequest(path: "/v3/opml", method: HTTPMethod.post, includeJSONHeaders: false, includeOauthToken: true) request.addValue("text/xml", forHTTPHeaderField: HTTPRequestHeader.contentType) request.addValue("application/json", forHTTPHeaderField: HTTPRequestHeader.acceptType) request.httpBody = opmlData @@ -170,7 +170,7 @@ protocol FeedlyAPICallerDelegate: AnyObject { guard !isSuspended else { throw TransportError.suspended } - var request = try urlRequest(path: "/v3/collections", method: HTTPMethod.post, addJSONHeaders: true, addOauthToken: true) + var request = try urlRequest(path: "/v3/collections", method: HTTPMethod.post, includeJSONHeaders: true, includeOauthToken: true) struct CreateCollectionBody: Encodable { var label: String @@ -191,7 +191,7 @@ protocol FeedlyAPICallerDelegate: AnyObject { guard !isSuspended else { throw TransportError.suspended } - var request = try urlRequest(path: "/v3/collections", method: HTTPMethod.post, addJSONHeaders: true, addOauthToken: true) + var request = try urlRequest(path: "/v3/collections", method: HTTPMethod.post, includeJSONHeaders: true, includeOauthToken: true) struct RenameCollectionBody: Encodable { var id: String @@ -220,7 +220,7 @@ protocol FeedlyAPICallerDelegate: AnyObject { guard let encodedID = encodeForURLPath(id) else { throw FeedlyAccountDelegateError.unexpectedResourceID(id) } - let request = try urlRequest(path: "/v3/collections/\(encodedID)", method: HTTPMethod.delete, addJSONHeaders: true, addOauthToken: true) + let request = try urlRequest(path: "/v3/collections/\(encodedID)", method: HTTPMethod.delete, includeJSONHeaders: true, includeOauthToken: true) let (httpResponse, _) = try await send(request: request, resultType: Optional.self) @@ -245,7 +245,7 @@ protocol FeedlyAPICallerDelegate: AnyObject { var request = URLRequest(url: url) request.httpMethod = HTTPMethod.delete - _addJSONHeaders(&request) + addJSONHeaders(&request) try addOauthAccessToken(&request) struct RemovableFeed: Encodable { @@ -267,63 +267,38 @@ protocol FeedlyAPICallerDelegate: AnyObject { extension FeedlyAPICaller: FeedlyAddFeedToCollectionService { - @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)) - } - } - - guard let accessToken = credentials?.secret else { - return DispatchQueue.main.async { - completion(.failure(CredentialsError.incompleteCredentials)) - } - } + @MainActor func addFeed(with feedID: FeedlyFeedResourceID, title: String? = nil, toCollectionWith collectionID: String) async throws -> [FeedlyFeed] { + + guard !isSuspended else { throw TransportError.suspended } guard let encodedID = encodeForURLPath(collectionID) else { - return DispatchQueue.main.async { - completion(.failure(FeedlyAccountDelegateError.unexpectedResourceID(collectionID))) - } + throw FeedlyAccountDelegateError.unexpectedResourceID(collectionID) } var components = baseURLComponents components.percentEncodedPath = "/v3/collections/\(encodedID)/feeds" - guard let url = components.url else { fatalError("\(components) does not produce a valid URL.") } var request = URLRequest(url: url) - request.httpMethod = "PUT" - request.addValue("application/json", forHTTPHeaderField: HTTPRequestHeader.contentType) - request.addValue("application/json", forHTTPHeaderField: "Accept-Type") - request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization) - - do { - struct AddFeedBody: Encodable { - var id: String - var title: String? - } - let encoder = JSONEncoder() - let data = try encoder.encode(AddFeedBody(id: feedId.id, title: title)) - request.httpBody = data - } catch { - return DispatchQueue.main.async { - completion(.failure(error)) - } + request.httpMethod = HTTPMethod.put + addJSONHeaders(&request) + try addOauthAccessToken(&request) + + struct AddFeedBody: Encodable { + var id: String + var title: String? } - - send(request: request, resultType: [FeedlyFeed].self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in - switch result { - case .success((_, let collectionFeeds)): - if let feeds = collectionFeeds { - completion(.success(feeds)) - } else { - completion(.failure(URLError(.cannotDecodeContentData))) - } - case .failure(let error): - completion(.failure(error)) - } + let encoder = JSONEncoder() + let data = try encoder.encode(AddFeedBody(id: feedID.id, title: title)) + request.httpBody = data + + let (_, collectionFeeds) = try await send(request: request, resultType: [FeedlyFeed].self) + guard let collectionFeeds else { + throw URLError(.cannotDecodeContentData) } + + return collectionFeeds } } @@ -834,24 +809,24 @@ extension FeedlyAPICaller: FeedlyLogoutService { private extension FeedlyAPICaller { - func urlRequest(path: String, method: String, addJSONHeaders: Bool, addOauthToken: Bool) throws -> URLRequest { + func urlRequest(path: String, method: String, includeJSONHeaders: Bool, includeOauthToken: Bool) throws -> URLRequest { let url = apiURL(path) var request = URLRequest(url: url) request.httpMethod = method - if addJSONHeaders { - _addJSONHeaders(&request) + if includeJSONHeaders { + addJSONHeaders(&request) } - if addOauthToken { + if includeOauthToken { try addOauthAccessToken(&request) } return request } - func _addJSONHeaders(_ request: inout URLRequest) { + func addJSONHeaders(_ request: inout URLRequest) { request.addValue("application/json", forHTTPHeaderField: HTTPRequestHeader.contentType) request.addValue("application/json", forHTTPHeaderField: "Accept-Type") diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyAddFeedToCollectionOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyAddFeedToCollectionOperation.swift index 38f20324f..6d7adfb65 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyAddFeedToCollectionOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyAddFeedToCollectionOperation.swift @@ -11,7 +11,7 @@ import CommonErrors import Feedly 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) async throws -> [FeedlyFeed] } final class FeedlyAddFeedToCollectionOperation: FeedlyOperation, FeedlyFeedsAndFoldersProviding, FeedlyResourceProviding { @@ -37,36 +37,24 @@ final class FeedlyAddFeedToCollectionOperation: FeedlyOperation, FeedlyFeedsAndF } override func run() { - service.addFeed(with: feedResource, title: feedName, toCollectionWith: collectionID) { [weak self] result in - guard let self = self else { - return + + Task { @MainActor in + + do { + let feedlyFeeds = try await service.addFeed(with: feedResource, title: feedName, toCollectionWith: collectionID) + + feedsAndFolders = [(feedlyFeeds, folder)] + + let feedsWithCreatedFeedID = feedlyFeeds.filter { $0.id == resource.id } + if feedsWithCreatedFeedID.isEmpty { + didFinish(with: AccountError.createErrorNotFound) + } else { + didFinish() + } + + } catch { + didFinish(with: error) } - if self.isCanceled { - self.didFinish() - return - } - self.didCompleteRequest(result) - } - } -} - -private extension FeedlyAddFeedToCollectionOperation { - - func didCompleteRequest(_ result: Result<[FeedlyFeed], Error>) { - switch result { - case .success(let feedlyFeeds): - feedsAndFolders = [(feedlyFeeds, folder)] - - let feedsWithCreatedFeedID = feedlyFeeds.filter { $0.id == resource.id } - - if feedsWithCreatedFeedID.isEmpty { - didFinish(with: AccountError.createErrorNotFound) - } else { - didFinish() - } - - case .failure(let error): - didFinish(with: error) } } }