Adding all new code, removed a lot obsolete code and fixed import paths etc.
Mac OS X launcher: * UI built on Swift * Why? * Apple seems to on purpose make it harder to get into Objective-C these days * Swift is compiled to native code, but has easiness of Javascript in programming * Perfect for the OS X UI, many guides & tutorials as well * "Backend" in Objective-C++ / C++14 * Why? * Originally written in Objective-C / C++14 with C++17 backports * Only for backend because of the time the development takes * Short summary of features: * Java * It can detect java from: * JAVA_HOME environment variable * "Internet Plug-Ins" Apple stuff * By the /usr/libexec/java_home binary helper * It can unpack a new version of I2P * Unpacks to ~/Library/I2P * Can check currently unpacked version in ~/Library/I2P via i2p.jar's "net.i2p.CoreVersion" * User Interface (a popover, see https://youtu.be/k8L3lQ5rUq0 for example of this concept) * Router control tab view * It can start the router * It can stop the router * It can detect already running router, then avoid fireing up one * It can show basic information about the router state & version * Log view tab (not yet done) * While left-click triggers popover, right-click draws a minimal context menu
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
40
launchers/macosx/I2PLauncher/Base.lproj/UserInterfaces.xib
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate"/>
|
||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||
<menu id="TTS-U7-WkT" userLabel="statusBarContextMenu">
|
||||
<items>
|
||||
<menuItem title="Open Console" id="cOe-UL-1TW">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="openConsoleClicked:" target="-1" id="uR3-lb-ikG"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Start I2P Router" id="RQ8-Q2-68A">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="startRouterClicked:" target="-1" id="Vl3-cC-77e"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Exit" id="hsL-CH-m3C">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="quickClicked:" target="-1" id="hiW-5d-nBX"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
<point key="canvasLocation" x="17" y="167"/>
|
||||
</menu>
|
||||
</objects>
|
||||
</document>
|
@ -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"
|
@ -3,47 +3,53 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>I2PLauncher</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Public Domain</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>0.9.35-experimental</string>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>images/AppIcon.icns</string>
|
||||
<string></string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>net.i2p.launcher</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>I2P</string>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.0.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>I2P</string>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLIconFile</key>
|
||||
<string>ItoopieTransparent</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>http+i2p</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>http+i2p</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.0.1</string>
|
||||
<key>NSUserNotificationAlertStyle</key>
|
||||
<string>alert</string>
|
||||
<string>1</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>LSUIElement</key>
|
||||
<true/>
|
||||
<key>NSAppleScriptEnabled</key>
|
||||
<true/>
|
||||
<key>CGDisableCoalescedUpdates</key>
|
||||
<true/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.5</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>I2P</string>
|
||||
<key>LSMinimumSystemVersionByArchitecture</key>
|
||||
<dict>
|
||||
<key>i386</key>
|
||||
<string>10.5.0</string>
|
||||
<key>x86_64</key>
|
||||
<string>10.6.0</string>
|
||||
</dict>
|
||||
<key>LSUIElement</key>
|
||||
<string>1</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2018 The I2P Project. All rights reserved.</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>UserInterfaces</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSServices</key>
|
||||
<array>
|
||||
<dict/>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -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`
|
162
launchers/macosx/I2PLauncher/Storyboard.storyboard
Normal file
@ -0,0 +1,162 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
<capability name="box content view" minToolsVersion="7.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--PopoverController-->
|
||||
<scene sceneID="4eF-i8-6L0">
|
||||
<objects>
|
||||
<viewController identifier="PopoverView" storyboardIdentifier="PopoverView" id="Ii7-Rb-Ls8" userLabel="PopoverController" customClass="PopoverViewController" customModule="I2PLauncher" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tabView key="view" allowsTruncatedLabels="NO" initialItem="Fle-u6-lgB" id="dmf-Go-6qY">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<tabViewItems>
|
||||
<tabViewItem label="Router Status & Control" identifier="" id="Fle-u6-lgB">
|
||||
<view key="view" canDrawConcurrently="YES" id="xXg-Bt-RWR" customClass="RouterStatusView" customModule="I2PLauncher" customModuleProvider="target">
|
||||
<rect key="frame" x="10" y="33" width="430" height="254"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<box fixedFrame="YES" title="Router information and status" translatesAutoresizingMaskIntoConstraints="NO" id="e8C-qI-SCp">
|
||||
<rect key="frame" x="6" y="16" width="279" height="231"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<view key="contentView" id="zBB-wE-VXr">
|
||||
<rect key="frame" x="2" y="2" width="275" height="214"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField identifier="RouterStatusLabel" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0Eo-re-5WK" userLabel="RouterStatusLabel">
|
||||
<rect key="frame" x="6" y="190" width="245" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Router status: Unknown" id="m7V-Se-tnf">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField identifier="RouterVersionLabel" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="sGy-NV-gmH" userLabel="RouterVersionLabel">
|
||||
<rect key="frame" x="6" y="171" width="245" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Router version: Unknown" id="Mda-Os-8O9">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField identifier="RouterStartedByLabel" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tgG-IT-Ojg" userLabel="RouterStartedByLabel">
|
||||
<rect key="frame" x="6" y="152" width="245" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Router started by launcher? No" id="WBg-nH-kwu">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField identifier="RouterUptimeLabel" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="f2W-Kc-cxB" userLabel="RouterUptimeLabel">
|
||||
<rect key="frame" x="6" y="133" width="245" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Router uptime: Unknown" id="uQ0-cW-tYL">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
</view>
|
||||
</box>
|
||||
<box fixedFrame="YES" title="Quick Control" translatesAutoresizingMaskIntoConstraints="NO" id="IIP-Qi-7dp">
|
||||
<rect key="frame" x="287" y="16" width="150" height="231"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<view key="contentView" identifier="QuickControlView" id="D8V-d8-0wT">
|
||||
<rect key="frame" x="2" y="2" width="146" height="214"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button identifier="startstopRouterButton" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1eu-Qw-TD9" userLabel="start-stop-button">
|
||||
<rect key="frame" x="0.0" y="176" width="147" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Start/Stop Router" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="OYN-sS-eQH">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<button identifier="restartRouterButton" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C1m-1t-Xiq" userLabel="restart-button">
|
||||
<rect key="frame" x="10" y="150" width="128" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Restart Router" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sih-uF-V06">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<button identifier="openConsoleButton" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XZi-oz-5gy" userLabel="open-console-button">
|
||||
<rect key="frame" x="11" y="123" width="126" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Open Console" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Yh8-lj-JFi">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
</subviews>
|
||||
</view>
|
||||
</box>
|
||||
</subviews>
|
||||
<connections>
|
||||
<outlet property="quickControlView" destination="D8V-d8-0wT" id="4to-rN-2eL"/>
|
||||
<outlet property="routerStartStopButton" destination="1eu-Qw-TD9" id="FLt-MK-y5u"/>
|
||||
<outlet property="routerStartedByLabel" destination="tgG-IT-Ojg" id="dA0-3w-cuF"/>
|
||||
<outlet property="routerStatusLabel" destination="0Eo-re-5WK" id="7dm-Et-eSn"/>
|
||||
<outlet property="routerUptimeLabel" destination="f2W-Kc-cxB" id="4Ya-qU-eb3"/>
|
||||
<outlet property="routerVersionLabel" destination="sGy-NV-gmH" id="tM5-4M-DKy"/>
|
||||
</connections>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Router Log Viewer" identifier="" id="IFq-CR-cjD" customClass="LogViewerViewController" customModule="I2PLauncher" customModuleProvider="target">
|
||||
<view key="view" id="7U9-d7-IVr">
|
||||
<rect key="frame" x="10" y="33" width="430" height="254"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<scrollView identifier="LoggerTextScrollView" fixedFrame="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jca-Kn-cO2" userLabel="LoggerTextScrollView">
|
||||
<rect key="frame" x="11" y="17" width="409" height="228"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<clipView key="contentView" ambiguous="YES" id="E5l-WA-qOn">
|
||||
<rect key="frame" x="1" y="1" width="407" height="226"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView identifier="LoggerTextView" ambiguous="YES" importsGraphics="NO" verticallyResizable="YES" usesFontPanel="YES" findStyle="panel" continuousSpellChecking="YES" allowsUndo="YES" usesRuler="YES" allowsNonContiguousLayout="YES" quoteSubstitution="YES" dashSubstitution="YES" spellingCorrection="YES" smartInsertDelete="YES" id="bgQ-8i-Xgb" userLabel="LoggerTextView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="407" height="226"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<size key="minSize" width="407" height="226"/>
|
||||
<size key="maxSize" width="463" height="10000000"/>
|
||||
<color key="insertionPointColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="MRF-Wt-zdZ">
|
||||
<rect key="frame" x="-100" y="-100" width="87" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="Xq6-ur-WuT">
|
||||
<rect key="frame" x="392" y="1" width="16" height="226"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="scrollView" destination="jca-Kn-cO2" id="qAi-hi-PsC"/>
|
||||
<outlet property="textFieldView" destination="bgQ-8i-Xgb" id="SbC-0r-xPR"/>
|
||||
</connections>
|
||||
</tabViewItem>
|
||||
</tabViewItems>
|
||||
</tabView>
|
||||
</viewController>
|
||||
<customObject id="d8g-wS-Zts" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-823" y="166"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
104
launchers/macosx/I2PLauncher/SwiftMainDelegate.swift
Normal file
@ -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!)
|
||||
}
|
||||
}
|
||||
|
29
launchers/macosx/I2PLauncher/Utils/ArrayExtensions.swift
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
87
launchers/macosx/I2PLauncher/Utils/DateTimeUtils.swift
Normal file
@ -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<Calendar.Component> = [.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"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
37
launchers/macosx/I2PLauncher/Utils/EventMonitor.swift
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
57
launchers/macosx/I2PLauncher/Utils/ReflectionFunctions.swift
Normal file
@ -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<T>(array: UnsafePointer<T>, count: UInt32, f: (UInt32, T) -> ()) {
|
||||
var ptr = array
|
||||
for i in 0..<count {
|
||||
f(i, ptr.pointee)
|
||||
ptr = ptr.successor()
|
||||
}
|
||||
}
|
||||
|
||||
/// Return name for a method
|
||||
func methodName(m: Method) -> 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
35
launchers/macosx/I2PLauncher/Utils/StringExtensions.swift
Normal file
@ -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<Character>([" ", "\n", "\t"])
|
||||
return self.split { whitespaces.contains($0) }.map(String.init)
|
||||
}
|
||||
|
||||
public func splitByColon() -> [String] {
|
||||
return self.split { $0 == ":" }.map(String.init)
|
||||
}
|
||||
}
|
||||
|
136
launchers/macosx/I2PLauncher/routermgmt/DetectJava.swift
Normal file
@ -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")
|
||||
}
|
||||
|
||||
|
||||
}
|
32
launchers/macosx/I2PLauncher/routermgmt/I2PSubprocess.swift
Normal file
@ -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
|
||||
}
|
||||
}
|
28
launchers/macosx/I2PLauncher/routermgmt/RouterDeployer.swift
Normal file
@ -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"
|
||||
]
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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<sockaddr_in>.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)"
|
||||
}
|
||||
}
|
||||
|
69
launchers/macosx/I2PLauncher/routermgmt/RouterRunner.swift
Normal file
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
//
|
||||
// AppleStuffExceptionHandler.h
|
||||
// I2PLauncher
|
||||
//
|
||||
// Created by Mikal Villa on 17/09/2018.
|
||||
// Copyright © 2018 The I2P Project. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
extern NSException* __nullable AppleStuffExecuteWithPossibleExceptionInBlock(dispatch_block_t _Nonnull block);
|
||||
|
@ -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;
|
||||
}
|
22
launchers/macosx/I2PLauncher/subprocesses/Error.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
159
launchers/macosx/I2PLauncher/subprocesses/Subprocess.swift
Normal file
@ -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 : "" )
|
||||
}
|
||||
}
|
113
launchers/macosx/I2PLauncher/subprocesses/TaskPipeline.swift
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB |