Skip to content

Commit

Permalink
feat: move custom commands to separate class
Browse files Browse the repository at this point in the history
  • Loading branch information
okwasniewski committed Sep 8, 2024
1 parent 0121c91 commit a318096
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 80 deletions.
12 changes: 12 additions & 0 deletions MiniSim.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@
76BF0AD92C8CB3E6003BE568 /* AcknowList in Frameworks */ = {isa = PBXBuildFile; productRef = 76BF0AD82C8CB3E6003BE568 /* AcknowList */; };
76BF0ADB2C8CB4CD003BE568 /* Package.resolved in Resources */ = {isa = PBXBuildFile; fileRef = 76BF0ADA2C8CB4CD003BE568 /* Package.resolved */; };
76BF0ADD2C8DF660003BE568 /* AccessibilityElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76BF0ADC2C8DF660003BE568 /* AccessibilityElementTests.swift */; };
76BF0ADF2C8E01B3003BE568 /* CustomCommandService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76BF0ADE2C8E01B3003BE568 /* CustomCommandService.swift */; };
76BF0AE12C8E027D003BE568 /* DeviceConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76BF0AE02C8E027D003BE568 /* DeviceConstants.swift */; };
76BF0AE32C8E041C003BE568 /* CustomCommandServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76BF0AE22C8E041C003BE568 /* CustomCommandServiceTests.swift */; };
76C1396A2C849A3F006CD80C /* MenuIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76C139692C849A3F006CD80C /* MenuIcons.swift */; };
76E4451229D4391000039025 /* Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76E4451129D4391000039025 /* Onboarding.swift */; };
76E4451429D4403F00039025 /* NSNotificationName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76E4451329D4403F00039025 /* NSNotificationName.swift */; };
Expand Down Expand Up @@ -178,6 +181,9 @@
76B70F832B0D5AB4009D87A4 /* Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = "<group>"; };
76BF0ADA2C8CB4CD003BE568 /* Package.resolved */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Package.resolved; path = MiniSim.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved; sourceTree = SOURCE_ROOT; };
76BF0ADC2C8DF660003BE568 /* AccessibilityElementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityElementTests.swift; sourceTree = "<group>"; };
76BF0ADE2C8E01B3003BE568 /* CustomCommandService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCommandService.swift; sourceTree = "<group>"; };
76BF0AE02C8E027D003BE568 /* DeviceConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceConstants.swift; sourceTree = "<group>"; };
76BF0AE22C8E041C003BE568 /* CustomCommandServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCommandServiceTests.swift; sourceTree = "<group>"; };
76C139692C849A3F006CD80C /* MenuIcons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuIcons.swift; sourceTree = "<group>"; };
76E4451129D4391000039025 /* Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Onboarding.swift; sourceTree = "<group>"; };
76E4451329D4403F00039025 /* NSNotificationName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSNotificationName.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -314,6 +320,8 @@
7699511C2C845B1900462287 /* DeviceParser.swift */,
76F04A10298A5AE000BF9CA3 /* ADB.swift */,
76B70F832B0D5AB4009D87A4 /* Shell.swift */,
76BF0ADE2C8E01B3003BE568 /* CustomCommandService.swift */,
76BF0AE02C8E027D003BE568 /* DeviceConstants.swift */,
);
path = Service;
sourceTree = "<group>";
Expand Down Expand Up @@ -418,6 +426,7 @@
7699511E2C845CBA00462287 /* DeviceParserTests.swift */,
76B70F812B0D50FE009D87A4 /* ADBTests.swift */,
76BF0ADC2C8DF660003BE568 /* AccessibilityElementTests.swift */,
76BF0AE22C8E041C003BE568 /* CustomCommandServiceTests.swift */,
);
path = MiniSimTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -612,7 +621,9 @@
76F269852A2A376A00424BDA /* CustomCommandError.swift in Sources */,
767799A029C30BF5009030F8 /* BlurredView.swift in Sources */,
7645D4C42982CB2B00019227 /* MiniSim.swift in Sources */,
76BF0ADF2C8E01B3003BE568 /* CustomCommandService.swift in Sources */,
76F2A912299033EA002D4EF6 /* DeviceError.swift in Sources */,
76BF0AE12C8E027D003BE568 /* DeviceConstants.swift in Sources */,
7631218E2A12B3BA00EE7F48 /* CustomCommandsViewModel.swift in Sources */,
4AFACC782AD74E9000EC369F /* DeviceListSection.swift in Sources */,
760554A32C085BEA001607FE /* Thread+Asserts.swift in Sources */,
Expand Down Expand Up @@ -678,6 +689,7 @@
files = (
7699511F2C845CBA00462287 /* DeviceParserTests.swift in Sources */,
76BF0ADD2C8DF660003BE568 /* AccessibilityElementTests.swift in Sources */,
76BF0AE32C8E041C003BE568 /* CustomCommandServiceTests.swift in Sources */,
76B70F7E2B0D361A009D87A4 /* UserDefaultsTests.swift in Sources */,
760DEACE2B0DFB6600253576 /* ShellStub.swift in Sources */,
76B70F822B0D50FE009D87A4 /* ADBTests.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion MiniSim/AppleScript Commands/GetCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class GetCommands: NSScriptCommand {
let commands = SubMenuItems.items(platform: platform, deviceType: deviceType)
.compactMap { $0 as? SubMenuActionItem }
.map { $0.commandItem }
let customCommands = DeviceService.getCustomCommands(platform: platform)
let customCommands = CustomCommandService.getCustomCommands(platform: platform)
.map { command in
Command(
id: command.id,
Expand Down
2 changes: 1 addition & 1 deletion MiniSim/Menu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ class Menu: NSMenu {
}

func createCustomCommandsMenu(for platform: Platform, isDeviceBooted: Bool, callback: Selector) -> [NSMenuItem] {
DeviceService.getCustomCommands(platform: platform)
CustomCommandService.getCustomCommands(platform: platform)
.filter { item in
if item.needBootedDevice && !isDeviceBooted {
return false
Expand Down
1 change: 1 addition & 0 deletions MiniSim/Service/Adb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ protocol ADBProtocol {

static func getAdbPath() throws -> String
static func getEmulatorPath() throws -> String
static func getAndroidHome() throws -> String
static func getAdbId(for deviceName: String) throws -> String
static func checkAndroidHome(
path: String,
Expand Down
53 changes: 53 additions & 0 deletions MiniSim/Service/CustomCommandService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Foundation

class CustomCommandService {

Check warning on line 3 in MiniSim/Service/CustomCommandService.swift

View workflow job for this annotation

GitHub Actions / lint

Convenience Type Violation: Types used for hosting only static members should be implemented as a caseless enum to avoid instantiation (convenience_type)
static var shell: ShellProtocol = Shell()
static var adb: ADBProtocol.Type = ADB.self

static func getCustomCommands(platform: Platform, userDefaults: UserDefaults = UserDefaults.standard) -> [Command] {
guard let commandsData = userDefaults.commands else { return [] }
guard let commands = try? JSONDecoder().decode([Command].self, from: commandsData) else {
return []
}

return commands.filter { $0.platform == platform }
}

static func getCustomCommand(
platform: Platform,
commandName: String,
userDefaults: UserDefaults = UserDefaults.standard
) -> Command? {
let commands = getCustomCommands(platform: platform, userDefaults: userDefaults)
return commands.first { $0.name == commandName }
}

static func runCustomCommand(_ device: Device, command: Command) throws {
// Thread.assertBackgroundThread()
var commandToExecute = command.command
.replacingOccurrences(of: Variables.deviceName.rawValue, with: device.name)

let deviceID = device.identifier ?? ""

if command.platform == .android {
commandToExecute = try commandToExecute
.replacingOccurrences(of: Variables.adbPath.rawValue, with: adb.getAdbPath())
.replacingOccurrences(of: Variables.adbId.rawValue, with: deviceID)
.replacingOccurrences(of: Variables.androidHomePath.rawValue, with: adb.getAndroidHome())
} else {
commandToExecute = commandToExecute
.replacingOccurrences(of: Variables.uuid.rawValue, with: deviceID)
.replacingOccurrences(of: Variables.xcrunPath.rawValue, with: DeviceConstants.ProcessPaths.xcrun.rawValue)
}

do {
try shell.execute(command: commandToExecute)
if command.bootsDevice ?? false && command.platform == .ios {
try? DeviceService.launchSimulatorApp(uuid: deviceID)
}
NotificationCenter.default.post(name: .commandDidSucceed, object: nil)
} catch {
throw CustomCommandError.commandError(errorMessage: error.localizedDescription)
}
}
}
16 changes: 16 additions & 0 deletions MiniSim/Service/DeviceConstants.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation

enum DeviceConstants {
static let deviceBootedError = "Unable to boot device in current state: Booted"
static let derivedDataLocation = "~/Library/Developer/Xcode/DerivedData"

enum ProcessPaths: String {
case xcrun = "/usr/bin/xcrun"
case xcodeSelect = "/usr/bin/xcode-select"
}

enum BundleURL: String {
case emulator = "qemu-system-aarch64"
case simulator = "Simulator.app"
}
}
92 changes: 17 additions & 75 deletions MiniSim/Service/DeviceService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ protocol DeviceServiceProtocol {
static func checkAndroidSetup() throws -> String

static func focusDevice(_ device: Device)
static func runCustomCommand(_ device: Device, command: Command) throws
static func getCustomCommands(platform: Platform) -> [Command]
static func getCustomCommand(platform: Platform, commandName: String) -> Command?
static func showSuccessMessage(title: String, message: String)
}

Expand All @@ -33,61 +30,6 @@ class DeviceService: DeviceServiceProtocol {
qos: .userInteractive,
attributes: .concurrent
)
private static let deviceBootedError = "Unable to boot device in current state: Booted"
private static let derivedDataLocation = "~/Library/Developer/Xcode/DerivedData"

private enum ProcessPaths: String {
case xcrun = "/usr/bin/xcrun"
case xcodeSelect = "/usr/bin/xcode-select"
}

private enum BundleURL: String {
case emulator = "qemu-system-aarch64"
case simulator = "Simulator.app"
}

static func getCustomCommands(platform: Platform) -> [Command] {
guard let commandsData = UserDefaults.standard.commands else { return [] }
guard let commands = try? JSONDecoder().decode([Command].self, from: commandsData) else {
return []
}

return commands.filter { $0.platform == platform }
}

static func getCustomCommand(platform: Platform, commandName: String) -> Command? {
let commands = getCustomCommands(platform: platform)
return commands.first { $0.name == commandName }
}

static func runCustomCommand(_ device: Device, command: Command) throws {
Thread.assertBackgroundThread()
var commandToExecute = command.command
.replacingOccurrences(of: Variables.deviceName.rawValue, with: device.name)

let deviceID = device.identifier ?? ""

if command.platform == .android {
commandToExecute = try commandToExecute
.replacingOccurrences(of: Variables.adbPath.rawValue, with: ADB.getAdbPath())
.replacingOccurrences(of: Variables.adbId.rawValue, with: deviceID)
.replacingOccurrences(of: Variables.androidHomePath.rawValue, with: ADB.getAndroidHome())
} else {
commandToExecute = commandToExecute
.replacingOccurrences(of: Variables.uuid.rawValue, with: deviceID)
.replacingOccurrences(of: Variables.xcrunPath.rawValue, with: ProcessPaths.xcrun.rawValue)
}

do {
try shellOut(to: commandToExecute)
if command.bootsDevice ?? false && command.platform == .ios {
try? launchSimulatorApp(uuid: deviceID)
}
NotificationCenter.default.post(name: .commandDidSucceed, object: nil)
} catch {
throw CustomCommandError.commandError(errorMessage: error.localizedDescription)
}
}

static func focusDevice(_ device: Device) {
queue.async {
Expand All @@ -100,11 +42,11 @@ class DeviceService: DeviceServiceProtocol {
for app in runningApps {
guard
let bundleURL = app.bundleURL?.absoluteString,
bundleURL.contains(BundleURL.simulator.rawValue) ||
bundleURL.contains(BundleURL.emulator.rawValue) else {
bundleURL.contains(DeviceConstants.BundleURL.simulator.rawValue) ||
bundleURL.contains(DeviceConstants.BundleURL.emulator.rawValue) else {
continue
}
let isAndroid = bundleURL.contains(BundleURL.emulator.rawValue)
let isAndroid = bundleURL.contains(DeviceConstants.BundleURL.emulator.rawValue)

for window in AccessibilityElement.allWindowsForPID(app.processIdentifier) {
guard let windowTitle = window.attribute(key: .title, type: String.self),
Expand Down Expand Up @@ -139,7 +81,7 @@ class DeviceService: DeviceServiceProtocol {
}

static func checkXcodeSetup() -> Bool {
FileManager.default.fileExists(atPath: ProcessPaths.xcrun.rawValue)
FileManager.default.fileExists(atPath: DeviceConstants.ProcessPaths.xcrun.rawValue)
}

static func checkAndroidSetup() throws -> String {
Expand Down Expand Up @@ -200,7 +142,7 @@ class DeviceService: DeviceServiceProtocol {
completion(nil)
}
} catch {
if error.localizedDescription.contains(deviceBootedError) {
if error.localizedDescription.contains(DeviceConstants.deviceBootedError) {
return
}
completionQueue.async {
Expand All @@ -219,9 +161,9 @@ extension DeviceService {
) {
self.queue.async {
do {
let amountCleared = try? shellOut(to: "du -sh \(derivedDataLocation)")
let amountCleared = try? shellOut(to: "du -sh \(DeviceConstants.derivedDataLocation)")
.match(###"\d+\.?\d+\w+"###).first?.first
try shellOut(to: "rm -rf \(derivedDataLocation)")
try shellOut(to: "rm -rf \(DeviceConstants.derivedDataLocation)")
completionQueue.async {
completion(amountCleared ?? "", nil)
}
Expand All @@ -245,7 +187,7 @@ extension DeviceService {
let outputFile = tempDirectory.appendingPathComponent("iosPhysicalDevices.json")

guard (try? shellOut(
to: ProcessPaths.xcrun.rawValue,
to: DeviceConstants.ProcessPaths.xcrun.rawValue,
arguments: ["devicectl", "list", "devices", "-j \(outputFile.path)"]
)) != nil else {
return []
Expand All @@ -257,7 +199,7 @@ extension DeviceService {

static func getIOSSimulators() throws -> [Device] {
let output = try shellOut(
to: ProcessPaths.xcrun.rawValue,
to: DeviceConstants.ProcessPaths.xcrun.rawValue,
arguments: ["simctl", "list", "devices", "available"]
)
return DeviceParserFactory().getParser(.iosSimulator).parse(output)
Expand All @@ -269,7 +211,7 @@ extension DeviceService {

if !isSimulatorRunning {
guard let activeDeveloperDir = try? shellOut(
to: ProcessPaths.xcodeSelect.rawValue,
to: DeviceConstants.ProcessPaths.xcodeSelect.rawValue,
arguments: ["-p"]
)
.trimmingCharacters(in: .whitespacesAndNewlines) else {
Expand All @@ -285,17 +227,17 @@ extension DeviceService {
private static func launchDevice(uuid: String) throws {
do {
try self.launchSimulatorApp(uuid: uuid)
try shellOut(to: ProcessPaths.xcrun.rawValue, arguments: ["simctl", "boot", uuid])
try shellOut(to: DeviceConstants.ProcessPaths.xcrun.rawValue, arguments: ["simctl", "boot", uuid])
} catch {
if !error.localizedDescription.contains(deviceBootedError) {
if !error.localizedDescription.contains(DeviceConstants.deviceBootedError) {
throw error
}
}
}

static func deleteSimulator(uuid: String) throws {
Thread.assertBackgroundThread()
try shellOut(to: ProcessPaths.xcrun.rawValue, arguments: ["simctl", "delete", uuid])
try shellOut(to: DeviceConstants.ProcessPaths.xcrun.rawValue, arguments: ["simctl", "delete", uuid])
}

static func handleiOSAction(device: Device, commandTag: SubMenuItems.Tags, itemName: String) {

Check warning on line 243 in MiniSim/Service/DeviceService.swift

View workflow job for this annotation

GitHub Actions / lint

Cyclomatic Complexity Violation: Function should have complexity 10 or less; currently complexity is 11 (cyclomatic_complexity)
Expand Down Expand Up @@ -329,12 +271,12 @@ extension DeviceService {
}
}
case .customCommand:
guard let command = DeviceService.getCustomCommand(platform: .ios, commandName: itemName) else {
guard let command = CustomCommandService.getCustomCommand(platform: .ios, commandName: itemName) else {
return
}

do {
try DeviceService.runCustomCommand(device, command: command)
try CustomCommandService.runCustomCommand(device, command: command)
} catch {
NSAlert.showError(message: error.localizedDescription)
}
Expand Down Expand Up @@ -479,8 +421,8 @@ extension DeviceService {
try DeviceService.sendText(device: device, text: text)

case .customCommand:
if let command = DeviceService.getCustomCommand(platform: .android, commandName: itemName) {
try DeviceService.runCustomCommand(device, command: command)
if let command = CustomCommandService.getCustomCommand(platform: .android, commandName: itemName) {
try CustomCommandService.runCustomCommand(device, command: command)
}
case .logcat:
try DeviceService.launchLogCat(device: device)
Expand Down
Loading

0 comments on commit a318096

Please sign in to comment.