mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-19 19:53:25 +01:00
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:
@@ -159,6 +159,7 @@
|
|||||||
BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */ = {isa = PBXBuildFile; fileRef = BF770E6822BD57DD002A40FE /* Silence.m4a */; };
|
BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */ = {isa = PBXBuildFile; fileRef = BF770E6822BD57DD002A40FE /* Silence.m4a */; };
|
||||||
BF7C627223DBB3B400515A2D /* AltStore2ToAltStore3.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF7C627123DBB3B400515A2D /* AltStore2ToAltStore3.xcmappingmodel */; };
|
BF7C627223DBB3B400515A2D /* AltStore2ToAltStore3.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF7C627123DBB3B400515A2D /* AltStore2ToAltStore3.xcmappingmodel */; };
|
||||||
BF7C627423DBB78C00515A2D /* InstalledAppPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7C627323DBB78C00515A2D /* InstalledAppPolicy.swift */; };
|
BF7C627423DBB78C00515A2D /* InstalledAppPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7C627323DBB78C00515A2D /* InstalledAppPolicy.swift */; };
|
||||||
|
BF88F97224F8727D00BB75DF /* AppManagerErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF88F97124F8727D00BB75DF /* AppManagerErrors.swift */; };
|
||||||
BF8CAE452489E772004D6CCE /* AnisetteDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CAE422489E772004D6CCE /* AnisetteDataManager.swift */; };
|
BF8CAE452489E772004D6CCE /* AnisetteDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CAE422489E772004D6CCE /* AnisetteDataManager.swift */; };
|
||||||
BF8CAE462489E772004D6CCE /* AppManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CAE432489E772004D6CCE /* AppManager.swift */; };
|
BF8CAE462489E772004D6CCE /* AppManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CAE432489E772004D6CCE /* AppManager.swift */; };
|
||||||
BF8CAE472489E772004D6CCE /* RequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CAE442489E772004D6CCE /* RequestHandler.swift */; };
|
BF8CAE472489E772004D6CCE /* RequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CAE442489E772004D6CCE /* RequestHandler.swift */; };
|
||||||
@@ -525,6 +526,7 @@
|
|||||||
BF7C627023DBB33300515A2D /* AltStore 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 3.xcdatamodel"; sourceTree = "<group>"; };
|
BF7C627023DBB33300515A2D /* AltStore 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 3.xcdatamodel"; sourceTree = "<group>"; };
|
||||||
BF7C627123DBB3B400515A2D /* AltStore2ToAltStore3.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore2ToAltStore3.xcmappingmodel; sourceTree = "<group>"; };
|
BF7C627123DBB3B400515A2D /* AltStore2ToAltStore3.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore2ToAltStore3.xcmappingmodel; sourceTree = "<group>"; };
|
||||||
BF7C627323DBB78C00515A2D /* InstalledAppPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstalledAppPolicy.swift; sourceTree = "<group>"; };
|
BF7C627323DBB78C00515A2D /* InstalledAppPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstalledAppPolicy.swift; sourceTree = "<group>"; };
|
||||||
|
BF88F97124F8727D00BB75DF /* AppManagerErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppManagerErrors.swift; sourceTree = "<group>"; };
|
||||||
BF8CAE422489E772004D6CCE /* AnisetteDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnisetteDataManager.swift; sourceTree = "<group>"; };
|
BF8CAE422489E772004D6CCE /* AnisetteDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnisetteDataManager.swift; sourceTree = "<group>"; };
|
||||||
BF8CAE432489E772004D6CCE /* AppManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppManager.swift; sourceTree = "<group>"; };
|
BF8CAE432489E772004D6CCE /* AppManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppManager.swift; sourceTree = "<group>"; };
|
||||||
BF8CAE442489E772004D6CCE /* RequestHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestHandler.swift; sourceTree = "<group>"; };
|
BF8CAE442489E772004D6CCE /* RequestHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestHandler.swift; sourceTree = "<group>"; };
|
||||||
@@ -1211,6 +1213,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */,
|
BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */,
|
||||||
|
BF88F97124F8727D00BB75DF /* AppManagerErrors.swift */,
|
||||||
);
|
);
|
||||||
path = "Managing Apps";
|
path = "Managing Apps";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2069,6 +2072,7 @@
|
|||||||
BF100C50232D7CD1006A8926 /* AltStoreToAltStore2.xcmappingmodel in Sources */,
|
BF100C50232D7CD1006A8926 /* AltStoreToAltStore2.xcmappingmodel in Sources */,
|
||||||
BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */,
|
BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */,
|
||||||
BFC57A6E2416FC5D00EB891E /* InstalledAppsCollectionHeaderView.swift in Sources */,
|
BFC57A6E2416FC5D00EB891E /* InstalledAppsCollectionHeaderView.swift in Sources */,
|
||||||
|
BF88F97224F8727D00BB75DF /* AppManagerErrors.swift in Sources */,
|
||||||
BF6C8FAE2429597900125131 /* BannerCollectionViewCell.swift in Sources */,
|
BF6C8FAE2429597900125131 /* BannerCollectionViewCell.swift in Sources */,
|
||||||
BFBBE2DD22931B20002097FA /* AltStore.xcdatamodeld in Sources */,
|
BFBBE2DD22931B20002097FA /* AltStore.xcdatamodeld in Sources */,
|
||||||
BF56D2AA23DF88310006506D /* AppID.swift in Sources */,
|
BF56D2AA23DF88310006506D /* AppID.swift in Sources */,
|
||||||
|
|||||||
@@ -302,13 +302,11 @@ private extension AppDelegate
|
|||||||
dispatchGroup.enter()
|
dispatchGroup.enter()
|
||||||
|
|
||||||
AppManager.shared.fetchSources() { (result) in
|
AppManager.shared.fetchSources() { (result) in
|
||||||
fetchSourcesResult = result
|
fetchSourcesResult = result.map { $0.0 }.mapError { $0 as Error }
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let sources = try result.get()
|
let (_, context) = try result.get()
|
||||||
|
|
||||||
guard let context = sources.first?.managedObjectContext else { return }
|
|
||||||
|
|
||||||
let previousUpdatesFetchRequest = InstalledApp.updatesFetchRequest() as! NSFetchRequest<NSFetchRequestResult>
|
let previousUpdatesFetchRequest = InstalledApp.updatesFetchRequest() as! NSFetchRequest<NSFetchRequestResult>
|
||||||
previousUpdatesFetchRequest.includesPendingChanges = false
|
previousUpdatesFetchRequest.includesPendingChanges = false
|
||||||
|
|||||||
@@ -178,20 +178,26 @@ private extension BrowseViewController
|
|||||||
AppManager.shared.fetchSources() { (result) in
|
AppManager.shared.fetchSources() { (result) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let sources = try result.get()
|
do
|
||||||
try sources.first?.managedObjectContext?.save()
|
{
|
||||||
|
let (_, context) = try result.get()
|
||||||
|
try context.save()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.loadingState = .finished(.success(()))
|
self.loadingState = .finished(.success(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch let error as NSError
|
catch let error as AppManager.FetchSourcesError
|
||||||
|
{
|
||||||
|
try error.managedObjectContext?.save()
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
{
|
{
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if self.dataSource.itemCount > 0
|
if self.dataSource.itemCount > 0
|
||||||
{
|
{
|
||||||
let error = error.withLocalizedFailure(NSLocalizedString("Failed to Fetch Sources", comment: ""))
|
|
||||||
|
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error)
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class ToastView: RSTToastView
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
text = error.localizedDescription
|
text = error.localizedDescription
|
||||||
detailText = underlyingError?.localizedDescription
|
detailText = underlyingError?.localizedDescription ?? error.localizedRecoverySuggestion
|
||||||
}
|
}
|
||||||
|
|
||||||
self.init(text: text, detailText: detailText)
|
self.init(text: text, detailText: detailText)
|
||||||
|
|||||||
@@ -197,15 +197,16 @@ extension AppManager
|
|||||||
self.run([fetchSourceOperation], context: nil)
|
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
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
let sources = Source.all(in: context)
|
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()
|
let dispatchGroup = DispatchGroup()
|
||||||
var fetchedSources = Set<Source>()
|
var fetchedSources = Set<Source>()
|
||||||
var error: Error?
|
|
||||||
|
var errors = [Source: Error]()
|
||||||
|
|
||||||
let managedObjectContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
let managedObjectContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||||
|
|
||||||
@@ -216,8 +217,10 @@ extension AppManager
|
|||||||
fetchSourceOperation.resultHandler = { (result) in
|
fetchSourceOperation.resultHandler = { (result) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let e): error = e
|
|
||||||
case .success(let source): fetchedSources.insert(source)
|
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()
|
dispatchGroup.leave()
|
||||||
@@ -227,14 +230,15 @@ extension AppManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatchGroup.notify(queue: .global()) {
|
dispatchGroup.notify(queue: .global()) {
|
||||||
if let error = error
|
managedObjectContext.perform {
|
||||||
|
if !errors.isEmpty
|
||||||
{
|
{
|
||||||
completionHandler(.failure(error))
|
let sources = Set(sources.compactMap { managedObjectContext.object(with: $0.objectID) as? Source })
|
||||||
|
completionHandler(.failure(.init(sources: sources, errors: errors, context: managedObjectContext)))
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
managedObjectContext.perform {
|
completionHandler(.success((fetchedSources, managedObjectContext)))
|
||||||
completionHandler(.success(fetchedSources))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
84
AltStore/Managing Apps/AppManagerErrors.swift
Normal file
84
AltStore/Managing Apps/AppManagerErrors.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -67,15 +67,25 @@ class AppPermission: NSManagedObject, Decodable, Fetchable
|
|||||||
{
|
{
|
||||||
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
|
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
|
||||||
|
|
||||||
super.init(entity: AppPermission.entity(), insertInto: nil)
|
super.init(entity: AppPermission.entity(), insertInto: context)
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
self.usageDescription = try container.decode(String.self, forKey: .usageDescription)
|
self.usageDescription = try container.decode(String.self, forKey: .usageDescription)
|
||||||
|
|
||||||
let rawType = try container.decode(String.self, forKey: .type)
|
let rawType = try container.decode(String.self, forKey: .type)
|
||||||
self.type = ALTAppPermissionType(rawValue: rawType)
|
self.type = ALTAppPermissionType(rawValue: rawType)
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
if let context = self.managedObjectContext
|
||||||
|
{
|
||||||
|
context.delete(self)
|
||||||
|
}
|
||||||
|
|
||||||
context.insert(self)
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,8 +88,10 @@ class Source: NSManagedObject, Fetchable, Decodable
|
|||||||
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
|
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
|
||||||
guard let sourceURL = decoder.sourceURL else { preconditionFailure("Decoder must have non-nil sourceURL.") }
|
guard let sourceURL = decoder.sourceURL else { preconditionFailure("Decoder must have non-nil sourceURL.") }
|
||||||
|
|
||||||
super.init(entity: Source.entity(), insertInto: nil)
|
super.init(entity: Source.entity(), insertInto: context)
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
self.sourceURL = sourceURL
|
self.sourceURL = sourceURL
|
||||||
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
@@ -107,6 +109,7 @@ class Source: NSManagedObject, Fetchable, Decodable
|
|||||||
app.sourceIdentifier = self.identifier
|
app.sourceIdentifier = self.identifier
|
||||||
app.sortIndex = Int32(index)
|
app.sortIndex = Int32(index)
|
||||||
}
|
}
|
||||||
|
self._apps = NSMutableOrderedSet(array: apps)
|
||||||
|
|
||||||
let newsItems = try container.decodeIfPresent([NewsItem].self, forKey: .news) ?? []
|
let newsItems = try container.decodeIfPresent([NewsItem].self, forKey: .news) ?? []
|
||||||
for (index, item) in newsItems.enumerated()
|
for (index, item) in newsItems.enumerated()
|
||||||
@@ -115,8 +118,6 @@ class Source: NSManagedObject, Fetchable, Decodable
|
|||||||
item.sortIndex = Int32(index)
|
item.sortIndex = Int32(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
context.insert(self)
|
|
||||||
|
|
||||||
for newsItem in newsItems
|
for newsItem in newsItems
|
||||||
{
|
{
|
||||||
guard let appID = newsItem.appID else { continue }
|
guard let appID = newsItem.appID else { continue }
|
||||||
@@ -130,11 +131,18 @@ class Source: NSManagedObject, Fetchable, Decodable
|
|||||||
newsItem.storeApp = nil
|
newsItem.storeApp = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must assign after we're inserted into context.
|
|
||||||
self._apps = NSMutableOrderedSet(array: apps)
|
|
||||||
self._newsItems = NSMutableOrderedSet(array: newsItems)
|
self._newsItems = NSMutableOrderedSet(array: newsItems)
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
if let context = self.managedObjectContext
|
||||||
|
{
|
||||||
|
context.delete(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Source
|
extension Source
|
||||||
|
|||||||
@@ -104,8 +104,11 @@ class StoreApp: NSManagedObject, Decodable, Fetchable
|
|||||||
{
|
{
|
||||||
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
|
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
|
||||||
|
|
||||||
super.init(entity: StoreApp.entity(), insertInto: nil)
|
// Must initialize with context in order for child context saves to work correctly.
|
||||||
|
super.init(entity: StoreApp.entity(), insertInto: context)
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
self.name = try container.decode(String.self, forKey: .name)
|
self.name = try container.decode(String.self, forKey: .name)
|
||||||
self.bundleIdentifier = try container.decode(String.self, forKey: .bundleIdentifier)
|
self.bundleIdentifier = try container.decode(String.self, forKey: .bundleIdentifier)
|
||||||
@@ -136,12 +139,18 @@ class StoreApp: NSManagedObject, Decodable, Fetchable
|
|||||||
self.isBeta = try container.decodeIfPresent(Bool.self, forKey: .isBeta) ?? false
|
self.isBeta = try container.decodeIfPresent(Bool.self, forKey: .isBeta) ?? false
|
||||||
|
|
||||||
let permissions = try container.decodeIfPresent([AppPermission].self, forKey: .permissions) ?? []
|
let permissions = try container.decodeIfPresent([AppPermission].self, forKey: .permissions) ?? []
|
||||||
|
|
||||||
context.insert(self)
|
|
||||||
|
|
||||||
// Must assign after we're inserted into context.
|
|
||||||
self._permissions = NSOrderedSet(array: permissions)
|
self._permissions = NSOrderedSet(array: permissions)
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
if let context = self.managedObjectContext
|
||||||
|
{
|
||||||
|
context.delete(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StoreApp
|
extension StoreApp
|
||||||
|
|||||||
@@ -177,20 +177,26 @@ private extension NewsViewController
|
|||||||
AppManager.shared.fetchSources() { (result) in
|
AppManager.shared.fetchSources() { (result) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let sources = try result.get()
|
do
|
||||||
try sources.first?.managedObjectContext?.save()
|
{
|
||||||
|
let (_, context) = try result.get()
|
||||||
|
try context.save()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.loadingState = .finished(.success(()))
|
self.loadingState = .finished(.success(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch let error as NSError
|
catch let error as AppManager.FetchSourcesError
|
||||||
|
{
|
||||||
|
try error.managedObjectContext?.save()
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
{
|
{
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if self.dataSource.itemCount > 0
|
if self.dataSource.itemCount > 0
|
||||||
{
|
{
|
||||||
let error = error.withLocalizedFailure(NSLocalizedString("Failed to Fetch Sources", comment: ""))
|
|
||||||
|
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error)
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,10 @@ class FetchSourceOperation: ResultOperation<Source>
|
|||||||
super.main()
|
super.main()
|
||||||
|
|
||||||
let dataTask = self.session.dataTask(with: self.sourceURL) { (data, response, error) in
|
let dataTask = self.session.dataTask(with: self.sourceURL) { (data, response, error) in
|
||||||
self.managedObjectContext.perform {
|
|
||||||
|
let childContext = DatabaseManager.shared.persistentContainer.newBackgroundContext(withParent: self.managedObjectContext)
|
||||||
|
childContext.mergePolicy = NSOverwriteMergePolicy
|
||||||
|
childContext.perform {
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let (data, _) = try Result((data, response), error).get()
|
let (data, _) = try Result((data, response), error).get()
|
||||||
@@ -68,24 +71,38 @@ class FetchSourceOperation: ResultOperation<Source>
|
|||||||
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Date is in invalid format.")
|
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Date is in invalid format.")
|
||||||
})
|
})
|
||||||
|
|
||||||
decoder.managedObjectContext = self.managedObjectContext
|
decoder.managedObjectContext = childContext
|
||||||
decoder.sourceURL = self.sourceURL
|
decoder.sourceURL = self.sourceURL
|
||||||
|
|
||||||
let source = try decoder.decode(Source.self, from: data)
|
let source = try decoder.decode(Source.self, from: data)
|
||||||
|
let identifier = source.identifier
|
||||||
|
|
||||||
if source.identifier == Source.altStoreIdentifier, let patreonAccessToken = source.userInfo?[.patreonAccessToken]
|
if identifier == Source.altStoreIdentifier, let patreonAccessToken = source.userInfo?[.patreonAccessToken]
|
||||||
{
|
{
|
||||||
Keychain.shared.patreonCreatorAccessToken = patreonAccessToken
|
Keychain.shared.patreonCreatorAccessToken = patreonAccessToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try childContext.save()
|
||||||
|
|
||||||
|
self.managedObjectContext.perform {
|
||||||
|
if let source = Source.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Source.identifier), identifier), in: self.managedObjectContext)
|
||||||
|
{
|
||||||
self.finish(.success(source))
|
self.finish(.success(source))
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.finish(.failure(OperationError.noSources))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
self.managedObjectContext.perform {
|
||||||
self.finish(.failure(error))
|
self.finish(.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.progress.addChild(dataTask.progress, withPendingUnitCount: 1)
|
self.progress.addChild(dataTask.progress, withPendingUnitCount: 1)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user