mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-19 03:33:36 +01:00
[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:
@@ -19,29 +19,53 @@ struct AppManager
|
|||||||
{
|
{
|
||||||
static let shared = 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()
|
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)
|
||||||
{
|
{
|
||||||
|
self.appQueue.async {
|
||||||
let lsApplicationWorkspace = unsafeBitCast(NSClassFromString("LSApplicationWorkspace")!, to: LSApplicationWorkspace.Type.self)
|
let lsApplicationWorkspace = unsafeBitCast(NSClassFromString("LSApplicationWorkspace")!, to: LSApplicationWorkspace.Type.self)
|
||||||
|
|
||||||
let options = ["CFBundleIdentifier": bundleIdentifier, "AllowInstallLocalProvisioned": NSNumber(value: true)] as [String : Any]
|
let options = ["CFBundleIdentifier": bundleIdentifier, "AllowInstallLocalProvisioned": NSNumber(value: true)] as [String : Any]
|
||||||
try lsApplicationWorkspace.default.installApplication(fileURL, withOptions: options)
|
let result = Result { try lsApplicationWorkspace.default.installApplication(fileURL, withOptions: options) }
|
||||||
|
|
||||||
|
completionHandler(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeApp(forBundleIdentifier bundleIdentifier: String)
|
func removeApp(forBundleIdentifier bundleIdentifier: String, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
||||||
{
|
{
|
||||||
|
self.appQueue.async {
|
||||||
let lsApplicationWorkspace = unsafeBitCast(NSClassFromString("LSApplicationWorkspace")!, to: LSApplicationWorkspace.Type.self)
|
let lsApplicationWorkspace = unsafeBitCast(NSClassFromString("LSApplicationWorkspace")!, to: LSApplicationWorkspace.Type.self)
|
||||||
lsApplicationWorkspace.default.uninstallApplication(bundleIdentifier, withOptions: nil)
|
lsApplicationWorkspace.default.uninstallApplication(bundleIdentifier, withOptions: nil)
|
||||||
|
|
||||||
|
completionHandler(.success(()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func install(_ profiles: Set<ALTProvisioningProfile>, activeProfiles: Set<String>?) throws
|
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
|
||||||
|
{
|
||||||
|
if let error = error
|
||||||
|
{
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
let installingBundleIDs = Set(profiles.map(\.bundleIdentifier))
|
let installingBundleIDs = Set(profiles.map(\.bundleIdentifier))
|
||||||
|
|
||||||
let profileURLs = try FileManager.default.contentsOfDirectory(at: .profilesDirectoryURL, includingPropertiesForKeys: nil, options: [])
|
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.
|
// Remove all inactive profiles (if active profiles are provided), and the previous profiles.
|
||||||
for fileURL in profileURLs
|
for fileURL in profileURLs
|
||||||
@@ -63,11 +87,23 @@ struct AppManager
|
|||||||
let destinationURL = URL.profilesDirectoryURL.appendingPathComponent(profile.uuid.uuidString.lowercased())
|
let destinationURL = URL.profilesDirectoryURL.appendingPathComponent(profile.uuid.uuidString.lowercased())
|
||||||
try profile.data.write(to: destinationURL, options: .atomic)
|
try profile.data.write(to: destinationURL, options: .atomic)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
completionHandler(.success(()))
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
completionHandler(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeProvisioningProfiles(forBundleIdentifiers bundleIdentifiers: Set<String>) throws
|
func removeProvisioningProfiles(forBundleIdentifiers bundleIdentifiers: Set<String>, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
||||||
{
|
{
|
||||||
let profileURLs = try FileManager.default.contentsOfDirectory(at: .profilesDirectoryURL, includingPropertiesForKeys: nil, options: [])
|
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
|
for fileURL in profileURLs
|
||||||
{
|
{
|
||||||
@@ -78,5 +114,13 @@ struct AppManager
|
|||||||
try FileManager.default.removeItem(at: fileURL)
|
try FileManager.default.removeItem(at: fileURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
completionHandler(.success(()))
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
completionHandler(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,12 +52,12 @@ struct RequestHandler: AltKit.RequestHandler
|
|||||||
guard case .beginInstallation(let request) = try result.get() else { throw ALTServerError(.unknownRequest) }
|
guard case .beginInstallation(let request) = try result.get() else { throw ALTServerError(.unknownRequest) }
|
||||||
guard let bundleIdentifier = request.bundleIdentifier else { throw ALTServerError(.invalidRequest) }
|
guard let bundleIdentifier = request.bundleIdentifier else { throw ALTServerError(.invalidRequest) }
|
||||||
|
|
||||||
try AppManager.shared.installApp(at: fileURL, bundleIdentifier: bundleIdentifier, activeProfiles: request.activeProfiles)
|
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)
|
print("Installed app with result:", result)
|
||||||
|
|
||||||
let response = InstallationProgressResponse(progress: 1.0)
|
completionHandler(result)
|
||||||
completionHandler(.success(response))
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -69,48 +69,56 @@ struct RequestHandler: AltKit.RequestHandler
|
|||||||
func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: Connection,
|
func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: Connection,
|
||||||
completionHandler: @escaping (Result<InstallProvisioningProfilesResponse, Error>) -> Void)
|
completionHandler: @escaping (Result<InstallProvisioningProfilesResponse, Error>) -> Void)
|
||||||
{
|
{
|
||||||
do
|
AppManager.shared.install(request.provisioningProfiles, activeProfiles: request.activeProfiles) { (result) in
|
||||||
|
switch result
|
||||||
{
|
{
|
||||||
try AppManager.shared.install(request.provisioningProfiles, activeProfiles: request.activeProfiles)
|
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 })
|
print("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier })
|
||||||
|
|
||||||
let response = InstallProvisioningProfilesResponse()
|
let response = InstallProvisioningProfilesResponse()
|
||||||
completionHandler(.success(response))
|
completionHandler(.success(response))
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", error)
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: Connection,
|
func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: Connection,
|
||||||
completionHandler: @escaping (Result<RemoveProvisioningProfilesResponse, Error>) -> Void)
|
completionHandler: @escaping (Result<RemoveProvisioningProfilesResponse, Error>) -> Void)
|
||||||
{
|
{
|
||||||
do
|
AppManager.shared.removeProvisioningProfiles(forBundleIdentifiers: request.bundleIdentifiers) { (result) in
|
||||||
|
switch result
|
||||||
{
|
{
|
||||||
try AppManager.shared.removeProvisioningProfiles(forBundleIdentifiers: request.bundleIdentifiers)
|
case .failure(let error):
|
||||||
|
print("Failed to remove profiles \(request.bundleIdentifiers):", error)
|
||||||
|
completionHandler(.failure(error))
|
||||||
|
|
||||||
|
case .success:
|
||||||
print("Removed profiles:", request.bundleIdentifiers)
|
print("Removed profiles:", request.bundleIdentifiers)
|
||||||
|
|
||||||
let response = RemoveProvisioningProfilesResponse()
|
let response = RemoveProvisioningProfilesResponse()
|
||||||
completionHandler(.success(response))
|
completionHandler(.success(response))
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
print("Failed to remove profiles \(request.bundleIdentifiers):", error)
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: Connection, completionHandler: @escaping (Result<RemoveAppResponse, Error>) -> Void)
|
func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: Connection, completionHandler: @escaping (Result<RemoveAppResponse, Error>) -> Void)
|
||||||
{
|
{
|
||||||
AppManager.shared.removeApp(forBundleIdentifier: request.bundleIdentifier)
|
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)
|
print("Removed app:", request.bundleIdentifier)
|
||||||
|
|
||||||
let response = RemoveAppResponse()
|
let response = RemoveAppResponse()
|
||||||
completionHandler(.success(response))
|
completionHandler(.success(response))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user