Use RSCore 3.

This commit is contained in:
Brent Simmons
2023-11-17 22:23:33 -08:00
parent 6cd8715eb0
commit c7b036f364
27 changed files with 446 additions and 208 deletions

View File

@@ -12,7 +12,7 @@ let package = Package(
targets: ["Account"]),
],
dependencies: [
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "2.0.1")),
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "3.0.0")),
.package(url: "https://github.com/Ranchero-Software/RSDatabase.git", .upToNextMajor(from: "2.0.0")),
.package(url: "https://github.com/Ranchero-Software/RSParser.git", .upToNextMajor(from: "2.0.2")),
.package(url: "https://github.com/Ranchero-Software/RSWeb.git", .upToNextMajor(from: "1.0.0")),

View File

@@ -432,15 +432,8 @@ public enum FetchType {
try await delegate.refreshAll(for: self)
}
public func sendArticleStatus(completion: ((Result<Void, Error>) -> Void)? = nil) {
delegate.sendArticleStatus(for: self) { result in
switch result {
case .success:
completion?(.success(()))
case .failure(let error):
completion?(.failure(error))
}
}
public func sendArticleStatus() async throws {
try await delegate.sendArticleStatus(for: self)
}
public func syncArticleStatus() async throws {

View File

@@ -27,8 +27,8 @@ import Secrets
func refreshAll(for account: Account) async throws
func syncArticleStatus(for account: Account) async throws
func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void))
func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void))
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)

View File

