Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concurrency Upgrades #1

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 45 additions & 72 deletions Account/Sources/Account/Account.swift
Original file line number Diff line number Diff line change
Expand Up @@ -681,28 +681,28 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
}

public func fetchArticlesAsync(_ fetchType: FetchType, _ completion: @escaping ArticleSetResultBlock) {
public func fetchArticlesAsync(_ fetchType: FetchType) async throws -> Set<Article> {
switch fetchType {
case .starred:
fetchStarredArticlesAsync(completion)
return await try fetchStarredArticlesAsync()
case .unread:
fetchUnreadArticlesAsync(completion)
return await try fetchUnreadArticlesAsync()
case .today:
fetchTodayArticlesAsync(completion)
return await try fetchTodayArticlesAsync()
case .folder(let folder, let readFilter):
if readFilter {
return fetchUnreadArticlesAsync(folder: folder, completion)
return await try fetchUnreadArticlesAsync(folder: folder)
} else {
return fetchArticlesAsync(folder: folder, completion)
return await try fetchArticlesAsync(folder: folder)
}
case .webFeed(let webFeed):
fetchArticlesAsync(webFeed: webFeed, completion)
return await try fetchArticlesAsync(webFeed: webFeed)
case .articleIDs(let articleIDs):
fetchArticlesAsync(articleIDs: articleIDs, completion)
return await try fetchArticlesAsync(articleIDs: articleIDs)
case .search(let searchString):
fetchArticlesMatchingAsync(searchString, completion)
return await try fetchArticlesMatchingAsync(searchString)
case .searchWithArticleIDs(let searchString, let articleIDs):
return fetchArticlesMatchingWithArticleIDsAsync(searchString, articleIDs, completion)
return await try fetchArticlesMatchingWithArticleIDsAsync(searchString, articleIDs)
}
}

Expand Down Expand Up @@ -774,24 +774,16 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
}

func update(webFeedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping DatabaseCompletionBlock) {
@MainActor func update(webFeedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool) async throws {
// Used only by syncing systems.
precondition(Thread.isMainThread)
precondition(type != .onMyMac && type != .cloudKit)
guard !webFeedIDsAndItems.isEmpty else {
completion(nil)
return
}

database.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: defaultRead) { updateArticlesResult in
switch updateArticlesResult {
case .success(let newAndUpdatedArticles):
self.sendNotificationAbout(newAndUpdatedArticles)
completion(nil)
case .failure(let databaseError):
completion(databaseError)
}
}
let newAndUpdatedArticles = await try database.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: defaultRead)
self.sendNotificationAbout(newAndUpdatedArticles)
}

