Files
NetNewsWire/ArticlesDatabase/Sources/ArticlesDatabase/ArticlesDatabaseCompatibility.swift

385 lines
12 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// ArticlesDatabase.swift
// NetNewsWire
//
// Created by Brent Simmons on 7/20/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import Database
import RSParser
import Articles
// This file exists for compatibility  it provides nonisolated functions and callback-based APIs.
// It will go away as we adopt structured concurrency.
public typealias UnreadCountDictionaryCompletionResult = Result<UnreadCountDictionary,DatabaseError>
public typealias UnreadCountDictionaryCompletionBlock = @Sendable (UnreadCountDictionaryCompletionResult) -> Void
public typealias SingleUnreadCountResult = Result<Int, DatabaseError>
public typealias SingleUnreadCountCompletionBlock = @Sendable (SingleUnreadCountResult) -> Void
public typealias UpdateArticlesResult = Result<ArticleChanges, DatabaseError>
public typealias UpdateArticlesCompletionBlock = @Sendable (UpdateArticlesResult) -> Void
public typealias ArticleSetResult = Result<Set<Article>, DatabaseError>
public typealias ArticleSetResultBlock = (ArticleSetResult) -> Void
public typealias ArticleIDsResult = Result<Set<String>, DatabaseError>
public typealias ArticleIDsCompletionBlock = @Sendable (ArticleIDsResult) -> Void
public typealias ArticleStatusesResult = Result<Set<ArticleStatus>, DatabaseError>
public typealias ArticleStatusesResultBlock = (ArticleStatusesResult) -> Void
public extension ArticlesDatabase {
// MARK: - Fetching Articles Async
nonisolated func fetchArticlesAsync(_ feedID: String, _ completion: @escaping ArticleSetResultBlock) {
Task {
do {
let articles = try await articles(feedID: feedID)
callArticleSetCompletion(completion, .success(articles))
} catch {
callArticleSetCompletion(completion, .failure(.suspended))
}
}
}
nonisolated func fetchArticlesAsync(_ feedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
Task {
do {
let articles = try await articles(feedIDs: feedIDs)
callArticleSetCompletion(completion, .success(articles))
} catch {
callArticleSetCompletion(completion, .failure(.suspended))
}
}
}
nonisolated func fetchArticlesAsync(articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
Task {
do {
let articles = try await articles(articleIDs: articleIDs)
callArticleSetCompletion(completion, .success(articles))
} catch {
callArticleSetCompletion(completion, .failure(.suspended))
}
}
}
nonisolated func fetchUnreadArticlesAsync(_ feedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
Task {
do {
let articles = try await unreadArticles(feedIDs: feedIDs, limit: limit)
callArticleSetCompletion(completion, .success(articles))
} catch {
callArticleSetCompletion(completion, .failure(.suspended))
}
}
}
nonisolated func fetchTodayArticlesAsync(_ feedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
Task {
do {
let articles = try await todayArticles(feedIDs: feedIDs, limit: limit)
callArticleSetCompletion(completion, .success(articles))
} catch {
callArticleSetCompletion(completion, .failure(.suspended))
}
}
}
nonisolated func fetchedStarredArticlesAsync(_ feedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
Task {
do {
let articles = try await starredArticles(feedIDs: feedIDs, limit: limit)
callArticleSetCompletion(completion, .success(articles))
} catch {
callArticleSetCompletion(completion, .failure(.suspended))
}
}
}
nonisolated func fetchArticlesMatchingAsync(_ searchString: String, _ feedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
Task {
do {
let articles = try await articlesMatching(searchString: searchString, feedIDs: feedIDs)
callArticleSetCompletion(completion, .success(articles))
} catch {
callArticleSetCompletion(completion, .failure(.suspended))
}
}
}
nonisolated func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
Task {
do {
let articles = try await articlesMatching(searchString: searchString, articleIDs: articleIDs)
callArticleSetCompletion(completion, .success(articles))
} catch {
callArticleSetCompletion(completion, .failure(.suspended))
}
}
}
// MARK: - Unread Counts
/// Fetch all non-zero unread counts.
nonisolated func fetchAllUnreadCounts(_ completion: @escaping UnreadCountDictionaryCompletionBlock) {
Task {
do {
let unreadCountDictionary = try await allUnreadCounts()
callUnreadCountDictionaryCompletion(completion, .success(unreadCountDictionary))
} catch {
callUnreadCountDictionaryCompletion(completion, .failure(.suspended))
}
}
}
/// Fetch unread count for a single feed.
nonisolated func fetchUnreadCount(_ feedID: String, _ completion: @escaping SingleUnreadCountCompletionBlock) {
Task {
do {
let unreadCount = try await unreadCount(feedID: feedID) ?? 0
callSingleUnreadCountCompletion(completion, .success(unreadCount))
} catch {
callSingleUnreadCountCompletion(completion, .failure(.suspended))
}
}
}
/// Fetch non-zero unread counts for given feedIDs.
nonisolated func fetchUnreadCounts(for feedIDs: Set<String>, _ completion: @escaping UnreadCountDictionaryCompletionBlock) {
Task {
do {
let unreadCountDictionary = try await unreadCounts(feedIDs: feedIDs)
callUnreadCountDictionaryCompletion(completion, .success(unreadCountDictionary))
} catch {
callUnreadCountDictionaryCompletion(completion, .failure(.suspended))
}
}
}
nonisolated func fetchUnreadCountForToday(for feedIDs: Set<String>, completion: @escaping SingleUnreadCountCompletionBlock) {
Task {
do {
let unreadCount = try await unreadCountForToday(feedIDs: feedIDs)!
callSingleUnreadCountCompletion(completion, .success(unreadCount))
} catch {
callSingleUnreadCountCompletion(completion, .failure(.suspended))
}
}
}
nonisolated func fetchUnreadCount(for feedIDs: Set<String>, since: Date, completion: @escaping SingleUnreadCountCompletionBlock) {
Task {
do {
let unreadCount = try await unreadCount(feedIDs: feedIDs, since: since)!
callSingleUnreadCountCompletion(completion, .success(unreadCount))
} catch {
callSingleUnreadCountCompletion(completion, .failure(.suspended))
}
}
}
nonisolated func fetchStarredAndUnreadCount(for feedIDs: Set<String>, completion: @escaping SingleUnreadCountCompletionBlock) {
Task {
do {
let unreadCount = try await starredAndUnreadCount(feedIDs: feedIDs)!
callSingleUnreadCountCompletion(completion, .success(unreadCount))
} catch {
callSingleUnreadCountCompletion(completion, .failure(.suspended))
}
}
}
// MARK: - Saving, Updating, and Deleting Articles
/// Update articles and save new ones  for feed-based systems (local and iCloud).
nonisolated func update(with parsedItems: Set<ParsedItem>, feedID: String, deleteOlder: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
Task {
do {
let articleChanges = try await update(parsedItems: parsedItems, feedID: feedID, deleteOlder: deleteOlder)
callUpdateArticlesCompletion(completion, .success(articleChanges))
} catch {
callUpdateArticlesCompletion(completion, .failure(.suspended))
}
}
}
/// Update articles and save new ones for sync systems (Feedbin, Feedly, etc.).
nonisolated func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
Task {
do {
let articleChanges = try await update(feedIDsAndItems: feedIDsAndItems, defaultRead: defaultRead)
callUpdateArticlesCompletion(completion, .success(articleChanges))
} catch {
callUpdateArticlesCompletion(completion, .failure(.suspended))
}
}
}
/// Delete articles
nonisolated func delete(articleIDs: Set<String>, completion: DatabaseCompletionBlock?) {
Task {
do {
try await delete(articleIDs: articleIDs)
callDatabaseCompletion(completion)
} catch {
callDatabaseCompletion(completion, .suspended)
}
}
}
// MARK: - Status
/// Fetch the articleIDs of unread articles.
nonisolated func fetchUnreadArticleIDsAsync(completion: @escaping ArticleIDsCompletionBlock) {
Task {
do {
let articleIDs = try await unreadArticleIDs()!
callArticleIDsCompletion(completion, .success(articleIDs))
} catch {
callArticleIDsCompletion(completion, .failure(.suspended))
}
}
}
/// Fetch the articleIDs of starred articles.
nonisolated func fetchStarredArticleIDsAsync(completion: @escaping ArticleIDsCompletionBlock) {
Task {
do {
let articleIDs = try await starredArticleIDs()!
callArticleIDsCompletion(completion, .success(articleIDs))
} catch {
callArticleIDsCompletion(completion, .failure(.suspended))
}
}
}
/// Fetch articleIDs for articles that we should have, but dont. These articles are either (starred) or (newer than the article cutoff date).
nonisolated func fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate(_ completion: @escaping ArticleIDsCompletionBlock) {
Task {
do {
let articleIDs = try await articleIDsForStatusesWithoutArticlesNewerThanCutoffDate()!
callArticleIDsCompletion(completion, .success(articleIDs))
} catch {
callArticleIDsCompletion(completion, .failure(.suspended))
}
}
}
nonisolated func mark(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping ArticleStatusesResultBlock) {
Task {
do {
let statuses = try await mark(articles: articles, statusKey: statusKey, flag: flag)!
callArticleStatusesCompletion(completion, .success(statuses))
} catch {
callArticleStatusesCompletion(completion, .failure(.suspended))
}
}
}
nonisolated func mark(articleIDs: Set<String>, statusKey: ArticleStatus.Key, flag: Bool, completion: @escaping DatabaseCompletionBlock) {
Task {
do {
try await mark(articleIDs: articleIDs, statusKey: statusKey, flag: flag)
callDatabaseCompletion(completion)
} catch {
callDatabaseCompletion(completion, .suspended)
}
}
}
/// Create statuses for specified articleIDs. For existing statuses, dont do anything.
/// For newly-created statuses, mark them as read and not-starred.
nonisolated func createStatusesIfNeeded(articleIDs: Set<String>, completion: @escaping DatabaseCompletionBlock) {
Task {
do {
try await createStatusesIfNeeded(articleIDs: articleIDs)
callDatabaseCompletion(completion)
} catch {
callDatabaseCompletion(completion, .suspended)
}
}
}
nonisolated private func callUnreadCountDictionaryCompletion(_ completion: @escaping UnreadCountDictionaryCompletionBlock, _ result: UnreadCountDictionaryCompletionResult) {
Task { @MainActor in
completion(result)
}
}
nonisolated private func callSingleUnreadCountCompletion(_ completion: @escaping SingleUnreadCountCompletionBlock, _ result: SingleUnreadCountResult) {
Task { @MainActor in
completion(result)
}
}
nonisolated private func callUpdateArticlesCompletion(_ completion: @escaping UpdateArticlesCompletionBlock, _ result: UpdateArticlesResult) {
Task { @MainActor in
completion(result)
}
}
nonisolated private func callArticleSetCompletion(_ completion: @escaping ArticleSetResultBlock, _ result: ArticleSetResult) {
Task { @MainActor in
completion(result)
}
}
nonisolated private func callArticleStatusesCompletion(_ completion: @escaping ArticleStatusesResultBlock, _ result: ArticleStatusesResult) {
Task { @MainActor in
completion(result)
}
}
nonisolated private func callArticleIDsCompletion(_ completion: @escaping ArticleIDsCompletionBlock, _ result: ArticleIDsResult) {
Task { @MainActor in
completion(result)
}
}
nonisolated private func callDatabaseCompletion(_ completion: DatabaseCompletionBlock?, _ error: DatabaseError? = nil) {
guard let completion else {
return
}
Task { @MainActor in
completion(error)
}
}
}