From 5c4613fd205bd505d2bd070b6fee2574abdb9f14 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Wed, 5 Jun 2019 11:03:49 -0700 Subject: [PATCH] [AltStore] Refreshes individual apps --- AltStore/Apps/AppManager.swift | 110 +++++++++++++------- AltStore/My Apps/MyAppsViewController.swift | 69 +++++++++--- 2 files changed, 128 insertions(+), 51 deletions(-) diff --git a/AltStore/Apps/AppManager.swift b/AltStore/Apps/AppManager.swift index a4827a9f..0a920341 100644 --- a/AltStore/Apps/AppManager.swift +++ b/AltStore/Apps/AppManager.swift @@ -180,11 +180,48 @@ extension AppManager } } - func refreshAllApps(completionHandler: @escaping (Result<[String: Result], AppError>) -> Void) + func refresh(_ app: InstalledApp, completionHandler: @escaping (Result) -> Void) + { + self.refresh([app]) { (result) in + do + { + guard let (_, result) = try result.get().first else { throw AppError.unknown } + completionHandler(result) + } + catch + { + completionHandler(.failure(error)) + } + } + } + + func refreshAllApps(completionHandler: @escaping (Result<[String: Result], AppError>) -> Void) + { + DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in + do + { + let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest + fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(InstalledApp.app)] + + let installedApps = try context.fetch(fetchRequest) + self.refresh(installedApps) { (result) in + context.perform { // keep context alive + completionHandler(result) + } + } + } + catch + { + completionHandler(.failure(.prepare(error))) + } + } + } + + private func refresh(_ installedApps: T, completionHandler: @escaping (Result<[String: Result], AppError>) -> Void) where T.Element == InstalledApp { let backgroundTaskID = RSTBeginBackgroundTask("com.rileytestut.AltStore.RefreshApps") - func finish(_ result: Result<[String: Result], AppError>) + func finish(_ result: Result<[String: Result], AppError>) { completionHandler(result) @@ -206,40 +243,30 @@ extension AppManager case .success(let certificate): let signer = ALTSigner(team: team, certificate: certificate) - DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in - do - { - let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest - fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(InstalledApp.app)] + let dispatchGroup = DispatchGroup() + var results = [String: Result]() + + let context = DatabaseManager.shared.persistentContainer.newBackgroundContext() + + for app in installedApps + { + dispatchGroup.enter() + + app.managedObjectContext?.perform { + let bundleIdentifier = app.bundleIdentifier + print("Refreshing App:", bundleIdentifier) - let installedApps = try context.fetch(fetchRequest) - - let dispatchGroup = DispatchGroup() - var results = [String: Result]() - - for app in installedApps - { - dispatchGroup.enter() - - let bundleIdentifier = app.bundleIdentifier - print("Refreshing App:", bundleIdentifier) - - self.refresh(app, signer: signer) { (result) in - print("Refreshed App: \(bundleIdentifier).", result) - results[bundleIdentifier] = result - dispatchGroup.leave() - } - } - - dispatchGroup.notify(queue: .global()) { - context.perform { // Keep context alive - finish(.success(results)) - } + self.refresh(app, signer: signer, context: context) { (result) in + print("Refreshed App: \(bundleIdentifier).", result) + results[bundleIdentifier] = result + dispatchGroup.leave() } } - catch - { - finish(.failure(.prepare(error))) + } + + dispatchGroup.notify(queue: .global()) { + context.perform { + finish(.success(results)) } } } @@ -643,7 +670,7 @@ private extension AppManager } } - func refresh(_ installedApp: InstalledApp, signer: ALTSigner, completionHandler: @escaping (Result) -> Void) + func refresh(_ installedApp: InstalledApp, signer: ALTSigner, context: NSManagedObjectContext, completionHandler: @escaping (Result) -> Void) { self.prepareProvisioningProfile(for: installedApp.app, team: signer.team) { (result) in switch result @@ -660,7 +687,20 @@ private extension AppManager // Send app to server installedApp.managedObjectContext?.perform { - self.sendAppToServer(fileURL: resignedURL, identifier: installedApp.bundleIdentifier, completionHandler: completionHandler) + self.sendAppToServer(fileURL: resignedURL, identifier: installedApp.bundleIdentifier) { (result) in + context.perform { + switch result + { + case .success: + let installedApp = context.object(with: installedApp.objectID) as! InstalledApp + installedApp.expirationDate = profile.expirationDate + completionHandler(.success(installedApp)) + + case .failure(let error): + completionHandler(.failure(error)) + } + } + } } } } diff --git a/AltStore/My Apps/MyAppsViewController.swift b/AltStore/My Apps/MyAppsViewController.swift index 6e30ab2d..eaaecd10 100644 --- a/AltStore/My Apps/MyAppsViewController.swift +++ b/AltStore/My Apps/MyAppsViewController.swift @@ -16,7 +16,7 @@ class MyAppsViewController: UITableViewController private lazy var dateFormatter: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .short - dateFormatter.timeStyle = .none + dateFormatter.timeStyle = .short return dateFormatter }() @@ -73,24 +73,61 @@ private extension MyAppsViewController extension MyAppsViewController { - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) + override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { - guard editingStyle == .delete else { return } - - let installedApp = self.dataSource.item(at: indexPath) - - DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in - let installedApp = context.object(with: installedApp.objectID) as! InstalledApp - context.delete(installedApp) + let deleteAction = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in + let installedApp = self.dataSource.item(at: indexPath) - do - { - try context.save() - } - catch - { - print("Failed to delete installed app.", error) + DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in + let installedApp = context.object(with: installedApp.objectID) as! InstalledApp + context.delete(installedApp) + + do + { + try context.save() + } + catch + { + print("Failed to delete installed app.", error) + } } } + + let refreshAction = UITableViewRowAction(style: .normal, title: "Refresh") { (action, indexPath) in + let installedApp = self.dataSource.item(at: indexPath) + + let toastView = RSTToastView(text: "Refreshing...", detailText: nil) + toastView.tintColor = .altPurple + toastView.activityIndicatorView.startAnimating() + toastView.show(in: self.navigationController?.view ?? self.view) + + AppManager.shared.refresh(installedApp) { (result) in + do + { + let app = try result.get() + try app.managedObjectContext?.save() + + DispatchQueue.main.async { + let toastView = RSTToastView(text: "Refreshed \(installedApp.app.name)!", detailText: nil) + toastView.tintColor = .altPurple + toastView.show(in: self.navigationController?.view ?? self.view, duration: 2) + } + } + catch + { + DispatchQueue.main.async { + let toastView = RSTToastView(text: "Failed to refresh \(installedApp.app.name)", detailText: error.localizedDescription) + toastView.tintColor = .altPurple + toastView.show(in: self.navigationController?.view ?? self.view, duration: 2) + } + } + } + } + + return [deleteAction, refreshAction] + } + + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) + { } }