Skip to content

Commit 7cda257

Browse files
authored
Add get_app_container command (#18)
1 parent 69ebb1c commit 7cda257

File tree

7 files changed

+139
-2
lines changed

7 files changed

+139
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ The following commands will be available in code in your (test) targets:
4242
- Trigger iCloud Sync
4343
- Open URLs including registered URL schemes
4444
- Erase the contents and settings of the simulator
45+
- Get app container
4546

4647
## ❔ Why would you (not) use this
4748

Sources/Simctl/SimctlClient.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ public class SimctlClient {
129129
public func openUrl(_ url: URL, completion: @escaping DataTaskCallback) {
130130
dataTask(.openURL(env, URLContainer(url: url)), completion)
131131
}
132+
133+
public func getAppContainer(_ container: AppContainer? = nil, completion: @escaping DataTaskCallback) {
134+
dataTask(.getAppContainer(env, container), completion)
135+
}
132136
}
133137

134138
// MARK: - Enviroment {
@@ -264,12 +268,14 @@ extension SimctlClient {
264268
case setStatusBarOverrides(SimctlClientEnvironment, Set<StatusBarOverride>)
265269
case clearStatusBarOverrides(SimctlClientEnvironment)
266270
case openURL(SimctlClientEnvironment, URLContainer)
271+
case getAppContainer(SimctlClientEnvironment, AppContainer?)
267272

268273
@inlinable var httpMethod: HttpMethod {
269274
switch self {
270275
case .sendPushNotification,
271276
.setStatusBarOverrides,
272-
.openURL:
277+
.openURL,
278+
.getAppContainer:
273279
return .post
274280

275281
case .setPrivacy,
@@ -318,6 +324,9 @@ extension SimctlClient {
318324

319325
case .openURL:
320326
return .openURL
327+
328+
case .getAppContainer:
329+
return .getAppContainer
321330
}
322331
}
323332

@@ -338,7 +347,8 @@ extension SimctlClient {
338347
let .triggerICloudSync(env),
339348
let .setStatusBarOverrides(env, _),
340349
let .clearStatusBarOverrides(env),
341-
let .openURL(env, _):
350+
let .openURL(env, _),
351+
let .getAppContainer(env, _):
342352
return setEnv(env)
343353

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

390+
case let .getAppContainer(_, container):
391+
return try? encoder.encode(container)
392+
380393
case .setPrivacy,
381394
.renameDevice,
382395
.terminateApp,

Sources/SimctlCLI/Commands.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,31 @@ extension ShellOutCommand {
137137
static func simctlSetStatusBarOverrides(device: UUID, overrides: Set<StatusBarOverride>) -> ShellOutCommand {
138138
.init(string: simctl("status_bar \(device.uuidString) override \(overrides.map { $0.command }.joined(separator: " "))"))
139139
}
140+
141+
/// Install an xcappdata package to a device, replacing the current contents of the container.
142+
///
143+
/// Usage: simctl install_app_data <device> <path to xcappdata package>
144+
/// This will replace the current contents of the container. If the app is currently running it will be terminated before the container is replaced.
145+
static func simctlInstallAppData(device: UUID, appData: URL) -> ShellOutCommand {
146+
.init(string: simctl("install_app_data \(device.uuidString) \(appData.path)"))
147+
}
148+
149+
/// Print the path of the installed app's container
150+
///
151+
/// Usage: simctl get_app_container <device> <app bundle identifier> [<container>]
152+
///
153+
/// container Optionally specify the container. Defaults to app.
154+
/// app The .app bundle
155+
/// data The application's data container
156+
/// groups The App Group containers
157+
/// <group identifier> A specific App Group container
158+
static func simctlGetAppContainer(device: UUID, appBundleIdentifier: String, container: AppContainer? = nil) -> ShellOutCommand {
159+
if let container = container {
160+
return .init(string: simctl("get_app_container \(device.uuidString) \(appBundleIdentifier) \(container.container)"))
161+
} else {
162+
return .init(string: simctl("get_app_container \(device.uuidString) \(appBundleIdentifier)"))
163+
}
164+
}
140165
}
141166

142167
internal enum ListFilterType: String {

Sources/SimctlCLI/SimctlServer.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,4 +337,35 @@ internal final class SimctlServer {
337337
}
338338
}
339339
}
340+
341+
func onGetAppContainer(_ closure: @escaping (UUID, String, AppContainer) -> Result<String, Swift.Error>) {
342+
server.POST[ServerPath.getAppContainer.rawValue] = { request in
343+
guard let deviceId = request.headerValue(for: .deviceUdid, UUID.init) else {
344+
return .badRequest(.text("Device Udid missing or corrupt."))
345+
}
346+
347+
guard let bundleId = request.headerValue(for: .bundleIdentifier) else {
348+
return .badRequest(.text("Bundle Id missing or corrupt."))
349+
}
350+
351+
let bodyData = Data(request.body)
352+
353+
let appContainer: AppContainer
354+
do {
355+
appContainer = try JSONDecoder().decode(AppContainer.self, from: bodyData)
356+
} catch {
357+
return .badRequest(.text(error.localizedDescription))
358+
}
359+
360+
let result = closure(deviceId, bundleId, appContainer)
361+
362+
switch result {
363+
case let .success(output):
364+
return .ok(.text(output))
365+
366+
case let .failure(error):
367+
return .badRequest(.text(error.localizedDescription))
368+
}
369+
}
370+
}
340371
}

Sources/SimctlCLI/StartServer.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ struct StartServer: ParsableCommand {
6868
runCommand(.simctlOpen(url: url, on: deviceId))
6969
}
7070

71+
server.onGetAppContainer { deviceId, appBundleId, container -> Result<String, Swift.Error> in
72+
runCommand(.simctlGetAppContainer(device: deviceId, appBundleIdentifier: appBundleId, container: container))
73+
}
74+
7175
server.startServer(on: port)
7276
}
7377
}

