Continue adopting MainActor.

This commit is contained in:
Brent Simmons
2023-07-09 11:34:56 -07:00
parent f7afdfc6c4
commit b49731cc34
38 changed files with 204 additions and 170 deletions

View File

@@ -9,7 +9,7 @@
import AppKit
import RSCore
@objc final class DetailKeyboardDelegate: NSObject, KeyboardDelegate {
@MainActor @objc final class DetailKeyboardDelegate: NSObject, KeyboardDelegate {
let shortcuts: Set<KeyboardShortcut>

View File

@@ -9,7 +9,7 @@
import AppKit
import RSCore
final class MainWindowKeyboardHandler: KeyboardDelegate {
@MainActor final class MainWindowKeyboardHandler: KeyboardDelegate {
static let shared = MainWindowKeyboardHandler()
let globalShortcuts: Set<KeyboardShortcut>

View File

@@ -12,8 +12,8 @@ import RSCore
@objc final class SharingServicePickerDelegate: NSObject, NSSharingServicePickerDelegate {
private let sharingServiceDelegate: SharingServiceDelegate
init(_ window: NSWindow?) {
@MainActor init(_ window: NSWindow?) {
sharingServiceDelegate = SharingServiceDelegate(window)
}
@@ -27,14 +27,14 @@ import RSCore
}
static func customSharingServices(for items: [Any]) -> [NSSharingService] {
guard let object = items.first else {
return [NSSharingService]()
}
let customServices: [SendToCommand] = [SendToMarsEditCommand(), SendToMicroBlogCommand()]
return customServices.compactMap { (sendToCommand) -> NSSharingService? in
guard let object = items.first else {
return nil
}
guard sendToCommand.canSendObject(object, selectedText: nil) else {
return nil
}

View File

@@ -9,7 +9,7 @@
import AppKit
import RSCore
@objc final class SidebarKeyboardDelegate: NSObject, KeyboardDelegate {
@MainActor @objc final class SidebarKeyboardDelegate: NSObject, KeyboardDelegate {
@IBOutlet weak var sidebarViewController: SidebarViewController?
let shortcuts: Set<KeyboardShortcut>

View File

@@ -181,7 +181,7 @@ extension Feed: PasteboardWriterOwner {
return [FeedPasteboardWriter.feedUTIType, .URL, .string, FeedPasteboardWriter.feedUTIInternalType]
}
func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
@MainActor func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
let plist: Any?
@@ -204,15 +204,15 @@ extension Feed: PasteboardWriterOwner {
private extension FeedPasteboardWriter {
var pasteboardFeed: PasteboardFeed {
@MainActor var pasteboardFeed: PasteboardFeed {
return PasteboardFeed(url: feed.url, feedID: feed.feedID, homePageURL: feed.homePageURL, name: feed.name, editedName: feed.editedName, accountID: feed.account?.accountID, accountType: feed.account?.type)
}
var exportDictionary: PasteboardFeedDictionary {
@MainActor var exportDictionary: PasteboardFeedDictionary {
return pasteboardFeed.exportDictionary()
}
var internalDictionary: PasteboardFeedDictionary {
@MainActor var internalDictionary: PasteboardFeedDictionary {
var dictionary = pasteboardFeed.internalDictionary()
if dictionary[PasteboardFeed.Key.containerName] == nil,
case let .folder(accountID, folderName) = containerID {

View File

@@ -109,7 +109,7 @@ extension Folder: PasteboardWriterOwner {
return [.string, FolderPasteboardWriter.folderUTIInternalType]
}
func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
@MainActor func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
let plist: Any?
@@ -127,11 +127,11 @@ extension Folder: PasteboardWriterOwner {
}
private extension FolderPasteboardWriter {
var pasteboardFolder: PasteboardFolder {
@MainActor var pasteboardFolder: PasteboardFolder {
return PasteboardFolder(name: folder.name ?? "", folderID: String(folder.folderID), accountID: folder.account?.accountID)
}
var internalDictionary: PasteboardFeedDictionary {
@MainActor var internalDictionary: PasteboardFeedDictionary {
return pasteboardFolder.internalDictionary()
}
}

View File

@@ -12,7 +12,7 @@ import Articles
import RSCore
import Account
@objc final class SidebarOutlineDataSource: NSObject, NSOutlineViewDataSource {
@MainActor @objc final class SidebarOutlineDataSource: NSObject, NSOutlineViewDataSource {
let treeController: TreeController
static let dragOperationNone = NSDragOperation(rawValue: 0)
@@ -250,7 +250,7 @@ private extension SidebarOutlineDataSource {
}
}
func accountForNode(_ node: Node) -> Account? {
@MainActor func accountForNode(_ node: Node) -> Account? {
if let account = node.representedObject as? Account {
return account
}
@@ -263,7 +263,7 @@ private extension SidebarOutlineDataSource {
return nil
}
func commonAccountsFor(_ nodes: Set<Node>) -> Set<Account> {
@MainActor func commonAccountsFor(_ nodes: Set<Node>) -> Set<Account> {
var accounts = Set<Account>()
for node in nodes {
guard let oneAccount = accountForNode(node) else {
@@ -274,7 +274,7 @@ private extension SidebarOutlineDataSource {
return accounts
}
func accountHasFolderRepresentingAnyDraggedFolders(_ account: Account, _ draggedFolders: Set<PasteboardFolder>) -> Bool {
@MainActor func accountHasFolderRepresentingAnyDraggedFolders(_ account: Account, _ draggedFolders: Set<PasteboardFolder>) -> Bool {
for draggedFolder in draggedFolders {
if account.existingFolder(with: draggedFolder.name) != nil {
return true
@@ -283,7 +283,7 @@ private extension SidebarOutlineDataSource {
return false
}
func validateLocalFolderDrop(_ outlineView: NSOutlineView, _ draggedFolder: PasteboardFolder, _ parentNode: Node, _ index: Int) -> NSDragOperation {
@MainActor func validateLocalFolderDrop(_ outlineView: NSOutlineView, _ draggedFolder: PasteboardFolder, _ parentNode: Node, _ index: Int) -> NSDragOperation {
guard let dropAccount = parentNode.representedObject as? Account, dropAccount.accountID != draggedFolder.accountID else {
return SidebarOutlineDataSource.dragOperationNone
}
@@ -297,7 +297,7 @@ private extension SidebarOutlineDataSource {
return .copy // different AccountIDs means can only copy
}
func validateLocalFoldersDrop(_ outlineView: NSOutlineView, _ draggedFolders: Set<PasteboardFolder>, _ parentNode: Node, _ index: Int) -> NSDragOperation {
@MainActor func validateLocalFoldersDrop(_ outlineView: NSOutlineView, _ draggedFolders: Set<PasteboardFolder>, _ parentNode: Node, _ index: Int) -> NSDragOperation {
guard let dropAccount = parentNode.representedObject as? Account else {
return SidebarOutlineDataSource.dragOperationNone
}
@@ -315,7 +315,7 @@ private extension SidebarOutlineDataSource {
return .copy // different AccountIDs means can only copy
}
func copyFeedInAccount(_ feed: Feed, _ destination: Container ) {
@MainActor func copyFeedInAccount(_ feed: Feed, _ destination: Container ) {
destination.account?.addFeed(feed, to: destination) { result in
switch result {
case .success:
@@ -326,7 +326,7 @@ private extension SidebarOutlineDataSource {
}
}
func moveFeedInAccount(_ feed: Feed, _ source: Container, _ destination: Container) {
@MainActor func moveFeedInAccount(_ feed: Feed, _ source: Container, _ destination: Container) {
BatchUpdate.shared.start()
source.account?.moveFeed(feed, from: source, to: destination) { result in
BatchUpdate.shared.end()
@@ -339,7 +339,7 @@ private extension SidebarOutlineDataSource {
}
}
func copyFeedBetweenAccounts(_ feed: Feed, _ destinationContainer: Container) {
@MainActor func copyFeedBetweenAccounts(_ feed: Feed, _ destinationContainer: Container) {
guard let destinationAccount = destinationContainer.account else {
return
}

View File

@@ -24,7 +24,7 @@ extension Article: PasteboardWriterOwner {
static let articleUTIInternal = "com.ranchero.NetNewsWire-Evergreen.internal.article"
static let articleUTIInternalType = NSPasteboard.PasteboardType(rawValue: articleUTIInternal)
private lazy var renderedHTML: String = {
@MainActor private lazy var renderedHTML: String = {
let rendering = ArticleRenderer.articleHTML(article: article, theme: ArticleThemesManager.shared.currentTheme)
return rendering.html
}()
@@ -46,7 +46,7 @@ extension Article: PasteboardWriterOwner {
return types
}
func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
@MainActor func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
let plist: Any?
switch type {
@@ -70,7 +70,7 @@ extension Article: PasteboardWriterOwner {
private extension ArticlePasteboardWriter {
func plainText() -> String {
@MainActor func plainText() -> String {
var s = ""
if let title = article.title {
@@ -137,7 +137,7 @@ private extension ArticlePasteboardWriter {
static let accountID = "accountID"
}
func exportDictionary() -> [String: Any] {
@MainActor func exportDictionary() -> [String: Any] {
var d = [String: Any]()
d[Key.articleID] = article.articleID
@@ -163,7 +163,7 @@ private extension ArticlePasteboardWriter {
return d
}
func internalDictionary() -> [String: Any] {
@MainActor func internalDictionary() -> [String: Any] {
var d = exportDictionary()
d[Key.accountID] = article.accountID
return d

View File

@@ -11,7 +11,7 @@ import RSCore
// Doesnt have any shortcuts of its own  theyre all in MainWindowKeyboardHandler.
@objc final class TimelineKeyboardDelegate: NSObject, KeyboardDelegate {
@MainActor @objc final class TimelineKeyboardDelegate: NSObject, KeyboardDelegate {
@IBOutlet weak var timelineViewController: TimelineViewController?
let shortcuts: Set<KeyboardShortcut>

View File

@@ -162,7 +162,7 @@ struct AddAccountsView: View {
}
var icloudAccount: some View {
@MainActor var icloudAccount: some View {
VStack(alignment: .leading) {
Text("label.text.cloudkit", comment: "iCloud")
.font(.headline)
@@ -261,7 +261,7 @@ struct AddAccountsView: View {
}
}
private func isCloudInUse() -> Bool {
@MainActor private func isCloudInUse() -> Bool {
AccountManager.shared.accounts.contains(where: { $0.type == .cloudKit })
}

View File

@@ -12,7 +12,7 @@ import Articles
import RSCore
@objc(ScriptableAccount)
class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
@MainActor class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
let account:Account
init (_ account:Account) {

View File

@@ -11,7 +11,7 @@ import Account
import Articles
@objc(ScriptableArticle)
class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
@MainActor class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
let article:Article
let container:ScriptingObjectContainer
@@ -111,7 +111,9 @@ class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
return article.status.boolStatus(forKey:.read)
}
set {
markArticles([self.article], statusKey: .read, flag: newValue)
Task { @MainActor in
markArticles([self.article], statusKey: .read, flag: newValue)
}
}
}
@@ -121,7 +123,9 @@ class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
return article.status.boolStatus(forKey:.starred)
}
set {
markArticles([self.article], statusKey: .starred, flag: newValue)
Task { @MainActor in
markArticles([self.article], statusKey: .starred, flag: newValue)
}
}
}

View File

@@ -50,7 +50,7 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
return self.classDescription as! NSScriptClassDescription
}
func deleteElement(_ element:ScriptingObject) {
@MainActor func deleteElement(_ element:ScriptingObject) {
if let scriptableFeed = element as? ScriptableFeed {
BatchUpdate.shared.perform {
folder.account?.removeFeed(scriptableFeed.feed, from: folder) { result in }
@@ -65,38 +65,43 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
or
tell account X to make new folder at end with properties {name:"new folder name"}
*/
class func handleCreateElement(command:NSCreateCommand) -> Any? {
class func handleCreateElement(command:NSCreateCommand) -> Any? {
guard command.isCreateCommand(forClass:"fold") else { return nil }
let name = command.property(forKey:"name") as? String ?? ""
// some combination of the tell target and the location specifier ("in" or "at")
// identifies where the new folder should be created
let (account, folder) = command.accountAndFolderForNewChild()
guard folder == nil else {
print("support for folders within folders is NYI");
return nil
}
command.suspendExecution()
account.addFolder(name) { result in
switch result {
case .success(let folder):
let scriptableAccount = ScriptableAccount(account)
let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount)
command.resumeExecution(withResult:scriptableFolder.objectSpecifier)
case .failure:
Task { @MainActor in
// some combination of the tell target and the location specifier ("in" or "at")
// identifies where the new folder should be created
let (account, folder) = command.accountAndFolderForNewChild()
guard folder == nil else {
print("NetNewsWire does not support creating folders within folders.");
command.resumeExecution(withResult:nil)
return
}
account.addFolder(name) { result in
switch result {
case .success(let folder):
let scriptableAccount = ScriptableAccount(account)
let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount)
command.resumeExecution(withResult:scriptableFolder.objectSpecifier)
case .failure:
command.resumeExecution(withResult:nil)
}
}
}
return nil
}
// MARK: --- Scriptable elements ---
@objc(feeds)
var feeds:NSArray {
@MainActor var feeds:NSArray {
let feeds = Array(folder.topLevelFeeds)
return feeds.map { ScriptableFeed($0, container:self) } as NSArray
}

View File

@@ -26,7 +26,7 @@ extension NSScriptCommand {
return true
}
func accountAndFolderForNewChild() -> (Account, Folder?) {
@MainActor func accountAndFolderForNewChild() -> (Account, Folder?) {
let appleEvent = self.appleEvent
var account = AccountManager.shared.defaultAccount
var folder:Folder? = nil

View File

@@ -71,7 +71,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
return url
}
class func scriptableFeed(_ feed: Feed, account: Account, folder: Folder?) -> ScriptableFeed {
@MainActor class func scriptableFeed(_ feed: Feed, account: Account, folder: Folder?) -> ScriptableFeed {
let scriptableAccount = ScriptableAccount(account)
if let folder = folder {
let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount)
@@ -85,33 +85,39 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
guard command.isCreateCommand(forClass:"Feed") else { return nil }
guard let arguments = command.arguments else {return nil}
let titleFromArgs = command.property(forKey:"name") as? String
let (account, folder) = command.accountAndFolderForNewChild()
guard let url = self.urlForNewFeed(arguments:arguments) else {return nil}
if let existingFeed = account.existingFeed(withURL:url) {
return scriptableFeed(existingFeed, account:account, folder:folder).objectSpecifier
}
let container: Container = folder != nil ? folder! : account
// We need to download the feed and parse it.
// RSParser does the callback for the download on the main thread.
// Because we can't wait here (on the main thread) for the callback, we have to return from this function.
// Generally, returning from an AppleEvent handler function means that handling the Apple event is over,
// but we dont yet have the result of the event yet, so we prevent the Apple event from returning by calling
// suspendExecution(). When we get the callback, we supply the event result and call resumeExecution().
command.suspendExecution()
account.createFeed(url: url, name: titleFromArgs, container: container, validateFeed: true) { result in
switch result {
case .success(let feed):
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
let scriptableFeed = self.scriptableFeed(feed, account:account, folder:folder)
command.resumeExecution(withResult:scriptableFeed.objectSpecifier)
case .failure:
// We need to download the feed and parse it.
// RSParser does the callback for the download on the main thread.
// Because we can't wait here (on the main thread) for the callback, we have to return from this function.
// Generally, returning from an AppleEvent handler function means that handling the Apple event is over,
// but we dont yet have the result of the event yet, so we prevent the Apple event from returning by calling
// suspendExecution(). When we get the callback, we supply the event result and call resumeExecution().
command.suspendExecution()
Task { @MainActor in
let (account, folder) = command.accountAndFolderForNewChild()
guard let url = self.urlForNewFeed(arguments:arguments) else {
command.resumeExecution(withResult:nil)
return
}
if let existingFeed = account.existingFeed(withURL:url) {
let scriptableFeed = scriptableFeed(existingFeed, account:account, folder:folder)
command.resumeExecution(withResult:scriptableFeed.objectSpecifier)
return
}
let container: Container = folder != nil ? folder! : account
account.createFeed(url: url, name: titleFromArgs, container: container, validateFeed: true) { result in
switch result {
case .success(let feed):
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
let scriptableFeed = self.scriptableFeed(feed, account:account, folder:folder)
command.resumeExecution(withResult:scriptableFeed.objectSpecifier)
case .failure:
command.resumeExecution(withResult:nil)
}
}
}
return nil

View File

@@ -235,7 +235,7 @@ private extension ActivityManager {
}
#endif
func makeKeywords(_ article: Article) -> [String] {
@MainActor func makeKeywords(_ article: Article) -> [String] {
let feedNameKeywords = makeKeywords(article.feed?.nameForDisplay)
let articleTitleKeywords = makeKeywords(ArticleStringFormatter.truncatedTitle(article))
return feedNameKeywords + articleTitleKeywords
@@ -274,11 +274,11 @@ private extension ActivityManager {
activity.becomeCurrent()
}
static func identifier(for folder: Folder) -> String {
@MainActor static func identifier(for folder: Folder) -> String {
return "account_\(folder.account!.accountID)_folder_\(folder.nameForDisplay)"
}
static func identifier(for feed: Feed) -> String {
@MainActor static func identifier(for feed: Feed) -> String {
return "account_\(feed.account!.accountID)_feed_\(feed.feedID)"
}

View File

@@ -14,7 +14,7 @@ import RSCore
import Articles
import Account
struct ArticleRenderer {
@MainActor struct ArticleRenderer {
typealias Rendering = (style: String, html: String, title: String, baseURL: String)
@@ -330,7 +330,7 @@ private extension ArticleRenderer {
private extension Article {
var baseURL: URL? {
@MainActor var baseURL: URL? {
var s = link
if s == nil {
s = feed?.homePageURL

View File

@@ -12,7 +12,7 @@ import RSTree
import Account
import Articles
final class DeleteCommand: UndoableCommand {
@MainActor final class DeleteCommand: UndoableCommand {
let treeController: TreeController?
let undoManager: UndoManager
@@ -112,7 +112,7 @@ private struct SidebarItemSpecifier {
return nil
}
init?(node: Node, errorHandler: @escaping (Error) -> ()) {
@MainActor init?(node: Node, errorHandler: @escaping (Error) -> ()) {
var account: Account?
@@ -142,7 +142,7 @@ private struct SidebarItemSpecifier {
}
func delete(completion: @escaping () -> Void) {
@MainActor func delete(completion: @escaping () -> Void) {
if let feed {
@@ -170,7 +170,7 @@ private struct SidebarItemSpecifier {
}
}
func restore() {
@MainActor func restore() {
if let _ = feed {
restoreFeed()
@@ -180,7 +180,7 @@ private struct SidebarItemSpecifier {
}
}
private func restoreFeed() {
@MainActor private func restoreFeed() {
guard let account = account, let feed = feed, let container = path.resolveContainer() else {
return
@@ -194,7 +194,7 @@ private struct SidebarItemSpecifier {
}
private func restoreFolder() {
@MainActor private func restoreFolder() {
guard let account = account, let folder = folder else {
return

View File

@@ -23,7 +23,7 @@ public extension Notification.Name {
static let MarkStatusCommandDidUndoDirectMarking = Notification.Name("MarkStatusCommandDidUndoDirectMarking")
}
final class MarkStatusCommand: UndoableCommand {
@MainActor final class MarkStatusCommand: UndoableCommand {
let undoActionName: String
let redoActionName: String

View File

@@ -23,23 +23,25 @@ final class SendToMarsEditCommand: SendToCommand {
func sendObject(_ object: Any?, selectedText: String?) {
guard canSendObject(object, selectedText: selectedText) else {
return
}
guard let article = (object as? ArticlePasteboardWriter)?.article else {
return
}
guard let app = appToUse(), app.launchIfNeeded(), app.bringToFront() else {
return
}
Task { @MainActor in
guard canSendObject(object, selectedText: selectedText) else {
return
}
guard let article = (object as? ArticlePasteboardWriter)?.article else {
return
}
guard let app = appToUse(), app.launchIfNeeded(), app.bringToFront() else {
return
}
send(article, to: app)
send(article, to: app)
}
}
}
private extension SendToMarsEditCommand {
func send(_ article: Article, to app: UserApp) {
@MainActor func send(_ article: Article, to app: UserApp) {
// App has already been launched.

View File

@@ -31,36 +31,38 @@ final class SendToMicroBlogCommand: SendToCommand {
func sendObject(_ object: Any?, selectedText: String?) {
guard canSendObject(object, selectedText: selectedText) else {
return
}
guard let article = (object as? ArticlePasteboardWriter)?.article else {
return
}
guard microBlogApp.launchIfNeeded(), microBlogApp.bringToFront() else {
return
}
Task { @MainActor in
guard canSendObject(object, selectedText: selectedText) else {
return
}
guard let article = (object as? ArticlePasteboardWriter)?.article else {
return
}
guard microBlogApp.launchIfNeeded(), microBlogApp.bringToFront() else {
return
}
// TODO: get text from contentHTML or contentText if no title and no selectedText.
// TODO: consider selectedText.
// TODO: get text from contentHTML or contentText if no title and no selectedText.
// TODO: consider selectedText.
let s = article.attributionString + article.linkString
let s = article.attributionString + article.linkString
let urlQueryDictionary = ["text": s]
guard let urlQueryString = urlQueryDictionary.urlQueryString else {
return
let urlQueryDictionary = ["text": s]
guard let urlQueryString = urlQueryDictionary.urlQueryString else {
return
}
guard let url = URL(string: "microblog://post?" + urlQueryString) else {
return
}
NSWorkspace.shared.open(url)
}
guard let url = URL(string: "microblog://post?" + urlQueryString) else {
return
}
NSWorkspace.shared.open(url)
}
}
private extension Article {
var attributionString: String {
@MainActor var attributionString: String {
// Feed name, or feed name + author name (if author is specified per-article).
// Includes trailing space.

View File

@@ -9,7 +9,7 @@
import Foundation
import Account
struct AddFeedDefaultContainer {
@MainActor struct AddFeedDefaultContainer {
static var defaultContainer: Container? {

View File

@@ -13,7 +13,7 @@ import Account
// These handle multiple accounts.
func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: (() -> Void)? = nil) {
@MainActor func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: (() -> Void)? = nil) {
let d: [String: Set<Article>] = accountAndArticlesDictionary(articles)
let group = DispatchGroup()
@@ -93,7 +93,7 @@ extension Article {
return datePublished ?? dateModified ?? status.dateArrived
}
var isAvailableToMarkUnread: Bool {
@MainActor var isAvailableToMarkUnread: Bool {
guard let markUnreadWindow = account?.behaviors.compactMap( { behavior -> Int? in
switch behavior {
case .disallowMarkAsUnreadAfterPeriod(let days):
@@ -133,7 +133,7 @@ extension Article {
}
}
func byline() -> String {
@MainActor func byline() -> String {
guard let authors = authors ?? feed?.authors, !authors.isEmpty else {
return ""
}
@@ -194,7 +194,7 @@ struct ArticlePathKey {
extension Article {
public var pathUserInfo: [AnyHashable : Any] {
@MainActor public var pathUserInfo: [AnyHashable : Any] {
return [
ArticlePathKey.accountID: accountID,
ArticlePathKey.accountName: account?.nameForDisplay ?? "",
@@ -209,7 +209,7 @@ extension Article {
extension Article: SortableArticle {
var sortableName: String {
@MainActor var sortableName: String {
return feed?.name ?? ""
}

View File

@@ -18,7 +18,7 @@ extension Notification.Name {
static let FaviconDidBecomeAvailable = Notification.Name("FaviconDidBecomeAvailableNotification") // userInfo key: FaviconDownloader.UserInfoKey.faviconURL
}
final class FaviconDownloader: Logging {
@MainActor final class FaviconDownloader: Logging {
private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
@@ -280,11 +280,15 @@ private extension FaviconDownloader {
}
func queueSaveHomePageToFaviconURLCacheIfNeeded() {
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageToFaviconURLCacheIfNeeded))
Task { @MainActor in
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageToFaviconURLCacheIfNeeded))
}
}
func queueSaveHomePageURLsWithNoFaviconURLCacheIfNeeded() {
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageURLsWithNoFaviconURLCacheIfNeeded))
Task { @MainActor in
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageURLsWithNoFaviconURLCacheIfNeeded))
}
}
func saveHomePageToFaviconURLCache() {

View File

@@ -20,7 +20,7 @@ extension Notification.Name {
public final class FeedIconDownloader {
private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
@MainActor private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
private let imageDownloader: ImageDownloader
@@ -28,7 +28,9 @@ public final class FeedIconDownloader {
private var feedURLToIconURLCachePath: String
private var feedURLToIconURLCacheDirty = false {
didSet {
queueSaveFeedURLToIconURLCacheIfNeeded()
Task { @MainActor in
queueSaveFeedURLToIconURLCacheIfNeeded()
}
}
}
@@ -36,7 +38,9 @@ public final class FeedIconDownloader {
private var homePageToIconURLCachePath: String
private var homePageToIconURLCacheDirty = false {
didSet {
queueSaveHomePageToIconURLCacheIfNeeded()
Task { @MainActor in
queueSaveHomePageToIconURLCacheIfNeeded()
}
}
}
@@ -44,7 +48,9 @@ public final class FeedIconDownloader {
private var homePagesWithNoIconURLCachePath: String
private var homePagesWithNoIconURLCacheDirty = false {
didSet {
queueHomePagesWithNoIconURLCacheIfNeeded()
Task { @MainActor in
queueHomePagesWithNoIconURLCacheIfNeeded()
}
}
}
@@ -255,15 +261,15 @@ private extension FeedIconDownloader {
homePagesWithNoIconURLCache = Set(decoded)
}
func queueSaveFeedURLToIconURLCacheIfNeeded() {
@MainActor func queueSaveFeedURLToIconURLCacheIfNeeded() {
FeedIconDownloader.saveQueue.add(self, #selector(saveFeedURLToIconURLCacheIfNeeded))
}
func queueSaveHomePageToIconURLCacheIfNeeded() {
@MainActor func queueSaveHomePageToIconURLCacheIfNeeded() {
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded))
}
func queueHomePagesWithNoIconURLCacheIfNeeded() {
@MainActor func queueHomePagesWithNoIconURLCacheIfNeeded() {
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded))
}

View File

@@ -55,7 +55,7 @@ struct ExtensionAccount: ExtensionContainer {
let containerID: ContainerIdentifier?
let folders: [ExtensionFolder]
init(account: Account) {
@MainActor init(account: Account) {
self.name = account.nameForDisplay
self.accountID = account.accountID
self.type = account.type
@@ -84,7 +84,7 @@ struct ExtensionFolder: ExtensionContainer {
let name: String
let containerID: ContainerIdentifier?
init(folder: Folder) {
@MainActor init(folder: Folder) {
self.accountName = folder.account?.nameForDisplay ?? ""
self.accountID = folder.account?.accountID ?? ""
self.name = folder.nameForDisplay

View File

@@ -21,10 +21,12 @@ final class ExtensionContainersFile: Logging {
private var isDirty = false {
didSet {
queueSaveToDiskIfNeeded()
Task { @MainActor in
queueSaveToDiskIfNeeded()
}
}
}
private let saveQueue = CoalescingQueue(name: "Save Queue", interval: 0.5)
@MainActor private let saveQueue = CoalescingQueue(name: "Save Queue", interval: 0.5)
init() {
if !FileManager.default.fileExists(atPath: ExtensionContainersFile.filePath) {
@@ -66,7 +68,7 @@ private extension ExtensionContainersFile {
isDirty = true
}
func queueSaveToDiskIfNeeded() {
@MainActor func queueSaveToDiskIfNeeded() {
saveQueue.add(self, #selector(saveToDiskIfNeeded))
}

View File

@@ -28,7 +28,7 @@ final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter, Logging {
return operationQueue
}
override init() {
@MainActor override init() {
operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
@@ -46,7 +46,9 @@ final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter, Logging {
func resume() {
NSFileCoordinator.addFilePresenter(self)
process()
Task { @MainActor in
process()
}
}
func suspend() {
@@ -93,7 +95,7 @@ final class ExtensionFeedAddRequestFile: NSObject, NSFilePresenter, Logging {
private extension ExtensionFeedAddRequestFile {
func process() {
@MainActor func process() {
let decoder = PropertyListDecoder()
let encoder = PropertyListEncoder()
@@ -128,7 +130,7 @@ private extension ExtensionFeedAddRequestFile {
requests?.forEach { processRequest($0) }
}
func processRequest(_ request: ExtensionFeedAddRequest) {
@MainActor func processRequest(_ request: ExtensionFeedAddRequest) {
var destinationAccountID: String? = nil
switch request.destinationContainerID {
case .account(let accountID):

View File

@@ -107,7 +107,9 @@ extension SmartFeed: ArticleFetcher {
private extension SmartFeed {
func queueFetchUnreadCounts() {
CoalescingQueue.standard.add(self, #selector(fetchUnreadCounts))
Task { @MainActor in
CoalescingQueue.standard.add(self, #selector(fetchUnreadCounts))
}
}
@MainActor func fetchUnreadCount(for account: Account) {

View File

@@ -17,9 +17,9 @@ protocol SmartFeedDelegate: ItemIdentifiable, DisplayNameProvider, ArticleFetche
func fetchUnreadCount(for: Account, completion: @escaping SingleUnreadCountCompletionBlock)
}
extension SmartFeedDelegate {
@MainActor extension SmartFeedDelegate {
@MainActor func fetchArticles() throws -> Set<Article> {
func fetchArticles() throws -> Set<Article> {
return try AccountManager.shared.fetchArticles(fetchType)
}

View File

@@ -10,7 +10,7 @@ import Foundation
import RSCore
import Account
final class SmartFeedsController: DisplayNameProvider, ContainerIdentifiable {
@MainActor final class SmartFeedsController: DisplayNameProvider, ContainerIdentifiable {
var containerID: ContainerIdentifier? {
return ContainerIdentifier.smartFeedController

View File

@@ -67,7 +67,7 @@ extension Array where Element == Article {
return false
}
func anyArticleIsReadAndCanMarkUnread() -> Bool {
@MainActor func anyArticleIsReadAndCanMarkUnread() -> Bool {
return anyArticlePassesTest { $0.status.read && $0.isAvailableToMarkUnread }
}
@@ -95,7 +95,7 @@ extension Array where Element == Article {
var i = 0
for article in self {
let otherArticle = otherArticles[i]
if article.account != otherArticle.account || article.articleID != otherArticle.articleID {
if article.accountID != otherArticle.accountID || article.articleID != otherArticle.articleID {
return false
}
i += 1

View File

@@ -33,7 +33,7 @@ final class FetchRequestOperation {
self.resultBlock = resultBlock
}
func run(_ completion: @escaping (FetchRequestOperation) -> Void) {
@MainActor func run(_ completion: @escaping (FetchRequestOperation) -> Void) {
precondition(Thread.isMainThread)
precondition(!isFinished)

View File

@@ -10,7 +10,7 @@ import Foundation
// Main thread only.
final class FetchRequestQueue {
@MainActor final class FetchRequestQueue {
private var pendingRequests = [FetchRequestOperation]()
private var currentRequest: FetchRequestOperation? = nil

View File

@@ -9,7 +9,7 @@
import Foundation
import Account
class AccountRefreshTimer {
@MainActor class AccountRefreshTimer {
var shuttingDown = false
@@ -75,5 +75,4 @@ class AccountRefreshTimer {
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log, completion: nil)
}
}

View File

@@ -19,7 +19,7 @@ class ArticleStatusSyncTimer {
private var lastTimedRefresh: Date?
private let launchTime = Date()
func fireOldTimer() {
@MainActor func fireOldTimer() {
if let timer = internalTimer {
if timer.fireDate < Date() {
timedRefresh(nil)
@@ -59,7 +59,7 @@ class ArticleStatusSyncTimer {
}
@objc func timedRefresh(_ sender: Timer?) {
@MainActor @objc func timedRefresh(_ sender: Timer?) {
guard !shuttingDown else {
return

View File

@@ -41,7 +41,7 @@ final class FeedTreeControllerDelegate: TreeControllerDelegate {
private extension FeedTreeControllerDelegate {
func childNodesForRootNode(_ rootNode: Node) -> [Node]? {
@MainActor func childNodesForRootNode(_ rootNode: Node) -> [Node]? {
var topLevelNodes = [Node]()
let smartFeedsNode = rootNode.existingOrNewChildNode(with: SmartFeedsController.shared)
@@ -54,7 +54,7 @@ private extension FeedTreeControllerDelegate {
return topLevelNodes
}
func childNodesForSmartFeeds(_ parentNode: Node) -> [Node] {
@MainActor func childNodesForSmartFeeds(_ parentNode: Node) -> [Node] {
return SmartFeedsController.shared.smartFeeds.compactMap { (feed) -> Node? in
// All Smart Feeds should remain visible despite the Hide Read Feeds setting
return parentNode.existingOrNewChildNode(with: feed as AnyObject)
@@ -132,7 +132,7 @@ private extension FeedTreeControllerDelegate {
return node
}
func sortedAccountNodes(_ parent: Node) -> [Node] {
@MainActor func sortedAccountNodes(_ parent: Node) -> [Node] {
let nodes = AccountManager.shared.sortedActiveAccounts.compactMap { (account) -> Node? in
let accountNode = parent.existingOrNewChildNode(with: account)
accountNode.canHaveChildNodes = true

View File

@@ -14,7 +14,7 @@ import Account
final class FolderTreeControllerDelegate: TreeControllerDelegate {
func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
@MainActor func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
return node.isRoot ? childNodesForRootNode(node) : childNodes(node)
}
@@ -22,7 +22,7 @@ final class FolderTreeControllerDelegate: TreeControllerDelegate {
private extension FolderTreeControllerDelegate {
func childNodesForRootNode(_ node: Node) -> [Node]? {
@MainActor func childNodesForRootNode(_ node: Node) -> [Node]? {
let accountNodes: [Node] = AccountManager.shared.sortedActiveAccounts.map { account in
let accountNode = Node(representedObject: account, parent: node)
@@ -33,7 +33,7 @@ private extension FolderTreeControllerDelegate {
}
func childNodes(_ node: Node) -> [Node]? {
@MainActor func childNodes(_ node: Node) -> [Node]? {
guard let account = node.representedObject as? Account, let folders = account.folders else {
return nil