source: launchers/macosx/main.mm @ 281c5f57

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

Bugfixes, improvements, more error handling and stability to the osx launcher.

  • Property mode set to 100644
File size: 14.1 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
37#define debug(format, ...) CFShow([NSString stringWithFormat:format, ## __VA_ARGS__]);
38
39@interface AppDelegate () <NSUserNotificationCenterDelegate, NSApplicationDelegate>
40@end
41
42#ifdef __cplusplus
43#import "SBridge.h"
44JvmListSharedPtr gRawJvmList = nullptr;
45#endif
46
47
48@interface AppDelegate () <NSUserNotificationCenterDelegate, NSApplicationDelegate>
49@end
50
51#ifdef __cplusplus
52maybeAnRouterRunner getGlobalRouterObject()
53{
54    std::lock_guard<std::mutex> lock(globalRouterStatusMutex);
55    return globalRouterStatus; // Remember this might be nullptr now.
56}
57
58void setGlobalRouterObject(I2PRouterTask* newRouter)
59{
60    std::lock_guard<std::mutex> lock(globalRouterStatusMutex);
61    globalRouterStatus = newRouter;
62}
63
64
65pthread_mutex_t mutex;
66
67bool getGlobalRouterIsRunning()
68{
69    pthread_mutex_lock(&mutex);
70    bool current = isRuterRunning;
71    pthread_mutex_unlock(&mutex);
72    return current;
73}
74void setGlobalRouterIsRunning(bool running)
75{
76    pthread_mutex_lock(&mutex);
77    isRuterRunning = running;
78    pthread_mutex_unlock(&mutex);
79}
80
81#endif
82
83
84@implementation ExtractMetaInfo : NSObject
85@end
86
87#ifdef __cplusplus
88
89bool replace(std::string& str, const std::string& from, const std::string& to) {
90  size_t start_pos = str.find(from);
91  if(start_pos == std::string::npos)
92    return false;
93  str.replace(start_pos, from.length(), to);
94  return true;
95}
96
97#endif
98
99@implementation AppDelegate
100
101- (void) awakeFromNib {
102}
103
104#ifdef __cplusplus
105
106#include <unistd.h>
107#include <sys/types.h>
108#include <pwd.h>
109#include <assert.h>
110
111#include "include/subprocess.hpp"
112
113using namespace subprocess;
114
115const char* RealHomeDirectory() {
116  struct passwd *pw = getpwuid(getuid());
117  assert(pw);
118  return pw->pw_dir;
119}
120
121- (void)extractI2PBaseDir:(void(^)(BOOL success, NSError *error))completion
122{
123 
124  NSBundle *launcherBundle = [NSBundle mainBundle];
125  auto homeDir = RealHomeDirectory();
126  NSLog(@"Home directory is %s", homeDir);
127 
128  std::string basePath(homeDir);
129  basePath.append("/Library/I2P");
130  auto jarResPath = [launcherBundle pathForResource:@"launcher" ofType:@"jar"];
131  NSLog(@"Trying to load launcher.jar from url = %@", jarResPath);
132  self.metaInfo.jarFile = jarResPath;
133  self.metaInfo.zipFile = [launcherBundle pathForResource:@"base" ofType:@"zip"];
134 
135  NSParameterAssert(basePath.c_str());
136  NSError *error = NULL;
137  BOOL success = NO;
138  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
139
140
141    try {
142      std::string basearg("-Di2p.dir.base=");
143      basearg += basePath;
144
145      std::string zippath("-Di2p.base.zip=");
146      zippath += [self.metaInfo.zipFile UTF8String];
147
148      std::string jarfile("-cp ");
149      jarfile += [self.metaInfo.jarFile UTF8String];
150
151      // Create directory
152      mkdir(basePath.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
153
154      auto cli = JavaRunner::defaultFlagsForExtractorJob;
155      setenv("I2PBASE", basePath.c_str(), true);
156      setenv("ZIPPATH", zippath.c_str(), true);
157      //setenv("DYLD_LIBRARY_PATH",".:/usr/lib:/lib:/usr/local/lib", true);
158
159      cli.push_back(basearg);
160      cli.push_back(zippath);
161      cli.push_back(jarfile);
162      cli.push_back("net.i2p.launchers.BaseExtractor");
163      auto rs = [[RouterProcessStatus alloc] init];
164      NSString* jh = [rs getJavaHome];
165      if (jh != nil) {
166        NSLog(@"jh er %@", jh);
167      }
168     
169      NSString* newString = [NSString stringWithFormat:@"file://%@", rs.getJavaHome];
170      NSURL *baseURL = [NSURL fileURLWithPath:newString];
171     
172      NSLog(@"MEEH URL PATH: %s", [baseURL fileSystemRepresentation]);
173
174      auto charCli = map(cli, [](std::string str){ return str.c_str(); });
175      std::string execStr = std::string([rs.getJavaHome UTF8String]);
176      // TODO: Cheap hack, make it better.
177      replace(execStr, "Internet Plug-Ins", "Internet\\ Plug-Ins");
178      replace(execStr, "\n", "");
179      NSLog(@"Java path1 = %s", execStr.c_str());
180      [rs setJavaHome: [NSString stringWithFormat:@"%s", execStr.c_str()]];
181      for_each(cli, [&execStr](std::string str){ execStr += std::string(" ") + str; });
182     
183      //execStr = replace(execStr, "\\\\ ", "\\ ");
184      //NSLog(@"Java path2 = %s", execStr.c_str());
185
186      NSLog(@"Trying cmd: %@", [NSString stringWithUTF8String:execStr.c_str()]);
187      try {
188        sendUserNotification(APP_IDSTR, @"Please hold on while we extract I2P. You'll get a new message once done!");
189        int extractStatus = Popen(execStr.c_str(), environment{{
190          {"ZIPPATH", zippath.c_str()},
191          {"I2PBASE", basePath.c_str()}
192        }}).wait();
193        NSLog(@"Extraction exit code %@",[NSString stringWithUTF8String:(std::to_string(extractStatus)).c_str()]);
194        if (extractStatus == 0)
195        {
196          //success = YES;
197          NSLog(@"Time to detect I2P version in install directory");
198          [self.swiftRuntime findInstalledI2PVersion];
199        }
200     
201      } catch (subprocess::OSError &err) {
202          auto errMsg = [NSString stringWithUTF8String:err.what()];
203          //success = NO;
204          NSLog(@"Exception: %@", errMsg);
205          sendUserNotification(APP_IDSTR, [NSString stringWithFormat:@"Error: %@", errMsg]);
206      }
207
208      // All done. Assume success and error are already set.
209      dispatch_async(dispatch_get_main_queue(), ^{
210        //sendUserNotification(APP_IDSTR, @"Extraction complete!", self.contentImage);
211        if (completion) {
212          completion(success, error);
213        }
214      });
215     
216     
217    } catch (OSError &err) {
218      auto errMsg = [NSString stringWithUTF8String:err.what()];
219      NSLog(@"Exception: %@", errMsg);
220    }
221  });
222   
223 
224}
225
226#endif
227
228- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center
229                               shouldPresentNotification:(NSUserNotification *)notification {
230    return YES;
231}
232
233
234#ifdef __cplusplus
235
236inline std::string getDefaultBaseDir()
237{
238  // Figure out base directory
239  const char* pathFromHome = "/Users/%s/Library/I2P";
240  auto username = getenv("USER");
241  char buffer[strlen(pathFromHome)+strlen(username)];
242  sprintf(buffer, pathFromHome, username);
243  std::string i2pBaseDir(buffer);
244  return i2pBaseDir;
245}
246
247- (NSString *)userSelectJavaHome:(JvmListPtr)rawJvmList
248{
249  NSString *appleScriptString = @"set jvmlist to {\"Newest\"";
250  for (auto item : *rawJvmList) {
251    auto str = strprintf(",\"%s\"", item->JVMName.c_str()).c_str();
252    NSString* tmp = [NSString stringWithUTF8String:str];
253    appleScriptString = [appleScriptString stringByAppendingString:tmp];
254  }
255  appleScriptString = [appleScriptString stringByAppendingString:@"}\nchoose from list jvmlist\n"];
256  NSAppleScript *theScript = [[NSAppleScript alloc] initWithSource:appleScriptString];
257  NSDictionary *theError = nil;
258  NSString* userResult = [[theScript executeAndReturnError: &theError] stringValue];
259  NSLog(@"User choosed %@.\n", userResult);
260  if (theError != nil)
261  {
262    NSLog(@"Error: %@.\n", theError);
263  }
264  return userResult;
265}
266
267
268- (void)userChooseJavaHome {
269  listAllJavaInstallsAvailable();
270  std::shared_ptr<JvmHomeContext> appContext = std::shared_ptr<JvmHomeContext>( new JvmHomeContext() );
271  for (auto item : *appContext->getJvmList()) {
272    printf("JVM %s (Version: %s, Directory: %s)\n", item->JVMName.c_str(), item->JVMPlatformVersion.c_str(), item->JVMHomePath.c_str());
273  }
274  JvmListPtr rawJvmList = appContext->getJvmList();
275  NSString * userJavaHome = [self userSelectJavaHome: rawJvmList];
276  // TODO: Add logic so user can set preferred JVM
277}
278
279#endif
280
281- (void)setApplicationDefaultPreferences {
282  auto defaultJVMHome = check_output({"/usr/libexec/java_home","-v",DEF_MIN_JVM_VER});
283  auto tmpStdStr = std::string(defaultJVMHome.buf.data());
284  trim(tmpStdStr);
285  auto cfDefaultHome  = CFStringCreateWithCString(NULL, const_cast<const char *>(tmpStdStr.c_str()), kCFStringEncodingUTF8);
286  /*[self.userPreferences registerDefaults:@{
287    @"javaHome" : (NSString *)cfDefaultHome,
288    @"lastI2PVersion" : (NSString *)CFSTR(DEF_I2P_VERSION),
289    @"enableLogging": @YES,
290    @"enableVerboseLogging": @YES,
291    @"autoStartRouter": @YES,
292    @"i2pBaseDirectory": (NSString *)CFStringCreateWithCString(NULL, const_cast<const char *>(getDefaultBaseDir().c_str()), kCFStringEncodingUTF8)
293  }];*/
294  if (self.enableVerboseLogging) NSLog(@"Default JVM home preference set to: %@", cfDefaultHome);
295
296  auto dict = [self.userPreferences dictionaryRepresentation];
297  [self.userPreferences setPersistentDomain:dict forName:NSAPPDOMAIN];
298
299  CFPreferencesSetMultiple((CFDictionaryRef)dict, NULL, CFAPPDOMAIN, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
300  CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
301
302  if (self.enableVerboseLogging) NSLog(@"Default preferences stored!");
303}
304
305
306- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
307  // Init application here
308 
309  self.swiftRuntime = [[SwiftMainDelegate alloc] init];
310 
311  [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
312  // Start with user preferences
313  self.userPreferences = [NSUserDefaults standardUserDefaults];
314  [self setApplicationDefaultPreferences];
315  self.enableLogging = [self.userPreferences boolForKey:@"enableLogging"];
316  self.enableVerboseLogging = [self.userPreferences boolForKey:@"enableVerboseLogging"];
317
318
319#ifdef __cplusplus
320  gRawJvmList = std::make_shared<std::list<JvmVersionPtr> >(std::list<JvmVersionPtr>());
321#endif
322  // In case we are unbundled, make us a proper UI application
323  [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
324  [NSApp activateIgnoringOtherApps:YES];
325
326  // TODO: Also check for new installations from time to time.
327 
328#ifdef __cplusplus
329  auto javaHomePref = [self.userPreferences stringForKey:@"javaHome"];
330  if (self.enableVerboseLogging)
331  {
332    NSLog(@"Java home from preferences: %@", javaHomePref);
333  }
334
335  if (self.enableVerboseLogging)
336  {
337    NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
338    NSLog(@"Appdomain is: %@", appDomain);
339  }
340
341  NSLog(@"We should have started the statusbar object by now...");
342  RouterProcessStatus* routerStatus = [[RouterProcessStatus alloc] init];
343
344  std::string i2pBaseDir(getDefaultBaseDir());
345
346  auto pref = self.userPreferences;
347 
348  bool shouldAutoStartRouter = false;
349
350  if (port_check(7657) != 0)
351  {
352    NSLog(@"Seems i2p is already running - I will not start the router (port 7657 is in use..)");
353    sendUserNotification(@"Found already running router", @"TCP port 7657 seem to be used by another i2p instance.");
354   
355    [routerStatus setRouterStatus: true];
356    [routerStatus setRouterRanByUs: false];
357    return;
358  } else {
359    shouldAutoStartRouter = true;
360  }
361
362  if (self.enableVerboseLogging) NSLog(@"processinfo %@", [[NSProcessInfo processInfo] arguments]);
363
364  auto getJavaBin = [&pref,&self]() -> std::string {
365    // Get Java home
366    /*NSString* val = @"";
367    val = [pref stringForKey:@"javaHome"];
368    if (val == NULL) val = @"";
369    if (self.enableVerboseLogging) NSLog(@"Javahome: %@", val);
370    auto javaHome = std::string([val UTF8String]);
371    //trim(javaHome); // Trim to remove endline
372    auto javaBin = std::string(javaHome);
373    javaBin += "/bin/java"; // Append java binary to path.
374    return javaBin;*/
375    DetectJava *dt = [[DetectJava alloc] init];
376    [dt findIt];
377    if ([dt isJavaFound]) {
378      return [dt.javaHome UTF8String];
379    } else {
380      throw new std::runtime_error("Java home fatal error");
381    }
382  };
383
384
385  NSBundle *launcherBundle = [NSBundle mainBundle];
386 
387  auto jarResPath = [launcherBundle pathForResource:@"launcher" ofType:@"jar"];
388  NSLog(@"Trying to load launcher.jar from url = %@", jarResPath);
389   
390  self.metaInfo = [[ExtractMetaInfo alloc] init];
391  //self.metaInfo.i2pBase = [NSString stringWithUTF8String:i2pBaseDir.c_str()];
392  self.metaInfo.javaBinary = [NSString stringWithUTF8String:getJavaBin().c_str()];
393  self.metaInfo.jarFile = [launcherBundle pathForResource:@"launcher" ofType:@"jar"];
394  self.metaInfo.zipFile = [launcherBundle pathForResource:@"base" ofType:@"zip"];
395
396  std::string basearg("-Di2p.dir.base=");
397  //basearg += i2pBaseDir;
398
399  std::string jarfile("-cp ");
400  jarfile += [self.metaInfo.zipFile UTF8String];
401 
402
403  struct stat sb;
404  if ( !(stat(i2pBaseDir.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) )
405  {
406    // I2P is not extracted.
407    if (self.enableVerboseLogging) NSLog(@"I2P Directory don't exists!");
408
409    [self extractI2PBaseDir:^(BOOL success, NSError *error) {
410      sendUserNotification(@"I2P is done extracting", @"I2P is now installed and ready to run!");
411      [self.swiftRuntime applicationDidFinishLaunching];
412      NSLog(@"Done extracting I2P");
413      if (shouldAutoStartRouter) [self startupI2PRouter];
414    }];
415
416  } else {
417    if (self.enableVerboseLogging) NSLog(@"I2P directory found!");
418    if (shouldAutoStartRouter) [self startupI2PRouter];
419    [self.swiftRuntime applicationDidFinishLaunching];
420  }
421 
422#endif
423}
424
425
426
427/**
428 *
429 * Exit sequence
430 *
431 **/
432- (void)applicationWillTerminate:(NSNotification *)aNotification {
433  // Tear down here
434  NSString *string = @"applicationWillTerminate executed";
435  NSLog(@"%@", string);
436  [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:nil];
437}
438
439
440/* wrapper for main */
441- (AppDelegate *)initWithArgc:(int)argc argv:(const char **)argv {
442  return self;
443}
444@end
445
446
447
448int main(int argc, const char **argv)
449{
450  NSApplication *app = [NSApplication sharedApplication];
451  //NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
452
453  AppDelegate *appDelegate = [[AppDelegate alloc] initWithArgc:argc argv:argv];
454  app.delegate = appDelegate;
455  [NSBundle loadNibNamed:@"I2Launcher" owner:NSApp];
456
457  [NSApp run];
458  // Handle any errors
459  //[pool drain];
460  return 0;
461}
462
463
464
Note: See TracBrowser for help on using the repository browser.