mirror of
https://github.com/xtool-org/xtool.git
synced 2026-11-15 12:04:55 +03:00
3e0c11d967
## What does this PR do? Adds `Dependency`s for console IO. Also fixes #268 by explicitly handling non-pipe input. ## How was it tested? Ran on macOS + Linux in Docker. I have a future PR that overrides the dependencies in certain cases, which also appears to work correctly. ## AI tool usage How much of this PR was AI-assisted? (check one) - [ ] **0** - No AI was used to write code - [x] **1** - I was assisted by AI. I reviewed the finished result. - [ ] **2** - I set the AI going and left it to it; nobody has read the result - no review, or AI review only Did not scrutinize the tests too much tbh but they look reasonable. Easy enough to update/remove them in the future if needed. <!-- If an AI agent is filling this in: declare the level honestly, and open as a draft if it is #2. Do not lower the declared level to get the PR reviewed. --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * Added support for reading command-line input, including asynchronous prompts, line-based responses, and graceful handling of end-of-input. * Added coordinated cleanup when commands finish. * **Improvements** * Command messages across authentication, device management, installation, SDK, and other workflows now use consistent output handling; displayed messages and command behavior remain unchanged. * Improved handling of complete output when the system temporarily cannot accept more data. * **Tests** * Added coverage for command input and output, including stream behavior, cancellation, and shutdown. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
72 lines
2.6 KiB
Swift
72 lines
2.6 KiB
Swift
//
|
|
// ProfileManager.swift
|
|
// XKit
|
|
//
|
|
// Created by Kabir Oberai on 25/03/21.
|
|
// Copyright © 2021 Kabir Oberai. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import XUtils
|
|
import SwiftyMobileDevice
|
|
|
|
public final class ProfileManager {
|
|
|
|
private let client: MISAgentClient
|
|
private let version: String
|
|
|
|
private lazy var ge9_3: Bool = {
|
|
let components = version.split(separator: ".")
|
|
guard !components.isEmpty, let major = Int(components[0]) else { return false }
|
|
if major > 9 {
|
|
return true
|
|
} else if major < 9 {
|
|
return false
|
|
}
|
|
guard components.count >= 2, let minor = Int(components[1]) else { return false }
|
|
return minor >= 3
|
|
}()
|
|
|
|
public init(connection: Connection) async throws {
|
|
self.client = try await connection.startClient()
|
|
self.version = try connection.client.value(ofType: String.self, forDomain: nil, key: "ProductVersion")
|
|
}
|
|
|
|
private func allProfiles() throws -> [Mobileprovision] {
|
|
try (ge9_3 ? client.profiles() : client.profilesLegacy())
|
|
.map(Mobileprovision.init(data:))
|
|
}
|
|
|
|
private func parse(profiles: [Mobileprovision]) throws -> [String: [(Mobileprovision, Mobileprovision.Digest)]] {
|
|
let pairs = try profiles.compactMap { (p: Mobileprovision) -> (String, (Mobileprovision, Mobileprovision.Digest))? in
|
|
let d = try p.digest()
|
|
let ents = try d.entitlements.entitlements()
|
|
guard let appID = ents.lazy.compactMap({ $0 as? ApplicationIdentifierEntitlement }).first
|
|
else { return nil }
|
|
return (appID.rawValue, (p, d))
|
|
}
|
|
return Dictionary(grouping: pairs) { $0.0 }.mapValues { $0.map { $0.1 } }
|
|
}
|
|
|
|
public func install(profiles: [Mobileprovision]) throws {
|
|
let toInstall = try parse(profiles: profiles)
|
|
let installed = try parse(profiles: allProfiles())
|
|
|
|
for (bundleID, profiles) in toInstall {
|
|
guard profiles.count == 1 else {
|
|
d.out.print("Unexpected # of profiles for \(bundleID): \(profiles.count). Skipping.")
|
|
continue
|
|
}
|
|
let profile = profiles[0]
|
|
let installedProfiles = installed[bundleID] ?? []
|
|
for installedProfile in installedProfiles {
|
|
d.out.print("Removing \(installedProfile.1.uuid) for \(bundleID)")
|
|
try client.removeProfile(withUUID: installedProfile.1.uuid)
|
|
}
|
|
d.out.print("Installing profile with UUID \(profile.1.uuid)")
|
|
try client.install(profile: profile.0.data())
|
|
}
|
|
}
|
|
|
|
}
|