diff --git a/AltKit/Bundle+AltStore.swift b/AltKit/Bundle+AltStore.swift index bca395f1..fa7b58e0 100644 --- a/AltKit/Bundle+AltStore.swift +++ b/AltKit/Bundle+AltStore.swift @@ -13,5 +13,15 @@ public extension Bundle struct Info { public static let deviceID = "ALTDeviceID" + + public static let urlTypes = "CFBundleURLTypes" + } +} + +public extension Bundle +{ + var infoPlistURL: URL { + let infoPlistURL = self.bundleURL.appendingPathComponent("Info.plist") + return infoPlistURL } } diff --git a/AltServer/ViewController.swift b/AltServer/ViewController.swift index d8c553c8..bd754dce 100644 --- a/AltServer/ViewController.swift +++ b/AltServer/ViewController.swift @@ -355,11 +355,11 @@ private extension ViewController let zippedURL = try FileManager.default.zipAppBundle(at: appBundleURL) let resigner = ALTSigner(team: team, certificate: certificate) - resigner.signApp(at: zippedURL, provisioningProfile: profile) { (resignedURL, error) in + resigner.signApp(at: zippedURL, provisioningProfile: profile) { (success, error) in do { - let resignedURL = try Result(resignedURL, error).get() - ALTDeviceManager.shared.installApp(at: resignedURL, toDeviceWithUDID: device.identifier) { (success, error) in + try Result(success, error).get() + ALTDeviceManager.shared.installApp(at: ipaURL, toDeviceWithUDID: device.identifier) { (success, error) in let result = Result(success, error) print(result) } diff --git a/AltStore/AppDelegate.swift b/AltStore/AppDelegate.swift index 8a5934b2..81810e9d 100644 --- a/AltStore/AppDelegate.swift +++ b/AltStore/AppDelegate.swift @@ -28,6 +28,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { else { print("Started DatabaseManager") + + AppManager.shared.refresh() } } @@ -46,6 +48,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationWillEnterForeground(_ application: UIApplication) { + AppManager.shared.refresh() ServerManager.shared.startDiscovering() } diff --git a/AltStore/Apps/AppManager.swift b/AltStore/Apps/AppManager.swift index 40f65ec4..93b74942 100644 --- a/AltStore/Apps/AppManager.swift +++ b/AltStore/Apps/AppManager.swift @@ -27,7 +27,7 @@ extension AppManager case download(URLError) case authentication(Error) case fetchingSigningResources(Error) - case sign(Error) + case prepare(Error) case install(Error) var errorDescription: String? { @@ -41,7 +41,7 @@ extension AppManager case .download(let error): return error.localizedDescription case .authentication(let error): return error.localizedDescription case .fetchingSigningResources(let error): return error.localizedDescription - case .sign(let error): return error.localizedDescription + case .prepare(let error): return error.localizedDescription case .install(let error): return error.localizedDescription } } @@ -59,11 +59,44 @@ class AppManager } } +extension AppManager +{ + func refresh() + { + let context = DatabaseManager.shared.persistentContainer.newBackgroundSavingViewContext() + + let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest + fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(InstalledApp.app)] + + do + { + let installedApps = try context.fetch(fetchRequest) + for app in installedApps + { + if UIApplication.shared.canOpenURL(app.openAppURL) + { + // App is still installed, good! + } + else + { + context.delete(app) + } + } + + try context.save() + } + catch + { + print("Error while fetching installed apps") + } + } +} + extension AppManager { func install(_ app: App, presentingViewController: UIViewController, completionHandler: @escaping (Result) -> Void) { - let ipaURL = app.ipaURL + let ipaURL = InstalledApp.ipaURL(for: app) let backgroundTaskID = RSTBeginBackgroundTask("com.rileytestut.AltStore.InstallApp") @@ -86,7 +119,6 @@ extension AppManager { case .failure(let error): finish(.failure(.download(error))) case .success: - // Authenticate self.authenticate(presentingViewController: presentingViewController) { (result) in switch result @@ -101,31 +133,30 @@ extension AppManager case .failure(let error): finish(.failure(.fetchingSigningResources(error))) case .success(let certificate, let profile): - // Sign app - app.managedObjectContext?.perform { - self.sign(app, team: team, certificate: certificate, provisioningProfile: profile) { (result) in + // Prepare app + DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in + let app = context.object(with: app.objectID) as! App + + let installedApp = InstalledApp(app: app, + bundleIdentifier: profile.appID.bundleIdentifier, + signedDate: Date(), + expirationDate: Date().addingTimeInterval(60 * 60 * 24 * 7), + context: context) + + self.prepare(installedApp, team: team, certificate: certificate, provisioningProfile: profile) { (result) in switch result { - case .failure(let error): finish(.failure(.sign(error))) + case .failure(let error): finish(.failure(.prepare(error))) case .success(let resignedURL): // Send app to server - app.managedObjectContext?.perform { - self.sendAppToServer(fileURL: resignedURL, identifier: app.identifier) { (result) in + context.perform { + self.sendAppToServer(fileURL: resignedURL, identifier: installedApp.bundleIdentifier) { (result) in switch result { case .failure(let error): finish(.failure(.install(error))) case .success: - - // Update database - DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in - let app = context.object(with: app.objectID) as! App - - let installedApp = InstalledApp(app: app, - bundleIdentifier: app.identifier, - signedDate: Date(), - expirationDate: Date().addingTimeInterval(60 * 60 * 24 * 7), - context: context) + context.perform { finish(.success(installedApp)) } } @@ -255,12 +286,52 @@ private extension AppManager } } - func sign(_ app: App, team: ALTTeam, certificate: ALTCertificate, provisioningProfile: ALTProvisioningProfile, completionHandler: @escaping (Result) -> Void) + func prepare(_ installedApp: InstalledApp, team altTeam: ALTTeam, certificate: ALTCertificate, provisioningProfile: ALTProvisioningProfile, completionHandler: @escaping (Result) -> Void) { - let signer = ALTSigner(team: team, certificate: certificate) - signer.signApp(at: app.ipaURL, provisioningProfile: provisioningProfile) { (resignedURL, error) in - let result = Result(resignedURL, error) - completionHandler(result) + do + { + let refreshedAppDirectory = installedApp.directoryURL.appendingPathComponent("Refreshed", isDirectory: true) + + if FileManager.default.fileExists(atPath: refreshedAppDirectory.path) + { + try FileManager.default.removeItem(at: refreshedAppDirectory) + } + try FileManager.default.createDirectory(at: refreshedAppDirectory, withIntermediateDirectories: true, attributes: nil) + + let appBundleURL = try FileManager.default.unzipAppBundle(at: installedApp.ipaURL, toDirectory: refreshedAppDirectory) + guard let bundle = Bundle(url: appBundleURL) else { throw ALTError(.missingAppBundle) } + + guard var infoDictionary = NSDictionary(contentsOf: bundle.infoPlistURL) as? [String: Any] else { throw ALTError(.missingInfoPlist) } + + var allURLSchemes = infoDictionary[Bundle.Info.urlTypes] as? [[String: Any]] ?? [] + + let altstoreURLScheme = ["CFBundleTypeRole": "Editor", + "CFBundleURLName": installedApp.bundleIdentifier, + "CFBundleURLSchemes": [installedApp.openAppURL.scheme!]] as [String : Any] + allURLSchemes.append(altstoreURLScheme) + + infoDictionary[Bundle.Info.urlTypes] = allURLSchemes + + try (infoDictionary as NSDictionary).write(to: bundle.infoPlistURL) + + let signer = ALTSigner(team: altTeam, certificate: certificate) + signer.signApp(at: appBundleURL, provisioningProfile: provisioningProfile) { (success, error) in + do + { + try Result(success, error).get() + + let resignedURL = try FileManager.default.zipAppBundle(at: appBundleURL) + completionHandler(.success(resignedURL)) + } + catch + { + completionHandler(.failure(error)) + } + } + } + catch + { + completionHandler(.failure(error)) } } diff --git a/AltStore/Info.plist b/AltStore/Info.plist index 46c04fea..3ff11536 100644 --- a/AltStore/Info.plist +++ b/AltStore/Info.plist @@ -2,6 +2,8 @@ + ALTDeviceID + 1c3416b7b0ab68773e6e7eb7f0d110f7c9353acc CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -18,6 +20,10 @@ 1.0 CFBundleVersion 1 + LSApplicationQueriesSchemes + + altstore-com.rileytestut.Delta + LSRequiresIPhoneOS UILaunchStoryboardName @@ -51,7 +57,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - ALTDeviceID - 1c3416b7b0ab68773e6e7eb7f0d110f7c9353acc diff --git a/AltStore/Model/App.swift b/AltStore/Model/App.swift index 0b892b13..b3f1bfc7 100644 --- a/AltStore/Model/App.swift +++ b/AltStore/Model/App.swift @@ -84,29 +84,3 @@ extension App return NSFetchRequest(entityName: "App") } } - -extension App -{ - class var appsDirectoryURL: URL { - let appsDirectoryURL = FileManager.default.applicationSupportDirectory.appendingPathComponent("Apps") - - do { try FileManager.default.createDirectory(at: appsDirectoryURL, withIntermediateDirectories: true, attributes: nil) } - catch { print(error) } - - return appsDirectoryURL - } - - var directoryURL: URL { - let directoryURL = App.appsDirectoryURL.appendingPathComponent(self.identifier) - - do { try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) } - catch { print(error) } - - return directoryURL - } - - var ipaURL: URL { - let ipaURL = self.directoryURL.appendingPathComponent("App.ipa") - return ipaURL - } -} diff --git a/AltStore/Model/InstalledApp.swift b/AltStore/Model/InstalledApp.swift index 89f343b1..c699569f 100644 --- a/AltStore/Model/InstalledApp.swift +++ b/AltStore/Model/InstalledApp.swift @@ -22,7 +22,7 @@ class InstalledApp: NSManagedObject @NSManaged var isBeta: Bool /* Relationships */ - @NSManaged private(set) var app: App? + @NSManaged private(set) var app: App! private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) { @@ -50,3 +50,48 @@ extension InstalledApp return NSFetchRequest(entityName: "InstalledApp") } } + +extension InstalledApp +{ + var openAppURL: URL { + // Don't use the actual bundle ID yet since we're hardcoding support for the first apps in AltStore. + let openAppURL = URL(string: "altstore-" + self.app.identifier + "://")! + return openAppURL + } +} + +extension InstalledApp +{ + class var appsDirectoryURL: URL { + let appsDirectoryURL = FileManager.default.applicationSupportDirectory.appendingPathComponent("Apps") + + do { try FileManager.default.createDirectory(at: appsDirectoryURL, withIntermediateDirectories: true, attributes: nil) } + catch { print(error) } + + return appsDirectoryURL + } + + class func ipaURL(for app: App) -> URL + { + let ipaURL = self.directoryURL(for: app).appendingPathComponent("App.ipa") + return ipaURL + } + + class func directoryURL(for app: App) -> URL + { + let directoryURL = InstalledApp.appsDirectoryURL.appendingPathComponent(app.identifier) + + do { try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) } + catch { print(error) } + + return directoryURL + } + + var directoryURL: URL { + return InstalledApp.directoryURL(for: self.app) + } + + var ipaURL: URL { + return InstalledApp.ipaURL(for: self.app) + } +} diff --git a/Dependencies/AltSign b/Dependencies/AltSign index 8ca7fa3f..e3cee11a 160000 --- a/Dependencies/AltSign +++ b/Dependencies/AltSign @@ -1 +1 @@ -Subproject commit 8ca7fa3f1994efd50f7e85ce031cd7b8e936cac1 +Subproject commit e3cee11a3a7b3df3a814d9154bffc2747839cb4d