Continue fixing concurrency warnings.

This commit is contained in:
Brent Simmons
2024-03-19 23:05:30 -07:00
parent 6ab10e871c
commit d0760f3d12
64 changed files with 444 additions and 459 deletions

View File

@@ -22,7 +22,7 @@ import RSParser
// Else,
// display error sheet.
class AddFeedController: AddFeedWindowControllerDelegate {
@MainActor final class AddFeedController: AddFeedWindowControllerDelegate {
private let hostWindow: NSWindow
private var addFeedWindowController: AddFeedWindowController?

View File

@@ -11,7 +11,7 @@ import RSCore
import RSTree
import Account
class FolderTreeMenu {
@MainActor final class FolderTreeMenu {
static func createFolderPopupMenu(with rootNode: Node, restrictToSpecialAccounts: Bool = false) -> NSMenu {

View File

@@ -16,31 +16,32 @@ class DetailIconSchemeHandler: NSObject, WKURLSchemeHandler {
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
guard let responseURL = urlSchemeTask.request.url, let iconImage = self.currentArticle?.iconImage() else {
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
Task { @MainActor in
let iconView = IconView(frame: CGRect(x: 0, y: 0, width: 48, height: 48))
iconView.iconImage = iconImage
let renderedImage = iconView.asImage()
guard let data = renderedImage.dataRepresentation() else {
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
let headerFields = ["Cache-Control": "no-cache"]
if let response = HTTPURLResponse(url: responseURL, statusCode: 200, httpVersion: nil, headerFields: headerFields) {
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(data)
urlSchemeTask.didFinish()
}
guard let responseURL = urlSchemeTask.request.url, let iconImage = self.currentArticle?.iconImage() else {
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
let iconView = IconView(frame: CGRect(x: 0, y: 0, width: 48, height: 48))
iconView.iconImage = iconImage
let renderedImage = iconView.asImage()
guard let data = renderedImage.dataRepresentation() else {
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
let headerFields = ["Cache-Control": "no-cache"]
if let response = HTTPURLResponse(url: responseURL, statusCode: 200, httpVersion: nil, headerFields: headerFields) {
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(data)
urlSchemeTask.didFinish()
}
}
}
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
urlSchemeTask.didFailWithError(URLError(.unknown))
}
}

View File

@@ -216,13 +216,26 @@ final class DetailWebViewController: NSViewController {
extension DetailWebViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
nonisolated func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == MessageName.windowDidScroll {
windowScrollY = message.body as? CGFloat
let updatedWindowScrollY = message.body as? CGFloat
Task { @MainActor in
windowScrollY = updatedWindowScrollY
}
} else if message.name == MessageName.mouseDidEnter, let link = message.body as? String {
delegate?.mouseDidEnter(self, link: link)
Task { @MainActor in
delegate?.mouseDidEnter(self, link: link)
}
} else if message.name == MessageName.mouseDidExit {
delegate?.mouseDidExit(self)
Task { @MainActor in
delegate?.mouseDidExit(self)
}
}
}
}
@@ -239,10 +252,13 @@ extension DetailWebViewController: WKNavigationDelegate, WKUIDelegate {
// WKNavigationDelegate
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
nonisolated public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.navigationType == .linkActivated {
if let url = navigationAction.request.url {
self.openInBrowser(url, flags: navigationAction.modifierFlags)
let flags = navigationAction.modifierFlags
Task { @MainActor in
self.openInBrowser(url, flags: flags)
}
}
decisionHandler(.cancel)
return
@@ -251,35 +267,42 @@ extension DetailWebViewController: WKNavigationDelegate, WKUIDelegate {
decisionHandler(.allow)
}
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// See note in viewDidLoad()
if waitingForFirstReload {
assert(webView.isHidden)
waitingForFirstReload = false
reloadHTML()
nonisolated public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// Waiting for the first navigation to complete isn't long enough to avoid the flash of white.
// A hard coded value is awful, but 5/100th of a second seems to be enough.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
webView.isHidden = false
}
} else {
if let windowScrollY = windowScrollY {
webView.evaluateJavaScript("window.scrollTo(0, \(windowScrollY));")
self.windowScrollY = nil
Task { @MainActor in
// See note in viewDidLoad()
if waitingForFirstReload {
assert(webView.isHidden)
waitingForFirstReload = false
reloadHTML()
// Waiting for the first navigation to complete isn't long enough to avoid the flash of white.
// A hard coded value is awful, but 5/100th of a second seems to be enough.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
webView.isHidden = false
}
} else {
if let windowScrollY = windowScrollY {
_ = try? await webView.evaluateJavaScript("window.scrollTo(0, \(windowScrollY));")
self.windowScrollY = nil
}
}
}
}
// WKUIDelegate
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
nonisolated func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
// This method is reached when WebKit handles a JavaScript based window.open() invocation, for example. One
// example where this is used is in YouTube's embedded video player when a user clicks on the video's title
// or on the "Watch in YouTube" button. For our purposes we'll handle such window.open calls the same way we
// handle clicks on a URL.
if let url = navigationAction.request.url {
self.openInBrowser(url, flags: navigationAction.modifierFlags)
let flags = navigationAction.modifierFlags
Task { @MainActor in
self.openInBrowser(url, flags: flags)
}
}
return nil

View File

@@ -115,16 +115,17 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
return sidebarViewController?.selectedObjects
}
func handle(_ response: UNNotificationResponse) {
let userInfo = response.notification.request.content.userInfo
guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any] else { return }
sidebarViewController?.deepLinkRevealAndSelect(for: articlePathUserInfo)
currentTimelineViewController?.goToDeepLink(for: articlePathUserInfo)
func handle(articlePathInfo: ArticlePathInfo) {
sidebarViewController?.deepLinkRevealAndSelect(for: articlePathInfo)
currentTimelineViewController?.goToDeepLink(for: articlePathInfo)
}
func handle(_ activity: NSUserActivity) {
guard let userInfo = activity.userInfo else { return }
guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any] else { return }
guard let userInfo = activity.userInfo, let articlePathUserInfo = ArticlePathInfo(userInfo: userInfo) else {
return
}
sidebarViewController?.deepLinkRevealAndSelect(for: articlePathUserInfo)
currentTimelineViewController?.goToDeepLink(for: articlePathUserInfo)
}

View File

@@ -10,7 +10,7 @@ import AppKit
import Account
import UniformTypeIdentifiers
struct NNW3ImportController {
@MainActor struct NNW3ImportController {
/// Import NNW3 subscriptions if they exist.
/// Return true if Subscriptions.plist was found and subscriptions were imported.

View File

@@ -11,7 +11,7 @@ import RSCore
// image - title - unreadCount
struct SidebarCellLayout {
@MainActor struct SidebarCellLayout {
let faviconRect: CGRect
let titleRect: CGRect

View File

@@ -10,7 +10,7 @@ import AppKit
import RSTree
import Account
enum SidebarDeleteItemsAlert {
@MainActor struct SidebarDeleteItemsAlert {
/// Builds a delete confirmation dialog for the supplied nodes
static func build(_ nodes: [Node]) -> NSAlert {

View File

@@ -12,7 +12,7 @@ import Articles
import RSCore
import Account
@objc final class SidebarOutlineDataSource: NSObject, NSOutlineViewDataSource {
@objc @MainActor final class SidebarOutlineDataSource: NSObject, NSOutlineViewDataSource {
let treeController: TreeController
static let dragOperationNone = NSDragOperation(rawValue: 0)

View File

@@ -17,13 +17,13 @@ extension Notification.Name {
}
protocol SidebarDelegate: AnyObject {
func sidebarSelectionDidChange(_: SidebarViewController, selectedObjects: [AnyObject]?)
func unreadCount(for: AnyObject) -> Int
func sidebarInvalidatedRestorationState(_: SidebarViewController)
@MainActor func sidebarSelectionDidChange(_: SidebarViewController, selectedObjects: [AnyObject]?)
@MainActor func unreadCount(for: AnyObject) -> Int
@MainActor func sidebarInvalidatedRestorationState(_: SidebarViewController)
}
@objc class SidebarViewController: NSViewController, NSOutlineViewDelegate, NSMenuDelegate, UndoableCommandRunner {
@objc @MainActor class SidebarViewController: NSViewController, NSOutlineViewDelegate, NSMenuDelegate, UndoableCommandRunner {
@IBOutlet weak var outlineView: NSOutlineView!
weak var delegate: SidebarDelegate?
@@ -483,9 +483,9 @@ protocol SidebarDelegate: AnyObject {
revealAndSelectRepresentedObject(feed as AnyObject)
}
func deepLinkRevealAndSelect(for userInfo: [AnyHashable : Any]) {
guard let accountNode = findAccountNode(userInfo),
let feedNode = findFeedNode(userInfo, beginningAt: accountNode),
func deepLinkRevealAndSelect(for articlePathInfo: ArticlePathInfo) {
guard let accountNode = findAccountNode(articlePathInfo),
let feedNode = findFeedNode(articlePathInfo, beginningAt: accountNode),
let feed = feedNode.representedObject as? SidebarItem else {
return
}
@@ -738,16 +738,17 @@ private extension SidebarViewController {
return nil
}
func findAccountNode(_ userInfo: [AnyHashable : Any]?) -> Node? {
guard let accountID = userInfo?[ArticlePathKey.accountID] as? String else {
func findAccountNode(_ articlePathInfo: ArticlePathInfo) -> Node? {
guard let accountID = articlePathInfo.accountID else {
return nil
}
if let node = treeController.rootNode.descendantNode(where: { ($0.representedObject as? Account)?.accountID == accountID }) {
return node
}
guard let accountName = userInfo?[ArticlePathKey.accountName] as? String else {
guard let accountName = articlePathInfo.accountName else {
return nil
}
@@ -758,8 +759,8 @@ private extension SidebarViewController {
return nil
}
func findFeedNode(_ userInfo: [AnyHashable : Any]?, beginningAt startingNode: Node) -> Node? {
guard let feedID = userInfo?[ArticlePathKey.feedID] as? String else {
func findFeedNode(_ articlePathInfo: ArticlePathInfo, beginningAt startingNode: Node) -> Node? {
guard let feedID = articlePathInfo.feedID else {
return nil
}
if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.feedID == feedID }) {

View File

@@ -8,7 +8,7 @@
import AppKit
class UnreadCountView : NSView {
@MainActor final class UnreadCountView : NSView {
struct Appearance {
static let padding = NSEdgeInsets(top: 1.0, left: 7.0, bottom: 1.0, right: 7.0)

View File

@@ -11,12 +11,13 @@ import Articles
import RSCore
extension Article: PasteboardWriterOwner {
public var pasteboardWriter: NSPasteboardWriting {
@MainActor public var pasteboardWriter: NSPasteboardWriting {
return ArticlePasteboardWriter(article: self)
}
}
@objc final class ArticlePasteboardWriter: NSObject, NSPasteboardWriting {
@objc @MainActor final class ArticlePasteboardWriter: NSObject, NSPasteboardWriting {
let article: Article
static let articleUTI = "com.ranchero.article"

View File

@@ -26,7 +26,7 @@ struct TextFieldSizeInfo {
let numberOfLinesUsed: Int // A two-line text field may only use one line, for instance. This would equal 1, then.
}
final class MultilineTextFieldSizer {
@MainActor final class MultilineTextFieldSizer {
private let numberOfLines: Int
private let font: NSFont
@@ -35,7 +35,7 @@ final class MultilineTextFieldSizer {
private let doubleLineHeightEstimate: Int
private var cache = [String: WidthHeightCache]() // Each string has a cache.
private var attributedCache = [NSAttributedString: WidthHeightCache]()
private static var sizers = [TextFieldSizerSpecifier: MultilineTextFieldSizer]()
@MainActor private static var sizers = [TextFieldSizerSpecifier: MultilineTextFieldSizer]()
private init(numberOfLines: Int, font: NSFont) {

View File

@@ -12,7 +12,7 @@ import AppKit
// Uses a cache.
// Main thready only.
final class SingleLineTextFieldSizer {
@MainActor final class SingleLineTextFieldSizer {
let font: NSFont
private let textField: NSTextField

View File

@@ -9,7 +9,7 @@
import AppKit
import Articles
struct TimelineCellData {
@MainActor struct TimelineCellData {
private static let noText = NSLocalizedString("(No Text)", comment: "No Text")

View File

@@ -9,7 +9,7 @@
import AppKit
import RSCore
struct TimelineCellLayout {
@MainActor struct TimelineCellLayout {
let width: CGFloat
let height: CGFloat

View File

@@ -11,10 +11,10 @@ import Account
import Articles
protocol TimelineContainerViewControllerDelegate: AnyObject {
func timelineSelectionDidChange(_: TimelineContainerViewController, articles: [Article]?, mode: TimelineSourceMode)
func timelineRequestedFeedSelection(_: TimelineContainerViewController, feed: Feed)
func timelineInvalidatedRestorationState(_: TimelineContainerViewController)
@MainActor func timelineSelectionDidChange(_: TimelineContainerViewController, articles: [Article]?, mode: TimelineSourceMode)
@MainActor func timelineRequestedFeedSelection(_: TimelineContainerViewController, feed: Feed)
@MainActor func timelineInvalidatedRestorationState(_: TimelineContainerViewController)
}
final class TimelineContainerViewController: NSViewController {

View File

@@ -13,9 +13,10 @@ import Account
import os.log
protocol TimelineDelegate: AnyObject {
func timelineSelectionDidChange(_: TimelineViewController, selectedArticles: [Article]?)
func timelineRequestedFeedSelection(_: TimelineViewController, feed: Feed)
func timelineInvalidatedRestorationState(_: TimelineViewController)
@MainActor func timelineSelectionDidChange(_: TimelineViewController, selectedArticles: [Article]?)
@MainActor func timelineRequestedFeedSelection(_: TimelineViewController, feed: Feed)
@MainActor func timelineInvalidatedRestorationState(_: TimelineViewController)
}
enum TimelineShowFeedName {
@@ -535,12 +536,15 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
// MARK: - Navigation
func goToDeepLink(for userInfo: [AnyHashable : Any]) {
guard let articleID = userInfo[ArticlePathKey.articleID] as? String else { return }
func goToDeepLink(for articlePathInfo: ArticlePathInfo) {
guard let articleID = articlePathInfo.articleID else {
return
}
Task {
if isReadFiltered ?? false {
if let accountName = userInfo[ArticlePathKey.accountName] as? String,
if let accountName = articlePathInfo.accountName,
let account = AccountManager.shared.existingActiveAccount(forDisplayName: accountName) {
exceptionArticleFetcher = SingleArticleFetcher(account: account, articleID: articleID)
await fetchAndReplaceArticles()