Skip to content

Commit

Permalink
Add get_app_container command (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
ctreffs authored Feb 15, 2022
1 parent 69ebb1c commit 7cda257
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ The following commands will be available in code in your (test) targets:
- Trigger iCloud Sync
- Open URLs including registered URL schemes
- Erase the contents and settings of the simulator
- Get app container

## ❔ Why would you (not) use this

Expand Down
17 changes: 15 additions & 2 deletions Sources/Simctl/SimctlClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ public class SimctlClient {
public func openUrl(_ url: URL, completion: @escaping DataTaskCallback) {
dataTask(.openURL(env, URLContainer(url: url)), completion)
}

public func getAppContainer(_ container: AppContainer? = nil, completion: @escaping DataTaskCallback) {
dataTask(.getAppContainer(env, container), completion)
}
}

// MARK: - Enviroment {
Expand Down Expand Up @@ -264,12 +268,14 @@ extension SimctlClient {
case setStatusBarOverrides(SimctlClientEnvironment, Set<StatusBarOverride>)
case clearStatusBarOverrides(SimctlClientEnvironment)
case openURL(SimctlClientEnvironment, URLContainer)
case getAppContainer(SimctlClientEnvironment, AppContainer?)

@inlinable var httpMethod: HttpMethod {
switch self {
case .sendPushNotification,
.setStatusBarOverrides,
.openURL:
.openURL,
.getAppContainer:
return .post

case .setPrivacy,
Expand Down Expand Up @@ -318,6 +324,9 @@ extension SimctlClient {

case .openURL:
return .openURL

case .getAppContainer:
return .getAppContainer
}
}

Expand All @@ -338,7 +347,8 @@ extension SimctlClient {
let .triggerICloudSync(env),
let .setStatusBarOverrides(env, _),
let .clearStatusBarOverrides(env),
let .openURL(env, _):
let .openURL(env, _),
let .getAppContainer(env, _):
return setEnv(env)

case let .setPrivacy(env, action, service):
Expand Down Expand Up @@ -377,6 +387,9 @@ extension SimctlClient {
case let .openURL(_, urlContainer):
return try? encoder.encode(urlContainer)

case let .getAppContainer(_, container):
return try? encoder.encode(container)

case .setPrivacy,
.renameDevice,
.terminateApp,
Expand Down
25 changes: 25 additions & 0 deletions Sources/SimctlCLI/Commands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,31 @@ extension ShellOutCommand {
static func simctlSetStatusBarOverrides(device: UUID, overrides: Set<StatusBarOverride>) -> ShellOutCommand {
.init(string: simctl("status_bar \(device.uuidString) override \(overrides.map { $0.command }.joined(separator: " "))"))
}

/// Install an xcappdata package to a device, replacing the current contents of the container.
///
/// Usage: simctl install_app_data <device> <path to xcappdata package>
/// This will replace the current contents of the container. If the app is currently running it will be terminated before the container is replaced.
static func simctlInstallAppData(device: UUID, appData: URL) -> ShellOutCommand {
.init(string: simctl("install_app_data \(device.uuidString) \(appData.path)"))
}

/// Print the path of the installed app's container
///
/// Usage: simctl get_app_container <device> <app bundle identifier> [<container>]
///
/// container Optionally specify the container. Defaults to app.
/// app The .app bundle
/// data The application's data container
/// groups The App Group containers
/// <group identifier> A specific App Group container
static func simctlGetAppContainer(device: UUID, appBundleIdentifier: String, container: AppContainer? = nil) -> ShellOutCommand {
if let container = container {
return .init(string: simctl("get_app_container \(device.uuidString) \(appBundleIdentifier) \(container.container)"))
} else {
return .init(string: simctl("get_app_container \(device.uuidString) \(appBundleIdentifier)"))
}
}
}

internal enum ListFilterType: String {
Expand Down
31 changes: 31 additions & 0 deletions Sources/SimctlCLI/SimctlServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -337,4 +337,35 @@ internal final class SimctlServer {
}
}
}

func onGetAppContainer(_ closure: @escaping (UUID, String, AppContainer) -> Result<String, Swift.Error>) {
server.POST[ServerPath.getAppContainer.rawValue] = { request in
guard let deviceId = request.headerValue(for: .deviceUdid, UUID.init) else {
return .badRequest(.text("Device Udid missing or corrupt."))
}

guard let bundleId = request.headerValue(for: .bundleIdentifier) else {
return .badRequest(.text("Bundle Id missing or corrupt."))
}

let bodyData = Data(request.body)

let appContainer: AppContainer
do {
appContainer = try JSONDecoder().decode(AppContainer.self, from: bodyData)
} catch {
return .badRequest(.text(error.localizedDescription))
}

let result = closure(deviceId, bundleId, appContainer)

switch result {
case let .success(output):
return .ok(.text(output))

case let .failure(error):
return .badRequest(.text(error.localizedDescription))
}
}
}
}
4 changes: 4 additions & 0 deletions Sources/SimctlCLI/StartServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ struct StartServer: ParsableCommand {
runCommand(.simctlOpen(url: url, on: deviceId))
}

server.onGetAppContainer { deviceId, appBundleId, container -> Result<String, Swift.Error> in
runCommand(.simctlGetAppContainer(device: deviceId, appBundleIdentifier: appBundleId, container: container))
}

server.startServer(on: port)
}
}
Expand Down
63 changes: 63 additions & 0 deletions Sources/SimctlShared/SimctlShared.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public enum ServerPath: String {
case uninstallApp = "/simctl/uninstallApp"
case statusBarOverrides = "/simctl/statusBarOverrides"
case openURL = "/simctl/openUrl"
case getAppContainer = "/simctl/getAppContainer"
}

/// Some permission changes will terminate the application if running.
Expand Down Expand Up @@ -353,3 +354,65 @@ public struct URLContainer: Codable {
self.url = url
}
}

public enum AppContainer: Codable {
case app
case data
case groups
case groupIdentifier(String)

public var container: String {
switch self {
case .app:
return "app"
case .data:
return "data"
case .groups:
return "groups"
case let .groupIdentifier(groupId):
return groupId
}
}

enum Keys: String, CodingKey {
case key
case value
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
let key = try container.decode(String.self, forKey: .key)
switch key {
case "app":
self = .app
case "data":
self = .data
case "groups":
self = .groups
case "groupID":
let groupID = try container.decode(String.self, forKey: .value)
self = .groupIdentifier(groupID)

default:
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Unexpected key \(key)", underlyingError: nil))
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: Keys.self)
switch self {
case .app:
try container.encode("app", forKey: .key)

case .data:
try container.encode("data", forKey: .key)

case .groups:
try container.encode("groups", forKey: .key)

case .groupIdentifier(let groupID):
try container.encode("groupID", forKey: .key)
try container.encode(groupID, forKey: .value)
}
}
}
Binary file modified bin/SimctlCLI
Binary file not shown.

0 comments on commit 7cda257

Please sign in to comment.