source: launchers/macosx/main.mm @ 3c0a8cf

Last change on this file since 3c0a8cf was 3c0a8cf, checked in by meeh <meeh@…>, 2 years ago

Mac OSX Launcher: A lot of bugfixes, refactoring and cleanup.

  • Property mode set to 100644
File size: 11.2 KB
Line 
1#include <functional>
2#include <memory>
3#include <iostream>
4#include <algorithm>
5#include <string>
6#include <list>
7#include <sys/stat.h>
8#include <stdlib.h>
9#include <future>
10#include <vector>
11
12#import <Foundation/Foundation.h>
13#import <Foundation/NSFileManager.h>
14
15
16#include <CoreFoundation/CoreFoundation.h>
17#include <CoreFoundation/CFStream.h>
18#include <CoreFoundation/CFPropertyList.h>
19#include <CoreFoundation/CFDictionary.h>
20#include <CoreFoundation/CFArray.h>
21#include <CoreFoundation/CFString.h>
22#include <CoreFoundation/CFPreferences.h>
23
24#import <objc/Object.h>
25#import <Cocoa/Cocoa.h>
26#import <AppKit/AppKit.h>
27#import <AppKit/NSApplication.h>
28
29#import "I2PLauncher-Swift.h"
30
31#include "AppDelegate.h"
32#include "RouterTask.h"
33#include "JavaHelper.h"
34#include "include/fn.h"
35#include "include/portcheck.h"
36#import "SBridge.h"
37
38#ifdef __cplusplus
39#include <string>
40
41#include "include/subprocess.hpp"
42#include "include/strutil.hpp"
43
44using namespace subprocess;
45
46#endif
47
48#define debug(format, ...) CFShow([NSString stringWithFormat:format, ## __VA_ARGS__]);
49
50@interface AppDelegate () <NSUserNotificationCenterDelegate, NSApplicationDelegate>
51@end
52
53
54@implementation ExtractMetaInfo : NSObject
55@end
56
57
58@implementation AppDelegate
59
60- (void) awakeFromNib {
61}
62
63
64- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center
65     shouldPresentNotification:(NSUserNotification *)notification {
66  return YES;
67}
68
69#ifdef __cplusplus
70
71- (void)extractI2PBaseDir:(void(^)(BOOL success, NSError *error))completion
72{
73 
74  NSBundle *launcherBundle = [NSBundle mainBundle];
75  auto homeDir = RealHomeDirectory();
76  NSLog(@"Home directory is %s", homeDir);
77 
78  std::string basePath(homeDir);
79  basePath.append("/Library/I2P");
80  auto jarResPath = [launcherBundle pathForResource:@"launcher" ofType:@"jar"];
81  NSLog(@"Trying to load launcher.jar from url = %@", jarResPath);
82  self.metaInfo.jarFile = jarResPath;
83  self.metaInfo.zipFile = [launcherBundle pathForResource:@"base" ofType:@"zip"];
84 
85  NSParameterAssert(basePath.c_str());
86  NSError *error = NULL;
87  BOOL success = NO;
88  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
89
90
91    try {
92      std::string basearg("-Di2p.dir.base=");
93      basearg += basePath;
94
95      std::string zippath("-Di2p.base.zip=");
96      zippath += [self.metaInfo.zipFile UTF8String];
97
98      std::string jarfile("-cp ");
99      jarfile += [self.metaInfo.jarFile UTF8String];
100
101      // Create directory
102      mkdir(basePath.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
103
104      auto cli = defaultFlagsForExtractorJob;
105      setenv("I2PBASE", basePath.c_str(), true);
106      setenv("ZIPPATH", zippath.c_str(), true);
107      //setenv("DYLD_LIBRARY_PATH",".:/usr/lib:/lib:/usr/local/lib", true);
108
109      cli.push_back(basearg);
110      cli.push_back(zippath);
111      cli.push_back(jarfile);
112      cli.push_back("net.i2p.launchers.BaseExtractor");
113      auto rs = [[RouterProcessStatus alloc] init];
114      NSString* jh = [rs getJavaHome];
115      if (jh != nil) {
116        NSLog(@"jh er %@", jh);
117      }
118     
119      NSString* newString = [NSString stringWithFormat:@"file://%@", rs.getJavaHome];
120      NSURL *baseURL = [NSURL fileURLWithPath:newString];
121     
122      std::string execStr = std::string([rs.getJavaHome UTF8String]);
123      for_each(cli, [&execStr](std::string str){ execStr += std::string(" ") + str; });
124
125      NSLog(@"Trying cmd: %@", [NSString stringWithUTF8String:execStr.c_str()]);
126      try {
127        sendUserNotification(APP_IDSTR, @"Please hold on while we extract I2P. You'll get a new message once done!");
128        int extractStatus = Popen(execStr.c_str(), environment{{
129          {"ZIPPATH", zippath.c_str()},
130          {"I2PBASE", basePath.c_str()}
131        }}).wait();
132        NSLog(@"Extraction exit code %@",[NSString stringWithUTF8String:(std::to_string(extractStatus)).c_str()]);
133        if (extractStatus == 0)
134        {
135          //success = YES;
136          NSLog(@"Time to detect I2P version in install directory");
137          [self.swiftRuntime findInstalledI2PVersion];
138        }
139     
140      } catch (subprocess::OSError &err) {
141          auto errMsg = [NSString stringWithUTF8String:err.what()];
142          //success = NO;
143          NSLog(@"Exception: %@", errMsg);
144          sendUserNotification(APP_IDSTR, [NSString stringWithFormat:@"Error: %@", errMsg]);
145      }
146
147      // All done. Assume success and error are already set.
148      dispatch_async(dispatch_get_main_queue(), ^{
149        //sendUserNotification(APP_IDSTR, @"Extraction complete!", self.contentImage);
150        if (completion) {
151          completion(success, error);
152        }
153      });
154     
155     
156    } catch (OSError &err) {
157      auto errMsg = [NSString stringWithUTF8String:err.what()];
158      NSLog(@"Exception: %@", errMsg);
159    }
160  });
161   
162 
163}
164
165
166- (NSString *)userSelectJavaHome:(JvmListPtr)rawJvmList
167{
168  NSString *appleScriptString = @"set jvmlist to {\"Newest\"";
169  for (auto item : *rawJvmList) {
170    auto str = strprintf(",\"%s\"", item->JVMName.c_str()).c_str();
171    NSString* tmp = [NSString stringWithUTF8String:str];
172    appleScriptString = [appleScriptString stringByAppendingString:tmp];
173  }
174  appleScriptString = [appleScriptString stringByAppendingString:@"}\nchoose from list jvmlist\n"];
175  NSAppleScript *theScript = [[NSAppleScript alloc] initWithSource:appleScriptString];
176  NSDictionary *theError = nil;
177  NSString* userResult = [[theScript executeAndReturnError: &theError] stringValue];
178  NSLog(@"User choosed %@.\n", userResult);
179  if (theError != nil)
180  {
181    NSLog(@"Error: %@.\n", theError);
182  }
183  return userResult;
184}
185
186- (void)setApplicationDefaultPreferences {
187  auto defaultJVMHome = check_output({"/usr/libexec/java_home","-v",DEF_MIN_JVM_VER});
188  auto tmpStdStr = std::string(defaultJVMHome.buf.data());
189  trim(tmpStdStr);
190  auto cfDefaultHome  = CFStringCreateWithCString(NULL, const_cast<const char *>(tmpStdStr.c_str()), kCFStringEncodingUTF8);
191  [self.userPreferences registerDefaults:@{
192    @"javaHome" : (NSString *)cfDefaultHome,
193    @"lastI2PVersion" : (NSString *)CFSTR(DEF_I2P_VERSION),
194    @"enableLogging": @YES,
195    @"enableVerboseLogging": @YES,
196    @"autoStartRouter": @YES,
197    @"i2pBaseDirectory": (NSString *)CFStringCreateWithCString(NULL, const_cast<const char *>(getDefaultBaseDir().c_str()), kCFStringEncodingUTF8)
198  }];
199  if (self.enableVerboseLogging) NSLog(@"Default JVM home preference set to: %@", cfDefaultHome);
200
201  auto dict = [self.userPreferences dictionaryRepresentation];
202  [self.userPreferences setPersistentDomain:dict forName:NSAPPDOMAIN];
203
204  CFPreferencesSetMultiple((CFDictionaryRef)dict, NULL, CFAPPDOMAIN, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
205  CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
206
207  if (self.enableVerboseLogging) NSLog(@"Default preferences stored!");
208#endif
209}
210
211
212
213
214- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
215  // Init application here
216 
217  self.swiftRuntime = [[SwiftMainDelegate alloc] init];
218 
219  // This setup allows the application to send notifications
220  [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
221 
222 
223  // Start with user preferences
224  self.userPreferences = [NSUserDefaults standardUserDefaults];
225  [self setApplicationDefaultPreferences];
226  self.enableLogging = [self.userPreferences boolForKey:@"enableLogging"];
227  self.enableVerboseLogging = [self.userPreferences boolForKey:@"enableVerboseLogging"];
228  // In case we are unbundled, make us a proper UI application
229  [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
230  [NSApp activateIgnoringOtherApps:YES];
231
232
233#ifdef __cplusplus
234  //gRawJvmList = std::make_shared<std::list<JvmVersionPtr> >(std::list<JvmVersionPtr>());
235
236  RouterProcessStatus* routerStatus = [[RouterProcessStatus alloc] init];
237  std::string i2pBaseDir(getDefaultBaseDir());
238  NSLog(@"i2pBaseDir = %s", i2pBaseDir.c_str());
239  bool shouldAutoStartRouter = false;
240 
241  // TODO: Make the port a setting which defaults to 7657
242  if (port_check(7657) != 0)
243  {
244    NSLog(@"Seems i2p is already running - I will not start the router (port 7657 is in use..)");
245    sendUserNotification(@"Found already running router", @"TCP port 7657 seem to be used by another i2p instance.");
246   
247    [routerStatus setRouterStatus: true];
248    [routerStatus setRouterRanByUs: false];
249    return;
250  } else {
251    shouldAutoStartRouter = true;
252  }
253
254  if (self.enableVerboseLogging) NSLog(@"processinfo %@", [[NSProcessInfo processInfo] arguments]);
255
256
257  NSBundle *launcherBundle = [NSBundle mainBundle];
258 
259  // Helper object to hold statefull path information
260  self.metaInfo = [[ExtractMetaInfo alloc] init];
261  self.metaInfo.i2pBase = [NSString stringWithUTF8String:i2pBaseDir.c_str()];
262  self.metaInfo.javaBinary = [routerStatus getJavaHome];
263  self.metaInfo.jarFile = [launcherBundle pathForResource:@"launcher" ofType:@"jar"];
264  self.metaInfo.zipFile = [launcherBundle pathForResource:@"base" ofType:@"zip"];
265
266  std::string basearg("-Di2p.dir.base=");
267  basearg += i2pBaseDir;
268
269  std::string jarfile("-cp ");
270  jarfile += [self.metaInfo.zipFile UTF8String];
271 
272  auto sBridge = [[SBridge alloc] init];
273 
274  // Initialize the Swift environment (the UI components)
275  [self.swiftRuntime applicationDidFinishLaunching];
276
277  struct stat sb;
278  if ( !(stat(i2pBaseDir.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) )
279  {
280    // I2P is not extracted.
281    if (self.enableVerboseLogging) NSLog(@"I2P Directory don't exists!");
282
283    // Might be hard to read if you're not used to Objective-C
284    // But this is a "function call" that contains a "callback function"
285    [self extractI2PBaseDir:^(BOOL success, NSError *error) {
286      sendUserNotification(@"I2P is done extracting", @"I2P is now installed and ready to run!");
287      NSLog(@"Done extracting I2P");
288     
289      if (shouldAutoStartRouter) {
290        [sBridge startupI2PRouter:self.metaInfo.i2pBase javaBinPath:self.metaInfo.javaBinary];
291        [routerStatus setRouterRanByUs: true];
292      }
293    }];
294
295  } else {
296    // I2P was already found extracted
297   
298    if (shouldAutoStartRouter) {
299      [sBridge startupI2PRouter:self.metaInfo.i2pBase javaBinPath:self.metaInfo.javaBinary];
300      [routerStatus setRouterRanByUs: true];
301    }
302  }
303 
304#endif
305}
306
307
308
309/**
310 *
311 * Exit sequence
312 *
313 **/
314- (void)applicationWillTerminate:(NSNotification *)aNotification {
315  // Tear down here
316  NSString *string = @"applicationWillTerminate executed";
317  NSLog(@"%@", string);
318  [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:nil];
319}
320
321
322/* wrapper for main */
323- (AppDelegate *)initWithArgc:(int)argc argv:(const char **)argv {
324  return self;
325}
326@end
327
328
329
330int main(int argc, const char **argv)
331{
332  NSApplication *app = [NSApplication sharedApplication];
333
334  AppDelegate *appDelegate = [[AppDelegate alloc] initWithArgc:argc argv:argv];
335  app.delegate = appDelegate;
336  auto mainBundle = [NSBundle mainBundle];
337  NSString* stringNameBundle = [mainBundle objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey];
338  if ([[NSRunningApplication runningApplicationsWithBundleIdentifier:[mainBundle bundleIdentifier]] count] > 1) {
339    [[NSAlert alertWithMessageText:[NSString stringWithFormat:@"Another copy of %@ is already running.",stringNameBundle]
340                     defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:@"This copy will now quit."] runModal];
341   
342    [NSApp terminate:nil];
343  }
344  [NSBundle loadNibNamed:@"I2Launcher" owner:NSApp];
345
346  [NSApp run];
347  return 0;
348}
349
350
351
Note: See TracBrowser for help on using the repository browser.