mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-02-02 20:16:54 +01:00
Fix numerous concurrency warnings.
This commit is contained in:
parent
52345724ce
commit
dfcf567270
@ -271,7 +271,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private func importOPML(for account: Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
private func importOPML(for account: Account, opmlFile: URL, completion: @escaping @Sendable (Result<Void, Error>) -> Void) {
|
||||
let data: Data
|
||||
|
||||
do {
|
||||
@ -286,21 +286,24 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
|
||||
caller.importOpml(data) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
os_log(.debug, log: self.log, "Import OPML done.")
|
||||
self.refreshProgress.completeTask()
|
||||
self.isOPMLImportInProgress = false
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(()))
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.debug, log: self.log, "Import OPML failed.")
|
||||
self.refreshProgress.completeTask()
|
||||
self.isOPMLImportInProgress = false
|
||||
DispatchQueue.main.async {
|
||||
let wrappedError = AccountError.wrappedError(error: error, account: account)
|
||||
completion(.failure(wrappedError))
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
switch result {
|
||||
case .success:
|
||||
os_log(.debug, log: self.log, "Import OPML done.")
|
||||
self.refreshProgress.completeTask()
|
||||
self.isOPMLImportInProgress = false
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(()))
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.debug, log: self.log, "Import OPML failed.")
|
||||
self.refreshProgress.completeTask()
|
||||
self.isOPMLImportInProgress = false
|
||||
DispatchQueue.main.async {
|
||||
let wrappedError = AccountError.wrappedError(error: error, account: account)
|
||||
completion(.failure(wrappedError))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import Articles
|
||||
import Database
|
||||
@preconcurrency import Parser
|
||||
import Parser
|
||||
import Web
|
||||
import SyncDatabase
|
||||
import os.log
|
||||
|
@ -9,7 +9,8 @@
|
||||
import Foundation
|
||||
|
||||
public protocol ContainerIdentifiable {
|
||||
var containerID: ContainerIdentifier? { get }
|
||||
|
||||
@MainActor var containerID: ContainerIdentifier? { get }
|
||||
}
|
||||
|
||||
public enum ContainerIdentifier: Hashable, Equatable, Sendable {
|
||||
|
@ -14,7 +14,7 @@ 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.
|
||||
/// Pass `true` to the completion handler if the failing request should be retried with a fresh token or `false` if the unauthorized request should complete with the original failure error.
|
||||
func reauthorizeFeedlyAPICaller(_ caller: FeedlyAPICaller, completionHandler: @escaping (Bool) -> ())
|
||||
@MainActor func reauthorizeFeedlyAPICaller(_ caller: FeedlyAPICaller, completionHandler: @escaping (Bool) -> ())
|
||||
}
|
||||
|
||||
final class FeedlyAPICaller {
|
||||
@ -86,55 +86,57 @@ final class FeedlyAPICaller {
|
||||
isSuspended = false
|
||||
}
|
||||
|
||||
func send<R: Decodable>(request: URLRequest, resultType: R.Type, dateDecoding: JSONDecoder.DateDecodingStrategy = .iso8601, keyDecoding: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys, completion: @escaping (Result<(HTTPURLResponse, R?), Error>) -> Void) {
|
||||
func send<R: Decodable & Sendable>(request: URLRequest, resultType: R.Type, dateDecoding: JSONDecoder.DateDecodingStrategy = .iso8601, keyDecoding: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys, completion: @escaping (Result<(HTTPURLResponse, R?), Error>) -> Void) {
|
||||
transport.send(request: request, resultType: resultType, dateDecoding: dateDecoding, keyDecoding: keyDecoding) { [weak self] result in
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
switch result {
|
||||
case .success:
|
||||
completion(result)
|
||||
case .failure(let error):
|
||||
switch error {
|
||||
case TransportError.httpError(let statusCode) where statusCode == 401:
|
||||
|
||||
assert(self == nil ? true : self?.delegate != nil, "Check the delegate is set to \(FeedlyAccountDelegate.self).")
|
||||
|
||||
guard let self = self, let delegate = self.delegate else {
|
||||
completion(result)
|
||||
return
|
||||
}
|
||||
|
||||
/// Capture the credentials before the reauthorization to check for a change.
|
||||
let credentialsBefore = self.credentials
|
||||
|
||||
delegate.reauthorizeFeedlyAPICaller(self) { [weak self] isReauthorizedAndShouldRetry in
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
guard isReauthorizedAndShouldRetry, let self = self else {
|
||||
completion(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for a change. Not only would it help debugging, but it'll also catch an infinitely recursive attempt to refresh.
|
||||
guard let accessToken = self.credentials?.secret, accessToken != credentialsBefore?.secret else {
|
||||
assertionFailure("Could not update the request with a new OAuth token. Did \(String(describing: self.delegate)) set them on \(self)?")
|
||||
completion(result)
|
||||
return
|
||||
}
|
||||
|
||||
var reauthorizedRequest = request
|
||||
reauthorizedRequest.setValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization)
|
||||
|
||||
self.send(request: reauthorizedRequest, resultType: resultType, dateDecoding: dateDecoding, keyDecoding: keyDecoding, completion: completion)
|
||||
}
|
||||
default:
|
||||
MainActor.assumeIsolated {
|
||||
|
||||
switch result {
|
||||
case .success:
|
||||
completion(result)
|
||||
case .failure(let error):
|
||||
switch error {
|
||||
case TransportError.httpError(let statusCode) where statusCode == 401:
|
||||
|
||||
assert(self == nil ? true : self?.delegate != nil, "Check the delegate is set to \(FeedlyAccountDelegate.self).")
|
||||
|
||||
guard let self = self, let delegate = self.delegate else {
|
||||
completion(result)
|
||||
return
|
||||
}
|
||||
|
||||
/// Capture the credentials before the reauthorization to check for a change.
|
||||
let credentialsBefore = self.credentials
|
||||
|
||||
delegate.reauthorizeFeedlyAPICaller(self) { [weak self] isReauthorizedAndShouldRetry in
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
guard isReauthorizedAndShouldRetry, let self = self else {
|
||||
completion(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for a change. Not only would it help debugging, but it'll also catch an infinitely recursive attempt to refresh.
|
||||
guard let accessToken = self.credentials?.secret, accessToken != credentialsBefore?.secret else {
|
||||
assertionFailure("Could not update the request with a new OAuth token. Did \(String(describing: self.delegate)) set them on \(self)?")
|
||||
completion(result)
|
||||
return
|
||||
}
|
||||
|
||||
var reauthorizedRequest = request
|
||||
reauthorizedRequest.setValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization)
|
||||
|
||||
self.send(request: reauthorizedRequest, resultType: resultType, dateDecoding: dateDecoding, keyDecoding: keyDecoding, completion: completion)
|
||||
}
|
||||
default:
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func importOpml(_ opmlData: Data, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
func importOpml(_ opmlData: Data, completion: @escaping @Sendable (Result<Void, Error>) -> ()) {
|
||||
guard !isSuspended else {
|
||||
return DispatchQueue.main.async {
|
||||
completion(.failure(TransportError.suspended))
|
||||
@ -566,7 +568,7 @@ extension FeedlyAPICaller: OAuthAcessTokenRefreshRequesting {
|
||||
|
||||
extension FeedlyAPICaller: FeedlyGetCollectionsService {
|
||||
|
||||
func getCollections(completion: @escaping (Result<[FeedlyCollection], Error>) -> ()) {
|
||||
func getCollections(completion: @escaping @Sendable (Result<[FeedlyCollection], Error>) -> ()) {
|
||||
guard !isSuspended else {
|
||||
return DispatchQueue.main.async {
|
||||
completion(.failure(TransportError.suspended))
|
||||
|
@ -43,5 +43,5 @@ public protocol OAuthAcessTokenRefreshRequesting {
|
||||
/// Implemented by concrete types to perform the actual request.
|
||||
protocol OAuthAccessTokenRefreshing: AnyObject {
|
||||
|
||||
func refreshAccessToken(with refreshToken: String, client: OAuthAuthorizationClient, completion: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ())
|
||||
@MainActor func refreshAccessToken(with refreshToken: String, client: OAuthAuthorizationClient, completion: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ())
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ public enum CloudKitZoneError: LocalizedError {
|
||||
}
|
||||
|
||||
public protocol CloudKitZoneDelegate: AnyObject {
|
||||
|
||||
func cloudKitDidModify(changed: [CKRecord], deleted: [CloudKitRecordKey], completion: @escaping (Result<Void, Error>) -> Void);
|
||||
}
|
||||
|
||||
|
@ -31,15 +31,18 @@ public final class FeedlyGetCollectionsOperation: FeedlyOperation, FeedlyCollect
|
||||
os_log(.debug, log: log, "Requesting collections.")
|
||||
|
||||
service.getCollections { result in
|
||||
switch result {
|
||||
case .success(let collections):
|
||||
os_log(.debug, log: self.log, "Received collections: %{public}@", collections.map { $0.id })
|
||||
self.collections = collections
|
||||
self.didFinish()
|
||||
|
||||
case .failure(let error):
|
||||
os_log(.debug, log: self.log, "Unable to request collections: %{public}@.", error as NSError)
|
||||
self.didFinish(with: error)
|
||||
|
||||
MainActor.assumeIsolated {
|
||||
switch result {
|
||||
case .success(let collections):
|
||||
os_log(.debug, log: self.log, "Received collections: %{public}@", collections.map { $0.id })
|
||||
self.collections = collections
|
||||
self.didFinish()
|
||||
|
||||
case .failure(let error):
|
||||
os_log(.debug, log: self.log, "Unable to request collections: %{public}@.", error as NSError)
|
||||
self.didFinish(with: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,5 +9,5 @@
|
||||
import Foundation
|
||||
|
||||
public protocol FeedlyGetCollectionsService: AnyObject {
|
||||
func getCollections(completion: @escaping (Result<[FeedlyCollection], Error>) -> ())
|
||||
func getCollections(completion: @escaping @Sendable (Result<[FeedlyCollection], Error>) -> ())
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import Cocoa
|
||||
import os.log
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
class ShareViewController: NSViewController {
|
||||
final class ShareViewController: NSViewController {
|
||||
|
||||
@IBOutlet weak var nameTextField: NSTextField!
|
||||
@IBOutlet weak var folderPopUpButton: NSPopUpButton!
|
||||
|
10
Parser/Sources/Swift/ParserData+Parser.swift
Normal file
10
Parser/Sources/Swift/ParserData+Parser.swift
Normal file
@ -0,0 +1,10 @@
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Brent Simmons on 4/7/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension ParserData: @unchecked Sendable {}
|
10
Parser/Sources/Swift/RSHTMLMetadata+Parser.swift
Normal file
10
Parser/Sources/Swift/RSHTMLMetadata+Parser.swift
Normal file
@ -0,0 +1,10 @@
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Brent Simmons on 4/7/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension RSHTMLMetadataParser: @unchecked Sendable {}
|
@ -14,7 +14,7 @@ struct HTMLMetadataDownloader {
|
||||
|
||||
static let serialDispatchQueue = DispatchQueue(label: "HTMLMetadataDownloader")
|
||||
|
||||
@MainActor static func downloadMetadata(for url: String, _ completion: @escaping (RSHTMLMetadata?) -> Void) {
|
||||
@MainActor static func downloadMetadata(for url: String, _ completion: @escaping @Sendable (RSHTMLMetadata?) -> Void) {
|
||||
guard let actualURL = URL(unicodeString: url) else {
|
||||
completion(nil)
|
||||
return
|
||||
@ -32,7 +32,7 @@ struct HTMLMetadataDownloader {
|
||||
}
|
||||
}
|
||||
|
||||
private static func parseMetadata(with parserData: ParserData, _ completion: @escaping (RSHTMLMetadata?) -> Void) {
|
||||
private static func parseMetadata(with parserData: ParserData, _ completion: @escaping @Sendable (RSHTMLMetadata?) -> Void) {
|
||||
serialDispatchQueue.async {
|
||||
let htmlMetadata = RSHTMLMetadataParser.htmlMetadata(with: parserData)
|
||||
DispatchQueue.main.async {
|
||||
|
@ -216,11 +216,13 @@ private extension FeedIconDownloader {
|
||||
|
||||
HTMLMetadataDownloader.downloadMetadata(for: homePageURL) { (metadata) in
|
||||
|
||||
self.urlsInProgress.remove(homePageURL)
|
||||
guard let metadata = metadata else {
|
||||
return
|
||||
MainActor.assumeIsolated {
|
||||
self.urlsInProgress.remove(homePageURL)
|
||||
guard let metadata = metadata else {
|
||||
return
|
||||
}
|
||||
self.pullIconURL(from: metadata, homePageURL: homePageURL, feed: feed)
|
||||
}
|
||||
self.pullIconURL(from: metadata, homePageURL: homePageURL, feed: feed)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user