Mac OS X Launcher:

* Bugfixes as always
  * Added Sparkle (native updater, https://sparkle-project.org/ )
  * The launcher will now extract and overwrite older versions if found
  * Rewrite of the java extraction part (to enable overwrite)
  * Move more functionality to use EventManager as it works quite well
  * Added check for updates menu item
This commit is contained in:
meeh
2018-09-23 03:33:29 +00:00
parent 3b38f5a161
commit 7a72049e28
15 changed files with 109 additions and 153 deletions

View File

@ -1,7 +1,8 @@
<?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">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14313.18"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@ -36,5 +37,6 @@
</items>
<point key="canvasLocation" x="17" y="167"/>
</menu>
<customObject id="xTS-ll-kWI" customClass="SUUpdater"/>
</objects>
</document>

View File

@ -5,4 +5,5 @@
#import "AppleStuffExceptionHandler.h"
#import "AppDelegate.h"
#import "RouterTask.h"
#import "Sparkle/SUUpdater.h"

View File

@ -17,24 +17,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<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>
<string>0.1.1</string>
<key>CFBundleVersion</key>
<string>4</string>
<string>5</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
@ -54,6 +39,8 @@
<dict/>
</array>
<key>SUFeedURL</key>
<string>http://i2browser.i2p/updates/v1/appcast.xml</string>
<string>https://download.i2p2.de/macosx/sparkle/updates/v1/appcast.xml</string>
<key>SUPublicEDKey</key>
<string>weKSpHXfJzk+5qy3UVfqsUwTeLnT9WCFVMwd9yW0+DA=</string>
</dict>
</plist>

View File

@ -154,6 +154,7 @@
</tabView>
</viewController>
<customObject id="d8g-wS-Zts" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<customObject id="4gn-BI-uSC" userLabel="Updater" customClass="SUUpdater"/>
</objects>
<point key="canvasLocation" x="-823" y="166"/>
</scene>

View File

@ -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) {

View File

@ -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")

View File

@ -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"

View File

@ -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...")

View File

@ -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")

View File

@ -39,15 +39,6 @@ const std::vector<std::string> 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;

View File

@ -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;

View File

@ -18,7 +18,7 @@
#include <string>
#include <vector>
#include "include/fn.h"
std::future<int> startupRouter(NSString* javaBin, NSArray<NSString*>* arguments, NSString* i2pBaseDir);
//std::future<int> startupRouter(NSString* javaBin, NSArray<NSString*>* arguments, NSString* i2pBaseDir, RouterProcessStatus* routerStatus = nil);
namespace osx {

View File

@ -26,7 +26,7 @@
std::future<int> startupRouter(NSString* javaBin, NSArray<NSString*>* arguments, NSString* i2pBaseDir) {
std::future<int> startupRouter(NSString* javaBin, NSArray<NSString*>* arguments, NSString* i2pBaseDir, RouterProcessStatus* routerStatus) {
@try {
RTaskOptions* options = [RTaskOptions alloc];
options.binPath = javaBin;
@ -36,12 +36,16 @@ std::future<int> startupRouter(NSString* javaBin, NSArray<NSString*>* 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<int> startupRouter(NSString* javaBin, NSArray<NSString*>* 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<int> startupRouter(NSString* javaBin, NSArray<NSString*>* 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<int> startupRouter(NSString* javaBin, NSArray<NSString*>* 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);

View File

@ -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

View File

@ -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<? extends ZipEntry> 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<Path>() {
@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")) );
}
}