mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-12 16:23:32 +01:00
[Both] Adds support for installing apps over USB
This commit is contained in:
@@ -3,3 +3,6 @@
|
||||
//
|
||||
|
||||
#import "ALTDeviceManager.h"
|
||||
#import "ALTWiredConnection.h"
|
||||
#import "ALTNotificationConnection.h"
|
||||
#import "AltKit.h"
|
||||
|
||||
@@ -59,7 +59,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
UserDefaults.standard.registerDefaults()
|
||||
|
||||
UNUserNotificationCenter.current().delegate = self
|
||||
|
||||
ConnectionManager.shared.start()
|
||||
ALTDeviceManager.shared.start()
|
||||
|
||||
let item = NSStatusBar.system.statusItem(withLength: -1)
|
||||
guard let button = item.button else { return }
|
||||
|
||||
24
AltServer/Connections/ALTNotificationConnection+Private.h
Normal file
24
AltServer/Connections/ALTNotificationConnection+Private.h
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// ALTNotificationConnection+Private.h
|
||||
// AltServer
|
||||
//
|
||||
// Created by Riley Testut on 1/10/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ALTNotificationConnection.h"
|
||||
|
||||
#include <libimobiledevice/libimobiledevice.h>
|
||||
#include <libimobiledevice/notification_proxy.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ALTNotificationConnection ()
|
||||
|
||||
@property (nonatomic, readonly) np_client_t client;
|
||||
|
||||
- (instancetype)initWithDevice:(ALTDevice *)device client:(np_client_t)client;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
30
AltServer/Connections/ALTNotificationConnection.h
Normal file
30
AltServer/Connections/ALTNotificationConnection.h
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// ALTNotificationConnection.h
|
||||
// AltServer
|
||||
//
|
||||
// Created by Riley Testut on 1/10/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
#import <AltSign/AltSign.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
NS_SWIFT_NAME(NotificationConnection)
|
||||
@interface ALTNotificationConnection : NSObject
|
||||
|
||||
@property (nonatomic, copy, readonly) ALTDevice *device;
|
||||
|
||||
@property (nonatomic, copy, nullable) void (^receivedNotificationHandler)(CFNotificationName notification);
|
||||
|
||||
- (void)startListeningForNotifications:(NSArray<NSString *> *)notifications
|
||||
completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
|
||||
|
||||
- (void)sendNotification:(CFNotificationName)notification
|
||||
completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
|
||||
|
||||
- (void)disconnect;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
93
AltServer/Connections/ALTNotificationConnection.m
Normal file
93
AltServer/Connections/ALTNotificationConnection.m
Normal file
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// ALTNotificationConnection.m
|
||||
// AltServer
|
||||
//
|
||||
// Created by Riley Testut on 1/10/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ALTNotificationConnection+Private.h"
|
||||
#import "AltKit.h"
|
||||
|
||||
void ALTDeviceReceivedNotification(const char *notification, void *user_data);
|
||||
|
||||
@implementation ALTNotificationConnection
|
||||
|
||||
- (instancetype)initWithDevice:(ALTDevice *)device client:(np_client_t)client
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
_device = [device copy];
|
||||
_client = client;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self disconnect];
|
||||
}
|
||||
|
||||
- (void)disconnect
|
||||
{
|
||||
np_client_free(self.client);
|
||||
_client = nil;
|
||||
}
|
||||
|
||||
- (void)startListeningForNotifications:(NSArray<NSString *> *)notifications completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
|
||||
{
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
|
||||
const char **notificationNames = (const char **)malloc((notifications.count + 1) * sizeof(char *));
|
||||
for (int i = 0; i < notifications.count; i++)
|
||||
{
|
||||
NSString *name = notifications[i];
|
||||
notificationNames[i] = name.UTF8String;
|
||||
}
|
||||
notificationNames[notifications.count] = NULL; // Must have terminating NULL entry.
|
||||
|
||||
np_error_t result = np_observe_notifications(self.client, notificationNames);
|
||||
if (result != NP_E_SUCCESS)
|
||||
{
|
||||
return completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
|
||||
}
|
||||
|
||||
result = np_set_notify_callback(self.client, ALTDeviceReceivedNotification, (__bridge void *)self);
|
||||
if (result != NP_E_SUCCESS)
|
||||
{
|
||||
return completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
|
||||
}
|
||||
|
||||
completionHandler(YES, nil);
|
||||
|
||||
free(notificationNames);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)sendNotification:(CFNotificationName)notification completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
|
||||
{
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
|
||||
np_error_t result = np_post_notification(self.client, [(__bridge NSString *)notification UTF8String]);
|
||||
if (result == NP_E_SUCCESS)
|
||||
{
|
||||
completionHandler(YES, nil);
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void ALTDeviceReceivedNotification(const char *notification, void *user_data)
|
||||
{
|
||||
ALTNotificationConnection *connection = (__bridge ALTNotificationConnection *)user_data;
|
||||
|
||||
if (connection.receivedNotificationHandler)
|
||||
{
|
||||
connection.receivedNotificationHandler((__bridge CFNotificationName)@(notification));
|
||||
}
|
||||
}
|
||||
23
AltServer/Connections/ALTWiredConnection+Private.h
Normal file
23
AltServer/Connections/ALTWiredConnection+Private.h
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// ALTWiredConnection+Private.h
|
||||
// AltServer
|
||||
//
|
||||
// Created by Riley Testut on 1/10/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ALTWiredConnection.h"
|
||||
|
||||
#include <libimobiledevice/libimobiledevice.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ALTWiredConnection ()
|
||||
|
||||
@property (nonatomic, readonly) idevice_connection_t connection;
|
||||
|
||||
- (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
25
AltServer/Connections/ALTWiredConnection.h
Normal file
25
AltServer/Connections/ALTWiredConnection.h
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// ALTWiredConnection.h
|
||||
// AltServer
|
||||
//
|
||||
// Created by Riley Testut on 1/10/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
#import <AltSign/AltSign.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
NS_SWIFT_NAME(WiredConnection)
|
||||
@interface ALTWiredConnection : NSObject
|
||||
|
||||
@property (nonatomic, copy, readonly) ALTDevice *device;
|
||||
|
||||
- (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler;
|
||||
- (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler;
|
||||
|
||||
- (void)disconnect;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
101
AltServer/Connections/ALTWiredConnection.m
Normal file
101
AltServer/Connections/ALTWiredConnection.m
Normal file
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// ALTWiredConnection.m
|
||||
// AltServer
|
||||
//
|
||||
// Created by Riley Testut on 1/10/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ALTWiredConnection+Private.h"
|
||||
#import "AltKit.h"
|
||||
|
||||
@implementation ALTWiredConnection
|
||||
|
||||
- (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
_device = [device copy];
|
||||
_connection = connection;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self disconnect];
|
||||
}
|
||||
|
||||
- (void)disconnect
|
||||
{
|
||||
idevice_disconnect(self.connection);
|
||||
_connection = nil;
|
||||
}
|
||||
|
||||
- (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
|
||||
{
|
||||
void (^finish)(NSError *error) = ^(NSError *error) {
|
||||
if (error != nil)
|
||||
{
|
||||
NSLog(@"Send Error: %@", error);
|
||||
completionHandler(NO, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(YES, nil);
|
||||
}
|
||||
};
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
|
||||
NSMutableData *mutableData = [data mutableCopy];
|
||||
while (mutableData.length > 0)
|
||||
{
|
||||
uint32_t sentBytes = 0;
|
||||
if (idevice_connection_send(self.connection, (const char *)mutableData.bytes, (int32_t)mutableData.length, &sentBytes) != IDEVICE_E_SUCCESS)
|
||||
{
|
||||
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
|
||||
}
|
||||
|
||||
[mutableData replaceBytesInRange:NSMakeRange(0, sentBytes) withBytes:NULL length:0];
|
||||
}
|
||||
|
||||
finish(nil);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler
|
||||
{
|
||||
void (^finish)(NSData *data, NSError *error) = ^(NSData *data, NSError *error) {
|
||||
if (error != nil)
|
||||
{
|
||||
NSLog(@"Receive Data Error: %@", error);
|
||||
}
|
||||
|
||||
completionHandler(data, error);
|
||||
};
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
|
||||
char bytes[4096];
|
||||
NSMutableData *receivedData = [NSMutableData dataWithCapacity:expectedSize];
|
||||
|
||||
while (receivedData.length < expectedSize)
|
||||
{
|
||||
uint32_t size = MIN(4096, (uint32_t)expectedSize - (uint32_t)receivedData.length);
|
||||
|
||||
uint32_t receivedBytes = 0;
|
||||
if (idevice_connection_receive(self.connection, bytes, size, &receivedBytes) != IDEVICE_E_SUCCESS)
|
||||
{
|
||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
|
||||
}
|
||||
|
||||
NSData *data = [NSData dataWithBytesNoCopy:bytes length:receivedBytes freeWhenDone:NO];
|
||||
[receivedData appendData:data];
|
||||
}
|
||||
|
||||
finish(receivedData, nil);
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
231
AltServer/Connections/ClientConnection.swift
Normal file
231
AltServer/Connections/ClientConnection.swift
Normal file
@@ -0,0 +1,231 @@
|
||||
//
|
||||
// ClientConnection.swift
|
||||
// AltServer
|
||||
//
|
||||
// Created by Riley Testut on 1/9/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
import AltKit
|
||||
import AltSign
|
||||
|
||||
extension ClientConnection
|
||||
{
|
||||
enum Connection
|
||||
{
|
||||
case wireless(NWConnection)
|
||||
case wired(WiredConnection)
|
||||
}
|
||||
}
|
||||
|
||||
class ClientConnection
|
||||
{
|
||||
let connection: Connection
|
||||
|
||||
init(connection: Connection)
|
||||
{
|
||||
self.connection = connection
|
||||
}
|
||||
|
||||
func disconnect()
|
||||
{
|
||||
switch self.connection
|
||||
{
|
||||
case .wireless(let connection):
|
||||
switch connection.state
|
||||
{
|
||||
case .cancelled, .failed:
|
||||
print("Disconnecting from \(connection.endpoint)...")
|
||||
|
||||
default:
|
||||
// State update handler might call this method again.
|
||||
connection.cancel()
|
||||
}
|
||||
|
||||
case .wired(let connection):
|
||||
connection.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
func send<T: Encodable>(_ response: T, shouldDisconnect: Bool = false, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
|
||||
{
|
||||
func finish(_ result: Result<Void, ALTServerError>)
|
||||
{
|
||||
completionHandler(result)
|
||||
|
||||
if shouldDisconnect
|
||||
{
|
||||
// Add short delay to prevent us from dropping connection too quickly.
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
|
||||
self.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
let data = try JSONEncoder().encode(response)
|
||||
let responseSize = withUnsafeBytes(of: Int32(data.count)) { Data($0) }
|
||||
|
||||
self.send(responseSize) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure: finish(.failure(.init(.lostConnection)))
|
||||
case .success:
|
||||
|
||||
self.send(data) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure: finish(.failure(.init(.lostConnection)))
|
||||
case .success: finish(.success(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
finish(.failure(.init(.invalidResponse)))
|
||||
}
|
||||
}
|
||||
|
||||
func receiveRequest(completionHandler: @escaping (Result<ServerRequest, ALTServerError>) -> Void)
|
||||
{
|
||||
let size = MemoryLayout<Int32>.size
|
||||
|
||||
print("Receiving request size")
|
||||
self.receiveData(expectedBytes: size) { (result) in
|
||||
do
|
||||
{
|
||||
let data = try result.get()
|
||||
|
||||
print("Receiving request...")
|
||||
let expectedBytes = Int(data.withUnsafeBytes { $0.load(as: Int32.self) })
|
||||
self.receiveData(expectedBytes: expectedBytes) { (result) in
|
||||
do
|
||||
{
|
||||
let data = try result.get()
|
||||
let request = try JSONDecoder().decode(ServerRequest.self, from: data)
|
||||
|
||||
print("Received installation request:", request)
|
||||
completionHandler(.success(request))
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(ALTServerError(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(ALTServerError(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func send(_ data: Data, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
switch self.connection
|
||||
{
|
||||
case .wireless(let connection):
|
||||
connection.send(content: data, completion: .contentProcessed { (error) in
|
||||
if let error = error
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(.success(()))
|
||||
}
|
||||
})
|
||||
|
||||
case .wired(let connection):
|
||||
connection.send(data) { (success, error) in
|
||||
if !success
|
||||
{
|
||||
completionHandler(.failure(ALTServerError(.lostConnection)))
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(.success(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receiveData(expectedBytes: Int, completionHandler: @escaping (Result<Data, Error>) -> Void)
|
||||
{
|
||||
func finish(data: Data?, error: Error?)
|
||||
{
|
||||
do
|
||||
{
|
||||
let data = try self.process(data: data, error: error)
|
||||
completionHandler(.success(data))
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(ALTServerError(error)))
|
||||
}
|
||||
}
|
||||
|
||||
switch self.connection
|
||||
{
|
||||
case .wireless(let connection):
|
||||
connection.receive(minimumIncompleteLength: expectedBytes, maximumLength: expectedBytes) { (data, _, _, error) in
|
||||
finish(data: data, error: error)
|
||||
}
|
||||
|
||||
case .wired(let connection):
|
||||
connection.receiveData(withExpectedSize: expectedBytes) { (data, error) in
|
||||
finish(data: data, error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ClientConnection: CustomStringConvertible
|
||||
{
|
||||
var description: String {
|
||||
switch self.connection
|
||||
{
|
||||
case .wireless(let connection): return "\(connection.endpoint) (Wireless)"
|
||||
case .wired(let connection): return "\(connection.device.name) (Wired)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ClientConnection
|
||||
{
|
||||
func process(data: Data?, error: Error?) throws -> Data
|
||||
{
|
||||
do
|
||||
{
|
||||
do
|
||||
{
|
||||
guard let data = data else { throw error ?? ALTServerError(.unknown) }
|
||||
return data
|
||||
}
|
||||
catch let error as NWError
|
||||
{
|
||||
print("Error receiving data from connection \(connection)", error)
|
||||
|
||||
throw ALTServerError(.lostConnection)
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw error
|
||||
}
|
||||
}
|
||||
catch let error as ALTServerError
|
||||
{
|
||||
throw error
|
||||
}
|
||||
catch
|
||||
{
|
||||
preconditionFailure("A non-ALTServerError should never be thrown from this method.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,10 +54,13 @@ class ConnectionManager
|
||||
private lazy var listener = self.makeListener()
|
||||
private let dispatchQueue = DispatchQueue(label: "com.rileytestut.AltServer.connections", qos: .utility)
|
||||
|
||||
private var connections = [NWConnection]()
|
||||
private var connections = [ClientConnection]()
|
||||
private var notificationConnections = [ALTDevice: NotificationConnection]()
|
||||
|
||||
private init()
|
||||
{
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(ConnectionManager.deviceDidConnect(_:)), name: .deviceManagerDeviceDidConnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(ConnectionManager.deviceDidDisconnect(_:)), name: .deviceManagerDeviceDidDisconnect, object: nil)
|
||||
}
|
||||
|
||||
func start()
|
||||
@@ -77,6 +80,16 @@ class ConnectionManager
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
func disconnect(_ connection: ClientConnection)
|
||||
{
|
||||
connection.disconnect()
|
||||
|
||||
if let index = self.connections.firstIndex(where: { $0 === connection })
|
||||
{
|
||||
self.connections.remove(at: index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ConnectionManager
|
||||
@@ -127,67 +140,18 @@ private extension ConnectionManager
|
||||
}
|
||||
|
||||
listener.newConnectionHandler = { [weak self] (connection) in
|
||||
self?.awaitRequest(from: connection)
|
||||
self?.prepare(connection)
|
||||
}
|
||||
|
||||
return listener
|
||||
}
|
||||
|
||||
func disconnect(_ connection: NWConnection)
|
||||
func prepare(_ connection: NWConnection)
|
||||
{
|
||||
switch connection.state
|
||||
{
|
||||
case .cancelled, .failed:
|
||||
print("Disconnecting from \(connection.endpoint)...")
|
||||
|
||||
if let index = self.connections.firstIndex(where: { $0 === connection })
|
||||
{
|
||||
self.connections.remove(at: index)
|
||||
}
|
||||
|
||||
default:
|
||||
// State update handler will call this method again.
|
||||
connection.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func process(data: Data?, error: NWError?, from connection: NWConnection) throws -> Data
|
||||
{
|
||||
do
|
||||
{
|
||||
do
|
||||
{
|
||||
guard let data = data else { throw error ?? ALTServerError(.unknown) }
|
||||
return data
|
||||
}
|
||||
catch let error as NWError
|
||||
{
|
||||
print("Error receiving data from connection \(connection)", error)
|
||||
|
||||
throw ALTServerError(.lostConnection)
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw error
|
||||
}
|
||||
}
|
||||
catch let error as ALTServerError
|
||||
{
|
||||
throw error
|
||||
}
|
||||
catch
|
||||
{
|
||||
preconditionFailure("A non-ALTServerError should never be thrown from this method.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ConnectionManager
|
||||
{
|
||||
func awaitRequest(from connection: NWConnection)
|
||||
{
|
||||
guard !self.connections.contains(where: { $0 === connection }) else { return }
|
||||
self.connections.append(connection)
|
||||
let clientConnection = ClientConnection(connection: .wireless(connection))
|
||||
|
||||
guard !self.connections.contains(where: { $0 === clientConnection }) else { return }
|
||||
self.connections.append(clientConnection)
|
||||
|
||||
connection.stateUpdateHandler = { [weak self] (state) in
|
||||
switch state
|
||||
@@ -196,17 +160,17 @@ private extension ConnectionManager
|
||||
|
||||
case .ready:
|
||||
print("Connected to client:", connection.endpoint)
|
||||
self?.handleRequest(for: connection)
|
||||
self?.handleRequest(for: clientConnection)
|
||||
|
||||
case .waiting:
|
||||
print("Waiting for connection...")
|
||||
|
||||
case .failed(let error):
|
||||
print("Failed to connect to service \(connection.endpoint).", error)
|
||||
self?.disconnect(connection)
|
||||
self?.disconnect(clientConnection)
|
||||
|
||||
case .cancelled:
|
||||
self?.disconnect(connection)
|
||||
self?.disconnect(clientConnection)
|
||||
|
||||
@unknown default: break
|
||||
}
|
||||
@@ -214,17 +178,85 @@ private extension ConnectionManager
|
||||
|
||||
connection.start(queue: self.dispatchQueue)
|
||||
}
|
||||
|
||||
func handleRequest(for connection: NWConnection)
|
||||
}
|
||||
|
||||
private extension ConnectionManager
|
||||
{
|
||||
func startNotificationConnection(to device: ALTDevice)
|
||||
{
|
||||
self.receiveRequest(from: connection) { (result) in
|
||||
ALTDeviceManager.shared.startNotificationConnection(to: device) { (connection, error) in
|
||||
guard let connection = connection else { return }
|
||||
|
||||
let notifications: [CFNotificationName] = [.wiredServerConnectionAvailableRequest, .wiredServerConnectionStartRequest]
|
||||
connection.startListening(forNotifications: notifications.map { String($0.rawValue) }) { (success, error) in
|
||||
guard success else { return }
|
||||
|
||||
connection.receivedNotificationHandler = { [weak self, weak connection] (notification) in
|
||||
guard let self = self, let connection = connection else { return }
|
||||
self.handle(notification, for: connection)
|
||||
}
|
||||
|
||||
self.notificationConnections[device] = connection
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stopNotificationConnection(to device: ALTDevice)
|
||||
{
|
||||
guard let connection = self.notificationConnections[device] else { return }
|
||||
connection.disconnect()
|
||||
|
||||
self.notificationConnections[device] = nil
|
||||
}
|
||||
|
||||
func handle(_ notification: CFNotificationName, for connection: NotificationConnection)
|
||||
{
|
||||
switch notification
|
||||
{
|
||||
case .wiredServerConnectionAvailableRequest:
|
||||
connection.sendNotification(.wiredServerConnectionAvailableResponse) { (success, error) in
|
||||
if let error = error, !success
|
||||
{
|
||||
print("Error sending wired server connection response.", error)
|
||||
}
|
||||
else
|
||||
{
|
||||
print("Sent wired server connection available response!")
|
||||
}
|
||||
}
|
||||
|
||||
case .wiredServerConnectionStartRequest:
|
||||
ALTDeviceManager.shared.startWiredConnection(to: connection.device) { (wiredConnection, error) in
|
||||
if let wiredConnection = wiredConnection
|
||||
{
|
||||
print("Started wired server connection!")
|
||||
|
||||
let clientConnection = ClientConnection(connection: .wired(wiredConnection))
|
||||
self.handleRequest(for: clientConnection)
|
||||
}
|
||||
else if let error = error
|
||||
{
|
||||
print("Error starting wired server connection.", error)
|
||||
}
|
||||
}
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ConnectionManager
|
||||
{
|
||||
func handleRequest(for connection: ClientConnection)
|
||||
{
|
||||
connection.receiveRequest() { (result) in
|
||||
print("Received initial request with result:", result)
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error):
|
||||
let response = ErrorResponse(error: ALTServerError(error))
|
||||
self.send(response, to: connection, shouldDisconnect: true) { (result) in
|
||||
connection.send(response, shouldDisconnect: true) { (result) in
|
||||
print("Sent error response with result:", result)
|
||||
}
|
||||
|
||||
@@ -236,34 +268,34 @@ private extension ConnectionManager
|
||||
|
||||
case .success:
|
||||
let response = ErrorResponse(error: ALTServerError(.unknownRequest))
|
||||
self.send(response, to: connection, shouldDisconnect: true) { (result) in
|
||||
connection.send(response, shouldDisconnect: true) { (result) in
|
||||
print("Sent unknown request response with result:", result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: NWConnection)
|
||||
func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: ClientConnection)
|
||||
{
|
||||
AnisetteDataManager.shared.requestAnisetteData { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error):
|
||||
let errorResponse = ErrorResponse(error: ALTServerError(error))
|
||||
self.send(errorResponse, to: connection, shouldDisconnect: true) { (result) in
|
||||
connection.send(errorResponse, shouldDisconnect: true) { (result) in
|
||||
print("Sent anisette data error response with result:", result)
|
||||
}
|
||||
|
||||
case .success(let anisetteData):
|
||||
let response = AnisetteDataResponse(anisetteData: anisetteData)
|
||||
self.send(response, to: connection, shouldDisconnect: true) { (result) in
|
||||
connection.send(response, shouldDisconnect: true) { (result) in
|
||||
print("Sent anisette data response with result:", result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: NWConnection)
|
||||
func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: ClientConnection)
|
||||
{
|
||||
var temporaryURL: URL?
|
||||
|
||||
@@ -278,19 +310,19 @@ private extension ConnectionManager
|
||||
switch result
|
||||
{
|
||||
case .failure(let error):
|
||||
print("Failed to process request from \(connection.endpoint).", error)
|
||||
print("Failed to process request from \(connection).", error)
|
||||
|
||||
let response = ErrorResponse(error: ALTServerError(error))
|
||||
self.send(response, to: connection, shouldDisconnect: true) { (result) in
|
||||
print("Sent install app error response to \(connection.endpoint) with result:", result)
|
||||
connection.send(response, shouldDisconnect: true) { (result) in
|
||||
print("Sent install app error response to \(connection) with result:", result)
|
||||
}
|
||||
|
||||
case .success:
|
||||
print("Processed request from \(connection.endpoint).")
|
||||
print("Processed request from \(connection).")
|
||||
|
||||
let response = InstallationProgressResponse(progress: 1.0)
|
||||
self.send(response, to: connection, shouldDisconnect: true) { (result) in
|
||||
print("Sent install app response to \(connection.endpoint) with result:", result)
|
||||
connection.send(response, shouldDisconnect: true) { (result) in
|
||||
print("Sent install app response to \(connection) with result:", result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,7 +338,7 @@ private extension ConnectionManager
|
||||
|
||||
print("Awaiting begin installation request...")
|
||||
|
||||
self.receiveRequest(from: connection) { (result) in
|
||||
connection.receiveRequest() { (result) in
|
||||
print("Received begin installation request with result:", result)
|
||||
|
||||
switch result
|
||||
@@ -326,8 +358,8 @@ private extension ConnectionManager
|
||||
|
||||
case .success:
|
||||
let response = ErrorResponse(error: ALTServerError(.unknownRequest))
|
||||
self.send(response, to: connection, shouldDisconnect: true) { (result) in
|
||||
print("Sent unknown request error response to \(connection.endpoint) with result:", result)
|
||||
connection.send(response, shouldDisconnect: true) { (result) in
|
||||
print("Sent unknown request error response to \(connection) with result:", result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,17 +367,15 @@ private extension ConnectionManager
|
||||
}
|
||||
}
|
||||
|
||||
func receiveApp(for request: PrepareAppRequest, from connection: NWConnection, completionHandler: @escaping (Result<URL, ALTServerError>) -> Void)
|
||||
func receiveApp(for request: PrepareAppRequest, from connection: ClientConnection, completionHandler: @escaping (Result<URL, ALTServerError>) -> Void)
|
||||
{
|
||||
connection.receive(minimumIncompleteLength: request.contentSize, maximumLength: request.contentSize) { (data, _, _, error) in
|
||||
connection.receiveData(expectedBytes: request.contentSize) { (result) in
|
||||
do
|
||||
{
|
||||
print("Received app data!")
|
||||
|
||||
let data = try self.process(data: data, error: error, from: connection)
|
||||
|
||||
print("Processed app data!")
|
||||
|
||||
let data = try result.get()
|
||||
|
||||
guard ALTDeviceManager.shared.availableDevices.contains(where: { $0.identifier == request.udid }) else { throw ALTServerError(.deviceNotFound) }
|
||||
|
||||
print("Writing app data...")
|
||||
@@ -366,7 +396,7 @@ private extension ConnectionManager
|
||||
}
|
||||
}
|
||||
|
||||
func installApp(at fileURL: URL, toDeviceWithUDID udid: String, connection: NWConnection, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
|
||||
func installApp(at fileURL: URL, toDeviceWithUDID udid: String, connection: ClientConnection, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
|
||||
{
|
||||
let serialQueue = DispatchQueue(label: "com.altstore.ConnectionManager.installQueue", qos: .default)
|
||||
var isSending = false
|
||||
@@ -397,7 +427,7 @@ private extension ConnectionManager
|
||||
print("Progress:", progress.fractionCompleted)
|
||||
let response = InstallationProgressResponse(progress: progress.fractionCompleted)
|
||||
|
||||
self.send(response, to: connection) { (result) in
|
||||
connection.send(response) { (result) in
|
||||
serialQueue.async {
|
||||
isSending = false
|
||||
}
|
||||
@@ -405,92 +435,19 @@ private extension ConnectionManager
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func send<T: Encodable>(_ response: T, to connection: NWConnection, shouldDisconnect: Bool = false, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
|
||||
private extension ConnectionManager
|
||||
{
|
||||
@objc func deviceDidConnect(_ notification: Notification)
|
||||
{
|
||||
func finish(_ result: Result<Void, ALTServerError>)
|
||||
{
|
||||
completionHandler(result)
|
||||
|
||||
if shouldDisconnect
|
||||
{
|
||||
// Add short delay to prevent us from dropping connection too quickly.
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
|
||||
self.disconnect(connection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
let data = try JSONEncoder().encode(response)
|
||||
let responseSize = withUnsafeBytes(of: Int32(data.count)) { Data($0) }
|
||||
|
||||
connection.send(content: responseSize, completion: .contentProcessed { (error) in
|
||||
do
|
||||
{
|
||||
if let error = error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
connection.send(content: data, completion: .contentProcessed { (error) in
|
||||
if error != nil
|
||||
{
|
||||
finish(.failure(.init(.lostConnection)))
|
||||
}
|
||||
else
|
||||
{
|
||||
finish(.success(()))
|
||||
}
|
||||
})
|
||||
}
|
||||
catch
|
||||
{
|
||||
finish(.failure(.init(.lostConnection)))
|
||||
}
|
||||
})
|
||||
}
|
||||
catch
|
||||
{
|
||||
finish(.failure(.init(.invalidResponse)))
|
||||
}
|
||||
guard let device = notification.object as? ALTDevice else { return }
|
||||
self.startNotificationConnection(to: device)
|
||||
}
|
||||
|
||||
func receiveRequest(from connection: NWConnection, completionHandler: @escaping (Result<ServerRequest, ALTServerError>) -> Void)
|
||||
@objc func deviceDidDisconnect(_ notification: Notification)
|
||||
{
|
||||
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
|
||||
{
|
||||
let data = try self.process(data: data, error: error, from: connection)
|
||||
|
||||
let request = try JSONDecoder().decode(ServerRequest.self, from: data)
|
||||
|
||||
print("Received installation request:", request)
|
||||
|
||||
completionHandler(.success(request))
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(ALTServerError(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completionHandler(.failure(ALTServerError(error)))
|
||||
}
|
||||
}
|
||||
guard let device = notification.object as? ALTDevice else { return }
|
||||
self.stopNotificationConnection(to: device)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,14 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AltSign/AltSign.h>
|
||||
|
||||
@class ALTWiredConnection;
|
||||
@class ALTNotificationConnection;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern NSNotificationName const ALTDeviceManagerDeviceDidConnectNotification NS_SWIFT_NAME(deviceManagerDeviceDidConnect);
|
||||
extern NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification NS_SWIFT_NAME(deviceManagerDeviceDidDisconnect);
|
||||
|
||||
@interface ALTDeviceManager : NSObject
|
||||
|
||||
@property (class, nonatomic, readonly) ALTDeviceManager *sharedManager;
|
||||
@@ -18,8 +24,15 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (nonatomic, readonly) NSArray<ALTDevice *> *connectedDevices;
|
||||
@property (nonatomic, readonly) NSArray<ALTDevice *> *availableDevices;
|
||||
|
||||
- (void)start;
|
||||
|
||||
/* App Installation */
|
||||
- (NSProgress *)installAppAtURL:(NSURL *)fileURL toDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
|
||||
|
||||
/* Connections */
|
||||
- (void)startWiredConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTWiredConnection *_Nullable connection, NSError *_Nullable error))completionHandler;
|
||||
- (void)startNotificationConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTNotificationConnection *_Nullable connection, NSError *_Nullable error))completionHandler;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
//
|
||||
|
||||
#import "ALTDeviceManager.h"
|
||||
#import "NSError+ALTServerError.h"
|
||||
|
||||
#import "AltKit.h"
|
||||
#import "ALTWiredConnection+Private.h"
|
||||
#import "ALTNotificationConnection+Private.h"
|
||||
|
||||
#include <libimobiledevice/libimobiledevice.h>
|
||||
#include <libimobiledevice/lockdown.h>
|
||||
@@ -17,8 +20,10 @@
|
||||
#include <libimobiledevice/misagent.h>
|
||||
|
||||
void ALTDeviceManagerUpdateStatus(plist_t command, plist_t status, void *udid);
|
||||
void ALTDeviceDidChangeConnectionStatus(const idevice_event_t *event, void *user_data);
|
||||
|
||||
NSErrorDomain const ALTDeviceErrorDomain = @"com.rileytestut.ALTDeviceError";
|
||||
NSNotificationName const ALTDeviceManagerDeviceDidConnectNotification = @"ALTDeviceManagerDeviceDidConnectNotification";
|
||||
NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALTDeviceManagerDeviceDidDisconnectNotification";
|
||||
|
||||
@interface ALTDeviceManager ()
|
||||
|
||||
@@ -26,6 +31,8 @@ NSErrorDomain const ALTDeviceErrorDomain = @"com.rileytestut.ALTDeviceError";
|
||||
@property (nonatomic, readonly) NSMutableDictionary<NSUUID *, NSProgress *> *installationProgress;
|
||||
@property (nonatomic, readonly) dispatch_queue_t installationQueue;
|
||||
|
||||
@property (nonatomic, readonly) NSMutableSet<ALTDevice *> *cachedDevices;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ALTDeviceManager
|
||||
@@ -50,11 +57,20 @@ NSErrorDomain const ALTDeviceErrorDomain = @"com.rileytestut.ALTDeviceError";
|
||||
_installationProgress = [NSMutableDictionary dictionary];
|
||||
|
||||
_installationQueue = dispatch_queue_create("com.rileytestut.AltServer.InstallationQueue", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
_cachedDevices = [NSMutableSet set];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)start
|
||||
{
|
||||
idevice_event_subscribe(ALTDeviceDidChangeConnectionStatus, nil);
|
||||
}
|
||||
|
||||
#pragma mark - App Installation -
|
||||
|
||||
- (NSProgress *)installAppAtURL:(NSURL *)fileURL toDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
|
||||
{
|
||||
NSProgress *progress = [NSProgress discreteProgressWithTotalUnitCount:4];
|
||||
@@ -527,6 +543,89 @@ NSErrorDomain const ALTDeviceErrorDomain = @"com.rileytestut.ALTDeviceError";
|
||||
return success;
|
||||
}
|
||||
|
||||
#pragma mark - Connections -
|
||||
|
||||
- (void)startWiredConnectionToDevice:(ALTDevice *)altDevice completionHandler:(void (^)(ALTWiredConnection * _Nullable, NSError * _Nullable))completionHandler
|
||||
{
|
||||
void (^finish)(ALTWiredConnection *connection, NSError *error) = ^(ALTWiredConnection *connection, NSError *error) {
|
||||
if (error != nil)
|
||||
{
|
||||
NSLog(@"Wired Connection Error: %@", error);
|
||||
}
|
||||
|
||||
completionHandler(connection, error);
|
||||
};
|
||||
|
||||
idevice_t device = NULL;
|
||||
idevice_connection_t connection = NULL;
|
||||
|
||||
/* Find Device */
|
||||
if (idevice_new_ignore_network(&device, altDevice.identifier.UTF8String) != IDEVICE_E_SUCCESS)
|
||||
{
|
||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]);
|
||||
}
|
||||
|
||||
/* Connect to Listening Socket */
|
||||
if (idevice_connect(device, ALTDeviceListeningSocket, &connection) != IDEVICE_E_SUCCESS)
|
||||
{
|
||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
|
||||
}
|
||||
|
||||
idevice_free(device);
|
||||
|
||||
ALTWiredConnection *wiredConnection = [[ALTWiredConnection alloc] initWithDevice:altDevice connection:connection];
|
||||
finish(wiredConnection, nil);
|
||||
}
|
||||
|
||||
- (void)startNotificationConnectionToDevice:(ALTDevice *)altDevice completionHandler:(void (^)(ALTNotificationConnection * _Nullable, NSError * _Nullable))completionHandler
|
||||
{
|
||||
void (^finish)(ALTNotificationConnection *, NSError *) = ^(ALTNotificationConnection *connection, NSError *error) {
|
||||
if (error != nil)
|
||||
{
|
||||
NSLog(@"Notification Connection Error: %@", error);
|
||||
}
|
||||
|
||||
completionHandler(connection, error);
|
||||
};
|
||||
|
||||
idevice_t device = NULL;
|
||||
lockdownd_client_t lockdownClient = NULL;
|
||||
lockdownd_service_descriptor_t service = NULL;
|
||||
|
||||
np_client_t client = NULL;
|
||||
|
||||
/* Find Device */
|
||||
if (idevice_new_ignore_network(&device, altDevice.identifier.UTF8String) != IDEVICE_E_SUCCESS)
|
||||
{
|
||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]);
|
||||
}
|
||||
|
||||
/* Connect to Device */
|
||||
if (lockdownd_client_new_with_handshake(device, &lockdownClient, "altserver") != LOCKDOWN_E_SUCCESS)
|
||||
{
|
||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
|
||||
}
|
||||
|
||||
/* Connect to Notification Proxy */
|
||||
if ((lockdownd_start_service(lockdownClient, "com.apple.mobile.notification_proxy", &service) != LOCKDOWN_E_SUCCESS) || service == NULL)
|
||||
{
|
||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
|
||||
}
|
||||
|
||||
/* Connect to Client */
|
||||
if (np_client_new(device, service, &client) != NP_E_SUCCESS)
|
||||
{
|
||||
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]);
|
||||
}
|
||||
|
||||
lockdownd_service_descriptor_free(service);
|
||||
lockdownd_client_free(lockdownClient);
|
||||
idevice_free(device);
|
||||
|
||||
ALTNotificationConnection *notificationConnection = [[ALTNotificationConnection alloc] initWithDevice:altDevice client:client];
|
||||
completionHandler(notificationConnection, nil);
|
||||
}
|
||||
|
||||
#pragma mark - Getters -
|
||||
|
||||
- (NSArray<ALTDevice *> *)connectedDevices
|
||||
@@ -683,3 +782,49 @@ void ALTDeviceManagerUpdateStatus(plist_t command, plist_t status, void *uuid)
|
||||
NSLog(@"Installation Progress: %@", @(percent));
|
||||
}
|
||||
}
|
||||
|
||||
void ALTDeviceDidChangeConnectionStatus(const idevice_event_t *event, void *user_data)
|
||||
{
|
||||
ALTDevice * (^deviceForUDID)(NSString *, NSArray<ALTDevice *> *) = ^ALTDevice *(NSString *udid, NSArray<ALTDevice *> *devices) {
|
||||
for (ALTDevice *device in devices)
|
||||
{
|
||||
if ([device.identifier isEqualToString:udid])
|
||||
{
|
||||
return device;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
};
|
||||
|
||||
switch (event->event)
|
||||
{
|
||||
case IDEVICE_DEVICE_ADD:
|
||||
{
|
||||
ALTDevice *device = deviceForUDID(@(event->udid), ALTDeviceManager.sharedManager.connectedDevices);
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:ALTDeviceManagerDeviceDidConnectNotification object:device];
|
||||
|
||||
if (device)
|
||||
{
|
||||
[ALTDeviceManager.sharedManager.cachedDevices addObject:device];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case IDEVICE_DEVICE_REMOVE:
|
||||
{
|
||||
ALTDevice *device = deviceForUDID(@(event->udid), ALTDeviceManager.sharedManager.cachedDevices.allObjects);
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:ALTDeviceManagerDeviceDidDisconnectNotification object:device];
|
||||
|
||||
if (device)
|
||||
{
|
||||
[ALTDeviceManager.sharedManager.cachedDevices removeObject:device];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user