mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-18 03:03:31 +01:00
[AltStore] Refactors fetch apps logic to use Source model objects
This commit is contained in:
@@ -109,7 +109,6 @@
|
|||||||
BF770E5822BC3D0F002A40FE /* OperationGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E5722BC3D0F002A40FE /* OperationGroup.swift */; };
|
BF770E5822BC3D0F002A40FE /* OperationGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E5722BC3D0F002A40FE /* OperationGroup.swift */; };
|
||||||
BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E6622BD57C3002A40FE /* BackgroundTaskManager.swift */; };
|
BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E6622BD57C3002A40FE /* BackgroundTaskManager.swift */; };
|
||||||
BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */ = {isa = PBXBuildFile; fileRef = BF770E6822BD57DD002A40FE /* Silence.m4a */; };
|
BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */ = {isa = PBXBuildFile; fileRef = BF770E6822BD57DD002A40FE /* Silence.m4a */; };
|
||||||
BF7B9EF322B82B1F0042C873 /* FetchAppsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7B9EF222B82B1F0042C873 /* FetchAppsOperation.swift */; };
|
|
||||||
BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8F69C122E659F700049BA1 /* AppContentViewController.swift */; };
|
BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8F69C122E659F700049BA1 /* AppContentViewController.swift */; };
|
||||||
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8F69C322E662D300049BA1 /* AppViewController.swift */; };
|
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8F69C322E662D300049BA1 /* AppViewController.swift */; };
|
||||||
BF9ABA4522DCFF43008935CF /* BrowseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4422DCFF43008935CF /* BrowseViewController.swift */; };
|
BF9ABA4522DCFF43008935CF /* BrowseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4422DCFF43008935CF /* BrowseViewController.swift */; };
|
||||||
@@ -176,6 +175,8 @@
|
|||||||
BFDB6A0B22AAEDB7007EA6D6 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0A22AAEDB7007EA6D6 /* Operation.swift */; };
|
BFDB6A0B22AAEDB7007EA6D6 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0A22AAEDB7007EA6D6 /* Operation.swift */; };
|
||||||
BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0C22AAFC19007EA6D6 /* OperationError.swift */; };
|
BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0C22AAFC19007EA6D6 /* OperationError.swift */; };
|
||||||
BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0E22AB2776007EA6D6 /* SendAppOperation.swift */; };
|
BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0E22AB2776007EA6D6 /* SendAppOperation.swift */; };
|
||||||
|
BFE338DD22F0E7F3002E24B9 /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338DC22F0E7F3002E24B9 /* Source.swift */; };
|
||||||
|
BFE338DF22F0EADB002E24B9 /* FetchSourceOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338DE22F0EADB002E24B9 /* FetchSourceOperation.swift */; };
|
||||||
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338E722F10E56002E24B9 /* LaunchViewController.swift */; };
|
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338E722F10E56002E24B9 /* LaunchViewController.swift */; };
|
||||||
BFE6325A22A83BEB00F30809 /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFE6325922A83BEB00F30809 /* Authentication.storyboard */; };
|
BFE6325A22A83BEB00F30809 /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFE6325922A83BEB00F30809 /* Authentication.storyboard */; };
|
||||||
BFE6325C22A83C0100F30809 /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6325B22A83C0100F30809 /* AuthenticationViewController.swift */; };
|
BFE6325C22A83C0100F30809 /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6325B22A83C0100F30809 /* AuthenticationViewController.swift */; };
|
||||||
@@ -356,7 +357,6 @@
|
|||||||
BF770E5722BC3D0F002A40FE /* OperationGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationGroup.swift; sourceTree = "<group>"; };
|
BF770E5722BC3D0F002A40FE /* OperationGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationGroup.swift; sourceTree = "<group>"; };
|
||||||
BF770E6622BD57C3002A40FE /* BackgroundTaskManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundTaskManager.swift; sourceTree = "<group>"; };
|
BF770E6622BD57C3002A40FE /* BackgroundTaskManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundTaskManager.swift; sourceTree = "<group>"; };
|
||||||
BF770E6822BD57DD002A40FE /* Silence.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Silence.m4a; sourceTree = "<group>"; };
|
BF770E6822BD57DD002A40FE /* Silence.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Silence.m4a; sourceTree = "<group>"; };
|
||||||
BF7B9EF222B82B1F0042C873 /* FetchAppsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchAppsOperation.swift; sourceTree = "<group>"; };
|
|
||||||
BF8F69C122E659F700049BA1 /* AppContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContentViewController.swift; sourceTree = "<group>"; };
|
BF8F69C122E659F700049BA1 /* AppContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContentViewController.swift; sourceTree = "<group>"; };
|
||||||
BF8F69C322E662D300049BA1 /* AppViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewController.swift; sourceTree = "<group>"; };
|
BF8F69C322E662D300049BA1 /* AppViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewController.swift; sourceTree = "<group>"; };
|
||||||
BF9ABA4422DCFF43008935CF /* BrowseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowseViewController.swift; sourceTree = "<group>"; };
|
BF9ABA4422DCFF43008935CF /* BrowseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowseViewController.swift; sourceTree = "<group>"; };
|
||||||
@@ -427,6 +427,8 @@
|
|||||||
BFDB6A0A22AAEDB7007EA6D6 /* Operation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = "<group>"; };
|
BFDB6A0A22AAEDB7007EA6D6 /* Operation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = "<group>"; };
|
||||||
BFDB6A0C22AAFC19007EA6D6 /* OperationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationError.swift; sourceTree = "<group>"; };
|
BFDB6A0C22AAFC19007EA6D6 /* OperationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationError.swift; sourceTree = "<group>"; };
|
||||||
BFDB6A0E22AB2776007EA6D6 /* SendAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendAppOperation.swift; sourceTree = "<group>"; };
|
BFDB6A0E22AB2776007EA6D6 /* SendAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendAppOperation.swift; sourceTree = "<group>"; };
|
||||||
|
BFE338DC22F0E7F3002E24B9 /* Source.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Source.swift; sourceTree = "<group>"; };
|
||||||
|
BFE338DE22F0EADB002E24B9 /* FetchSourceOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchSourceOperation.swift; sourceTree = "<group>"; };
|
||||||
BFE338E722F10E56002E24B9 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = "<group>"; };
|
BFE338E722F10E56002E24B9 /* LaunchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = "<group>"; };
|
||||||
BFE6325922A83BEB00F30809 /* Authentication.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Authentication.storyboard; sourceTree = "<group>"; };
|
BFE6325922A83BEB00F30809 /* Authentication.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Authentication.storyboard; sourceTree = "<group>"; };
|
||||||
BFE6325B22A83C0100F30809 /* AuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = "<group>"; };
|
BFE6325B22A83C0100F30809 /* AuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = "<group>"; };
|
||||||
@@ -851,6 +853,7 @@
|
|||||||
BFBBE2DE22931F73002097FA /* App.swift */,
|
BFBBE2DE22931F73002097FA /* App.swift */,
|
||||||
BF3D648722E79A3700E9056B /* AppPermission.swift */,
|
BF3D648722E79A3700E9056B /* AppPermission.swift */,
|
||||||
BFBBE2E022931F81002097FA /* InstalledApp.swift */,
|
BFBBE2E022931F81002097FA /* InstalledApp.swift */,
|
||||||
|
BFE338DC22F0E7F3002E24B9 /* Source.swift */,
|
||||||
BFE6326522A857C100F30809 /* Team.swift */,
|
BFE6326522A857C100F30809 /* Team.swift */,
|
||||||
);
|
);
|
||||||
path = Model;
|
path = Model;
|
||||||
@@ -905,7 +908,7 @@
|
|||||||
BFDB6A0722AAED73007EA6D6 /* ResignAppOperation.swift */,
|
BFDB6A0722AAED73007EA6D6 /* ResignAppOperation.swift */,
|
||||||
BFDB6A0E22AB2776007EA6D6 /* SendAppOperation.swift */,
|
BFDB6A0E22AB2776007EA6D6 /* SendAppOperation.swift */,
|
||||||
BF770E5022BB1CF6002A40FE /* InstallAppOperation.swift */,
|
BF770E5022BB1CF6002A40FE /* InstallAppOperation.swift */,
|
||||||
BF7B9EF222B82B1F0042C873 /* FetchAppsOperation.swift */,
|
BFE338DE22F0EADB002E24B9 /* FetchSourceOperation.swift */,
|
||||||
);
|
);
|
||||||
path = Operations;
|
path = Operations;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1274,7 +1277,6 @@
|
|||||||
files = (
|
files = (
|
||||||
BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */,
|
BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */,
|
||||||
BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */,
|
BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */,
|
||||||
BF7B9EF322B82B1F0042C873 /* FetchAppsOperation.swift in Sources */,
|
|
||||||
BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */,
|
BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */,
|
||||||
BFE6325E22A8497000F30809 /* SelectTeamViewController.swift in Sources */,
|
BFE6325E22A8497000F30809 /* SelectTeamViewController.swift in Sources */,
|
||||||
BFD2478F2284C8F900981D42 /* Button.swift in Sources */,
|
BFD2478F2284C8F900981D42 /* Button.swift in Sources */,
|
||||||
@@ -1283,11 +1285,13 @@
|
|||||||
BFB11692229322E400BB457C /* DatabaseManager.swift in Sources */,
|
BFB11692229322E400BB457C /* DatabaseManager.swift in Sources */,
|
||||||
BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */,
|
BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */,
|
||||||
BFBBE2E122931F81002097FA /* InstalledApp.swift in Sources */,
|
BFBBE2E122931F81002097FA /* InstalledApp.swift in Sources */,
|
||||||
|
BFE338DF22F0EADB002E24B9 /* FetchSourceOperation.swift in Sources */,
|
||||||
BFBBE2DF22931F73002097FA /* App.swift in Sources */,
|
BFBBE2DF22931F73002097FA /* App.swift in Sources */,
|
||||||
BF9ABA4522DCFF43008935CF /* BrowseViewController.swift in Sources */,
|
BF9ABA4522DCFF43008935CF /* BrowseViewController.swift in Sources */,
|
||||||
BF43002E22A714AF0051E2BC /* Keychain.swift in Sources */,
|
BF43002E22A714AF0051E2BC /* Keychain.swift in Sources */,
|
||||||
BF770E5422BC044E002A40FE /* AppOperationContext.swift in Sources */,
|
BF770E5422BC044E002A40FE /* AppOperationContext.swift in Sources */,
|
||||||
BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */,
|
BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */,
|
||||||
|
BFE338DD22F0E7F3002E24B9 /* Source.swift in Sources */,
|
||||||
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */,
|
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */,
|
||||||
BFE6326822A858F300F30809 /* Account.swift in Sources */,
|
BFE6326822A858F300F30809 /* Account.swift in Sources */,
|
||||||
BFE6326622A857C200F30809 /* Team.swift in Sources */,
|
BFE6326622A857C200F30809 /* Team.swift in Sources */,
|
||||||
|
|||||||
@@ -176,22 +176,22 @@ extension AppDelegate
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var fetchAppsResult: Result<[App], Error>?
|
var fetchSourceResult: Result<Source, Error>?
|
||||||
var serversResult: Result<Void, Error>?
|
var serversResult: Result<Void, Error>?
|
||||||
|
|
||||||
let dispatchGroup = DispatchGroup()
|
let dispatchGroup = DispatchGroup()
|
||||||
dispatchGroup.enter()
|
dispatchGroup.enter()
|
||||||
dispatchGroup.enter()
|
dispatchGroup.enter()
|
||||||
|
|
||||||
AppManager.shared.fetchApps() { (result) in
|
AppManager.shared.fetchSource() { (result) in
|
||||||
fetchAppsResult = result
|
fetchSourceResult = result
|
||||||
dispatchGroup.leave()
|
dispatchGroup.leave()
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let apps = try result.get()
|
let source = try result.get()
|
||||||
|
|
||||||
guard let context = apps.first?.managedObjectContext else { return }
|
guard let context = source.managedObjectContext else { return }
|
||||||
|
|
||||||
let updatesFetchRequest = InstalledApp.updatesFetchRequest()
|
let updatesFetchRequest = InstalledApp.updatesFetchRequest()
|
||||||
updatesFetchRequest.includesPendingChanges = true
|
updatesFetchRequest.includesPendingChanges = true
|
||||||
@@ -230,13 +230,13 @@ extension AppDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatchGroup.notify(queue: .main) {
|
dispatchGroup.notify(queue: .main) {
|
||||||
guard let fetchAppsResult = fetchAppsResult, let serversResult = serversResult else {
|
guard let fetchSourceResult = fetchSourceResult, let serversResult = serversResult else {
|
||||||
completionHandler(.failed)
|
completionHandler(.failed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call completionHandler early to improve chances of refreshing in the background again.
|
// Call completionHandler early to improve chances of refreshing in the background again.
|
||||||
switch (fetchAppsResult, serversResult)
|
switch (fetchSourceResult, serversResult)
|
||||||
{
|
{
|
||||||
case (.success, .success): completionHandler(.newData)
|
case (.success, .success): completionHandler(.newData)
|
||||||
case (.success, .failure(ConnectionError.serverNotFound)): completionHandler(.newData)
|
case (.success, .failure(ConnectionError.serverNotFound)): completionHandler(.newData)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class BrowseViewController: UICollectionViewController
|
|||||||
{
|
{
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
self.fetchApps()
|
self.fetchSource()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||||
@@ -57,10 +57,18 @@ private extension BrowseViewController
|
|||||||
func makeDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<App, UIImage>
|
func makeDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<App, UIImage>
|
||||||
{
|
{
|
||||||
let fetchRequest = App.fetchRequest() as NSFetchRequest<App>
|
let fetchRequest = App.fetchRequest() as NSFetchRequest<App>
|
||||||
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \App.name, ascending: false)]
|
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \App.sortIndex, ascending: true), NSSortDescriptor(keyPath: \App.name, ascending: true)]
|
||||||
fetchRequest.predicate = NSPredicate(format: "%K != %@", #keyPath(App.bundleIdentifier), App.altstoreAppID)
|
|
||||||
fetchRequest.returnsObjectsAsFaults = false
|
fetchRequest.returnsObjectsAsFaults = false
|
||||||
|
|
||||||
|
if let source = Source.fetchAltStoreSource(in: DatabaseManager.shared.viewContext)
|
||||||
|
{
|
||||||
|
fetchRequest.predicate = NSPredicate(format: "%K != %@ AND %K == %@", #keyPath(App.bundleIdentifier), App.altstoreAppID, #keyPath(App.source), source)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fetchRequest.predicate = NSPredicate(format: "%K != %@", #keyPath(App.bundleIdentifier), App.altstoreAppID)
|
||||||
|
}
|
||||||
|
|
||||||
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<App, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<App, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
||||||
dataSource.cellConfigurationHandler = { (cell, app, indexPath) in
|
dataSource.cellConfigurationHandler = { (cell, app, indexPath) in
|
||||||
let cell = cell as! BrowseCollectionViewCell
|
let cell = cell as! BrowseCollectionViewCell
|
||||||
@@ -98,13 +106,13 @@ private extension BrowseViewController
|
|||||||
return dataSource
|
return dataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchApps()
|
func fetchSource()
|
||||||
{
|
{
|
||||||
AppManager.shared.fetchApps() { (result) in
|
AppManager.shared.fetchSource() { (result) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let apps = try result.get()
|
let source = try result.get()
|
||||||
try apps.first?.managedObjectContext?.save()
|
try source.managedObjectContext?.save()
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import Roxas
|
|||||||
|
|
||||||
extension AppManager
|
extension AppManager
|
||||||
{
|
{
|
||||||
static let didFetchAppsNotification = Notification.Name("com.altstore.AppManager.didFetchApps")
|
static let didFetchSourceNotification = Notification.Name("com.altstore.AppManager.didFetchSource")
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppManager
|
class AppManager
|
||||||
@@ -86,21 +86,27 @@ extension AppManager
|
|||||||
|
|
||||||
extension AppManager
|
extension AppManager
|
||||||
{
|
{
|
||||||
func fetchApps(completionHandler: @escaping (Result<[App], Error>) -> Void)
|
func fetchSource(completionHandler: @escaping (Result<Source, Error>) -> Void)
|
||||||
{
|
{
|
||||||
let fetchAppsOperation = FetchAppsOperation()
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
fetchAppsOperation.resultHandler = { (result) in
|
guard let source = Source.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Source.identifier), Source.altStoreIdentifier), in: context) else {
|
||||||
|
return completionHandler(.failure(OperationError.noSources))
|
||||||
|
}
|
||||||
|
|
||||||
|
let fetchSourceOperation = FetchSourceOperation(sourceURL: source.sourceURL)
|
||||||
|
fetchSourceOperation.resultHandler = { (result) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
completionHandler(.failure(error))
|
completionHandler(.failure(error))
|
||||||
|
|
||||||
case .success(let apps):
|
case .success(let source):
|
||||||
completionHandler(.success(apps))
|
completionHandler(.success(source))
|
||||||
NotificationCenter.default.post(name: AppManager.didFetchAppsNotification, object: self)
|
NotificationCenter.default.post(name: AppManager.didFetchSourceNotification, object: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.operationQueue.addOperation(fetchAppsOperation)
|
self.operationQueue.addOperation(fetchSourceOperation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||||
<attribute name="screenshotNames" attributeType="Transformable" syncable="YES"/>
|
<attribute name="screenshotNames" attributeType="Transformable" syncable="YES"/>
|
||||||
<attribute name="size" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
<attribute name="size" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||||
|
<attribute name="sortIndex" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||||
<attribute name="subtitle" optional="YES" attributeType="String" syncable="YES"/>
|
<attribute name="subtitle" optional="YES" attributeType="String" syncable="YES"/>
|
||||||
<attribute name="tintColor" optional="YES" attributeType="Transformable" syncable="YES"/>
|
<attribute name="tintColor" optional="YES" attributeType="Transformable" syncable="YES"/>
|
||||||
<attribute name="version" attributeType="String" syncable="YES"/>
|
<attribute name="version" attributeType="String" syncable="YES"/>
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
<attribute name="versionDescription" optional="YES" attributeType="String" syncable="YES"/>
|
<attribute name="versionDescription" optional="YES" attributeType="String" syncable="YES"/>
|
||||||
<relationship name="installedApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="storeApp" inverseEntity="InstalledApp" syncable="YES"/>
|
<relationship name="installedApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="storeApp" inverseEntity="InstalledApp" syncable="YES"/>
|
||||||
<relationship name="permissions" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppPermission" inverseName="app" inverseEntity="AppPermission" syncable="YES"/>
|
<relationship name="permissions" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppPermission" inverseName="app" inverseEntity="AppPermission" syncable="YES"/>
|
||||||
|
<relationship name="source" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="apps" inverseEntity="Source" syncable="YES"/>
|
||||||
<uniquenessConstraints>
|
<uniquenessConstraints>
|
||||||
<uniquenessConstraint>
|
<uniquenessConstraint>
|
||||||
<constraint value="bundleIdentifier"/>
|
<constraint value="bundleIdentifier"/>
|
||||||
@@ -54,6 +56,17 @@
|
|||||||
</uniquenessConstraint>
|
</uniquenessConstraint>
|
||||||
</uniquenessConstraints>
|
</uniquenessConstraints>
|
||||||
</entity>
|
</entity>
|
||||||
|
<entity name="Source" representedClassName="Source" syncable="YES">
|
||||||
|
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="sourceURL" attributeType="URI" syncable="YES"/>
|
||||||
|
<relationship name="apps" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="App" inverseName="source" inverseEntity="App" syncable="YES"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="identifier"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
<entity name="Team" representedClassName="Team" syncable="YES">
|
<entity name="Team" representedClassName="Team" syncable="YES">
|
||||||
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||||
<attribute name="isActiveTeam" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
<attribute name="isActiveTeam" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||||
@@ -68,9 +81,10 @@
|
|||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="Account" positionX="-36" positionY="90" width="128" height="135"/>
|
<element name="Account" positionX="-36" positionY="90" width="128" height="135"/>
|
||||||
<element name="App" positionX="-63" positionY="-18" width="128" height="270"/>
|
<element name="App" positionX="-63" positionY="-18" width="128" height="300"/>
|
||||||
<element name="AppPermission" positionX="-45" positionY="90" width="128" height="90"/>
|
<element name="AppPermission" positionX="-45" positionY="90" width="128" height="90"/>
|
||||||
<element name="InstalledApp" positionX="-63" positionY="0" width="128" height="150"/>
|
<element name="InstalledApp" positionX="-63" positionY="0" width="128" height="150"/>
|
||||||
<element name="Team" positionX="-45" positionY="81" width="128" height="120"/>
|
<element name="Team" positionX="-45" positionY="81" width="128" height="120"/>
|
||||||
|
<element name="Source" positionX="-45" positionY="99" width="128" height="105"/>
|
||||||
</elements>
|
</elements>
|
||||||
</model>
|
</model>
|
||||||
@@ -39,8 +39,11 @@ class App: NSManagedObject, Decodable, Fetchable
|
|||||||
@NSManaged private(set) var downloadURL: URL
|
@NSManaged private(set) var downloadURL: URL
|
||||||
@NSManaged private(set) var tintColor: UIColor?
|
@NSManaged private(set) var tintColor: UIColor?
|
||||||
|
|
||||||
|
@NSManaged var sortIndex: Int32
|
||||||
|
|
||||||
/* Relationships */
|
/* Relationships */
|
||||||
@NSManaged var installedApp: InstalledApp?
|
@NSManaged var installedApp: InstalledApp?
|
||||||
|
@NSManaged var source: Source?
|
||||||
@objc(permissions) @NSManaged var _permissions: NSOrderedSet
|
@objc(permissions) @NSManaged var _permissions: NSOrderedSet
|
||||||
|
|
||||||
@nonobjc var permissions: [AppPermission] {
|
@nonobjc var permissions: [AppPermission] {
|
||||||
|
|||||||
@@ -118,8 +118,11 @@ private extension DatabaseManager
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
let source = Source.makeAltStoreSource(in: context)
|
||||||
|
|
||||||
storeApp = App.makeAltStoreApp(in: context)
|
storeApp = App.makeAltStoreApp(in: context)
|
||||||
storeApp.version = localApp.version
|
storeApp.version = localApp.version
|
||||||
|
storeApp.source = source
|
||||||
}
|
}
|
||||||
|
|
||||||
let installedApp: InstalledApp
|
let installedApp: InstalledApp
|
||||||
|
|||||||
@@ -30,6 +30,20 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy
|
|||||||
permission.managedObjectContext?.delete(permission)
|
permission.managedObjectContext?.delete(permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case let databaseObject as Source:
|
||||||
|
guard let conflictedObject = conflict.conflictingObjects.first as? Source else { break }
|
||||||
|
|
||||||
|
let bundleIdentifiers = Set(conflictedObject.apps.map { $0.bundleIdentifier })
|
||||||
|
|
||||||
|
for app in databaseObject.apps
|
||||||
|
{
|
||||||
|
if !bundleIdentifiers.contains(app.bundleIdentifier)
|
||||||
|
{
|
||||||
|
// No longer listed in Source, so remove it from database.
|
||||||
|
app.managedObjectContext?.delete(app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
92
AltStore/Model/Source.swift
Normal file
92
AltStore/Model/Source.swift
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
//
|
||||||
|
// Source.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 7/30/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
extension Source
|
||||||
|
{
|
||||||
|
static let altStoreIdentifier = "com.rileytestut.AltStore"
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc(Source)
|
||||||
|
class Source: NSManagedObject, Fetchable, Decodable
|
||||||
|
{
|
||||||
|
/* Properties */
|
||||||
|
@NSManaged var name: String
|
||||||
|
@NSManaged var identifier: String
|
||||||
|
@NSManaged var sourceURL: URL
|
||||||
|
|
||||||
|
/* Relationships */
|
||||||
|
@objc(apps) @NSManaged private(set) var _apps: NSOrderedSet
|
||||||
|
|
||||||
|
@nonobjc var apps: [App] {
|
||||||
|
get {
|
||||||
|
return self._apps.array as! [App]
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self._apps = NSOrderedSet(array: newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey
|
||||||
|
{
|
||||||
|
case name
|
||||||
|
case identifier
|
||||||
|
case sourceURL
|
||||||
|
case apps
|
||||||
|
}
|
||||||
|
|
||||||
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
|
{
|
||||||
|
super.init(entity: entity, insertInto: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(from decoder: Decoder) throws
|
||||||
|
{
|
||||||
|
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
|
||||||
|
|
||||||
|
super.init(entity: Source.entity(), insertInto: nil)
|
||||||
|
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
self.name = try container.decode(String.self, forKey: .name)
|
||||||
|
self.identifier = try container.decode(String.self, forKey: .identifier)
|
||||||
|
self.sourceURL = try container.decode(URL.self, forKey: .sourceURL)
|
||||||
|
|
||||||
|
let apps = try container.decodeIfPresent([App].self, forKey: .apps) ?? []
|
||||||
|
for (index, app) in apps.enumerated()
|
||||||
|
{
|
||||||
|
app.sortIndex = Int32(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
context.insert(self)
|
||||||
|
|
||||||
|
// Must assign after we're inserted into context.
|
||||||
|
self._apps = NSMutableOrderedSet(array: apps)
|
||||||
|
|
||||||
|
print("Downloaded Order:", self.apps.map { $0.bundleIdentifier })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Source
|
||||||
|
{
|
||||||
|
class func makeAltStoreSource(in context: NSManagedObjectContext) -> Source
|
||||||
|
{
|
||||||
|
let source = Source(context: context)
|
||||||
|
source.name = "AltStore"
|
||||||
|
source.identifier = Source.altStoreIdentifier
|
||||||
|
source.sourceURL = URL(string: "https://www.dropbox.com/s/6qi1vt6hsi88lv6/Apps-Dev.json?dl=1")!
|
||||||
|
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
class func fetchAltStoreSource(in context: NSManagedObjectContext) -> Source?
|
||||||
|
{
|
||||||
|
let source = Source.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Source.identifier), Source.altStoreIdentifier), in: context)
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,7 +57,7 @@ class MyAppsViewController: UICollectionViewController
|
|||||||
{
|
{
|
||||||
super.init(coder: aDecoder)
|
super.init(coder: aDecoder)
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(MyAppsViewController.didFetchApps(_:)), name: AppManager.didFetchAppsNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(MyAppsViewController.didFetchSource(_:)), name: AppManager.didFetchSourceNotification, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad()
|
override func viewDidLoad()
|
||||||
@@ -522,7 +522,7 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
private extension MyAppsViewController
|
private extension MyAppsViewController
|
||||||
{
|
{
|
||||||
@objc func didFetchApps(_ notification: Notification)
|
@objc func didFetchSource(_ notification: Notification)
|
||||||
{
|
{
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if self.updatesDataSource.fetchedResultsController.fetchedObjects == nil
|
if self.updatesDataSource.fetchedResultsController.fetchedObjects == nil
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
//
|
//
|
||||||
// FetchAppsOperation.swift
|
// FetchSourceOperation.swift
|
||||||
// AltStore
|
// AltStore
|
||||||
//
|
//
|
||||||
// Created by Riley Testut on 6/17/19.
|
// Created by Riley Testut on 7/30/19.
|
||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
@objc(FetchAppsOperation)
|
@objc(FetchSourceOperation)
|
||||||
class FetchAppsOperation: ResultOperation<[App]>
|
class FetchSourceOperation: ResultOperation<Source>
|
||||||
{
|
{
|
||||||
|
let sourceURL: URL
|
||||||
|
|
||||||
private let session = URLSession(configuration: .default)
|
private let session = URLSession(configuration: .default)
|
||||||
|
|
||||||
private lazy var dateFormatter: DateFormatter = {
|
private lazy var dateFormatter: DateFormatter = {
|
||||||
@@ -20,13 +22,16 @@ class FetchAppsOperation: ResultOperation<[App]>
|
|||||||
return dateFormatter
|
return dateFormatter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
init(sourceURL: URL)
|
||||||
|
{
|
||||||
|
self.sourceURL = sourceURL
|
||||||
|
}
|
||||||
|
|
||||||
override func main()
|
override func main()
|
||||||
{
|
{
|
||||||
super.main()
|
super.main()
|
||||||
|
|
||||||
let appsURL = URL(string: "https://www.dropbox.com/s/6qi1vt6hsi88lv6/Apps-Dev.json?dl=1")!
|
let dataTask = self.session.dataTask(with: self.sourceURL) { (data, response, error) in
|
||||||
|
|
||||||
let dataTask = self.session.dataTask(with: appsURL) { (data, response, error) in
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@@ -36,8 +41,8 @@ class FetchAppsOperation: ResultOperation<[App]>
|
|||||||
decoder.dateDecodingStrategy = .formatted(self.dateFormatter)
|
decoder.dateDecodingStrategy = .formatted(self.dateFormatter)
|
||||||
decoder.managedObjectContext = context
|
decoder.managedObjectContext = context
|
||||||
|
|
||||||
let apps = try decoder.decode([App].self, from: data)
|
let source = try decoder.decode(Source.self, from: data)
|
||||||
self.finish(.success(apps))
|
self.finish(.success(source))
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -25,6 +25,8 @@ enum OperationError: LocalizedError
|
|||||||
|
|
||||||
case iOSVersionNotSupported(ALTApplication)
|
case iOSVersionNotSupported(ALTApplication)
|
||||||
|
|
||||||
|
case noSources
|
||||||
|
|
||||||
var errorDescription: String? {
|
var errorDescription: String? {
|
||||||
switch self {
|
switch self {
|
||||||
case .unknown: return NSLocalizedString("An unknown error occured.", comment: "")
|
case .unknown: return NSLocalizedString("An unknown error occured.", comment: "")
|
||||||
@@ -35,6 +37,7 @@ enum OperationError: LocalizedError
|
|||||||
case .unknownUDID: return NSLocalizedString("Unknown device UDID.", comment: "")
|
case .unknownUDID: return NSLocalizedString("Unknown device UDID.", comment: "")
|
||||||
case .invalidApp: return NSLocalizedString("The app is invalid.", comment: "")
|
case .invalidApp: return NSLocalizedString("The app is invalid.", comment: "")
|
||||||
case .invalidParameters: return NSLocalizedString("Invalid parameters.", comment: "")
|
case .invalidParameters: return NSLocalizedString("Invalid parameters.", comment: "")
|
||||||
|
case .noSources: return NSLocalizedString("There are no AltStore sources.", comment: "")
|
||||||
case .iOSVersionNotSupported(let app):
|
case .iOSVersionNotSupported(let app):
|
||||||
let name = app.name
|
let name = app.name
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
[
|
{
|
||||||
|
"name": "AltStore",
|
||||||
|
"identifier": "com.rileytestut.AltStore",
|
||||||
|
"sourceURL": "https://www.dropbox.com/s/6qi1vt6hsi88lv6/Apps-Dev.json?dl=1",
|
||||||
|
"apps": [
|
||||||
{
|
{
|
||||||
"name": "AltStore",
|
"name": "AltStore",
|
||||||
"bundleIdentifier": "com.rileytestut.AltStore",
|
"bundleIdentifier": "com.rileytestut.AltStore",
|
||||||
@@ -43,7 +47,7 @@
|
|||||||
"screenshotNames": [
|
"screenshotNames": [
|
||||||
"Delta1",
|
"Delta1",
|
||||||
"Delta2",
|
"Delta2",
|
||||||
"Delta3",
|
"Delta3"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -67,7 +71,8 @@
|
|||||||
],
|
],
|
||||||
"screenshotNames": [
|
"screenshotNames": [
|
||||||
"Clip1",
|
"Clip1",
|
||||||
"Clip2",
|
"Clip2"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user