// // Runner.swift // FastlaneSwiftRunner // // Created by Joshua Liebowitz on 8/26/17. // // // ** NOTE ** // This file is provided by fastlane and WILL be overwritten in future updates // If you want to add extra functionality to this project, create a new file in a // new group so that it won't be marked for upgrade // import Foundation let logger: Logger = { return Logger() }() let runner: Runner = { return Runner() }() func desc(_ laneDescription: String) { // no-op, this is handled in fastlane/lane_list.rb } class Runner { fileprivate var thread: Thread! fileprivate var socketClient: SocketClient! fileprivate let dispatchGroup: DispatchGroup = DispatchGroup() fileprivate var returnValue: String? // lol, so safe fileprivate var currentlyExecutingCommand: RubyCommandable? = nil fileprivate var shouldLeaveDispatchGroupDuringDisconnect = false func executeCommand(_ command: RubyCommandable) -> String { self.dispatchGroup.enter() currentlyExecutingCommand = command socketClient.send(rubyCommand: command) let secondsToWait = DispatchTimeInterval.seconds(SocketClient.defaultCommandTimeoutSeconds) let connectTimeout = DispatchTime.now() + secondsToWait let timeoutResult = self.dispatchGroup.wait(timeout: connectTimeout) let failureMessage = "command didn't execute in: \(SocketClient.defaultCommandTimeoutSeconds) seconds" let success = testDispatchTimeoutResult(timeoutResult, failureMessage: failureMessage, timeToWait: secondsToWait) guard success else { log(message: "command timeout") fatalError() } if let returnValue = self.returnValue { return returnValue } else { return "" } } } // Handle threading stuff extension Runner { func startSocketThread(port: UInt32) { let secondsToWait = DispatchTimeInterval.seconds(SocketClient.connectTimeoutSeconds) self.dispatchGroup.enter() self.socketClient = SocketClient(port: port, commandTimeoutSeconds:timeout, socketDelegate: self) self.thread = Thread(target: self, selector: #selector(startSocketComs), object: nil) self.thread!.name = "socket thread" self.thread!.start() let connectTimeout = DispatchTime.now() + secondsToWait let timeoutResult = self.dispatchGroup.wait(timeout: connectTimeout) let failureMessage = "couldn't start socket thread in: \(SocketClient.connectTimeoutSeconds) seconds" let success = testDispatchTimeoutResult(timeoutResult, failureMessage: failureMessage, timeToWait: secondsToWait) guard success else { log(message: "socket thread timeout") fatalError() } } func disconnectFromFastlaneProcess() { self.shouldLeaveDispatchGroupDuringDisconnect = true self.dispatchGroup.enter() socketClient.sendComplete() let connectTimeout = DispatchTime.now() + 2 _ = self.dispatchGroup.wait(timeout: connectTimeout) } @objc func startSocketComs() { guard let socketClient = self.socketClient else { return } socketClient.connectAndOpenStreams() self.dispatchGroup.leave() } fileprivate func testDispatchTimeoutResult(_ timeoutResult: DispatchTimeoutResult, failureMessage: String, timeToWait: DispatchTimeInterval) -> Bool { switch timeoutResult { case .success: return true case .timedOut: log(message: "timeout: \(failureMessage)") return false } } } extension Runner : SocketClientDelegateProtocol { func commandExecuted(serverResponse: SocketClientResponse) { switch serverResponse { case .success(let returnedObject, let closureArgumentValue): verbose(message: "command executed") self.returnValue = returnedObject if let command = self.currentlyExecutingCommand as? RubyCommand { if let closureArgumentValue = closureArgumentValue { command.performCallback(callbackArg: closureArgumentValue) } } self.dispatchGroup.leave() case .clientInitiatedCancelAcknowledged: verbose(message: "server acknowledged a cancel request") self.dispatchGroup.leave() case .alreadyClosedSockets, .connectionFailure, .malformedRequest, .malformedResponse, .serverError: log(message: "error encountered while executing command:\n\(serverResponse)") self.dispatchGroup.leave() case .commandTimeout(let timeout): log(message: "Runner timed out after \(timeout) second(s)") } } func connectionsOpened() { DispatchQueue.main.async { verbose(message: "connected!") } } func connectionsClosed() { DispatchQueue.main.async { self.thread?.cancel() self.thread = nil self.socketClient = nil verbose(message: "connection closed!") if self.shouldLeaveDispatchGroupDuringDisconnect { self.dispatchGroup.leave() } exit(0) } } } class Logger { enum LogMode { init(logMode: String) { switch logMode { case "normal", "default": self = .normal case "verbose": self = .verbose default: logger.log(message: "unrecognized log mode: \(logMode), defaulting to 'normal'") self = .normal } } case normal case verbose } public static var logMode: LogMode = .normal func log(message: String) { let timestamp = NSDate().timeIntervalSince1970 print("[\(timestamp)]: \(message)") } func verbose(message: String) { if Logger.logMode == .verbose { let timestamp = NSDate().timeIntervalSince1970 print("[\(timestamp)]: \(message)") } } } func log(message: String) { logger.log(message: message) } func verbose(message: String) { logger.verbose(message: message) } // Please don't remove the lines below // They are used to detect outdated files // FastlaneRunnerAPIVersion [0.9.2]