From 452e5fb8e8fd1f22be98b26ece0010af09fb3d7d Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Sat, 24 Apr 2010 17:10:55 +0000 Subject: Bunch of improvements to the serve processes panel, including: - MCPKit's listProcesses (mysql_list_processes) method is now used, instead of running the query ourselves. - The ability to set whether or not the list is auto refreshed. - The ability to set the autorefresh interval, either via the presets or by entering a custom value (the interface for this might need updating). - Get the process list is now performed on a background thread. This completes the implementation of issue #607. --- Source/SPProcessListController.m | 277 ++++++++++++++++++++++++++++++++------- 1 file changed, 226 insertions(+), 51 deletions(-) (limited to 'Source/SPProcessListController.m') diff --git a/Source/SPProcessListController.m b/Source/SPProcessListController.m index 131b78d3..92744276 100644 --- a/Source/SPProcessListController.m +++ b/Source/SPProcessListController.m @@ -33,7 +33,13 @@ @interface SPProcessListController (PrivateAPI) -- (void)_getDatabaseProcessList; +- (void)_processListRefreshed; +- (void)_startAutoRefreshTimer; +- (void)_killAutoRefreshTimer; +- (void)_fireAutoRefresh:(NSTimer *)timer; +- (void)_updateSelectedAutoRefreshIntervalInterface; +- (void)_startAutoRefreshTimerWithInterval:(NSTimeInterval)interval; +- (void)_getDatabaseProcessListInBackground:(id)object; - (void)_killProcessQueryWithId:(NSUInteger)processId; - (void)_killProcessConnectionWithId:(NSUInteger)processId; - (void)_updateServerProcessesFilterForFilterString:(NSString *)filterString; @@ -44,18 +50,20 @@ @synthesize connection; +#pragma mark - +#pragma mark Initialisation + /** * Initialisation */ - (id)init { if ((self = [super initWithWindowNibName:@"DatabaseProcessList"])) { + + autoRefreshTimer = nil; processes = [[NSMutableArray alloc] init]; prefs = [NSUserDefaults standardUserDefaults]; - - // Default the process list comment to SHOW FULL PROCESSLIST - showFullProcessList = [prefs boolForKey:SPProcessListShowFullProcessList]; } return self; @@ -88,6 +96,15 @@ [prefs addObserver:self forKeyPath:SPUseMonospacedFonts options:NSKeyValueObservingOptionNew context:NULL]; } +/** + * Interface loading + */ +- (void)windowDidLoad +{ + // Update the selected auto refresh interval + [self _updateSelectedAutoRefreshIntervalInterface]; +} + #pragma mark - #pragma mark IBAction methods @@ -136,16 +153,27 @@ } /** - * Close the process list sheet. + * Close the current sheet */ -- (void)close +- (IBAction)closeSheet:(id)sender { - // If the filtered array is allocated and it's not a reference to the processes array get rid of it - if ((processesFiltered) && (processesFiltered != processes)) { - [processesFiltered release], processesFiltered = nil; + [NSApp endSheet:[sender window] returnCode:[sender tag]]; + [[sender window] orderOut:self]; +} + +/** + * If required start the auto refresh timer. + */ +- (void)showWindow:(id)sender +{ + // If the auto refresh option is enable start the timer + if ([prefs boolForKey:SPProcessListEnableAutoRefresh]) { + + // Start the auto refresh time but by pass the interface updates + [self _startAutoRefreshTimer]; } - [super close]; + [super showWindow:sender]; } /** @@ -165,24 +193,9 @@ [refreshProcessesButton setEnabled:NO]; [saveProcessesButton setEnabled:NO]; [filterProcessesSearchField setEnabled:NO]; - - [self _getDatabaseProcessList]; - - // Reapply any filters is required - if ([[filterProcessesSearchField stringValue] length] > 0) { - [self _updateServerProcessesFilterForFilterString:[filterProcessesSearchField stringValue]]; - } - - [processListTableView reloadData]; - - // Enable controls - [filterProcessesSearchField setEnabled:YES]; - [saveProcessesButton setEnabled:YES]; - [refreshProcessesButton setEnabled:YES]; - - // Stop progress Indicator - [refreshProgressIndicator stopAnimation:self]; - [refreshProgressIndicator setHidden:YES]; + + // Get the processes list on a background thread + [NSThread detachNewThreadSelector:@selector(_getDatabaseProcessListInBackground:) toTarget:self withObject:nil]; } /** @@ -256,21 +269,46 @@ } /** - * + * Toggles the display of the process ID table column. */ - (IBAction)toggleShowProcessID:(id)sender { [[processListTableView tableColumnWithIdentifier:TABLEVIEW_ID_COLUMN_IDENTIFIER] setHidden:([sender state])]; } +/** + * Toggles whether or not auto refresh is enabled. + */ +- (IBAction)toggleProcessListAutoRefresh:(id)sender +{ + BOOL enable = [sender state]; + + // Enable/Disable the refresh button + [refreshProcessesButton setEnabled:(!enable)]; + + (enable) ? [self _startAutoRefreshTimer] : [self _killAutoRefreshTimer]; +} + +/** + * Changes the auto refresh time interval based on the selected item + */ +- (IBAction)setAutoRefreshInterval:(id)sender +{ + [self _startAutoRefreshTimerWithInterval:[sender tag]]; +} + /** * */ -- (IBAction)toggeleShowFullProcessList:(id)sender +- (IBAction)setCustomAutoRefreshInterval:(id)sender { - showFullProcessList = (!showFullProcessList); - - [self refreshProcessList:self]; + [customIntervalTextField setStringValue:[prefs stringForKey:SPProcessListAutoRrefreshInterval]]; + + [NSApp beginSheet:customIntervalWindow + modalForWindow:[self window] + modalDelegate:self + didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) + contextInfo:nil]; } #pragma mark - @@ -295,19 +333,29 @@ - (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo { // Order out current sheet to suppress overlapping of sheets - if ([sheet respondsToSelector:@selector(orderOut:)]) + if ([sheet respondsToSelector:@selector(orderOut:)]) { [sheet orderOut:nil]; - else if ([sheet respondsToSelector:@selector(window)]) + } + else if ([sheet respondsToSelector:@selector(window)]) { [[sheet window] orderOut:nil]; + } if (returnCode == NSAlertDefaultReturn) { - NSUInteger processId = [[[processes objectAtIndex:[processListTableView selectedRow]] valueForKey:@"Id"] integerValue]; - if ([contextInfo isEqualToString:SPKillProcessQueryMode]) { - [self _killProcessQueryWithId:processId]; + if (sheet == customIntervalWindow) { + NSLog(@"Set custom value to: %d", [customIntervalTextField integerValue]); + + [self _startAutoRefreshTimerWithInterval:[customIntervalTextField integerValue]]; } - else if ([contextInfo isEqualToString:SPKillProcessConnectionMode]) { - [self _killProcessConnectionWithId:processId]; + else { + NSUInteger processId = [[[processes objectAtIndex:[processListTableView selectedRow]] valueForKey:@"Id"] integerValue]; + + if ([contextInfo isEqualToString:SPKillProcessQueryMode]) { + [self _killProcessQueryWithId:processId]; + } + else if ([contextInfo isEqualToString:SPKillProcessConnectionMode]) { + [self _killProcessConnectionWithId:processId]; + } } } } @@ -357,6 +405,10 @@ return ([processListTableView numberOfSelectedRows] == 1); } + if ((action == @selector(setAutoRefreshInterval:)) || (action == @selector(setCustomAutoRefreshInterval:))) { + return [prefs boolForKey:SPProcessListEnableAutoRefresh]; + } + return YES; } @@ -425,6 +477,26 @@ if (object == filterProcessesSearchField) { [self _updateServerProcessesFilterForFilterString:[object stringValue]]; } + else if (object == customIntervalTextField) { + [customIntervalButton setEnabled:(([[customIntervalTextField stringValue] length] > 0) && ([customIntervalTextField integerValue] > 0))]; + } +} + +#pragma mark - +#pragma mark Window delegate methods + +/** + * Kill the auto refresh timer if it's running. + */ +- (void)windowWillClose:(NSNotification *)notification +{ + // If the filtered array is allocated and it's not a reference to the processes array get rid of it + if ((processesFiltered) && (processesFiltered != processes)) { + [processesFiltered release], processesFiltered = nil; + } + + // Kill the auto refresh timer if running + [self _killAutoRefreshTimer]; } #pragma mark - @@ -438,22 +510,120 @@ [processes release], processes = nil; + if (autoRefreshTimer) [autoRefreshTimer release], autoRefreshTimer = nil; + [super dealloc]; } -@end +#pragma mark - +#pragma mark Private API + +/** + * Called by the background thread on the main thread once it has completed getting the list of processes. + */ +- (void)_processListRefreshed +{ + // Reapply any filters is required + if ([[filterProcessesSearchField stringValue] length] > 0) { + [self _updateServerProcessesFilterForFilterString:[filterProcessesSearchField stringValue]]; + } + + [processListTableView reloadData]; + + // Enable controls + [filterProcessesSearchField setEnabled:YES]; + [saveProcessesButton setEnabled:YES]; + [refreshProcessesButton setEnabled:(![autoRefreshButton state])]; + + // Stop progress Indicator + [refreshProgressIndicator stopAnimation:self]; + [refreshProgressIndicator setHidden:YES]; +} + +/** + * Starts the auto refresh timer. + */ +- (void)_startAutoRefreshTimer +{ + autoRefreshTimer = [[NSTimer scheduledTimerWithTimeInterval:[prefs doubleForKey:SPProcessListAutoRrefreshInterval] target:self selector:@selector(_fireAutoRefresh:) userInfo:nil repeats:YES] retain]; +} + +/** + * Kills the auto refresh timer. + */ +- (void)_killAutoRefreshTimer +{ + // If the auto refresh timer is running, kill it + if (autoRefreshTimer && [autoRefreshTimer isValid]) { + [autoRefreshTimer invalidate]; + [autoRefreshTimer release], autoRefreshTimer = nil; + } +} -@implementation SPProcessListController (PrivateAPI) +/** + * Refreshes the process list when called by the auto refesh timer. + */ +- (void)_fireAutoRefresh:(NSTimer *)timer +{ + [self refreshProcessList:self]; +} /** - * Gets the current process list form the database; + * */ -- (void)_getDatabaseProcessList +- (void)_updateSelectedAutoRefreshIntervalInterface +{ + BOOL found = NO; + NSUInteger interval = [prefs integerForKey:SPProcessListAutoRrefreshInterval]; + + NSArray *items = [[autoRefreshIntervalMenuItem submenu] itemArray]; + + // Uncheck all items + for (NSMenuItem *item in items) + { + [item setState:NSOffState]; + } + + // Check the selected item + for (NSMenuItem *item in items) + { + if (interval == [item tag]) { + found = YES; + [item setState:NSOnState]; + break; + } + } + + // If a match wasn't found then a custom value is set + if (!found) [[items objectAtIndex:([items count] - 1)] setState:NSOnState]; +} + +/** + * Starts the auto refresh time with the supplied time interval. + */ +- (void)_startAutoRefreshTimerWithInterval:(NSTimeInterval)interval { + [prefs setDouble:interval forKey:SPProcessListAutoRrefreshInterval]; + + // Update the interface + [self _updateSelectedAutoRefreshIntervalInterface]; + + // Kill the timer and restart it with the new interval + [self _killAutoRefreshTimer]; + [self _startAutoRefreshTimer]; +} + +/** + * Gets a list of current database processed on a background thread. + */ +- (void)_getDatabaseProcessListInBackground:(id)object; +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSUInteger i = 0; // Get processes - MCPResult *processList = [connection queryString:(showFullProcessList) ? @"SHOW FULL PROCESSLIST" : @"SHOW PROCESSLIST"]; + MCPResult *processList = [connection listProcesses]; [processList setReturnDataAsStrings:YES]; @@ -465,6 +635,11 @@ { [processes addObject:[processList fetchRowAsDictionary]]; } + + // Update the UI on the main thread + [self performSelectorOnMainThread:@selector(_processListRefreshed) withObject:nil waitUntilDone:NO]; + + [pool release]; } /** @@ -536,13 +711,13 @@ // Perform filtering for (NSDictionary *process in processes) { - if (([[process objectForKey:@"Id"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || - ([[process objectForKey:@"User"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || - ([[process objectForKey:@"Host"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || - ((![[process objectForKey:@"db"] isNSNull]) && ([[process objectForKey:@"db"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound)) || + if (([[process objectForKey:@"Id"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || + ([[process objectForKey:@"User"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || + ([[process objectForKey:@"Host"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || + ((![[process objectForKey:@"db"] isNSNull]) && ([[process objectForKey:@"db"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound)) || ([[process objectForKey:@"Command"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || - ([[process objectForKey:@"Time"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || - ((![[process objectForKey:@"State"] isNSNull]) && ([[process objectForKey:@"State"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound)) || + ([[process objectForKey:@"Time"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound) || + ((![[process objectForKey:@"State"] isNSNull]) && ([[process objectForKey:@"State"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound)) || ((![[process objectForKey:@"Info"] isNSNull]) && ([[process objectForKey:@"Info"] rangeOfString:filterString options:NSCaseInsensitiveSearch].location != NSNotFound))) { [processesFiltered addObject:process]; -- cgit v1.2.3