Files
i2p.i2p/launchers/macosx/I2PLauncher/routermgmt/RouterRunner.swift

217 lines
7.4 KiB
Swift

//
// RouterRunner.swift
// I2PLauncher
//
// Created by Mikal Villa on 18/09/2018.
// Copyright © 2018 The I2P Project. All rights reserved.
//
import Foundation
class RouterRunner: NSObject {
var daemonPath: String?
var arguments: String?
static var launchAgent: LaunchAgent?
let routerStatus: RouterProcessStatus = RouterProcessStatus()
var currentRunningProcess: Subprocess?
var currentProcessResults: ExecutionResult?
let domainLabel = String(NSString(format: "%@.I2PRouter", APPDOMAIN))
let plistName = String(NSString(format: "%@.I2PRouter.plist", APPDOMAIN))
let appSupportPath = FileManager.default.urls(for: FileManager.SearchPathDirectory.applicationSupportDirectory, in: FileManager.SearchPathDomainMask.userDomainMask)
override init() {
super.init()
}
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 applicationsSupportPath: URL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
let defaultStartupFlags:[String] = [
"-Djava.awt.headless=true",
"".appendingFormat("-Di2p.base.dir=%@", Preferences.shared().i2pBaseDirectory),
"".appendingFormat("-Dwrapper.logfile=%@/i2p/router.log", applicationsSupportPath.absoluteString),
"".appendingFormat("-Dwrapper.java.pidfile=%@/i2p/router.pid", applicationsSupportPath.absoluteString),
"-Dwrapper.logfile.loglevel=DEBUG", // TODO: Allow loglevel to be set from Preferences?
"-Dwrapper.console.loglevel=DEBUG",
"net.i2p.router.Router"
]
let javaCliArgs = Preferences.shared().javaCommandPath.splitByWhitespace()
self.daemonPath = javaCliArgs[0]
self.arguments = defaultStartupFlags.joined(separator: " ")
let basePath = Preferences.shared().i2pBaseDirectory
let jars = try! FileManager.default.contentsOfDirectory(atPath: basePath+"/lib")
var classpath:String = "."
for jar in jars {
if (jar.hasSuffix(".jar")) {
classpath += ":"+basePath+"/lib/"+jar
}
}
var cliArgs:[String] = [
self.daemonPath!,
]
cliArgs.append(contentsOf: javaCliArgs.dropFirst())
cliArgs.append(contentsOf: [
"-cp",
classpath,
])
// This allow java arguments to be passed from the settings
cliArgs.append(contentsOf: Preferences.shared().javaCommandOptions.splitByWhitespace())
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 = NSString(format: "%@/router.stderr.log", Preferences.shared().i2pLogDirectory) as String
agent.standardOutPath = NSString(format: "%@/router.stdout.log", Preferences.shared().i2pLogDirectory) as String
agent.environmentVariables = [
"PATH": "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin",
"I2PBASE": basePath,
]
agent.disabled = false
agent.processType = ProcessType.adaptive
RouterRunner.launchAgent = agent
// NOTE: I suspect this is better to solve in the application
agent.runAtLoad = false //Preferences.shared().startRouterOnLauncherStart
agent.keepAlive = true
DispatchQueue(label: "background_starter").async {
do {
// TODO: Find a better way than sleep
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:
DispatchQueue.main.async {
RouterManager.shared().eventManager.trigger(eventName: "router_can_start", information: agent)
}
break
case .unloaded:
break
}
} catch {
DispatchQueue.main.async {
RouterManager.shared().eventManager.trigger(eventName: "router_setup_error", information: "\(error)")
}
}
}
return agent
}
func StartAgent(_ information:Any? = nil) {
if (RouterManager.shared().checkIfRouterCanStart()) {
let agent = RouterRunner.launchAgent ?? information as! LaunchAgent
DispatchQueue(label: "background_block").async {
LaunchAgentManager.shared.start(agent, { (proc) in
NSLog("Will call onLaunchdStarted")
})
}
} else {
SBridge.sendUserNotification("Whops! Please wait", formattedMsg: "I'm sorry but it's still something unresolved before we can start the I2P router. Please wait.")
}
}
func StopAgent(_ callback: @escaping () -> () = {}) {
let agentStatus = LaunchAgentManager.shared.status(RouterRunner.launchAgent!)
DispatchQueue(label: "background_block").async {
do {
switch agentStatus {
case .running:
// For now we need to use unload to stop it.
try LaunchAgentManager.shared.unload(RouterRunner.launchAgent!, { (proc) in
// Called when stop is actually executed
proc.waitUntilExit()
DispatchQueue.main.async {
RouterManager.shared().eventManager.trigger(eventName: "router_stop", information: "ok")
callback()
}
})
try LaunchAgentManager.shared.load(RouterRunner.launchAgent!)
break
case .unloaded:
// Seems it sometimes get unloaded on stop, we load it again.
try! LaunchAgentManager.shared.load(RouterRunner.launchAgent!)
return
default: break
}
} catch {
NSLog("Error \(error)")
}
}
}
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)
}
}
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
}
*/
}
}