Sources/SimctlShared/SimctlShared.swift

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ public enum ServerPath: String {
8585
case uninstallApp = "/simctl/uninstallApp"
8686
case statusBarOverrides = "/simctl/statusBarOverrides"
8787
case openURL = "/simctl/openUrl"
88+
case getAppContainer = "/simctl/getAppContainer"
8889
}
8990

9091
/// Some permission changes will terminate the application if running.
@@ -353,3 +354,65 @@ public struct URLContainer: Codable {
353354
self.url = url
354355
}
355356
}
357+
358+
public enum AppContainer: Codable {
359+
case app
360+
case data
361+
case groups
362+
case groupIdentifier(String)
363+
364+
public var container: String {
365+
switch self {
366+
case .app:
367+
return "app"
368+
case .data:
369+
return "data"
370+
case .groups:
371+
return "groups"
372+
case let .groupIdentifier(groupId):
373+
return groupId
374+
}
375+
}
376+
377+
enum Keys: String, CodingKey {
378+
case key
379+
case value
380+
}
381+
382+
public init(from decoder: Decoder) throws {
383+
let container = try decoder.container(keyedBy: Keys.self)
384+
let key = try container.decode(String.self, forKey: .key)
385+
switch key {
386+
case "app":
387+
self = .app
388+
case "data":
389+
self = .data
390+
case "groups":
391+
self = .groups
392+
case "groupID":
393+
let groupID = try container.decode(String.self, forKey: .value)
394+
self = .groupIdentifier(groupID)
395+
396+
default:
397+
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Unexpected key \(key)", underlyingError: nil))
398+
}
399+
}
400+
401+
public func encode(to encoder: Encoder) throws {
402+
var container = encoder.container(keyedBy: Keys.self)
403+
switch self {
404+
case .app:
405+
try container.encode("app", forKey: .key)
406+
407+
case .data:
408+
try container.encode("data", forKey: .key)
409+
410+
case .groups:
411+
try container.encode("groups", forKey: .key)
412+
413+
case .groupIdentifier(let groupID):
414+
try container.encode("groupID", forKey: .key)
415+
try container.encode(groupID, forKey: .value)
416+
}
417+
}
418+
}

bin/SimctlCLI

145 KB
Binary file not shown.

0 commit comments

Comments
 (0)