diff --git a/Calabash Launcher.xcodeproj/project.pbxproj b/Calabash Launcher.xcodeproj/project.pbxproj index 7246ddf..e786261 100644 --- a/Calabash Launcher.xcodeproj/project.pbxproj +++ b/Calabash Launcher.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ D71FD8681F9E6C140091FD30 /* AppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71FD8671F9E6C140091FD30 /* AppHandler.swift */; }; D74D80851F97F7BA0049A84F /* click_image.png in Resources */ = {isa = PBXBuildFile; fileRef = D74D80841F97F7BA0049A84F /* click_image.png */; }; D7CAAEC01F940B76006F8AE8 /* Gemfile.lock in Resources */ = {isa = PBXBuildFile; fileRef = D7CAAEBF1F940B76006F8AE8 /* Gemfile.lock */; }; + D7DDE9AC1FA92DBA0083D00E /* CommandsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DDE9AB1FA92DBA0083D00E /* CommandsController.swift */; }; D7E21B4A1F969FDD0044169A /* Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E21B491F969FDD0044169A /* Localization.swift */; }; D7E21B4C1F96B0470044169A /* RegexHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E21B4B1F96B0470044169A /* RegexHandler.swift */; }; ED06A0E21E850CA70092C96E /* BashOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED06A0E11E850CA70092C96E /* BashOutput.swift */; }; @@ -35,6 +36,7 @@ ED66FA821E0AB8570081A30D /* quit_irb_session.command in Resources */ = {isa = PBXBuildFile; fileRef = ED66FA811E0AB8570081A30D /* quit_irb_session.command */; }; ED7045051DD61B9000272059 /* get_screen.command in Resources */ = {isa = PBXBuildFile; fileRef = ED7045041DD61B9000272059 /* get_screen.command */; }; ED7045071DD6202400272059 /* get_elements_by_offset.command in Resources */ = {isa = PBXBuildFile; fileRef = ED7045061DD6202400272059 /* get_elements_by_offset.command */; }; + ED7278961FA78E050022505E /* app_download.command in Resources */ = {isa = PBXBuildFile; fileRef = ED7278951FA78E050022505E /* app_download.command */; }; ED85D2ED1F94D8A000E5E2D8 /* change_sim_language.command in Resources */ = {isa = PBXBuildFile; fileRef = ED85D2EC1F94D8A000E5E2D8 /* change_sim_language.command */; }; ED85D2F01F950A9400E5E2D8 /* LanguageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED85D2EF1F950A9400E5E2D8 /* LanguageViewController.swift */; }; ED9472941E365F9D00FE2982 /* ElementConstructorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED9472931E365F9C00FE2982 /* ElementConstructorViewController.swift */; }; @@ -77,6 +79,7 @@ D71FD8671F9E6C140091FD30 /* AppHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHandler.swift; sourceTree = ""; }; D74D80841F97F7BA0049A84F /* click_image.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = click_image.png; sourceTree = ""; }; D7CAAEBF1F940B76006F8AE8 /* Gemfile.lock */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Gemfile.lock; sourceTree = ""; }; + D7DDE9AB1FA92DBA0083D00E /* CommandsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandsController.swift; sourceTree = ""; }; D7E21B491F969FDD0044169A /* Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; D7E21B4B1F96B0470044169A /* RegexHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegexHandler.swift; sourceTree = ""; }; ED06A0E11E850CA70092C96E /* BashOutput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BashOutput.swift; sourceTree = ""; }; @@ -98,6 +101,7 @@ ED66FA811E0AB8570081A30D /* quit_irb_session.command */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = quit_irb_session.command; sourceTree = ""; }; ED7045041DD61B9000272059 /* get_screen.command */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = get_screen.command; sourceTree = ""; }; ED7045061DD6202400272059 /* get_elements_by_offset.command */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = get_elements_by_offset.command; sourceTree = ""; }; + ED7278951FA78E050022505E /* app_download.command */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = app_download.command; sourceTree = ""; }; ED85D2EC1F94D8A000E5E2D8 /* change_sim_language.command */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = change_sim_language.command; sourceTree = ""; }; ED85D2EF1F950A9400E5E2D8 /* LanguageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageViewController.swift; sourceTree = ""; }; ED9472931E365F9C00FE2982 /* ElementConstructorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElementConstructorViewController.swift; sourceTree = ""; }; @@ -197,6 +201,7 @@ EDD303941F8F86B00042E27F /* bash */ = { isa = PBXGroup; children = ( + ED7278951FA78E050022505E /* app_download.command */, ED85D2EC1F94D8A000E5E2D8 /* change_sim_language.command */, ED9472951E37BD3E00FE2982 /* get_uniq_elements.command */, ED66FA811E0AB8570081A30D /* quit_irb_session.command */, @@ -268,6 +273,7 @@ isa = PBXGroup; children = ( FD1047071F90AB2100A89D1C /* TagsController.swift */, + D7DDE9AB1FA92DBA0083D00E /* CommandsController.swift */, ); path = Controllers; sourceTree = ""; @@ -354,6 +360,7 @@ ED4C14F01DB2A10E00A1190E /* BuildScript.command in Resources */, EDBC88071DFEE8A6004CB840 /* kill_process.command in Resources */, D74D80851F97F7BA0049A84F /* click_image.png in Resources */, + ED7278961FA78E050022505E /* app_download.command in Resources */, ED7045051DD61B9000272059 /* get_screen.command in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -374,6 +381,7 @@ FD4D0F351F8D086100EAD142 /* ApplicationStateHandler.swift in Sources */, ED9472941E365F9D00FE2982 /* ElementConstructorViewController.swift in Sources */, EDB433361DD49AF5001BABEC /* InspectorViewController.swift in Sources */, + D7DDE9AC1FA92DBA0083D00E /* CommandsController.swift in Sources */, 5D948C941F8F556D0001BC50 /* Language.swift in Sources */, ED85D2F01F950A9400E5E2D8 /* LanguageViewController.swift in Sources */, EDC4C6331F9A1DC000EAE830 /* TextViewPrinter.swift in Sources */, diff --git a/Changelog.md b/Changelog.md index 402c215..ea6f39b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,6 +3,7 @@ ## Added - Added "Reset to default" functionality ([#36](https://github.com/JoeSSS/calabash-launcher/pull/36)) +- Added functionality to download APP from link ([#69](https://github.com/xing/calabash-launcher/pull/69)) - Added possibility to choose path from drop-down menu ([#62](https://github.com/xing/calabash-launcher/pull/62)) - Added additional field in settings that allows to add run variables to the test run ([#66](https://github.com/xing/calabash-launcher/pull/66)) diff --git a/Launcher/Classes/ApplicationStateHandler.swift b/Launcher/Classes/ApplicationStateHandler.swift index 735dab8..8d33e61 100644 --- a/Launcher/Classes/ApplicationStateHandler.swift +++ b/Launcher/Classes/ApplicationStateHandler.swift @@ -8,7 +8,7 @@ class ApplicationStateHandler { case simulatorRadioButtonState = "simRadioButton" case physicalRadioButtonState = "phyRadioButton" case isLaunched = "wasLaunched" - case buildNumber = "buildNumber" + case buildName = "buildName" case filePath = "FilePath" case phoneName = "PhoneName" case phoneUDID = "PhoneUDID" @@ -47,12 +47,12 @@ class ApplicationStateHandler { } } - var buildNumber: Int { + var buildName: String? { get { - return defaults.integer(forKey: .buildNumber) + return defaults.string(forKey: .buildName) } set { - defaults.set(newValue, forKey: .buildNumber) + defaults.set(newValue, forKey: .buildName) } } diff --git a/Launcher/Classes/Constants.swift b/Launcher/Classes/Constants.swift index 3dc0fa1..5195b8a 100644 --- a/Launcher/Classes/Constants.swift +++ b/Launcher/Classes/Constants.swift @@ -7,7 +7,12 @@ enum Constants { static let pluginDevice = "Please plug-in your device.".localized static let installSimulator = "Please install an iOS simulator.".localized static let installSimulatorOrPluginDevice = "Please install a simulator or plug-in your device.".localized - static let notCompatibleWithDeviceType = "not compatible with chosen device type." + static let useLocalBuild = "Skipping download. Use a local app version.".localized + static let notCompatibleWithDeviceType = "not compatible with chosen device type.".localized + } + + enum Keys { + static let linkInfo = "linksInfo" } enum FilePaths { @@ -17,6 +22,7 @@ enum Constants { static let buildScript = main.path(forResource: "BuildScript", ofType: .bash) static let killProcess = main.path(forResource: "kill_process", ofType: .bash) static let flash = main.path(forResource: "flash", ofType: .bash) + static let appDownload = main.path(forResource: "app_download", ofType: .bash) // Interactive Ruby Shell static let createIRBSession = main.path(forResource: "create_irb_session", ofType: .bash) diff --git a/Launcher/Classes/Controllers/CommandsController.swift b/Launcher/Classes/Controllers/CommandsController.swift new file mode 100644 index 0000000..0020ee1 --- /dev/null +++ b/Launcher/Classes/Controllers/CommandsController.swift @@ -0,0 +1,23 @@ +import AppKit +import CommandsCore + +class CommandsController { + let applicationStateHandler = ApplicationStateHandler() + + func downloadApp(from url: URL, textView: NSTextView) { + let textViewPrinter = TextViewPrinter(textView: textView) + guard let launchPath = Constants.FilePaths.Bash.appDownload else { return } + let outputStream = CommandTextOutputStream() + outputStream.textHandler = { text in + DispatchQueue.main.async { + textViewPrinter.printToTextView(text) + } + } + + guard let path = applicationStateHandler.filePath else { return } + let filePath = path.absoluteString.replacingOccurrences(of: "file://", with: "") + DispatchQueue.global(qos: .background).async { + CommandExecutor(launchPath: launchPath,arguments: [url.absoluteString, filePath], outputStream: outputStream).execute() + } + } +} diff --git a/Launcher/Classes/PlistOperations.swift b/Launcher/Classes/PlistOperations.swift index 9dfb988..8cf6351 100644 --- a/Launcher/Classes/PlistOperations.swift +++ b/Launcher/Classes/PlistOperations.swift @@ -1,17 +1,38 @@ import Foundation class PlistOperations { - func createPlist(data : [String : Any]) { - let fileManager = FileManager.default - + + let plistPath: String + let fileManager = FileManager.default + let dictionaryKey: String + + public init(forKey key: String, defaultPlistPath path: String = "/CalabashLauncherSettings.plist") { + dictionaryKey = key let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String - let path = documentDirectory.appending("/CalabashLauncherSettings.plist") - - if !fileManager.fileExists(atPath: path) { - try? fileManager.removeItem(atPath: path) + plistPath = documentDirectory.appending(path) + } + + func create(from dictionary: [String: Any]) { + let someData = NSDictionary(dictionary: dictionary) + someData.write(toFile: plistPath, atomically: true) + } + + private func read() -> [NSDictionary] { + guard + fileManager.fileExists(atPath: plistPath), + let dictionary = NSDictionary(contentsOfFile: plistPath) else { return [] } + + return dictionary.mutableArrayValue(forKey: dictionaryKey).flatMap { element -> NSDictionary? in + guard let dictionary = element as? NSDictionary else { return nil } + return dictionary } - - let someData = NSDictionary(dictionary: data) - someData.write(toFile: path, atomically: true) + } + + func readValues() -> [String] { + return read().flatMap { $0.allValues } as? [String] ?? [] + } + + func readKeys() -> [String] { + return read().flatMap { $0.allKeys } as? [String] ?? [] } } diff --git a/Launcher/Classes/ViewControllers/SettingsViewController.swift b/Launcher/Classes/ViewControllers/SettingsViewController.swift index 7fbf86e..69012ad 100644 --- a/Launcher/Classes/ViewControllers/SettingsViewController.swift +++ b/Launcher/Classes/ViewControllers/SettingsViewController.swift @@ -2,13 +2,12 @@ import AppKit class SettingsViewController: NSViewController { let applicationStateHandler = ApplicationStateHandler() - let plistOperations = PlistOperations() - var linkData: [String: Any] = ["items" : []] + let plistOperations = PlistOperations(forKey: Constants.Keys.linkInfo) var pathChanged = false var hasWarnings = false var singleLinkData: [String: String] = [:] var elements: [(NSTextField, NSTextField)] = [] - + @IBOutlet var fileBrowser: NSPathControl! @IBOutlet weak var cucumberProfileField: NSTextField! @IBOutlet weak var linkField1: NSTextField! @@ -41,6 +40,17 @@ class SettingsViewController: NSViewController { cucumberProfileField.stringValue = cucumberProfile } + let linkArray = plistOperations.readKeys() + let linkDescriptionArray = plistOperations.readValues() + + for (index, element) in linkArray.enumerated() { + elements[index].0.stringValue = String(describing: element) + } + + for (index, element) in linkDescriptionArray.enumerated() { + elements[index].1.stringValue = String(describing: element) + } + if let additionalParameters = applicationStateHandler.additionalRunParameters { additionalRunParameters.stringValue = additionalParameters } @@ -51,10 +61,11 @@ class SettingsViewController: NSViewController { } @IBAction func clickSaveButton(_ sender: Any) { - linkData["items"] = [:] + var linkData: [String: Any] = [Constants.Keys.linkInfo : []] + linkData[Constants.Keys.linkInfo] = [:] hasWarnings = false var warningState = false - var existingItems = linkData["items"] as? [[String: String]] ?? [] + var existingItems = linkData[Constants.Keys.linkInfo] as? [[String: String]] ?? [] for element in elements { (singleLinkData, warningState) = getLinkDataFrom(linkField: element.0, linkDescriptionField: element.1) @@ -69,14 +80,21 @@ class SettingsViewController: NSViewController { if hasWarnings { existingItems = [[:]] } else { - linkData["items"] = existingItems - plistOperations.createPlist(data: linkData) + linkData[Constants.Keys.linkInfo] = existingItems + plistOperations.create(from: linkData) } applicationStateHandler.cucumberProfile = cucumberProfileField.stringValue applicationStateHandler.additionalRunParameters = additionalRunParameters.stringValue - // Restart APP after new path is available. Close Settings and save settings otherwise. + // Reload build picker to get new elements + if + let tabViewController = NSApplication.shared.mainWindow?.contentViewController as? NSTabViewController, + let tasksViewController = tabViewController.childViewControllers.first as? TasksViewController { + tasksViewController.getValuesForBuildPicker() + } + + // Restart app after new path is available. Close Settings and save settings otherwise. if pathChanged { AppHandler().restartApplication() } else if !hasWarnings { diff --git a/Launcher/Classes/ViewControllers/TasksViewController.swift b/Launcher/Classes/ViewControllers/TasksViewController.swift index 2148227..b5f69d7 100644 --- a/Launcher/Classes/ViewControllers/TasksViewController.swift +++ b/Launcher/Classes/ViewControllers/TasksViewController.swift @@ -18,9 +18,11 @@ class TasksViewController: NSViewController { @IBOutlet var cautionBuildImage: NSImageView! @IBOutlet weak var textField: NSTextField! @IBOutlet weak var progressBar: NSProgressIndicator! + @IBOutlet weak var downloadButton: NSButton! let localization = Localization() let deviceCollector = DeviceCollector() + let plistOperations = PlistOperations(forKey: Constants.Keys.linkInfo) var textViewPrinter: TextViewPrinter! var deviceListIsEmpty = false @objc dynamic var isRunning = false @@ -29,6 +31,7 @@ class TasksViewController: NSViewController { var devices = [""] var timer: Timer! var pathToCalabashFolder = "" + var linkInfoArray = [""] var isDeviceListEmpty: Bool { return phoneComboBox.numberOfItems == 0 } @@ -41,9 +44,8 @@ class TasksViewController: NSViewController { placeholderText.setAttributes([.foregroundColor: NSColor.lightGray], range: NSRange(location: 0, length: "Console Input (Beta)".count)) textField.placeholderAttributedString = placeholderText timer = .scheduledTimer(timeInterval: 40, target: self, selector: #selector(self.limitOfChars), userInfo: nil, repeats: true); - + getValuesForBuildPicker() // Disable these elements for the moment, as it cannot work for people outside XING - buildPicker.isEnabled = false getDeviceButton.isEnabled = false physicalDeviceRadioButton.isEnabled = false simulatorRadioButton.state = .on @@ -125,8 +127,18 @@ class TasksViewController: NSViewController { CommandExecutor(launchPath: Constants.FilePaths.Bash.killProcess ?? "", arguments: []).execute() } + @IBAction func clickDownloadButton(_ sender: Any) { + guard let url = URL(string: plistOperations.readKeys()[buildPicker.indexOfSelectedItem]) else { return } + CommandsController().downloadApp(from: url, textView: textView) + } + @IBAction func buildPicker(_ sender: Any) { - // To be developed + applicationStateHandler.buildName = buildPicker.titleOfSelectedItem + if buildPicker.titleOfSelectedItem == Constants.Strings.useLocalBuild { + downloadButton.isEnabled = false + } else { + downloadButton.isEnabled = true + } } @IBAction func clearBufferButton(_ sender: Any) { @@ -311,6 +323,25 @@ class TasksViewController: NSViewController { } } + func getValuesForBuildPicker() { + buildPicker.removeAllItems() + linkInfoArray = plistOperations.readValues() + buildPicker.addItems(withTitles: linkInfoArray) + buildPicker.addItem(withTitle: Constants.Strings.useLocalBuild) + + if let buildName = applicationStateHandler.buildName, buildPicker.itemTitles.contains(buildName) { + buildPicker.selectItem(withTitle: buildName) + } else { + buildPicker.selectItem(withTitle: Constants.Strings.useLocalBuild) + } + + if buildPicker.titleOfSelectedItem == Constants.Strings.useLocalBuild { + downloadButton.isEnabled = false + } else { + downloadButton.isEnabled = true + } + } + func quitIrbSession() { CommandExecutor(launchPath: Constants.FilePaths.Bash.quitIRBSession ?? "", arguments: []).execute() } @@ -318,11 +349,7 @@ class TasksViewController: NSViewController { func statePreservation() { applicationStateHandler.simulatorRadioButtonState = simulatorRadioButton.state.rawValue applicationStateHandler.physicalButtonState = physicalDeviceRadioButton.state.rawValue - applicationStateHandler.buildNumber = buildPicker.indexOfSelectedItem - applicationStateHandler.phoneName = phoneComboBox.titleOfSelectedItem - applicationStateHandler.language = languagePopUpButton.title applicationStateHandler.tag = tagPicker.stringValue - applicationStateHandler.debugState = debugCheckbox.state.rawValue } func runScript() { diff --git a/Launcher/Storyboards/Base.lproj/Main.storyboard b/Launcher/Storyboards/Base.lproj/Main.storyboard index 6422a41..627606d 100644 --- a/Launcher/Storyboards/Base.lproj/Main.storyboard +++ b/Launcher/Storyboards/Base.lproj/Main.storyboard @@ -529,23 +529,28 @@ - + - + - - - - - - - + + @@ -699,6 +704,7 @@ + @@ -1220,7 +1226,7 @@ - + @@ -1229,7 +1235,7 @@ - + @@ -1238,7 +1244,7 @@ - + @@ -1283,7 +1289,7 @@ - + diff --git a/Scripts/bash/BuildScript.command b/Scripts/bash/BuildScript.command index 5e91fd0..3fdb147 100755 --- a/Scripts/bash/BuildScript.command +++ b/Scripts/bash/BuildScript.command @@ -3,7 +3,7 @@ file=${3}/Gemfile if [ ! -e "$file" ]; then echo $file -echo "The path to Calabash folder is incorrect, please choose the right one. File chooser is in the APP Settings" +echo "The path to Calabash folder is incorrect; please choose the right one. File chooser is in the app Settings" exit fi diff --git a/Scripts/bash/app_download.command b/Scripts/bash/app_download.command new file mode 100755 index 0000000..9f19bc3 --- /dev/null +++ b/Scripts/bash/app_download.command @@ -0,0 +1,23 @@ +#!/bin/bash --login +export LANG=en_US.UTF-8 +export LANGUAGE=en_US.UTF-8 +export LC_ALL=en_US.UTF-8 + +filename=${1##*/} +extension=${filename##*.} + +cd ${2} +rm ${filename} +curl -O ${1} +if [ -e "$filename" ]; then + echo "${filename} has been succesfully downloaded" +else + echo "Whoops, looks like your link is incorrect: '${1}'" +fi + +if [ ${extension} == "zip" ]; then + echo "Extracting app from archive" + ditto -xk ${filename} . + rm ${filename} + echo "Extracted Succesfully" +fi diff --git a/Scripts/bash/console.command b/Scripts/bash/console.command index 255c890..ed63a83 100755 --- a/Scripts/bash/console.command +++ b/Scripts/bash/console.command @@ -4,7 +4,7 @@ file=${1}/Gemfile if [ ! -e "$file" ]; then echo $file -echo "The path to Calabash folder is incorrect, please choose the right one. File chooser is in the APP Settings" +echo "The path to Calabash folder is incorrect; please choose the right one. File chooser is in the app Settings" else if open -Ra "iTerm" ; then