Skip to content
Snippets Groups Projects
Commit 0d80e73f authored by Cam Saul's avatar Cam Saul Committed by GitHub
Browse files

Merge pull request #6116 from metabase/mac-app-improvements

Major Mac App Improvements
parents c2cba7f6 8bfaaf38
No related branches found
No related tags found
No related merge requests found
......@@ -537,6 +537,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
......@@ -569,6 +570,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.9;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
VALID_ARCHS = x86_64;
};
name = Debug;
};
......@@ -576,6 +578,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
......@@ -601,7 +604,9 @@
GCC_WARN_UNUSED_VARIABLE = YES;
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.9;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
VALID_ARCHS = x86_64;
};
name = Release;
};
......
......@@ -14,4 +14,7 @@
@property (readonly) NSUInteger port;
- (void)stopMetabaseTask;
- (void)startMetabaseTask;
@end
......@@ -33,50 +33,35 @@ static AppDelegate *sInstance = nil;
return sInstance;
}
- (id)init {
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskTimedOut:) name:MetabaseTaskTimedOutNotification object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
sInstance = self;
[[SUUpdater sharedUpdater] checkForUpdatesInBackground];
self.task = [MetabaseTask task];
self.healthChecker.port = self.task.port;
[self startMetabaseTask];
[self.healthChecker start];
}
- (void)applicationDidBecomeActive:(NSNotification *)notification {
// re-start the health checker if it's not checking like it should be : the HEALTH CHECKER HEALTH CHECKER
if (self.healthChecker.lastCheckTime) {
const CFTimeInterval timeSinceLastHealthCheck = CFAbsoluteTimeGetCurrent() - self.healthChecker.lastCheckTime;
if (timeSinceLastHealthCheck > 5.0f) {
NSLog(@"Last health check was %.0f ago, restarting health checker!", timeSinceLastHealthCheck);
[self.healthChecker start];
}
}
// (re)start the health checker just to be extra double-safe it's still running
[self.healthChecker start];
}
- (void)applicationWillTerminate:(NSNotification *)notification {
self.task = nil;
[self stopMetabaseTask];
}
#pragma mark - Notifications
#pragma mark - Static Methods
- (void)startMetabaseTask {
self.task = [MetabaseTask task];
self.healthChecker.port = self.task.port;
}
- (void)taskTimedOut:(NSNotification *)notification {
NSLog(@"Metabase task timed out. Restarting...");
[self.healthChecker resetTimeout];
self.task = [MetabaseTask task];
- (void)stopMetabaseTask {
[self.task disableTerminationAlert];
self.task = nil;
}
......@@ -92,7 +77,9 @@ static AppDelegate *sInstance = nil;
- (void)setTask:(MetabaseTask *)task {
[_task terminate];
_task = task;
[task launch];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[task launch];
});
}
- (NSUInteger)port {
......
......@@ -16,6 +16,10 @@
- (void)launch;
/// Remove the task termination handler that pops up the 'Task died unexpectedly' alert.
/// For cases when we want to kill the Metabase task without freaking the user out, e.g. for Reset Password
- (void)disableTerminationAlert;
- (NSUInteger)port;
@end
......@@ -11,8 +11,11 @@
@interface MetabaseTask ()
@property (nonatomic) NSUInteger port;
@property (nonatomic, strong) NSMutableArray *lastMessages; ///< 10 most recently logged messages.
@end
@implementation MetabaseTask
+ (MetabaseTask *)task {
......@@ -34,6 +37,12 @@
regex = [NSRegularExpression regularExpressionWithPattern:@"\\[\\d+m" options:0 error:nil];
message = [regex stringByReplacingMatchesInString:message options:0 range:NSMakeRange(0, message.length) withTemplate:@""];
// add the message to the recently logged messages. If we now have more than 5 remove the oldest
[self.lastMessages addObject:message];
if (self.lastMessages.count > 10) [self.lastMessages removeObjectAtIndex:0];
// log the message the normal way as well
NSLog(@"%@", message);
}
......@@ -63,7 +72,7 @@
self.task = [[NSTask alloc] init];
self.task.launchPath = JREPath();
self.task.environment = @{@"MB_DB_FILE": [DBPath() stringByAppendingString:@";AUTO_SERVER=TRUE"],
self.task.environment = @{@"MB_DB_FILE": DBPath(),
@"MB_PLUGINS_DIR": PluginsDirPath(),
@"MB_JETTY_PORT": @(self.port),
@"MB_CLIENT": @"OSX"};
......@@ -74,9 +83,15 @@
__weak MetabaseTask *weakSelf = self;
self.task.terminationHandler = ^(NSTask *task){
NSLog(@"\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Task terminated with exit code %d !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", task.terminationStatus);
NSLog(@"\n\n!!!!! Task terminated with exit code %d !!!!!\n\n", task.terminationStatus);
dispatch_async(dispatch_get_main_queue(), ^{
if ([[NSAlert alertWithMessageText:@"Fatal Error" defaultButton:@"Restart" alternateButton:@"Quit" otherButton:nil informativeTextWithFormat:@"The Metabase server terminated unexpectedly."] runModal] == NSAlertDefaultReturn) {
if ([[NSAlert alertWithMessageText:@"Fatal Error"
defaultButton:@"Restart"
alternateButton:@"Quit"
otherButton:nil
informativeTextWithFormat:@"The Metabase server terminated unexpectedly.\n\nMessages:\n%@", [weakSelf.lastMessages componentsJoinedByString:@""]] // components should already have newline at end
runModal] == NSAlertDefaultReturn) {
[weakSelf launch];
} else {
exit(task.terminationStatus);
......@@ -94,6 +109,10 @@
_port = 0;
}
- (void)disableTerminationAlert {
self.task.terminationHandler = nil;
}
#pragma mark - Getters / Setters
......@@ -105,5 +124,11 @@
}
return _port;
}
- (NSMutableArray *)lastMessages {
if (!_lastMessages) _lastMessages = [NSMutableArray array];
return _lastMessages;
}
@end
......@@ -6,6 +6,7 @@
// Copyright (c) 2015 Metabase. All rights reserved.
//
#import "AppDelegate.h"
#import "ResetPasswordTask.h"
@interface ResetPasswordTask ()
......@@ -19,28 +20,29 @@
- (void)resetPasswordForEmailAddress:(NSString *)emailAddress success:(void (^)(NSString *))successBlock error:(void (^)(NSString *))errorBlock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// first, we need to stop the main Metabase task so we can access the DB
NSLog(@"Stopping Metabase task in order to reset password...");
[[AppDelegate instance] stopMetabaseTask];
self.task = [[NSTask alloc] init];
NSString *dbPath = [DBPath() stringByAppendingString:@";IFEXISTS=TRUE;AUTO_SERVER=TRUE"];
self.task.environment = @{@"MB_DB_FILE": dbPath, @"HOME": @"/Users/camsaul"};
// time travelers from the future: this is hardcoded since I'm the only one who works on this. I give you permission to fix it - Cam
#define DEBUG_RUN_LEIN_TASK 0
#if DEBUG_RUN_LEIN_TASK
self.task.environment = @{@"MB_DB_FILE": dbPath};
self.task.environment = @{@"MB_DB_FILE": DBPath()};
self.task.currentDirectoryPath = @"/Users/cam/metabase";
self.task.launchPath = @"/usr/local/bin/lein";
self.task.arguments = @[@"run", @"reset-password", emailAddress];
NSLog(@"Launching ResetPasswordTask\nMB_DB_FILE='%@' lein run reset-password %@", dbPath, emailAddress);
NSLog(@"Launching ResetPasswordTask\nMB_DB_FILE='%@' lein run reset-password %@", DBPath(), emailAddress);
#else
self.task.environment = @{@"MB_DB_FILE": dbPath};
self.task.environment = @{@"MB_DB_FILE": DBPath()};
self.task.launchPath = JREPath();
self.task.arguments = @[@"-Djava.awt.headless=true", // this prevents the extra java icon from popping up in the dock when running
@"-Xverify:none", // disable bytecode verification for faster launch speed, not really needed here since JAR is packaged as part of signed .app
@"-jar", UberjarPath(),
@"reset-password", emailAddress];
NSLog(@"Launching ResetPasswordTask\nMB_DB_FILE='%@' %@ -jar %@ reset-password %@", dbPath, JREPath(), UberjarPath(), emailAddress);
NSLog(@"Launching ResetPasswordTask\nMB_DB_FILE='%@' %@ -jar %@ reset-password %@", DBPath(), JREPath(), UberjarPath(), emailAddress);
#endif
__weak ResetPasswordTask *weakSelf = self;
......@@ -54,6 +56,10 @@
} else {
errorBlock(weakSelf.output.length ? weakSelf.output : @"An unknown error has occured.");
}
// now restart the main Metabase task
NSLog(@"Reset password complete, restarting Metabase task...");
[[AppDelegate instance] startMetabaseTask];
});
};
......
......@@ -8,17 +8,14 @@
static NSString * const MetabaseTaskBecameHealthyNotification = @"MetabaseTaskBecameHealthyNotification";
static NSString * const MetabaseTaskBecameUnhealthyNotification = @"MetabaseTaskBecameUnhealthyNotification";
static NSString * const MetabaseTaskTimedOutNotification = @"MetabaseTaskTimedOutNotification";
/// Manages the MetabaseTask (server) and restarts it if it gets unresponsive
@interface TaskHealthChecker : NSObject
@property () NSUInteger port;
/// (re)start the health checker
- (void)start;
- (void)stop;
- (void)resetTimeout;
- (CFAbsoluteTime)lastCheckTime;
@end
......@@ -9,21 +9,16 @@
#import "TaskHealthChecker.h"
/// Check out health every this many seconds
static const CGFloat HealthCheckIntervalSeconds = 2.0f;
static const CGFloat HealthCheckIntervalSeconds = 1.0f;
/// This number should be lower than HealthCheckIntervalSeconds so requests don't end up piling up
static const CGFloat HealthCheckRequestTimeout = 1.75f;
/// After this many seconds of being unhealthy, consider the task timed out so it can be killed
static const CFTimeInterval TimeoutIntervalSeconds = 60.0f;
@interface TaskHealthChecker ()
@property (strong, nonatomic) NSOperationQueue *healthCheckOperationQueue;
@property (strong, nonatomic) NSTimer *healthCheckTimer;
@property (nonatomic) BOOL healthy;
@property CFAbsoluteTime lastHealthyTime;
@property CFAbsoluteTime lastCheckTime;
/// Set this to YES after server has started successfully one time
/// we'll hold of on the whole killing the Metabase server until it launches one time, I guess
......@@ -39,17 +34,11 @@ static const CFTimeInterval TimeoutIntervalSeconds = 60.0f;
#pragma mark - Local Methods
- (void)resetTimeout {
self.lastHealthyTime = CFAbsoluteTimeGetCurrent();
}
- (void)start {
NSLog(@"(re)starting health checker @ 0x%zx...", (size_t)self);
self.healthCheckOperationQueue = [[NSOperationQueue alloc] init];
self.healthCheckOperationQueue.maxConcurrentOperationCount = 1;
[self resetTimeout];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
self.healthCheckTimer = [NSTimer timerWithTimeInterval:HealthCheckIntervalSeconds target:self selector:@selector(checkHealth) userInfo:nil repeats:YES];
self.healthCheckTimer.tolerance = HealthCheckIntervalSeconds / 2.0f;
......@@ -64,8 +53,6 @@ static const CFTimeInterval TimeoutIntervalSeconds = 60.0f;
}
- (void)checkHealth:(void(^)(BOOL healthy))completion {
self.lastCheckTime = CFAbsoluteTimeGetCurrent();
/// Cancel any pending checks so they don't pile up indefinitely
[self.healthCheckOperationQueue cancelAllOperations];
......@@ -117,29 +104,11 @@ static const CFTimeInterval TimeoutIntervalSeconds = 60.0f;
}
- (void)setHealthy:(BOOL)healthy {
if (healthy) {
self.lastHealthyTime = CFAbsoluteTimeGetCurrent();
} else {
const CFTimeInterval timeSinceWasLastHealthy = CFAbsoluteTimeGetCurrent() - self.lastHealthyTime;
if (timeSinceWasLastHealthy >= TimeoutIntervalSeconds) {
__weak TaskHealthChecker *weakSelf = self;
if (!self.hasEverBeenHealthy) {
NSLog(@"We've been waiting %.0f seconds, what's going on?", timeSinceWasLastHealthy);
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:MetabaseTaskTimedOutNotification object:weakSelf];
}
}
if (_healthy == healthy) return;
_healthy = healthy;
NSLog(@"\n\n"
"+--------------------------------------------------------------------+\n"
"| Server status has transitioned to: %@ |\n"
"+--------------------------------------------------------------------+\n\n", (healthy ? @"HEALTHY " : @"NOT HEALTHY"));
NSLog(@"Server is now %@", healthy ? @"HEALTHY" : @"UNHEALTHY");
__weak TaskHealthChecker *weakSelf = self;
NSString *notification = healthy ? MetabaseTaskBecameHealthyNotification : MetabaseTaskBecameUnhealthyNotification;
[[NSNotificationCenter defaultCenter] postNotificationName:notification object:weakSelf];
......
......@@ -38,6 +38,8 @@ NSString *BaseURL() {
@property (nonatomic) BOOL loading;
@property (nonatomic, strong) NSString *launchRoute; ///< redirect to this URL on launch if set. Used for password reset to take you to reset password page.
@end
@implementation MainViewController
......@@ -91,7 +93,14 @@ NSString *BaseURL() {
- (void)taskBecameHealthy:(NSNotification *)notification {
dispatch_async(dispatch_get_main_queue(), ^{
[self loadMainPage];
if (self.launchRoute) {
[self navigateToRoute:self.launchRoute];
self.launchRoute = nil;
} else {
[self loadMainPage];
}
dispatch_async(dispatch_get_main_queue(), ^{
self.loading = NO;
});
......@@ -107,12 +116,17 @@ NSString *BaseURL() {
#pragma mark - Local Methods
- (void)navigateToRoute:(nonnull NSString *)route {
NSString *urlString = [BaseURL() stringByAppendingString:route];
NSLog(@"Connecting to Metabase instance, navigating to page: %@", urlString);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
request.cachePolicy = NSURLCacheStorageAllowedInMemoryOnly;
[self.webView.mainFrame loadRequest:request];
}
- (void)loadMainPage {
NSLog(@"Connecting to Metabase instance at: %@", BaseURL());
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:BaseURL()]];
request.cachePolicy = NSURLCacheStorageAllowedInMemoryOnly;
[self.webView.mainFrame loadRequest:request];
[self navigateToRoute:@"/"];
}
- (void)downloadWithMethod:(NSString *)methodString url:(NSString *)urlString params:(NSDictionary *)paramsDict extensions:(NSArray *)extensions {
......@@ -251,12 +265,9 @@ NSString *BaseURL() {
- (void)resetPasswordWindowController:(ResetPasswordWindowController *)resetPasswordWindowController didFinishWithResetToken:(NSString *)resetToken {
self.resetPasswordWindowController = nil;
NSString *passwordResetURLString = [NSString stringWithFormat:@"%@/auth/reset_password/%@", BaseURL(), resetToken];
NSLog(@"Navigating to password reset URL '%@'...", passwordResetURLString);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:passwordResetURLString]];
[self.webView.mainFrame loadRequest:request];
// now tell the app to reroute to the reset password page once Metabase relauches
self.launchRoute = [@"/auth/reset_password/" stringByAppendingString:resetToken];
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment