diff --git a/launchers/macosx/I2PLauncher/Base.lproj/UserInterfaces.xib b/launchers/macosx/I2PLauncher/Base.lproj/UserInterfaces.xib index a733a1265..bc21f8b43 100644 --- a/launchers/macosx/I2PLauncher/Base.lproj/UserInterfaces.xib +++ b/launchers/macosx/I2PLauncher/Base.lproj/UserInterfaces.xib @@ -1,7 +1,8 @@ - + - + + @@ -36,5 +37,6 @@ + diff --git a/launchers/macosx/I2PLauncher/I2PLauncher-Bridging-Header.h b/launchers/macosx/I2PLauncher/I2PLauncher-Bridging-Header.h index 9b963b18c..1ece6baa9 100644 --- a/launchers/macosx/I2PLauncher/I2PLauncher-Bridging-Header.h +++ b/launchers/macosx/I2PLauncher/I2PLauncher-Bridging-Header.h @@ -5,4 +5,5 @@ #import "AppleStuffExceptionHandler.h" #import "AppDelegate.h" #import "RouterTask.h" +#import "Sparkle/SUUpdater.h" diff --git a/launchers/macosx/I2PLauncher/Info.plist b/launchers/macosx/I2PLauncher/Info.plist index 912ffda85..9b424ac42 100644 --- a/launchers/macosx/I2PLauncher/Info.plist +++ b/launchers/macosx/I2PLauncher/Info.plist @@ -17,24 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLIconFile - ItoopieTransparent - CFBundleURLName - http+i2p - CFBundleURLSchemes - - http+i2p - - - + 0.1.1 CFBundleVersion - 4 + 5 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion @@ -54,6 +39,8 @@ SUFeedURL - http://i2browser.i2p/updates/v1/appcast.xml + https://download.i2p2.de/macosx/sparkle/updates/v1/appcast.xml + SUPublicEDKey + weKSpHXfJzk+5qy3UVfqsUwTeLnT9WCFVMwd9yW0+DA= diff --git a/launchers/macosx/I2PLauncher/Storyboard.storyboard b/launchers/macosx/I2PLauncher/Storyboard.storyboard index 3399d4683..1b72a5fe8 100644 --- a/launchers/macosx/I2PLauncher/Storyboard.storyboard +++ b/launchers/macosx/I2PLauncher/Storyboard.storyboard @@ -154,6 +154,7 @@ + diff --git a/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift b/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift index daa600680..e0de9e5d3 100644 --- a/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift +++ b/launchers/macosx/I2PLauncher/SwiftMainDelegate.swift @@ -69,11 +69,17 @@ import Cocoa } @objc func applicationDidFinishLaunching() { - print("Hello from swift!") var i2pPath = NSHomeDirectory() i2pPath += "/Library/I2P" - findInstalledI2PVersion() + } + + @objc func listenForEvent(eventName: String, callbackActionFn: @escaping ((Any?)->()) ) { + RouterManager.shared().eventManager.listenTo(eventName: eventName, action: callbackActionFn ) + } + + @objc func triggerEvent(en: String, details: String? = nil) { + RouterManager.shared().eventManager.trigger(eventName: en, information: details) } @objc static func openLink(url: String) { diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift index 402516c9a..695b402c3 100644 --- a/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift +++ b/launchers/macosx/I2PLauncher/routermgmt/RouterManager.swift @@ -38,6 +38,7 @@ class RouterManager : NSObject { 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") + RouterManager.shared().eventManager.trigger(eventName: "router_must_upgrade", information: "got new version") } else { Swift.print("event! - router version: No update needed") RouterManager.shared().eventManager.trigger(eventName: "router_can_start", information: "all ok") diff --git a/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus.swift b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus.swift index db3db08c6..29b343fa7 100644 --- a/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus.swift +++ b/launchers/macosx/I2PLauncher/routermgmt/RouterProcessStatus.swift @@ -36,23 +36,16 @@ import AppKit @objc func triggerEvent(en: String, details: String? = nil) { RouterManager.shared().eventManager.trigger(eventName: en, information: details) } + + @objc func listenForEvent(eventName: String, callbackActionFn: @escaping ((Any?)->()) ) { + RouterManager.shared().eventManager.listenTo(eventName: eventName, action: callbackActionFn ) + } } extension RouterProcessStatus { - static var isRouterRunning : Bool = false - static var isRouterChildProcess : Bool = false + static var isRouterRunning : Bool = (RouterManager.shared().getRouterTask() != nil) + static var isRouterChildProcess : Bool = (RouterManager.shared().getRouterTask() != nil) static var routerVersion : String? = Optional.none - static var routerUptime : String? = Optional.none{ - //Called before the change - willSet(newValue){ - print("RouterProcessStatus.routerUptime will change from ", (self.routerUptime ?? "nil"), " to "+(newValue ?? "nil")) - } - - //Called after the change - didSet{ - print("RouterProcessStatus.routerUptime did change to "+self.routerUptime!) - } - } static var routerStartedAt : Date? = Optional.none static var knownJavaBinPath : String? = Optional.none static var i2pDirectoryPath : String = NSHomeDirectory() + "/Library/I2P" diff --git a/launchers/macosx/I2PLauncher/userinterface/RouterStatusView.swift b/launchers/macosx/I2PLauncher/userinterface/RouterStatusView.swift index 0359f5c6d..464dd501d 100644 --- a/launchers/macosx/I2PLauncher/userinterface/RouterStatusView.swift +++ b/launchers/macosx/I2PLauncher/userinterface/RouterStatusView.swift @@ -28,7 +28,7 @@ import Cocoa @objc func actionBtnStartRouter(_ sender: Any?) { NSLog("START ROUTER") - if (!(RouterManager.shared().getRouterTask()?.isRunning())!) { + if (RouterManager.shared().getRouterTask() == nil) { SBridge.sharedInstance().startupI2PRouter(RouterProcessStatus.i2pDirectoryPath, javaBinPath: RouterProcessStatus.knownJavaBinPath!) } RouterManager.shared().updateState() @@ -36,7 +36,7 @@ import Cocoa @objc func actionBtnStopRouter(_ sender: Any?) { NSLog("STOP ROUTER") - if ((RouterManager.shared().getRouterTask()?.isRunning())!) { + if (RouterManager.shared().getRouterTask() != nil) { NSLog("Found running router") RouterManager.shared().getRouterTask()?.requestShutdown() RouterManager.shared().updateState() @@ -44,7 +44,7 @@ import Cocoa } @objc func actionBtnRestartRouter(sender: Any?) { - if ((RouterManager.shared().getRouterTask()?.isRunning())!) { + if (RouterManager.shared().getRouterTask() != nil) { RouterManager.shared().getRouterTask()?.requestRestart() } else { NSLog("Can't restart a non running router, start it however...") diff --git a/launchers/macosx/I2PLauncher/userinterface/StatusBarController.swift b/launchers/macosx/I2PLauncher/userinterface/StatusBarController.swift index eee8ef493..14665c76e 100644 --- a/launchers/macosx/I2PLauncher/userinterface/StatusBarController.swift +++ b/launchers/macosx/I2PLauncher/userinterface/StatusBarController.swift @@ -9,12 +9,13 @@ 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) + let storyboard = NSStoryboard(name: "Storyboard", bundle: Bundle.main) + + var updateObjectRef : SUUpdater? @objc func handleOpenConsole(_ sender: Any?) { SwiftMainDelegate.openLink(url: "http://localhost:7657") @@ -23,8 +24,13 @@ import Cocoa @objc func constructMenu() -> NSMenu { let menu = NSMenu() + let updateMenuItem = NSMenuItem(title: "Check for updates", action: #selector(self.updateObjectRef?.checkForUpdates(_:)), keyEquivalent: "U") + updateMenuItem.isEnabled = true + menu.addItem(NSMenuItem(title: "Open I2P Console", action: #selector(self.handleOpenConsole(_:)), keyEquivalent: "O")) menu.addItem(NSMenuItem.separator()) + menu.addItem(updateMenuItem) + menu.addItem(NSMenuItem.separator()) menu.addItem(NSMenuItem(title: "Quit I2P Launcher", action: #selector(SwiftMainDelegate.terminate(_:)), keyEquivalent: "q")) return menu @@ -34,6 +40,9 @@ import Cocoa override init() { super.init() popover.contentViewController = PopoverViewController.freshController() + updateObjectRef = SUUpdater.shared() + updateObjectRef?.checkForUpdatesInBackground() + if let button = statusItem.button { button.image = NSImage(named:"StatusBarButtonImage") diff --git a/launchers/macosx/RouterTask.h b/launchers/macosx/RouterTask.h index 3d2e625ba..7ef511827 100644 --- a/launchers/macosx/RouterTask.h +++ b/launchers/macosx/RouterTask.h @@ -39,15 +39,6 @@ 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) NSFileHandle *input; -*/ - @property (strong) NSPipe *processPipe; @property (atomic) BOOL isRouterRunning; @property (atomic) BOOL userRequestedRestart; diff --git a/launchers/macosx/RouterTask.mm b/launchers/macosx/RouterTask.mm index 7b8ff3c46..2f269869b 100644 --- a/launchers/macosx/RouterTask.mm +++ b/launchers/macosx/RouterTask.mm @@ -44,42 +44,6 @@ [self.routerTask setStandardOutput:self.processPipe]; [self.routerTask setStandardError:self.processPipe]; - /* - 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 NSLog(@"termHandler triggered!"); @@ -112,8 +76,6 @@ - (int) execute { @try { - auto swiftRouterStatus = [[RouterProcessStatus alloc] init]; - [swiftRouterStatus triggerEventWithEn:@"router_start" details:@"normal start"]; [self.routerTask launch]; self.isRouterRunning = YES; return 1; diff --git a/launchers/macosx/SBridge.h b/launchers/macosx/SBridge.h index bfb0dd4d7..bb0ccde4a 100644 --- a/launchers/macosx/SBridge.h +++ b/launchers/macosx/SBridge.h @@ -18,7 +18,7 @@ #include #include #include "include/fn.h" -std::future startupRouter(NSString* javaBin, NSArray* arguments, NSString* i2pBaseDir); +//std::future startupRouter(NSString* javaBin, NSArray* arguments, NSString* i2pBaseDir, RouterProcessStatus* routerStatus = nil); namespace osx { diff --git a/launchers/macosx/SBridge.mm b/launchers/macosx/SBridge.mm index a1cb16b45..c8ba2c142 100644 --- a/launchers/macosx/SBridge.mm +++ b/launchers/macosx/SBridge.mm @@ -26,7 +26,7 @@ -std::future startupRouter(NSString* javaBin, NSArray* arguments, NSString* i2pBaseDir) { +std::future startupRouter(NSString* javaBin, NSArray* arguments, NSString* i2pBaseDir, RouterProcessStatus* routerStatus) { @try { RTaskOptions* options = [RTaskOptions alloc]; options.binPath = javaBin; @@ -36,12 +36,16 @@ std::future startupRouter(NSString* javaBin, NSArray* arguments, [[SBridge sharedInstance] setCurrentRouterInstance:instance]; [instance execute]; + if (routerStatus != nil) { + [routerStatus setRouterStatus: true]; + [routerStatus setRouterRanByUs: true]; + [routerStatus triggerEventWithEn:@"router_start" details:@"normal start"]; + } 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]]; + if (routerStatus != nil) [routerStatus triggerEventWithEn:@"router_pid" details:[NSString stringWithFormat:@"%d", pid]]; return std::async(std::launch::async, [&pid]{ return pid; @@ -54,10 +58,11 @@ std::future startupRouter(NSString* javaBin, NSArray* arguments, 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]; + if (routerStatus != nil) { + [routerStatus setRouterStatus: false]; + [routerStatus setRouterRanByUs: false]; + [routerStatus triggerEventWithEn:@"router_exception" details:errStr]; + } return std::async(std::launch::async, [&]{ return 0; @@ -100,8 +105,6 @@ std::future startupRouter(NSString* javaBin, NSArray* arguments, { std::string basePath([i2pRootPath UTF8String]); - // Get paths - //NSBundle *launcherBundle = [NSBundle mainBundle]; auto classPathStr = buildClassPathForObjC(basePath); RouterProcessStatus* routerStatus = [[RouterProcessStatus alloc] init]; @@ -137,12 +140,11 @@ std::future startupRouter(NSString* javaBin, NSArray* arguments, auto nsJavaBin = javaBinPath; auto nsBasePath = i2pRootPath; NSArray* arrArguments = [NSArray arrayWithObjects:&argList[0] count:argList.size()]; - // We don't really know yet, but per now a workaround - [routerStatus setRouterStatus: true]; + NSLog(@"Trying to run command: %@", javaBinPath); NSLog(@"With I2P Base dir: %@", i2pRootPath); NSLog(@"And Arguments: %@", arrArguments); - startupRouter(nsJavaBin, arrArguments, nsBasePath); + startupRouter(nsJavaBin, arrArguments, nsBasePath, routerStatus); } catch (std::exception &err) { auto errMsg = [NSString stringWithUTF8String:err.what()]; NSLog(@"Exception: %@", errMsg); diff --git a/launchers/macosx/main.mm b/launchers/macosx/main.mm index 655704358..6b6fd9062 100644 --- a/launchers/macosx/main.mm +++ b/launchers/macosx/main.mm @@ -200,9 +200,6 @@ using namespace subprocess; shouldAutoStartRouter = true; } - if (self.enableVerboseLogging) NSLog(@"processinfo %@", [[NSProcessInfo processInfo] arguments]); - - NSBundle *launcherBundle = [NSBundle mainBundle]; // Helper object to hold statefull path information @@ -218,6 +215,23 @@ using namespace subprocess; std::string jarfile("-cp "); jarfile += [self.metaInfo.zipFile UTF8String]; + // Might be hard to read if you're not used to Objective-C + // But this is a "function call" that contains a "callback function" + [routerStatus listenForEventWithEventName:@"router_can_start" callbackActionFn:^(NSString* information) { + NSLog(@"Got signal, router can be started"); + [[SBridge sharedInstance] startupI2PRouter:self.metaInfo.i2pBase javaBinPath:self.metaInfo.javaBinary]; + }]; + + // This will trigger the router start after an upgrade. + [routerStatus listenForEventWithEventName:@"router_must_upgrade" callbackActionFn:^(NSString* information) { + NSLog(@"Got signal, router must be upgraded"); + [self extractI2PBaseDir:^(BOOL success, NSError *error) { + sendUserNotification(@"I2P is done extracting", @"I2P is now installed and ready to run!"); + NSLog(@"Done extracting I2P"); + [routerStatus triggerEventWithEn:@"router_can_start" details:@"upgrade complete"]; + }]; + }]; + // Initialize the Swift environment (the UI components) [self.swiftRuntime applicationDidFinishLaunching]; @@ -226,37 +240,16 @@ using namespace subprocess; { // I2P is not extracted. if (self.enableVerboseLogging) NSLog(@"I2P Directory don't exists!"); - - // Might be hard to read if you're not used to Objective-C - // But this is a "function call" that contains a "callback function" - [self extractI2PBaseDir:^(BOOL success, NSError *error) { - sendUserNotification(@"I2P is done extracting", @"I2P is now installed and ready to run!"); - NSLog(@"Done extracting I2P"); - - NSLog(@"Time to detect I2P version in install directory"); - [self.swiftRuntime findInstalledI2PVersion]; - if (shouldAutoStartRouter) { - [[SBridge sharedInstance] startupI2PRouter:self.metaInfo.i2pBase javaBinPath:self.metaInfo.javaBinary]; - [routerStatus setRouterRanByUs: true]; - } - }]; - + [routerStatus triggerEventWithEn:@"router_must_upgrade" details:@"deploy needed"]; } else { // I2P was already found extracted NSLog(@"Time to detect I2P version in install directory"); [self.swiftRuntime findInstalledI2PVersion]; - - if (shouldAutoStartRouter) { - [[SBridge sharedInstance] startupI2PRouter:self.metaInfo.i2pBase javaBinPath:self.metaInfo.javaBinary]; - [routerStatus setRouterRanByUs: true]; - } } #endif } - - /** * * Exit sequence diff --git a/launchers/macosx/src/main/java/net/i2p/launchers/BaseExtractor.java b/launchers/macosx/src/main/java/net/i2p/launchers/BaseExtractor.java index 9bbf12b2b..c7009cf17 100644 --- a/launchers/macosx/src/main/java/net/i2p/launchers/BaseExtractor.java +++ b/launchers/macosx/src/main/java/net/i2p/launchers/BaseExtractor.java @@ -13,6 +13,10 @@ import java.nio.file.Path; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; + +import static java.nio.file.Files.*; /** * As the name suggest, it extracts the base path. @@ -24,35 +28,39 @@ public class BaseExtractor extends EnvCheck { public boolean printDebug = false; - public void runExtract(String zipFilename) { - String destinationPath = this.baseDirPath; - try(ZipFile file = new ZipFile(zipFilename)) { - FileSystem fileSystem = FileSystems.getDefault(); - Enumeration entries = file.entries(); - - try { - Files.createDirectory(fileSystem.getPath(destinationPath)); - } catch (IOException e) { - // It's OK to fail here. + public void unzip(final Path zipFile) { + try { + String destinationPath = this.baseDirPath; + final Path destDir = Files.createDirectories(FileSystems.getDefault().getPath(destinationPath)); + if (notExists(destDir)) { + createDirectories(destDir); } - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - if (printDebug) System.out.println("Found entry: "+entry.toString()); - if (entry.isDirectory()) { - if (printDebug) System.out.println("Creating Directory:" + destinationPath + "/" + entry.getName()); - Files.createDirectories(fileSystem.getPath(destinationPath + "/" + entry.getName())); - } else { - InputStream is = file.getInputStream(entry); - BufferedInputStream bis = new BufferedInputStream(is); - String uncompressedFileName = destinationPath + "/" + entry.getName(); - Path uncompressedFilePath = fileSystem.getPath(uncompressedFileName); - Files.createFile(uncompressedFilePath); - BufferedOutputStream fileOutput = new BufferedOutputStream(new FileOutputStream(uncompressedFileName)); - while (bis.available() > 0) fileOutput.write(bis.read()); - fileOutput.close(); - if (printDebug) System.out.println("Written :" + entry.getName()); - } + try (FileSystem zipFileSystem = FileSystems.newFileSystem(zipFile, null)) { + final Path root = zipFileSystem.getRootDirectories().iterator().next(); + + walkFileTree(root, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, + BasicFileAttributes attrs) throws IOException { + final Path destFile = Paths.get(destDir.toString(), file.toString()); + try { + copy(file, destFile, StandardCopyOption.REPLACE_EXISTING); + } catch (DirectoryNotEmptyException ignore) { + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, + BasicFileAttributes attrs) throws IOException { + final Path dirToCreate = Paths.get(destDir.toString(), dir.toString()); + if (notExists(dirToCreate)) { + createDirectory(dirToCreate); + } + return FileVisitResult.CONTINUE; + } + }); } } catch (IOException e) { // @@ -72,6 +80,6 @@ public class BaseExtractor extends EnvCheck { if (debug != null) { be.printDebug = true; } - be.runExtract(System.getProperty("i2p.base.zip")); + be.unzip( FileSystems.getDefault().getPath(System.getProperty("i2p.base.zip")) ); } }