func update(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping ArticleSetResultBlock) {
Expand Down Expand Up @@ -1034,40 +1026,40 @@ private extension Account {
return try database.fetchStarredArticles(flattenedWebFeeds().webFeedIDs())
}

func fetchStarredArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
database.fetchedStarredArticlesAsync(flattenedWebFeeds().webFeedIDs(), completion)
func fetchStarredArticlesAsync() async throws -> Set<Article> {
return await try database.fetchedStarredArticlesAsync(flattenedWebFeeds().webFeedIDs())
}

func fetchUnreadArticles() throws -> Set<Article> {
return try fetchUnreadArticles(forContainer: self)
}

func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
fetchUnreadArticlesAsync(forContainer: self, completion)
func fetchUnreadArticlesAsync() async throws -> Set<Article> {
return await try fetchUnreadArticlesAsync(forContainer: self)
}

func fetchTodayArticles() throws -> Set<Article> {
return try database.fetchTodayArticles(flattenedWebFeeds().webFeedIDs())
}

func fetchTodayArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
database.fetchTodayArticlesAsync(flattenedWebFeeds().webFeedIDs(), completion)
func fetchTodayArticlesAsync() async throws -> Set<Article> {
return await try database.fetchTodayArticlesAsync(flattenedWebFeeds().webFeedIDs())
}

func fetchArticles(folder: Folder) throws -> Set<Article> {
return try fetchArticles(forContainer: folder)
}

func fetchArticlesAsync(folder: Folder, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync(forContainer: folder, completion)
func fetchArticlesAsync(folder: Folder) async throws -> Set<Article> {
return await try fetchArticlesAsync(forContainer: folder)
}

func fetchUnreadArticles(folder: Folder) throws -> Set<Article> {
return try fetchUnreadArticles(forContainer: folder)
}

func fetchUnreadArticlesAsync(folder: Folder, _ completion: @escaping ArticleSetResultBlock) {
fetchUnreadArticlesAsync(forContainer: folder, completion)
func fetchUnreadArticlesAsync(folder: Folder) async throws -> Set<Article> {
return await try fetchUnreadArticlesAsync(forContainer: folder)
}

func fetchArticles(webFeed: WebFeed) throws -> Set<Article> {
Expand All @@ -1076,16 +1068,10 @@ private extension Account {
return articles
}

func fetchArticlesAsync(webFeed: WebFeed, _ completion: @escaping ArticleSetResultBlock) {
database.fetchArticlesAsync(webFeed.webFeedID) { [weak self] articleSetResult in
switch articleSetResult {
case .success(let articles):
self?.validateUnreadCount(webFeed, articles)
completion(.success(articles))
case .failure(let databaseError):
completion(.failure(databaseError))
}
}
func fetchArticlesAsync(webFeed: WebFeed) async throws -> Set<Article> {
let articles = await try database.fetchArticlesAsync(webFeed.webFeedID)
self?.validateUnreadCount(webFeed, articles)
return articles
}

func fetchArticlesMatching(_ searchString: String) throws -> Set<Article> {
Expand All @@ -1096,20 +1082,20 @@ private extension Account {
return try database.fetchArticlesMatchingWithArticleIDs(searchString, articleIDs)
}

func fetchArticlesMatchingAsync(_ searchString: String, _ completion: @escaping ArticleSetResultBlock) {
database.fetchArticlesMatchingAsync(searchString, flattenedWebFeeds().webFeedIDs(), completion)
func fetchArticlesMatchingAsync(_ searchString: String) async throws -> Set<Article> {
return await try database.fetchArticlesMatchingAsync(searchString, flattenedWebFeeds().webFeedIDs())
}

func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
database.fetchArticlesMatchingWithArticleIDsAsync(searchString, articleIDs, completion)
func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>) async throws -> Set<Article> {
return await try database.fetchArticlesMatchingWithArticleIDsAsync(searchString, articleIDs)
}

func fetchArticles(articleIDs: Set<String>) throws -> Set<Article> {
return try database.fetchArticles(articleIDs: articleIDs)
}

func fetchArticlesAsync(articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
return database.fetchArticlesAsync(articleIDs: articleIDs, completion)
func fetchArticlesAsync(articleIDs: Set<String>) async throws -> Set<Article> {
return await try database.fetchArticlesAsync(articleIDs: articleIDs)
}

func fetchUnreadArticles(webFeed: WebFeed) throws -> Set<Article> {
Expand All @@ -1118,11 +1104,10 @@ private extension Account {
return articles
}

func fetchUnreadArticlesAsync(for webFeed: WebFeed, completion: @escaping (Set<Article>) -> Void) {
// database.fetchUnreadArticlesAsync(for: Set([feed.feedID])) { [weak self] (articles) in
// self?.validateUnreadCount(feed, articles)
// callback(articles)
// }
func fetchUnreadArticlesAsync(for webFeed: WebFeed) async -> Set<Article> {
// let articles = await database.fetchUnreadArticlesAsync(for: Set([feed.feedID]))
// self?.validateUnreadCount(feed, articles)
// return articles
}


Expand All @@ -1133,17 +1118,11 @@ private extension Account {
return articles
}

func fetchArticlesAsync(forContainer container: Container, _ completion: @escaping ArticleSetResultBlock) {
func fetchArticlesAsync(forContainer container: Container) async throws -> Set<Article> {
let webFeeds = container.flattenedWebFeeds()
database.fetchArticlesAsync(webFeeds.webFeedIDs()) { [weak self] (articleSetResult) in
switch articleSetResult {
case .success(let articles):
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)
completion(.success(articles))
case .failure(let databaseError):
completion(.failure(databaseError))
}
}
let articles = await try database.fetchArticlesAsync(webFeeds.webFeedIDs())
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)
return articles
}

func fetchUnreadArticles(forContainer container: Container) throws -> Set<Article> {
Expand All @@ -1153,17 +1132,11 @@ private extension Account {
return articles
}

func fetchUnreadArticlesAsync(forContainer container: Container, _ completion: @escaping ArticleSetResultBlock) {
func fetchUnreadArticlesAsync(forContainer container: Container) async throws -> Set<Article> {
let webFeeds = container.flattenedWebFeeds()
database.fetchUnreadArticlesAsync(webFeeds.webFeedIDs()) { [weak self] (articleSetResult) in
switch articleSetResult {
case .success(let articles):
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)
completion(.success(articles))
case .failure(let databaseError):
completion(.failure(databaseError))
}
}
let articles = await try database.fetchUnreadArticlesAsync(webFeeds.webFeedIDs())
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)
return articles
}

func validateUnreadCountsAfterFetchingUnreadArticles(_ webFeeds: Set<WebFeed>, _ articles: Set<Article>) {
Expand Down
32 changes: 16 additions & 16 deletions Account/Sources/Account/AccountDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,26 @@ protocol AccountDelegate {

var refreshProgress: DownloadProgress { get }

func receiveRemoteNotification(for account: Account, userInfo: [AnyHashable : Any], completion: @escaping () -> Void)
func receiveRemoteNotification(for account: Account, userInfo: [AnyHashable : Any]) async

func refreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void)
func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void))
func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void))
func refreshAll(for account: Account) async throws
func sendArticleStatus(for account: Account) async throws
func refreshArticleStatus(for account: Account) async throws

func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void)
func importOPML(for account:Account, opmlFile: URL) async throws

func createFolder(for account: Account, name: String, completion: @escaping (Result<Folder, Error>) -> Void)
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result<Void, Error>) -> Void)
func createFolder(for account: Account, name: String) async throws -> Folder
func renameFolder(for account: Account, with folder: Folder, to name: String) async throws
func removeFolder(for account: Account, with folder: Folder) async throws

func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void)
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
func addWebFeed(for account: Account, with: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void)
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void)
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void)
func createWebFeed(for account: Account, url: String, name: String?, container: Container) async throws -> WebFeed
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String) async throws
func addWebFeed(for account: Account, with: WebFeed, to container: Container) async throws
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container) async throws
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container) async throws