@@ -25,7 +25,7 @@ public enum FeedbinAccountDelegateError: String, Error {
@MainActor final class FeedbinAccountDelegate: AccountDelegate, Logging {
private let database: SyncDatabase
private let caller: FeedbinAPICaller
let behaviors: AccountBehaviors = [.disallowFeedCopyInRootFolder]
@@ -128,7 +128,20 @@ public enum FeedbinAccountDelegateError: String, Error {
}
}
func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
func sendArticleStatus(for account: Account) async throws {
try await withCheckedThrowingContinuation { continuation in
self.sendArticleStatus(for: account) { result in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
private func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
Task { @MainActor in
logger.debug("Sending article statuses")
@@ -190,7 +203,21 @@ public enum FeedbinAccountDelegateError: String, Error {
}
}
func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
func refreshArticleStatus(for account: Account) async throws {
try await withCheckedThrowingContinuation { continuation in
self.refreshArticleStatus(for: account) { result in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
private func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
logger.debug("Refreshing article statuses...")

View File

@@ -76,14 +76,14 @@ final class LocalAccountDelegate: AccountDelegate, Logging {
return
}
func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
completion(.success(()))
func sendArticleStatus(for account: Account) async throws {
return
}
func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
completion(.success(()))
func refreshArticleStatus(for account: Account) async throws {
return
}
func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
var fileData: Data?

View File

@@ -469,9 +469,9 @@ extension NewsBlurAccountDelegate {
// Download the initial articles
downloadFeed(account: account, feed: feed, page: 1) { result in
self.refreshArticleStatus(for: account) { result in
switch result {
case .success:
Task { @MainActor in
do {
try await self.refreshArticleStatus(for: account)
self.refreshMissingStories(for: account) { result in
switch result {
case .success:
@@ -485,8 +485,7 @@ extension NewsBlurAccountDelegate {
completion(.failure(error))
}
}
case .failure(let error):
} catch {
completion(.failure(error))
}
}

View File

@@ -151,7 +151,21 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging {
}
}
func sendArticleStatus(for account: Account, completion: @escaping (Result<Void, Error>) -> ()) {
func sendArticleStatus(for account: Account) async throws {
try await withCheckedThrowingContinuation { continuation in
self.sendArticleStatus(for: account) { result in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
private func sendArticleStatus(for account: Account, completion: @escaping (Result<Void, Error>) -> ()) {
Task { @MainActor in
logger.debug("Sending story statuses")
@@ -223,7 +237,21 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging {
}
}
func refreshArticleStatus(for account: Account, completion: @escaping (Result<Void, Error>) -> ()) {
func refreshArticleStatus(for account: Account) async throws {
try await withCheckedThrowingContinuation { continuation in
self.refreshArticleStatus(for: account) { result in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
private func refreshArticleStatus(for account: Account, completion: @escaping (Result<Void, Error>) -> ()) {
logger.debug("Refreshing story statuses...")
let group = DispatchGroup()

View File

@@ -39,7 +39,7 @@ public enum ReaderAPIAccountDelegateError: LocalizedError {
@MainActor final class ReaderAPIAccountDelegate: AccountDelegate, Logging {
private let variant: ReaderAPIVariant
private let database: SyncDatabase
private let caller: ReaderAPICaller
@@ -206,7 +206,21 @@ public enum ReaderAPIAccountDelegateError: LocalizedError {
}
}
func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
func sendArticleStatus(for account: Account) async throws {
try await withCheckedThrowingContinuation { continuation in
self.sendArticleStatus(for: account) { result in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
private func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
logger.debug("Sending article statuses")
@MainActor func processStatuses(_ syncStatuses: [SyncStatus]) {
@@ -253,7 +267,21 @@ public enum ReaderAPIAccountDelegateError: LocalizedError {
}
}
func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
func refreshArticleStatus(for account: Account) async throws {
try await withCheckedThrowingContinuation { continuation in
self.refreshArticleStatus(for: account) { result in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
private func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
logger.debug("Refreshing article statuses...")
let group = DispatchGroup()

View File

@@ -267,10 +267,12 @@ import RSDatabase
public func sendArticleStatusAll(completion: (() -> Void)? = nil) {
let group = DispatchGroup()
for account in activeAccounts {
group.enter()
account.sendArticleStatus() { _ in
Task { @MainActor in
try? await account.sendArticleStatus()
group.leave()
}
}
@@ -280,6 +282,7 @@ import RSDatabase
}
}
public func syncArticleStatusAll() async {
await withTaskGroup(of: Void.self) { group in

View File

@@ -102,42 +102,30 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
}
func syncArticleStatus(for account: Account) async throws {
try await sendArticleStatus(for: account)
}
func sendArticleStatus(for account: Account) async throws {
try await sendArticleStatus(for: account, showProgress: false)
}
func refreshArticleStatus(for account: Account) async throws {
try await withCheckedThrowingContinuation { continuation in
sendArticleStatus(for: account) { result in
switch result {
case .success:
self.refreshArticleStatus(for: account) { result in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
}
case .failure(let error):
continuation.resume(throwing: error)
let op = CloudKitReceiveStatusOperation(articlesZone: self.articlesZone)
op.completionBlock = { mainThreadOperaion in
if mainThreadOperaion.isCanceled {
continuation.resume(throwing: CloudKitAccountDelegateError.unknown)
} else {
continuation.resume()
}
}
mainThreadOperationQueue.add(op)
}
}
func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
sendArticleStatus(for: account, showProgress: false, completion: completion)
}
func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
let op = CloudKitReceiveStatusOperation(articlesZone: articlesZone)
op.completionBlock = { mainThreadOperaion in
if mainThreadOperaion.isCanceled {
completion(.failure(CloudKitAccountDelegateError.unknown))
} else {
completion(.success(()))
}
}
mainThreadOperationQueue.add(op)
}
func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
guard refreshProgress.isComplete else {
completion(.success(()))
@@ -454,7 +442,7 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
try? await self.database.insertStatuses(syncStatuses)
if let count = try? await self.database.selectPendingCount(), count > 100 {
self.sendArticleStatus(for: account, showProgress: false) { _ in }
try? await self.sendArticleStatus(for: account, showProgress: false)
}
completion(.success(()))
case .failure(let error):
@@ -517,52 +505,92 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging {
}
private extension CloudKitAccountDelegate {
func initialRefreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
func fail(_ error: Error) {
self.processAccountError(account, error)
self.refreshProgress.clear()
completion(.failure(error))
}
refreshProgress.isIndeterminate = true
refreshProgress.addToNumberOfTasksAndRemaining(3)
accountZone.fetchChangesInZone() { result in
self.refreshProgress.completeTask()
let feeds = account.flattenedFeeds()
self.refreshProgress.addToNumberOfTasksAndRemaining(feeds.count)
func fetchChangesInZone() async throws {
switch result {
case .success:
self.refreshArticleStatus(for: account) { result in
self.refreshProgress.completeTask()
self.refreshProgress.isIndeterminate = false
switch result {
case .success:
self.combinedRefresh(account, feeds) { result in
self.refreshProgress.clear()
switch result {
case .success:
account.metadata.lastArticleFetchEndTime = Date()
case .failure(let error):
fail(error)
}
}
case .failure(let error):
fail(error)
}
try await withCheckedThrowingContinuation { continuation in
self.accountZone.fetchChangesInZone { result in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
case .failure(let error):
fail(error)
}
}
}
@MainActor func initialRefreshAll(for account : Account) async throws {
refreshProgress.isIndeterminate = true
refreshProgress.addToNumberOfTasksAndRemaining(3)
do {
try await fetchChangesInZone()
refreshProgress.completeTask()
let feeds = account.flattenedFeeds()
refreshProgress.addToNumberOfTasksAndRemaining(feeds.count)
try await refreshArticleStatus(for: account)
refreshProgress.completeTask()
refreshProgress.isIndeterminate = false
await combinedRefresh(account, feeds)
refreshProgress.clear()
account.metadata.lastArticleFetchEndTime = Date()
} catch {
processAccountError(account, error)
refreshProgress.clear()
throw error
}
}
// func initialRefreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
//
// func fail(_ error: Error) {
// self.processAccountError(account, error)
// self.refreshProgress.clear()
// completion(.failure(error))
// }
//
// refreshProgress.isIndeterminate = true
// refreshProgress.addToNumberOfTasksAndRemaining(3)
// accountZone.fetchChangesInZone() { result in
// self.refreshProgress.completeTask()
//
// let feeds = account.flattenedFeeds()
// self.refreshProgress.addToNumberOfTasksAndRemaining(feeds.count)
//
// switch result {
// case .success:
// self.refreshArticleStatus(for: account) { result in
// self.refreshProgress.completeTask()
// self.refreshProgress.isIndeterminate = false
// switch result {
// case .success:
//
// self.combinedRefresh(account, feeds) { result in
// self.refreshProgress.clear()
// switch result {
// case .success:
// account.metadata.lastArticleFetchEndTime = Date()
// case .failure(let error):
// fail(error)
// }
// }
//
// case .failure(let error):
// fail(error)
// }
// }
// case .failure(let error):
// fail(error)
// }
// }
//
// }
func standardRefreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
let intialFeedsCount = account.flattenedFeeds().count
@@ -583,6 +611,36 @@ private extension CloudKitAccountDelegate {
let feeds = account.flattenedFeeds()
self.refreshProgress.addToNumberOfTasksAndRemaining(feeds.count - intialFeedsCount)
Task { @MainActor in
do {
try await self.refreshArticleStatus(for: account)
self.refreshProgress.completeTask()
self.refreshProgress.isIndeterminate = false
self.combinedRefresh(account, feeds) { result in
do {
try await self.sendArticleStatus(for: account, showProgress: true)
if case .failure(let error) = result {
fail(error)
} else {
account.metadata.lastArticleFetchEndTime = Date()
completion(.success(()))
}
} catch {
fail(error)
}
}
} catch {
self.refreshProgress.completeTask()
self.refreshProgress.isIndeterminate = false
}
}
self.refreshArticleStatus(for: account) { result in
switch result {
case .success:
@@ -611,20 +669,31 @@ private extension CloudKitAccountDelegate {
}
func combinedRefresh(_ account: Account, _ feeds: Set<Feed>, completion: @escaping (Result<Void, Error>) -> Void) {
func combinedRefresh(_ account: Account, _ feeds: Set<Feed>) async {
let feedURLs = Set(feeds.map{ $0.url })
let group = DispatchGroup()
try await withCheckedContinuation { continuation in
refresher.refreshFeedURLs(feedURLs) {
continuation.resume()
}
}
}
group.enter()
refresher.refreshFeedURLs(feedURLs) {
group.leave()
}
group.notify(queue: DispatchQueue.main) {
completion(.success(()))
}
}
// func combinedRefresh(_ account: Account, _ feeds: Set<Feed>, completion: @escaping (Result<Void, Error>) -> Void) {
//
// let feedURLs = Set(feeds.map{ $0.url })
// let group = DispatchGroup()
//
// group.enter()
// refresher.refreshFeedURLs(feedURLs) {
// group.leave()
// }
//
// group.notify(queue: DispatchQueue.main) {
// completion(.success(()))
// }
// }
func createRSSFeed(for account: Account, url: URL, editedName: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
@@ -739,12 +808,13 @@ private extension CloudKitAccountDelegate {
case .success(let articles):
self.storeArticleChanges(new: articles, updated: Set<Article>(), deleted: Set<Article>()) {
self.refreshProgress.completeTask()
self.sendArticleStatus(for: account, showProgress: true) { result in
switch result {
case .success:
self.refreshArticleStatus(for: account) { _ in }
case .failure(let error):
self.logger.error("CloudKit Feed send articles error: \(error.localizedDescription, privacy: .public)")
Task { @MainActor in
do {
try await self.sendArticleStatus(for: account, showProgress: true)
try await self.refreshArticleStatus(for: account)
} catch {
self.logger.error("CloudKit Feed send articles error: \(error.localizedDescription, privacy: .public)")
}
}
}
@@ -805,20 +875,24 @@ private extension CloudKitAccountDelegate {
}
}
func sendArticleStatus(for account: Account, showProgress: Bool, completion: @escaping ((Result<Void, Error>) -> Void)) {
let op = CloudKitSendStatusOperation(account: account,
articlesZone: articlesZone,
refreshProgress: refreshProgress,
showProgress: showProgress,
database: database)
op.completionBlock = { mainThreadOperaion in
if mainThreadOperaion.isCanceled {
completion(.failure(CloudKitAccountDelegateError.unknown))
} else {
completion(.success(()))
func sendArticleStatus(for account: Account, showProgress: Bool) async throws {
try await withCheckedThrowingContinuation { continuation in
let op = CloudKitSendStatusOperation(account: account,
articlesZone: self.articlesZone,
refreshProgress: self.refreshProgress,
showProgress: showProgress,
database: self.database)
op.completionBlock = { mainThreadOperation in
if mainThreadOperation.isCanceled {
continuation.resume(throwing: CloudKitAccountDelegateError.unknown)
} else {
continuation.resume()
}
}
mainThreadOperationQueue.add(op)
}
mainThreadOperationQueue.add(op)
}

View File

@@ -253,6 +253,29 @@ final class CloudKitAccountZone: CloudKitZone {
}
}
func findOrCreateAccount() async throws -> String {
guard let database else {
return
}
let predicate = NSPredicate(format: "isAccount = \"1\"")
let ckQuery = CKQuery(recordType: CloudKitContainer.recordType, predicate: predicate)
do {
let records = try await database.perform(ckQuery, inZoneWith: zoneID)
if records.count > 0 {
return records[0].externalID
} else {
createContainer(name: "Account", isAccount: true, completion: completion)
}
} catch {
switch CloudKitZoneResult.resolve(error) {
}
}
}
func findOrCreateAccount(completion: @escaping (Result<String, Error>) -> Void) {
let predicate = NSPredicate(format: "isAccount = \"1\"")
let ckQuery = CKQuery(recordType: CloudKitContainer.recordType, predicate: predicate)
@@ -343,6 +366,21 @@ private extension CloudKitAccountZone {
return record
}
func createContainer(name: String, isAccount: Bool) async throws -> String {
let record = CKRecord(recordType: CloudKitContainer.recordType, recordID: generateRecordID())
record[CloudKitContainer.Fields.name] = name
record[CloudKitContainer.Fields.isAccount] = isAccount ? "1" : "0"
save(record) { result in
switch result {
case .success:
completion(.success(record.externalID))
case .failure(let error):
completion(.failure(error))
}
}
}
func createContainer(name: String, isAccount: Bool, completion: @escaping (Result<String, Error>) -> Void) {
let record = CKRecord(recordType: CloudKitContainer.recordType, recordID: generateRecordID())
record[CloudKitContainer.Fields.name] = name

View File

@@ -31,32 +31,19 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
self.articlesZone = articlesZone
}
@MainActor func cloudKitWasChanged(updated: [CKRecord], deleted: [CloudKitRecordKey], completion: @escaping (Result<Void, Error>) -> Void) {
for deletedRecordKey in deleted {
switch deletedRecordKey.recordType {
case CloudKitAccountZone.CloudKitFeed.recordType:
removeFeed(deletedRecordKey.recordID.externalID)
case CloudKitAccountZone.CloudKitContainer.recordType:
removeContainer(deletedRecordKey.recordID.externalID)
default:
assertionFailure("Unknown record type: \(deletedRecordKey.recordType)")
@MainActor func cloudKitWasChanged(updated: [CKRecord], deleted: [CloudKitRecordKey]) async throws {
try await withCheckedThrowingContinuation { continuation in
self.cloudKitWasChanged(updated: updated, deleted: deleted) { result in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
for changedRecord in updated {
switch changedRecord.recordType {
case CloudKitAccountZone.CloudKitFeed.recordType:
addOrUpdateFeed(changedRecord)
case CloudKitAccountZone.CloudKitContainer.recordType:
addOrUpdateContainer(changedRecord)
default:
assertionFailure("Unknown record type: \(changedRecord.recordType)")
}
}
completion(.success(()))
}
@MainActor func addOrUpdateFeed(_ record: CKRecord) {
guard let account = account,
let urlString = record[CloudKitAccountZone.CloudKitFeed.Fields.url] as? String,
@@ -140,7 +127,33 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
private extension CloudKitAcountZoneDelegate {
@MainActor func updateFeed(_ feed: Feed, name: String?, editedName: String?, homePageURL: String?, containerExternalIDs: [String]) {
@MainActor func cloudKitWasChanged(updated: [CKRecord], deleted: [CloudKitRecordKey], completion: @escaping (Result<Void, Error>) -> Void) {
for deletedRecordKey in deleted {
switch deletedRecordKey.recordType {
case CloudKitAccountZone.CloudKitFeed.recordType:
removeFeed(deletedRecordKey.recordID.externalID)
case CloudKitAccountZone.CloudKitContainer.recordType:
removeContainer(deletedRecordKey.recordID.externalID)
default:
assertionFailure("Unknown record type: \(deletedRecordKey.recordType)")
}
}
for changedRecord in updated {
switch changedRecord.recordType {
case CloudKitAccountZone.CloudKitFeed.recordType:
addOrUpdateFeed(changedRecord)
case CloudKitAccountZone.CloudKitContainer.recordType:
addOrUpdateContainer(changedRecord)
default:
assertionFailure("Unknown record type: \(changedRecord.recordType)")
}
}
completion(.success(()))
}
@MainActor func updateFeed(_ feed: Feed, name: String?, editedName: String?, homePageURL: String?, containerExternalIDs: [String]) {
guard let account = account else { return }
feed.name = name

View File

@@ -84,7 +84,30 @@ final class CloudKitArticlesZone: CloudKitZone {
self.save(compressedRecords, completion: completion)
}
}
@MainActor func saveNewArticles(_ articles: Set<Article>) async throws {
guard !articles.isEmpty else {
return
}
let records: [CKRecord] = {
var recordsAccumulator = [CKRecord]()
let saveArticles = articles.filter { $0.status.read == false || $0.status.starred == true }
for saveArticle in saveArticles {
recordsAccumulator.append(makeStatusRecord(saveArticle))
recordsAccumulator.append(makeArticleRecord(saveArticle))
}
return recordsAccumulator
}()
compressionQueue.async {
let compressedRecords = self.compressArticleRecords(records)
self.save(compressedRecords, completion: completion)
}
}
func deleteArticles(_ feedExternalID: String, completion: @escaping ((Result<Void, Error>) -> Void)) {
let predicate = NSPredicate(format: "webFeedExternalID = %@", feedExternalID)
let ckQuery = CKQuery(recordType: CloudKitArticleStatus.recordType, predicate: predicate)

View File

@@ -28,6 +28,22 @@ class CloudKitArticlesZoneDelegate: CloudKitZoneDelegate, Logging {
self.articlesZone = articlesZone
}
func cloudKitWasChanged(updated: [CKRecord], deleted: [CloudKitRecordKey]) async throws {
try await withCheckedThrowingContinuation { continuation in
cloudKitWasChanged(updated: updated, deleted: deleted) { result in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
}
private extension CloudKitArticlesZoneDelegate {
func cloudKitWasChanged(updated: [CKRecord], deleted: [CloudKitRecordKey], completion: @escaping (Result<Void, Error>) -> Void) {
Task { @MainActor in
@@ -54,9 +70,6 @@ class CloudKitArticlesZoneDelegate: CloudKitZoneDelegate, Logging {
}
}
}
}
private extension CloudKitArticlesZoneDelegate {
func delete(recordKeys: [CloudKitRecordKey], pendingStarredStatusArticleIDs: Set<String>, completion: @escaping (Error?) -> Void) {
let receivedRecordIDs = recordKeys.filter({ $0.recordType == CloudKitArticlesZone.CloudKitArticleStatus.recordType }).map({ $0.recordID })

View File

@@ -32,16 +32,15 @@ class CloudKitReceiveStatusOperation: MainThreadOperation, Logging {
logger.debug("Refreshing article statuses...")
articlesZone.fetchChangesInZone() { result in
self.logger.debug("Done refreshing article statuses.")
switch result {
case .success:
Task { @MainActor in
do {
try await articlesZone.fetchChangesInZone()
self.logger.debug("Done refreshing article statuses.")
self.operationDelegate?.operationDidComplete(self)
case .failure(let error):
self.logger.error("Receive status error: \(error.localizedDescription, privacy: .public)")
} catch {
self.logger.error("Receive status error: \(error.localizedDescription, privacy: .public)")
self.operationDelegate?.cancelOperation(self)
}
}
}
}

View File

@@ -36,13 +36,11 @@ class CloudKitRemoteNotificationOperation: MainThreadOperation, Logging {
logger.debug("Processing remote notification...")
accountZone.receiveRemoteNotification(userInfo: userInfo) {
articlesZone.receiveRemoteNotification(userInfo: self.userInfo) {
self.logger.debug("Done processing remote notification.")
self.operationDelegate?.operationDidComplete(self)
}
Task { @MainActor in
await accountZone.receiveRemoteNotification(userInfo: self.userInfo)
await articlesZone.receiveRemoteNotification(userInfo: self.userInfo)
self.logger.debug("Done processing remote notification.")
self.operationDelegate?.operationDidComplete(self)
}
}
}

View File

@@ -148,46 +148,48 @@ final class FeedlyAccountDelegate: AccountDelegate, Logging {
func syncArticleStatus(for account: Account) async throws {
do {
try await sendArticleStatus(for: account)
try await refreshArticleStatus(for: account)
} catch {
self.logger.error("Failed to send article status for account \(String(describing: account.type), privacy: .public): \(error.localizedDescription, privacy: .public)")
throw error
}
}
func sendArticleStatus(for account: Account) async throws {
// Ensure remote articles have the same status as they do locally.
try await withCheckedThrowingContinuation { continuation in
sendArticleStatus(for: account) { result in
let send = FeedlySendArticleStatusesOperation(database: database, service: caller)
send.completionBlock = { operation in
continuation.resume()
}
operationQueue.add(send)
}
}
func refreshArticleStatus(for account: Account) async throws {
try await withCheckedThrowingContinuation { continuation in
self.refreshArticleStatus(for: account) { result in
switch result {
case .success:
self.refreshArticleStatus(for: account) { result in
switch result {
case .success:
continuation.resume()
case .failure(let error):
self.logger.error("Failed to refresh article status for account \(String(describing: account.type), privacy: .public): \(error.localizedDescription, privacy: .public)")
continuation.resume(throwing: error)
}
}
continuation.resume()
case .failure(let error):
self.logger.error("Failed to send article status for account \(String(describing: account.type), privacy: .public): \(error.localizedDescription, privacy: .public)")
continuation.resume(throwing: error)
}
}
}
}
func sendArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
// Ensure remote articles have the same status as they do locally.
let send = FeedlySendArticleStatusesOperation(database: database, service: caller)
send.completionBlock = { operation in
// TODO: not call with success if operation was canceled? Not sure.
DispatchQueue.main.async {
completion(.success(()))
}
}
operationQueue.add(send)
}
/// Attempts to ensure local articles have the same status as they do remotely.
/// So if the user is using another client roughly simultaneously with this app,
/// this app does its part to ensure the articles have a consistent status between both.
///
/// - Parameter account: The account whose articles have a remote status.
/// - Parameter completion: Call on the main queue.
func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
private func refreshArticleStatus(for account: Account, completion: @escaping ((Result<Void, Error>) -> Void)) {
guard let credentials = credentials else {
return completion(.success(()))
}
@@ -557,7 +559,7 @@ final class FeedlyAccountDelegate: AccountDelegate, Logging {
try await self.database.insertStatuses(syncStatuses)
let count = try await self.database.selectPendingCount()
if count > 100 {
self.sendArticleStatus(for: account) { _ in }
try? await self.sendArticleStatus(for: account)
}
completion(.success(()))
} catch {

View File

@@ -11,7 +11,7 @@ let package = Package(
targets: ["Articles"]),
],
dependencies: [
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "2.0.1")),
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "3.0.0")),
],
targets: [
.target(

View File

@@ -4,7 +4,7 @@
import PackageDescription
var dependencies: [Package.Dependency] = [
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "2.0.1")),
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "3.0.0")),
.package(url: "https://github.com/Ranchero-Software/RSDatabase.git", .upToNextMajor(from: "2.0.0")),
.package(url: "https://github.com/Ranchero-Software/RSParser.git", .upToNextMajor(from: "2.0.2")),
]

View File

@@ -13,7 +13,7 @@ let package = Package(
targets: ["FeedFinder"]),
],
dependencies: [
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "2.0.1")),
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "3.0.0")),
.package(url: "https://github.com/Ranchero-Software/RSParser.git", .upToNextMajor(from: "2.0.2")),
.package(url: "https://github.com/Ranchero-Software/RSWeb.git", .upToNextMajor(from: "1.0.0")),
.package(path: "../AccountError"),

View File

@@ -5272,7 +5272,7 @@
repositoryURL = "https://github.com/Ranchero-Software/RSCore.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.0.1;
minimumVersion = 3.0.0;
};
};
510ECA4024D1DCD0001C31A6 /* XCRemoteSwiftPackageReference "RSTree" */ = {

View File

@@ -14,8 +14,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/Ranchero-Software/RSCore.git",
"state" : {
"revision" : "dcaa40ceb2c8acd182fbcd69d1f8e56df97e38b1",
"version" : "2.0.1"
"revision" : "cee6d96e036cc4ad08ad5f79364f87f9c291c43c",
"version" : "3.0.0"
}
},
{

View File

@@ -15,7 +15,7 @@ let package = Package(
dependencies: [
.package(path: "../../Secrets"),
.package(url: "https://github.com/Ranchero-Software/RSWeb.git", .upToNextMajor(from: "1.0.0")),
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "2.0.1")),
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "3.0.0")),
.package(url: "https://github.com/Ranchero-Software/RSParser.git", .upToNextMajor(from: "2.0.2"))
],
targets: [

View File

@@ -13,7 +13,7 @@ let package = Package(
targets: ["LocalAccount"]),
],
dependencies: [
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "2.0.1")),
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "3.0.0")),
.package(url: "https://github.com/Ranchero-Software/RSWeb.git", .upToNextMajor(from: "1.0.0")),
.package(url: "https://github.com/Ranchero-Software/RSParser.git", .upToNextMajor(from: "2.0.2")),
],

View File

@@ -15,7 +15,7 @@ let package = Package(
dependencies: [
.package(path: "../../Secrets"),
.package(url: "https://github.com/Ranchero-Software/RSWeb.git", .upToNextMajor(from: "1.0.0")),
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "2.0.1")),
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "3.0.0")),
.package(url: "https://github.com/Ranchero-Software/RSParser.git", .upToNextMajor(from: "2.0.2"))
],
targets: [

View File

@@ -16,7 +16,7 @@ let package = Package(
.package(path: "../../Secrets"),
.package(path: "../../AccountError"),
.package(url: "https://github.com/Ranchero-Software/RSWeb.git", .upToNextMajor(from: "1.0.0")),
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "2.0.1")),
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "3.0.0")),
.package(url: "https://github.com/Ranchero-Software/RSParser.git", .upToNextMajor(from: "2.0.2"))
],
targets: [

View File

@@ -2,7 +2,7 @@
import PackageDescription
var dependencies: [Package.Dependency] = [
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "2.0.1")),
.package(url: "https://github.com/Ranchero-Software/RSCore.git", .upToNextMajor(from: "3.0.0")),
.package(url: "https://github.com/Ranchero-Software/RSDatabase.git", .upToNextMajor(from: "2.0.0")),
]