Improves error handling when fetching multiple sources

Fetching sources is no longer all or nothing. Now if a source cannot be fetched, it won’t prevent other sources from being updated.
This commit is contained in:
Riley Testut
2020-08-27 16:23:50 -07:00
parent ad33f6e1fb
commit b7564207b3
11 changed files with 262 additions and 116 deletions

View File

@@ -197,15 +197,16 @@ extension AppManager
self.run([fetchSourceOperation], context: nil)
}
func fetchSources(completionHandler: @escaping (Result<Set<Source>, Error>) -> Void)
func fetchSources(completionHandler: @escaping (Result<(Set<Source>, NSManagedObjectContext), FetchSourcesError>) -> Void)
{
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
let sources = Source.all(in: context)
guard !sources.isEmpty else { return completionHandler(.failure(OperationError.noSources)) }
guard !sources.isEmpty else { return completionHandler(.failure(.init(OperationError.noSources))) }
let dispatchGroup = DispatchGroup()
var fetchedSources = Set<Source>()
var error: Error?
var errors = [Source: Error]()
let managedObjectContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
@@ -216,8 +217,10 @@ extension AppManager
fetchSourceOperation.resultHandler = { (result) in
switch result
{
case .failure(let e): error = e
case .success(let source): fetchedSources.insert(source)
case .failure(let error):
let source = managedObjectContext.object(with: source.objectID) as! Source
errors[source] = error
}
dispatchGroup.leave()
@@ -227,14 +230,15 @@ extension AppManager
}
dispatchGroup.notify(queue: .global()) {
if let error = error
{
completionHandler(.failure(error))
}
else
{
managedObjectContext.perform {
completionHandler(.success(fetchedSources))
managedObjectContext.perform {
if !errors.isEmpty
{
let sources = Set(sources.compactMap { managedObjectContext.object(with: $0.objectID) as? Source })
completionHandler(.failure(.init(sources: sources, errors: errors, context: managedObjectContext)))
}
else
{
completionHandler(.success((fetchedSources, managedObjectContext)))
}
}

View File

@@ -0,0 +1,84 @@
//
// AppManagerErrors.swift
// AltStore
//
// Created by Riley Testut on 8/27/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
import CoreData
extension AppManager
{
struct FetchSourcesError: LocalizedError, CustomNSError
{
var primaryError: Error?
var sources: Set<Source>?
var errors = [Source: Error]()
var managedObjectContext: NSManagedObjectContext?
var errorDescription: String? {
if let error = self.primaryError
{
return error.localizedDescription
}
else
{
var localizedDescription: String?
self.managedObjectContext?.performAndWait {
if self.sources?.count == 1
{
localizedDescription = NSLocalizedString("Could not refresh store.", comment: "")
}
else if self.errors.count == 1
{
guard let source = self.errors.keys.first else { return }
localizedDescription = String(format: NSLocalizedString("Could not refresh source “%@”.", comment: ""), source.name)
}
else
{
localizedDescription = String(format: NSLocalizedString("Could not refresh %@ sources.", comment: ""), NSNumber(value: self.errors.count))
}
}
return localizedDescription
}
}
var recoverySuggestion: String? {
if let error = self.primaryError as NSError?
{
return error.localizedRecoverySuggestion
}
else if self.errors.count == 1
{
return nil
}
else
{
return NSLocalizedString("Tap to view source errors.", comment: "")
}
}
var errorUserInfo: [String : Any] {
guard let error = self.errors.values.first, self.errors.count == 1 else { return [:] }
return [NSUnderlyingErrorKey: error]
}
init(_ error: Error)
{
self.primaryError = error
}
init(sources: Set<Source>, errors: [Source: Error], context: NSManagedObjectContext)
{
self.sources = sources
self.errors = errors
self.managedObjectContext = context
}
}
}