diff --git a/launchers/macosx/I2PLauncher/16x16-Itoopie Transparent.png b/launchers/macosx/16x16-Itoopie Transparent.png
similarity index 100%
rename from launchers/macosx/I2PLauncher/16x16-Itoopie Transparent.png
rename to launchers/macosx/16x16-Itoopie Transparent.png
diff --git a/launchers/macosx/I2PLauncher/AppDelegate.h b/launchers/macosx/AppDelegate.h
similarity index 100%
rename from launchers/macosx/I2PLauncher/AppDelegate.h
rename to launchers/macosx/AppDelegate.h
diff --git a/launchers/macosx/I2PLauncher/Base.lproj/UserInterfaces.xib b/launchers/macosx/I2PLauncher/Base.lproj/UserInterfaces.xib
new file mode 100644
index 000000000..a733a1265
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/Base.lproj/UserInterfaces.xib
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/launchers/macosx/I2PLauncher/I2PLauncher-Bridging-Header.h b/launchers/macosx/I2PLauncher/I2PLauncher-Bridging-Header.h
new file mode 100644
index 000000000..a83635db3
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/I2PLauncher-Bridging-Header.h
@@ -0,0 +1,5 @@
+//
+// Use this file to import your target's public headers that you would like to expose to Swift.
+//
+#import "SBridge.h"
+#import "AppleStuffExceptionHandler.h"
diff --git a/launchers/macosx/I2PLauncher/Info.plist b/launchers/macosx/I2PLauncher/Info.plist
index 1e0372a28..6ba8490e6 100644
--- a/launchers/macosx/I2PLauncher/Info.plist
+++ b/launchers/macosx/I2PLauncher/Info.plist
@@ -3,47 +3,53 @@
CFBundleDevelopmentRegion
- English
+ $(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
- I2PLauncher
- NSHumanReadableCopyright
- Public Domain
- CFBundleGetInfoString
- 0.9.35-experimental
+ $(EXECUTABLE_NAME)
CFBundleIconFile
- images/AppIcon.icns
+
CFBundleIdentifier
- net.i2p.launcher
+ $(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
- I2P
+ $(PRODUCT_NAME)
CFBundlePackageType
APPL
CFBundleShortVersionString
- 0.0.1
- CFBundleSignature
- I2P
+ 1.0
+ CFBundleURLTypes
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleURLIconFile
+ ItoopieTransparent
+ CFBundleURLName
+ http+i2p
+ CFBundleURLSchemes
+
+ http+i2p
+
+
+
CFBundleVersion
- 0.0.1
- NSUserNotificationAlertStyle
- alert
+ 1
+ LSMinimumSystemVersion
+ $(MACOSX_DEPLOYMENT_TARGET)
+ LSUIElement
+
NSAppleScriptEnabled
- CGDisableCoalescedUpdates
-
- LSMinimumSystemVersion
- 10.5
- CFBundleDisplayName
- I2P
- LSMinimumSystemVersionByArchitecture
-
- i386
- 10.5.0
- x86_64
- 10.6.0
-
- LSUIElement
- 1
+ NSHumanReadableCopyright
+ Copyright © 2018 The I2P Project. All rights reserved.
+ NSMainNibFile
+ UserInterfaces
+ NSPrincipalClass
+ NSApplication
+ NSServices
+
+
+
diff --git a/launchers/macosx/I2PLauncher/README.md b/launchers/macosx/I2PLauncher/README.md
deleted file mode 100644
index e82338e83..000000000
--- a/launchers/macosx/I2PLauncher/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# The Objective-C++ part of the Mac OS X launcher
-
-## Why?
-
-Code signing, OS X integration.
-
-## Howto build?
-
-Preferred tool is [ninja](https://ninja-build.org/). A makefile is also available.
-
-Build with: `ninja`
diff --git a/launchers/macosx/I2PLauncher/Storyboard.storyboard b/launchers/macosx/I2PLauncher/Storyboard.storyboard
new file mode 100644
index 000000000..11e2e37bd
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/Storyboard.storyboard
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift b/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift
new file mode 100644
index 000000000..c0bcefb33
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift
@@ -0,0 +1,104 @@
+//
+// SwiftMainDelegate.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 17/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+import Cocoa
+
+@objc class SwiftMainDelegate : NSObject {
+
+ //let statusItem = NSStatusBar.system().statusItem(withLength: NSSquareStatusItemLength )
+ let statusBarController = StatusBarController()
+ let javaDetector = DetectJava()
+ static let objCBridge = SBridge()
+
+ override init() {
+ super.init()
+
+ self.javaDetector.findIt()
+ if (!javaDetector.isJavaFound()) {
+ print("Could not find java....")
+ terminate("No java..")
+ }
+ let javaBinPath = self.javaDetector.javaHome
+ print("Found java home = ", javaBinPath)
+
+ let (portIsNotTaken, descPort) = RouterProcessStatus.checkTcpPortForListen(port: 7657)
+ if (!portIsNotTaken) {
+ RouterProcessStatus.isRouterRunning = true
+ RouterProcessStatus.isRouterChildProcess = false
+ print("I2P Router seems to be running")
+ } else {
+ RouterProcessStatus.isRouterRunning = false
+ print("I2P Router seems to NOT be running")
+
+ }
+
+
+ }
+
+ func findInstalledI2PVersion(jarPath: String, javaBin: String) {
+ var i2pPath = NSHomeDirectory()
+ i2pPath += "/Library/I2P"
+ var jExecPath:String = javaBin
+
+ // Sometimes, home will return the binary, sometimes the actual home dir. This fixes the diverge.
+ // If JRE is detected, binary follows - if it's JDK, home follows.
+ if (jExecPath.hasSuffix("Home")) {
+ jExecPath += "/jre/bin/java"
+ }
+
+ let subCmd = jExecPath + " -cp " + jarPath + " net.i2p.CoreVersion"
+
+ var cmdArgs:[String] = ["-c", subCmd]
+ print(cmdArgs)
+ let sub:Subprocess = Subprocess.init(executablePath: "/bin/sh", arguments: cmdArgs)
+ let results:ExecutionResult = sub.execute(captureOutput: true)!
+ if (results.didCaptureOutput) {
+ print("captured output")
+ let i2pVersion = results.outputLines.first?.replace(target: "I2P Core version: ", withString: "")
+ print("I2P version detected: ",i2pVersion!)
+ RouterProcessStatus.routerVersion = i2pVersion
+ } else {
+ print("did NOT captured output")
+
+ }
+ }
+
+ @objc func applicationDidFinishLaunching() {
+ print("Hello from swift!")
+ var i2pPath = NSHomeDirectory()
+ i2pPath += "/Library/I2P"
+
+ let javaBinPath = self.javaDetector.javaHome.replace(target: " ", withString: "\\ ")
+
+ let fileManager = FileManager()
+ var ok = ObjCBool(true)
+ let doesI2PDirExists = fileManager.fileExists(atPath: i2pPath, isDirectory: &ok)
+
+ if (!doesI2PDirExists) {
+ // Deploy
+ }
+
+ let i2pJarPath = i2pPath + "/lib/i2p.jar"
+
+ findInstalledI2PVersion(jarPath: i2pJarPath, javaBin: javaBinPath)
+ }
+
+ @objc static func openLink(url: String) {
+ objCBridge.openUrl(url)
+ }
+
+ @objc func applicationWillTerminate() {
+ // Shutdown stuff
+ }
+
+ @objc func terminate(_ why: Any?) {
+ print("Stopping cause of ", why!)
+ }
+}
+
diff --git a/launchers/macosx/I2PLauncher/Utils/ArrayExtensions.swift b/launchers/macosx/I2PLauncher/Utils/ArrayExtensions.swift
new file mode 100644
index 000000000..ccfc807b0
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/Utils/ArrayExtensions.swift
@@ -0,0 +1,29 @@
+//
+// ArrayExtension.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 17/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+
+
+extension Array where Element: NSAttributedString {
+ func joined2(separator: NSAttributedString) -> NSAttributedString {
+ var isFirst = true
+ return self.reduce(NSMutableAttributedString()) {
+ (r, e) in
+ if isFirst {
+ isFirst = false
+ } else {
+ r.append(separator)
+ }
+ r.append(e)
+ return r
+ }
+ }
+
+}
+
+
diff --git a/launchers/macosx/I2PLauncher/Utils/DateTimeUtils.swift b/launchers/macosx/I2PLauncher/Utils/DateTimeUtils.swift
new file mode 100644
index 000000000..471dffc24
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/Utils/DateTimeUtils.swift
@@ -0,0 +1,87 @@
+//
+// DateTimeUtils.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 18/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+
+extension Date {
+ init(date: NSDate) {
+ self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate)
+ }
+}
+
+extension NSDate {
+ convenience init(date: Date) {
+ self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate)
+ }
+}
+
+class DateTimeUtils {
+ static func timeAgoSinceDate(date:NSDate, numericDates:Bool) -> String {
+ let calendar = NSCalendar.current
+ let unitFlags: Set = [.minute, .hour, .day, .weekOfYear, .month, .year, .second]
+ let now = NSDate()
+ let earliest = now.earlierDate(date as Date)
+ let latest = (earliest == now as Date) ? date : now
+ let components = calendar.dateComponents(unitFlags, from: earliest as Date, to: latest as Date)
+
+ if (components.year! >= 2) {
+ return "\(components.year!) years ago"
+ } else if (components.year! >= 1){
+ if (numericDates){
+ return "1 year ago"
+ } else {
+ return "Last year"
+ }
+ } else if (components.month! >= 2) {
+ return "\(components.month!) months ago"
+ } else if (components.month! >= 1){
+ if (numericDates){
+ return "1 month ago"
+ } else {
+ return "Last month"
+ }
+ } else if (components.weekOfYear! >= 2) {
+ return "\(components.weekOfYear!) weeks ago"
+ } else if (components.weekOfYear! >= 1){
+ if (numericDates){
+ return "1 week ago"
+ } else {
+ return "Last week"
+ }
+ } else if (components.day! >= 2) {
+ return "\(components.day!) days ago"
+ } else if (components.day! >= 1){
+ if (numericDates){
+ return "1 day ago"
+ } else {
+ return "Yesterday"
+ }
+ } else if (components.hour! >= 2) {
+ return "\(components.hour!) hours ago"
+ } else if (components.hour! >= 1){
+ if (numericDates){
+ return "1 hour ago"
+ } else {
+ return "An hour ago"
+ }
+ } else if (components.minute! >= 2) {
+ return "\(components.minute!) minutes ago"
+ } else if (components.minute! >= 1){
+ if (numericDates){
+ return "1 minute ago"
+ } else {
+ return "A minute ago"
+ }
+ } else if (components.second! >= 3) {
+ return "\(components.second!) seconds ago"
+ } else {
+ return "Just now"
+ }
+
+ }
+}
diff --git a/launchers/macosx/I2PLauncher/Utils/EventMonitor.swift b/launchers/macosx/I2PLauncher/Utils/EventMonitor.swift
new file mode 100644
index 000000000..008e0e44e
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/Utils/EventMonitor.swift
@@ -0,0 +1,37 @@
+//
+// EventMonitor.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 02/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+import Cocoa
+
+public class EventMonitor {
+ private var monitor: Any?
+ private let mask: NSEvent.EventTypeMask
+ private let handler: (NSEvent?) -> Void
+
+ public init(mask: NSEvent.EventTypeMask, handler: @escaping (NSEvent?) -> Void) {
+ self.mask = mask
+ self.handler = handler
+ }
+
+ deinit {
+ stop()
+ }
+
+ public func start() {
+ monitor = NSEvent.addGlobalMonitorForEvents(matching: mask, handler: handler)
+ }
+
+ public func stop() {
+ if monitor != nil {
+ NSEvent.removeMonitor(monitor!)
+ monitor = nil
+ }
+ }
+}
+
diff --git a/launchers/macosx/I2PLauncher/Utils/ReflectionFunctions.swift b/launchers/macosx/I2PLauncher/Utils/ReflectionFunctions.swift
new file mode 100644
index 000000000..dffbcf84b
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/Utils/ReflectionFunctions.swift
@@ -0,0 +1,57 @@
+//
+// ReflectionFunctions.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 17/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+
+class ReflectionFunctions {
+
+ /// Given pointer to first element of a C array, invoke a function for each element
+ func enumerateCArray(array: UnsafePointer, count: UInt32, f: (UInt32, T) -> ()) {
+ var ptr = array
+ for i in 0.. String? {
+ let sel = method_getName(m)
+ let nameCString = sel_getName(sel)
+ return String(cString: nameCString!)
+ }
+
+ /// Print the names for each method in a class
+ func printMethodNamesForClass(cls: AnyClass) {
+ var methodCount: UInt32 = 0
+ let methodList = class_copyMethodList(cls, &methodCount)
+ if methodList != nil && methodCount > 0 {
+ enumerateCArray(array: methodList!, count: methodCount) { i, m in
+ let name = methodName(m: m!) ?? "unknown"
+ print("#\(i): \(name)")
+ }
+
+ free(methodList)
+ }
+ }
+
+ /// Print the names for each method in a class with a specified name
+ func printMethodNamesForClassNamed(classname: String) {
+ // NSClassFromString() is declared to return AnyClass!, but should be AnyClass?
+ let maybeClass: AnyClass? = NSClassFromString(classname)
+ if let cls: AnyClass = maybeClass {
+ printMethodNamesForClass(cls: cls)
+ }
+ else {
+ print("\(classname): no such class")
+ }
+ }
+}
+
+
+
diff --git a/launchers/macosx/I2PLauncher/Utils/StringExtensions.swift b/launchers/macosx/I2PLauncher/Utils/StringExtensions.swift
new file mode 100644
index 000000000..1bd3f1898
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/Utils/StringExtensions.swift
@@ -0,0 +1,35 @@
+//
+// StringExtensions.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 17/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+
+extension String {
+
+ func replace(target: String, withString: String) -> String
+ {
+ return self.replacingOccurrences(of: target, with: withString, options: NSString.CompareOptions.literal, range: nil)
+ }
+
+ /// Returns an array of string obtained splitting self at each newline ("\n").
+ /// If the last character is a newline, it will be ignored (no empty string
+ /// will be appended at the end of the array)
+ public func splitByNewline() -> [String] {
+ return self.split { $0 == "\n" }.map(String.init)
+ }
+
+ /// Returns an array of string obtained splitting self at each space, newline or TAB character
+ public func splitByWhitespace() -> [String] {
+ let whitespaces = Set([" ", "\n", "\t"])
+ return self.split { whitespaces.contains($0) }.map(String.init)
+ }
+
+ public func splitByColon() -> [String] {
+ return self.split { $0 == ":" }.map(String.init)
+ }
+}
+
diff --git a/launchers/macosx/I2PLauncher/routermgmt/DetectJava.swift b/launchers/macosx/I2PLauncher/routermgmt/DetectJava.swift
new file mode 100644
index 000000000..aa5d59ad0
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/routermgmt/DetectJava.swift
@@ -0,0 +1,136 @@
+//
+// DetectJava.swift
+// JavaI2PWrapper
+//
+// Created by Mikal Villa on 24/03/2018.
+// Copyright © 2018 I2P. All rights reserved.
+//
+
+import Foundation
+
+class DetectJava : NSObject {
+
+ var hasJRE : Bool = false
+ var userWantJRE : Bool = false
+ var userAcceptOracleEULA : Bool = false
+
+ // Java checks
+ var javaHome: String = ""{
+
+ //Called before the change
+ willSet(newValue){
+ print("DetectJava.javaHome will change from "+self.javaHome+" to "+newValue)
+ }
+
+ //Called after the change
+ didSet{
+ hasJRE = true
+ print("MDetectJava.javaHome did change from "+oldValue+" to "+self.javaHome)
+ }
+ };
+ private var testedEnv : Bool = false
+ private var testedJH : Bool = false
+ private var testedDfl : Bool = false
+
+ func isJavaFound() -> Bool {
+ if !(self.javaHome.isEmpty) {
+ return true
+ }
+ return false
+ }
+
+ func findIt() {
+ print("Start with checking environment variable")
+ self.checkJavaEnvironmentVariable()
+ if !(self.javaHome.isEmpty) {
+ RouterProcessStatus.knownJavaBinPath = Optional.some(self.javaHome)
+ hasJRE = true
+ return
+ }
+ print("Checking default JRE install path")
+ self.checkDefaultJREPath()
+ if !(self.javaHome.isEmpty) {
+ RouterProcessStatus.knownJavaBinPath = Optional.some(self.javaHome)
+ hasJRE = true
+ return
+ }
+ print("Checking with the java_home util")
+ self.runJavaHomeCmd()
+ if !(self.javaHome.isEmpty) {
+ RouterProcessStatus.knownJavaBinPath = Optional.some(self.javaHome)
+ hasJRE = true
+ return
+ }
+ }
+
+ func runJavaHomeCmd() {
+ let task = Process()
+ task.launchPath = "/usr/libexec/java_home"
+ task.arguments = []
+ let pipe = Pipe()
+ task.standardOutput = pipe
+ let outHandle = pipe.fileHandleForReading
+ outHandle.waitForDataInBackgroundAndNotify()
+
+ var obs1 : NSObjectProtocol!
+ obs1 = NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable,
+ object: outHandle, queue: nil) { notification -> Void in
+ let data = outHandle.availableData
+ if data.count > 0 {
+ let str = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
+ if (str != nil) {
+ let stringVal = str! as String
+ print("got output: "+stringVal)
+ self.javaHome = stringVal
+ }
+ // TODO: Found something, check it out
+ outHandle.waitForDataInBackgroundAndNotify()
+ } else {
+ print("EOF on stdout from process")
+ NotificationCenter.default.removeObserver(obs1)
+ // No JRE so far
+ }
+ }
+
+ var obs2 : NSObjectProtocol!
+ obs2 = NotificationCenter.default.addObserver(forName: Process.didTerminateNotification,
+ object: task, queue: nil) { notification -> Void in
+ print("terminated")
+ NotificationCenter.default.removeObserver(obs2)
+ }
+
+ task.launch()
+ task.waitUntilExit()
+ self.testedJH = true
+ }
+
+ func checkDefaultJREPath() {
+ let defaultJREPath = "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"
+ if FileManager.default.fileExists(atPath: defaultJREPath) {
+ // Found it!!
+ self.javaHome = defaultJREPath
+ }
+ self.testedDfl = true
+ // No JRE here
+ }
+
+ func getEnvironmentVar(_ name: String) -> String? {
+ guard let rawValue = getenv(name) else { return nil }
+ return String(utf8String: rawValue)
+ }
+
+ func checkJavaEnvironmentVariable() {
+ let dic = ProcessInfo.processInfo.environment
+ //ProcessInfo.processInfo.environment["JAVA_HOME"]
+ if let javaHomeEnv = dic["JAVA_HOME"] {
+ // Maybe we got an JRE
+ print("Found JAVA_HOME with value:")
+ print(javaHomeEnv)
+ self.javaHome = javaHomeEnv
+ }
+ self.testedEnv = true
+ print("JAVA HOME is not set")
+ }
+
+
+}
diff --git a/launchers/macosx/I2PLauncher/routermgmt/I2PSubprocess.swift b/launchers/macosx/I2PLauncher/routermgmt/I2PSubprocess.swift
new file mode 100644
index 000000000..386543961
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/routermgmt/I2PSubprocess.swift
@@ -0,0 +1,32 @@
+//
+// I2PSubprocess.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 18/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+
+protocol I2PSubprocess {
+ var subprocessPath: String? { get set }
+ var arguments: String? { get set }
+ var timeWhenStarted: Date? { get set }
+
+ func findJava();
+
+}
+
+extension I2PSubprocess {
+ func toString() -> String {
+ let jp = self.subprocessPath!
+ let args = self.arguments!
+
+ var presentation:String = "I2PSubprocess[ cmd="
+ presentation += jp
+ presentation += " , args="
+ presentation += args
+ presentation += "]"
+ return presentation
+ }
+}
diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterDeployer.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterDeployer.swift
new file mode 100644
index 000000000..31e407ae3
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/routermgmt/RouterDeployer.swift
@@ -0,0 +1,28 @@
+//
+// RouterDeployer.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 18/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+
+class RouterDeployer: NSObject, I2PSubprocess {
+ var subprocessPath: String?
+ var timeWhenStarted: Date?
+
+ var arguments: String?
+
+ func findJava() {
+ //
+ }
+
+ let javaBinaryPath = RouterProcessStatus.knownJavaBinPath
+
+ let defaultFlagsForExtractorJob:[String] = [
+ "-Xmx512M",
+ "-Xms128m",
+ "-Djava.awt.headless=true"
+ ]
+}
diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus+ObjectiveC.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus+ObjectiveC.swift
new file mode 100644
index 000000000..cda1db500
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus+ObjectiveC.swift
@@ -0,0 +1,18 @@
+//
+// RouterProcessStatus+ObjectiveC.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 18/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+
+extension RouterProcessStatus {
+ static func createNewRouterProcess(i2pPath: String, javaBinPath: String) {
+ let bridge = SBridge()
+ let timeWhenStarted = Date()
+ RouterProcessStatus.routerStartedAt = timeWhenStarted
+ bridge.startupI2PRouter(i2pPath, javaBinPath: javaBinPath)
+ }
+}
diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus.swift
new file mode 100644
index 000000000..eb4ed37bd
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus.swift
@@ -0,0 +1,89 @@
+//
+// RouterProcessStatus.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 18/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+import AppKit
+
+class RouterProcessStatus : NSObject {
+
+ /**
+ *
+ * Why the functions bellow? Because the Objective-C bridge is limited, it can't do Swift "static's" over it.
+ *
+ **/
+
+ func setRouterStatus(_ isRunning: Bool = false) {
+ RouterProcessStatus.isRouterRunning = isRunning
+ }
+
+ func setRouterRanByUs(_ ranByUs: Bool = false) {
+ RouterProcessStatus.isRouterChildProcess = ranByUs
+ }
+
+ func getRouterIsRunning() -> Bool? {
+ return RouterProcessStatus.isRouterRunning
+ }
+}
+
+extension RouterProcessStatus {
+ static var isRouterRunning : Bool = false
+ static var isRouterChildProcess : Bool = false
+ static var routerVersion : String? = Optional.none
+ static var routerUptime : 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
+
+}
+
+
+
+
+extension RouterProcessStatus {
+ static func checkTcpPortForListen(port: in_port_t) -> (Bool, descr: String){
+
+ let socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0)
+ if socketFileDescriptor == -1 {
+ return (false, "SocketCreationFailed, \(descriptionOfLastError())")
+ }
+
+ var addr = sockaddr_in()
+ let sizeOfSockkAddr = MemoryLayout.size
+ addr.sin_len = __uint8_t(sizeOfSockkAddr)
+ addr.sin_family = sa_family_t(AF_INET)
+ addr.sin_port = Int(OSHostByteOrder()) == OSLittleEndian ? _OSSwapInt16(port) : port
+ addr.sin_addr = in_addr(s_addr: inet_addr("0.0.0.0"))
+ addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0)
+ var bind_addr = sockaddr()
+ memcpy(&bind_addr, &addr, Int(sizeOfSockkAddr))
+
+ if Darwin.bind(socketFileDescriptor, &bind_addr, socklen_t(sizeOfSockkAddr)) == -1 {
+ let details = descriptionOfLastError()
+ release(socket: socketFileDescriptor)
+ return (false, "\(port), BindFailed, \(details)")
+ }
+ if listen(socketFileDescriptor, SOMAXCONN ) == -1 {
+ let details = descriptionOfLastError()
+ release(socket: socketFileDescriptor)
+ return (false, "\(port), ListenFailed, \(details)")
+ }
+ release(socket: socketFileDescriptor)
+ return (true, "\(port) is free for use")
+ }
+
+ static func release(socket: Int32) {
+ Darwin.shutdown(socket, SHUT_RDWR)
+ close(socket)
+ }
+ static func descriptionOfLastError() -> String {
+ return String(cString: UnsafePointer(strerror(errno))) ?? "Error: \(errno)"
+ }
+}
+
diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterRunner.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterRunner.swift
new file mode 100644
index 000000000..50a7fd761
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/routermgmt/RouterRunner.swift
@@ -0,0 +1,69 @@
+//
+// RouterRunner.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 18/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+
+class RouterRunner: NSObject, I2PSubprocess {
+
+ var subprocessPath: String?
+ var arguments: String?
+ var timeWhenStarted: Date?
+
+ var currentRunningProcess: Subprocess?
+ var currentProcessResults: ExecutionResult?
+
+ func findJava() {
+ self.subprocessPath = RouterProcessStatus.knownJavaBinPath
+ }
+
+ 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"
+ ]
+
+ 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: " "))
+ };
+ var newArgs:[String] = ["-c ",
+ self.subprocessPath!,
+ " ",
+ self.arguments!,
+ ]
+ self.currentRunningProcess = Optional.some(Subprocess.init(executablePath: "/bin/sh", arguments: newArgs))
+ }
+
+ init(cmdPath: String?, _ cmdArgs: String? = Optional.none) {
+ super.init()
+ self.subInit(cmdPath: cmdPath, cmdArgs: cmdArgs)
+ }
+
+ init(coder: NSCoder) {
+ super.init()
+ self.subInit(cmdPath: Optional.none, cmdArgs: Optional.none)
+ }
+
+ func execute() {
+ if (self.currentRunningProcess != Optional.none!) {
+ print("Already executing! Process ", self.toString())
+ }
+ self.timeWhenStarted = Date()
+ RouterProcessStatus.routerStartedAt = self.timeWhenStarted
+
+ self.currentProcessResults = self.currentRunningProcess?.execute(captureOutput: true)
+ }
+
+}
diff --git a/launchers/macosx/I2PLauncher/subprocesses/AppleStuffExceptionHandler.h b/launchers/macosx/I2PLauncher/subprocesses/AppleStuffExceptionHandler.h
new file mode 100644
index 000000000..fc9fd9877
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/subprocesses/AppleStuffExceptionHandler.h
@@ -0,0 +1,12 @@
+//
+// AppleStuffExceptionHandler.h
+// I2PLauncher
+//
+// Created by Mikal Villa on 17/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+#import
+
+extern NSException* __nullable AppleStuffExecuteWithPossibleExceptionInBlock(dispatch_block_t _Nonnull block);
+
diff --git a/launchers/macosx/I2PLauncher/subprocesses/AppleStuffExceptionHandler.m b/launchers/macosx/I2PLauncher/subprocesses/AppleStuffExceptionHandler.m
new file mode 100644
index 000000000..951a38355
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/subprocesses/AppleStuffExceptionHandler.m
@@ -0,0 +1,22 @@
+//
+// AppleStuffExceptionHandler.m
+// I2PLauncher
+//
+// Created by Mikal Villa on 17/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+#import "AppleStuffExceptionHandler.h"
+
+NSException* __nullable AppleStuffExecuteWithPossibleExceptionInBlock(dispatch_block_t _Nonnull block) {
+
+ @try {
+ if (block != nil) {
+ block();
+ }
+ }
+ @catch (NSException *exception) {
+ return exception;
+ }
+ return nil;
+}
diff --git a/launchers/macosx/I2PLauncher/subprocesses/Error.swift b/launchers/macosx/I2PLauncher/subprocesses/Error.swift
new file mode 100644
index 000000000..cc3cfe02a
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/subprocesses/Error.swift
@@ -0,0 +1,22 @@
+//
+// Error.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 17/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+
+public class Error {
+
+ /// Prints to console the arguments and exits with status 1
+ static func die(arguments: Any...) -> Never {
+ let output = "ERROR: " + arguments.reduce("") { $0 + "\($1) " }
+ let trimOutput = output.trimmingCharacters(in: CharacterSet.whitespaces) + "\n"
+ let stderr = FileHandle.standardError
+ stderr.write(trimOutput.data(using: String.Encoding.utf8)!)
+ exit(1)
+ }
+}
+
diff --git a/launchers/macosx/I2PLauncher/subprocesses/ExecutionResult.swift b/launchers/macosx/I2PLauncher/subprocesses/ExecutionResult.swift
new file mode 100644
index 000000000..f482bfd20
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/subprocesses/ExecutionResult.swift
@@ -0,0 +1,63 @@
+//
+// ExecutionResult.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 17/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+
+public struct ExecutionResult {
+
+ /// Whether the output was captured
+ public let didCaptureOutput : Bool
+
+ /// The return status of the last subprocess
+ public var status: Int32 {
+ return pipelineStatuses.last!
+ }
+
+ /// Return status of all subprocesses in the pipeline
+ public let pipelineStatuses : [Int32]
+
+ /// The output of the subprocess. Empty string if no output was produced or not captured
+ public let output: String
+
+ /// The error output of the last subprocess. Empty string if no error output was produced or not captured
+ public var errors : String {
+ return pipelineErrors?.last ?? ""
+ }
+
+ /// The error output of all subprocesses in the pipeline. Empty string if no error output was produced or not captured
+ public let pipelineErrors : [String]?
+
+ /// The output, split by newline
+ /// - SeeAlso: `output`
+ public var outputLines : [String] {
+ return self.output.splitByNewline()
+ }
+
+ /// The error output, split by newline
+ /// - SeeAlso: `output`
+ public var errorsLines : [String] {
+ return self.errors.splitByNewline()
+ }
+
+ /// An execution result where no output was captured
+ init(pipelineStatuses: [Int32]) {
+ self.pipelineStatuses = pipelineStatuses
+ self.didCaptureOutput = false
+ self.pipelineErrors = nil
+ self.output = ""
+ }
+
+ /// An execution result where output was captured
+ init(pipelineStatuses: [Int32], pipelineErrors : [String], output : String) {
+ self.pipelineStatuses = pipelineStatuses
+ self.pipelineErrors = pipelineErrors
+ self.output = output
+ self.didCaptureOutput = true
+ }
+}
+
diff --git a/launchers/macosx/I2PLauncher/subprocesses/Subprocess+CompactAPI.swift b/launchers/macosx/I2PLauncher/subprocesses/Subprocess+CompactAPI.swift
new file mode 100644
index 000000000..cb472e0c3
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/subprocesses/Subprocess+CompactAPI.swift
@@ -0,0 +1,118 @@
+//
+// Subprocess+CompactAPI.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 17/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+
+
+// MARK: - Compact API
+extension Subprocess {
+
+ /**
+ Executes a subprocess and wait for completion, returning the output. If there is an error in creating the task,
+ it immediately exits the process with status 1
+ - returns: the output as a String
+ - note: in case there is any error in executing the process or creating the task, it will halt execution. Use
+ the constructor and `output` instance method for a more graceful error handling
+ */
+ public static func output(
+ executablePath: String,
+ _ arguments: String...,
+ workingDirectory: String = ".") -> String {
+
+ let process = Subprocess.init(executablePath: executablePath, arguments: arguments, workingDirectory: workingDirectory)
+ guard let result = process.execute(captureOutput: true) else {
+ Error.die(arguments: "Can't execute \"\(process)\"")
+ }
+ if result.status != 0 {
+ let errorLines = result.errors == "" ? "" : "\n" + result.errors
+ Error.die(arguments: "Process \"\(process)\" returned status \(result.status)", errorLines)
+ }
+ return result.output
+ }
+
+ /**
+ Executes a subprocess and wait for completion, returning the execution result. If there is an error in creating the task,
+ it immediately exits the process with status 1
+ - returns: the execution result
+ - note: in case there is any error in executing the process or creating the task, it will halt execution. Use
+ the constructor and `execute` instance method for a more graceful error handling
+ */
+ public static func execute(
+ executablePath: String,
+ _ arguments: String...,
+ workingDirectory: String = ".") -> ExecutionResult {
+
+ let process = Subprocess.init(executablePath: executablePath, arguments: arguments, workingDirectory: workingDirectory)
+ guard let result = process.execute(captureOutput: true) else {
+ Error.die(arguments: "Can't execute \"\(process)\"")
+ }
+ return result
+ }
+
+ /**
+ Executes a subprocess and wait for completion, returning the output as an array of lines. If there is an error
+ in creating or executing the task, it immediately exits the process with status 1
+ - returns: the output as a String
+ - note: in case there is any error in executing the process or creating the task, it will halt execution. Use
+ the constructor and `output` instance method for a more graceful error handling
+ */
+ public static func outputLines(
+ executablePath: String,
+ _ arguments: String...,
+ workingDirectory: String = ".") -> [String] {
+
+ let process = Subprocess.init(executablePath: executablePath, arguments: arguments, workingDirectory: workingDirectory)
+ guard let result = process.execute(captureOutput: true) else {
+ Error.die(arguments: "Can't execute \"\(process)\"")
+ }
+ if result.status != 0 {
+ let errorLines = result.errors == "" ? "" : "\n" + result.errors
+ Error.die(arguments: "Process \"\(process)\" returned status \(result.status)", errorLines)
+ }
+ return result.outputLines
+ }
+
+ /**
+ Executes a subprocess and wait for completion, returning the exit status. If there is an error in creating the task,
+ it immediately exits the process with status 1
+ - returns: the output as a String
+ - note: in case there is any error in launching the process or creating the task, it will halt execution. Use
+ the constructor and the `run` instance method for a more graceful error handling
+ */
+ public static func run(
+ executablePath: String,
+ _ arguments: String...,
+ workingDirectory: String = ".") -> Int32 {
+
+ let process = Subprocess.init(executablePath: executablePath, arguments: arguments, workingDirectory: workingDirectory)
+ guard let result = process.run() else {
+ Error.die(arguments: "Can't execute \"\(process)\"")
+ }
+ return result
+ }
+
+ /**
+ Executes a subprocess and wait for completion. If there is an error in creating the task, or if the tasks
+ returns an exit status other than 0, it immediately exits the process with status 1
+ - note: in case there is any error in launching the process or creating the task, or if the task exists with a exit status other than 0, it will halt execution. Use
+ the constructor and `run` instance method for a more graceful error handling
+ */
+ public static func runOrDie(
+ executablePath: String,
+ _ arguments: String...,
+ workingDirectory: String = ".") {
+
+ let process = Subprocess.init(executablePath: executablePath, arguments: arguments, workingDirectory: workingDirectory)
+ guard let result = process.run() else {
+ Error.die(arguments: "Can't execute \"\(process)\"")
+ }
+ if result != 0 {
+ Error.die(arguments: "Process \"\(process)\" returned status \(result)")
+ }
+ }
+}
diff --git a/launchers/macosx/I2PLauncher/subprocesses/Subprocess.swift b/launchers/macosx/I2PLauncher/subprocesses/Subprocess.swift
new file mode 100644
index 000000000..2b83e747a
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/subprocesses/Subprocess.swift
@@ -0,0 +1,159 @@
+//
+// Subprocess.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 17/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+
+
+public class Subprocess {
+ /// The path to the executable
+ let executablePath : String
+
+ /// Arguments to pass to the executable
+ let arguments : [String]
+
+ /// Working directory for the executable
+ let workingDirectory : String
+
+ /// Process to pipe to, if any
+ let pipeDestination : Subprocess?
+
+ public convenience init(
+ executablePath: String,
+ arguments: [String] = [],
+ workingDirectory: String = "."
+ ) {
+ self.init(executablePath: executablePath,
+ arguments: arguments,
+ workingDirectory: workingDirectory,
+ pipeTo: nil)
+ }
+
+ public init(
+ executablePath: String,
+ arguments: [String] = [],
+ workingDirectory: String = ".",
+ pipeTo: Subprocess?
+ ) {
+ self.executablePath = executablePath
+ self.arguments = arguments
+ self.workingDirectory = workingDirectory
+ self.pipeDestination = pipeTo
+ }
+
+ /**
+ Returns a subprocess ready to be executed
+ - SeeAlso: init(executablePath:arguments:workingDirectory)
+ */
+ public convenience init(
+ _ executablePath: String,
+ _ arguments: String...,
+ workingDirectory: String = ".") {
+ self.init(executablePath: executablePath, arguments: arguments, workingDirectory: workingDirectory, pipeTo: nil)
+ }
+}
+
+// Public API
+extension Subprocess {
+
+ /**
+ Executes the subprocess and wait for completition, returning the exit status
+ - returns: the termination status, or nil if it was not possible to execute the process
+ */
+ public func run() -> Int32? {
+ return self.execute(captureOutput: false)?.status
+ }
+
+ /**
+ Executes the subprocess and wait for completion, returning the output
+ - returns: the output of the process, or nil if it was not possible to execute the process
+ - warning: the entire output will be stored in a String in memory
+ */
+ public func output() -> String? {
+ return self.execute(captureOutput: true)?.output
+ }
+
+ /**
+ Executes the subprocess and wait for completition, returning the exit status
+ - returns: the execution result, or nil if it was not possible to execute the process
+ */
+ public func execute(captureOutput: Bool = false) -> ExecutionResult? {
+ return buildPipeline(captureOutput: captureOutput).run()
+ }
+}
+
+// Piping of STDIN, STDERR and STDOUT
+extension Subprocess {
+
+ /// Pipes the output to this process to another process.
+ /// Will return a new subprocess, you should execute that subprocess to
+ /// run the entire pipe
+ public func pipe(to destination: Subprocess) -> Subprocess {
+ let downstreamProcess : Subprocess
+ if let existingPipe = self.pipeDestination {
+ downstreamProcess = existingPipe.pipe(to: destination)
+ } else {
+ downstreamProcess = destination
+ }
+ return Subprocess(executablePath: self.executablePath, arguments: self.arguments, workingDirectory: self.workingDirectory, pipeTo: downstreamProcess)
+ }
+}
+
+public func | (lhs: Subprocess, rhs: Subprocess) -> Subprocess {
+ return lhs.pipe(to: rhs)
+}
+
+public func | (lhs: String, rhs: String) -> String {
+ return "(\(lhs)\(rhs))"
+}
+
+// MARK: - Process execution
+public enum SubprocessError : LocalizedError {
+ case Error(status: Int, message: String)
+}
+
+extension Subprocess {
+
+ /// Returns the task to execute
+ private func task() -> Process {
+ let task = Process()
+ task.launchPath = self.executablePath
+ task.arguments = self.arguments
+ task.currentDirectoryPath = self.workingDirectory
+ return task
+ }
+
+ /// Returns the task pipeline for all the downstream processes
+ public func buildPipeline(captureOutput: Bool, input: AnyObject? = nil) -> TaskPipeline {
+ let task = self.task()
+
+ if let inPipe = input {
+ task.standardInput = inPipe
+ }
+
+ if let downstreamProcess = self.pipeDestination {
+ let downstreamPipeline = downstreamProcess.buildPipeline(captureOutput: captureOutput, input: task.standardOutput as AnyObject)
+ return downstreamPipeline.addToHead(task: task)
+ }
+ return TaskPipeline(task: task, captureOutput: captureOutput)
+ }
+}
+
+// Description for pretty print etc.
+extension Subprocess : CustomStringConvertible {
+
+ public var description : String {
+ return self.executablePath
+ + (self.arguments.count > 0
+ ? " " + self.arguments
+ .map { $0.replace(target: "\\ ", withString: " ") }
+ .joined(separator: " ")
+ : ""
+ )
+ + (self.pipeDestination != nil ? " | " + self.pipeDestination!.description : "" )
+ }
+}
diff --git a/launchers/macosx/I2PLauncher/subprocesses/TaskPipeline.swift b/launchers/macosx/I2PLauncher/subprocesses/TaskPipeline.swift
new file mode 100644
index 000000000..a960dd3f4
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/subprocesses/TaskPipeline.swift
@@ -0,0 +1,113 @@
+//
+// TaskPipeline.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 17/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+
+
+// Extend the stdlib with the extension bellow
+extension Process {
+ /// Launches a task, captures any objective-c exception and relaunches it as Swift error
+ public func launchCapturingExceptions() throws {
+ if let exception = AppleStuffExecuteWithPossibleExceptionInBlock({
+ self.launch()
+ }) {
+ let reason = exception.reason ?? "unknown error"
+ throw SubprocessError.Error(status: -1, message: reason)
+ }
+ }
+}
+
+/// A pipeline of tasks, connected in a cascade pattern with pipes
+public struct TaskPipeline {
+
+ /// List of tasks in the pipeline
+ let tasks: [Process]
+
+ /// Output pipe
+ let outputPipe: Pipe?
+
+ /// Whether the pipeline should capture output to stdErr and stdOut
+ let captureOutput : Bool
+
+ /// Adds a task to the head of the pipeline, that is, the task will provide the input
+ /// for the first task currently on the head of the pipeline
+ func addToHead(task: Process) -> TaskPipeline {
+ guard let firstTask = tasks.first else {
+ fatalError("Expecting at least one task")
+ }
+ let inoutPipe = Pipe()
+ firstTask.standardInput = inoutPipe
+ task.standardOutput = inoutPipe
+
+ var errorPipe : Pipe?
+ if self.captureOutput {
+ errorPipe = Pipe()
+ task.standardError = errorPipe
+ }
+ return TaskPipeline(tasks: [task] + self.tasks, outputPipe: self.outputPipe, captureOutput: self.captureOutput)
+ }
+
+ /// Start all tasks in the pipeline, then wait for them to complete
+ /// - returns: the return status of the last process in the pipe, or nil if there was an error
+ func run() -> ExecutionResult? {
+
+ let runTasks = launchAndReturnNotFailedTasks()
+ if runTasks.count != self.tasks.count {
+ // dropped a task? it's because it failed to start, so error
+ return nil
+ }
+ runTasks.forEach { $0.waitUntilExit() }
+
+ // exit status
+ let exitStatuses = runTasks.map { $0.terminationStatus }
+ guard captureOutput else {
+ return ExecutionResult(pipelineStatuses: exitStatuses)
+ }
+
+ // output
+ let errorOutput = runTasks.map { task -> String in
+ guard let errorPipe = task.standardError as? Pipe else { return "" }
+ let readData = errorPipe.fileHandleForReading.readDataToEndOfFile()
+ return String(data: readData, encoding: String.Encoding.utf8)!
+ }
+ let output = String(data: self.outputPipe!.fileHandleForReading.readDataToEndOfFile(), encoding: String.Encoding.utf8)!
+ return ExecutionResult(pipelineStatuses: exitStatuses, pipelineErrors: errorOutput, output: output)
+ }
+
+ /// Run all tasks and return the tasks that did not fail to launch
+ private func launchAndReturnNotFailedTasks() -> [Process] {
+ return self.tasks.flatMap { task -> Process? in
+ do {
+ try task.launchCapturingExceptions()
+ return task
+ } catch {
+ return nil
+ }
+ }
+ }
+
+ init(task: Process, captureOutput: Bool) {
+ self.tasks = [task]
+ self.captureOutput = captureOutput
+ if captureOutput {
+ self.outputPipe = Pipe()
+ task.standardOutput = self.outputPipe
+ let errorPipe = Pipe()
+ task.standardError = errorPipe
+ } else {
+ self.outputPipe = nil
+ }
+ }
+
+ private init(tasks: [Process], outputPipe: Pipe?, captureOutput: Bool) {
+ self.tasks = tasks
+ self.outputPipe = outputPipe
+ self.captureOutput = captureOutput
+ }
+}
+
diff --git a/launchers/macosx/I2PLauncher/userinterface/LogViewController.swift b/launchers/macosx/I2PLauncher/userinterface/LogViewController.swift
new file mode 100644
index 000000000..b8abcfc7c
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/userinterface/LogViewController.swift
@@ -0,0 +1,24 @@
+//
+// LogViewController.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 18/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+import AppKit
+
+class LogViewerViewController : NSTabViewItem {
+
+ @IBOutlet var scrollView: NSScrollView?
+ @IBOutlet var textFieldView: NSTextView?
+
+
+
+}
+
+class LogViewController {
+
+}
+
diff --git a/launchers/macosx/I2PLauncher/userinterface/PopoverViewController.swift b/launchers/macosx/I2PLauncher/userinterface/PopoverViewController.swift
new file mode 100644
index 000000000..3c1923e4f
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/userinterface/PopoverViewController.swift
@@ -0,0 +1,118 @@
+//
+// PopoverViewController.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 18/09/2018.
+// Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Cocoa
+
+class PopoverViewController: NSViewController {
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ //super.init(nibName: "UserInterfaces", bundle: Bundle.main)!
+ //let nib = NSNib(nibNamed: "UserInterfaces", bundle: Bundle.main)
+
+ }
+
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ // Do view setup here.
+ }
+
+
+
+}
+
+@objc class RouterStatusView : NSView {
+ static var instance: RouterStatusView?
+
+ static func getInstance() -> RouterStatusView? {
+ if (self.instance != Optional.none) {
+ return RouterStatusView.instance
+ }
+ return Optional.none
+ }
+
+ @IBOutlet var routerStatusLabel: NSTextField?
+ @IBOutlet var routerVersionLabel: NSTextField?
+ @IBOutlet var routerStartedByLabel: NSTextField?
+ @IBOutlet var routerUptimeLabel: NSTextField?
+
+ @IBOutlet var quickControlView: NSView?
+ @IBOutlet var routerStartStopButton: NSButton?
+
+
+ @objc func actionBtnStartRouter(_ sender: Any?) {
+ NSLog("START ROUTER")
+ (sender as! NSButton).cell?.stringValue = "Stop Router"
+ let timeWhenStarted = Date()
+ RouterProcessStatus.routerStartedAt = timeWhenStarted
+ SwiftMainDelegate.objCBridge.startupI2PRouter(RouterProcessStatus.i2pDirectoryPath, javaBinPath: RouterProcessStatus.knownJavaBinPath!)
+ }
+ @objc func actionBtnStopRouter(_ sender: Any?) {
+ NSLog("STOP ROUTER")
+ }
+ @objc func actionBtnRestartRouter(sender: Any?) {}
+
+ override func viewWillDraw() {
+ super.viewWillDraw()
+ if (RouterStatusView.instance == Optional.none) {
+ RouterStatusView.instance = self
+ }
+ self.setRouterStatusLabelText()
+ }
+
+ func setRouterStatusLabelText() {
+ if (RouterProcessStatus.isRouterRunning) {
+ routerStatusLabel?.cell?.stringValue = "Router status: Running"
+ routerStartStopButton?.action = #selector(self.actionBtnStopRouter(_:))
+ } else {
+ routerStatusLabel?.cell?.stringValue = "Router status: Not running"
+ routerStartStopButton?.action = #selector(self.actionBtnStartRouter(_:))
+ }
+ routerStartStopButton?.needsDisplay = true
+ routerStartStopButton?.target = self
+ quickControlView?.needsDisplay = true
+
+ if (RouterProcessStatus.routerVersion?.isEmpty)! {
+ routerVersionLabel?.cell?.stringValue = "Router version: Still unknown"
+ } else {
+ routerVersionLabel?.cell?.stringValue = "Router version: " + RouterProcessStatus.routerVersion!
+ }
+ if (RouterProcessStatus.routerStartedAt != Optional.none) {
+ routerUptimeLabel?.cell?.stringValue = "Router has runned for " + DateTimeUtils.timeAgoSinceDate(date: NSDate(date: RouterProcessStatus.routerStartedAt!), numericDates: false)
+ }
+ }
+
+ init() {
+ let c = NSCoder()
+ super.init(coder: c)!
+ self.setRouterStatusLabelText()
+ }
+
+ required init?(coder decoder: NSCoder) {
+ super.init(coder: decoder)
+ self.setRouterStatusLabelText()
+ }
+
+}
+
+
+extension PopoverViewController {
+ static func freshController() -> PopoverViewController {
+ let storyboard = NSStoryboard(name: "Storyboard", bundle: Bundle.main)
+ //2.
+ let identifier = NSStoryboard.SceneIdentifier(string: "PopoverView")
+ //3.
+ guard let viewcontroller = storyboard.instantiateController(withIdentifier: identifier as String) as? PopoverViewController else {
+ fatalError("Why cant i find PopoverViewController? - Check PopoverViewController.storyboard")
+ }
+ //let viewcontroller = PopoverViewController()
+ return viewcontroller
+ }
+}
+
diff --git a/launchers/macosx/I2PLauncher/userinterface/StatusBarController.swift b/launchers/macosx/I2PLauncher/userinterface/StatusBarController.swift
new file mode 100644
index 000000000..fd28d54f0
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/userinterface/StatusBarController.swift
@@ -0,0 +1,120 @@
+//
+// StatusBarController.swift
+// I2PLauncher
+//
+// Created by Mikal Villa on 13/03/2018.
+// Copyright © 2018 I2P. All rights reserved.
+//
+
+import Foundation
+import Cocoa
+
+
+@objc class StatusBarController: NSObject, NSMenuDelegate {
+
+ let popover = NSPopover()
+ let statusItem = NSStatusBar.system().statusItem(withLength: NSVariableStatusItemLength)
+ //let storyboard = NSStoryboard(name: "Storyboard", bundle: nil)
+
+ @objc func handleOpenConsole(_ sender: Any?) {
+ SwiftMainDelegate.openLink(url: "http://localhost:7657")
+ }
+
+ @objc func constructMenu() -> NSMenu {
+ let menu = NSMenu()
+ let sb = SwiftMainDelegate.objCBridge
+
+ menu.addItem(NSMenuItem(title: "Open I2P Console", action: #selector(self.handleOpenConsole(_:)), keyEquivalent: "O"))
+ menu.addItem(NSMenuItem.separator())
+ menu.addItem(NSMenuItem(title: "Quit I2P Launcher", action: #selector(SwiftMainDelegate.terminate(_:)), keyEquivalent: "q"))
+
+ return menu
+ }
+
+
+ override init() {
+ super.init()//(xib: "UserInterface", bundle: nil)
+ popover.contentViewController = PopoverViewController.freshController()
+
+ if let button = statusItem.button {
+ button.image = NSImage(named:"StatusBarButtonImage")
+ //button.title = "I2P"
+ button.toolTip = "I2P Launch Manager"
+ //button.isVisible = true
+ //button.action = #selector(self.statusBarButtonClicked)
+ //button.sendAction(on: [.leftMouseUp, .rightMouseUp])
+ //button.doubleAction = #selector(self.systemBarIconDoubleClick)
+ button.target = self
+ button.action = #selector(self.statusBarButtonClicked(sender:))
+ button.sendAction(on: [.leftMouseUp, .rightMouseUp])
+ }
+ }
+
+ @IBAction func openConsoleClicked(_ sender: Any) {
+ NSLog("openConsoleClicked got clicked")
+ let realSender = sender as! NSMenuItem
+ NSWorkspace.shared().open(URL(string: "http://127.0.0.1:7657")!)
+ NSLog("Sender: @%", realSender)
+ }
+
+ @IBAction func quitClicked(_ sender: NSMenuItem) {
+ NSApplication.shared().terminate(self)
+ }
+
+ // Submenu
+
+ @IBAction func startRouterClicked(_ sender: NSMenuItem) {
+
+ }
+
+ @IBAction func restartRouterClicked(_ sender: NSMenuItem) {
+
+ }
+
+ @IBAction func stopRouterClicked(_ sender: NSMenuItem) {
+
+ }
+
+ func statusBarButtonClicked(sender: NSStatusBarButton) {
+ let event = NSApp.currentEvent!
+
+ if event.type == NSEventType.rightMouseUp {
+ closePopover(sender: nil)
+
+ let ctxMenu = constructMenu()
+
+ statusItem.menu = ctxMenu
+ statusItem.popUpMenu(ctxMenu)
+
+ // This is critical, otherwise clicks won't be processed again
+ statusItem.menu = nil
+ } else {
+ togglePopover(sender: nil)
+ }
+ }
+
+ func togglePopover(sender: AnyObject?) {
+ if popover.isShown {
+ closePopover(sender: sender)
+ } else {
+ showPopover(sender: sender)
+ }
+ }
+
+ func showPopover(sender: AnyObject?) {
+ if let button = statusItem.button {
+ let inst = RouterStatusView.getInstance()
+ if (inst != nil) {
+ if (inst != Optional.none) { RouterStatusView.getInstance()?.setRouterStatusLabelText() }
+ }
+ popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
+ }
+ }
+
+ func closePopover(sender: AnyObject?) {
+ popover.performClose(sender)
+ }
+
+
+}
+
diff --git a/launchers/macosx/I2PLauncher/ItoopieTransparent.png b/launchers/macosx/ItoopieTransparent.png
similarity index 100%
rename from launchers/macosx/I2PLauncher/ItoopieTransparent.png
rename to launchers/macosx/ItoopieTransparent.png
diff --git a/launchers/macosx/I2PLauncher/JavaHelper.h b/launchers/macosx/JavaHelper.h
similarity index 100%
rename from launchers/macosx/I2PLauncher/JavaHelper.h
rename to launchers/macosx/JavaHelper.h
diff --git a/launchers/macosx/I2PLauncher/RouterTask.h b/launchers/macosx/RouterTask.h
similarity index 100%
rename from launchers/macosx/I2PLauncher/RouterTask.h
rename to launchers/macosx/RouterTask.h
diff --git a/launchers/macosx/I2PLauncher/RouterTask.mm b/launchers/macosx/RouterTask.mm
similarity index 100%
rename from launchers/macosx/I2PLauncher/RouterTask.mm
rename to launchers/macosx/RouterTask.mm
diff --git a/launchers/macosx/I2PLauncher/fullBuild.sh b/launchers/macosx/fullBuild.sh
similarity index 100%
rename from launchers/macosx/I2PLauncher/fullBuild.sh
rename to launchers/macosx/fullBuild.sh
diff --git a/launchers/macosx/I2PLauncher/images/128x128-Itoopie Transparent.png b/launchers/macosx/images/128x128-Itoopie Transparent.png
similarity index 100%
rename from launchers/macosx/I2PLauncher/images/128x128-Itoopie Transparent.png
rename to launchers/macosx/images/128x128-Itoopie Transparent.png
diff --git a/launchers/macosx/I2PLauncher/images/128x128-Itoopie Transparent@2x.png b/launchers/macosx/images/128x128-Itoopie Transparent@2x.png
similarity index 100%
rename from launchers/macosx/I2PLauncher/images/128x128-Itoopie Transparent@2x.png
rename to launchers/macosx/images/128x128-Itoopie Transparent@2x.png
diff --git a/launchers/macosx/I2PLauncher/images/16x16-Itoopie Transparent@2x.png b/launchers/macosx/images/16x16-Itoopie Transparent@2x.png
similarity index 100%
rename from launchers/macosx/I2PLauncher/images/16x16-Itoopie Transparent@2x.png
rename to launchers/macosx/images/16x16-Itoopie Transparent@2x.png
diff --git a/launchers/macosx/I2PLauncher/images/256x256-Itoopie Transparent.png b/launchers/macosx/images/256x256-Itoopie Transparent.png
similarity index 100%
rename from launchers/macosx/I2PLauncher/images/256x256-Itoopie Transparent.png
rename to launchers/macosx/images/256x256-Itoopie Transparent.png
diff --git a/launchers/macosx/I2PLauncher/images/256x256-Itoopie Transparent@2x.png b/launchers/macosx/images/256x256-Itoopie Transparent@2x.png
similarity index 100%
rename from launchers/macosx/I2PLauncher/images/256x256-Itoopie Transparent@2x.png
rename to launchers/macosx/images/256x256-Itoopie Transparent@2x.png
diff --git a/launchers/macosx/I2PLauncher/images/32x32-Itoopie Transparent.png b/launchers/macosx/images/32x32-Itoopie Transparent.png
similarity index 100%
rename from launchers/macosx/I2PLauncher/images/32x32-Itoopie Transparent.png
rename to launchers/macosx/images/32x32-Itoopie Transparent.png
diff --git a/launchers/macosx/I2PLauncher/images/32x32-Itoopie Transparent@2x.png b/launchers/macosx/images/32x32-Itoopie Transparent@2x.png
similarity index 100%
rename from launchers/macosx/I2PLauncher/images/32x32-Itoopie Transparent@2x.png
rename to launchers/macosx/images/32x32-Itoopie Transparent@2x.png
diff --git a/launchers/macosx/I2PLauncher/images/512x512-Itoopie Transparent.png b/launchers/macosx/images/512x512-Itoopie Transparent.png
similarity index 100%
rename from launchers/macosx/I2PLauncher/images/512x512-Itoopie Transparent.png
rename to launchers/macosx/images/512x512-Itoopie Transparent.png
diff --git a/launchers/macosx/I2PLauncher/images/512x512-Itoopie Transparent@2x.png b/launchers/macosx/images/512x512-Itoopie Transparent@2x.png
similarity index 100%
rename from launchers/macosx/I2PLauncher/images/512x512-Itoopie Transparent@2x.png
rename to launchers/macosx/images/512x512-Itoopie Transparent@2x.png
diff --git a/launchers/macosx/I2PLauncher/images/AppIcon.icns b/launchers/macosx/images/AppIcon.icns
similarity index 100%
rename from launchers/macosx/I2PLauncher/images/AppIcon.icns
rename to launchers/macosx/images/AppIcon.icns
diff --git a/launchers/macosx/I2PLauncher/PidWatcher.h b/launchers/macosx/include/PidWatcher.h
similarity index 100%
rename from launchers/macosx/I2PLauncher/PidWatcher.h
rename to launchers/macosx/include/PidWatcher.h
diff --git a/launchers/macosx/I2PLauncher/portcheck.h b/launchers/macosx/include/portcheck.h
similarity index 100%
rename from launchers/macosx/I2PLauncher/portcheck.h
rename to launchers/macosx/include/portcheck.h
diff --git a/launchers/macosx/I2PLauncher/main.mm b/launchers/macosx/main.mm
similarity index 100%
rename from launchers/macosx/I2PLauncher/main.mm
rename to launchers/macosx/main.mm