diff --git a/Account/Sources/Account/LocalAccountRefresher.swift b/Account/Sources/Account/LocalAccountRefresher.swift index c8724e47f..a3785a6f6 100644 --- a/Account/Sources/Account/LocalAccountRefresher.swift +++ b/Account/Sources/Account/LocalAccountRefresher.swift @@ -101,36 +101,37 @@ extension LocalAccountRefresher: DownloadSessionDelegate { } let parserData = ParserData(url: feed.url, data: data) - FeedParser.parse(parserData) { (parsedFeed, error) in - - guard let account = feed.account, let parsedFeed = parsedFeed, error == nil else { - completion() - self.delegate?.localAccountRefresher(self, requestCompletedFor: feed) - return - } - - Task { @MainActor in + Task { @MainActor in - do { - let articleChanges = try await account.update(feed: feed, with: parsedFeed) + do { + let parsedFeed = try await FeedParser.parse(parserData) - if let httpResponse = response as? HTTPURLResponse { - feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse) - } - feed.contentHash = dataHash - - self.delegate?.localAccountRefresher(self, requestCompletedFor: feed) - self.delegate?.localAccountRefresher(self, articleChanges: articleChanges) { - completion() - } - - } catch { + guard let account = feed.account, let parsedFeed else { completion() self.delegate?.localAccountRefresher(self, requestCompletedFor: feed) + return } + + + let articleChanges = try await account.update(feed: feed, with: parsedFeed) + + if let httpResponse = response as? HTTPURLResponse { + feed.conditionalGetInfo = HTTPConditionalGetInfo(urlResponse: httpResponse) + } + feed.contentHash = dataHash + + self.delegate?.localAccountRefresher(self, requestCompletedFor: feed) + self.delegate?.localAccountRefresher(self, articleChanges: articleChanges) { + completion() + } + + } catch { + completion() + self.delegate?.localAccountRefresher(self, requestCompletedFor: feed) } } } + func downloadSession(_ downloadSession: DownloadSession, shouldContinueAfterReceivingData data: Data, representedObject: AnyObject) -> Bool { let feed = representedObject as! Feed diff --git a/LocalAccount/Sources/LocalAccount/InitialFeedDownloader.swift b/LocalAccount/Sources/LocalAccount/InitialFeedDownloader.swift index c2b38a2dd..8c1a92ef8 100644 --- a/LocalAccount/Sources/LocalAccount/InitialFeedDownloader.swift +++ b/LocalAccount/Sources/LocalAccount/InitialFeedDownloader.swift @@ -23,14 +23,25 @@ public struct InitialFeedDownloader { @MainActor public static func download(_ url: URL,_ completion: @escaping @Sendable (_ parsedFeed: ParsedFeed?) -> Void) { - downloadUsingCache(url) { (data, response, error) in - guard let data = data else { + Task { + + guard let downloadData = try? await downloadUsingCache(url) else { + completion(nil) + return + } + guard let data = downloadData.data else { completion(nil) return } let parserData = ParserData(url: url.absoluteString, data: data) - FeedParser.parse(parserData) { (parsedFeed, error) in + + Task.detached { + guard let parsedFeed = try? await FeedParser.parse(parserData) else { + completion(nil) + return + } + completion(parsedFeed) } } diff --git a/Parser/Sources/ObjC/ParserData.h b/Parser/Sources/ObjC/ParserData.h index 30517d98d..fe4885144 100644 --- a/Parser/Sources/ObjC/ParserData.h +++ b/Parser/Sources/ObjC/ParserData.h @@ -10,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN +__attribute__((swift_attr("@Sendable"))) @interface ParserData : NSObject @property (nonatomic, readonly) NSString *url; diff --git a/Parser/Sources/Swift/Feeds/FeedParser.swift b/Parser/Sources/Swift/Feeds/FeedParser.swift index a6aaacc3a..adf859a26 100644 --- a/Parser/Sources/Swift/Feeds/FeedParser.swift +++ b/Parser/Sources/Swift/Feeds/FeedParser.swift @@ -12,12 +12,8 @@ import ParserObjC // FeedParser handles RSS, Atom, JSON Feed, and RSS-in-JSON. // You don’t need to know the type of feed. -public typealias FeedParserCallback = (_ parsedFeed: ParsedFeed?, _ error: Error?) -> Void - public struct FeedParser { - private static let parseQueue = DispatchQueue(label: "FeedParser parse queue") - public static func canParse(_ parserData: ParserData) -> Bool { let type = feedType(parserData) @@ -30,62 +26,26 @@ public struct FeedParser { } } - public static func mightBeAbleToParseBasedOnPartialData(_ parserData: ParserData) -> Bool { + public static func parse(_ parserData: ParserData) async throws -> ParsedFeed? { - let type = feedType(parserData, isPartialData: true) + let type = feedType(parserData) switch type { - case .jsonFeed, .rssInJSON, .rss, .atom, .unknown: - return true - default: - return false - } - } - public static func parse(_ parserData: ParserData) throws -> ParsedFeed? { + case .jsonFeed: + return try JSONFeedParser.parse(parserData) - // This is generally fast enough to call on the main thread — - // but it’s probably a good idea to use a background queue if - // you might be doing a lot of parsing. (Such as in a feed reader.) + case .rssInJSON: + return try RSSInJSONParser.parse(parserData) - do { - let type = feedType(parserData) + case .rss: + return RSSParser.parse(parserData) - switch type { + case .atom: + return AtomParser.parse(parserData) - case .jsonFeed: - return try JSONFeedParser.parse(parserData) - - case .rssInJSON: - return try RSSInJSONParser.parse(parserData) - - case .rss: - return RSSParser.parse(parserData) - - case .atom: - return AtomParser.parse(parserData) - - case .unknown, .notAFeed: - return nil - } - } - catch { throw error } - } - - public static func parse(_ parserData: ParserData, _ completion: @escaping FeedParserCallback) { - - parseQueue.async { - do { - let parsedFeed = try parse(parserData) - DispatchQueue.main.async { - completion(parsedFeed, nil) - } - } - catch { - DispatchQueue.main.async { - completion(nil, error) - } - } + case .unknown, .notAFeed: + return nil } } }