mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-16 02:03:32 +01:00
Activates apps by reinstalling then restoring backup on iOS 13.5+
To activate an inactive app that has been deleted from the phone, AltStore will reinstall the app, as well as restore any app data from when it was deactivated.
This commit is contained in:
@@ -319,13 +319,19 @@ extension AppManager
|
|||||||
|
|
||||||
func activate(_ installedApp: InstalledApp, presentingViewController: UIViewController?, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void)
|
func activate(_ installedApp: InstalledApp, presentingViewController: UIViewController?, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void)
|
||||||
{
|
{
|
||||||
let group = self.refresh([installedApp], presentingViewController: presentingViewController)
|
let group = RefreshGroup()
|
||||||
|
|
||||||
|
let operation = AppOperation.activate(installedApp)
|
||||||
|
self.perform([operation], presentingViewController: presentingViewController, group: group)
|
||||||
|
|
||||||
group.completionHandler = { (results) in
|
group.completionHandler = { (results) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let result = results.values.first else { throw OperationError.unknown }
|
guard let result = results.values.first else { throw OperationError.unknown }
|
||||||
|
|
||||||
let installedApp = try result.get()
|
let installedApp = try result.get()
|
||||||
|
assert(installedApp.managedObjectContext != nil)
|
||||||
|
|
||||||
installedApp.managedObjectContext?.perform {
|
installedApp.managedObjectContext?.perform {
|
||||||
installedApp.isActive = true
|
installedApp.isActive = true
|
||||||
completionHandler(.success(installedApp))
|
completionHandler(.success(installedApp))
|
||||||
@@ -445,13 +451,15 @@ private extension AppManager
|
|||||||
case install(AppProtocol)
|
case install(AppProtocol)
|
||||||
case update(AppProtocol)
|
case update(AppProtocol)
|
||||||
case refresh(InstalledApp)
|
case refresh(InstalledApp)
|
||||||
|
case activate(InstalledApp)
|
||||||
case deactivate(InstalledApp)
|
case deactivate(InstalledApp)
|
||||||
|
|
||||||
var app: AppProtocol {
|
var app: AppProtocol {
|
||||||
switch self
|
switch self
|
||||||
{
|
{
|
||||||
case .install(let app), .update(let app),
|
case .install(let app), .update(let app),
|
||||||
.refresh(let app as AppProtocol), .deactivate(let app as AppProtocol):
|
.refresh(let app as AppProtocol), .activate(let app as AppProtocol),
|
||||||
|
.deactivate(let app as AppProtocol):
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -533,7 +541,7 @@ private extension AppManager
|
|||||||
}
|
}
|
||||||
progress?.addChild(installProgress, withPendingUnitCount: 80)
|
progress?.addChild(installProgress, withPendingUnitCount: 80)
|
||||||
|
|
||||||
case .activate(let app): fallthrough
|
case .activate(let app) where UserDefaults.standard.isLegacyDeactivationSupported: fallthrough
|
||||||
case .refresh(let app):
|
case .refresh(let app):
|
||||||
// Check if backup app is installed in place of real app.
|
// Check if backup app is installed in place of real app.
|
||||||
let uti = UTTypeCopyDeclaration(app.installedBackupAppUTI as CFString)?.takeRetainedValue() as NSDictionary?
|
let uti = UTTypeCopyDeclaration(app.installedBackupAppUTI as CFString)?.takeRetainedValue() as NSDictionary?
|
||||||
@@ -559,6 +567,12 @@ private extension AppManager
|
|||||||
progress?.addChild(installProgress, withPendingUnitCount: 80)
|
progress?.addChild(installProgress, withPendingUnitCount: 80)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case .activate(let app):
|
||||||
|
let activateProgress = self._activate(app, operation: operation, group: group) { (result) in
|
||||||
|
self.finish(operation, result: result, group: group, progress: progress)
|
||||||
|
}
|
||||||
|
progress?.addChild(activateProgress, withPendingUnitCount: 80)
|
||||||
|
|
||||||
case .deactivate(let app):
|
case .deactivate(let app):
|
||||||
let deactivateProgress = self._deactivate(app, operation: operation, group: group) { (result) in
|
let deactivateProgress = self._deactivate(app, operation: operation, group: group) { (result) in
|
||||||
self.finish(operation, result: result, group: group, progress: progress)
|
self.finish(operation, result: result, group: group, progress: progress)
|
||||||
@@ -791,6 +805,135 @@ private extension AppManager
|
|||||||
return progress
|
return progress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func _activate(_ app: InstalledApp, operation appOperation: AppOperation, group: RefreshGroup, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
|
||||||
|
{
|
||||||
|
let progress = Progress.discreteProgress(totalUnitCount: 100)
|
||||||
|
|
||||||
|
let restoreContext = InstallAppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context)
|
||||||
|
let appContext = InstallAppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context)
|
||||||
|
|
||||||
|
let installBackupAppProgress = Progress.discreteProgress(totalUnitCount: 100)
|
||||||
|
let installBackupAppOperation = RSTAsyncBlockOperation { [weak self] (operation) in
|
||||||
|
app.managedObjectContext?.perform {
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
let progress = self._installBackupApp(for: app, operation: appOperation, group: group, context: restoreContext) { (result) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .success(let installedApp): restoreContext.installedApp = installedApp
|
||||||
|
case .failure(let error):
|
||||||
|
restoreContext.error = error
|
||||||
|
appContext.error = error
|
||||||
|
}
|
||||||
|
|
||||||
|
operation.finish()
|
||||||
|
}
|
||||||
|
installBackupAppProgress.addChild(progress, withPendingUnitCount: 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
progress.addChild(installBackupAppProgress, withPendingUnitCount: 30)
|
||||||
|
|
||||||
|
let restoreAppOperation = BackupAppOperation(action: .restore, context: restoreContext)
|
||||||
|
restoreAppOperation.resultHandler = { (result) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .success: break
|
||||||
|
case .failure(let error):
|
||||||
|
restoreContext.error = error
|
||||||
|
appContext.error = error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
restoreAppOperation.addDependency(installBackupAppOperation)
|
||||||
|
progress.addChild(restoreAppOperation.progress, withPendingUnitCount: 15)
|
||||||
|
|
||||||
|
let installAppProgress = Progress.discreteProgress(totalUnitCount: 100)
|
||||||
|
let installAppOperation = RSTAsyncBlockOperation { [weak self] (operation) in
|
||||||
|
app.managedObjectContext?.perform {
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
let progress = self._install(app, operation: appOperation, group: group, context: appContext) { (result) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .success(let installedApp): appContext.installedApp = installedApp
|
||||||
|
case .failure(let error): appContext.error = error
|
||||||
|
}
|
||||||
|
|
||||||
|
operation.finish()
|
||||||
|
}
|
||||||
|
installAppProgress.addChild(progress, withPendingUnitCount: 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
installAppOperation.addDependency(restoreAppOperation)
|
||||||
|
progress.addChild(installAppProgress, withPendingUnitCount: 50)
|
||||||
|
|
||||||
|
let cleanUpProgress = Progress.discreteProgress(totalUnitCount: 100)
|
||||||
|
let cleanUpOperation = RSTAsyncBlockOperation { (operation) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let installedApp = try Result(appContext.installedApp, appContext.error).get()
|
||||||
|
|
||||||
|
var result: Result<Void, Error>!
|
||||||
|
installedApp.managedObjectContext?.performAndWait {
|
||||||
|
result = Result { try installedApp.managedObjectContext?.save() }
|
||||||
|
}
|
||||||
|
try result.get()
|
||||||
|
|
||||||
|
// Successfully saved, so _now_ we can remove backup.
|
||||||
|
|
||||||
|
let removeAppBackupOperation = RemoveAppBackupOperation(context: appContext)
|
||||||
|
removeAppBackupOperation.resultHandler = { (result) in
|
||||||
|
installedApp.managedObjectContext?.perform {
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error):
|
||||||
|
// Don't report error, since it doesn't really matter.
|
||||||
|
print("Failed to delete app backup.", error)
|
||||||
|
|
||||||
|
case .success: break
|
||||||
|
}
|
||||||
|
|
||||||
|
completionHandler(.success(installedApp))
|
||||||
|
operation.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cleanUpProgress.addChild(removeAppBackupOperation.progress, withPendingUnitCount: 100)
|
||||||
|
|
||||||
|
group.add([removeAppBackupOperation])
|
||||||
|
self.run([removeAppBackupOperation], context: group.context)
|
||||||
|
}
|
||||||
|
catch let error where restoreContext.installedApp != nil
|
||||||
|
{
|
||||||
|
// Activation failed, but restore app was installed, so remove the app.
|
||||||
|
|
||||||
|
// Remove error so operation doesn't quit early,
|
||||||
|
restoreContext.error = nil
|
||||||
|
|
||||||
|
let removeAppOperation = RemoveAppOperation(context: restoreContext)
|
||||||
|
removeAppOperation.resultHandler = { (result) in
|
||||||
|
completionHandler(.failure(error))
|
||||||
|
operation.finish()
|
||||||
|
}
|
||||||
|
cleanUpProgress.addChild(removeAppOperation.progress, withPendingUnitCount: 100)
|
||||||
|
|
||||||
|
group.add([removeAppOperation])
|
||||||
|
self.run([removeAppOperation], context: group.context)
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Activation failed.
|
||||||
|
completionHandler(.failure(error))
|
||||||
|
operation.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cleanUpOperation.addDependency(installAppOperation)
|
||||||
|
progress.addChild(cleanUpProgress, withPendingUnitCount: 5)
|
||||||
|
|
||||||
|
group.add([installBackupAppOperation, restoreAppOperation, installAppOperation, cleanUpOperation])
|
||||||
|
self.run([installBackupAppOperation, installAppOperation, restoreAppOperation, cleanUpOperation], context: group.context)
|
||||||
|
|
||||||
|
return progress
|
||||||
|
}
|
||||||
|
|
||||||
private func _deactivate(_ app: InstalledApp, operation appOperation: AppOperation, group: RefreshGroup, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
|
private func _deactivate(_ app: InstalledApp, operation appOperation: AppOperation, group: RefreshGroup, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
|
||||||
{
|
{
|
||||||
let progress = Progress.discreteProgress(totalUnitCount: 100)
|
let progress = Progress.discreteProgress(totalUnitCount: 100)
|
||||||
@@ -978,7 +1121,7 @@ private extension AppManager
|
|||||||
event = nil
|
event = nil
|
||||||
|
|
||||||
case .update: event = .updatedApp(installedApp)
|
case .update: event = .updatedApp(installedApp)
|
||||||
case .deactivate: event = nil
|
case .activate, .deactivate: event = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if let event = event
|
if let event = event
|
||||||
@@ -1036,7 +1179,7 @@ private extension AppManager
|
|||||||
switch operation
|
switch operation
|
||||||
{
|
{
|
||||||
case .install, .update: return self.installationProgress[operation.bundleIdentifier]
|
case .install, .update: return self.installationProgress[operation.bundleIdentifier]
|
||||||
case .refresh, .deactivate: return self.refreshProgress[operation.bundleIdentifier]
|
case .refresh, .activate, .deactivate: return self.refreshProgress[operation.bundleIdentifier]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1045,7 +1188,7 @@ private extension AppManager
|
|||||||
switch operation
|
switch operation
|
||||||
{
|
{
|
||||||
case .install, .update: self.installationProgress[operation.bundleIdentifier] = progress
|
case .install, .update: self.installationProgress[operation.bundleIdentifier] = progress
|
||||||
case .refresh, .deactivate: self.refreshProgress[operation.bundleIdentifier] = progress
|
case .refresh, .activate, .deactivate: self.refreshProgress[operation.bundleIdentifier] = progress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user