diff --git a/launchers/macosx/I2PLauncher.xcodeproj/project.pbxproj b/launchers/macosx/I2PLauncher.xcodeproj/project.pbxproj index fd43e932e..8241347b7 100644 --- a/launchers/macosx/I2PLauncher.xcodeproj/project.pbxproj +++ b/launchers/macosx/I2PLauncher.xcodeproj/project.pbxproj @@ -43,6 +43,9 @@ BFBDCB0021505BEE0014EB07 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFBDCAFF21505BED0014EB07 /* AppKit.framework */; }; BFBDCB02215060190014EB07 /* DetectJava.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBDCB01215060190014EB07 /* DetectJava.swift */; }; BFBDCB04215060970014EB07 /* StatusBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBDCB03215060970014EB07 /* StatusBarController.swift */; }; + BFDD81DA2156B3E30014EB07 /* RouterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDD81D92156B3E30014EB07 /* RouterManager.swift */; }; + BFE16BF82156C61E0014EB07 /* RouterStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE16BF72156C61E0014EB07 /* RouterStatusView.swift */; }; + BFE16BFA2156DAED0014EB07 /* EventManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE16BF92156DAED0014EB07 /* EventManager.swift */; }; BFE1CBAD2151908F0014EB07 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFE1CBAC2151908F0014EB07 /* CoreFoundation.framework */; }; BFF4581C213C48EA0014EB07 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF4581B213C48EA0014EB07 /* EventMonitor.swift */; }; /* End PBXBuildFile section */ @@ -94,6 +97,10 @@ BFBDCAFF21505BED0014EB07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; BFBDCB01215060190014EB07 /* DetectJava.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectJava.swift; sourceTree = ""; }; BFBDCB03215060970014EB07 /* StatusBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarController.swift; sourceTree = ""; }; + BFDD81D92156B3E30014EB07 /* RouterManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouterManager.swift; sourceTree = ""; }; + BFE16BF72156C61E0014EB07 /* RouterStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouterStatusView.swift; sourceTree = ""; }; + BFE16BF92156DAED0014EB07 /* EventManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventManager.swift; sourceTree = ""; }; + BFE16BFB2156E94E0014EB07 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = ../../../Sparkle/build/Release/Sparkle.framework; sourceTree = ""; }; BFE1CBAC2151908F0014EB07 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; BFF45818213C428E0014EB07 /* I2PLauncher-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "I2PLauncher-Bridging-Header.h"; sourceTree = ""; }; BFF4581B213C48EA0014EB07 /* EventMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMonitor.swift; sourceTree = ""; }; @@ -134,6 +141,7 @@ BF07789C21506D2B0014EB07 /* PopoverViewController.swift */, BFBDCB03215060970014EB07 /* StatusBarController.swift */, BF531514215105B40014EB07 /* LogViewController.swift */, + BFE16BF72156C61E0014EB07 /* RouterStatusView.swift */, ); path = userinterface; sourceTree = ""; @@ -191,6 +199,7 @@ BF5061922113C6ED0014EB07 /* Frameworks */ = { isa = PBXGroup; children = ( + BFE16BFB2156E94E0014EB07 /* Sparkle.framework */, BFE1CBAC2151908F0014EB07 /* CoreFoundation.framework */, BF865416215182820014EB07 /* Foundation.framework */, BF865414215180F60014EB07 /* libswiftDarwin.tbd */, @@ -209,6 +218,7 @@ BF5315062150C55B0014EB07 /* RouterRunner.swift */, BF5315082150C6760014EB07 /* RouterDeployer.swift */, BF53150A2150C6E80014EB07 /* I2PSubprocess.swift */, + BFDD81D92156B3E30014EB07 /* RouterManager.swift */, ); path = routermgmt; sourceTree = ""; @@ -221,6 +231,7 @@ BFBDCAF52150428D0014EB07 /* StringExtensions.swift */, BFBDCAF7215047FE0014EB07 /* ArrayExtensions.swift */, BF53150C2150CE310014EB07 /* DateTimeUtils.swift */, + BFE16BF92156DAED0014EB07 /* EventManager.swift */, ); path = Utils; sourceTree = ""; @@ -334,8 +345,10 @@ BFBDCB04215060970014EB07 /* StatusBarController.swift in Sources */, BFBDCAF8215047FE0014EB07 /* ArrayExtensions.swift in Sources */, BF5315072150C55B0014EB07 /* RouterRunner.swift in Sources */, + BFE16BFA2156DAED0014EB07 /* EventManager.swift in Sources */, BFBDCAF12150420C0014EB07 /* ExecutionResult.swift in Sources */, BF5315092150C6760014EB07 /* RouterDeployer.swift in Sources */, + BFE16BF82156C61E0014EB07 /* RouterStatusView.swift in Sources */, BFBDCAEF215041E30014EB07 /* Error.swift in Sources */, BF1EFA41215141110014EB07 /* RouterTask.mm in Sources */, BF7506CB21509CFD0014EB07 /* RouterProcessStatus.swift in Sources */, @@ -350,6 +363,7 @@ BF531515215105B40014EB07 /* LogViewController.swift in Sources */, BF5315132150EB510014EB07 /* RouterProcessStatus+ObjectiveC.swift in Sources */, BFBDCAFE2150567D0014EB07 /* SwiftMainDelegate.swift in Sources */, + BFDD81DA2156B3E30014EB07 /* RouterManager.swift in Sources */, BF53150B2150C6E80014EB07 /* I2PSubprocess.swift in Sources */, BFF4581C213C48EA0014EB07 /* EventMonitor.swift in Sources */, BF1EFA3A215140E60014EB07 /* SBridge.mm in Sources */, diff --git a/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift b/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift index b5b60bf1c..daa600680 100644 --- a/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift +++ b/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift @@ -11,10 +11,9 @@ import Cocoa @objc class SwiftMainDelegate : NSObject { - //let statusItem = NSStatusBar.system().statusItem(withLength: NSSquareStatusItemLength ) let statusBarController = StatusBarController() + let sharedRouterMgmr = RouterManager.shared() static let javaDetector = DetectJava() - static let objCBridge = SBridge() override init() { super.init() @@ -37,10 +36,7 @@ import Cocoa } else { RouterProcessStatus.isRouterRunning = false print("I2P Router seems to NOT be running") - } - - } // End of init() @objc func findInstalledI2PVersion() { @@ -63,13 +59,12 @@ import Cocoa 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: "") NSLog("I2P version detected: %@",i2pVersion ?? "Unknown") RouterProcessStatus.routerVersion = i2pVersion + RouterManager.shared().eventManager.trigger(eventName: "router_version", information: i2pVersion) } else { - print("did NOT captured output") - + print("Warning: Version Detection did NOT captured output") } } @@ -78,21 +73,11 @@ import Cocoa var i2pPath = NSHomeDirectory() i2pPath += "/Library/I2P" - 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() } @objc static func openLink(url: String) { - objCBridge.openUrl(url) + SBridge.sharedInstance().openUrl(url) } @objc func applicationWillTerminate() { diff --git a/launchers/macosx/I2PLauncher/Utils/EventManager.swift b/launchers/macosx/I2PLauncher/Utils/EventManager.swift new file mode 100644 index 000000000..e519d8cce --- /dev/null +++ b/launchers/macosx/I2PLauncher/Utils/EventManager.swift @@ -0,0 +1,83 @@ +// +// EventManager.swift +// I2PLauncher +// +// Created by Mikal Villa on 22/09/2018. +// Copyright © 2018 The I2P Project. All rights reserved. +// + +import Foundation + +class EventManager { + var listeners = Dictionary(); + + // Create a new event listener, not expecting information from the trigger + // @param eventName: Matching trigger eventNames will cause this listener to fire + // @param action: The function/lambda you want executed when the event triggers + func listenTo(eventName:String, action: @escaping (()->())) { + let newListener = EventListenerAction(callback: action) + addListener(eventName: eventName, newEventListener: newListener) + } + + // Create a new event listener, expecting information from the trigger + // @param eventName: Matching trigger eventNames will cause this listener to fire + // @param action: The function/lambda you want executed when the event triggers + func listenTo(eventName:String, action: @escaping ((Any?)->())) { + let newListener = EventListenerAction(callback: action) + addListener(eventName: eventName, newEventListener: newListener) + } + + internal func addListener(eventName:String, newEventListener:EventListenerAction) { + if let listenerArray = self.listeners[eventName] { + listenerArray.add(newEventListener) + } else { + self.listeners[eventName] = [newEventListener] as NSMutableArray + } + } + + // Removes all listeners by default, or specific listeners through paramters + // @param eventName: If an event name is passed, only listeners for that event will be removed + func removeListeners(eventNameToRemoveOrNil:String?) { + if let eventNameToRemove = eventNameToRemoveOrNil { + if let actionArray = self.listeners[eventNameToRemove] { + actionArray.removeAllObjects() + } + } else { + self.listeners.removeAll(keepingCapacity: false) + } + } + + // Triggers an event + // @param eventName: Matching listener eventNames will fire when this is called + // @param information: pass values to your listeners + func trigger(eventName:String, information:Any? = nil) { + if let actionObjects = self.listeners[eventName] { + for actionObject in actionObjects { + if let actionToPerform = actionObject as? EventListenerAction { + if let methodToCall = actionToPerform.actionExpectsInfo { + methodToCall(information) + } + else if let methodToCall = actionToPerform.action { + methodToCall() + } + } + } + } + } +} + +// Class to hold actions to live in NSMutableArray +class EventListenerAction { + let action:(() -> ())? + let actionExpectsInfo:((Any?) -> ())? + + init(callback: @escaping (() -> ()) ) { + self.action = callback + self.actionExpectsInfo = nil + } + + init(callback: @escaping ((Any?) -> ()) ) { + self.actionExpectsInfo = callback + self.action = nil + } +} diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift new file mode 100644 index 000000000..402516c9a --- /dev/null +++ b/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift @@ -0,0 +1,99 @@ +// +// RouterManager.swift +// I2PLauncher +// +// Created by Mikal Villa on 22/09/2018. +// Copyright © 2018 The I2P Project. All rights reserved. +// + +import Foundation + +class RouterManager : NSObject { + + // MARK: - Properties + + static let packedVersion : String = "0.9.36" + + let eventManager = EventManager() + + var logViewStorage: NSTextStorage? + + private static func handleRouterStart(information:Any?) { + NSLog("event! - handle router start") + RouterProcessStatus.routerStartedAt = Date() + RouterProcessStatus.isRouterChildProcess = true + RouterProcessStatus.isRouterRunning = true + } + private static func handleRouterStop(information:Any?) { + NSLog("event! - handle router stop") + RouterProcessStatus.routerStartedAt = nil + RouterProcessStatus.isRouterChildProcess = false + RouterProcessStatus.isRouterRunning = false + } + private static func handleRouterPid(information:Any?) { + Swift.print("event! - handle router pid: %s", information ?? "") + } + private static func handleRouterVersion(information:Any?) { + Swift.print("event! - handle router version: %s", information ?? "") + let currentVersion : String = information as! String + if (packedVersion.compare(currentVersion, options: .numeric) == .orderedDescending) { + Swift.print("event! - router version: Packed version is newer, gonna re-deploy") + } else { + Swift.print("event! - router version: No update needed") + RouterManager.shared().eventManager.trigger(eventName: "router_can_start", information: "all ok") + } + } + + private static var sharedRouterManager: RouterManager = { + let inst = DetectJava() + let routerManager = RouterManager(detectJavaInstance: inst) + + // Configuration + // ... + routerManager.updateState() + + routerManager.eventManager.listenTo(eventName: "router_start", action: handleRouterStart) + routerManager.eventManager.listenTo(eventName: "router_stop", action: handleRouterStop) + routerManager.eventManager.listenTo(eventName: "router_pid", action: handleRouterPid) + routerManager.eventManager.listenTo(eventName: "router_version", action: handleRouterVersion) + return routerManager + }() + + // MARK: - + + let detectJava: DetectJava + private var routerInstance: I2PRouterTask?{ + //Called after the change + didSet{ + print("RouterManager.routerInstance did change to ", self.routerInstance ?? "null") + if (self.routerInstance != nil) { + RouterProcessStatus.isRouterRunning = (self.routerInstance?.isRouterRunning)! + } + } + }; + + // Initialization + + private init(detectJavaInstance: DetectJava) { + self.detectJava = detectJavaInstance + } + + // MARK: - Accessors + + class func shared() -> RouterManager { + return sharedRouterManager + } + + func setRouterTask(router: I2PRouterTask) { + self.routerInstance = router + } + + func getRouterTask() -> I2PRouterTask? { + return self.routerInstance + } + + func updateState() { + self.routerInstance = SBridge.sharedInstance()?.currentRouterInstance + } + +} diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus+ObjectiveC.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus+ObjectiveC.swift index cda1db500..0c4c8c3f8 100644 --- a/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus+ObjectiveC.swift +++ b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus+ObjectiveC.swift @@ -9,10 +9,15 @@ 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) + SBridge.sharedInstance().startupI2PRouter(i2pPath, javaBinPath: javaBinPath) + RouterManager.shared().updateState() + } + static func shutdownRouterChildProcess() { + RouterManager.shared().getRouterTask()?.requestShutdown() + RouterManager.shared().updateState() } } diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus.swift index 3ff99f349..db3db08c6 100644 --- a/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus.swift +++ b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus.swift @@ -32,6 +32,10 @@ import AppKit @objc func getJavaHome() -> String { return RouterProcessStatus.knownJavaBinPath! } + + @objc func triggerEvent(en: String, details: String? = nil) { + RouterManager.shared().eventManager.trigger(eventName: en, information: details) + } } extension RouterProcessStatus { diff --git a/launchers/macosx/I2PLauncher/userinterface/LogViewController.swift b/launchers/macosx/I2PLauncher/userinterface/LogViewController.swift index b8abcfc7c..616f76ed4 100644 --- a/launchers/macosx/I2PLauncher/userinterface/LogViewController.swift +++ b/launchers/macosx/I2PLauncher/userinterface/LogViewController.swift @@ -14,11 +14,43 @@ class LogViewerViewController : NSTabViewItem { @IBOutlet var scrollView: NSScrollView? @IBOutlet var textFieldView: NSTextView? - + private var outputPipe : Pipe? + override init(identifier: Any?) { + super.init(identifier: identifier) + self.captureStandardOutputAndRouteToTextView() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + self.captureStandardOutputAndRouteToTextView() + } + + + func captureStandardOutputAndRouteToTextView() { + outputPipe = RouterManager.shared().getRouterTask()?.processPipe + outputPipe?.fileHandleForReading.waitForDataInBackgroundAndNotify() + + NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: outputPipe?.fileHandleForReading , queue: nil) { + notification in + + let output = self.outputPipe?.fileHandleForReading.availableData + let outputString = String(data: output!, encoding: String.Encoding.utf8) ?? "" + + DispatchQueue.main.async(execute: { + let previousOutput = self.textFieldView?.string ?? "" + let nextOutput = previousOutput + "\n" + outputString + self.textFieldView?.string = nextOutput + + let range = NSRange(location:nextOutput.characters.count,length:0) + self.textFieldView?.scrollRangeToVisible(range) + + }) + + self.outputPipe?.fileHandleForReading.waitForDataInBackgroundAndNotify() + } + + } } -class LogViewController { - -} diff --git a/launchers/macosx/I2PLauncher/userinterface/PopoverViewController.swift b/launchers/macosx/I2PLauncher/userinterface/PopoverViewController.swift index 4980326c3..0c4d2f653 100644 --- a/launchers/macosx/I2PLauncher/userinterface/PopoverViewController.swift +++ b/launchers/macosx/I2PLauncher/userinterface/PopoverViewController.swift @@ -12,9 +12,6 @@ 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) - } @@ -22,83 +19,6 @@ class PopoverViewController: NSViewController { 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 != nil) { - 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 let version = RouterProcessStatus.routerVersion { - routerVersionLabel?.cell?.stringValue = "Router version: " + version - } else { - routerVersionLabel?.cell?.stringValue = "Router version: Still unknown" - } - if let routerStartTime = RouterProcessStatus.routerStartedAt { - routerUptimeLabel?.cell?.stringValue = "Router has runned for " + DateTimeUtils.timeAgoSinceDate(date: NSDate(date: routerStartTime), numericDates: false) - } - } - - init() { - let c = NSCoder() - super.init(coder: c)! - self.setRouterStatusLabelText() - } - - required init?(coder decoder: NSCoder) { - super.init(coder: decoder) - self.setRouterStatusLabelText() - } - } diff --git a/launchers/macosx/I2PLauncher/userinterface/RouterStatusView.swift b/launchers/macosx/I2PLauncher/userinterface/RouterStatusView.swift new file mode 100644 index 000000000..0359f5c6d --- /dev/null +++ b/launchers/macosx/I2PLauncher/userinterface/RouterStatusView.swift @@ -0,0 +1,113 @@ +// +// RouterStatusView.swift +// I2PLauncher +// +// Created by Mikal Villa on 22/09/2018. +// Copyright © 2018 The I2P Project. All rights reserved. +// + +import Cocoa + +@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") + if (!(RouterManager.shared().getRouterTask()?.isRunning())!) { + SBridge.sharedInstance().startupI2PRouter(RouterProcessStatus.i2pDirectoryPath, javaBinPath: RouterProcessStatus.knownJavaBinPath!) + } + RouterManager.shared().updateState() + } + + @objc func actionBtnStopRouter(_ sender: Any?) { + NSLog("STOP ROUTER") + if ((RouterManager.shared().getRouterTask()?.isRunning())!) { + NSLog("Found running router") + RouterManager.shared().getRouterTask()?.requestShutdown() + RouterManager.shared().updateState() + } + } + + @objc func actionBtnRestartRouter(sender: Any?) { + if ((RouterManager.shared().getRouterTask()?.isRunning())!) { + RouterManager.shared().getRouterTask()?.requestRestart() + } else { + NSLog("Can't restart a non running router, start it however...") + SBridge.sharedInstance().startupI2PRouter(RouterProcessStatus.i2pDirectoryPath, javaBinPath: RouterProcessStatus.knownJavaBinPath!) + } + RouterManager.shared().updateState() + } + + + + override func viewWillDraw() { + super.viewWillDraw() + if (RouterStatusView.instance != nil) { + RouterStatusView.instance = self + } + self.setRouterStatusLabelText() + } + + func setRouterStatusLabelText() { + if (RouterProcessStatus.isRouterRunning) { + routerStatusLabel?.cell?.stringValue = "Router status: Running" + routerStartStopButton?.title = "Stop Router" + routerStartStopButton?.action = #selector(self.actionBtnStopRouter(_:)) + } else { + routerStatusLabel?.cell?.stringValue = "Router status: Not running" + routerStartStopButton?.title = "Start Router" + routerStartStopButton?.action = #selector(self.actionBtnStartRouter(_:)) + } + routerStartStopButton?.needsDisplay = true + routerStartStopButton?.target = self + quickControlView?.needsDisplay = true + + let staticStartedByLabelText = "Router started by launcher?" + if RouterProcessStatus.isRouterChildProcess { + routerStartedByLabel?.cell?.stringValue = staticStartedByLabelText+" Yes" + } else { + routerStartedByLabel?.cell?.stringValue = staticStartedByLabelText+" No" + } + routerStartedByLabel?.needsDisplay = true + + if let version = RouterProcessStatus.routerVersion { + routerVersionLabel?.cell?.stringValue = "Router version: " + version + } else { + routerVersionLabel?.cell?.stringValue = "Router version: Still unknown" + } + if let routerStartTime = RouterProcessStatus.routerStartedAt { + routerUptimeLabel?.cell?.stringValue = "Uptime: Router started " + DateTimeUtils.timeAgoSinceDate(date: NSDate(date: routerStartTime), numericDates: false) + } else { + routerUptimeLabel?.cell?.stringValue = "Uptime: Router isn't running" + } + routerUptimeLabel?.needsDisplay = true + } + + + init() { + let c = NSCoder() + super.init(coder: c)! + self.setRouterStatusLabelText() + } + + required init?(coder decoder: NSCoder) { + super.init(coder: decoder) + self.setRouterStatusLabelText() + } + +} diff --git a/launchers/macosx/I2PLauncher/userinterface/StatusBarController.swift b/launchers/macosx/I2PLauncher/userinterface/StatusBarController.swift index 16579fe8e..eee8ef493 100644 --- a/launchers/macosx/I2PLauncher/userinterface/StatusBarController.swift +++ b/launchers/macosx/I2PLauncher/userinterface/StatusBarController.swift @@ -22,7 +22,6 @@ import Cocoa @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()) @@ -33,17 +32,12 @@ import Cocoa override init() { - super.init()//(xib: "UserInterface", bundle: nil) + super.init() 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]) @@ -97,6 +91,7 @@ import Cocoa if popover.isShown { closePopover(sender: sender) } else { + RouterManager.shared().updateState() showPopover(sender: sender) } } diff --git a/launchers/macosx/RouterTask.h b/launchers/macosx/RouterTask.h index 762a31853..3d2e625ba 100644 --- a/launchers/macosx/RouterTask.h +++ b/launchers/macosx/RouterTask.h @@ -39,11 +39,16 @@ const std::vector defaultFlagsForExtractorJob { @class I2PRouterTask; @interface I2PRouterTask : NSObject @property (strong) NSTask* routerTask; + +// TODO: Not in use, remove? +/* @property (strong) NSUserDefaults *userPreferences; @property (strong) NSFileHandle *readLogHandle; @property (strong) NSMutableData *totalLogData; -@property (strong) NSPipe *processPipe; @property (strong) NSFileHandle *input; +*/ + +@property (strong) NSPipe *processPipe; @property (atomic) BOOL isRouterRunning; @property (atomic) BOOL userRequestedRestart; - (instancetype) initWithOptions : (RTaskOptions*) options; diff --git a/launchers/macosx/RouterTask.mm b/launchers/macosx/RouterTask.mm index 8fc231780..7b8ff3c46 100644 --- a/launchers/macosx/RouterTask.mm +++ b/launchers/macosx/RouterTask.mm @@ -31,7 +31,7 @@ { self.userRequestedRestart = NO; self.isRouterRunning = NO; - self.input = [NSFileHandle fileHandleWithStandardInput]; + //self.input = [NSFileHandle fileHandleWithStandardInput]; self.routerTask = [NSTask new]; self.processPipe = [NSPipe new]; [self.routerTask setLaunchPath:options.binPath]; @@ -44,13 +44,41 @@ [self.routerTask setStandardOutput:self.processPipe]; [self.routerTask setStandardError:self.processPipe]; - NSFileHandle *stdoutFileHandle = [self.processPipe fileHandleForReading]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(routerStdoutData:) - name:NSFileHandleDataAvailableNotification - object:stdoutFileHandle]; + /* + NSFileHandle *stdoutFileHandle = [self.processPipe fileHandleForReading]; + dup2([[self.processPipe fileHandleForWriting] fileDescriptor], fileno(stdout)); + auto source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, [stdoutFileHandle fileDescriptor], 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); + dispatch_source_set_event_handler(source, ^{ + void* data = malloc(4096); + ssize_t readResult = 0; + do + { + errno = 0; + readResult = read([stdoutFileHandle fileDescriptor], data, 4096); + } while (readResult == -1 && errno == EINTR); + if (readResult > 0) + { + //AppKit UI should only be updated from the main thread + dispatch_async(dispatch_get_main_queue(),^{ + NSString* stdOutString = [[NSString alloc] initWithBytesNoCopy:data length:readResult encoding:NSUTF8StringEncoding freeWhenDone:YES]; + NSAttributedString* stdOutAttributedString = [[NSAttributedString alloc] initWithString:stdOutString]; + NSLog(@"Router stdout: %@", stdOutString); + //auto logForwarder = new LogForwarder(); + //[logForwarder appendLogViewWithLogLine:stdOutAttributedString]; + }); + } + else{free(data);} + }); + dispatch_resume(source); + */ + /* + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(routerStdoutData:) + name:NSFileHandleDataAvailableNotification + object:stdoutFileHandle]; [stdoutFileHandle waitForDataInBackgroundAndNotify]; + */ [self.routerTask setTerminationHandler:^(NSTask* task) { // Cleanup @@ -58,6 +86,8 @@ auto swiftRouterStatus = [[RouterProcessStatus alloc] init]; [swiftRouterStatus setRouterStatus: false]; [swiftRouterStatus setRouterRanByUs: false]; + [swiftRouterStatus triggerEventWithEn:@"router_stop" details:@"normal shutdown"]; + [[SBridge sharedInstance] setCurrentRouterInstance:nil]; sendUserNotification(APP_IDSTR, @"I2P Router has stopped"); }]; return self; @@ -82,8 +112,9 @@ - (int) execute { @try { + auto swiftRouterStatus = [[RouterProcessStatus alloc] init]; + [swiftRouterStatus triggerEventWithEn:@"router_start" details:@"normal start"]; [self.routerTask launch]; - watchPid([self.routerTask processIdentifier]); self.isRouterRunning = YES; return 1; } @@ -91,8 +122,11 @@ { NSLog(@"Expection occurred %@", [e reason]); auto swiftRouterStatus = [[RouterProcessStatus alloc] init]; + self.isRouterRunning = NO; [swiftRouterStatus setRouterStatus: false]; [swiftRouterStatus setRouterRanByUs: false]; + [swiftRouterStatus triggerEventWithEn:@"router_stop" details:@"error shutdown"]; + [[SBridge sharedInstance] setCurrentRouterInstance:nil]; sendUserNotification(@"An error occured, can't start the I2P Router", [e reason]); return 0; } diff --git a/launchers/macosx/SBridge.h b/launchers/macosx/SBridge.h index 1b52151ec..bfb0dd4d7 100644 --- a/launchers/macosx/SBridge.h +++ b/launchers/macosx/SBridge.h @@ -9,6 +9,8 @@ #import #import +#import "RouterTask.h" + #ifdef __cplusplus #include #include @@ -54,7 +56,9 @@ inline std::string buildClassPathForObjC(std::string basePath) #endif @interface SBridge : NSObject +@property (nonatomic, assign) I2PRouterTask* currentRouterInstance; - (NSString*) buildClassPath:(NSString*)i2pPath; - (void) startupI2PRouter:(NSString*)i2pRootPath javaBinPath:(NSString*)javaBinPath; - (void) openUrl:(NSString*)url; ++ (instancetype)sharedInstance; // this makes it a singleton @end diff --git a/launchers/macosx/SBridge.mm b/launchers/macosx/SBridge.mm index f885f7ce4..a1cb16b45 100644 --- a/launchers/macosx/SBridge.mm +++ b/launchers/macosx/SBridge.mm @@ -33,9 +33,16 @@ std::future startupRouter(NSString* javaBin, NSArray* arguments, options.arguments = arguments; options.i2pBaseDir = i2pBaseDir; auto instance = [[I2PRouterTask alloc] initWithOptions: options]; + + [[SBridge sharedInstance] setCurrentRouterInstance:instance]; [instance execute]; sendUserNotification(APP_IDSTR, @"The I2P router is starting up."); auto pid = [instance getPID]; + NSLog(@"Got pid: %d", pid); + + auto swiftRouterStatus = [[RouterProcessStatus alloc] init]; + [swiftRouterStatus triggerEventWithEn:@"router_pid" details:[NSString stringWithFormat:@"%d", pid]]; + return std::async(std::launch::async, [&pid]{ return pid; }); @@ -45,6 +52,13 @@ std::future startupRouter(NSString* javaBin, NSArray* arguments, auto errStr = [NSString stringWithFormat:@"Expection occurred %@",[e reason]]; NSLog(@"%@", errStr); sendUserNotification(APP_IDSTR, errStr); + [[SBridge sharedInstance] setCurrentRouterInstance:nil]; + + auto swiftRouterStatus = [[RouterProcessStatus alloc] init]; + [swiftRouterStatus setRouterStatus: false]; + [swiftRouterStatus setRouterRanByUs: false]; + [swiftRouterStatus triggerEventWithEn:@"router_exception" details:errStr]; + return std::async(std::launch::async, [&]{ return 0; }); @@ -55,6 +69,16 @@ std::future startupRouter(NSString* javaBin, NSArray* arguments, @implementation SBridge +// this makes it a singleton ++ (instancetype)sharedInstance { + static SBridge *sharedInstance = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + sharedInstance = [[SBridge alloc] init]; + }); + return sharedInstance; +} - (void) openUrl:(NSString*)url { @@ -125,6 +149,7 @@ std::future startupRouter(NSString* javaBin, NSArray* arguments, sendUserNotification(APP_IDSTR, [NSString stringWithFormat:@"Error: %@", errMsg]); [routerStatus setRouterStatus: false]; [routerStatus setRouterRanByUs: false]; + [routerStatus triggerEventWithEn:@"router_exception" details:[NSString stringWithFormat:@"Error: %@", errMsg]]; } } @end diff --git a/launchers/macosx/main.mm b/launchers/macosx/main.mm index faf4cc8fd..655704358 100644 --- a/launchers/macosx/main.mm +++ b/launchers/macosx/main.mm @@ -204,7 +204,6 @@ using namespace subprocess; NSBundle *launcherBundle = [NSBundle mainBundle]; - auto sBridge = [[SBridge alloc] init]; // Helper object to hold statefull path information self.metaInfo = [[ExtractMetaInfo alloc] init]; @@ -237,7 +236,7 @@ using namespace subprocess; NSLog(@"Time to detect I2P version in install directory"); [self.swiftRuntime findInstalledI2PVersion]; if (shouldAutoStartRouter) { - [sBridge startupI2PRouter:self.metaInfo.i2pBase javaBinPath:self.metaInfo.javaBinary]; + [[SBridge sharedInstance] startupI2PRouter:self.metaInfo.i2pBase javaBinPath:self.metaInfo.javaBinary]; [routerStatus setRouterRanByUs: true]; } }]; @@ -248,7 +247,7 @@ using namespace subprocess; [self.swiftRuntime findInstalledI2PVersion]; if (shouldAutoStartRouter) { - [sBridge startupI2PRouter:self.metaInfo.i2pBase javaBinPath:self.metaInfo.javaBinary]; + [[SBridge sharedInstance] startupI2PRouter:self.metaInfo.i2pBase javaBinPath:self.metaInfo.javaBinary]; [routerStatus setRouterRanByUs: true]; } } diff --git a/launchers/macosx/osx_create_dmg.sh b/launchers/macosx/osx_create_dmg.sh new file mode 100755 index 000000000..26c21b1d5 --- /dev/null +++ b/launchers/macosx/osx_create_dmg.sh @@ -0,0 +1,107 @@ +#!/bin/bash + +APP_NAME="I2PLauncher" +VERSION="0.9.36" +DMG_BACKGROUND_IMG="Background.png" + +APP_EXE="${APP_NAME}.app/Contents/MacOS/${APP_NAME}" +VOL_NAME="${APP_NAME} ${VERSION}" +DMG_TMP="${VOL_NAME}-temp.dmg" +DMG_FINAL="${VOL_NAME}.dmg" +STAGING_DIR="/tmp/mkdmg$$" + +# Check the background image DPI and convert it if it isn't 72x72 +_BACKGROUND_IMAGE_DPI_H=`sips -g dpiHeight ${DMG_BACKGROUND_IMG} | grep -Eo '[0-9]+\.[0-9]+'` +_BACKGROUND_IMAGE_DPI_W=`sips -g dpiWidth ${DMG_BACKGROUND_IMG} | grep -Eo '[0-9]+\.[0-9]+'` + +if [ $(echo " $_BACKGROUND_IMAGE_DPI_H != 72.0 " | bc) -eq 1 -o $(echo " $_BACKGROUND_IMAGE_DPI_W != 72.0 " | bc) -eq 1 ]; then + echo "WARNING: The background image's DPI is not 72. This will result in distorted backgrounds on Mac OS X 10.7+." + echo " I will convert it to 72 DPI for you." + + _DMG_BACKGROUND_TMP="${DMG_BACKGROUND_IMG%.*}"_dpifix."${DMG_BACKGROUND_IMG##*.}" + + sips -s dpiWidth 72 -s dpiHeight 72 ${DMG_BACKGROUND_IMG} --out ${_DMG_BACKGROUND_TMP} + + DMG_BACKGROUND_IMG="${_DMG_BACKGROUND_TMP}" +fi + +# clear out any old data +rm -rf "${STAGING_DIR}" "${DMG_TMP}" "${DMG_FINAL}" + +# copy over the stuff we want in the final disk image to our staging dir +mkdir -p "${STAGING_DIR}" +cp -rpf "${APP_NAME}.app" "${STAGING_DIR}" +# ... cp anything else you want in the DMG - documentation, etc. + +# figure out how big our DMG needs to be +# assumes our contents are at least 1M! +SIZE=`du -sh "${STAGING_DIR}" | sed 's/\([0-9\.]*\)M\(.*\)/\1/'` +SIZE=`echo "${SIZE} + 1.0" | bc | awk '{print int($1+0.5)}'` + +if [ $? -ne 0 ]; then + echo "Error: Cannot compute size of staging dir" + exit +fi + +# create the temp DMG file +hdiutil create -srcfolder "${STAGING_DIR}" -volname "${VOL_NAME}" -fs HFS+ \ + -fsargs "-c c=64,a=16,e=16" -format UDRW -size ${SIZE}M "${DMG_TMP}" + +echo "Created DMG: ${DMG_TMP}" + +# mount it and save the device +DEVICE=$(hdiutil attach -readwrite -noverify "${DMG_TMP}" | \ + egrep '^/dev/' | sed 1q | awk '{print $1}') + +sleep 2 + +# add a link to the Applications dir +echo "Add link to /Applications" +pushd /Volumes/"${VOL_NAME}" +ln -s /Applications +popd + +# add a background image +mkdir /Volumes/"${VOL_NAME}"/.background +cp "${DMG_BACKGROUND_IMG}" /Volumes/"${VOL_NAME}"/.background/ + +# tell the Finder to resize the window, set the background, +# change the icon size, place the icons in the right position, etc. +echo ' + tell application "Finder" + tell disk "'${VOL_NAME}'" + open + set current view of container window to icon view + set toolbar visible of container window to false + set statusbar visible of container window to false + set the bounds of container window to {400, 100, 920, 440} + set viewOptions to the icon view options of container window + set arrangement of viewOptions to not arranged + set icon size of viewOptions to 72 + set background picture of viewOptions to file ".background:'${DMG_BACKGROUND_IMG}'" + set position of item "'${APP_NAME}'.app" of container window to {160, 205} + set position of item "Applications" of container window to {360, 205} + close + open + update without registering applications + delay 2 + end tell + end tell +' | osascript + +sync + +# unmount it +hdiutil detach "${DEVICE}" + +# now make the final image a compressed disk image +echo "Creating compressed image" +hdiutil convert "${DMG_TMP}" -format UDZO -imagekey zlib-level=9 -o "${DMG_FINAL}" + +# clean up +rm -rf "${DMG_TMP}" +rm -rf "${STAGING_DIR}" + +echo 'Done.' + +exit