From 3988a8645dba315478d0f1c10a2a4ec5b1da6775 Mon Sep 17 00:00:00 2001 From: meeh <meeh@mail.i2p> Date: Thu, 11 Oct 2018 16:59:59 +0000 Subject: [PATCH] OSX Launcher: major updates to the glue between 'backend' and GUI. Implemented the use of the new LaunchAgent classes --- .../routermgmt/RouterManager.swift | 41 +++- .../RouterProcessStatus+ObjectiveC.swift | 4 +- .../routermgmt/RouterProcessStatus.swift | 8 +- .../I2PLauncher/routermgmt/RouterRunner.swift | 228 +++++++++++++++--- 4 files changed, 225 insertions(+), 56 deletions(-) diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift index 8715542e34..e274d2017e 100644 --- a/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift +++ b/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift @@ -19,34 +19,42 @@ class RouterManager : NSObject { // MARK: - Properties - static let packedVersion : String = "0.9.36" + static let packedVersion : String = "0.9.37" let eventManager = EventManager() + let routerRunner = RouterRunner() var logViewStorage: NSTextStorage? private static func handleRouterException(information:Any?) { - NSLog("event! - handle router exception") - NSLog(information as! String) + Logger.MLog(level:1,"event! - handle router exception") + Logger.MLog(level:1,information as! String) } private static func handleRouterStart(information:Any?) { - NSLog("event! - handle router start") + Logger.MLog(level:1,"event! - handle router start") RouterProcessStatus.routerStartedAt = Date() RouterProcessStatus.isRouterChildProcess = true RouterProcessStatus.isRouterRunning = true } + private static func handleRouterAlreadyStarted(information:Any?) { + Logger.MLog(level:1,"event! - handle router already started"); + } private static func handleRouterStop(information:Any?) { - NSLog("event! - handle router stop") + Logger.MLog(level:1,"event! - handle router stop") + // TODO: Double check, check if pid stored exists RouterProcessStatus.routerStartedAt = nil RouterProcessStatus.isRouterChildProcess = false RouterProcessStatus.isRouterRunning = false } private static func handleRouterPid(information:Any?) { - Swift.print("event! - handle router pid: ", information ?? "") + Logger.MLog(level:1,"".appendingFormat("event! - handle router pid: ", information as! String!)) + if (information != nil) { + let intPid = Int(information as! String) + } } private static func handleRouterVersion(information:Any?) { do { - Swift.print("event! - handle router version: ", information ?? "") + Logger.MLog(level:1, "".appendingFormat("event! - handle router version: ", information as! String!)) guard let currentVersion : String = information as? String else { throw ErrorsInRouterMgmr.InvalidVersion } @@ -54,11 +62,11 @@ class RouterManager : NSObject { throw ErrorsInRouterMgmr.InvalidVersion } if (packedVersion.compare(currentVersion, options: .numeric) == .orderedDescending) { - Swift.print("event! - router version: Packed version is newer, gonna re-deploy") + Logger.MLog(level:1,"event! - router version: Packed version is newer, gonna re-deploy") RouterManager.shared().eventManager.trigger(eventName: "router_must_upgrade", information: "got new version") } else { - Swift.print("event! - router version: No update needed") - RouterManager.shared().eventManager.trigger(eventName: "router_can_start", information: "all ok") + Logger.MLog(level:1,"event! - router version: No update needed") + RouterManager.shared().eventManager.trigger(eventName: "router_can_setup", information: "all ok") } } catch ErrorsInRouterMgmr.InvalidVersion { // This is most likely due to an earlier extract got killed halfway or something @@ -72,8 +80,7 @@ class RouterManager : NSObject { } private static var sharedRouterManager: RouterManager = { - let inst = DetectJava() - let routerManager = RouterManager(detectJavaInstance: inst) + let routerManager = RouterManager(detectJavaInstance: DetectJava.shared()) // Configuration // ... @@ -84,6 +91,9 @@ class RouterManager : NSObject { routerManager.eventManager.listenTo(eventName: "router_pid", action: handleRouterPid) routerManager.eventManager.listenTo(eventName: "router_version", action: handleRouterVersion) routerManager.eventManager.listenTo(eventName: "router_exception", action: handleRouterException) + routerManager.eventManager.listenTo(eventName: "router_already_running", action: handleRouterAlreadyStarted) + routerManager.eventManager.listenTo(eventName: "router_can_start", action: routerManager.routerRunner.StartAgent) + routerManager.eventManager.listenTo(eventName: "router_can_setup", action: routerManager.routerRunner.SetupAgent) return routerManager }() @@ -108,6 +118,13 @@ class RouterManager : NSObject { // MARK: - Accessors + static func logInfo(format: String, messages: String...) { + //SBridge.sharedInstance().logMessageWithFormat(0, format, messages)func k(_ x: Int32, _ params: String...) { + /*withVaList(messages) { + genericLogger(x, $0) + }*/ + } + class func shared() -> RouterManager { return sharedRouterManager } diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus+ObjectiveC.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus+ObjectiveC.swift index 0c4c8c3f8d..a4f2b4868f 100644 --- a/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus+ObjectiveC.swift +++ b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus+ObjectiveC.swift @@ -10,10 +10,10 @@ import Foundation extension RouterProcessStatus { - static func createNewRouterProcess(i2pPath: String, javaBinPath: String) { + static func createNewRouterProcess(i2pPath: String) { let timeWhenStarted = Date() RouterProcessStatus.routerStartedAt = timeWhenStarted - SBridge.sharedInstance().startupI2PRouter(i2pPath, javaBinPath: javaBinPath) + SBridge.sharedInstance().startupI2PRouter(i2pPath) RouterManager.shared().updateState() } static func shutdownRouterChildProcess() { diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus.swift index 29b343fa75..ca1abd0b83 100644 --- a/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus.swift +++ b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus.swift @@ -30,7 +30,11 @@ import AppKit } @objc func getJavaHome() -> String { - return RouterProcessStatus.knownJavaBinPath! + return DetectJava.shared().javaHome + } + + @objc func getJavaViaLibexec() -> Array<String> { + return DetectJava.shared().getJavaViaLibexecBin() } @objc func triggerEvent(en: String, details: String? = nil) { @@ -47,10 +51,8 @@ extension RouterProcessStatus { static var isRouterChildProcess : Bool = (RouterManager.shared().getRouterTask() != nil) static var routerVersion : String? = Optional.none static var routerStartedAt : Date? = Optional.none - static var knownJavaBinPath : String? = Optional.none static var i2pDirectoryPath : String = NSHomeDirectory() + "/Library/I2P" - static var knownRouterSubTaskRef : I2PSubprocess? = Optional.none } diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterRunner.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterRunner.swift index 09d04db116..aa416f7709 100644 --- a/launchers/macosx/I2PLauncher/routermgmt/RouterRunner.swift +++ b/launchers/macosx/I2PLauncher/routermgmt/RouterRunner.swift @@ -8,62 +8,212 @@ import Foundation -class RouterRunner: NSObject, I2PSubprocess { +class RouterRunner: NSObject { - var subprocessPath: String? + + var daemonPath: String? var arguments: String? - var timeWhenStarted: Date? + + static var launchAgent: LaunchAgent? + let routerStatus: RouterProcessStatus = RouterProcessStatus() var currentRunningProcess: Subprocess? var currentProcessResults: ExecutionResult? - func findJava() { - self.subprocessPath = RouterProcessStatus.knownJavaBinPath - } + let domainLabel = "net.i2p.macosx.I2PRouter" + + let plistName = "net.i2p.macosx.I2PRouterAgent.plist" - let defaultStartupFlags:[String] = [ - "-Xmx512M", - "-Xms128m", - "-Djava.awt.headless=true", - "-Dwrapper.logfile=/tmp/router.log", - "-Dwrapper.logfile.loglevel=DEBUG", - "-Dwrapper.java.pidfile=/tmp/routerjvm.pid", - "-Dwrapper.console.loglevel=DEBUG" + let defaultStartupCommand:String = "/usr/libexec/java_home" + + let defaultJavaHomeArgs:[String] = [ + "-v", + "1.7+", + "--exec", + "java", ] - private func subInit(cmdPath: String?, cmdArgs: String?) { - // Use this as common init - self.subprocessPath = cmdPath - self.arguments = cmdArgs - if (self.arguments?.isEmpty)! { - self.arguments = Optional.some(defaultStartupFlags.joined(separator: " ")) - }; - let newArgs:[String] = ["-c ", - self.subprocessPath!, - " ", - self.arguments!, + let appSupportPath = FileManager.default.urls(for: FileManager.SearchPathDirectory.applicationSupportDirectory, in: FileManager.SearchPathDomainMask.userDomainMask) + + func SetupAgent() { + let agent = SetupAndReturnAgent() + RouterRunner.launchAgent = agent + } + + typealias Async = (_ success: () -> Void, _ failure: (NSError) -> Void) -> Void + + func retry(numberOfTimes: Int, _ sleepForS: UInt32, task: () -> Async, success: () -> Void, failure: (NSError) -> Void) { + task()(success, { error in + if numberOfTimes > 1 { + sleep(sleepForS) + retry(numberOfTimes: numberOfTimes - 1, sleepForS, task: task, success: success, failure: failure) + } else { + failure(error) + } + }) + } + + func SetupAndReturnAgent() -> LaunchAgent { + + let defaultStartupFlags:[String] = [ + "-Xmx512M", + "-Xms128m", + "-Djava.awt.headless=true", + "".appendingFormat("-Di2p.base.dir=%@", NSHomeDirectory()+"/Library/I2P"), + "".appendingFormat("-Dwrapper.logfile=%@/Library/I2P/router.log", NSHomeDirectory()), + "-Dwrapper.logfile.loglevel=DEBUG", + "".appendingFormat("-Dwrapper.java.pidfile=%@/i2p/router.pid", appSupportPath.description), + "-Dwrapper.console.loglevel=DEBUG", + "net.i2p.router.Router" + ] + + self.daemonPath = self.defaultStartupCommand + self.arguments = defaultStartupFlags.joined(separator: " ") + + let basePath = NSHomeDirectory()+"/Library/I2P" + + let jars = try! FileManager.default.contentsOfDirectory(atPath: basePath+"/lib") + var classpath:String = "." + for jar in jars { + classpath += ":"+basePath+"/lib/"+jar + } + + var cliArgs:[String] = [ + self.daemonPath!, + ] + cliArgs.append(contentsOf: self.defaultJavaHomeArgs) + cliArgs.append(contentsOf: [ + "-cp", + classpath, + ]) + cliArgs.append(contentsOf: defaultStartupFlags) + let agent = LaunchAgent(label: self.domainLabel,program: cliArgs) + agent.launchOnlyOnce = false + agent.keepAlive = false + agent.workingDirectory = basePath + agent.userName = NSUserName() + agent.standardErrorPath = NSHomeDirectory()+"/Library/Logs/I2P/router.stderr.log" + agent.standardOutPath = NSHomeDirectory()+"/Library/Logs/I2P/router.stdout.log" + agent.environmentVariables = [ + "PATH": "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin", + "I2PBASE": basePath, ] - self.currentRunningProcess = Optional.some(Subprocess.init(executablePath: "/bin/sh", arguments: newArgs)) + agent.disabled = false + agent.processType = ProcessType.adaptive + RouterRunner.launchAgent = agent + + let userPreferences = UserDefaults.standard + let shouldStartupAtLogin = userPreferences.bool(forKey: "startRouterAtLogin") + agent.runAtLoad = shouldStartupAtLogin + agent.keepAlive = true + + do { + + try LaunchAgentManager.shared.write(agent, called: self.plistName) + sleep(1) + try LaunchAgentManager.shared.load(agent) + sleep(1) + + let agentStatus = LaunchAgentManager.shared.status(agent) + switch agentStatus { + case .running: + break + case .loaded: + break + case .unloaded: + sleep(2) + break + } + + + RouterManager.shared().eventManager.trigger(eventName: "router_can_start", information: agent) + } catch { + RouterManager.shared().eventManager.trigger(eventName: "router_setup_error", information: "\(error)") + } + return agent } - init(cmdPath: String?, _ cmdArgs: String? = Optional.none) { - super.init() - self.subInit(cmdPath: cmdPath, cmdArgs: cmdArgs) + func StartAgent(information:Any?) { + let agent = RouterRunner.launchAgent! + LaunchAgentManager.shared.start(agent) + sleep(1) + let agentStatus = agent.status() + switch agentStatus { + case .running(let pid): + RouterManager.shared().eventManager.trigger(eventName: "router_start", information: String(pid)) + routerStatus.setRouterStatus(true) + routerStatus.setRouterRanByUs(true) + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + // Delayed message to ensure UI has been initialized. + RouterManager.shared().eventManager.trigger(eventName: "router_pid", information: String(pid)) + } + break + + default: break + } } - init(coder: NSCoder) { - super.init() - self.subInit(cmdPath: Optional.none, cmdArgs: Optional.none) + func StopAgent() { + var agentStatus = LaunchAgentManager.shared.status(RouterRunner.launchAgent!) + switch agentStatus { + case .running: + LaunchAgentManager.shared.stop(RouterRunner.launchAgent!) + break + case .loaded, .unloaded: + try! LaunchAgentManager.shared.load(RouterRunner.launchAgent!) + routerStatus.setRouterStatus(false) + routerStatus.setRouterRanByUs(false) + RouterManager.shared().eventManager.trigger(eventName: "router_stop", information: "ok") + return; + break + default: break + } + sleep(1) + agentStatus = LaunchAgentManager.shared.status(RouterRunner.launchAgent!) + switch agentStatus { + case .loaded, .unloaded: + try! LaunchAgentManager.shared.load(RouterRunner.launchAgent!) + routerStatus.setRouterStatus(false) + routerStatus.setRouterRanByUs(false) + RouterManager.shared().eventManager.trigger(eventName: "router_stop", information: "ok") + break + default: break + } } - func execute() { - if (self.currentRunningProcess != Optional.none!) { - print("Already executing! Process ", self.toString()) + func SetupLaunchd() { + do { + try LaunchAgentManager.shared.write(RouterRunner.launchAgent!, called: self.plistName) + try LaunchAgentManager.shared.load(RouterRunner.launchAgent!) + } catch { + RouterManager.shared().eventManager.trigger(eventName: "router_exception", information: error) } - self.timeWhenStarted = Date() - RouterProcessStatus.routerStartedAt = self.timeWhenStarted - - self.currentProcessResults = self.currentRunningProcess?.execute(captureOutput: true) + } + + func TeardownLaunchd() { + /*let status = LaunchAgentManager.shared.status(RouterRunner.launchAgent!) + switch status { + case .running:*/ + do { + // Unload no matter previous state! + try LaunchAgentManager.shared.unload(RouterRunner.launchAgent!) + + let plistPath = NSHomeDirectory()+"/Library/LaunchAgents/"+self.plistName + + sleep(1) + if FileManager.default.fileExists(atPath: plistPath) { + try FileManager.default.removeItem(atPath: plistPath) + } + } catch LaunchAgentManagerError.urlNotSet(label: self.domainLabel) { + Logger.MLog(level:3, "URL not set in launch agent") + } catch { + Logger.MLog(level:3, "".appendingFormat("Error in launch agent: %s", error as CVarArg)) + RouterManager.shared().eventManager.trigger(eventName: "router_exception", information: error) + } + /* break + default: break + } + */ } } -- GitLab