[AltStore] Refreshes individual apps

This commit is contained in:
Riley Testut
2019-06-05 11:03:49 -07:00
parent d1c8aa8c0e
commit 5c4613fd20
2 changed files with 128 additions and 51 deletions

View File

@@ -180,11 +180,48 @@ extension AppManager
} }
} }
func refreshAllApps(completionHandler: @escaping (Result<[String: Result<Void, Error>], AppError>) -> Void) func refresh(_ app: InstalledApp, completionHandler: @escaping (Result<InstalledApp, Error>) -> 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<InstalledApp, Error>], AppError>) -> Void)
{
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
do
{
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
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<T: Collection>(_ installedApps: T, completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], AppError>) -> Void) where T.Element == InstalledApp
{ {
let backgroundTaskID = RSTBeginBackgroundTask("com.rileytestut.AltStore.RefreshApps") let backgroundTaskID = RSTBeginBackgroundTask("com.rileytestut.AltStore.RefreshApps")
func finish(_ result: Result<[String: Result<Void, Error>], AppError>) func finish(_ result: Result<[String: Result<InstalledApp, Error>], AppError>)
{ {
completionHandler(result) completionHandler(result)
@@ -206,40 +243,30 @@ extension AppManager
case .success(let certificate): case .success(let certificate):
let signer = ALTSigner(team: team, certificate: certificate) let signer = ALTSigner(team: team, certificate: certificate)
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in let dispatchGroup = DispatchGroup()
do var results = [String: Result<InstalledApp, Error>]()
{
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(InstalledApp.app)]
let installedApps = try context.fetch(fetchRequest) let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
let dispatchGroup = DispatchGroup() for app in installedApps
var results = [String: Result<Void, Error>]() {
dispatchGroup.enter()
for app in installedApps app.managedObjectContext?.perform {
{ let bundleIdentifier = app.bundleIdentifier
dispatchGroup.enter() print("Refreshing App:", bundleIdentifier)
let bundleIdentifier = app.bundleIdentifier self.refresh(app, signer: signer, context: context) { (result) in
print("Refreshing App:", bundleIdentifier) print("Refreshed App: \(bundleIdentifier).", result)
results[bundleIdentifier] = result
self.refresh(app, signer: signer) { (result) in dispatchGroup.leave()
print("Refreshed App: \(bundleIdentifier).", result)
results[bundleIdentifier] = result
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .global()) {
context.perform { // Keep context alive
finish(.success(results))
}
} }
} }
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, Error>) -> Void) func refresh(_ installedApp: InstalledApp, signer: ALTSigner, context: NSManagedObjectContext, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void)
{ {
self.prepareProvisioningProfile(for: installedApp.app, team: signer.team) { (result) in self.prepareProvisioningProfile(for: installedApp.app, team: signer.team) { (result) in
switch result switch result
@@ -660,7 +687,20 @@ private extension AppManager
// Send app to server // Send app to server
installedApp.managedObjectContext?.perform { 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))
}
}
}
} }
} }
} }

View File

@@ -16,7 +16,7 @@ class MyAppsViewController: UITableViewController
private lazy var dateFormatter: DateFormatter = { private lazy var dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .none dateFormatter.timeStyle = .short
return dateFormatter return dateFormatter
}() }()
@@ -73,24 +73,61 @@ private extension MyAppsViewController
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 deleteAction = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in
let installedApp = self.dataSource.item(at: indexPath)
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)
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in do
let installedApp = context.object(with: installedApp.objectID) as! InstalledApp {
context.delete(installedApp) try context.save()
}
do catch
{ {
try context.save() print("Failed to delete installed app.", error)
} }
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)
{
} }
} }