[AltDaemon] Synchronizes AppManager operations

Installing and removing apps is now done on a serial dispatch queue, and installing/removing profiles uses file coordination.
This commit is contained in:
Riley Testut
2020-06-05 14:35:05 -07:00
parent 186ad09ab3
commit db87d9ca7b
2 changed files with 135 additions and 83 deletions

View File

@@ -19,63 +19,107 @@ struct AppManager
{
static let shared = AppManager()
private let appQueue = DispatchQueue(label: "com.rileytestut.AltDaemon.appQueue", qos: .userInitiated)
private let profilesQueue = OperationQueue()
private let fileCoordinator = NSFileCoordinator()
private init()
{
self.profilesQueue.name = "com.rileytestut.AltDaemon.profilesQueue"
self.profilesQueue.qualityOfService = .userInitiated
}
func installApp(at fileURL: URL, bundleIdentifier: String, activeProfiles: Set<String>?) throws
func installApp(at fileURL: URL, bundleIdentifier: String, activeProfiles: Set<String>?, completionHandler: @escaping (Result<Void, Error>) -> Void)
{
let lsApplicationWorkspace = unsafeBitCast(NSClassFromString("LSApplicationWorkspace")!, to: LSApplicationWorkspace.Type.self)
let options = ["CFBundleIdentifier": bundleIdentifier, "AllowInstallLocalProvisioned": NSNumber(value: true)] as [String : Any]
try lsApplicationWorkspace.default.installApplication(fileURL, withOptions: options)
}
func removeApp(forBundleIdentifier bundleIdentifier: String)
{
let lsApplicationWorkspace = unsafeBitCast(NSClassFromString("LSApplicationWorkspace")!, to: LSApplicationWorkspace.Type.self)
lsApplicationWorkspace.default.uninstallApplication(bundleIdentifier, withOptions: nil)
}
func install(_ profiles: Set<ALTProvisioningProfile>, activeProfiles: Set<String>?) throws
{
let installingBundleIDs = Set(profiles.map(\.bundleIdentifier))
let profileURLs = try FileManager.default.contentsOfDirectory(at: .profilesDirectoryURL, includingPropertiesForKeys: nil, options: [])
// Remove all inactive profiles (if active profiles are provided), and the previous profiles.
for fileURL in profileURLs
{
guard let profile = ALTProvisioningProfile(url: fileURL) else { continue }
self.appQueue.async {
let lsApplicationWorkspace = unsafeBitCast(NSClassFromString("LSApplicationWorkspace")!, to: LSApplicationWorkspace.Type.self)
if installingBundleIDs.contains(profile.bundleIdentifier) || (activeProfiles?.contains(profile.bundleIdentifier) == false && profile.isFreeProvisioningProfile)
{
try FileManager.default.removeItem(at: fileURL)
}
else
{
print("Ignoring:", profile.bundleIdentifier, profile.uuid)
}
}
for profile in profiles
{
let destinationURL = URL.profilesDirectoryURL.appendingPathComponent(profile.uuid.uuidString.lowercased())
try profile.data.write(to: destinationURL, options: .atomic)
let options = ["CFBundleIdentifier": bundleIdentifier, "AllowInstallLocalProvisioned": NSNumber(value: true)] as [String : Any]
let result = Result { try lsApplicationWorkspace.default.installApplication(fileURL, withOptions: options) }
completionHandler(result)
}
}
func removeProvisioningProfiles(forBundleIdentifiers bundleIdentifiers: Set<String>) throws
func removeApp(forBundleIdentifier bundleIdentifier: String, completionHandler: @escaping (Result<Void, Error>) -> Void)
{
let profileURLs = try FileManager.default.contentsOfDirectory(at: .profilesDirectoryURL, includingPropertiesForKeys: nil, options: [])
for fileURL in profileURLs
{
guard let profile = ALTProvisioningProfile(url: fileURL) else { continue }
self.appQueue.async {
let lsApplicationWorkspace = unsafeBitCast(NSClassFromString("LSApplicationWorkspace")!, to: LSApplicationWorkspace.Type.self)
lsApplicationWorkspace.default.uninstallApplication(bundleIdentifier, withOptions: nil)
if bundleIdentifiers.contains(profile.bundleIdentifier)
completionHandler(.success(()))
}
}
func install(_ profiles: Set<ALTProvisioningProfile>, activeProfiles: Set<String>?, completionHandler: @escaping (Result<Void, Error>) -> Void)
{
let intent = NSFileAccessIntent.writingIntent(with: .profilesDirectoryURL, options: [])
self.fileCoordinator.coordinate(with: [intent], queue: self.profilesQueue) { (error) in
do
{
try FileManager.default.removeItem(at: fileURL)
if let error = error
{
throw error
}
let installingBundleIDs = Set(profiles.map(\.bundleIdentifier))
let profileURLs = try FileManager.default.contentsOfDirectory(at: intent.url, includingPropertiesForKeys: nil, options: [])
// Remove all inactive profiles (if active profiles are provided), and the previous profiles.
for fileURL in profileURLs
{
guard let profile = ALTProvisioningProfile(url: fileURL) else { continue }
if installingBundleIDs.contains(profile.bundleIdentifier) || (activeProfiles?.contains(profile.bundleIdentifier) == false && profile.isFreeProvisioningProfile)
{
try FileManager.default.removeItem(at: fileURL)
}
else
{
print("Ignoring:", profile.bundleIdentifier, profile.uuid)
}
}
for profile in profiles
{
let destinationURL = URL.profilesDirectoryURL.appendingPathComponent(profile.uuid.uuidString.lowercased())
try profile.data.write(to: destinationURL, options: .atomic)
}
completionHandler(.success(()))
}
catch
{
completionHandler(.failure(error))
}
}
}
func removeProvisioningProfiles(forBundleIdentifiers bundleIdentifiers: Set<String>, completionHandler: @escaping (Result<Void, Error>) -> Void)
{
let intent = NSFileAccessIntent.writingIntent(with: .profilesDirectoryURL, options: [])
self.fileCoordinator.coordinate(with: [intent], queue: self.profilesQueue) { (error) in
do
{
let profileURLs = try FileManager.default.contentsOfDirectory(at: intent.url, includingPropertiesForKeys: nil, options: [])
for fileURL in profileURLs
{
guard let profile = ALTProvisioningProfile(url: fileURL) else { continue }
if bundleIdentifiers.contains(profile.bundleIdentifier)
{
try FileManager.default.removeItem(at: fileURL)
}
}
completionHandler(.success(()))
}
catch
{
completionHandler(.failure(error))
}
}
}

View File

@@ -52,12 +52,12 @@ struct RequestHandler: AltKit.RequestHandler
guard case .beginInstallation(let request) = try result.get() else { throw ALTServerError(.unknownRequest) }
guard let bundleIdentifier = request.bundleIdentifier else { throw ALTServerError(.invalidRequest) }
try AppManager.shared.installApp(at: fileURL, bundleIdentifier: bundleIdentifier, activeProfiles: request.activeProfiles)
print("Installed app with result:", result)
let response = InstallationProgressResponse(progress: 1.0)
completionHandler(.success(response))
AppManager.shared.installApp(at: fileURL, bundleIdentifier: bundleIdentifier, activeProfiles: request.activeProfiles) { (result) in
let result = result.map { InstallationProgressResponse(progress: 1.0) }
print("Installed app with result:", result)
completionHandler(result)
}
}
catch
{
@@ -69,48 +69,56 @@ struct RequestHandler: AltKit.RequestHandler
func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: Connection,
completionHandler: @escaping (Result<InstallProvisioningProfilesResponse, Error>) -> Void)
{
do
{
try AppManager.shared.install(request.provisioningProfiles, activeProfiles: request.activeProfiles)
print("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier })
let response = InstallProvisioningProfilesResponse()
completionHandler(.success(response))
}
catch
{
print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", error)
completionHandler(.failure(error))
AppManager.shared.install(request.provisioningProfiles, activeProfiles: request.activeProfiles) { (result) in
switch result
{
case .failure(let error):
print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", error)
completionHandler(.failure(error))
case .success:
print("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier })
let response = InstallProvisioningProfilesResponse()
completionHandler(.success(response))
}
}
}
func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: Connection,
completionHandler: @escaping (Result<RemoveProvisioningProfilesResponse, Error>) -> Void)
{
do
{
try AppManager.shared.removeProvisioningProfiles(forBundleIdentifiers: request.bundleIdentifiers)
print("Removed profiles:", request.bundleIdentifiers)
let response = RemoveProvisioningProfilesResponse()
completionHandler(.success(response))
}
catch
{
print("Failed to remove profiles \(request.bundleIdentifiers):", error)
completionHandler(.failure(error))
AppManager.shared.removeProvisioningProfiles(forBundleIdentifiers: request.bundleIdentifiers) { (result) in
switch result
{
case .failure(let error):
print("Failed to remove profiles \(request.bundleIdentifiers):", error)
completionHandler(.failure(error))
case .success:
print("Removed profiles:", request.bundleIdentifiers)
let response = RemoveProvisioningProfilesResponse()
completionHandler(.success(response))
}
}
}
func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: Connection, completionHandler: @escaping (Result<RemoveAppResponse, Error>) -> Void)
{
AppManager.shared.removeApp(forBundleIdentifier: request.bundleIdentifier)
print("Removed app:", request.bundleIdentifier)
let response = RemoveAppResponse()
completionHandler(.success(response))
AppManager.shared.removeApp(forBundleIdentifier: request.bundleIdentifier) { (result) in
switch result
{
case .failure(let error):
print("Failed to remove app \(request.bundleIdentifier):", error)
completionHandler(.failure(error))
case .success:
print("Removed app:", request.bundleIdentifier)
let response = RemoveAppResponse()
completionHandler(.success(response))
}
}
}
}