func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void)
func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void)
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container) async throws
func restoreFolder(for account: Account, folder: Folder) async throws

func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool)

Expand All @@ -51,7 +51,7 @@ protocol AccountDelegate {

func accountWillBeDeleted(_ account: Account)

static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, completion: @escaping (Result<Credentials?, Error>) -> Void)
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?) async throws -> Credentials?

/// Suspend all network activity
func suspendNetwork()
Expand Down
9 changes: 5 additions & 4 deletions Account/Sources/Account/AccountManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -343,19 +343,20 @@ public final class AccountManager: UnreadCountProvider {
return articles
}

public func fetchArticlesAsync(_ fetchType: FetchType, _ completion: @escaping ArticleSetResultBlock) {
precondition(Thread.isMainThread)
public func fetchArticlesAsync(_ fetchType: FetchType) async -> ArticleSetResult {
precondition(Thread.isMainThread) // TODO

var allFetchedArticles = Set<Article>()
let numberOfAccounts = activeAccounts.count
var accountsReporting = 0

guard numberOfAccounts > 0 else {
completion(.success(allFetchedArticles))
return
return .success(allFetchedArticles)
}

// TODO: refactor this loop to use detached tasks and/or actors.
for account in activeAccounts {
// TODO: type error here right now:
account.fetchArticlesAsync(fetchType) { (articleSetResult) in
accountsReporting += 1

Expand Down
39 changes: 14 additions & 25 deletions Account/Sources/Account/ArticleFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import ArticlesDatabase
public protocol ArticleFetcher {

func fetchArticles() throws -> Set<Article>
func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock)
func fetchArticlesAsync() async throws -> Set<Article>
func fetchUnreadArticles() throws -> Set<Article>
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock)
func fetchUnreadArticlesAsync() async throws -> Set<Article>
}

extension WebFeed: ArticleFetcher {
Expand All @@ -24,33 +24,24 @@ extension WebFeed: ArticleFetcher {
return try account?.fetchArticles(.webFeed(self)) ?? Set<Article>()
}

public func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
public func fetchArticlesAsync() async throws -> Set<Article> {
guard let account = account else {
assertionFailure("Expected feed.account, but got nil.")
completion(.success(Set<Article>()))
return
return Set<Article>()
}
account.fetchArticlesAsync(.webFeed(self), completion)
return await try account.fetchArticlesAsync(.webFeed(self))
}

public func fetchUnreadArticles() throws -> Set<Article> {
return try fetchArticles().unreadArticles()
}

public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
public func fetchUnreadArticlesAsync() async throws -> Set<Article> {
guard let account = account else {
assertionFailure("Expected feed.account, but got nil.")
completion(.success(Set<Article>()))
return
}
account.fetchArticlesAsync(.webFeed(self)) { articleSetResult in
switch articleSetResult {
case .success(let articles):
completion(.success(articles.unreadArticles()))
case .failure(let error):
completion(.failure(error))
}
return Set<Article>()
}
return await try account.fetchArticlesAsync(.webFeed(self)).unreadArticles()
}
}

Expand All @@ -64,13 +55,12 @@ extension Folder: ArticleFetcher {
return try account.fetchArticles(.folder(self, false))
}

public func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
public func fetchArticlesAsync() async throws -> Set<Article> {
guard let account = account else {
assertionFailure("Expected folder.account, but got nil.")
completion(.success(Set<Article>()))
return
return Set<Article>()
}
account.fetchArticlesAsync(.folder(self, false), completion)
return await try account.fetchArticlesAsync(.folder(self, false))
}

public func fetchUnreadArticles() throws -> Set<Article> {
Expand All @@ -81,12 +71,11 @@ extension Folder: ArticleFetcher {
return try account.fetchArticles(.folder(self, true))
}

public func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
public func fetchUnreadArticlesAsync() async throws -> Set<Article> {
guard let account = account else {
assertionFailure("Expected folder.account, but got nil.")
completion(.success(Set<Article>()))
return
return Set<Article>()
}
account.fetchArticlesAsync(.folder(self, true), completion)
return await try account.fetchArticlesAsync(.folder(self, true))
}
}
Loading