From 530470972f81f8086e58895985bee7e2bcad42cc Mon Sep 17 00:00:00 2001
From: meeh <meeh@mail.i2p>
Date: Sat, 13 Oct 2018 03:54:01 +0000
Subject: [PATCH] OSX Launcher: general cleanup, responsibility delegation, and
 fixes

---
 .../I2PLauncher/SwiftMainDelegate.swift       |   3 +-
 .../Utils/FolderContentMonitor.swift          | 111 +++++++++++++++
 launchers/macosx/I2PLauncher/logger_c.h       |   3 +
 .../routermgmt/RouterManager.swift            |  22 +++
 .../I2PLauncher/routermgmt/RouterRunner.swift | 126 +++++++++---------
 5 files changed, 199 insertions(+), 66 deletions(-)
 create mode 100644 launchers/macosx/I2PLauncher/Utils/FolderContentMonitor.swift

diff --git a/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift b/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift
index ed5ef7857c..719e0ed012 100644
--- a/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift
+++ b/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift
@@ -93,7 +93,8 @@ class Logger {
   }
   
   @objc static func openLink(url: String) {
-    SBridge.sharedInstance().openUrl(url)
+    NSLog("Trying to open \(url)")
+    NSWorkspace.shared().open(NSURL(string: url)! as URL)
   }
   
   @objc func applicationWillTerminate() {
diff --git a/launchers/macosx/I2PLauncher/Utils/FolderContentMonitor.swift b/launchers/macosx/I2PLauncher/Utils/FolderContentMonitor.swift
new file mode 100644
index 0000000000..168989344a
--- /dev/null
+++ b/launchers/macosx/I2PLauncher/Utils/FolderContentMonitor.swift
@@ -0,0 +1,111 @@
+//
+//  FolderContentMonitor.swift
+//  I2PLauncher
+//
+//  Created by Mikal Villa on 28/09/2018.
+//  Copyright © 2018 The I2P Project. All rights reserved.
+//
+
+import Foundation
+
+/*
+ infix operator ~>
+ 
+ private let queue = DispatchQueue(label: "background")
+ 
+ func ~> <R> (
+ backgroundClosure:   @escaping () -> R,
+ mainClosure:         @escaping (_ result: R) -> ())
+ {
+ queue.async {
+ let result = backgroundClosure()
+ DispatchQueue.main.async(execute: {
+ mainClosure(result)
+ })
+ }
+ }
+ */
+
+public struct Event: CustomStringConvertible {
+  
+  public let eventId: FSEventStreamEventId
+  public let eventPath: String
+  public let eventFlags: FSEventStreamEventFlags
+  
+  public var description: String {
+    return "\(eventId) - \(eventFlags) - \(eventPath)"
+  }
+}
+
+public class FolderContentMonitor {
+  
+  let callback: (Event) -> Void
+  
+  public init(pathsToWatch: [String], sinceWhen: FSEventStreamEventId = FSEventStreamEventId(kFSEventStreamEventIdSinceNow), callback: @escaping (Event) -> Void) {
+    
+    self.lastEventId = sinceWhen
+    self.pathsToWatch = pathsToWatch
+    self.callback = callback
+  }
+  
+  deinit {
+    stop()
+  }
+  
+  // MARK: - Private Properties
+  private let eventCallback: FSEventStreamCallback = {
+    (stream: ConstFSEventStreamRef,
+    contextInfo: UnsafeMutableRawPointer?,
+    numEvents: Int,
+    eventPaths: UnsafeMutableRawPointer,
+    eventFlags: UnsafePointer<FSEventStreamEventFlags>,
+    eventIds: UnsafePointer<FSEventStreamEventId>) in
+    
+    let fileSystemWatcher: FolderContentMonitor = unsafeBitCast(contextInfo, to: FolderContentMonitor.self)
+    
+    guard let paths = unsafeBitCast(eventPaths, to: NSArray.self) as? [String] else { return }
+    
+    for index in 0..<numEvents {
+      fileSystemWatcher.processEvent(eventId: eventIds[index], eventPath: paths[index], eventFlags: eventFlags[index])
+    }
+    
+    fileSystemWatcher.lastEventId = eventIds[numEvents - 1]
+  }
+  
+  private let pathsToWatch: [String]
+  private var started = false
+  private var streamRef: FSEventStreamRef!
+  
+  private func processEvent(eventId: FSEventStreamEventId, eventPath: String, eventFlags: FSEventStreamEventFlags) {
+    
+    let event = Event(eventId: eventId, eventPath: eventPath, eventFlags: eventFlags)
+    callback(event)
+  }
+  
+  public private(set) var lastEventId: FSEventStreamEventId
+  
+  public func start() {
+    guard started == false else { return }
+    
+    var context = FSEventStreamContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
+    context.info = Unmanaged.passUnretained(self).toOpaque()
+    let flags = UInt32(kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagFileEvents)
+    streamRef = FSEventStreamCreate(kCFAllocatorDefault, eventCallback, &context, pathsToWatch as CFArray, lastEventId, 0, flags)
+    
+    FSEventStreamScheduleWithRunLoop(streamRef, CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue)
+    FSEventStreamStart(streamRef)
+    
+    started = true
+  }
+  
+  public func stop() {
+    guard started == true else { return }
+    
+    FSEventStreamStop(streamRef)
+    FSEventStreamInvalidate(streamRef)
+    FSEventStreamRelease(streamRef)
+    streamRef = nil
+    
+    started = false
+  }
+}
diff --git a/launchers/macosx/I2PLauncher/logger_c.h b/launchers/macosx/I2PLauncher/logger_c.h
index 95b11c8236..102204b38f 100644
--- a/launchers/macosx/I2PLauncher/logger_c.h
+++ b/launchers/macosx/I2PLauncher/logger_c.h
@@ -82,4 +82,7 @@ inline void MLog(int loglevel, NSString* format, ...)
 }
 
 
