mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-08 22:33:26 +01:00
Downloads, resigns, and installs apps from start to finish
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,5 +27,4 @@ xcuserdata
|
||||
*.xcscmblueprint
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.hmap
|
||||
@@ -39,3 +39,22 @@ public extension Result where Success == Void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Result
|
||||
{
|
||||
init<T, U>(_ values: (T?, U?), _ error: Failure?) where Success == (T, U)
|
||||
{
|
||||
if let value1 = values.0, let value2 = values.1
|
||||
{
|
||||
self = .success((value1, value2))
|
||||
}
|
||||
else if let error = error
|
||||
{
|
||||
self = .failure(error)
|
||||
}
|
||||
else
|
||||
{
|
||||
preconditionFailure("Error must be non-nil if either provided values are nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
AltServer/App.ipa
Normal file
BIN
AltServer/App.ipa
Normal file
Binary file not shown.
@@ -173,6 +173,27 @@ private extension ConnectionManager
|
||||
guard !self.connections.contains(where: { $0 === connection }) else { return }
|
||||
self.connections.append(connection)
|
||||
|
||||
func finish(error: ALTServerError?)
|
||||
{
|
||||
if let error = error
|
||||
{
|
||||
print("Failed to process request from \(connection.endpoint).", error)
|
||||
}
|
||||
else
|
||||
{
|
||||
print("Processed request from \(connection.endpoint).")
|
||||
}
|
||||
|
||||
let success = (error == nil)
|
||||
let response = ServerResponse(success: success, error: error)
|
||||
|
||||
self.send(response, to: connection) { (result) in
|
||||
print("Sent response to \(connection.endpoint) with result:", result)
|
||||
|
||||
self.disconnect(connection)
|
||||
}
|
||||
}
|
||||
|
||||
connection.stateUpdateHandler = { [weak self] (state) in
|
||||
switch state
|
||||
{
|
||||
@@ -181,6 +202,23 @@ private extension ConnectionManager
|
||||
case .ready:
|
||||
print("Connected to client:", connection.endpoint)
|
||||
|
||||
self?.receiveRequest(from: connection) { (result) in
|
||||
print("Received request with result:", result)
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): finish(error: error)
|
||||
case .success(let request, let fileURL):
|
||||
print("Installing to device..")
|
||||
|
||||
ALTDeviceManager.shared.installApp(at: fileURL, toDeviceWithUDID: request.udid) { (success, error) in
|
||||
print("Installed app with result:", result)
|
||||
let error = error.map { $0 as? ALTServerError ?? ALTServerError(.unknown) }
|
||||
finish(error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case .waiting:
|
||||
print("Waiting for connection...")
|
||||
|
||||
@@ -196,50 +234,20 @@ private extension ConnectionManager
|
||||
}
|
||||
|
||||
connection.start(queue: self.dispatchQueue)
|
||||
|
||||
func finish(error: ALTServerError?)
|
||||
{
|
||||
if let error = error
|
||||
{
|
||||
print("Failed to process request from \(connection.endpoint).", error)
|
||||
}
|
||||
else
|
||||
{
|
||||
print("Processed request from \(connection.endpoint).")
|
||||
}
|
||||
|
||||
let success = (error == nil)
|
||||
let response = ServerResponse(success: success, error: error)
|
||||
|
||||
self.send(response, to: connection) { (result) in
|
||||
print("Sent response to \(connection) with result:", result)
|
||||
|
||||
self.disconnect(connection)
|
||||
}
|
||||
}
|
||||
|
||||
self.receiveRequest(from: connection) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): finish(error: error)
|
||||
case .success(let request, let fileURL):
|
||||
ALTDeviceManager.shared.installApp(at: fileURL, toDeviceWithUDID: request.udid) { (success, error) in
|
||||
let error = error.map { $0 as? ALTServerError ?? ALTServerError(.unknown) }
|
||||
finish(error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receiveRequest(from connection: NWConnection, completionHandler: @escaping (Result<(ServerRequest, URL), ALTServerError>) -> Void)
|
||||
{
|
||||
let size = MemoryLayout<Int32>.size
|
||||
|
||||
print("Receiving request size")
|
||||
connection.receive(minimumIncompleteLength: size, maximumLength: size) { (data, _, _, error) in
|
||||
do
|
||||
{
|
||||
let data = try self.process(data: data, error: error, from: connection)
|
||||
|
||||
print("Receiving request")
|
||||
|
||||
let expectedBytes = Int(data.withUnsafeBytes { $0.load(as: Int32.self) })
|
||||
connection.receive(minimumIncompleteLength: expectedBytes, maximumLength: expectedBytes) { (data, _, _, error) in
|
||||
do
|
||||
@@ -247,6 +255,9 @@ private extension ConnectionManager
|
||||
let data = try self.process(data: data, error: error, from: connection)
|
||||
|
||||
let request = try JSONDecoder().decode(ServerRequest.self, from: data)
|
||||
|
||||
print("Receiving app data (Size: \(request.contentSize))")
|
||||
|
||||
self.process(request, from: connection, completionHandler: completionHandler)
|
||||
}
|
||||
catch
|
||||
@@ -267,10 +278,16 @@ private extension ConnectionManager
|
||||
connection.receive(minimumIncompleteLength: request.contentSize, maximumLength: request.contentSize) { (data, _, _, error) in
|
||||
do
|
||||
{
|
||||
print("Received app data!")
|
||||
|
||||
let data = try self.process(data: data, error: error, from: connection)
|
||||
|
||||
print("Processed app data!")
|
||||
|
||||
guard ALTDeviceManager.shared.connectedDevices.contains(where: { $0.identifier == request.udid }) else { throw ALTServerError(.deviceNotFound) }
|
||||
|
||||
print("Writing app data...")
|
||||
|
||||
let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".ipa")
|
||||
try data.write(to: temporaryURL, options: .atomic)
|
||||
|
||||
@@ -280,6 +297,8 @@ private extension ConnectionManager
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Error processing app data:", error)
|
||||
|
||||
completionHandler(.failure(ALTServerError(error)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ NSErrorDomain const ALTDeviceErrorDomain = @"com.rileytestut.ALTDeviceError";
|
||||
|
||||
@property (nonatomic, readonly) NSMutableDictionary<NSUUID *, void (^)(void)> *installationCompletionHandlers;
|
||||
@property (nonatomic, readonly) NSMutableDictionary<NSUUID *, NSProgress *> *installationProgress;
|
||||
@property (nonatomic, readonly) NSMutableDictionary<NSUUID *, NSValue *> *installationClients;
|
||||
|
||||
@end
|
||||
|
||||
@@ -47,6 +48,7 @@ NSErrorDomain const ALTDeviceErrorDomain = @"com.rileytestut.ALTDeviceError";
|
||||
{
|
||||
_installationCompletionHandlers = [NSMutableDictionary dictionary];
|
||||
_installationProgress = [NSMutableDictionary dictionary];
|
||||
_installationClients = [NSMutableDictionary dictionary];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -235,6 +237,9 @@ NSErrorDomain const ALTDeviceErrorDomain = @"com.rileytestut.ALTDeviceError";
|
||||
return progress;
|
||||
}
|
||||
|
||||
NSValue *value = [NSValue valueWithPointer:(const void *)np];
|
||||
|
||||
self.installationClients[UUID] = value;
|
||||
self.installationProgress[UUID] = progress;
|
||||
self.installationCompletionHandlers[UUID] = ^{
|
||||
finish(nil);
|
||||
@@ -357,18 +362,17 @@ NSErrorDomain const ALTDeviceErrorDomain = @"com.rileytestut.ALTDeviceError";
|
||||
#pragma mark - Getters -
|
||||
|
||||
- (NSArray<ALTDevice *> *)connectedDevices
|
||||
{
|
||||
{
|
||||
NSMutableArray *connectedDevices = [NSMutableArray array];
|
||||
|
||||
int count = 0;
|
||||
char **udids = NULL;
|
||||
|
||||
if (idevice_get_device_list(&udids, &count) < 0)
|
||||
{
|
||||
fprintf(stderr, "ERROR: Unable to retrieve device list!\n");
|
||||
return @[];
|
||||
}
|
||||
|
||||
NSMutableArray *connectedDevices = [NSMutableArray array];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
char *udid = udids[i];
|
||||
@@ -435,9 +439,19 @@ void ALTDeviceManagerDidFinishAppInstallation(const char *notification, void *uu
|
||||
if (completionHandler != nil)
|
||||
{
|
||||
completionHandler();
|
||||
|
||||
ALTDeviceManager.sharedManager.installationCompletionHandlers[UUID] = nil;
|
||||
ALTDeviceManager.sharedManager.installationProgress[UUID] = nil;
|
||||
}
|
||||
|
||||
NSValue *value = ALTDeviceManager.sharedManager.installationClients[UUID];
|
||||
if (value != nil)
|
||||
{
|
||||
np_client_t np = (np_client_t)value.pointerValue;
|
||||
np_set_notify_callback(np, NULL, uuid);
|
||||
|
||||
ALTDeviceManager.sharedManager.installationClients[UUID] = nil;
|
||||
}
|
||||
}
|
||||
|
||||
void ALTDeviceManagerUpdateStatus(plist_t command, plist_t status, void *uuid)
|
||||
|
||||
@@ -276,8 +276,7 @@ private extension ViewController
|
||||
|
||||
func registerAppID(name appName: String, identifier: String, team: ALTTeam, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
|
||||
{
|
||||
var bundleID = "com." + team.account.firstName.lowercased() + team.account.lastName.lowercased() + "." + identifier
|
||||
bundleID = bundleID.replacingOccurrences(of: " ", with: "")
|
||||
let bundleID = "com.\(team.identifier).\(identifier)"
|
||||
|
||||
ALTAppleAPI.shared.fetchAppIDs(for: team) { (appIDs, error) in
|
||||
do
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */; };
|
||||
BF1E312B229F474900370A3C /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3129229F474900370A3C /* ConnectionManager.swift */; };
|
||||
BF1E3137229F599C00370A3C /* App.ipa in Resources */ = {isa = PBXBuildFile; fileRef = BF1E3136229F599C00370A3C /* App.ipa */; };
|
||||
BF1E315722A061F500370A3C /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
|
||||
BF1E315822A061F900370A3C /* Result+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBAC8852295C90300587369 /* Result+Conveniences.swift */; };
|
||||
BF1E315922A061FB00370A3C /* Bundle+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */; };
|
||||
@@ -143,6 +143,7 @@
|
||||
BFD52C2022A1A9EC000B7ED1 /* node.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1D22A1A9EC000B7ED1 /* node.c */; };
|
||||
BFD52C2122A1A9EC000B7ED1 /* node_list.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1E22A1A9EC000B7ED1 /* node_list.c */; };
|
||||
BFD52C2222A1A9EC000B7ED1 /* cnary.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1F22A1A9EC000B7ED1 /* cnary.c */; };
|
||||
BFFC044E22A204F40066B31F /* App.ipa in Resources */ = {isa = PBXBuildFile; fileRef = BFFC044D22A204F30066B31F /* App.ipa */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -205,9 +206,9 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppManager.swift; sourceTree = "<group>"; };
|
||||
BF1E3128229F474900370A3C /* ServerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerProtocol.swift; sourceTree = "<group>"; };
|
||||
BF1E3129229F474900370A3C /* ConnectionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionManager.swift; sourceTree = "<group>"; };
|
||||
BF1E3136229F599C00370A3C /* App.ipa */ = {isa = PBXFileReference; lastKnownFileType = file; name = App.ipa; path = ../../../../../../../../../../Desktop/App.ipa; sourceTree = "<group>"; };
|
||||
BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+AltStore.swift"; sourceTree = "<group>"; };
|
||||
BF1E314722A060F300370A3C /* AltStore-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AltStore-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
BF1E314822A060F400370A3C /* NSError+ALTServerError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSError+ALTServerError.h"; sourceTree = "<group>"; };
|
||||
@@ -348,6 +349,7 @@
|
||||
BFD52C1D22A1A9EC000B7ED1 /* node.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = node.c; path = Dependencies/libplist/libcnary/node.c; sourceTree = SOURCE_ROOT; };
|
||||
BFD52C1E22A1A9EC000B7ED1 /* node_list.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = node_list.c; path = Dependencies/libplist/libcnary/node_list.c; sourceTree = SOURCE_ROOT; };
|
||||
BFD52C1F22A1A9EC000B7ED1 /* cnary.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = cnary.c; path = Dependencies/libplist/libcnary/cnary.c; sourceTree = SOURCE_ROOT; };
|
||||
BFFC044D22A204F30066B31F /* App.ipa */ = {isa = PBXFileReference; lastKnownFileType = file; path = App.ipa; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -554,7 +556,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF458693229872EA00BD7491 /* Assets.xcassets */,
|
||||
BF1E3136229F599C00370A3C /* App.ipa */,
|
||||
BFFC044D22A204F30066B31F /* App.ipa */,
|
||||
);
|
||||
name = Resources;
|
||||
sourceTree = "<group>";
|
||||
@@ -664,6 +666,7 @@
|
||||
BFD2476F2284B9A500981D42 /* AppsViewController.swift */,
|
||||
BFD247922284D4B700981D42 /* AppTableViewCell.swift */,
|
||||
BFD2479B2284E19A00981D42 /* AppDetailViewController.swift */,
|
||||
BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */,
|
||||
);
|
||||
path = Apps;
|
||||
sourceTree = "<group>";
|
||||
@@ -905,7 +908,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
BF1E3137229F599C00370A3C /* App.ipa in Resources */,
|
||||
BFFC044E22A204F40066B31F /* App.ipa in Resources */,
|
||||
BF458694229872EA00BD7491 /* Assets.xcassets in Resources */,
|
||||
BF458697229872EA00BD7491 /* Main.storyboard in Resources */,
|
||||
);
|
||||
@@ -1027,6 +1030,7 @@
|
||||
BFB1169B2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift in Sources */,
|
||||
BFD247932284D4B700981D42 /* AppTableViewCell.swift in Sources */,
|
||||
BFD52BD422A0800A000B7ED1 /* ServerManager.swift in Sources */,
|
||||
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
||||
BFD52BD622A08A85000B7ED1 /* Server.swift in Sources */,
|
||||
BFD2479F2284FBD000981D42 /* UIColor+AltStore.swift in Sources */,
|
||||
);
|
||||
|
||||
@@ -118,69 +118,36 @@ private extension AppDetailViewController
|
||||
@IBAction func downloadApp(_ sender: UIButton)
|
||||
{
|
||||
guard self.app.installedApp == nil else { return }
|
||||
|
||||
sender.isIndicatingActivity = true
|
||||
|
||||
let appURL = Bundle.main.url(forResource: "App", withExtension: "ipa")!
|
||||
|
||||
do
|
||||
{
|
||||
try FileManager.default.copyItem(at: appURL, to: self.app.ipaURL, shouldReplace: true)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Failed to copy .ipa", error)
|
||||
}
|
||||
|
||||
if let server = ServerManager.shared.discoveredServers.first
|
||||
{
|
||||
sender.isIndicatingActivity = true
|
||||
|
||||
server.install(self.app) { (result) in
|
||||
AppManager.shared.install(self.app, presentingViewController: self) { (result) in
|
||||
do
|
||||
{
|
||||
let installedApp = try result.get()
|
||||
|
||||
do { try installedApp.managedObjectContext?.save() }
|
||||
catch { print("Failed to save context.", error) }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
switch result
|
||||
{
|
||||
case .success:
|
||||
let toastView = RSTToastView(text: "Installed \(self.app.name)!", detailText: nil)
|
||||
toastView.tintColor = .altPurple
|
||||
toastView.show(in: self.navigationController!.view, duration: 2)
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
let app = context.object(with: self.app.objectID) as! App
|
||||
|
||||
_ = InstalledApp(app: app,
|
||||
bundleIdentifier: app.identifier,
|
||||
signedDate: Date(),
|
||||
expirationDate: Date().addingTimeInterval(60 * 60 * 24 * 7),
|
||||
context: context)
|
||||
|
||||
do
|
||||
{
|
||||
try context.save()
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Failed to save context for downloaded app app.", error)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
let toastView = RSTToastView(text: "Failed to install \(self.app.name)", detailText: error.localizedDescription)
|
||||
toastView.tintColor = .altPurple
|
||||
toastView.show(in: self.navigationController!.view, duration: 2)
|
||||
}
|
||||
|
||||
sender.isIndicatingActivity = false
|
||||
let toastView = RSTToastView(text: "Installed \(self.app.name)!", detailText: nil)
|
||||
toastView.tintColor = .altPurple
|
||||
toastView.show(in: self.navigationController!.view, duration: 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
let toastView = RSTToastView(text: "Could not find AltServer", detailText: nil)
|
||||
toastView.tintColor = .altPurple
|
||||
toastView.show(in: self.navigationController!.view, duration: 2)
|
||||
catch
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
let toastView = RSTToastView(text: "Failed to install \(self.app.name)", detailText: error.localizedDescription)
|
||||
toastView.tintColor = .altPurple
|
||||
toastView.show(in: self.navigationController!.view, duration: 2)
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.update()
|
||||
sender.isIndicatingActivity = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
448
AltStore/Apps/AppManager.swift
Normal file
448
AltStore/Apps/AppManager.swift
Normal file
@@ -0,0 +1,448 @@
|
||||
//
|
||||
// AppManager.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 5/29/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
import AltSign
|
||||
import AltKit
|
||||
|
||||
import Roxas
|
||||
|
||||
extension AppManager
|
||||
{
|
||||
enum AppError: LocalizedError
|
||||
{
|
||||
case unknown
|
||||
case missingUDID
|
||||
case noServersFound
|
||||
case missingPrivateKey
|
||||
case missingCertificate
|
||||
|
||||
case download(URLError)
|
||||
case authentication(Error)
|
||||
case fetchingSigningResources(Error)
|
||||
case sign(Error)
|
||||
case install(Error)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self
|
||||
{
|
||||
case .unknown: return "An unknown error occured."
|
||||
case .missingUDID: return "The UDID for this device is unknown."
|
||||
case .noServersFound: return "An active AltServer could not be found."
|
||||
case .missingPrivateKey: return "A valid private key must be provided."
|
||||
case .missingCertificate: return "A valid certificate must be provided."
|
||||
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 .install(let error): return error.localizedDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AppManager
|
||||
{
|
||||
static let shared = AppManager()
|
||||
|
||||
private let session = URLSession(configuration: .default)
|
||||
|
||||
private init()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
extension AppManager
|
||||
{
|
||||
func install(_ app: App, presentingViewController: UIViewController, completionHandler: @escaping (Result<InstalledApp, AppError>) -> Void)
|
||||
{
|
||||
let ipaURL = app.ipaURL
|
||||
|
||||
let backgroundTaskID = RSTBeginBackgroundTask("com.rileytestut.AltStore.InstallApp")
|
||||
|
||||
func finish(_ result: Result<InstalledApp, AppError>)
|
||||
{
|
||||
completionHandler(result)
|
||||
|
||||
RSTEndBackgroundTask(backgroundTaskID)
|
||||
}
|
||||
|
||||
// Download app
|
||||
self.downloadApp(from: app.downloadURL) { (result) in
|
||||
let result = result.flatMap { (fileURL) -> Result<Void, URLError> in
|
||||
// Copy downloaded app to proper location
|
||||
let result = Result { try FileManager.default.copyItem(at: fileURL, to: ipaURL, shouldReplace: true) }
|
||||
return result.mapError { _ in URLError(.cannotWriteToFile) }
|
||||
}
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): finish(.failure(.download(error)))
|
||||
case .success:
|
||||
|
||||
// Authenticate
|
||||
self.authenticate(presentingViewController: presentingViewController) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): finish(.failure(.authentication(error)))
|
||||
case .success(let team):
|
||||
|
||||
// Fetch signing resources
|
||||
self.fetchSigningResources(for: app, team: team, presentingViewController: presentingViewController) { (result) in
|
||||
switch result
|
||||
{
|
||||
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
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): finish(.failure(.sign(error)))
|
||||
case .success(let resignedURL):
|
||||
|
||||
// Send app to server
|
||||
app.managedObjectContext?.perform {
|
||||
self.sendAppToServer(fileURL: resignedURL, identifier: app.identifier) { (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)
|
||||
finish(.success(installedApp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppManager
|
||||
{
|
||||
func downloadApp(from url: URL, completionHandler: @escaping (Result<URL, URLError>) -> Void)
|
||||
{
|
||||
let downloadTask = self.session.downloadTask(with: url) { (fileURL, response, error) in
|
||||
do
|
||||
{
|
||||
let (fileURL, _) = try Result((fileURL, response), error).get()
|
||||
completionHandler(.success(fileURL))
|
||||
}
|
||||
catch let error as URLError
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(URLError(.unknown)))
|
||||
}
|
||||
}
|
||||
|
||||
downloadTask.resume()
|
||||
}
|
||||
|
||||
func authenticate(presentingViewController: UIViewController, completionHandler: @escaping (Result<ALTTeam, Error>) -> Void)
|
||||
{
|
||||
let alertController = UIAlertController(title: "Enter Apple ID + Password", message: "", preferredStyle: .alert)
|
||||
alertController.addTextField { (textField) in
|
||||
textField.placeholder = "Apple ID"
|
||||
textField.textContentType = .emailAddress
|
||||
}
|
||||
alertController.addTextField { (textField) in
|
||||
textField.placeholder = "Password"
|
||||
textField.textContentType = .password
|
||||
}
|
||||
alertController.addAction(.cancel)
|
||||
alertController.addAction(UIAlertAction(title: "Sign In", style: .default) { [unowned alertController] (action) in
|
||||
guard
|
||||
let emailAddress = alertController.textFields![0].text,
|
||||
let password = alertController.textFields![1].text,
|
||||
!emailAddress.isEmpty, !password.isEmpty
|
||||
else { return completionHandler(.failure(ALTAppleAPIError(.incorrectCredentials))) }
|
||||
|
||||
ALTAppleAPI.shared.authenticate(appleID: emailAddress, password: password) { (account, error) in
|
||||
do
|
||||
{
|
||||
let account = try Result(account, error).get()
|
||||
self.fetchTeam(for: account, presentingViewController: presentingViewController, completionHandler: completionHandler)
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
presentingViewController.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func fetchSigningResources(for app: App, team: ALTTeam, presentingViewController: UIViewController, completionHandler: @escaping (Result<(ALTCertificate, ALTProvisioningProfile), Error>) -> Void)
|
||||
{
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { return completionHandler(.failure(AppError.missingUDID)) }
|
||||
|
||||
let device = ALTDevice(name: UIDevice.current.name, identifier: udid)
|
||||
self.register(device, team: team) { (result) in
|
||||
do
|
||||
{
|
||||
_ = try result.get()
|
||||
|
||||
self.fetchCertificate(for: team, presentingViewController: presentingViewController) { (result) in
|
||||
do
|
||||
{
|
||||
let certificate = try result.get()
|
||||
|
||||
app.managedObjectContext?.perform {
|
||||
self.register(app, with: team) { (result) in
|
||||
do
|
||||
{
|
||||
let appID = try result.get()
|
||||
self.fetchProvisioningProfile(for: appID, team: team) { (result) in
|
||||
do
|
||||
{
|
||||
let provisioningProfile = try result.get()
|
||||
completionHandler(.success((certificate, provisioningProfile)))
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sign(_ app: App, team: ALTTeam, certificate: ALTCertificate, provisioningProfile: ALTProvisioningProfile, completionHandler: @escaping (Result<URL, Error>) -> 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)
|
||||
}
|
||||
}
|
||||
|
||||
func sendAppToServer(fileURL: URL, identifier: String, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
guard let server = ServerManager.shared.discoveredServers.first else { return completionHandler(.failure(AppError.noServersFound)) }
|
||||
|
||||
server.installApp(at: fileURL, identifier: identifier) { (result) in
|
||||
let result = result.mapError { $0 as Error }
|
||||
completionHandler(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppManager
|
||||
{
|
||||
func fetchTeam(for account: ALTAccount, presentingViewController: UIViewController, completionHandler: @escaping (Result<ALTTeam, Error>) -> Void)
|
||||
{
|
||||
ALTAppleAPI.shared.fetchTeams(for: account) { (teams, error) in
|
||||
do
|
||||
{
|
||||
let teams = try Result(teams, error).get()
|
||||
guard teams.count > 0 else { throw ALTAppleAPIError(.noTeams) }
|
||||
|
||||
if let team = teams.first, teams.count == 1
|
||||
{
|
||||
completionHandler(.success(team))
|
||||
}
|
||||
else
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
let alertController = UIAlertController(title: "Select Team", message: "", preferredStyle: .actionSheet)
|
||||
alertController.addAction(.cancel)
|
||||
|
||||
for team in teams
|
||||
{
|
||||
alertController.addAction(UIAlertAction(title: team.name, style: .default) { (action) in
|
||||
completionHandler(.success(team))
|
||||
})
|
||||
}
|
||||
|
||||
presentingViewController.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchCertificate(for team: ALTTeam, presentingViewController: UIViewController, completionHandler: @escaping (Result<ALTCertificate, Error>) -> Void)
|
||||
{
|
||||
ALTAppleAPI.shared.fetchCertificates(for: team) { (certificates, error) in
|
||||
do
|
||||
{
|
||||
let certificates = try Result(certificates, error).get()
|
||||
|
||||
if certificates.count < 1
|
||||
{
|
||||
let machineName = "AltStore - " + UIDevice.current.name
|
||||
ALTAppleAPI.shared.addCertificate(machineName: machineName, to: team) { (certificate, error) in
|
||||
do
|
||||
{
|
||||
let certificate = try Result(certificate, error).get()
|
||||
guard let privateKey = certificate.privateKey else { throw AppError.missingPrivateKey }
|
||||
|
||||
ALTAppleAPI.shared.fetchCertificates(for: team) { (certificates, error) in
|
||||
do
|
||||
{
|
||||
let certificates = try Result(certificates, error).get()
|
||||
|
||||
guard let certificate = certificates.first(where: { $0.identifier == certificate.identifier }) else {
|
||||
throw AppError.missingCertificate
|
||||
}
|
||||
|
||||
certificate.privateKey = privateKey
|
||||
|
||||
completionHandler(.success(certificate))
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
let alertController = UIAlertController(title: "Too Many Certificates", message: "Please select the certificate you would like to revoke.", preferredStyle: .actionSheet)
|
||||
alertController.addAction(.cancel)
|
||||
|
||||
for certificate in certificates
|
||||
{
|
||||
alertController.addAction(UIAlertAction(title: certificate.name, style: .default) { (action) in
|
||||
ALTAppleAPI.shared.revoke(certificate, for: team) { (success, error) in
|
||||
do
|
||||
{
|
||||
try Result(success, error).get()
|
||||
self.fetchCertificate(for: team, presentingViewController: presentingViewController, completionHandler: completionHandler)
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
presentingViewController.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func register(_ device: ALTDevice, team: ALTTeam, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
||||
{
|
||||
ALTAppleAPI.shared.fetchDevices(for: team) { (devices, error) in
|
||||
do
|
||||
{
|
||||
let devices = try Result(devices, error).get()
|
||||
|
||||
if let device = devices.first(where: { $0.identifier == device.identifier })
|
||||
{
|
||||
completionHandler(.success(device))
|
||||
}
|
||||
else
|
||||
{
|
||||
ALTAppleAPI.shared.registerDevice(name: device.name, identifier: device.identifier, team: team) { (device, error) in
|
||||
completionHandler(Result(device, error))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func register(_ app: App, with team: ALTTeam, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
|
||||
{
|
||||
let appName = app.name
|
||||
let bundleID = "com.\(team.identifier).\(app.identifier)"
|
||||
|
||||
ALTAppleAPI.shared.fetchAppIDs(for: team) { (appIDs, error) in
|
||||
do
|
||||
{
|
||||
let appIDs = try Result(appIDs, error).get()
|
||||
|
||||
if let appID = appIDs.first(where: { $0.bundleIdentifier == bundleID })
|
||||
{
|
||||
completionHandler(.success(appID))
|
||||
}
|
||||
else
|
||||
{
|
||||
ALTAppleAPI.shared.addAppID(withName: appName, bundleIdentifier: bundleID, team: team) { (appID, error) in
|
||||
completionHandler(Result(appID, error))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchProvisioningProfile(for appID: ALTAppID, team: ALTTeam, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
|
||||
{
|
||||
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, team: team) { (profile, error) in
|
||||
completionHandler(Result(profile, error))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14490.99" systemVersion="18D109" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="App" representedClassName="App" syncable="YES">
|
||||
<attribute name="developerName" attributeType="String" syncable="YES"/>
|
||||
<attribute name="downloadURL" attributeType="URI" syncable="YES"/>
|
||||
<attribute name="iconName" attributeType="String" syncable="YES"/>
|
||||
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||
<attribute name="localizedDescription" attributeType="String" syncable="YES"/>
|
||||
@@ -26,7 +27,7 @@
|
||||
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="App" inverseName="installedApp" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="App" positionX="-63" positionY="-18" width="128" height="195"/>
|
||||
<element name="App" positionX="-63" positionY="-18" width="128" height="210"/>
|
||||
<element name="InstalledApp" positionX="-63" positionY="0" width="128" height="135"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -28,6 +28,8 @@ class App: NSManagedObject, Decodable
|
||||
@NSManaged private(set) var versionDate: Date
|
||||
@NSManaged private(set) var versionDescription: String?
|
||||
|
||||
@NSManaged private(set) var downloadURL: URL
|
||||
|
||||
/* Relationships */
|
||||
@NSManaged private(set) var installedApp: InstalledApp?
|
||||
|
||||
@@ -47,6 +49,7 @@ class App: NSManagedObject, Decodable
|
||||
case versionDate
|
||||
case iconName
|
||||
case screenshotNames
|
||||
case downloadURL
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws
|
||||
@@ -68,6 +71,8 @@ class App: NSManagedObject, Decodable
|
||||
self.iconName = try container.decode(String.self, forKey: .iconName)
|
||||
self.screenshotNames = try container.decodeIfPresent([String].self, forKey: .screenshotNames) ?? []
|
||||
|
||||
self.downloadURL = try container.decode(URL.self, forKey: .downloadURL)
|
||||
|
||||
context.insert(self)
|
||||
}
|
||||
}
|
||||
|
||||
BIN
AltStore/Resources/App.ipa
Normal file
BIN
AltStore/Resources/App.ipa
Normal file
Binary file not shown.
@@ -6,6 +6,7 @@
|
||||
"version": "1.0",
|
||||
"versionDate": "2019-05-20",
|
||||
"versionDescription": "Finally, after over five years of waiting, Delta is out of beta and ready for everyone to enjoy!\n\nCurrently supports NES, SNES, N64, GB(C), and GBA games, with more to come in the future.",
|
||||
"downloadURL": "https://www.dropbox.com/s/hdbr7mkae1ljivz/App.ipa?dl=1",
|
||||
"localizedDescription": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed enim ut sem viverra aliquet eget sit amet. Viverra mauris in aliquam sem fringilla ut. Egestas erat imperdiet sed euismod nisi porta. Sit amet dictum sit amet justo donec enim diam vulputate. Phasellus vestibulum lorem sed risus ultricies tristique nulla. Elit pellentesque habitant morbi tristique. Ut lectus arcu bibendum at. Ullamcorper a lacus vestibulum sed. Mi tempus imperdiet nulla malesuada pellentesque elit eget gravida. Nec feugiat nisl pretium fusce id velit ut. Amet nulla facilisi morbi tempus. Ut sem nulla pharetra diam sit amet nisl.\n\nTortor at auctor urna nunc id cursus metus. Commodo ullamcorper a lacus vestibulum sed arcu non odio. Faucibus turpis in eu mi bibendum neque egestas. Auctor augue mauris augue neque gravida in fermentum et sollicitudin. Aliquam vestibulum morbi blandit cursus risus at ultrices mi tempus. Placerat in egestas erat imperdiet sed euismod nisi. Aliquam id diam maecenas ultricies mi eget mauris. Nunc faucibus a pellentesque sit amet porttitor eget dolor. Sed faucibus turpis in eu mi. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus sed. Id semper risus in hendrerit gravida rutrum quisque. At lectus urna duis convallis convallis. Egestas maecenas pharetra convallis posuere. Id velit ut tortor pretium viverra. Quam pellentesque nec nam aliquam sem et tortor consequat. Risus pretium quam vulputate dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar.\n\nTempor orci eu lobortis elementum nibh tellus. Mattis rhoncus urna neque viverra justo nec. Maecenas pharetra convallis posuere morbi leo. Rhoncus mattis rhoncus urna neque viverra justo nec. Gravida dictum fusce ut placerat orci nulla pellentesque dignissim enim. At imperdiet dui accumsan sit amet. Elit sed vulputate mi sit amet mauris commodo. Pellentesque habitant morbi tristique senectus. Tortor id aliquet lectus proin nibh. Magna etiam tempor orci eu lobortis elementum. Est pellentesque elit ullamcorper dignissim. Dapibus ultrices in iaculis nunc. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Diam vel quam elementum pulvinar. Vel turpis nunc eget lorem dolor sed viverra. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae. Euismod nisi porta lorem mollis. Massa id neque aliquam vestibulum morbi blandit.\n\nMassa massa ultricies mi quis hendrerit dolor magna eget est. Augue interdum velit euismod in pellentesque massa. Sed risus ultricies tristique nulla aliquet enim. Risus viverra adipiscing at in tellus. Donec adipiscing tristique risus nec feugiat. Eget sit amet tellus cras adipiscing enim eu turpis. Auctor neque vitae tempus quam pellentesque nec. Sit amet tellus cras adipiscing enim eu turpis egestas. Dui faucibus in ornare quam viverra. Fermentum iaculis eu non diam phasellus vestibulum lorem. Odio ut enim blandit volutpat maecenas. Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique. Pellentesque diam volutpat commodo sed egestas egestas. Aliquam purus sit amet luctus venenatis lectus magna fringilla. Viverra mauris in aliquam sem fringilla ut morbi tincidunt. Elit duis tristique sollicitudin nibh sit. Fermentum dui faucibus in ornare quam viverra orci sagittis. Aliquet eget sit amet tellus cras adipiscing.",
|
||||
"iconName": "DeltaIcon",
|
||||
"screenshotNames": [
|
||||
@@ -21,6 +22,7 @@
|
||||
"developerName": "Riley Testut",
|
||||
"version": "1.0",
|
||||
"versionDate": "2019-06-20",
|
||||
"downloadURL": "https://www.dropbox.com/s/hdbr7mkae1ljivz/App.ipa?dl=1",
|
||||
"localizedDescription": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed enim ut sem viverra aliquet eget sit amet. Viverra mauris in aliquam sem fringilla ut. Egestas erat imperdiet sed euismod nisi porta. Sit amet dictum sit amet justo donec enim diam vulputate. Phasellus vestibulum lorem sed risus ultricies tristique nulla. Elit pellentesque habitant morbi tristique. Ut lectus arcu bibendum at. Ullamcorper a lacus vestibulum sed. Mi tempus imperdiet nulla malesuada pellentesque elit eget gravida. Nec feugiat nisl pretium fusce id velit ut. Amet nulla facilisi morbi tempus. Ut sem nulla pharetra diam sit amet nisl.\n\nTortor at auctor urna nunc id cursus metus. Commodo ullamcorper a lacus vestibulum sed arcu non odio. Faucibus turpis in eu mi bibendum neque egestas. Auctor augue mauris augue neque gravida in fermentum et sollicitudin. Aliquam vestibulum morbi blandit cursus risus at ultrices mi tempus. Placerat in egestas erat imperdiet sed euismod nisi. Aliquam id diam maecenas ultricies mi eget mauris. Nunc faucibus a pellentesque sit amet porttitor eget dolor. Sed faucibus turpis in eu mi. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus sed. Id semper risus in hendrerit gravida rutrum quisque. At lectus urna duis convallis convallis. Egestas maecenas pharetra convallis posuere. Id velit ut tortor pretium viverra. Quam pellentesque nec nam aliquam sem et tortor consequat. Risus pretium quam vulputate dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar.\n\nTempor orci eu lobortis elementum nibh tellus. Mattis rhoncus urna neque viverra justo nec. Maecenas pharetra convallis posuere morbi leo. Rhoncus mattis rhoncus urna neque viverra justo nec. Gravida dictum fusce ut placerat orci nulla pellentesque dignissim enim. At imperdiet dui accumsan sit amet. Elit sed vulputate mi sit amet mauris commodo. Pellentesque habitant morbi tristique senectus. Tortor id aliquet lectus proin nibh. Magna etiam tempor orci eu lobortis elementum. Est pellentesque elit ullamcorper dignissim. Dapibus ultrices in iaculis nunc. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Diam vel quam elementum pulvinar. Vel turpis nunc eget lorem dolor sed viverra. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae. Euismod nisi porta lorem mollis. Massa id neque aliquam vestibulum morbi blandit.\n\nMassa massa ultricies mi quis hendrerit dolor magna eget est. Augue interdum velit euismod in pellentesque massa. Sed risus ultricies tristique nulla aliquet enim. Risus viverra adipiscing at in tellus. Donec adipiscing tristique risus nec feugiat. Eget sit amet tellus cras adipiscing enim eu turpis. Auctor neque vitae tempus quam pellentesque nec. Sit amet tellus cras adipiscing enim eu turpis egestas. Dui faucibus in ornare quam viverra. Fermentum iaculis eu non diam phasellus vestibulum lorem. Odio ut enim blandit volutpat maecenas. Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique. Pellentesque diam volutpat commodo sed egestas egestas. Aliquam purus sit amet luctus venenatis lectus magna fringilla. Viverra mauris in aliquam sem fringilla ut morbi tincidunt. Elit duis tristique sollicitudin nibh sit. Fermentum dui faucibus in ornare quam viverra orci sagittis. Aliquet eget sit amet tellus cras adipiscing.",
|
||||
"iconName": "ClipboardIcon"
|
||||
}
|
||||
|
||||
@@ -41,11 +41,8 @@ struct Server: Equatable
|
||||
|
||||
private let dispatchQueue = DispatchQueue(label: "com.rileytestut.AltStore.server", qos: .utility)
|
||||
|
||||
func install(_ app: App, completionHandler: @escaping (Result<Void, InstallError>) -> Void)
|
||||
func installApp(at fileURL: URL, identifier: String, completionHandler: @escaping (Result<Void, InstallError>) -> Void)
|
||||
{
|
||||
let ipaURL = app.ipaURL
|
||||
let appID = app.identifier
|
||||
|
||||
var isFinished = false
|
||||
|
||||
var serverConnection: NWConnection?
|
||||
@@ -63,12 +60,12 @@ struct Server: Equatable
|
||||
|
||||
if let error = error
|
||||
{
|
||||
print("Failed to install \(appID).", error)
|
||||
print("Failed to install \(identifier).", error)
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
else
|
||||
{
|
||||
print("Installed \(appID)!")
|
||||
print("Installed \(identifier)!")
|
||||
completionHandler(.success(()))
|
||||
}
|
||||
}
|
||||
@@ -80,7 +77,7 @@ struct Server: Equatable
|
||||
case .success(let connection):
|
||||
serverConnection = connection
|
||||
|
||||
self.sendApp(at: ipaURL, via: connection) { (result) in
|
||||
self.sendApp(at: fileURL, via: connection) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): finish(error: error)
|
||||
@@ -140,6 +137,7 @@ private extension Server
|
||||
let requestSizeData = withUnsafeBytes(of: requestSize) { Data($0) }
|
||||
|
||||
// Send request data size.
|
||||
print("Sending request data size \(requestSize)")
|
||||
connection.send(content: requestSizeData, completion: .contentProcessed { (error) in
|
||||
if error != nil
|
||||
{
|
||||
@@ -148,6 +146,7 @@ private extension Server
|
||||
else
|
||||
{
|
||||
// Send request.
|
||||
print("Sending request \(request)")
|
||||
connection.send(content: requestData, completion: .contentProcessed { (error) in
|
||||
if error != nil
|
||||
{
|
||||
@@ -156,6 +155,7 @@ private extension Server
|
||||
else
|
||||
{
|
||||
// Send app data.
|
||||
print("Sending app data (Size: \(appData.count))")
|
||||
connection.send(content: appData, completion: .contentProcessed { (error) in
|
||||
if error != nil
|
||||
{
|
||||
|
||||
2
Dependencies/AltSign
vendored
2
Dependencies/AltSign
vendored
Submodule Dependencies/AltSign updated: 91cec126ee...8ca7fa3f19
Reference in New Issue
Block a user