mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-17 18:53:40 +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,63 +19,107 @@ 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)
|
||||||
{
|
{
|
||||||
let lsApplicationWorkspace = unsafeBitCast(NSClassFromString("LSApplicationWorkspace")!, to: LSApplicationWorkspace.Type.self)
|
self.appQueue.async {
|
||||||
|
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 }
|
|
||||||
|
|
||||||
if installingBundleIDs.contains(profile.bundleIdentifier) || (activeProfiles?.contains(profile.bundleIdentifier) == false && profile.isFreeProvisioningProfile)
|
let options = ["CFBundleIdentifier": bundleIdentifier, "AllowInstallLocalProvisioned": NSNumber(value: true)] as [String : Any]
|
||||||
{
|
let result = Result { try lsApplicationWorkspace.default.installApplication(fileURL, withOptions: options) }
|
||||||
try FileManager.default.removeItem(at: fileURL)
|
|
||||||
}
|
completionHandler(result)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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: [])
|
self.appQueue.async {
|
||||||
|
let lsApplicationWorkspace = unsafeBitCast(NSClassFromString("LSApplicationWorkspace")!, to: LSApplicationWorkspace.Type.self)
|
||||||
for fileURL in profileURLs
|
lsApplicationWorkspace.default.uninstallApplication(bundleIdentifier, withOptions: nil)
|
||||||
{
|
|
||||||
guard let profile = ALTProvisioningProfile(url: fileURL) else { continue }
|
|
||||||
|
|
||||||
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier })
|
print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", error)
|
||||||
|
completionHandler(.failure(error))
|
||||||
let response = InstallProvisioningProfilesResponse()
|
|
||||||
completionHandler(.success(response))
|
case .success:
|
||||||
}
|
print("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier })
|
||||||
catch
|
|
||||||
{
|
let response = InstallProvisioningProfilesResponse()
|
||||||
print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", error)
|
completionHandler(.success(response))
|
||||||
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("Removed profiles:", request.bundleIdentifiers)
|
print("Failed to remove profiles \(request.bundleIdentifiers):", error)
|
||||||
|
completionHandler(.failure(error))
|
||||||
let response = RemoveProvisioningProfilesResponse()
|
|
||||||
completionHandler(.success(response))
|
case .success:
|
||||||
}
|
print("Removed profiles:", request.bundleIdentifiers)
|
||||||
catch
|
|
||||||
{
|
let response = RemoveProvisioningProfilesResponse()
|
||||||
print("Failed to remove profiles \(request.bundleIdentifiers):", error)
|
completionHandler(.success(response))
|
||||||
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
|
||||||
print("Removed app:", request.bundleIdentifier)
|
{
|
||||||
|
case .failure(let error):
|
||||||
let response = RemoveAppResponse()
|
print("Failed to remove app \(request.bundleIdentifier):", error)
|
||||||
completionHandler(.success(response))
|
completionHandler(.failure(error))
|
||||||
|
|
||||||
|
case .success:
|
||||||
|
print("Removed app:", request.bundleIdentifier)
|
||||||
|
|
||||||
|
let response = RemoveAppResponse()
|
||||||
|
completionHandler(.success(response))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user