+
+#define MMLog(format_string,...) ((MLog(1, [NSString stringWithFormat:format_string,##__VA_ARGS__])))
+
 #endif /* logger_c_h */
diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift
index e274d2017e..4776b84a37 100644
--- a/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift
+++ b/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift
@@ -25,6 +25,7 @@ class RouterManager : NSObject {
   let routerRunner = RouterRunner()
   
   var logViewStorage: NSTextStorage?
+  var lastRouterPid : String? = nil
   
   private static func handleRouterException(information:Any?) {
     Logger.MLog(level:1,"event! - handle router exception")
@@ -94,6 +95,27 @@ class RouterManager : NSObject {
     routerManager.eventManager.listenTo(eventName: "router_already_running", action: handleRouterAlreadyStarted)
     routerManager.eventManager.listenTo(eventName: "router_can_start", action: routerManager.routerRunner.StartAgent)
     routerManager.eventManager.listenTo(eventName: "router_can_setup", action: routerManager.routerRunner.SetupAgent)
+    
+    //routerManager.eventManager.listenTo(eventName: "launch_agent_running", action: routerManager.routerRunner.onLoadedAgent)
+    
+    routerManager.eventManager.listenTo(eventName: "launch_agent_running", action: {
+      let agent = RouterRunner.launchAgent!
+      let agentStatus = agent.status()
+      switch agentStatus {
+      case .running(let pid):
+        DispatchQueue.main.async {
+          routerManager.eventManager.trigger(eventName: "router_start", information: String(pid))
+          routerManager.routerRunner.routerStatus.setRouterStatus(true)
+          routerManager.routerRunner.routerStatus.setRouterRanByUs(true)
+          routerManager.eventManager.trigger(eventName: "router_pid", information: String(pid))
+        }
+        break
+        
+      default:
+        NSLog("wtf, agent status = \(agentStatus)")
+        break
+      }
+    })
     return routerManager
   }()
   
diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterRunner.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterRunner.swift
index aa416f7709..3f11a0c9a9 100644
--- a/launchers/macosx/I2PLauncher/routermgmt/RouterRunner.swift
+++ b/launchers/macosx/I2PLauncher/routermgmt/RouterRunner.swift
@@ -22,7 +22,7 @@ class RouterRunner: NSObject {
   
   let domainLabel = "net.i2p.macosx.I2PRouter"
   
-  let plistName = "net.i2p.macosx.I2PRouterAgent.plist"
+  let plistName = "net.i2p.macosx.I2PRouter.plist"
   
   let defaultStartupCommand:String = "/usr/libexec/java_home"
   
@@ -35,6 +35,10 @@ class RouterRunner: NSObject {
   
   let appSupportPath = FileManager.default.urls(for: FileManager.SearchPathDirectory.applicationSupportDirectory, in: FileManager.SearchPathDomainMask.userDomainMask)
   
+  override init() {
+    super.init()
+  }
+  
   func SetupAgent() {
     let agent = SetupAndReturnAgent()
     RouterRunner.launchAgent = agent
@@ -106,78 +110,70 @@ class RouterRunner: NSObject {
     let shouldStartupAtLogin = userPreferences.bool(forKey: "startRouterAtLogin")
     agent.runAtLoad = shouldStartupAtLogin
     agent.keepAlive = true
-    
-    do {
-      
-      try LaunchAgentManager.shared.write(agent, called: self.plistName)
-      sleep(1)
-      try LaunchAgentManager.shared.load(agent)
-      sleep(1)
-      
-      let agentStatus = LaunchAgentManager.shared.status(agent)
-      switch agentStatus {
-      case .running:
-        break
-      case .loaded:
-        break
-      case .unloaded:
-        sleep(2)
-        break
+    DispatchQueue(label: "background_starter").async {
+      do {
+        try LaunchAgentManager.shared.write(agent, called: self.plistName)
+        sleep(1)
+        try LaunchAgentManager.shared.load(agent)
+        sleep(1)
+        
+        let agentStatus = LaunchAgentManager.shared.status(agent)
+        switch agentStatus {
+        case .running:
+          break
+        case .loaded:
+          DispatchQueue.main.async {
+            RouterManager.shared().eventManager.trigger(eventName: "router_can_start", information: agent)
+          }
+          break
+        case .unloaded:
+          break
+        }
+      } catch {
+        DispatchQueue.main.async {
+          RouterManager.shared().eventManager.trigger(eventName: "router_setup_error", information: "\(error)")
+        }
       }
-      
-      
-      RouterManager.shared().eventManager.trigger(eventName: "router_can_start", information: agent)
-    } catch {
-      RouterManager.shared().eventManager.trigger(eventName: "router_setup_error", information: "\(error)")
     }
     return agent
   }
   
-  func StartAgent(information:Any?) {
-    let agent = RouterRunner.launchAgent!
-    LaunchAgentManager.shared.start(agent)
-    sleep(1)
-    let agentStatus = agent.status()
-    switch agentStatus {
-    case .running(let pid):
-      RouterManager.shared().eventManager.trigger(eventName: "router_start", information: String(pid))
-      routerStatus.setRouterStatus(true)
-      routerStatus.setRouterRanByUs(true)
-      DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
-        // Delayed message to ensure UI has been initialized.
-        RouterManager.shared().eventManager.trigger(eventName: "router_pid", information: String(pid))
-      }
-      break
-      
-    default: break
+  
+  func StartAgent(_ information:Any? = nil) {
+    let agent = RouterRunner.launchAgent ?? information as! LaunchAgent
+    DispatchQueue(label: "background_block").async {
+      LaunchAgentManager.shared.start(agent, { (proc) in
+        NSLog("Will call onLaunchdStarted")
+      })
     }
   }
   
-  func StopAgent() {
-    var agentStatus = LaunchAgentManager.shared.status(RouterRunner.launchAgent!)
-    switch agentStatus {
-    case .running:
-      LaunchAgentManager.shared.stop(RouterRunner.launchAgent!)
-      break
-    case .loaded, .unloaded:
-      try! LaunchAgentManager.shared.load(RouterRunner.launchAgent!)
-      routerStatus.setRouterStatus(false)
-      routerStatus.setRouterRanByUs(false)
-      RouterManager.shared().eventManager.trigger(eventName: "router_stop", information: "ok")
-      return;
-      break
-    default: break
-    }
-    sleep(1)
-    agentStatus = LaunchAgentManager.shared.status(RouterRunner.launchAgent!)
-    switch agentStatus {
-    case .loaded, .unloaded:
-      try! LaunchAgentManager.shared.load(RouterRunner.launchAgent!)
-      routerStatus.setRouterStatus(false)
-      routerStatus.setRouterRanByUs(false)
-      RouterManager.shared().eventManager.trigger(eventName: "router_stop", information: "ok")
-      break
-    default: break
+  func StopAgent(_ callback: @escaping () -> () = {}) {
+    let agentStatus = LaunchAgentManager.shared.status(RouterRunner.launchAgent!)
+    DispatchQueue(label: "background_block").async {
+      do {
+        switch agentStatus {
+        case .running:
+          // For now we need to use unload to stop it.
+          try LaunchAgentManager.shared.unload(RouterRunner.launchAgent!, { (proc) in
+            // Called when stop is actually executed
+            proc.waitUntilExit()
+            DispatchQueue.main.async {
+              RouterManager.shared().eventManager.trigger(eventName: "router_stop", information: "ok")
+              callback()
+            }
+          })
+          try LaunchAgentManager.shared.load(RouterRunner.launchAgent!)
+          break
+        case .unloaded:
+          // Seems it sometimes get unloaded on stop, we load it again.
+          try! LaunchAgentManager.shared.load(RouterRunner.launchAgent!)
+          return
+        default: break
+        }
+      } catch {
+        NSLog("Error \(error)")
+      }
     }
   }
   
-- 
GitLab