1//
2//  Installer.m
3//  Keybase
4//
5//  Created by Gabriel on 11/23/15.
6//  Copyright © 2015 Keybase. All rights reserved.
7//
8
9#import "Installer.h"
10
11#import <KBKit/KBKit.h>
12#import "Options.h"
13#import "Uninstaller.h"
14
15@interface Installer ()
16@property Options *options;
17@property KBMemLogger *memLogger;
18@end
19
20typedef NS_ENUM (NSInteger, KBExit) {
21  KBExitOK = 0,
22  KBExitIgnoreError = 0,
23  KBExitError = 1,
24  KBExitFuseKextError = 4,
25  KBExitFuseKextPermissionError = 5,
26  KBExitAuthCanceledError = 6,
27  KBExitFuseKextMountsPresentError = 7,
28  KBExitFuseCriticalUpdate = 8,
29};
30
31@implementation Installer
32
33- (void)applicationDidFinishLaunching:(NSNotification *)notification {
34  dispatch_async(dispatch_get_main_queue(), ^{
35    [self run];
36  });
37}
38
39- (void)run {
40  // Check process arguments directly for debug (instead of GBCli) to setup logging right away
41  NSArray *arguments = [[NSProcessInfo processInfo] arguments];
42  BOOL debug = NO;
43  if ([arguments containsObject:@"--debug"]) {
44    debug = YES;
45  }
46#if DEBUG
47  debug = YES;
48#endif
49
50  [KBWorkspace setupLogging:debug];
51
52  _memLogger = [[KBMemLogger alloc] init];
53  [DDLog addLogger:_memLogger withLevel:DDLogLevelDebug];
54
55  [KBAppearance setCurrentAppearance:[KBUIAppearance appearance]];
56
57  DDLogDebug(@"Version: %@", NSBundle.mainBundle.infoDictionary[(NSString *)kCFBundleVersionKey]);
58
59  GBSettings *settings = [GBSettings settingsWithName:@"Settings" parent:nil];
60#if DEBUG
61  [settings setObject:@"/Applications/Keybase.app" forKey:@"app-path"];
62  //  [self.settings setObject:@"/Volumes/Keybase/Keybase.app" forKey:@"app-path"];
63  [settings setObject:@"prod" forKey:@"run-mode"];
64  [settings setObject:@"10" forKey:@"timeout"];
65#endif
66  _options = [[Options alloc] initWithSettings:settings];
67  NSError *parseError = nil;
68  if (![_options parseArgs:&parseError]) {
69    DDLogError(@"Error parsing: %@", parseError);
70    [self exit:KBExitError];
71    return;
72  }
73
74  if ([_options isUninstall]) {
75    [self uninstall];
76  } else {
77    [self install];
78  }
79}
80
81- (void)waitForLog {
82  dispatch_semaphore_t sema = dispatch_semaphore_create(0);
83  dispatch_async(DDLog.loggingQueue, ^{
84    dispatch_semaphore_signal(sema);
85  });
86  dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC));
87}
88
89- (void)exit:(KBExit)code {
90  [self waitForLog];
91  exit(code);
92}
93
94- (void)install {
95  [self install:^(NSError *error, KBEnvironment *environment, KBExit exitCode) {
96    dispatch_async(dispatch_get_main_queue(), ^{
97      DDLogInfo(@"Exit(%@)", @(exitCode));
98      [self exit:exitCode];
99    });
100  }];
101}
102
103- (void)uninstall {
104  [Uninstaller uninstallWithOptions:_options completion:^(NSError *error) {
105    if (error) {
106      DDLogError(@"Error uninstalling: %@", error);
107      [self exit:KBExitError];
108      return;
109    }
110    DDLogInfo(@"Uninstalled");
111    [self exit:KBExitOK];
112  }];
113}
114
115+ (instancetype)sharedDelegate {
116  return (Installer *)[NSApp delegate];
117}
118
119- (IBAction)quit:(id)sender {
120  [NSApplication.sharedApplication terminate:sender];
121}
122
123- (void)install:(void (^)(NSError *error, KBEnvironment *environment, KBExit exit))completion {
124  KBEnvironment *environment = [self.options environment];
125
126  KBInstaller *installer = [[KBInstaller alloc] init];
127  [installer installWithEnvironment:environment force:NO stopOnError:YES completion:^(NSError *error, NSArray *installables) {
128    [self checkError:error environment:environment completion:^(NSError *error, KBExit exit) {
129      completion(error, environment, exit);
130    }];
131  }];
132}
133
134- (void)checkError:(NSError *)error environment:(KBEnvironment *)environment completion:(void (^)(NSError *error, KBExit exit))completion {
135  if (!error) {
136    completion(nil, KBExitOK);
137    return;
138  }
139
140  if (error.code == KBErrorCodeFuseKextPermission) {
141    completion(nil, KBExitFuseKextPermissionError);
142    return;
143  }
144
145  if (error.code == KBErrorCodeFuseKext) {
146    completion(nil, KBExitFuseKextError);
147    return;
148  }
149
150  if (error.code == errAuthorizationCanceled) {
151    completion(nil, KBExitAuthCanceledError);
152    return;
153  }
154
155  if (error.code == KBErrorCodeFuseKextMountsPresent) {
156    completion(nil, KBExitFuseKextMountsPresentError);
157    return;
158  }
159
160  if (error.code == KBErrorCodeFuseCriticalUpdate) {
161    completion(nil, KBExitFuseCriticalUpdate);
162  }
163
164  completion(nil, KBExitError);
165}
166
167- (void)showErrorDialog:(NSError *)error environment:(KBEnvironment *)environment completion:(void (^)(NSError *error, KBExit exit))completion {
168  NSAlert *alert = [[NSAlert alloc] init];
169  [alert setMessageText:@"Keybase Error"];
170
171  NSString *info = error.localizedDescription;
172  if (![NSString gh_isBlank:error.localizedRecoverySuggestion]) {
173    info = NSStringWithFormat(@"%@\n\n%@", info, error.localizedRecoverySuggestion);
174  }
175
176  [alert setInformativeText:info];
177  [alert addButtonWithTitle:@"OK"];
178
179  NSURL *URL = error.userInfo[NSURLErrorKey];
180  if (URL) {
181    [alert addButtonWithTitle:@"Troubleshoot"];
182  } else {
183    [alert addButtonWithTitle:@"More Details"];
184  }
185
186  [alert setAlertStyle:NSWarningAlertStyle];
187  NSModalResponse response = [alert runModal];
188  if (response == NSAlertFirstButtonReturn) {
189    completion(error, KBExitIgnoreError);
190  } else if (response == NSAlertSecondButtonReturn) {
191    if (URL) {
192      [[NSWorkspace sharedWorkspace] openURL:URL];
193    } else {
194      [self showMoreDetails:error environment:environment completion:completion];
195    }
196  } else {
197    DDLogError(@"Unknown error dialog return button");
198    completion(error, KBExitIgnoreError);
199  }
200}
201
202- (void)showMoreDetails:(NSError *)error environment:(KBEnvironment *)environment completion:(void (^)(NSError *error, KBExit exit))completion {
203  NSAlert *alert = [[NSAlert alloc] init];
204  [alert setMessageText:@"Keybase Error"];
205  [alert setInformativeText:error.localizedDescription];
206  [alert addButtonWithTitle:@"OK"];
207
208  KBTextView *textView = [[KBTextView alloc] init];
209  textView.editable = NO;
210  textView.view.textContainerInset = CGSizeMake(5, 5);
211
212  NSMutableString *info = [NSMutableString stringWithString:[environment debugInstallables]];
213  if (_memLogger) {
214    [info appendString:@"Log:\n"];
215    [info appendString:[_memLogger messages]];
216  }
217  [textView setText:info style:KBTextStyleDefault options:KBTextOptionsMonospace|KBTextOptionsSmall alignment:NSLeftTextAlignment lineBreakMode:NSLineBreakByCharWrapping];
218
219  textView.frame = CGRectMake(0, 0, 500, 200);
220  textView.borderType = NSBezelBorder;
221  alert.accessoryView = textView;
222  [alert runModal];
223
224  completion(error, KBExitIgnoreError);
225}
226
227@end
228